import art from a previously exported library
[vuplus_xbmc] / xbmc / video / VideoDatabase.cpp
1 /*
2  *      Copyright (C) 2005-2012 Team XBMC
3  *      http://www.xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "threads/SystemClock.h"
22 #include "VideoDatabase.h"
23 #include "video/windows/GUIWindowVideoBase.h"
24 #include "utils/RegExp.h"
25 #include "addons/AddonManager.h"
26 #include "GUIInfoManager.h"
27 #include "Util.h"
28 #include "utils/URIUtils.h"
29 #include "utils/XMLUtils.h"
30 #include "GUIPassword.h"
31 #include "filesystem/StackDirectory.h"
32 #include "filesystem/MultiPathDirectory.h"
33 #include "VideoInfoScanner.h"
34 #include "guilib/GUIWindowManager.h"
35 #include "filesystem/Directory.h"
36 #include "filesystem/File.h"
37 #include "filesystem/SpecialProtocol.h"
38 #include "dialogs/GUIDialogExtendedProgressBar.h"
39 #include "dialogs/GUIDialogProgress.h"
40 #include "dialogs/GUIDialogYesNo.h"
41 #include "FileItem.h"
42 #include "settings/AdvancedSettings.h"
43 #include "settings/GUISettings.h"
44 #include "settings/Settings.h"
45 #include "utils/StringUtils.h"
46 #include "guilib/LocalizeStrings.h"
47 #include "utils/TimeUtils.h"
48 #include "utils/log.h"
49 #include "TextureCache.h"
50 #include "addons/AddonInstaller.h"
51 #include "interfaces/AnnouncementManager.h"
52 #include "dbwrappers/dataset.h"
53 #include "utils/LabelFormatter.h"
54 #include "XBDateTime.h"
55 #include "URL.h"
56 #include "video/VideoDbUrl.h"
57 #include "playlists/SmartPlayList.h"
58 #include "utils/GroupUtils.h"
59
60 using namespace std;
61 using namespace dbiplus;
62 using namespace XFILE;
63 using namespace VIDEO;
64 using namespace ADDON;
65
66 //********************************************************************************************************************************
67 CVideoDatabase::CVideoDatabase(void)
68 {
69 }
70
71 //********************************************************************************************************************************
72 CVideoDatabase::~CVideoDatabase(void)
73 {}
74
75 //********************************************************************************************************************************
76 bool CVideoDatabase::Open()
77 {
78   return CDatabase::Open(g_advancedSettings.m_databaseVideo);
79 }
80
81 bool CVideoDatabase::CreateTables()
82 {
83   /* indexes should be added on any columns that are used in in  */
84   /* a where or a join. primary key on a column is the same as a */
85   /* unique index on that column, so there is no need to add any */
86   /* index if no other columns are refered                       */
87
88   /* order of indexes are important, for an index to be considered all  */
89   /* columns up to the column in question have to have been specified   */
90   /* select * from actorlinkmovie where idMovie = 1, can not take       */
91   /* advantage of a index that has been created on ( idGenre, idMovie ) */
92   /*, hower on on ( idMovie, idGenre ) will be considered for use       */
93
94   BeginTransaction();
95   try
96   {
97     CDatabase::CreateTables();
98
99     CLog::Log(LOGINFO, "create bookmark table");
100     m_pDS->exec("CREATE TABLE bookmark ( idBookmark integer primary key, idFile integer, timeInSeconds double, totalTimeInSeconds double, thumbNailImage text, player text, playerState text, type integer)\n");
101     m_pDS->exec("CREATE INDEX ix_bookmark ON bookmark (idFile, type)");
102
103     CLog::Log(LOGINFO, "create settings table");
104     m_pDS->exec("CREATE TABLE settings ( idFile integer, Deinterlace bool,"
105                 "ViewMode integer,ZoomAmount float, PixelRatio float, VerticalShift float, AudioStream integer, SubtitleStream integer,"
106                 "SubtitleDelay float, SubtitlesOn bool, Brightness float, Contrast float, Gamma float,"
107                 "VolumeAmplification float, AudioDelay float, OutputToAllSpeakers bool, ResumeTime integer, Crop bool, CropLeft integer,"
108                 "CropRight integer, CropTop integer, CropBottom integer, Sharpness float, NoiseReduction float, NonLinStretch bool, PostProcess bool,"
109                 "ScalingMethod integer, DeinterlaceMode integer)\n");
110     m_pDS->exec("CREATE UNIQUE INDEX ix_settings ON settings ( idFile )\n");
111
112     CLog::Log(LOGINFO, "create stacktimes table");
113     m_pDS->exec("CREATE TABLE stacktimes (idFile integer, times text)\n");
114     m_pDS->exec("CREATE UNIQUE INDEX ix_stacktimes ON stacktimes ( idFile )\n");
115
116     CLog::Log(LOGINFO, "create genre table");
117     m_pDS->exec("CREATE TABLE genre ( idGenre integer primary key, strGenre text)\n");
118
119     CLog::Log(LOGINFO, "create genrelinkmovie table");
120     m_pDS->exec("CREATE TABLE genrelinkmovie ( idGenre integer, idMovie integer)\n");
121     m_pDS->exec("CREATE UNIQUE INDEX ix_genrelinkmovie_1 ON genrelinkmovie ( idGenre, idMovie)\n");
122     m_pDS->exec("CREATE UNIQUE INDEX ix_genrelinkmovie_2 ON genrelinkmovie ( idMovie, idGenre)\n");
123
124     CLog::Log(LOGINFO, "create country table");
125     m_pDS->exec("CREATE TABLE country ( idCountry integer primary key, strCountry text)\n");
126
127     CLog::Log(LOGINFO, "create countrylinkmovie table");
128     m_pDS->exec("CREATE TABLE countrylinkmovie ( idCountry integer, idMovie integer)\n");
129     m_pDS->exec("CREATE UNIQUE INDEX ix_countrylinkmovie_1 ON countrylinkmovie ( idCountry, idMovie)\n");
130     m_pDS->exec("CREATE UNIQUE INDEX ix_countrylinkmovie_2 ON countrylinkmovie ( idMovie, idCountry)\n");
131
132     CLog::Log(LOGINFO, "create movie table");
133     CStdString columns = "CREATE TABLE movie ( idMovie integer primary key, idFile integer";
134     for (int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
135     {
136       CStdString column;
137       column.Format(",c%02d text", i);
138       columns += column;
139     }
140     columns += ", idSet integer)";
141     m_pDS->exec(columns.c_str());
142     m_pDS->exec("CREATE UNIQUE INDEX ix_movie_file_1 ON movie (idFile, idMovie)");
143     m_pDS->exec("CREATE UNIQUE INDEX ix_movie_file_2 ON movie (idMovie, idFile)");
144
145     CLog::Log(LOGINFO, "create actorlinkmovie table");
146     m_pDS->exec("CREATE TABLE actorlinkmovie ( idActor integer, idMovie integer, strRole text, iOrder integer)\n");
147     m_pDS->exec("CREATE UNIQUE INDEX ix_actorlinkmovie_1 ON actorlinkmovie ( idActor, idMovie )\n");
148     m_pDS->exec("CREATE UNIQUE INDEX ix_actorlinkmovie_2 ON actorlinkmovie ( idMovie, idActor )\n");
149
150     CLog::Log(LOGINFO, "create directorlinkmovie table");
151     m_pDS->exec("CREATE TABLE directorlinkmovie ( idDirector integer, idMovie integer)\n");
152     m_pDS->exec("CREATE UNIQUE INDEX ix_directorlinkmovie_1 ON directorlinkmovie ( idDirector, idMovie )\n");
153     m_pDS->exec("CREATE UNIQUE INDEX ix_directorlinkmovie_2 ON directorlinkmovie ( idMovie, idDirector )\n");
154
155     CLog::Log(LOGINFO, "create writerlinkmovie table");
156     m_pDS->exec("CREATE TABLE writerlinkmovie ( idWriter integer, idMovie integer)\n");
157     m_pDS->exec("CREATE UNIQUE INDEX ix_writerlinkmovie_1 ON writerlinkmovie ( idWriter, idMovie )\n");
158     m_pDS->exec("CREATE UNIQUE INDEX ix_writerlinkmovie_2 ON writerlinkmovie ( idMovie, idWriter )\n");
159
160     CLog::Log(LOGINFO, "create actors table");
161     m_pDS->exec("CREATE TABLE actors ( idActor integer primary key, strActor text, strThumb text )\n");
162
163     CLog::Log(LOGINFO, "create path table");
164     m_pDS->exec("CREATE TABLE path ( idPath integer primary key, strPath text, strContent text, strScraper text, strHash text, scanRecursive integer, useFolderNames bool, strSettings text, noUpdate bool, exclude bool, dateAdded text)");
165     m_pDS->exec("CREATE INDEX ix_path ON path ( strPath(255) )");
166
167     CLog::Log(LOGINFO, "create files table");
168     m_pDS->exec("CREATE TABLE files ( idFile integer primary key, idPath integer, strFilename text, playCount integer, lastPlayed text, dateAdded text)");
169     m_pDS->exec("CREATE INDEX ix_files ON files ( idPath, strFilename(255) )");
170
171     CLog::Log(LOGINFO, "create tvshow table");
172     columns = "CREATE TABLE tvshow ( idShow integer primary key";
173     for (int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
174     {
175       CStdString column;
176       column.Format(",c%02d text", i);
177       columns += column;
178     }
179     columns += ")";
180     m_pDS->exec(columns.c_str());
181
182     CLog::Log(LOGINFO, "create directorlinktvshow table");
183     m_pDS->exec("CREATE TABLE directorlinktvshow ( idDirector integer, idShow integer)\n");
184     m_pDS->exec("CREATE UNIQUE INDEX ix_directorlinktvshow_1 ON directorlinktvshow ( idDirector, idShow )\n");
185     m_pDS->exec("CREATE UNIQUE INDEX ix_directorlinktvshow_2 ON directorlinktvshow ( idShow, idDirector )\n");
186
187     CLog::Log(LOGINFO, "create actorlinktvshow table");
188     m_pDS->exec("CREATE TABLE actorlinktvshow ( idActor integer, idShow integer, strRole text, iOrder integer)\n");
189     m_pDS->exec("CREATE UNIQUE INDEX ix_actorlinktvshow_1 ON actorlinktvshow ( idActor, idShow )\n");
190     m_pDS->exec("CREATE UNIQUE INDEX ix_actorlinktvshow_2 ON actorlinktvshow ( idShow, idActor )\n");
191
192     CLog::Log(LOGINFO, "create studiolinktvshow table");
193     m_pDS->exec("CREATE TABLE studiolinktvshow ( idStudio integer, idShow integer)\n");
194     m_pDS->exec("CREATE UNIQUE INDEX ix_studiolinktvshow_1 ON studiolinktvshow ( idStudio, idShow)\n");
195     m_pDS->exec("CREATE UNIQUE INDEX ix_studiolinktvshow_2 ON studiolinktvshow ( idShow, idStudio)\n");
196
197     CLog::Log(LOGINFO, "create episode table");
198     columns = "CREATE TABLE episode ( idEpisode integer primary key, idFile integer";
199     for (int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
200     {
201       CStdString column;
202       if ( i == VIDEODB_ID_EPISODE_SEASON || i == VIDEODB_ID_EPISODE_EPISODE || i == VIDEODB_ID_EPISODE_BOOKMARK)
203         column.Format(",c%02d varchar(24)", i);
204       else
205         column.Format(",c%02d text", i);
206
207       columns += column;
208     }
209     columns += ", idShow integer)";
210     m_pDS->exec(columns.c_str());
211     m_pDS->exec("CREATE UNIQUE INDEX ix_episode_file_1 on episode (idEpisode, idFile)");
212     m_pDS->exec("CREATE UNIQUE INDEX id_episode_file_2 on episode (idFile, idEpisode)");
213     CStdString createColIndex;
214     createColIndex.Format("CREATE INDEX ix_episode_season_episode on episode (c%02d, c%02d)", VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_EPISODE_EPISODE);
215     m_pDS->exec(createColIndex.c_str());
216     createColIndex.Format("CREATE INDEX ix_episode_bookmark on episode (c%02d)", VIDEODB_ID_EPISODE_BOOKMARK);
217     m_pDS->exec(createColIndex.c_str());
218     m_pDS->exec("CREATE INDEX ix_episode_show1 on episode(idEpisode,idShow)");
219     m_pDS->exec("CREATE INDEX ix_episode_show2 on episode(idShow,idEpisode)");
220
221     CLog::Log(LOGINFO, "create tvshowlinkpath table");
222     m_pDS->exec("CREATE TABLE tvshowlinkpath (idShow integer, idPath integer)\n");
223     m_pDS->exec("CREATE UNIQUE INDEX ix_tvshowlinkpath_1 ON tvshowlinkpath ( idShow, idPath )\n");
224     m_pDS->exec("CREATE UNIQUE INDEX ix_tvshowlinkpath_2 ON tvshowlinkpath ( idPath, idShow )\n");
225
226     CLog::Log(LOGINFO, "create actorlinkepisode table");
227     m_pDS->exec("CREATE TABLE actorlinkepisode ( idActor integer, idEpisode integer, strRole text, iOrder integer)\n");
228     m_pDS->exec("CREATE UNIQUE INDEX ix_actorlinkepisode_1 ON actorlinkepisode ( idActor, idEpisode )\n");
229     m_pDS->exec("CREATE UNIQUE INDEX ix_actorlinkepisode_2 ON actorlinkepisode ( idEpisode, idActor )\n");
230
231     CLog::Log(LOGINFO, "create directorlinkepisode table");
232     m_pDS->exec("CREATE TABLE directorlinkepisode ( idDirector integer, idEpisode integer)\n");
233     m_pDS->exec("CREATE UNIQUE INDEX ix_directorlinkepisode_1 ON directorlinkepisode ( idDirector, idEpisode )\n");
234     m_pDS->exec("CREATE UNIQUE INDEX ix_directorlinkepisode_2 ON directorlinkepisode ( idEpisode, idDirector )\n");
235
236     CLog::Log(LOGINFO, "create writerlinkepisode table");
237     m_pDS->exec("CREATE TABLE writerlinkepisode ( idWriter integer, idEpisode integer)\n");
238     m_pDS->exec("CREATE UNIQUE INDEX ix_writerlinkepisode_1 ON writerlinkepisode ( idWriter, idEpisode )\n");
239     m_pDS->exec("CREATE UNIQUE INDEX ix_writerlinkepisode_2 ON writerlinkepisode ( idEpisode, idWriter )\n");
240
241     CLog::Log(LOGINFO, "create genrelinktvshow table");
242     m_pDS->exec("CREATE TABLE genrelinktvshow ( idGenre integer, idShow integer)\n");
243     m_pDS->exec("CREATE UNIQUE INDEX ix_genrelinktvshow_1 ON genrelinktvshow ( idGenre, idShow)\n");
244     m_pDS->exec("CREATE UNIQUE INDEX ix_genrelinktvshow_2 ON genrelinktvshow ( idShow, idGenre)\n");
245
246     CLog::Log(LOGINFO, "create movielinktvshow table");
247     m_pDS->exec("CREATE TABLE movielinktvshow ( idMovie integer, IdShow integer)\n");
248     m_pDS->exec("CREATE UNIQUE INDEX ix_movielinktvshow_1 ON movielinktvshow ( idShow, idMovie)\n");
249     m_pDS->exec("CREATE UNIQUE INDEX ix_movielinktvshow_2 ON movielinktvshow ( idMovie, idShow)\n");
250
251     CLog::Log(LOGINFO, "create studio table");
252     m_pDS->exec("CREATE TABLE studio ( idStudio integer primary key, strStudio text)\n");
253
254     CLog::Log(LOGINFO, "create studiolinkmovie table");
255     m_pDS->exec("CREATE TABLE studiolinkmovie ( idStudio integer, idMovie integer)\n");
256     m_pDS->exec("CREATE UNIQUE INDEX ix_studiolinkmovie_1 ON studiolinkmovie ( idStudio, idMovie)\n");
257     m_pDS->exec("CREATE UNIQUE INDEX ix_studiolinkmovie_2 ON studiolinkmovie ( idMovie, idStudio)\n");
258
259     CLog::Log(LOGINFO, "create musicvideo table");
260     columns = "CREATE TABLE musicvideo ( idMVideo integer primary key, idFile integer";
261     for (int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
262     {
263       CStdString column;
264       column.Format(",c%02d text", i);
265       columns += column;
266     }
267     columns += ")";
268     m_pDS->exec(columns.c_str());
269
270     m_pDS->exec("CREATE UNIQUE INDEX ix_musicvideo_file_1 on musicvideo (idMVideo, idFile)");
271     m_pDS->exec("CREATE UNIQUE INDEX ix_musicvideo_file_2 on musicvideo (idFile, idMVideo)");
272
273     CLog::Log(LOGINFO, "create artistlinkmusicvideo table");
274     m_pDS->exec("CREATE TABLE artistlinkmusicvideo ( idArtist integer, idMVideo integer)\n");
275     m_pDS->exec("CREATE UNIQUE INDEX ix_artistlinkmusicvideo_1 ON artistlinkmusicvideo ( idArtist, idMVideo)\n");
276     m_pDS->exec("CREATE UNIQUE INDEX ix_artistlinkmusicvideo_2 ON artistlinkmusicvideo ( idMVideo, idArtist)\n");
277
278     CLog::Log(LOGINFO, "create genrelinkmusicvideo table");
279     m_pDS->exec("CREATE TABLE genrelinkmusicvideo ( idGenre integer, idMVideo integer)\n");
280     m_pDS->exec("CREATE UNIQUE INDEX ix_genrelinkmusicvideo_1 ON genrelinkmusicvideo ( idGenre, idMVideo)\n");
281     m_pDS->exec("CREATE UNIQUE INDEX ix_genrelinkmusicvideo_2 ON genrelinkmusicvideo ( idMVideo, idGenre)\n");
282
283     CLog::Log(LOGINFO, "create studiolinkmusicvideo table");
284     m_pDS->exec("CREATE TABLE studiolinkmusicvideo ( idStudio integer, idMVideo integer)\n");
285     m_pDS->exec("CREATE UNIQUE INDEX ix_studiolinkmusicvideo_1 ON studiolinkmusicvideo ( idStudio, idMVideo)\n");
286     m_pDS->exec("CREATE UNIQUE INDEX ix_studiolinkmusicvideo_2 ON studiolinkmusicvideo ( idMVideo, idStudio)\n");
287
288     CLog::Log(LOGINFO, "create directorlinkmusicvideo table");
289     m_pDS->exec("CREATE TABLE directorlinkmusicvideo ( idDirector integer, idMVideo integer)\n");
290     m_pDS->exec("CREATE UNIQUE INDEX ix_directorlinkmusicvideo_1 ON directorlinkmusicvideo ( idDirector, idMVideo )\n");
291     m_pDS->exec("CREATE UNIQUE INDEX ix_directorlinkmusicvideo_2 ON directorlinkmusicvideo ( idMVideo, idDirector )\n");
292
293     CLog::Log(LOGINFO, "create streaminfo table");
294     m_pDS->exec("CREATE TABLE streamdetails (idFile integer, iStreamType integer, "
295       "strVideoCodec text, fVideoAspect float, iVideoWidth integer, iVideoHeight integer, "
296       "strAudioCodec text, iAudioChannels integer, strAudioLanguage text, strSubtitleLanguage text, iVideoDuration integer)");
297     m_pDS->exec("CREATE INDEX ix_streamdetails ON streamdetails (idFile)");
298
299    CLog::Log(LOGINFO, "create sets table");
300     m_pDS->exec("CREATE TABLE sets ( idSet integer primary key, strSet text)\n");
301
302     // create basepath indices
303     m_pDS->exec("CREATE INDEX ixMovieBasePath ON movie ( c23(12) )");
304     m_pDS->exec("CREATE INDEX ixMusicVideoBasePath ON musicvideo ( c14(12) )");
305     m_pDS->exec("CREATE INDEX ixEpisodeBasePath ON episode ( c19(12) )");
306     m_pDS->exec("CREATE INDEX ixTVShowBasePath on tvshow ( c17(12) )");
307
308     CLog::Log(LOGINFO, "create seasons table");
309     m_pDS->exec("CREATE TABLE seasons ( idSeason integer primary key, idShow integer, season integer)");
310     m_pDS->exec("CREATE INDEX ix_seasons ON seasons (idShow, season)");
311
312     CLog::Log(LOGINFO, "create art table");
313     m_pDS->exec("CREATE TABLE art(art_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, type TEXT, url TEXT)");
314     m_pDS->exec("CREATE INDEX ix_art ON art(media_id, media_type(20), type(20))");
315
316     CLog::Log(LOGINFO, "create tag table");
317     m_pDS->exec("CREATE TABLE tag (idTag integer primary key, strTag text)");
318     m_pDS->exec("CREATE UNIQUE INDEX ix_tag_1 ON tag (strTag(255))");
319
320     CLog::Log(LOGINFO, "create taglinks table");
321     m_pDS->exec("CREATE TABLE taglinks (idTag integer, idMedia integer, media_type TEXT)");
322     m_pDS->exec("CREATE UNIQUE INDEX ix_taglinks_1 ON taglinks (idTag, media_type(20), idMedia)");
323     m_pDS->exec("CREATE UNIQUE INDEX ix_taglinks_2 ON taglinks (idMedia, media_type(20), idTag)");
324     m_pDS->exec("CREATE INDEX ix_taglinks_3 ON taglinks (media_type(20))");
325
326     CLog::Log(LOGINFO, "create deletion triggers");
327     m_pDS->exec("CREATE TRIGGER delete_movie AFTER DELETE ON movie FOR EACH ROW BEGIN "
328                 "DELETE FROM art WHERE media_id=old.idMovie AND media_type='movie'; "
329                 "DELETE FROM taglinks WHERE idMedia=old.idMovie AND media_type='movie'; "
330                 "END");
331     m_pDS->exec("CREATE TRIGGER delete_tvshow AFTER DELETE ON tvshow FOR EACH ROW BEGIN "
332                 "DELETE FROM art WHERE media_id=old.idShow AND media_type='tvshow'; "
333                 "DELETE FROM taglinks WHERE idMedia=old.idShow AND media_type='tvshow'; "
334                 "END");
335     m_pDS->exec("CREATE TRIGGER delete_musicvideo AFTER DELETE ON musicvideo FOR EACH ROW BEGIN "
336                 "DELETE FROM art WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
337                 "DELETE FROM taglinks WHERE idMedia=old.idMVideo AND media_type='musicvideo'; "
338                 "END");
339     m_pDS->exec("CREATE TRIGGER delete_episode AFTER DELETE ON episode FOR EACH ROW BEGIN "
340                 "DELETE FROM art WHERE media_id=old.idEpisode AND media_type='episode'; "
341                 "END");
342     m_pDS->exec("CREATE TRIGGER delete_season AFTER DELETE ON seasons FOR EACH ROW BEGIN "
343                 "DELETE FROM art WHERE media_id=old.idSeason AND media_type='season'; "
344                 "END");
345     m_pDS->exec("CREATE TRIGGER delete_set AFTER DELETE ON sets FOR EACH ROW BEGIN "
346                 "DELETE FROM art WHERE media_id=old.idSet AND media_type='set'; "
347                 "END");
348     m_pDS->exec("CREATE TRIGGER delete_person AFTER DELETE ON actors FOR EACH ROW BEGIN "
349                 "DELETE FROM art WHERE media_id=old.idActor AND media_type IN ('actor','artist','writer','director'); "
350                 "END");
351     m_pDS->exec("CREATE TRIGGER delete_tag AFTER DELETE ON taglinks FOR EACH ROW BEGIN "
352                 "DELETE FROM tag WHERE idTag=old.idTag AND idTag NOT IN (SELECT DISTINCT idTag FROM taglinks); "
353                 "END");
354
355     // we create views last to ensure all indexes are rolled in
356     CreateViews();
357   }
358   catch (...)
359   {
360     CLog::Log(LOGERROR, "%s unable to create tables:%i", __FUNCTION__, (int)GetLastError());
361     RollbackTransaction();
362     return false;
363   }
364   CommitTransaction();
365   return true;
366 }
367
368 void CVideoDatabase::CreateViews()
369 {
370   CLog::Log(LOGINFO, "create episodeview");
371   m_pDS->exec("DROP VIEW IF EXISTS episodeview");
372   CStdString episodeview = PrepareSQL("CREATE VIEW episodeview AS SELECT "
373                                       "  episode.*,"
374                                       "  files.strFileName AS strFileName,"
375                                       "  path.strPath AS strPath,"
376                                       "  files.playCount AS playCount,"
377                                       "  files.lastPlayed AS lastPlayed,"
378                                       "  files.dateAdded AS dateAdded,"
379                                       "  tvshow.c%02d AS strTitle,"
380                                       "  tvshow.c%02d AS strStudio,"
381                                       "  tvshow.c%02d AS premiered,"
382                                       "  tvshow.c%02d AS mpaa,"
383                                       "  tvshow.c%02d AS strShowPath, "
384                                       "  bookmark.timeInSeconds AS resumeTimeInSeconds, "
385                                       "  bookmark.totalTimeInSeconds AS totalTimeInSeconds, "
386                                       "  seasons.idSeason AS idSeason "
387                                       "FROM episode"
388                                       "  JOIN files ON"
389                                       "    files.idFile=episode.idFile"
390                                       "  JOIN tvshow ON"
391                                       "    tvshow.idShow=episode.idShow"
392                                       "  LEFT JOIN seasons ON"
393                                       "    seasons.idShow=episode.idShow AND seasons.season=episode.c%02d"
394                                       "  JOIN path ON"
395                                       "    files.idPath=path.idPath"
396                                       "  LEFT JOIN bookmark ON"
397                                       "    bookmark.idFile=episode.idFile AND bookmark.type=1", VIDEODB_ID_TV_TITLE, VIDEODB_ID_TV_STUDIOS, VIDEODB_ID_TV_PREMIERED, VIDEODB_ID_TV_MPAA, VIDEODB_ID_TV_BASEPATH, VIDEODB_ID_EPISODE_SEASON);
398   m_pDS->exec(episodeview.c_str());
399
400   CLog::Log(LOGINFO, "create tvshowview");
401   m_pDS->exec("DROP VIEW IF EXISTS tvshowview");
402   CStdString tvshowview = PrepareSQL("CREATE VIEW tvshowview AS SELECT "
403                                      "  tvshow.*,"
404                                      "  path.strPath AS strPath,"
405                                      "  path.dateAdded AS dateAdded,"
406                                      "  MAX(files.lastPlayed) AS lastPlayed,"
407                                      "  NULLIF(COUNT(episode.c12), 0) AS totalCount,"
408                                      "  COUNT(files.playCount) AS watchedcount,"
409                                      "  NULLIF(COUNT(DISTINCT(episode.c12)), 0) AS totalSeasons "
410                                      "FROM tvshow"
411                                      "  LEFT JOIN tvshowlinkpath ON"
412                                      "    tvshowlinkpath.idShow=tvshow.idShow"
413                                      "  LEFT JOIN path ON"
414                                      "    path.idPath=tvshowlinkpath.idPath"
415                                      "  LEFT JOIN episode ON"
416                                      "    episode.idShow=tvshow.idShow"
417                                      "  LEFT JOIN files ON"
418                                      "    files.idFile=episode.idFile "
419                                      "GROUP BY tvshow.idShow;");
420   m_pDS->exec(tvshowview.c_str());
421
422   CLog::Log(LOGINFO, "create musicvideoview");
423   m_pDS->exec("DROP VIEW IF EXISTS musicvideoview");
424   m_pDS->exec("CREATE VIEW musicvideoview AS SELECT"
425               "  musicvideo.*,"
426               "  files.strFileName as strFileName,"
427               "  path.strPath as strPath,"
428               "  files.playCount as playCount,"
429               "  files.lastPlayed as lastPlayed,"
430               "  files.dateAdded as dateAdded, "
431               "  bookmark.timeInSeconds AS resumeTimeInSeconds, "
432               "  bookmark.totalTimeInSeconds AS totalTimeInSeconds "
433               "FROM musicvideo"
434               "  JOIN files ON"
435               "    files.idFile=musicvideo.idFile"
436               "  JOIN path ON"
437               "    path.idPath=files.idPath"
438               "  LEFT JOIN bookmark ON"
439               "    bookmark.idFile=musicvideo.idFile AND bookmark.type=1");
440
441   CLog::Log(LOGINFO, "create movieview");
442   m_pDS->exec("DROP VIEW IF EXISTS movieview");
443   m_pDS->exec("CREATE VIEW movieview AS SELECT"
444               "  movie.*,"
445               "  sets.strSet AS strSet,"
446               "  files.strFileName AS strFileName,"
447               "  path.strPath AS strPath,"
448               "  files.playCount AS playCount,"
449               "  files.lastPlayed AS lastPlayed, "
450               "  files.dateAdded AS dateAdded, "
451               "  bookmark.timeInSeconds AS resumeTimeInSeconds, "
452               "  bookmark.totalTimeInSeconds AS totalTimeInSeconds "
453               "FROM movie"
454               "  LEFT JOIN sets ON"
455               "    sets.idSet = movie.idSet"
456               "  JOIN files ON"
457               "    files.idFile=movie.idFile"
458               "  JOIN path ON"
459               "    path.idPath=files.idPath"
460               "  LEFT JOIN bookmark ON"
461               "    bookmark.idFile=movie.idFile AND bookmark.type=1");
462 }
463
464 //********************************************************************************************************************************
465 int CVideoDatabase::GetPathId(const CStdString& strPath)
466 {
467   CStdString strSQL;
468   try
469   {
470     int idPath=-1;
471     if (NULL == m_pDB.get()) return -1;
472     if (NULL == m_pDS.get()) return -1;
473
474     CStdString strPath1(strPath);
475     if (URIUtils::IsStack(strPath) || strPath.Mid(0,6).Equals("rar://") || strPath.Mid(0,6).Equals("zip://"))
476       URIUtils::GetParentPath(strPath,strPath1);
477
478     URIUtils::AddSlashAtEnd(strPath1);
479
480     strSQL=PrepareSQL("select idPath from path where strPath='%s'",strPath1.c_str());
481     m_pDS->query(strSQL.c_str());
482     if (!m_pDS->eof())
483       idPath = m_pDS->fv("path.idPath").get_asInt();
484
485     m_pDS->close();
486     return idPath;
487   }
488   catch (...)
489   {
490     CLog::Log(LOGERROR, "%s unable to getpath (%s)", __FUNCTION__, strSQL.c_str());
491   }
492   return -1;
493 }
494
495 bool CVideoDatabase::GetPaths(set<CStdString> &paths)
496 {
497   try
498   {
499     if (NULL == m_pDB.get()) return false;
500     if (NULL == m_pDS.get()) return false;
501
502     paths.clear();
503
504     // grab all paths with movie content set
505     if (!m_pDS->query("select strPath,noUpdate from path"
506                       " where (strContent = 'movies' or strContent = 'musicvideos')"
507                       " and strPath NOT like 'multipath://%%'"
508                       " order by strPath"))
509       return false;
510
511     while (!m_pDS->eof())
512     {
513       if (!m_pDS->fv("noUpdate").get_asBool())
514         paths.insert(m_pDS->fv("strPath").get_asString());
515       m_pDS->next();
516     }
517     m_pDS->close();
518
519     // then grab all tvshow paths
520     if (!m_pDS->query("select strPath,noUpdate from path"
521                       " where ( strContent = 'tvshows'"
522                       "       or idPath in (select idPath from tvshowlinkpath))"
523                       " and strPath NOT like 'multipath://%%'"
524                       " order by strPath"))
525       return false;
526
527     while (!m_pDS->eof())
528     {
529       if (!m_pDS->fv("noUpdate").get_asBool())
530         paths.insert(m_pDS->fv("strPath").get_asString());
531       m_pDS->next();
532     }
533     m_pDS->close();
534
535     // finally grab all other paths holding a movie which is not a stack or a rar archive
536     // - this isnt perfect but it should do fine in most situations.
537     // reason we need it to hold a movie is stacks from different directories (cdx folders for instance)
538     // not making mistakes must take priority
539     if (!m_pDS->query("select strPath,noUpdate from path"
540                        " where idPath in (select idPath from files join movie on movie.idFile=files.idFile)"
541                        " and idPath NOT in (select idPath from tvshowlinkpath)"
542                        " and idPath NOT in (select idPath from files where strFileName like 'video_ts.ifo')" // dvd folders get stacked to a single item in parent folder
543                        " and idPath NOT in (select idPath from files where strFileName like 'index.bdmv')" // bluray folders get stacked to a single item in parent folder
544                        " and strPath NOT like 'multipath://%%'"
545                        " and strContent NOT in ('movies', 'tvshows', 'None')" // these have been added above
546                        " order by strPath"))
547
548       return false;
549     while (!m_pDS->eof())
550     {
551       if (!m_pDS->fv("noUpdate").get_asBool())
552         paths.insert(m_pDS->fv("strPath").get_asString());
553       m_pDS->next();
554     }
555     m_pDS->close();
556     return true;
557   }
558   catch (...)
559   {
560     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
561   }
562   return false;
563 }
564
565 bool CVideoDatabase::GetPathsForTvShow(int idShow, set<int>& paths)
566 {
567   CStdString strSQL;
568   try
569   {
570     if (NULL == m_pDB.get()) return false;
571     if (NULL == m_pDS.get()) return false;
572     strSQL = PrepareSQL("SELECT DISTINCT idPath FROM files JOIN episode ON episode.idFile=files.idFile WHERE episode.idShow=%i",idShow);
573     m_pDS->query(strSQL.c_str());
574     while (!m_pDS->eof())
575     {
576       paths.insert(m_pDS->fv(0).get_asInt());
577       m_pDS->next();
578     }
579     m_pDS->close();
580     return true;
581   }
582   catch (...)
583   {
584     CLog::Log(LOGERROR, "%s error during query: %s",__FUNCTION__, strSQL.c_str());
585   }
586   return false;
587 }
588
589 int CVideoDatabase::RunQuery(const CStdString &sql)
590 {
591   unsigned int time = XbmcThreads::SystemClockMillis();
592   int rows = -1;
593   if (m_pDS->query(sql.c_str()))
594   {
595     rows = m_pDS->num_rows();
596     if (rows == 0)
597       m_pDS->close();
598   }
599   CLog::Log(LOGDEBUG, "%s took %d ms for %d items query: %s", __FUNCTION__, XbmcThreads::SystemClockMillis() - time, rows, sql.c_str());
600   return rows;
601 }
602
603 bool CVideoDatabase::GetSubPaths(const CStdString &basepath, vector< pair<int,string> >& subpaths)
604 {
605   CStdString sql;
606   try
607   {
608     if (!m_pDB.get() || !m_pDS.get())
609       return false;
610
611     CStdString path(basepath);
612     URIUtils::AddSlashAtEnd(path);
613     sql = PrepareSQL("SELECT idPath,strPath FROM path WHERE SUBSTR(strPath,1,%i)='%s'", StringUtils::utf8_strlen(path.c_str()), path.c_str());
614     m_pDS->query(sql.c_str());
615     while (!m_pDS->eof())
616     {
617       subpaths.push_back(make_pair(m_pDS->fv(0).get_asInt(), m_pDS->fv(1).get_asString()));
618       m_pDS->next();
619     }
620     m_pDS->close();
621     return true;
622   }
623   catch (...)
624   {
625     CLog::Log(LOGERROR, "%s error during query: %s",__FUNCTION__, sql.c_str());
626   }
627   return false;
628 }
629
630 int CVideoDatabase::AddPath(const CStdString& strPath, const CStdString &strDateAdded /*= "" */)
631 {
632   CStdString strSQL;
633   try
634   {
635     int idPath = GetPathId(strPath);
636     if (idPath >= 0)
637       return idPath; // already have the path
638
639     if (NULL == m_pDB.get()) return -1;
640     if (NULL == m_pDS.get()) return -1;
641
642     CStdString strPath1(strPath);
643     if (URIUtils::IsStack(strPath) || strPath.Mid(0,6).Equals("rar://") || strPath.Mid(0,6).Equals("zip://"))
644       URIUtils::GetParentPath(strPath,strPath1);
645
646     URIUtils::AddSlashAtEnd(strPath1);
647
648     // only set dateadded if we got one
649     if (!strDateAdded.empty())
650       strSQL=PrepareSQL("insert into path (idPath, strPath, strContent, strScraper, dateAdded) values (NULL,'%s','','', '%s')", strPath1.c_str(), strDateAdded.c_str());
651     else
652       strSQL=PrepareSQL("insert into path (idPath, strPath, strContent, strScraper) values (NULL,'%s','','')", strPath1.c_str());
653     m_pDS->exec(strSQL.c_str());
654     idPath = (int)m_pDS->lastinsertid();
655     return idPath;
656   }
657   catch (...)
658   {
659     CLog::Log(LOGERROR, "%s unable to addpath (%s)", __FUNCTION__, strSQL.c_str());
660   }
661   return -1;
662 }
663
664 bool CVideoDatabase::GetPathHash(const CStdString &path, CStdString &hash)
665 {
666   try
667   {
668     if (NULL == m_pDB.get()) return false;
669     if (NULL == m_pDS.get()) return false;
670
671     CStdString strSQL=PrepareSQL("select strHash from path where strPath='%s'", path.c_str());
672     m_pDS->query(strSQL.c_str());
673     if (m_pDS->num_rows() == 0)
674       return false;
675     hash = m_pDS->fv("strHash").get_asString();
676     return true;
677   }
678   catch (...)
679   {
680     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, path.c_str());
681   }
682
683   return false;
684 }
685
686 //********************************************************************************************************************************
687 int CVideoDatabase::AddFile(const CStdString& strFileNameAndPath)
688 {
689   CStdString strSQL = "";
690   try
691   {
692     int idFile;
693     if (NULL == m_pDB.get()) return -1;
694     if (NULL == m_pDS.get()) return -1;
695
696     CStdString strFileName, strPath;
697     SplitPath(strFileNameAndPath,strPath,strFileName);
698
699     int idPath = AddPath(strPath);
700     if (idPath < 0)
701       return -1;
702
703     CStdString strSQL=PrepareSQL("select idFile from files where strFileName='%s' and idPath=%i", strFileName.c_str(),idPath);
704
705     m_pDS->query(strSQL.c_str());
706     if (m_pDS->num_rows() > 0)
707     {
708       idFile = m_pDS->fv("idFile").get_asInt() ;
709       m_pDS->close();
710       return idFile;
711     }
712     m_pDS->close();
713
714     strSQL=PrepareSQL("insert into files (idFile, idPath, strFileName) values(NULL, %i, '%s')", idPath, strFileName.c_str());
715     m_pDS->exec(strSQL.c_str());
716     idFile = (int)m_pDS->lastinsertid();
717     return idFile;
718   }
719   catch (...)
720   {
721     CLog::Log(LOGERROR, "%s unable to addfile (%s)", __FUNCTION__, strSQL.c_str());
722   }
723   return -1;
724 }
725
726 int CVideoDatabase::AddFile(const CFileItem& item)
727 {
728   if (item.IsVideoDb() && item.HasVideoInfoTag())
729     return AddFile(item.GetVideoInfoTag()->m_strFileNameAndPath);
730   return AddFile(item.GetPath());
731 }
732
733 void CVideoDatabase::UpdateFileDateAdded(int idFile, const CStdString& strFileNameAndPath)
734 {
735   if (idFile < 0 || strFileNameAndPath.empty())
736     return;
737     
738   CStdString strSQL = "";
739   try
740   {
741     if (NULL == m_pDB.get()) return;
742     if (NULL == m_pDS.get()) return;
743
744     CStdString file = strFileNameAndPath;
745     if (URIUtils::IsStack(strFileNameAndPath))
746       file = CStackDirectory::GetFirstStackedFile(strFileNameAndPath);
747
748     if (URIUtils::IsInArchive(file))
749       file = CURL(file).GetHostName();
750
751     CDateTime dateAdded;
752     // Skip looking at the files ctime/mtime if defined by the user through as.xml
753     if (g_advancedSettings.m_iVideoLibraryDateAdded > 0)
754     {
755       // Let's try to get the modification datetime
756       struct __stat64 buffer;
757       if (CFile::Stat(file, &buffer) == 0 && (buffer.st_mtime != 0 || buffer.st_ctime !=0))
758       {
759         time_t now = time(NULL);
760         time_t addedTime;
761         // Prefer the modification time if it's valid
762         if (g_advancedSettings.m_iVideoLibraryDateAdded == 1)
763         {
764           if (buffer.st_mtime != 0 && (time_t)buffer.st_mtime <= now)
765             addedTime = (time_t)buffer.st_mtime;
766           else
767             addedTime = (time_t)buffer.st_ctime;
768         }
769         // Use the newer of the creation and modification time
770         else
771         {
772           addedTime = max((time_t)buffer.st_ctime, (time_t)buffer.st_mtime);
773           // if the newer of the two dates is in the future, we try it with the older one
774           if (addedTime > now)
775             addedTime = min((time_t)buffer.st_ctime, (time_t)buffer.st_mtime);
776         }
777
778         // make sure the datetime does is not in the future
779         if (addedTime <= now)
780         {
781           struct tm *time = localtime(&addedTime);
782           if (time)
783             dateAdded = *time;
784         }
785       }
786     }
787
788     if (!dateAdded.IsValid())
789       dateAdded = CDateTime::GetCurrentDateTime();
790
791     strSQL = PrepareSQL("update files set dateAdded='%s' where idFile=%d", dateAdded.GetAsDBDateTime().c_str(), idFile);
792     m_pDS->exec(strSQL.c_str());
793   }
794   catch (...)
795   {
796     CLog::Log(LOGERROR, "%s unable to update dateadded for file (%s)", __FUNCTION__, strSQL.c_str());
797   }
798 }
799
800 bool CVideoDatabase::SetPathHash(const CStdString &path, const CStdString &hash)
801 {
802   try
803   {
804     if (NULL == m_pDB.get()) return false;
805     if (NULL == m_pDS.get()) return false;
806
807     if (hash.IsEmpty())
808     { // this is an empty folder - we need only add it to the path table
809       // if the path actually exists
810       if (!CDirectory::Exists(path))
811         return false;
812     }
813     int idPath = AddPath(path);
814     if (idPath < 0) return false;
815
816     CStdString strSQL=PrepareSQL("update path set strHash='%s' where idPath=%ld", hash.c_str(), idPath);
817     m_pDS->exec(strSQL.c_str());
818
819     return true;
820   }
821   catch (...)
822   {
823     CLog::Log(LOGERROR, "%s (%s, %s) failed", __FUNCTION__, path.c_str(), hash.c_str());
824   }
825
826   return false;
827 }
828
829 bool CVideoDatabase::LinkMovieToTvshow(int idMovie, int idShow, bool bRemove)
830 {
831    try
832   {
833     if (NULL == m_pDB.get()) return false;
834     if (NULL == m_pDS.get()) return false;
835
836     if (bRemove) // delete link
837     {
838       CStdString strSQL=PrepareSQL("delete from movielinktvshow where idMovie=%i and idShow=%i", idMovie, idShow);
839       m_pDS->exec(strSQL.c_str());
840       return true;
841     }
842
843     CStdString strSQL=PrepareSQL("insert into movielinktvshow (idShow,idMovie) values (%i,%i)", idShow,idMovie);
844     m_pDS->exec(strSQL.c_str());
845
846     return true;
847   }
848   catch (...)
849   {
850     CLog::Log(LOGERROR, "%s (%i, %i) failed", __FUNCTION__, idMovie, idShow);
851   }
852
853   return false;
854 }
855
856 bool CVideoDatabase::IsLinkedToTvshow(int idMovie)
857 {
858    try
859   {
860     if (NULL == m_pDB.get()) return false;
861     if (NULL == m_pDS.get()) return false;
862
863     CStdString strSQL=PrepareSQL("select * from movielinktvshow where idMovie=%i", idMovie);
864     m_pDS->query(strSQL.c_str());
865     if (m_pDS->eof())
866     {
867       m_pDS->close();
868       return false;
869     }
870
871     m_pDS->close();
872     return true;
873   }
874   catch (...)
875   {
876     CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, idMovie);
877   }
878
879   return false;
880 }
881
882 bool CVideoDatabase::GetLinksToTvShow(int idMovie, vector<int>& ids)
883 {
884    try
885   {
886     if (NULL == m_pDB.get()) return false;
887     if (NULL == m_pDS.get()) return false;
888
889     CStdString strSQL=PrepareSQL("select * from movielinktvshow where idMovie=%i", idMovie);
890     m_pDS2->query(strSQL.c_str());
891     while (!m_pDS2->eof())
892     {
893       ids.push_back(m_pDS2->fv(1).get_asInt());
894       m_pDS2->next();
895     }
896
897     m_pDS2->close();
898     return true;
899   }
900   catch (...)
901   {
902     CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, idMovie);
903   }
904
905   return false;
906 }
907
908
909 //********************************************************************************************************************************
910 int CVideoDatabase::GetFileId(const CStdString& strFilenameAndPath)
911 {
912   try
913   {
914     if (NULL == m_pDB.get()) return -1;
915     if (NULL == m_pDS.get()) return -1;
916     CStdString strPath, strFileName;
917     SplitPath(strFilenameAndPath,strPath,strFileName);
918
919     int idPath = GetPathId(strPath);
920     if (idPath >= 0)
921     {
922       CStdString strSQL;
923       strSQL=PrepareSQL("select idFile from files where strFileName='%s' and idPath=%i", strFileName.c_str(),idPath);
924       m_pDS->query(strSQL.c_str());
925       if (m_pDS->num_rows() > 0)
926       {
927         int idFile = m_pDS->fv("files.idFile").get_asInt();
928         m_pDS->close();
929         return idFile;
930       }
931     }
932   }
933   catch (...)
934   {
935     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
936   }
937   return -1;
938 }
939
940 int CVideoDatabase::GetFileId(const CFileItem &item)
941 {
942   if (item.IsVideoDb() && item.HasVideoInfoTag())
943     return GetFileId(item.GetVideoInfoTag()->m_strFileNameAndPath);
944   return GetFileId(item.GetPath());
945 }
946
947 //********************************************************************************************************************************
948 int CVideoDatabase::GetMovieId(const CStdString& strFilenameAndPath)
949 {
950   try
951   {
952     if (NULL == m_pDB.get()) return -1;
953     if (NULL == m_pDS.get()) return -1;
954     int idMovie = -1;
955
956     // needed for query parameters
957     int idFile = GetFileId(strFilenameAndPath);
958     int idPath=-1;
959     CStdString strPath;
960     if (idFile < 0)
961     {
962       CStdString strFile;
963       SplitPath(strFilenameAndPath,strPath,strFile);
964
965       // have to join movieinfo table for correct results
966       idPath = GetPathId(strPath);
967       if (idPath < 0 && strPath != strFilenameAndPath)
968         return -1;
969     }
970
971     if (idFile == -1 && strPath != strFilenameAndPath)
972       return -1;
973
974     CStdString strSQL;
975     if (idFile == -1)
976       strSQL=PrepareSQL("select idMovie from movie join files on files.idFile=movie.idFile where files.idPath=%i",idPath);
977     else
978       strSQL=PrepareSQL("select idMovie from movie where idFile=%i", idFile);
979
980     CLog::Log(LOGDEBUG, "%s (%s), query = %s", __FUNCTION__, strFilenameAndPath.c_str(), strSQL.c_str());
981     m_pDS->query(strSQL.c_str());
982     if (m_pDS->num_rows() > 0)
983       idMovie = m_pDS->fv("idMovie").get_asInt();
984     m_pDS->close();
985
986     return idMovie;
987   }
988   catch (...)
989   {
990     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
991   }
992   return -1;
993 }
994
995 int CVideoDatabase::GetTvShowId(const CStdString& strPath)
996 {
997   try
998   {
999     if (NULL == m_pDB.get()) return -1;
1000     if (NULL == m_pDS.get()) return -1;
1001     int idTvShow = -1;
1002
1003     // have to join movieinfo table for correct results
1004     int idPath = GetPathId(strPath);
1005     if (idPath < 0)
1006       return -1;
1007
1008     CStdString strSQL;
1009     CStdString strPath1=strPath;
1010     CStdString strParent;
1011     int iFound=0;
1012
1013     strSQL=PrepareSQL("select idShow from tvshowlinkpath where tvshowlinkpath.idPath=%i",idPath);
1014     m_pDS->query(strSQL);
1015     if (!m_pDS->eof())
1016       iFound = 1;
1017
1018     while (iFound == 0 && URIUtils::GetParentPath(strPath1, strParent))
1019     {
1020       strSQL=PrepareSQL("select idShow from path,tvshowlinkpath where tvshowlinkpath.idPath=path.idPath and strPath='%s'",strParent.c_str());
1021       m_pDS->query(strSQL.c_str());
1022       if (!m_pDS->eof())
1023       {
1024         int idShow = m_pDS->fv("idShow").get_asInt();
1025         if (idShow != -1)
1026           iFound = 2;
1027       }
1028       strPath1 = strParent;
1029     }
1030
1031     if (m_pDS->num_rows() > 0)
1032       idTvShow = m_pDS->fv("idShow").get_asInt();
1033     m_pDS->close();
1034
1035     return idTvShow;
1036   }
1037   catch (...)
1038   {
1039     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strPath.c_str());
1040   }
1041   return -1;
1042 }
1043
1044 int CVideoDatabase::GetEpisodeId(const CStdString& strFilenameAndPath, int idEpisode, int idSeason) // input value is episode/season number hint - for multiparters
1045 {
1046   try
1047   {
1048     if (NULL == m_pDB.get()) return -1;
1049     if (NULL == m_pDS.get()) return -1;
1050
1051     // need this due to the nested GetEpisodeInfo query
1052     auto_ptr<Dataset> pDS;
1053     pDS.reset(m_pDB->CreateDataset());
1054     if (NULL == pDS.get()) return -1;
1055
1056     int idFile = GetFileId(strFilenameAndPath);
1057     if (idFile < 0)
1058       return -1;
1059
1060     CStdString strSQL=PrepareSQL("select idEpisode from episode where idFile=%i", idFile);
1061
1062     CLog::Log(LOGDEBUG, "%s (%s), query = %s", __FUNCTION__, strFilenameAndPath.c_str(), strSQL.c_str());
1063     pDS->query(strSQL.c_str());
1064     if (pDS->num_rows() > 0)
1065     {
1066       if (idEpisode == -1)
1067         idEpisode = pDS->fv("episode.idEpisode").get_asInt();
1068       else // use the hint!
1069       {
1070         while (!pDS->eof())
1071         {
1072           CVideoInfoTag tag;
1073           int idTmpEpisode = pDS->fv("episode.idEpisode").get_asInt();
1074           GetEpisodeInfo(strFilenameAndPath,tag,idTmpEpisode);
1075           if (tag.m_iEpisode == idEpisode && (idSeason == -1 || tag.m_iSeason == idSeason)) {
1076             // match on the episode hint, and there's no season hint or a season hint match
1077             idEpisode = idTmpEpisode;
1078             break;
1079           }
1080           pDS->next();
1081         }
1082         if (pDS->eof())
1083           idEpisode = -1;
1084       }
1085     }
1086     else
1087       idEpisode = -1;
1088
1089     pDS->close();
1090
1091     return idEpisode;
1092   }
1093   catch (...)
1094   {
1095     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
1096   }
1097   return -1;
1098 }
1099
1100 int CVideoDatabase::GetMusicVideoId(const CStdString& strFilenameAndPath)
1101 {
1102   try
1103   {
1104     if (NULL == m_pDB.get()) return -1;
1105     if (NULL == m_pDS.get()) return -1;
1106
1107     int idFile = GetFileId(strFilenameAndPath);
1108     if (idFile < 0)
1109       return -1;
1110
1111     CStdString strSQL=PrepareSQL("select idMVideo from musicvideo where idFile=%i", idFile);
1112
1113     CLog::Log(LOGDEBUG, "%s (%s), query = %s", __FUNCTION__, strFilenameAndPath.c_str(), strSQL.c_str());
1114     m_pDS->query(strSQL.c_str());
1115     int idMVideo=-1;
1116     if (m_pDS->num_rows() > 0)
1117       idMVideo = m_pDS->fv("idMVideo").get_asInt();
1118     m_pDS->close();
1119
1120     return idMVideo;
1121   }
1122   catch (...)
1123   {
1124     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
1125   }
1126   return -1;
1127 }
1128
1129 //********************************************************************************************************************************
1130 int CVideoDatabase::AddMovie(const CStdString& strFilenameAndPath)
1131 {
1132   try
1133   {
1134     if (NULL == m_pDB.get()) return -1;
1135     if (NULL == m_pDS.get()) return -1;
1136
1137     int idMovie = GetMovieId(strFilenameAndPath);
1138     if (idMovie < 0)
1139     {
1140       int idFile = AddFile(strFilenameAndPath);
1141       if (idFile < 0)
1142         return -1;
1143       UpdateFileDateAdded(idFile, strFilenameAndPath);
1144       CStdString strSQL=PrepareSQL("insert into movie (idMovie, idFile) values (NULL, %i)", idFile);
1145       m_pDS->exec(strSQL.c_str());
1146       idMovie = (int)m_pDS->lastinsertid();
1147 //      CommitTransaction();
1148     }
1149
1150     return idMovie;
1151   }
1152   catch (...)
1153   {
1154     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
1155   }
1156   return -1;
1157 }
1158
1159 int CVideoDatabase::AddTvShow(const CStdString& strPath)
1160 {
1161   try
1162   {
1163     if (NULL == m_pDB.get()) return -1;
1164     if (NULL == m_pDS.get()) return -1;
1165
1166     CStdString strSQL=PrepareSQL("select tvshowlinkpath.idShow from path,tvshowlinkpath where path.strPath='%s' and path.idPath=tvshowlinkpath.idPath",strPath.c_str());
1167     m_pDS->query(strSQL.c_str());
1168     if (m_pDS->num_rows() != 0)
1169       return m_pDS->fv("tvshowlinkpath.idShow").get_asInt();
1170
1171     strSQL=PrepareSQL("insert into tvshow (idShow) values (NULL)");
1172     m_pDS->exec(strSQL.c_str());
1173     int idTvShow = (int)m_pDS->lastinsertid();
1174
1175     // Get the creation datetime of the tvshow directory
1176     CDateTime dateAdded;
1177     // Skip looking at the files ctime/mtime if defined by the user through as.xml
1178     if (g_advancedSettings.m_iVideoLibraryDateAdded > 0)
1179     {
1180       struct __stat64 buffer;
1181       if (XFILE::CFile::Stat(strPath, &buffer) == 0)
1182       {
1183         time_t now = time(NULL);
1184         // Make sure we have a valid date (i.e. not in the future)
1185         if ((time_t)buffer.st_ctime <= now)
1186         {
1187           struct tm *time = localtime((const time_t*)&buffer.st_ctime);
1188           if (time)
1189             dateAdded = *time;
1190         }
1191       }
1192     }
1193
1194     if (!dateAdded.IsValid())
1195       dateAdded = CDateTime::GetCurrentDateTime();
1196
1197     int idPath = AddPath(strPath, dateAdded.GetAsDBDateTime());
1198     strSQL=PrepareSQL("insert into tvshowlinkpath values (%i,%i)",idTvShow,idPath);
1199     m_pDS->exec(strSQL.c_str());
1200
1201 //    CommitTransaction();
1202
1203     return idTvShow;
1204   }
1205   catch (...)
1206   {
1207     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strPath.c_str());
1208   }
1209   return -1;
1210 }
1211
1212 //********************************************************************************************************************************
1213 int CVideoDatabase::AddEpisode(int idShow, const CStdString& strFilenameAndPath)
1214 {
1215   try
1216   {
1217     if (NULL == m_pDB.get()) return -1;
1218     if (NULL == m_pDS.get()) return -1;
1219
1220     int idFile = AddFile(strFilenameAndPath);
1221     if (idFile < 0)
1222       return -1;
1223     UpdateFileDateAdded(idFile, strFilenameAndPath);
1224
1225     CStdString strSQL=PrepareSQL("insert into episode (idEpisode, idFile, idShow) values (NULL, %i, %i)", idFile, idShow);
1226     m_pDS->exec(strSQL.c_str());
1227     return (int)m_pDS->lastinsertid();
1228   }
1229   catch (...)
1230   {
1231     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
1232   }
1233   return -1;
1234 }
1235
1236 int CVideoDatabase::AddMusicVideo(const CStdString& strFilenameAndPath)
1237 {
1238   try
1239   {
1240     if (NULL == m_pDB.get()) return -1;
1241     if (NULL == m_pDS.get()) return -1;
1242
1243     int idMVideo = GetMusicVideoId(strFilenameAndPath);
1244     if (idMVideo < 0)
1245     {
1246       int idFile = AddFile(strFilenameAndPath);
1247       if (idFile < 0)
1248         return -1;
1249       UpdateFileDateAdded(idFile, strFilenameAndPath);
1250       CStdString strSQL=PrepareSQL("insert into musicvideo (idMVideo, idFile) values (NULL, %i)", idFile);
1251       m_pDS->exec(strSQL.c_str());
1252       idMVideo = (int)m_pDS->lastinsertid();
1253     }
1254
1255     return idMVideo;
1256   }
1257   catch (...)
1258   {
1259     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
1260   }
1261   return -1;
1262 }
1263
1264 //********************************************************************************************************************************
1265 int CVideoDatabase::AddToTable(const CStdString& table, const CStdString& firstField, const CStdString& secondField, const CStdString& value)
1266 {
1267   try
1268   {
1269     if (NULL == m_pDB.get()) return -1;
1270     if (NULL == m_pDS.get()) return -1;
1271
1272     CStdString strSQL = PrepareSQL("select %s from %s where %s like '%s'", firstField.c_str(), table.c_str(), secondField.c_str(), value.c_str());
1273     m_pDS->query(strSQL.c_str());
1274     if (m_pDS->num_rows() == 0)
1275     {
1276       m_pDS->close();
1277       // doesnt exists, add it
1278       strSQL = PrepareSQL("insert into %s (%s, %s) values(NULL, '%s')", table.c_str(), firstField.c_str(), secondField.c_str(), value.c_str());      
1279       m_pDS->exec(strSQL.c_str());
1280       int id = (int)m_pDS->lastinsertid();
1281       return id;
1282     }
1283     else
1284     {
1285       int id = m_pDS->fv(firstField).get_asInt();
1286       m_pDS->close();
1287       return id;
1288     }
1289   }
1290   catch (...)
1291   {
1292     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, value.c_str() );
1293   }
1294
1295   return -1;
1296 }
1297
1298 int CVideoDatabase::AddSet(const CStdString& strSet)
1299 {
1300   if (strSet.empty())
1301     return -1;
1302
1303   return AddToTable("sets", "idSet", "strSet", strSet);
1304 }
1305
1306 int CVideoDatabase::AddTag(const std::string& tag)
1307 {
1308   if (tag.empty())
1309     return -1;
1310
1311   return AddToTable("tag", "idTag", "strTag", tag);
1312 }
1313
1314 int CVideoDatabase::AddGenre(const CStdString& strGenre)
1315 {
1316   return AddToTable("genre", "idGenre", "strGenre", strGenre);
1317 }
1318
1319 int CVideoDatabase::AddStudio(const CStdString& strStudio)
1320 {
1321   return AddToTable("studio", "idStudio", "strStudio", strStudio);
1322 }
1323
1324 //********************************************************************************************************************************
1325 int CVideoDatabase::AddCountry(const CStdString& strCountry)
1326 {
1327   return AddToTable("country", "idCountry", "strCountry", strCountry);
1328 }
1329
1330 int CVideoDatabase::AddActor(const CStdString& strActor, const CStdString& thumbURLs, const CStdString &thumb)
1331 {
1332   try
1333   {
1334     if (NULL == m_pDB.get()) return -1;
1335     if (NULL == m_pDS.get()) return -1;
1336     int idActor = -1;
1337     CStdString strSQL=PrepareSQL("select idActor from actors where strActor like '%s'", strActor.c_str());
1338     m_pDS->query(strSQL.c_str());
1339     if (m_pDS->num_rows() == 0)
1340     {
1341       m_pDS->close();
1342       // doesnt exists, add it
1343       strSQL=PrepareSQL("insert into actors (idActor, strActor, strThumb) values( NULL, '%s','%s')", strActor.c_str(),thumbURLs.c_str());
1344       m_pDS->exec(strSQL.c_str());
1345       idActor = (int)m_pDS->lastinsertid();
1346     }
1347     else
1348     {
1349       idActor = m_pDS->fv("idActor").get_asInt();
1350       m_pDS->close();
1351       // update the thumb url's
1352       if (!thumbURLs.IsEmpty())
1353       {
1354         strSQL=PrepareSQL("update actors set strThumb='%s' where idActor=%i",thumbURLs.c_str(),idActor);
1355         m_pDS->exec(strSQL.c_str());
1356       }
1357     }
1358     // add artwork
1359     if (!thumb.IsEmpty())
1360       SetArtForItem(idActor, "actor", "thumb", thumb);
1361     return idActor;
1362   }
1363   catch (...)
1364   {
1365     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strActor.c_str() );
1366   }
1367   return -1;
1368 }
1369
1370
1371
1372 void CVideoDatabase::AddLinkToActor(const char *table, int actorID, const char *secondField, int secondID, const CStdString &role, int order)
1373 {
1374   try
1375   {
1376     if (NULL == m_pDB.get()) return ;
1377     if (NULL == m_pDS.get()) return ;
1378
1379     CStdString strSQL=PrepareSQL("select * from %s where idActor=%i and %s=%i", table, actorID, secondField, secondID);
1380     m_pDS->query(strSQL.c_str());
1381     if (m_pDS->num_rows() == 0)
1382     {
1383       // doesnt exists, add it
1384       strSQL=PrepareSQL("insert into %s (idActor, %s, strRole, iOrder) values(%i,%i,'%s',%i)", table, secondField, actorID, secondID, role.c_str(), order);
1385       m_pDS->exec(strSQL.c_str());
1386     }
1387     m_pDS->close();
1388   }
1389   catch (...)
1390   {
1391     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
1392   }
1393 }
1394
1395 void CVideoDatabase::AddToLinkTable(const char *table, const char *firstField, int firstID, const char *secondField, int secondID, const char *typeField /* = NULL */, const char *type /* = NULL */)
1396 {
1397   try
1398   {
1399     if (NULL == m_pDB.get()) return ;
1400     if (NULL == m_pDS.get()) return ;
1401
1402     CStdString strSQL = PrepareSQL("select * from %s where %s=%i and %s=%i", table, firstField, firstID, secondField, secondID);
1403     if (typeField != NULL && type != NULL)
1404       strSQL += PrepareSQL(" and %s='%s'", typeField, type);
1405     m_pDS->query(strSQL.c_str());
1406     if (m_pDS->num_rows() == 0)
1407     {
1408       // doesnt exists, add it
1409       if (typeField == NULL || type == NULL)
1410         strSQL = PrepareSQL("insert into %s (%s,%s) values(%i,%i)", table, firstField, secondField, firstID, secondID);
1411       else
1412         strSQL = PrepareSQL("insert into %s (%s,%s,%s) values(%i,%i,'%s')", table, firstField, secondField, typeField, firstID, secondID, type);
1413       m_pDS->exec(strSQL.c_str());
1414     }
1415     m_pDS->close();
1416   }
1417   catch (...)
1418   {
1419     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
1420   }
1421 }
1422
1423 void CVideoDatabase::RemoveFromLinkTable(const char *table, const char *firstField, int firstID, const char *secondField, int secondID, const char *typeField /* = NULL */, const char *type /* = NULL */)
1424 {
1425   try
1426   {
1427     if (NULL == m_pDB.get()) return ;
1428     if (NULL == m_pDS.get()) return ;
1429
1430     CStdString strSQL = PrepareSQL("DELETE FROM %s WHERE %s = %i AND %s = %i", table, firstField, firstID, secondField, secondID);
1431     if (typeField != NULL && type != NULL)
1432       strSQL += PrepareSQL(" AND %s='%s'", typeField, type);
1433     m_pDS->exec(strSQL.c_str());
1434   }
1435   catch (...)
1436   {
1437     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
1438   }
1439 }
1440
1441 //****Tags****
1442 void CVideoDatabase::AddTagToItem(int idMovie, int idTag, const std::string &type)
1443 {
1444   if (type.empty())
1445     return;
1446
1447   AddToLinkTable("taglinks", "idTag", idTag, "idMedia", idMovie, "media_type", type.c_str());
1448 }
1449
1450 void CVideoDatabase::RemoveTagFromItem(int idItem, int idTag, const std::string &type)
1451 {
1452   if (type.empty())
1453     return;
1454
1455   RemoveFromLinkTable("taglinks", "idTag", idTag, "idMedia", idItem, "media_type", type.c_str());
1456 }
1457
1458 void CVideoDatabase::RemoveTagsFromItem(int idItem, const std::string &type)
1459 {
1460   if (type.empty())
1461     return;
1462
1463   m_pDS2->exec(PrepareSQL("DELETE FROM taglinks WHERE idMedia=%d AND media_type='%s'", idItem, type.c_str()));
1464 }
1465
1466 //****Actors****
1467 void CVideoDatabase::AddActorToMovie(int idMovie, int idActor, const CStdString& strRole, int order)
1468 {
1469   AddLinkToActor("actorlinkmovie", idActor, "idMovie", idMovie, strRole, order);
1470 }
1471
1472 void CVideoDatabase::AddActorToTvShow(int idTvShow, int idActor, const CStdString& strRole, int order)
1473 {
1474   AddLinkToActor("actorlinktvshow", idActor, "idShow", idTvShow, strRole, order);
1475 }
1476
1477 void CVideoDatabase::AddActorToEpisode(int idEpisode, int idActor, const CStdString& strRole, int order)
1478 {
1479   AddLinkToActor("actorlinkepisode", idActor, "idEpisode", idEpisode, strRole, order);
1480 }
1481
1482 void CVideoDatabase::AddArtistToMusicVideo(int idMVideo, int idArtist)
1483 {
1484   AddToLinkTable("artistlinkmusicvideo", "idArtist", idArtist, "idMVideo", idMVideo);
1485 }
1486
1487 //****Directors + Writers****
1488 void CVideoDatabase::AddDirectorToMovie(int idMovie, int idDirector)
1489 {
1490   AddToLinkTable("directorlinkmovie", "idDirector", idDirector, "idMovie", idMovie);
1491 }
1492
1493 void CVideoDatabase::AddDirectorToTvShow(int idTvShow, int idDirector)
1494 {
1495   AddToLinkTable("directorlinktvshow", "idDirector", idDirector, "idShow", idTvShow);
1496 }
1497
1498 void CVideoDatabase::AddWriterToEpisode(int idEpisode, int idWriter)
1499 {
1500   AddToLinkTable("writerlinkepisode", "idWriter", idWriter, "idEpisode", idEpisode);
1501 }
1502
1503 void CVideoDatabase::AddWriterToMovie(int idMovie, int idWriter)
1504 {
1505   AddToLinkTable("writerlinkmovie", "idWriter", idWriter, "idMovie", idMovie);
1506 }
1507
1508 void CVideoDatabase::AddDirectorToEpisode(int idEpisode, int idDirector)
1509 {
1510   AddToLinkTable("directorlinkepisode", "idDirector", idDirector, "idEpisode", idEpisode);
1511 }
1512
1513 void CVideoDatabase::AddDirectorToMusicVideo(int idMVideo, int idDirector)
1514 {
1515   AddToLinkTable("directorlinkmusicvideo", "idDirector", idDirector, "idMVideo", idMVideo);
1516 }
1517
1518 //****Studios****
1519 void CVideoDatabase::AddStudioToMovie(int idMovie, int idStudio)
1520 {
1521   AddToLinkTable("studiolinkmovie", "idStudio", idStudio, "idMovie", idMovie);
1522 }
1523
1524 void CVideoDatabase::AddStudioToTvShow(int idTvShow, int idStudio)
1525 {
1526   AddToLinkTable("studiolinktvshow", "idStudio", idStudio, "idShow", idTvShow);
1527 }
1528
1529 void CVideoDatabase::AddStudioToMusicVideo(int idMVideo, int idStudio)
1530 {
1531   AddToLinkTable("studiolinkmusicvideo", "idStudio", idStudio, "idMVideo", idMVideo);
1532 }
1533
1534 //****Genres****
1535 void CVideoDatabase::AddGenreToMovie(int idMovie, int idGenre)
1536 {
1537   AddToLinkTable("genrelinkmovie", "idGenre", idGenre, "idMovie", idMovie);
1538 }
1539
1540 void CVideoDatabase::AddGenreToTvShow(int idTvShow, int idGenre)
1541 {
1542   AddToLinkTable("genrelinktvshow", "idGenre", idGenre, "idShow", idTvShow);
1543 }
1544
1545 void CVideoDatabase::AddGenreToMusicVideo(int idMVideo, int idGenre)
1546 {
1547   AddToLinkTable("genrelinkmusicvideo", "idGenre", idGenre, "idMVideo", idMVideo);
1548 }
1549
1550 //****Country****
1551 void CVideoDatabase::AddCountryToMovie(int idMovie, int idCountry)
1552 {
1553   AddToLinkTable("countrylinkmovie", "idCountry", idCountry, "idMovie", idMovie);
1554 }
1555
1556 //********************************************************************************************************************************
1557 bool CVideoDatabase::LoadVideoInfo(const CStdString& strFilenameAndPath, CVideoInfoTag& details)
1558 {
1559   if (GetMovieInfo(strFilenameAndPath, details))
1560     return true;
1561   if (GetEpisodeInfo(strFilenameAndPath, details))
1562     return true;
1563   if (GetMusicVideoInfo(strFilenameAndPath, details))
1564     return true;
1565   if (GetFileInfo(strFilenameAndPath, details))
1566     return true;
1567
1568   return false;
1569 }
1570
1571 bool CVideoDatabase::HasMovieInfo(const CStdString& strFilenameAndPath)
1572 {
1573   try
1574   {
1575     if (NULL == m_pDB.get()) return false;
1576     if (NULL == m_pDS.get()) return false;
1577     int idMovie = GetMovieId(strFilenameAndPath);
1578     return (idMovie > 0); // index of zero is also invalid
1579   }
1580   catch (...)
1581   {
1582     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
1583   }
1584   return false;
1585 }
1586
1587 bool CVideoDatabase::HasTvShowInfo(const CStdString& strPath)
1588 {
1589   try
1590   {
1591     if (NULL == m_pDB.get()) return false;
1592     if (NULL == m_pDS.get()) return false;
1593     int idTvShow = GetTvShowId(strPath);
1594     return (idTvShow > 0); // index of zero is also invalid
1595   }
1596   catch (...)
1597   {
1598     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strPath.c_str());
1599   }
1600   return false;
1601 }
1602
1603 bool CVideoDatabase::HasEpisodeInfo(const CStdString& strFilenameAndPath)
1604 {
1605   try
1606   {
1607     if (NULL == m_pDB.get()) return false;
1608     if (NULL == m_pDS.get()) return false;
1609     int idEpisode = GetEpisodeId(strFilenameAndPath);
1610     return (idEpisode > 0); // index of zero is also invalid
1611   }
1612   catch (...)
1613   {
1614     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
1615   }
1616   return false;
1617 }
1618
1619 bool CVideoDatabase::HasMusicVideoInfo(const CStdString& strFilenameAndPath)
1620 {
1621   try
1622   {
1623     if (NULL == m_pDB.get()) return false;
1624     if (NULL == m_pDS.get()) return false;
1625     int idMVideo = GetMusicVideoId(strFilenameAndPath);
1626     return (idMVideo > 0); // index of zero is also invalid
1627   }
1628   catch (...)
1629   {
1630     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
1631   }
1632   return false;
1633 }
1634
1635 void CVideoDatabase::DeleteDetailsForTvShow(const CStdString& strPath, int idTvShow /* = -1 */)
1636 {
1637   try
1638   {
1639     if (NULL == m_pDB.get()) return ;
1640     if (NULL == m_pDS.get()) return ;
1641
1642     if (idTvShow < 0)
1643     {
1644       idTvShow = GetTvShowId(strPath);
1645       if (idTvShow < 0)
1646         return;
1647     }
1648
1649     CStdString strSQL;
1650     strSQL=PrepareSQL("delete from genrelinktvshow where idShow=%i", idTvShow);
1651     m_pDS->exec(strSQL.c_str());
1652
1653     strSQL=PrepareSQL("delete from actorlinktvshow where idShow=%i", idTvShow);
1654     m_pDS->exec(strSQL.c_str());
1655
1656     strSQL=PrepareSQL("delete from directorlinktvshow where idShow=%i", idTvShow);
1657     m_pDS->exec(strSQL.c_str());
1658
1659     strSQL=PrepareSQL("delete from studiolinktvshow where idShow=%i", idTvShow);
1660     m_pDS->exec(strSQL.c_str());
1661
1662     // remove all info other than the id
1663     // we do this due to the way we have the link between the file + movie tables.
1664
1665     strSQL = "update tvshow set ";
1666     for (int iType = VIDEODB_ID_TV_MIN + 1; iType < VIDEODB_ID_TV_MAX; iType++)
1667     {
1668       CStdString column;
1669       column.Format("c%02d=NULL,", iType);
1670       strSQL += column;
1671     }
1672     strSQL = strSQL.Mid(0, strSQL.size() - 1) + PrepareSQL(" where idShow=%i", idTvShow);
1673     m_pDS->exec(strSQL.c_str());
1674   }
1675   catch (...)
1676   {
1677     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strPath.c_str());
1678   }
1679 }
1680
1681 //********************************************************************************************************************************
1682 void CVideoDatabase::GetMoviesByActor(const CStdString& strActor, CFileItemList& items)
1683 {
1684   Filter filter;
1685   filter.join  = "LEFT JOIN actorlinkmovie ON actorlinkmovie.idMovie=movieview.idMovie "
1686                  "LEFT JOIN actors a ON a.idActor=actorlinkmovie.idActor "
1687                  "LEFT JOIN directorlinkmovie ON directorlinkmovie.idMovie=movieview.idMovie "
1688                  "LEFT JOIN actors d ON d.idActor=directorlinkmovie.idDirector";
1689   filter.where = PrepareSQL("a.strActor='%s' OR d.strActor='%s'", strActor.c_str(), strActor.c_str());
1690   filter.group = "movieview.idMovie";
1691   GetMoviesByWhere("videodb://1/2/", filter, items);
1692 }
1693
1694 void CVideoDatabase::GetTvShowsByActor(const CStdString& strActor, CFileItemList& items)
1695 {
1696   Filter filter;
1697   filter.join  = "LEFT JOIN actorlinktvshow ON actorlinktvshow.idShow=tvshowview.idShow "
1698                  "LEFT JOIN actors a ON a.idActor=actorlinktvshow.idActor "
1699                  "LEFT JOIN directorlinktvshow ON directorlinktvshow.idShow=tvshowview.idShow "
1700                  "LEFT JOIN actors d ON d.idActor=directorlinktvshow.idDirector";
1701   filter.where = PrepareSQL("a.strActor='%s' OR d.strActor='%s'", strActor.c_str(), strActor.c_str());
1702   filter.group = "tvshowview.idShow";
1703   GetTvShowsByWhere("videodb://2/2/", filter, items);
1704 }
1705
1706 void CVideoDatabase::GetEpisodesByActor(const CStdString& strActor, CFileItemList& items)
1707 {
1708   Filter filter;
1709   filter.join  = "LEFT JOIN actorlinkepisode ON actorlinkepisode.idEpisode=episodeview.idEpisode "
1710                  "LEFT JOIN actors a ON a.idActor=actorlinkepisode.idActor "
1711                  "LEFT JOIN directorlinkepisode ON directorlinkepisode.idEpisode=episodeview.idEpisode "
1712                  "LEFT JOIN actors d ON d.idActor=directorlinkepisode.idDirector";
1713   filter.where = PrepareSQL("a.strActor='%s' OR d.strActor='%s'", strActor.c_str(), strActor.c_str());
1714   filter.group = "episodeview.idEpisode";
1715   GetEpisodesByWhere("videodb://2/2/", filter, items);
1716 }
1717
1718 void CVideoDatabase::GetMusicVideosByArtist(const CStdString& strArtist, CFileItemList& items)
1719 {
1720   try
1721   {
1722     items.Clear();
1723     if (NULL == m_pDB.get()) return ;
1724     if (NULL == m_pDS.get()) return ;
1725
1726     CStdString strSQL;
1727     if (strArtist.IsEmpty())  // TODO: SMARTPLAYLISTS what is this here for???
1728       strSQL=PrepareSQL("select distinct * from musicvideoview join artistlinkmusicvideo on artistlinkmusicvideo.idMVideo=musicvideoview.idMVideo join actors on actors.idActor=artistlinkmusicvideo.idArtist");
1729     else
1730       strSQL=PrepareSQL("select * from musicvideoview join artistlinkmusicvideo on artistlinkmusicvideo.idMVideo=musicvideoview.idMVideo join actors on actors.idActor=artistlinkmusicvideo.idArtist where actors.strActor='%s'", strArtist.c_str());
1731     m_pDS->query( strSQL.c_str() );
1732
1733     while (!m_pDS->eof())
1734     {
1735       CVideoInfoTag tag = GetDetailsForMusicVideo(m_pDS);
1736       CFileItemPtr pItem(new CFileItem(tag));
1737       pItem->SetLabel(StringUtils::Join(tag.m_artist, g_advancedSettings.m_videoItemSeparator));
1738       items.Add(pItem);
1739       m_pDS->next();
1740     }
1741     m_pDS->close();
1742   }
1743   catch (...)
1744   {
1745     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strArtist.c_str());
1746   }
1747 }
1748
1749 //********************************************************************************************************************************
1750 bool CVideoDatabase::GetMovieInfo(const CStdString& strFilenameAndPath, CVideoInfoTag& details, int idMovie /* = -1 */)
1751 {
1752   try
1753   {
1754     // TODO: Optimize this - no need for all the queries!
1755     if (idMovie < 0)
1756       idMovie = GetMovieId(strFilenameAndPath);
1757     if (idMovie < 0) return false;
1758
1759     CStdString sql = PrepareSQL("select * from movieview where idMovie=%i", idMovie);
1760     if (!m_pDS->query(sql.c_str()))
1761       return false;
1762     details = GetDetailsForMovie(m_pDS, true);
1763     return !details.IsEmpty();
1764   }
1765   catch (...)
1766   {
1767     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
1768   }
1769   return false;
1770 }
1771
1772 //********************************************************************************************************************************
1773 bool CVideoDatabase::GetTvShowInfo(const CStdString& strPath, CVideoInfoTag& details, int idTvShow /* = -1 */)
1774 {
1775   try
1776   {
1777     if (idTvShow < 0)
1778       idTvShow = GetTvShowId(strPath);
1779     if (idTvShow < 0) return false;
1780
1781     CStdString sql = PrepareSQL("SELECT * FROM tvshowview WHERE idShow=%i", idTvShow);
1782     if (!m_pDS->query(sql.c_str()))
1783       return false;
1784     details = GetDetailsForTvShow(m_pDS, true);
1785     return !details.IsEmpty();
1786   }
1787   catch (...)
1788   {
1789     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strPath.c_str());
1790   }
1791   return false;
1792 }
1793
1794 bool CVideoDatabase::GetEpisodeInfo(const CStdString& strFilenameAndPath, CVideoInfoTag& details, int idEpisode /* = -1 */)
1795 {
1796   try
1797   {
1798     // TODO: Optimize this - no need for all the queries!
1799     if (idEpisode < 0)
1800       idEpisode = GetEpisodeId(strFilenameAndPath);
1801     if (idEpisode < 0) return false;
1802
1803     CStdString sql = PrepareSQL("select * from episodeview where idEpisode=%i",idEpisode);
1804     if (!m_pDS->query(sql.c_str()))
1805       return false;
1806     details = GetDetailsForEpisode(m_pDS, true);
1807     return !details.IsEmpty();
1808   }
1809   catch (...)
1810   {
1811     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
1812   }
1813   return false;
1814 }
1815
1816 bool CVideoDatabase::GetMusicVideoInfo(const CStdString& strFilenameAndPath, CVideoInfoTag& details, int idMVideo /* = -1 */)
1817 {
1818   try
1819   {
1820     // TODO: Optimize this - no need for all the queries!
1821     if (idMVideo < 0)
1822       idMVideo = GetMusicVideoId(strFilenameAndPath);
1823     if (idMVideo < 0) return false;
1824
1825     CStdString sql = PrepareSQL("select * from musicvideoview where idMVideo=%i", idMVideo);
1826     if (!m_pDS->query(sql.c_str()))
1827       return false;
1828     details = GetDetailsForMusicVideo(m_pDS, true);
1829     return !details.IsEmpty();
1830   }
1831   catch (...)
1832   {
1833     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
1834   }
1835   return false;
1836 }
1837
1838 bool CVideoDatabase::GetSetInfo(int idSet, CVideoInfoTag& details)
1839 {
1840   try
1841   {
1842     if (idSet < 0)
1843       return false;
1844
1845     Filter filter;
1846     filter.where = PrepareSQL("sets.idSet=%d", idSet);
1847     CFileItemList items;
1848     if (!GetSetsByWhere("videodb://1/7/", filter, items) ||
1849         items.Size() != 1 ||
1850         !items[0]->HasVideoInfoTag())
1851       return false;
1852
1853     details = *(items[0]->GetVideoInfoTag());
1854     return !details.IsEmpty();
1855   }
1856   catch (...)
1857   {
1858     CLog::Log(LOGERROR, "%s (%d) failed", __FUNCTION__, idSet);
1859   }
1860   return false;
1861 }
1862
1863 bool CVideoDatabase::GetFileInfo(const CStdString& strFilenameAndPath, CVideoInfoTag& details, int idFile /* = -1 */)
1864 {
1865   try
1866   {
1867     if (idFile < 0)
1868       idFile = GetFileId(strFilenameAndPath);
1869     if (idFile < 0)
1870       return false;
1871
1872     CStdString sql = PrepareSQL("SELECT * FROM files "
1873                                 "JOIN path ON path.idPath = files.idPath "
1874                                 "LEFT JOIN bookmark ON bookmark.idFile = files.idFile AND bookmark.type = %i "
1875                                 "WHERE files.idFile = %i", CBookmark::RESUME, idFile);
1876     if (!m_pDS->query(sql.c_str()))
1877       return false;
1878
1879     details.m_iFileId = m_pDS->fv("files.idFile").get_asInt();
1880     details.m_strPath = m_pDS->fv("path.strPath").get_asString();
1881     CStdString strFileName = m_pDS->fv("files.strFilename").get_asString();
1882     ConstructPath(details.m_strFileNameAndPath, details.m_strPath, strFileName);
1883     details.m_playCount = max(details.m_playCount, m_pDS->fv("files.playCount").get_asInt());
1884     if (!details.m_lastPlayed.IsValid())
1885       details.m_lastPlayed.SetFromDBDateTime(m_pDS->fv("files.lastPlayed").get_asString());
1886     if (!details.m_dateAdded.IsValid())
1887       details.m_dateAdded.SetFromDBDateTime(m_pDS->fv("files.dateAdded").get_asString());
1888     if (!details.m_resumePoint.IsSet())
1889     {
1890       details.m_resumePoint.timeInSeconds = m_pDS->fv("bookmark.timeInSeconds").get_asInt();
1891       details.m_resumePoint.totalTimeInSeconds = m_pDS->fv("bookmark.totalTimeInSeconds").get_asInt();
1892       details.m_resumePoint.type = CBookmark::RESUME;
1893     }
1894
1895     return !details.IsEmpty();
1896   }
1897   catch (...)
1898   {
1899     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
1900   }
1901   return false;
1902 }
1903
1904 void CVideoDatabase::AddGenreAndDirectorsAndStudios(const CVideoInfoTag& details, vector<int>& vecDirectors, vector<int>& vecGenres, vector<int>& vecStudios)
1905 {
1906   // add all directors
1907   for (unsigned int i = 0; i < details.m_director.size(); i++)
1908     vecDirectors.push_back(AddActor(details.m_director[i],""));
1909
1910   // add all genres
1911   for (unsigned int i = 0; i < details.m_genre.size(); i++)
1912     vecGenres.push_back(AddGenre(details.m_genre[i]));
1913   // add all studios
1914   for (unsigned int i = 0; i < details.m_studio.size(); i++)
1915     vecStudios.push_back(AddStudio(details.m_studio[i]));
1916 }
1917
1918 CStdString CVideoDatabase::GetValueString(const CVideoInfoTag &details, int min, int max, const SDbTableOffsets *offsets) const
1919 {
1920   CStdString sql;
1921   for (int i = min + 1; i < max; ++i)
1922   {
1923     switch (offsets[i].type)
1924     {
1925     case VIDEODB_TYPE_STRING:
1926       sql += PrepareSQL("c%02d='%s',", i, ((CStdString*)(((char*)&details)+offsets[i].offset))->c_str());
1927       break;
1928     case VIDEODB_TYPE_INT:
1929       sql += PrepareSQL("c%02d='%i',", i, *(int*)(((char*)&details)+offsets[i].offset));
1930       break;
1931     case VIDEODB_TYPE_COUNT:
1932       {
1933         int value = *(int*)(((char*)&details)+offsets[i].offset);
1934         if (value)
1935           sql += PrepareSQL("c%02d=%i,", i, value);
1936         else
1937           sql += PrepareSQL("c%02d=NULL,", i);
1938       }
1939       break;
1940     case VIDEODB_TYPE_BOOL:
1941       sql += PrepareSQL("c%02d='%s',", i, *(bool*)(((char*)&details)+offsets[i].offset)?"true":"false");
1942       break;
1943     case VIDEODB_TYPE_FLOAT:
1944       sql += PrepareSQL("c%02d='%f',", i, *(float*)(((char*)&details)+offsets[i].offset));
1945       break;
1946     case VIDEODB_TYPE_STRINGARRAY:
1947       sql += PrepareSQL("c%02d='%s',", i, StringUtils::Join(*((std::vector<std::string>*)(((char*)&details)+offsets[i].offset)), g_advancedSettings.m_videoItemSeparator).c_str());
1948       break;
1949     case VIDEODB_TYPE_DATE:
1950       sql += PrepareSQL("c%02d='%s',", i, ((CDateTime*)(((char*)&details)+offsets[i].offset))->GetAsDBDate().c_str());
1951       break;
1952     case VIDEODB_TYPE_DATETIME:
1953       sql += PrepareSQL("c%02d='%s',", i, ((CDateTime*)(((char*)&details)+offsets[i].offset))->GetAsDBDateTime().c_str());
1954       break;
1955     }
1956   }
1957   sql.TrimRight(',');
1958   return sql;
1959 }
1960
1961 //********************************************************************************************************************************
1962 int CVideoDatabase::SetDetailsForMovie(const CStdString& strFilenameAndPath, const CVideoInfoTag& details, const map<string, string> &artwork, int idMovie /* = -1 */)
1963 {
1964   try
1965   {
1966     BeginTransaction();
1967
1968     if (idMovie < 0)
1969       idMovie = GetMovieId(strFilenameAndPath);
1970
1971     if (idMovie > -1)
1972       DeleteMovie(strFilenameAndPath, true, idMovie); // true to keep the table entry
1973     else
1974     {
1975       // only add a new movie if we don't already have a valid idMovie
1976       // (DeleteMovie is called with bKeepId == true so the movie won't
1977       // be removed from the movie table)
1978       idMovie = AddMovie(strFilenameAndPath);
1979       if (idMovie < 0)
1980       {
1981         CommitTransaction();
1982         return idMovie;
1983       }
1984     }
1985
1986     vector<int> vecDirectors;
1987     vector<int> vecGenres;
1988     vector<int> vecStudios;
1989     AddGenreAndDirectorsAndStudios(details,vecDirectors,vecGenres,vecStudios);
1990
1991     for (unsigned int i = 0; i < vecGenres.size(); ++i)
1992       AddGenreToMovie(idMovie, vecGenres[i]);
1993
1994     for (unsigned int i = 0; i < vecDirectors.size(); ++i)
1995       AddDirectorToMovie(idMovie, vecDirectors[i]);
1996
1997     for (unsigned int i = 0; i < vecStudios.size(); ++i)
1998       AddStudioToMovie(idMovie, vecStudios[i]);
1999
2000     // add writers...
2001     for (unsigned int i = 0; i < details.m_writingCredits.size(); i++)
2002       AddWriterToMovie(idMovie, AddActor(details.m_writingCredits[i],""));
2003
2004     // add cast...
2005     int order = 0;
2006     for (CVideoInfoTag::iCast it = details.m_cast.begin(); it != details.m_cast.end(); ++it)
2007     {
2008       int idActor = AddActor(it->strName, it->thumbUrl.m_xml, it->thumb);
2009       AddActorToMovie(idMovie, idActor, it->strRole, order++);
2010     }
2011
2012     // add set...
2013     int idSet = -1;
2014     if (!details.m_strSet.empty())
2015     {
2016       idSet = AddSet(details.m_strSet);
2017       // add art if not available
2018       map<string, string> setArt;
2019       if (!GetArtForItem(idSet, "set", setArt))
2020         SetArtForItem(idSet, "set", artwork);
2021     }
2022
2023     // add tags...
2024     for (unsigned int i = 0; i < details.m_tags.size(); i++)
2025     {
2026       int idTag = AddTag(details.m_tags[i]);
2027       AddTagToItem(idMovie, idTag, "movie");
2028     }
2029
2030     // add countries...
2031     for (unsigned int i = 0; i < details.m_country.size(); i++)
2032       AddCountryToMovie(idMovie, AddCountry(details.m_country[i]));
2033
2034     if (details.HasStreamDetails())
2035       SetStreamDetailsForFileId(details.m_streamDetails, GetFileId(strFilenameAndPath));
2036
2037     SetArtForItem(idMovie, "movie", artwork);
2038
2039     // update our movie table (we know it was added already above)
2040     // and insert the new row
2041     CStdString sql = "update movie set " + GetValueString(details, VIDEODB_ID_MIN, VIDEODB_ID_MAX, DbMovieOffsets);
2042     if (idSet > 0)
2043       sql += PrepareSQL(", idSet = %i", idSet);
2044     else
2045       sql += ", idSet = NULL";
2046     sql += PrepareSQL(" where idMovie=%i", idMovie);
2047     m_pDS->exec(sql.c_str());
2048     CommitTransaction();
2049
2050     return idMovie;
2051   }
2052   catch (...)
2053   {
2054     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
2055   }
2056   return -1;
2057 }
2058
2059 int CVideoDatabase::SetDetailsForTvShow(const CStdString& strPath, const CVideoInfoTag& details, const map<string, string> &artwork, const map<int, map<string, string> > &seasonArt, int idTvShow /*= -1 */)
2060 {
2061   try
2062   {
2063     if (!m_pDB.get() || !m_pDS.get())
2064     {
2065       CLog::Log(LOGERROR, "%s: called without database open", __FUNCTION__);
2066       return -1;
2067     }
2068
2069     BeginTransaction();
2070
2071     if (idTvShow < 0)
2072       idTvShow = GetTvShowId(strPath);
2073
2074     if (idTvShow > -1)
2075       DeleteDetailsForTvShow(strPath, idTvShow);
2076     else
2077     {
2078       idTvShow = AddTvShow(strPath);
2079       if (idTvShow < 0)
2080       {
2081         CommitTransaction();
2082         return idTvShow;
2083       }
2084     }
2085
2086     vector<int> vecDirectors;
2087     vector<int> vecGenres;
2088     vector<int> vecStudios;
2089     AddGenreAndDirectorsAndStudios(details,vecDirectors,vecGenres,vecStudios);
2090
2091     // add cast...
2092     int order = 0;
2093     for (CVideoInfoTag::iCast it = details.m_cast.begin(); it != details.m_cast.end(); ++it)
2094     {
2095       int idActor = AddActor(it->strName, it->thumbUrl.m_xml, it->thumb);
2096       AddActorToTvShow(idTvShow, idActor, it->strRole, order++);
2097     }
2098
2099     unsigned int i;
2100     for (i = 0; i < vecGenres.size(); ++i)
2101       AddGenreToTvShow(idTvShow, vecGenres[i]);
2102
2103     for (i = 0; i < vecDirectors.size(); ++i)
2104       AddDirectorToTvShow(idTvShow, vecDirectors[i]);
2105
2106     for (i = 0; i < vecStudios.size(); ++i)
2107       AddStudioToTvShow(idTvShow, vecStudios[i]);
2108
2109     // add tags...
2110     for (unsigned int i = 0; i < details.m_tags.size(); i++)
2111     {
2112       int idTag = AddTag(details.m_tags[i]);
2113       AddTagToItem(idTvShow, idTag, "tvshow");
2114     }
2115
2116     // add "all seasons" - the rest are added in SetDetailsForEpisode
2117     AddSeason(idTvShow, -1);
2118
2119     SetArtForItem(idTvShow, "tvshow", artwork);
2120     for (map<int, map<string, string> >::const_iterator i = seasonArt.begin(); i != seasonArt.end(); ++i)
2121     {
2122       int idSeason = AddSeason(idTvShow, i->first);
2123       if (idSeason > -1)
2124         SetArtForItem(idSeason, "season", i->second);
2125     }
2126
2127     // and insert the new row
2128     CStdString sql = "update tvshow set " + GetValueString(details, VIDEODB_ID_TV_MIN, VIDEODB_ID_TV_MAX, DbTvShowOffsets);
2129     sql += PrepareSQL(" where idShow=%i", idTvShow);
2130     m_pDS->exec(sql.c_str());
2131
2132     CommitTransaction();
2133
2134     return idTvShow;
2135   }
2136   catch (...)
2137   {
2138     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strPath.c_str());
2139   }
2140
2141   return -1;
2142 }
2143
2144 int CVideoDatabase::SetDetailsForEpisode(const CStdString& strFilenameAndPath, const CVideoInfoTag& details, const map<string, string> &artwork, int idShow, int idEpisode)
2145 {
2146   try
2147   {
2148     BeginTransaction();
2149     if (idEpisode < 0)
2150       idEpisode = GetEpisodeId(strFilenameAndPath);
2151
2152     if (idEpisode > 0)
2153       DeleteEpisode(strFilenameAndPath, idEpisode, true); // true to keep the table entry
2154     else
2155     {
2156       // only add a new episode if we don't already have a valid idEpisode
2157       // (DeleteEpisode is called with bKeepId == true so the episode won't
2158       // be removed from the episode table)
2159       idEpisode = AddEpisode(idShow,strFilenameAndPath);
2160       if (idEpisode < 0)
2161       {
2162         CommitTransaction();
2163         return -1;
2164       }
2165     }
2166
2167     vector<int> vecDirectors;
2168     vector<int> vecGenres;
2169     vector<int> vecStudios;
2170     AddGenreAndDirectorsAndStudios(details,vecDirectors,vecGenres,vecStudios);
2171
2172     // add cast...
2173     int order = 0;
2174     for (CVideoInfoTag::iCast it = details.m_cast.begin(); it != details.m_cast.end(); ++it)
2175     {
2176       int idActor = AddActor(it->strName, it->thumbUrl.m_xml, it->thumb);
2177       AddActorToEpisode(idEpisode, idActor, it->strRole, order++);
2178     }
2179
2180     // add writers...
2181     for (unsigned int i = 0; i < details.m_writingCredits.size(); i++)
2182       AddWriterToEpisode(idEpisode, AddActor(details.m_writingCredits[i],""));
2183
2184     for (unsigned int i = 0; i < vecDirectors.size(); ++i)
2185     {
2186       AddDirectorToEpisode(idEpisode, vecDirectors[i]);
2187     }
2188
2189     if (details.HasStreamDetails())
2190     {
2191       if (details.m_iFileId != -1)
2192         SetStreamDetailsForFileId(details.m_streamDetails, details.m_iFileId);
2193       else
2194         SetStreamDetailsForFile(details.m_streamDetails, strFilenameAndPath);
2195     }
2196
2197     // ensure we have this season already added
2198     AddSeason(idShow, details.m_iSeason);
2199
2200     SetArtForItem(idEpisode, "episode", artwork);
2201
2202     // and insert the new row
2203     CStdString sql = "update episode set " + GetValueString(details, VIDEODB_ID_EPISODE_MIN, VIDEODB_ID_EPISODE_MAX, DbEpisodeOffsets);
2204     sql += PrepareSQL(" where idEpisode=%i", idEpisode);
2205     m_pDS->exec(sql.c_str());
2206     CommitTransaction();
2207
2208     return idEpisode;
2209   }
2210   catch (...)
2211   {
2212     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
2213   }
2214   return -1;
2215 }
2216
2217 int CVideoDatabase::GetSeasonId(int showID, int season)
2218 {
2219   CStdString sql = PrepareSQL("idShow=%i AND season=%i", showID, season);
2220   CStdString id = GetSingleValue("seasons", "idSeason", sql);
2221   if (id.IsEmpty())
2222     return -1;
2223   return strtol(id.c_str(), NULL, 10);
2224 }
2225
2226 int CVideoDatabase::AddSeason(int showID, int season)
2227 {
2228   int seasonId = GetSeasonId(showID, season);
2229   if (seasonId < 0)
2230   {
2231     if (ExecuteQuery(PrepareSQL("INSERT INTO seasons (idShow,season) VALUES(%i,%i)", showID, season)))
2232       seasonId = (int)m_pDS->lastinsertid();
2233   }
2234   return seasonId;
2235 }
2236
2237 int CVideoDatabase::SetDetailsForMusicVideo(const CStdString& strFilenameAndPath, const CVideoInfoTag& details, const map<string, string> &artwork, int idMVideo /* = -1 */)
2238 {
2239   try
2240   {
2241     BeginTransaction();
2242
2243     if (idMVideo < 0)
2244       idMVideo = GetMusicVideoId(strFilenameAndPath);
2245
2246     if (idMVideo > -1)
2247       DeleteMusicVideo(strFilenameAndPath, true, idMVideo); // Keep id
2248     else
2249     {
2250       // only add a new musicvideo if we don't already have a valid idMVideo
2251       // (DeleteMusicVideo is called with bKeepId == true so the musicvideo won't
2252       // be removed from the musicvideo table)
2253       idMVideo = AddMusicVideo(strFilenameAndPath);
2254       if (idMVideo < 0)
2255       {
2256         CommitTransaction();
2257         return -1;
2258       }
2259     }
2260
2261     vector<int> vecDirectors;
2262     vector<int> vecGenres;
2263     vector<int> vecStudios;
2264     AddGenreAndDirectorsAndStudios(details,vecDirectors,vecGenres,vecStudios);
2265
2266     // add artists...
2267     if (!details.m_artist.empty())
2268     {
2269       for (unsigned int i = 0; i < details.m_artist.size(); i++)
2270       {
2271         CStdString artist = details.m_artist[i];
2272         artist.Trim();
2273         int idArtist = AddActor(artist,"");
2274         AddArtistToMusicVideo(idMVideo, idArtist);
2275       }
2276     }
2277
2278     unsigned int i;
2279     for (i = 0; i < vecGenres.size(); ++i)
2280     {
2281       AddGenreToMusicVideo(idMVideo, vecGenres[i]);
2282     }
2283
2284     for (i = 0; i < vecDirectors.size(); ++i)
2285     {
2286       AddDirectorToMusicVideo(idMVideo, vecDirectors[i]);
2287     }
2288
2289     for (i = 0; i < vecStudios.size(); ++i)
2290     {
2291       AddStudioToMusicVideo(idMVideo, vecStudios[i]);
2292     }
2293
2294     // add tags...
2295     for (unsigned int i = 0; i < details.m_tags.size(); i++)
2296     {
2297       int idTag = AddTag(details.m_tags[i]);
2298       AddTagToItem(idMVideo, idTag, "musicvideo");
2299     }
2300
2301     if (details.HasStreamDetails())
2302       SetStreamDetailsForFileId(details.m_streamDetails, GetFileId(strFilenameAndPath));
2303
2304     SetArtForItem(idMVideo, "musicvideo", artwork);
2305
2306     // update our movie table (we know it was added already above)
2307     // and insert the new row
2308     CStdString sql = "update musicvideo set " + GetValueString(details, VIDEODB_ID_MUSICVIDEO_MIN, VIDEODB_ID_MUSICVIDEO_MAX, DbMusicVideoOffsets);
2309     sql += PrepareSQL(" where idMVideo=%i", idMVideo);
2310     m_pDS->exec(sql.c_str());
2311     CommitTransaction();
2312
2313     return idMVideo;
2314   }
2315   catch (...)
2316   {
2317     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
2318   }
2319   return -1;
2320 }
2321
2322 void CVideoDatabase::SetStreamDetailsForFile(const CStreamDetails& details, const CStdString &strFileNameAndPath)
2323 {
2324   // AddFile checks to make sure the file isn't already in the DB first
2325   int idFile = AddFile(strFileNameAndPath);
2326   if (idFile < 0)
2327     return;
2328   SetStreamDetailsForFileId(details, idFile);
2329 }
2330
2331 void CVideoDatabase::SetStreamDetailsForFileId(const CStreamDetails& details, int idFile)
2332 {
2333   if (idFile < 0)
2334     return;
2335
2336   try
2337   {
2338     BeginTransaction();
2339     m_pDS->exec(PrepareSQL("DELETE FROM streamdetails WHERE idFile = %i", idFile));
2340
2341     for (int i=1; i<=details.GetVideoStreamCount(); i++)
2342     {
2343       m_pDS->exec(PrepareSQL("INSERT INTO streamdetails "
2344         "(idFile, iStreamType, strVideoCodec, fVideoAspect, iVideoWidth, iVideoHeight, iVideoDuration) "
2345         "VALUES (%i,%i,'%s',%f,%i,%i,%i)",
2346         idFile, (int)CStreamDetail::VIDEO,
2347         details.GetVideoCodec(i).c_str(), details.GetVideoAspect(i),
2348         details.GetVideoWidth(i), details.GetVideoHeight(i), details.GetVideoDuration(i)));
2349     }
2350     for (int i=1; i<=details.GetAudioStreamCount(); i++)
2351     {
2352       m_pDS->exec(PrepareSQL("INSERT INTO streamdetails "
2353         "(idFile, iStreamType, strAudioCodec, iAudioChannels, strAudioLanguage) "
2354         "VALUES (%i,%i,'%s',%i,'%s')",
2355         idFile, (int)CStreamDetail::AUDIO,
2356         details.GetAudioCodec(i).c_str(), details.GetAudioChannels(i),
2357         details.GetAudioLanguage(i).c_str()));
2358     }
2359     for (int i=1; i<=details.GetSubtitleStreamCount(); i++)
2360     {
2361       m_pDS->exec(PrepareSQL("INSERT INTO streamdetails "
2362         "(idFile, iStreamType, strSubtitleLanguage) "
2363         "VALUES (%i,%i,'%s')",
2364         idFile, (int)CStreamDetail::SUBTITLE,
2365         details.GetSubtitleLanguage(i).c_str()));
2366     }
2367
2368     // update the runtime information, if empty
2369     if (details.GetVideoDuration())
2370     {
2371       vector< pair<string, int> > tables;
2372       tables.push_back(make_pair("movie", VIDEODB_ID_RUNTIME));
2373       tables.push_back(make_pair("episode", VIDEODB_ID_EPISODE_RUNTIME));
2374       tables.push_back(make_pair("musicvideo", VIDEODB_ID_MUSICVIDEO_RUNTIME));
2375       for (vector< pair<string, int> >::iterator i = tables.begin(); i != tables.end(); ++i)
2376       {
2377         CStdString sql = PrepareSQL("update %s set c%02d=%d where idFile=%d and c%02d=''",
2378                                     i->first.c_str(), i->second, details.GetVideoDuration(), idFile, i->second);
2379         m_pDS->exec(sql);
2380       }
2381     }
2382
2383     CommitTransaction();
2384   }
2385   catch (...)
2386   {
2387     RollbackTransaction();
2388     CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, idFile);
2389   }
2390 }
2391
2392 //********************************************************************************************************************************
2393 void CVideoDatabase::GetFilePathById(int idMovie, CStdString &filePath, VIDEODB_CONTENT_TYPE iType)
2394 {
2395   try
2396   {
2397     if (NULL == m_pDB.get()) return ;
2398     if (NULL == m_pDS.get()) return ;
2399
2400     if (idMovie < 0) return ;
2401
2402     CStdString strSQL;
2403     if (iType == VIDEODB_CONTENT_MOVIES)
2404       strSQL=PrepareSQL("select path.strPath,files.strFileName from path, files, movie where path.idPath=files.idPath and files.idFile=movie.idFile and movie.idMovie=%i order by strFilename", idMovie );
2405     if (iType == VIDEODB_CONTENT_EPISODES)
2406       strSQL=PrepareSQL("select path.strPath,files.strFileName from path, files, episode where path.idPath=files.idPath and files.idFile=episode.idFile and episode.idEpisode=%i order by strFilename", idMovie );
2407     if (iType == VIDEODB_CONTENT_TVSHOWS)
2408       strSQL=PrepareSQL("select path.strPath from path,tvshowlinkpath where path.idPath=tvshowlinkpath.idPath and tvshowlinkpath.idShow=%i", idMovie );
2409     if (iType ==VIDEODB_CONTENT_MUSICVIDEOS)
2410       strSQL=PrepareSQL("select path.strPath,files.strFileName from path, files, musicvideo where path.idPath=files.idPath and files.idFile=musicvideo.idFile and musicvideo.idMVideo=%i order by strFilename", idMovie );
2411
2412     m_pDS->query( strSQL.c_str() );
2413     if (!m_pDS->eof())
2414     {
2415       if (iType != VIDEODB_CONTENT_TVSHOWS)
2416       {
2417         CStdString fileName = m_pDS->fv("files.strFilename").get_asString();
2418         ConstructPath(filePath,m_pDS->fv("path.strPath").get_asString(),fileName);
2419       }
2420       else
2421         filePath = m_pDS->fv("path.strPath").get_asString();
2422     }
2423     m_pDS->close();
2424   }
2425   catch (...)
2426   {
2427     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
2428   }
2429 }
2430
2431 //********************************************************************************************************************************
2432 void CVideoDatabase::GetBookMarksForFile(const CStdString& strFilenameAndPath, VECBOOKMARKS& bookmarks, CBookmark::EType type /*= CBookmark::STANDARD*/, bool bAppend, long partNumber)
2433 {
2434   try
2435   {
2436     if (URIUtils::IsStack(strFilenameAndPath) && CFileItem(CStackDirectory::GetFirstStackedFile(strFilenameAndPath),false).IsDVDImage())
2437     {
2438       CStackDirectory dir;
2439       CFileItemList fileList;
2440       dir.GetDirectory(strFilenameAndPath, fileList);
2441       if (!bAppend)
2442         bookmarks.clear();
2443       for (int i = fileList.Size() - 1; i >= 0; i--) // put the bookmarks of the highest part first in the list
2444         GetBookMarksForFile(fileList[i]->GetPath(), bookmarks, type, true, (i+1));
2445     }
2446     else
2447     {
2448       int idFile = GetFileId(strFilenameAndPath);
2449       if (idFile < 0) return ;
2450       if (!bAppend)
2451         bookmarks.erase(bookmarks.begin(), bookmarks.end());
2452       if (NULL == m_pDB.get()) return ;
2453       if (NULL == m_pDS.get()) return ;
2454
2455       CStdString strSQL=PrepareSQL("select * from bookmark where idFile=%i and type=%i order by timeInSeconds", idFile, (int)type);
2456       m_pDS->query( strSQL.c_str() );
2457       while (!m_pDS->eof())
2458       {
2459         CBookmark bookmark;
2460         bookmark.timeInSeconds = m_pDS->fv("timeInSeconds").get_asDouble();
2461         bookmark.partNumber = partNumber;
2462         bookmark.totalTimeInSeconds = m_pDS->fv("totalTimeInSeconds").get_asDouble();
2463         bookmark.thumbNailImage = m_pDS->fv("thumbNailImage").get_asString();
2464         bookmark.playerState = m_pDS->fv("playerState").get_asString();
2465         bookmark.player = m_pDS->fv("player").get_asString();
2466         bookmark.type = type;
2467         if (type == CBookmark::EPISODE)
2468         {
2469           CStdString strSQL2=PrepareSQL("select c%02d, c%02d from episode where c%02d=%i order by c%02d, c%02d", VIDEODB_ID_EPISODE_EPISODE, VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_EPISODE_BOOKMARK, m_pDS->fv("idBookmark").get_asInt(), VIDEODB_ID_EPISODE_SORTSEASON, VIDEODB_ID_EPISODE_SORTEPISODE);
2470           m_pDS2->query(strSQL2.c_str());
2471           bookmark.episodeNumber = m_pDS2->fv(0).get_asInt();
2472           bookmark.seasonNumber = m_pDS2->fv(1).get_asInt();
2473           m_pDS2->close();
2474         }
2475         bookmarks.push_back(bookmark);
2476         m_pDS->next();
2477       }
2478       //sort(bookmarks.begin(), bookmarks.end(), SortBookmarks);
2479       m_pDS->close();
2480     }
2481   }
2482   catch (...)
2483   {
2484     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
2485   }
2486 }
2487
2488 bool CVideoDatabase::GetResumeBookMark(const CStdString& strFilenameAndPath, CBookmark &bookmark)
2489 {
2490   VECBOOKMARKS bookmarks;
2491   GetBookMarksForFile(strFilenameAndPath, bookmarks, CBookmark::RESUME);
2492   if (bookmarks.size() > 0)
2493   {
2494     bookmark = bookmarks[0];
2495     return true;
2496   }
2497   return false;
2498 }
2499
2500 void CVideoDatabase::DeleteResumeBookMark(const CStdString &strFilenameAndPath)
2501 {
2502   if (!m_pDB.get() || !m_pDS.get())
2503     return;
2504
2505   int fileID = GetFileId(strFilenameAndPath);
2506   if (fileID < -1)
2507     return;
2508
2509   try
2510   {
2511     CStdString sql = PrepareSQL("delete from bookmark where idFile=%i and type=%i", fileID, CBookmark::RESUME);
2512     m_pDS->exec(sql.c_str());
2513   }
2514   catch(...)
2515   {
2516     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
2517   }
2518 }
2519
2520 void CVideoDatabase::GetEpisodesByFile(const CStdString& strFilenameAndPath, vector<CVideoInfoTag>& episodes)
2521 {
2522   try
2523   {
2524     CStdString strSQL = PrepareSQL("select * from episodeview where idFile=%i order by c%02d, c%02d asc", GetFileId(strFilenameAndPath), VIDEODB_ID_EPISODE_SORTSEASON, VIDEODB_ID_EPISODE_SORTEPISODE);
2525     m_pDS->query(strSQL.c_str());
2526     while (!m_pDS->eof())
2527     {
2528       episodes.push_back(GetDetailsForEpisode(m_pDS));
2529       m_pDS->next();
2530     }
2531     m_pDS->close();
2532   }
2533   catch (...)
2534   {
2535     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
2536   }
2537 }
2538
2539 //********************************************************************************************************************************
2540 void CVideoDatabase::AddBookMarkToFile(const CStdString& strFilenameAndPath, const CBookmark &bookmark, CBookmark::EType type /*= CBookmark::STANDARD*/)
2541 {
2542   try
2543   {
2544     int idFile = AddFile(strFilenameAndPath);
2545     if (idFile < 0)
2546       return;
2547     if (NULL == m_pDB.get()) return ;
2548     if (NULL == m_pDS.get()) return ;
2549
2550     CStdString strSQL;
2551     int idBookmark=-1;
2552     if (type == CBookmark::RESUME) // get the same resume mark bookmark each time type
2553     {
2554       strSQL=PrepareSQL("select idBookmark from bookmark where idFile=%i and type=1", idFile);
2555     }
2556     else if (type == CBookmark::STANDARD) // get the same bookmark again, and update. not sure here as a dvd can have same time in multiple places, state will differ thou
2557     {
2558       /* get a bookmark within the same time as previous */
2559       double mintime = bookmark.timeInSeconds - 0.5f;
2560       double maxtime = bookmark.timeInSeconds + 0.5f;
2561       strSQL=PrepareSQL("select idBookmark from bookmark where idFile=%i and type=%i and (timeInSeconds between %f and %f) and playerState='%s'", idFile, (int)type, mintime, maxtime, bookmark.playerState.c_str());
2562     }
2563
2564     if (type != CBookmark::EPISODE)
2565     {
2566       // get current id
2567       m_pDS->query( strSQL.c_str() );
2568       if (m_pDS->num_rows() != 0)
2569         idBookmark = m_pDS->get_field_value("idBookmark").get_asInt();
2570       m_pDS->close();
2571     }
2572     // update or insert depending if it existed before
2573     if (idBookmark >= 0 )
2574       strSQL=PrepareSQL("update bookmark set timeInSeconds = %f, totalTimeInSeconds = %f, thumbNailImage = '%s', player = '%s', playerState = '%s' where idBookmark = %i", bookmark.timeInSeconds, bookmark.totalTimeInSeconds, bookmark.thumbNailImage.c_str(), bookmark.player.c_str(), bookmark.playerState.c_str(), idBookmark);
2575     else
2576       strSQL=PrepareSQL("insert into bookmark (idBookmark, idFile, timeInSeconds, totalTimeInSeconds, thumbNailImage, player, playerState, type) values(NULL,%i,%f,%f,'%s','%s','%s', %i)", idFile, bookmark.timeInSeconds, bookmark.totalTimeInSeconds, bookmark.thumbNailImage.c_str(), bookmark.player.c_str(), bookmark.playerState.c_str(), (int)type);
2577
2578     m_pDS->exec(strSQL.c_str());
2579   }
2580   catch (...)
2581   {
2582     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
2583   }
2584 }
2585
2586 void CVideoDatabase::ClearBookMarkOfFile(const CStdString& strFilenameAndPath, CBookmark& bookmark, CBookmark::EType type /*= CBookmark::STANDARD*/)
2587 {
2588   try
2589   {
2590     int idFile = GetFileId(strFilenameAndPath);
2591     if (idFile < 0) return ;
2592     if (NULL == m_pDB.get()) return ;
2593     if (NULL == m_pDS.get()) return ;
2594
2595     /* a litle bit uggly, we clear first bookmark that is within one second of given */
2596     /* should be no problem since we never add bookmarks that are closer than that   */
2597     double mintime = bookmark.timeInSeconds - 0.5f;
2598     double maxtime = bookmark.timeInSeconds + 0.5f;
2599     CStdString strSQL = PrepareSQL("select idBookmark from bookmark where idFile=%i and type=%i and playerState like '%s' and player like '%s' and (timeInSeconds between %f and %f)", idFile, type, bookmark.playerState.c_str(), bookmark.player.c_str(), mintime, maxtime);
2600
2601     m_pDS->query( strSQL.c_str() );
2602     if (m_pDS->num_rows() != 0)
2603     {
2604       int idBookmark = m_pDS->get_field_value("idBookmark").get_asInt();
2605       strSQL=PrepareSQL("delete from bookmark where idBookmark=%i",idBookmark);
2606       m_pDS->exec(strSQL.c_str());
2607       if (type == CBookmark::EPISODE)
2608       {
2609         strSQL=PrepareSQL("update episode set c%02d=-1 where idFile=%i and c%02d=%i", VIDEODB_ID_EPISODE_BOOKMARK, idFile, VIDEODB_ID_EPISODE_BOOKMARK, idBookmark);
2610         m_pDS->exec(strSQL.c_str());
2611       }
2612     }
2613
2614     m_pDS->close();
2615   }
2616   catch (...)
2617   {
2618     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
2619   }
2620 }
2621
2622 //********************************************************************************************************************************
2623 void CVideoDatabase::ClearBookMarksOfFile(const CStdString& strFilenameAndPath, CBookmark::EType type /*= CBookmark::STANDARD*/)
2624 {
2625   try
2626   {
2627     int idFile = GetFileId(strFilenameAndPath);
2628     if (idFile < 0) return ;
2629     if (NULL == m_pDB.get()) return ;
2630     if (NULL == m_pDS.get()) return ;
2631
2632     CStdString strSQL=PrepareSQL("delete from bookmark where idFile=%i and type=%i", idFile, (int)type);
2633     m_pDS->exec(strSQL.c_str());
2634     if (type == CBookmark::EPISODE)
2635     {
2636       strSQL=PrepareSQL("update episode set c%02d=-1 where idFile=%i", VIDEODB_ID_EPISODE_BOOKMARK, idFile);
2637       m_pDS->exec(strSQL.c_str());
2638     }
2639   }
2640   catch (...)
2641   {
2642     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
2643   }
2644 }
2645
2646
2647 bool CVideoDatabase::GetBookMarkForEpisode(const CVideoInfoTag& tag, CBookmark& bookmark)
2648 {
2649   try
2650   {
2651     CStdString strSQL = PrepareSQL("select bookmark.* from bookmark join episode on episode.c%02d=bookmark.idBookmark where episode.idEpisode=%i", VIDEODB_ID_EPISODE_BOOKMARK, tag.m_iDbId);
2652     m_pDS->query( strSQL.c_str() );
2653     if (!m_pDS->eof())
2654     {
2655       bookmark.timeInSeconds = m_pDS->fv("timeInSeconds").get_asDouble();
2656       bookmark.totalTimeInSeconds = m_pDS->fv("totalTimeInSeconds").get_asDouble();
2657       bookmark.thumbNailImage = m_pDS->fv("thumbNailImage").get_asString();
2658       bookmark.playerState = m_pDS->fv("playerState").get_asString();
2659       bookmark.player = m_pDS->fv("player").get_asString();
2660       bookmark.type = (CBookmark::EType)m_pDS->fv("type").get_asInt();
2661     }
2662     else
2663     {
2664       m_pDS->close();
2665       return false;
2666     }
2667     m_pDS->close();
2668   }
2669   catch (...)
2670   {
2671     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
2672     return false;
2673   }
2674   return true;
2675 }
2676
2677 void CVideoDatabase::AddBookMarkForEpisode(const CVideoInfoTag& tag, const CBookmark& bookmark)
2678 {
2679   try
2680   {
2681     int idFile = GetFileId(tag.m_strFileNameAndPath);
2682     // delete the current episode for the selected episode number
2683     CStdString strSQL = PrepareSQL("delete from bookmark where idBookmark in (select c%02d from episode where c%02d=%i and c%02d=%i and idFile=%i)", VIDEODB_ID_EPISODE_BOOKMARK, VIDEODB_ID_EPISODE_SEASON, tag.m_iSeason, VIDEODB_ID_EPISODE_EPISODE, tag.m_iEpisode, idFile);
2684     m_pDS->exec(strSQL.c_str());
2685
2686     AddBookMarkToFile(tag.m_strFileNameAndPath, bookmark, CBookmark::EPISODE);
2687     int idBookmark = (int)m_pDS->lastinsertid();
2688     strSQL = PrepareSQL("update episode set c%02d=%i where c%02d=%i and c%02d=%i and idFile=%i", VIDEODB_ID_EPISODE_BOOKMARK, idBookmark, VIDEODB_ID_EPISODE_SEASON, tag.m_iSeason, VIDEODB_ID_EPISODE_EPISODE, tag.m_iEpisode, idFile);
2689     m_pDS->exec(strSQL.c_str());
2690   }
2691   catch (...)
2692   {
2693     CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, tag.m_iDbId);
2694   }
2695 }
2696
2697 void CVideoDatabase::DeleteBookMarkForEpisode(const CVideoInfoTag& tag)
2698 {
2699   try
2700   {
2701     CStdString strSQL = PrepareSQL("delete from bookmark where idBookmark in (select c%02d from episode where idEpisode=%i)", VIDEODB_ID_EPISODE_BOOKMARK, tag.m_iDbId);
2702     m_pDS->exec(strSQL.c_str());
2703     strSQL = PrepareSQL("update episode set c%02d=-1 where idEpisode=%i", VIDEODB_ID_EPISODE_BOOKMARK, tag.m_iDbId);
2704     m_pDS->exec(strSQL.c_str());
2705   }
2706   catch (...)
2707   {
2708     CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, tag.m_iDbId);
2709   }
2710 }
2711
2712 //********************************************************************************************************************************
2713 void CVideoDatabase::DeleteMovie(int idMovie, bool bKeepId /* = false */)
2714 {
2715   if (idMovie < 0)
2716     return;
2717
2718   CStdString path;
2719   GetFilePathById(idMovie, path, VIDEODB_CONTENT_MOVIES);
2720   if (!path.empty())
2721     DeleteMovie(path, bKeepId, idMovie);
2722 }
2723
2724 void CVideoDatabase::DeleteMovie(const CStdString& strFilenameAndPath, bool bKeepId /* = false */, int idMovie /* = -1 */)
2725 {
2726   try
2727   {
2728     if (NULL == m_pDB.get()) return ;
2729     if (NULL == m_pDS.get()) return ;
2730     if (idMovie < 0)
2731     {
2732       idMovie = GetMovieId(strFilenameAndPath);
2733       if (idMovie < 0)
2734         return;
2735     }
2736
2737     BeginTransaction();
2738
2739     CStdString strSQL;
2740     strSQL=PrepareSQL("delete from genrelinkmovie where idMovie=%i", idMovie);
2741     m_pDS->exec(strSQL.c_str());
2742
2743     strSQL=PrepareSQL("delete from actorlinkmovie where idMovie=%i", idMovie);
2744     m_pDS->exec(strSQL.c_str());
2745
2746     strSQL=PrepareSQL("delete from directorlinkmovie where idMovie=%i", idMovie);
2747     m_pDS->exec(strSQL.c_str());
2748
2749     strSQL=PrepareSQL("delete from studiolinkmovie where idMovie=%i", idMovie);
2750     m_pDS->exec(strSQL.c_str());
2751
2752     strSQL=PrepareSQL("delete from countrylinkmovie where idMovie=%i", idMovie);
2753     m_pDS->exec(strSQL.c_str());
2754
2755     DeleteStreamDetails(GetFileId(strFilenameAndPath));
2756
2757     // keep the movie table entry, linking to tv shows, and bookmarks
2758     // so we can update the data in place
2759     // the ancilliary tables are still purged
2760     if (!bKeepId)
2761     {
2762       ClearBookMarksOfFile(strFilenameAndPath);
2763
2764       strSQL=PrepareSQL("delete from movie where idMovie=%i", idMovie);
2765       m_pDS->exec(strSQL.c_str());
2766
2767       strSQL=PrepareSQL("delete from movielinktvshow where idMovie=%i", idMovie);
2768       m_pDS->exec(strSQL.c_str());
2769     }
2770
2771     //TODO: move this below CommitTransaction() once UPnP doesn't rely on this anymore
2772     if (!bKeepId)
2773       AnnounceRemove("movie", idMovie);
2774
2775     CStdString strPath, strFileName;
2776     SplitPath(strFilenameAndPath,strPath,strFileName);
2777     InvalidatePathHash(strPath);
2778     CommitTransaction();
2779
2780   }
2781   catch (...)
2782   {
2783     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
2784   }
2785 }
2786
2787 void CVideoDatabase::DeleteTvShow(int idTvShow, bool bKeepId /* = false */)
2788 {
2789   if (idTvShow < 0)
2790     return;
2791
2792   CStdString path;
2793   GetFilePathById(idTvShow, path, VIDEODB_CONTENT_TVSHOWS);
2794   if (!path.empty())
2795     DeleteTvShow(path, bKeepId, idTvShow);
2796 }
2797
2798 void CVideoDatabase::DeleteTvShow(const CStdString& strPath, bool bKeepId /* = false */, int idTvShow /* = -1 */)
2799 {
2800   try
2801   {
2802     if (NULL == m_pDB.get()) return ;
2803     if (NULL == m_pDS.get()) return ;
2804     if (idTvShow < 0)
2805     {
2806       idTvShow = GetTvShowId(strPath);
2807       if (idTvShow < 0)
2808         return;
2809     }
2810
2811     BeginTransaction();
2812
2813     CStdString strSQL=PrepareSQL("select episode.idEpisode,path.strPath,files.strFileName from episode,path,files where episode.idShow=%i and episode.idFile=files.idFile and files.idPath=path.idPath",idTvShow);
2814     m_pDS2->query(strSQL.c_str());
2815     while (!m_pDS2->eof())
2816     {
2817       CStdString strFilenameAndPath;
2818       CStdString strPath = m_pDS2->fv("path.strPath").get_asString();
2819       CStdString strFileName = m_pDS2->fv("files.strFilename").get_asString();
2820       ConstructPath(strFilenameAndPath, strPath, strFileName);
2821       DeleteEpisode(strFilenameAndPath, m_pDS2->fv(0).get_asInt(), bKeepId);
2822       m_pDS2->next();
2823     }
2824
2825     DeleteDetailsForTvShow(strPath, idTvShow);
2826
2827     strSQL=PrepareSQL("delete from seasons where idShow=%i", idTvShow);
2828     m_pDS->exec(strSQL.c_str());
2829
2830     // keep tvshow table and movielink table so we can update data in place
2831     if (!bKeepId)
2832     {
2833       strSQL=PrepareSQL("delete from tvshow where idShow=%i", idTvShow);
2834       m_pDS->exec(strSQL.c_str());
2835
2836       strSQL=PrepareSQL("delete from tvshowlinkpath where idShow=%i", idTvShow);
2837       m_pDS->exec(strSQL.c_str());
2838
2839       strSQL=PrepareSQL("delete from movielinktvshow where idShow=%i", idTvShow);
2840       m_pDS->exec(strSQL.c_str());
2841     }
2842
2843     //TODO: move this below CommitTransaction() once UPnP doesn't rely on this anymore
2844     if (!bKeepId)
2845       AnnounceRemove("tvshow", idTvShow);
2846
2847     InvalidatePathHash(strPath);
2848
2849     CommitTransaction();
2850
2851   }
2852   catch (...)
2853   {
2854     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strPath.c_str());
2855   }
2856 }
2857
2858 void CVideoDatabase::DeleteEpisode(int idEpisode, bool bKeepId /* = false */)
2859 {
2860   if (idEpisode < 0)
2861     return;
2862
2863   CStdString path;
2864   GetFilePathById(idEpisode, path, VIDEODB_CONTENT_EPISODES);
2865   if (!path.empty())
2866     DeleteEpisode(path, idEpisode, bKeepId);
2867 }
2868
2869 void CVideoDatabase::DeleteEpisode(const CStdString& strFilenameAndPath, int idEpisode /* = -1 */, bool bKeepId /* = false */)
2870 {
2871   try
2872   {
2873     if (NULL == m_pDB.get()) return ;
2874     if (NULL == m_pDS.get()) return ;
2875     if (idEpisode < 0)
2876     {
2877       idEpisode = GetEpisodeId(strFilenameAndPath);
2878       if (idEpisode < 0)
2879       {
2880         return ;
2881       }
2882     }
2883
2884     //TODO: move this below CommitTransaction() once UPnP doesn't rely on this anymore
2885     if (!bKeepId)
2886       AnnounceRemove("episode", idEpisode);
2887
2888     CStdString strSQL;
2889     strSQL=PrepareSQL("delete from actorlinkepisode where idEpisode=%i", idEpisode);
2890     m_pDS->exec(strSQL.c_str());
2891
2892     strSQL=PrepareSQL("delete from directorlinkepisode where idEpisode=%i", idEpisode);
2893     m_pDS->exec(strSQL.c_str());
2894
2895     strSQL=PrepareSQL("delete from writerlinkepisode where idEpisode=%i", idEpisode);
2896     m_pDS->exec(strSQL.c_str());
2897
2898     DeleteStreamDetails(GetFileId(strFilenameAndPath));
2899
2900     // keep episode table entry and bookmarks so we can update the data in place
2901     // the ancilliary tables are still purged
2902     if (!bKeepId)
2903     {
2904       ClearBookMarksOfFile(strFilenameAndPath);
2905
2906       strSQL=PrepareSQL("delete from episode where idEpisode=%i", idEpisode);
2907       m_pDS->exec(strSQL.c_str());
2908     }
2909
2910   }
2911   catch (...)
2912   {
2913     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
2914   }
2915 }
2916
2917 void CVideoDatabase::DeleteMusicVideo(int idMusicVideo, bool bKeepId /* = false */)
2918 {
2919   if (idMusicVideo < 0)
2920     return;
2921
2922   CStdString path;
2923   GetFilePathById(idMusicVideo, path, VIDEODB_CONTENT_MUSICVIDEOS);
2924   if (!path.empty())
2925     DeleteMusicVideo(path, bKeepId, idMusicVideo);
2926 }
2927
2928 void CVideoDatabase::DeleteMusicVideo(const CStdString& strFilenameAndPath, bool bKeepId /* = false */, int idMVideo /* = -1 */)
2929 {
2930   try
2931   {
2932     if (NULL == m_pDB.get()) return ;
2933     if (NULL == m_pDS.get()) return ;
2934     if (idMVideo < 0)
2935     {
2936       idMVideo = GetMusicVideoId(strFilenameAndPath);
2937       if (idMVideo < 0)
2938         return;
2939     }
2940
2941     BeginTransaction();
2942
2943     CStdString strSQL;
2944     strSQL=PrepareSQL("delete from genrelinkmusicvideo where idMVideo=%i", idMVideo);
2945     m_pDS->exec(strSQL.c_str());
2946
2947     strSQL=PrepareSQL("delete from artistlinkmusicvideo where idMVideo=%i", idMVideo);
2948     m_pDS->exec(strSQL.c_str());
2949
2950     strSQL=PrepareSQL("delete from directorlinkmusicvideo where idMVideo=%i", idMVideo);
2951     m_pDS->exec(strSQL.c_str());
2952
2953     strSQL=PrepareSQL("delete from studiolinkmusicvideo where idMVideo=%i", idMVideo);
2954     m_pDS->exec(strSQL.c_str());
2955
2956     DeleteStreamDetails(GetFileId(strFilenameAndPath));
2957
2958     // keep the music video table entry and bookmarks so we can update data in place
2959     // the ancilliary tables are still purged
2960     if (!bKeepId)
2961     {
2962       ClearBookMarksOfFile(strFilenameAndPath);
2963
2964       strSQL=PrepareSQL("delete from musicvideo where idMVideo=%i", idMVideo);
2965       m_pDS->exec(strSQL.c_str());
2966     }
2967
2968     //TODO: move this below CommitTransaction() once UPnP doesn't rely on this anymore
2969     if (!bKeepId)
2970       AnnounceRemove("musicvideo", idMVideo);
2971
2972     CStdString strPath, strFileName;
2973     SplitPath(strFilenameAndPath,strPath,strFileName);
2974     InvalidatePathHash(strPath);
2975     CommitTransaction();
2976
2977   }
2978   catch (...)
2979   {
2980     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
2981   }
2982 }
2983
2984 void CVideoDatabase::DeleteStreamDetails(int idFile)
2985 {
2986     m_pDS->exec(PrepareSQL("delete from streamdetails where idFile=%i", idFile));
2987 }
2988
2989 void CVideoDatabase::DeleteSet(int idSet)
2990 {
2991   try
2992   {
2993     if (NULL == m_pDB.get()) return ;
2994     if (NULL == m_pDS.get()) return ;
2995
2996     CStdString strSQL;
2997     strSQL=PrepareSQL("delete from sets where idSet = %i", idSet);
2998     m_pDS->exec(strSQL.c_str());
2999     strSQL=PrepareSQL("update movie set idSet = null where idSet = %i", idSet);
3000     m_pDS->exec(strSQL.c_str());
3001   }
3002   catch (...)
3003   {
3004     CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, idSet);
3005   }
3006 }
3007
3008 void CVideoDatabase::DeleteTag(int idTag, VIDEODB_CONTENT_TYPE mediaType)
3009 {
3010   try
3011   {
3012     if (m_pDB.get() == NULL || m_pDS.get() == NULL)
3013       return;
3014
3015     std::string type;
3016     if (mediaType == VIDEODB_CONTENT_MOVIES)
3017       type = "movie";
3018     else if (mediaType == VIDEODB_CONTENT_TVSHOWS)
3019       type = "tvshow";
3020     else if (mediaType == VIDEODB_CONTENT_MUSICVIDEOS)
3021       type = "musicvideo";
3022     else
3023       return;
3024
3025     CStdString strSQL;
3026     strSQL = PrepareSQL("DELETE FROM taglinks WHERE idTag = %i AND media_type = '%s'", idTag, type.c_str());
3027     m_pDS->exec(strSQL.c_str());
3028   }
3029   catch (...)
3030   {
3031     CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, idTag);
3032   }
3033 }
3034
3035 void CVideoDatabase::GetDetailsFromDB(auto_ptr<Dataset> &pDS, int min, int max, const SDbTableOffsets *offsets, CVideoInfoTag &details, int idxOffset)
3036 {
3037   GetDetailsFromDB(pDS->get_sql_record(), min, max, offsets, details, idxOffset);
3038 }
3039
3040 void CVideoDatabase::GetDetailsFromDB(const dbiplus::sql_record* const record, int min, int max, const SDbTableOffsets *offsets, CVideoInfoTag &details, int idxOffset)
3041 {
3042   for (int i = min + 1; i < max; i++)
3043   {
3044     switch (offsets[i].type)
3045     {
3046     case VIDEODB_TYPE_STRING:
3047       *(CStdString*)(((char*)&details)+offsets[i].offset) = record->at(i+idxOffset).get_asString();
3048       break;
3049     case VIDEODB_TYPE_INT:
3050     case VIDEODB_TYPE_COUNT:
3051       *(int*)(((char*)&details)+offsets[i].offset) = record->at(i+idxOffset).get_asInt();
3052       break;
3053     case VIDEODB_TYPE_BOOL:
3054       *(bool*)(((char*)&details)+offsets[i].offset) = record->at(i+idxOffset).get_asBool();
3055       break;
3056     case VIDEODB_TYPE_FLOAT:
3057       *(float*)(((char*)&details)+offsets[i].offset) = record->at(i+idxOffset).get_asFloat();
3058       break;
3059     case VIDEODB_TYPE_STRINGARRAY:
3060       *(std::vector<std::string>*)(((char*)&details)+offsets[i].offset) = StringUtils::Split(record->at(i+idxOffset).get_asString(), g_advancedSettings.m_videoItemSeparator);
3061       break;
3062     case VIDEODB_TYPE_DATE:
3063       ((CDateTime*)(((char*)&details)+offsets[i].offset))->SetFromDBDate(record->at(i+idxOffset).get_asString());
3064       break;
3065     case VIDEODB_TYPE_DATETIME:
3066       ((CDateTime*)(((char*)&details)+offsets[i].offset))->SetFromDBDateTime(record->at(i+idxOffset).get_asString());
3067       break;
3068     }
3069   }
3070 }
3071
3072 DWORD movieTime = 0;
3073 DWORD castTime = 0;
3074
3075 CVideoInfoTag CVideoDatabase::GetDetailsByTypeAndId(VIDEODB_CONTENT_TYPE type, int id)
3076 {
3077   CVideoInfoTag details;
3078   details.Reset();
3079
3080   switch (type)
3081   {
3082     case VIDEODB_CONTENT_MOVIES:
3083       GetMovieInfo("", details, id);
3084       break;
3085     case VIDEODB_CONTENT_TVSHOWS:
3086       GetTvShowInfo("", details, id);
3087       break;
3088     case VIDEODB_CONTENT_EPISODES:
3089       GetEpisodeInfo("", details, id);
3090       break;
3091     case VIDEODB_CONTENT_MUSICVIDEOS:
3092       GetMusicVideoInfo("", details, id);
3093       break;
3094     default:
3095       break;
3096   }
3097
3098   return details;
3099 }
3100
3101 bool CVideoDatabase::GetStreamDetails(CVideoInfoTag& tag) const
3102 {
3103   if (tag.m_iFileId < 0)
3104     return false;
3105
3106   bool retVal = false;
3107
3108   CStreamDetails& details = tag.m_streamDetails;
3109   details.Reset();
3110
3111   auto_ptr<Dataset> pDS(m_pDB->CreateDataset());
3112   try
3113   {
3114     CStdString strSQL = PrepareSQL("SELECT * FROM streamdetails WHERE idFile = %i", tag.m_iFileId);
3115     pDS->query(strSQL);
3116
3117     while (!pDS->eof())
3118     {
3119       CStreamDetail::StreamType e = (CStreamDetail::StreamType)pDS->fv(1).get_asInt();
3120       switch (e)
3121       {
3122       case CStreamDetail::VIDEO:
3123         {
3124           CStreamDetailVideo *p = new CStreamDetailVideo();
3125           p->m_strCodec = pDS->fv(2).get_asString();
3126           p->m_fAspect = pDS->fv(3).get_asFloat();
3127           p->m_iWidth = pDS->fv(4).get_asInt();
3128           p->m_iHeight = pDS->fv(5).get_asInt();
3129           p->m_iDuration = pDS->fv(10).get_asInt();
3130           details.AddStream(p);
3131           retVal = true;
3132           break;
3133         }
3134       case CStreamDetail::AUDIO:
3135         {
3136           CStreamDetailAudio *p = new CStreamDetailAudio();
3137           p->m_strCodec = pDS->fv(6).get_asString();
3138           if (pDS->fv(7).get_isNull())
3139             p->m_iChannels = -1;
3140           else
3141             p->m_iChannels = pDS->fv(7).get_asInt();
3142           p->m_strLanguage = pDS->fv(8).get_asString();
3143           details.AddStream(p);
3144           retVal = true;
3145           break;
3146         }
3147       case CStreamDetail::SUBTITLE:
3148         {
3149           CStreamDetailSubtitle *p = new CStreamDetailSubtitle();
3150           p->m_strLanguage = pDS->fv(9).get_asString();
3151           details.AddStream(p);
3152           retVal = true;
3153           break;
3154         }
3155       }
3156
3157       pDS->next();
3158     }
3159
3160     pDS->close();
3161   }
3162   catch (...)
3163   {
3164     CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, tag.m_iFileId);
3165   }
3166   details.DetermineBestStreams();
3167
3168   if (details.GetVideoDuration() > 0)
3169     tag.m_duration = details.GetVideoDuration();
3170
3171   return retVal;
3172 }
3173  
3174 bool CVideoDatabase::GetResumePoint(CVideoInfoTag& tag)
3175 {
3176   if (tag.m_iFileId < 0)
3177     return false;
3178
3179   bool match = false;
3180
3181   try
3182   {
3183     if (URIUtils::IsStack(tag.m_strFileNameAndPath) && CFileItem(CStackDirectory::GetFirstStackedFile(tag.m_strFileNameAndPath),false).IsDVDImage())
3184     {
3185       CStackDirectory dir;
3186       CFileItemList fileList;
3187       dir.GetDirectory(tag.m_strFileNameAndPath, fileList);
3188       tag.m_resumePoint.Reset();
3189       for (int i = fileList.Size() - 1; i >= 0; i--)
3190       {
3191         CBookmark bookmark;
3192         if (GetResumeBookMark(fileList[i]->GetPath(), bookmark))
3193         {
3194           tag.m_resumePoint = bookmark;
3195           tag.m_resumePoint.partNumber = (i+1); /* store part number in here */
3196           match = true;
3197           break;
3198         }
3199       }
3200     }
3201     else
3202     {
3203       CStdString strSQL=PrepareSQL("select timeInSeconds, totalTimeInSeconds from bookmark where idFile=%i and type=%i order by timeInSeconds", tag.m_iFileId, CBookmark::RESUME);
3204       m_pDS2->query( strSQL.c_str() );
3205       if (!m_pDS2->eof())
3206       {
3207         tag.m_resumePoint.timeInSeconds = m_pDS2->fv(0).get_asDouble();
3208         tag.m_resumePoint.totalTimeInSeconds = m_pDS2->fv(1).get_asDouble();
3209         tag.m_resumePoint.partNumber = 0; // regular files or non-iso stacks don't need partNumber
3210         tag.m_resumePoint.type = CBookmark::RESUME;
3211         match = true;
3212       }
3213       m_pDS2->close();
3214     }
3215   }
3216   catch (...)
3217   {
3218     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, tag.m_strFileNameAndPath.c_str());
3219   }
3220
3221   return match;
3222 }
3223
3224 CVideoInfoTag CVideoDatabase::GetDetailsForMovie(auto_ptr<Dataset> &pDS, bool getDetails /* = false */)
3225 {
3226   return GetDetailsForMovie(pDS->get_sql_record(), getDetails);
3227 }
3228
3229 CVideoInfoTag CVideoDatabase::GetDetailsForMovie(const dbiplus::sql_record* const record, bool getDetails /* = false */)
3230 {
3231   CVideoInfoTag details;
3232
3233   if (record == NULL)
3234     return details;
3235
3236   DWORD time = XbmcThreads::SystemClockMillis();
3237   int idMovie = record->at(0).get_asInt();
3238
3239   GetDetailsFromDB(record, VIDEODB_ID_MIN, VIDEODB_ID_MAX, DbMovieOffsets, details);
3240
3241   details.m_iDbId = idMovie;
3242   details.m_type = "movie";
3243   
3244   details.m_iSetId = record->at(VIDEODB_DETAILS_MOVIE_SET_ID).get_asInt();
3245   details.m_strSet = record->at(VIDEODB_DETAILS_MOVIE_SET_NAME).get_asString();
3246   details.m_iFileId = record->at(VIDEODB_DETAILS_FILEID).get_asInt();
3247   details.m_strPath = record->at(VIDEODB_DETAILS_MOVIE_PATH).get_asString();
3248   CStdString strFileName = record->at(VIDEODB_DETAILS_MOVIE_FILE).get_asString();
3249   ConstructPath(details.m_strFileNameAndPath,details.m_strPath,strFileName);
3250   details.m_playCount = record->at(VIDEODB_DETAILS_MOVIE_PLAYCOUNT).get_asInt();
3251   details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MOVIE_LASTPLAYED).get_asString());
3252   details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MOVIE_DATEADDED).get_asString());
3253   details.m_resumePoint.timeInSeconds = record->at(VIDEODB_DETAILS_MOVIE_RESUME_TIME).get_asInt();
3254   details.m_resumePoint.totalTimeInSeconds = record->at(VIDEODB_DETAILS_MOVIE_TOTAL_TIME).get_asInt();
3255   details.m_resumePoint.type = CBookmark::RESUME;
3256
3257   movieTime += XbmcThreads::SystemClockMillis() - time; time = XbmcThreads::SystemClockMillis();
3258
3259   if (getDetails)
3260   {
3261     GetCast("movie", "idMovie", details.m_iDbId, details.m_cast);
3262
3263     castTime += XbmcThreads::SystemClockMillis() - time; time = XbmcThreads::SystemClockMillis();
3264     details.m_strPictureURL.Parse();
3265
3266     // get tags
3267     CStdString strSQL = PrepareSQL("SELECT tag.strTag FROM tag, taglinks WHERE taglinks.idMedia = %i AND taglinks.media_type = 'movie' AND taglinks.idTag = tag.idTag ORDER BY tag.idTag", idMovie);
3268     m_pDS2->query(strSQL.c_str());
3269     while (!m_pDS2->eof())
3270     {
3271       details.m_tags.push_back(m_pDS2->fv("tag.strTag").get_asString());
3272       m_pDS2->next();
3273     }
3274
3275     // create tvshowlink string
3276     vector<int> links;
3277     GetLinksToTvShow(idMovie,links);
3278     for (unsigned int i=0;i<links.size();++i)
3279     {
3280       CStdString strSQL = PrepareSQL("select c%02d from tvshow where idShow=%i",
3281                          VIDEODB_ID_TV_TITLE,links[i]);
3282       m_pDS2->query(strSQL.c_str());
3283       if (!m_pDS2->eof())
3284         details.m_showLink.push_back(m_pDS2->fv(0).get_asString());
3285     }
3286     m_pDS2->close();
3287
3288     // get streamdetails
3289     GetStreamDetails(details);
3290   }
3291   return details;
3292 }
3293
3294 CVideoInfoTag CVideoDatabase::GetDetailsForTvShow(auto_ptr<Dataset> &pDS, bool getDetails /* = false */)
3295 {
3296   return GetDetailsForTvShow(pDS->get_sql_record(), getDetails);
3297 }
3298
3299 CVideoInfoTag CVideoDatabase::GetDetailsForTvShow(const dbiplus::sql_record* const record, bool getDetails /* = false */)
3300 {
3301   CVideoInfoTag details;
3302
3303   if (record == NULL)
3304     return details;
3305
3306   DWORD time = XbmcThreads::SystemClockMillis();
3307   int idTvShow = record->at(0).get_asInt();
3308
3309   GetDetailsFromDB(record, VIDEODB_ID_TV_MIN, VIDEODB_ID_TV_MAX, DbTvShowOffsets, details, 1);
3310   details.m_iDbId = idTvShow;
3311   details.m_type = "tvshow";
3312   details.m_strPath = record->at(VIDEODB_DETAILS_TVSHOW_PATH).get_asString();
3313   details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_TVSHOW_DATEADDED).get_asString());
3314   details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_TVSHOW_LASTPLAYED).get_asString());
3315   details.m_iEpisode = record->at(VIDEODB_DETAILS_TVSHOW_NUM_EPISODES).get_asInt();
3316   details.m_playCount = record->at(VIDEODB_DETAILS_TVSHOW_NUM_WATCHED).get_asInt();
3317   details.m_strShowPath = details.m_strPath;
3318   details.m_strShowTitle = details.m_strTitle;
3319
3320   movieTime += XbmcThreads::SystemClockMillis() - time; time = XbmcThreads::SystemClockMillis();
3321
3322   if (getDetails)
3323   {
3324     GetCast("tvshow", "idShow", details.m_iDbId, details.m_cast);
3325
3326     // get tags
3327     CStdString strSQL = PrepareSQL("SELECT tag.strTag FROM tag, taglinks WHERE taglinks.idMedia = %i AND taglinks.media_type = 'tvshow' AND taglinks.idTag = tag.idTag ORDER BY tag.idTag", idTvShow);
3328     m_pDS2->query(strSQL.c_str());
3329     while (!m_pDS2->eof())
3330     {
3331       details.m_tags.push_back(m_pDS2->fv("tag.strTag").get_asString());
3332       m_pDS2->next();
3333     }
3334
3335     castTime += XbmcThreads::SystemClockMillis() - time; time = XbmcThreads::SystemClockMillis();
3336     details.m_strPictureURL.Parse();
3337   }
3338   return details;
3339 }
3340
3341 CVideoInfoTag CVideoDatabase::GetDetailsForEpisode(auto_ptr<Dataset> &pDS, bool getDetails /* = false */)
3342 {
3343   return GetDetailsForEpisode(pDS->get_sql_record(), getDetails);
3344 }
3345
3346 CVideoInfoTag CVideoDatabase::GetDetailsForEpisode(const dbiplus::sql_record* const record, bool getDetails /* = false */)
3347 {
3348   CVideoInfoTag details;
3349
3350   if (record == NULL)
3351     return details;
3352
3353   DWORD time = XbmcThreads::SystemClockMillis();
3354   int idEpisode = record->at(0).get_asInt();
3355
3356   GetDetailsFromDB(record, VIDEODB_ID_EPISODE_MIN, VIDEODB_ID_EPISODE_MAX, DbEpisodeOffsets, details);
3357   details.m_iDbId = idEpisode;
3358   details.m_type = "episode";
3359   details.m_iFileId = record->at(VIDEODB_DETAILS_FILEID).get_asInt();
3360   details.m_strPath = record->at(VIDEODB_DETAILS_EPISODE_PATH).get_asString();
3361   CStdString strFileName = record->at(VIDEODB_DETAILS_EPISODE_FILE).get_asString();
3362   ConstructPath(details.m_strFileNameAndPath,details.m_strPath,strFileName);
3363   details.m_playCount = record->at(VIDEODB_DETAILS_EPISODE_PLAYCOUNT).get_asInt();
3364   details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_EPISODE_LASTPLAYED).get_asString());
3365   details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_EPISODE_DATEADDED).get_asString());
3366   details.m_strMPAARating = record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_MPAA).get_asString();
3367   details.m_strShowTitle = record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_NAME).get_asString();
3368   details.m_studio = StringUtils::Split(record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_STUDIO).get_asString(), g_advancedSettings.m_videoItemSeparator);
3369   details.m_premiered.SetFromDBDate(record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_AIRED).get_asString());
3370   details.m_iIdShow = record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_ID).get_asInt();
3371   details.m_strShowPath = record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_PATH).get_asString();
3372   details.m_iIdSeason = record->at(VIDEODB_DETAILS_EPISODE_SEASON_ID).get_asInt();
3373
3374   details.m_resumePoint.timeInSeconds = record->at(VIDEODB_DETAILS_EPISODE_RESUME_TIME).get_asInt();
3375   details.m_resumePoint.totalTimeInSeconds = record->at(VIDEODB_DETAILS_EPISODE_TOTAL_TIME).get_asInt();
3376   details.m_resumePoint.type = CBookmark::RESUME;
3377
3378   movieTime += XbmcThreads::SystemClockMillis() - time; time = XbmcThreads::SystemClockMillis();
3379
3380   if (getDetails)
3381   {
3382     GetCast("episode", "idEpisode", details.m_iDbId, details.m_cast);
3383     GetCast("tvshow", "idShow", details.m_iIdShow, details.m_cast);
3384
3385     castTime += XbmcThreads::SystemClockMillis() - time; time = XbmcThreads::SystemClockMillis();
3386     details.m_strPictureURL.Parse();
3387     CStdString strSQL = PrepareSQL("select * from bookmark join episode on episode.c%02d=bookmark.idBookmark where episode.idEpisode=%i and bookmark.type=%i", VIDEODB_ID_EPISODE_BOOKMARK,details.m_iDbId,CBookmark::EPISODE);
3388     m_pDS2->query(strSQL.c_str());
3389     if (!m_pDS2->eof())
3390       details.m_fEpBookmark = m_pDS2->fv("bookmark.timeInSeconds").get_asFloat();
3391     m_pDS2->close();
3392
3393     // get streamdetails
3394     GetStreamDetails(details);
3395   }
3396   return details;
3397 }
3398
3399 CVideoInfoTag CVideoDatabase::GetDetailsForMusicVideo(auto_ptr<Dataset> &pDS, bool getDetails /* = false */)
3400 {
3401   return GetDetailsForMusicVideo(pDS->get_sql_record(), getDetails);
3402 }
3403
3404 CVideoInfoTag CVideoDatabase::GetDetailsForMusicVideo(const dbiplus::sql_record* const record, bool getDetails /* = false */)
3405 {
3406   CVideoInfoTag details;
3407
3408   unsigned int time = XbmcThreads::SystemClockMillis();
3409   int idMVideo = record->at(0).get_asInt();
3410
3411   GetDetailsFromDB(record, VIDEODB_ID_MUSICVIDEO_MIN, VIDEODB_ID_MUSICVIDEO_MAX, DbMusicVideoOffsets, details);
3412   details.m_iDbId = idMVideo;
3413   details.m_type = "musicvideo";
3414   
3415   details.m_iFileId = record->at(VIDEODB_DETAILS_FILEID).get_asInt();
3416   details.m_strPath = record->at(VIDEODB_DETAILS_MUSICVIDEO_PATH).get_asString();
3417   CStdString strFileName = record->at(VIDEODB_DETAILS_MUSICVIDEO_FILE).get_asString();
3418   ConstructPath(details.m_strFileNameAndPath,details.m_strPath,strFileName);
3419   details.m_playCount = record->at(VIDEODB_DETAILS_MUSICVIDEO_PLAYCOUNT).get_asInt();
3420   details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MUSICVIDEO_LASTPLAYED).get_asString());
3421   details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MUSICVIDEO_DATEADDED).get_asString());
3422   details.m_resumePoint.timeInSeconds = record->at(VIDEODB_DETAILS_MUSICVIDEO_RESUME_TIME).get_asInt();
3423   details.m_resumePoint.totalTimeInSeconds = record->at(VIDEODB_DETAILS_MUSICVIDEO_TOTAL_TIME).get_asInt();
3424   details.m_resumePoint.type = CBookmark::RESUME;
3425
3426   movieTime += XbmcThreads::SystemClockMillis() - time; time = XbmcThreads::SystemClockMillis();
3427
3428   if (getDetails)
3429   {
3430     // get tags
3431     CStdString strSQL = PrepareSQL("SELECT tag.strTag FROM tag, taglinks WHERE taglinks.idMedia = %i AND taglinks.media_type = 'musicvideo' AND taglinks.idTag = tag.idTag ORDER BY tag.idTag", idMVideo);
3432     m_pDS2->query(strSQL.c_str());
3433     while (!m_pDS2->eof())
3434     {
3435       details.m_tags.push_back(m_pDS2->fv("tag.strTag").get_asString());
3436       m_pDS2->next();
3437     }
3438     m_pDS2->close();
3439
3440     details.m_strPictureURL.Parse();
3441
3442     // get streamdetails
3443     GetStreamDetails(details);
3444   }
3445   return details;
3446 }
3447
3448 void CVideoDatabase::GetCast(const CStdString &table, const CStdString &table_id, int type_id, vector<SActorInfo> &cast)
3449 {
3450   try
3451   {
3452     if (!m_pDB.get()) return;
3453     if (!m_pDS2.get()) return;
3454
3455     CStdString sql = PrepareSQL("SELECT actors.strActor,"
3456                                 "  actorlink%s.strRole,"
3457                                 "  actors.strThumb,"
3458                                 "  art.url "
3459                                 "FROM actorlink%s"
3460                                 "  JOIN actors ON"
3461                                 "    actorlink%s.idActor=actors.idActor"
3462                                 "  LEFT JOIN art ON"
3463                                 "    art.media_id=actors.idActor AND art.media_type='actor' AND art.type='thumb' "
3464                                 "WHERE actorlink%s.%s=%i "
3465                                 "ORDER BY actorlink%s.iOrder",table.c_str(), table.c_str(), table.c_str(), table.c_str(), table_id.c_str(), type_id, table.c_str());
3466     m_pDS2->query(sql.c_str());
3467     while (!m_pDS2->eof())
3468     {
3469       SActorInfo info;
3470       info.strName = m_pDS2->fv(0).get_asString();
3471       bool found = false;
3472       for (vector<SActorInfo>::iterator i = cast.begin(); i != cast.end(); ++i)
3473       {
3474         if (i->strName == info.strName)
3475         {
3476           found = true;
3477           break;
3478         }
3479       }
3480       if (!found)
3481       {
3482         info.strRole = m_pDS2->fv(1).get_asString();
3483         info.thumbUrl.ParseString(m_pDS2->fv(2).get_asString());
3484         info.thumb = m_pDS2->fv(3).get_asString();
3485         cast.push_back(info);
3486       }
3487       m_pDS2->next();
3488     }
3489     m_pDS2->close();
3490   }
3491   catch (...)
3492   {
3493     CLog::Log(LOGERROR, "%s(%s,%s,%i) failed", __FUNCTION__, table.c_str(), table_id.c_str(), type_id);
3494   }
3495 }
3496
3497 /// \brief GetVideoSettings() obtains any saved video settings for the current file.
3498 /// \retval Returns true if the settings exist, false otherwise.
3499 bool CVideoDatabase::GetVideoSettings(const CStdString &strFilenameAndPath, CVideoSettings &settings)
3500 {
3501   try
3502   {
3503     // obtain the FileID (if it exists)
3504 #ifdef NEW_VIDEODB_METHODS
3505     if (NULL == m_pDB.get()) return false;
3506     if (NULL == m_pDS.get()) return false;
3507     CStdString strPath, strFileName;
3508     URIUtils::Split(strFilenameAndPath, strPath, strFileName);
3509     CStdString strSQL=PrepareSQL("select * from settings, files, path where settings.idFile=files.idFile and path.idPath=files.idPath and path.strPath='%s' and files.strFileName='%s'", strPath.c_str() , strFileName.c_str());
3510 #else
3511     int idFile = GetFileId(strFilenameAndPath);
3512     if (idFile < 0) return false;
3513     if (NULL == m_pDB.get()) return false;
3514     if (NULL == m_pDS.get()) return false;
3515     // ok, now obtain the settings for this file
3516     CStdString strSQL=PrepareSQL("select * from settings where settings.idFile = '%i'", idFile);
3517 #endif
3518     m_pDS->query( strSQL.c_str() );
3519     if (m_pDS->num_rows() > 0)
3520     { // get the video settings info
3521       settings.m_AudioDelay = m_pDS->fv("AudioDelay").get_asFloat();
3522       settings.m_AudioStream = m_pDS->fv("AudioStream").get_asInt();
3523       settings.m_Brightness = m_pDS->fv("Brightness").get_asFloat();
3524       settings.m_Contrast = m_pDS->fv("Contrast").get_asFloat();
3525       settings.m_CustomPixelRatio = m_pDS->fv("PixelRatio").get_asFloat();
3526       settings.m_CustomNonLinStretch = m_pDS->fv("NonLinStretch").get_asBool();
3527       settings.m_NoiseReduction = m_pDS->fv("NoiseReduction").get_asFloat();
3528       settings.m_PostProcess = m_pDS->fv("PostProcess").get_asBool();
3529       settings.m_Sharpness = m_pDS->fv("Sharpness").get_asFloat();
3530       settings.m_CustomZoomAmount = m_pDS->fv("ZoomAmount").get_asFloat();
3531       settings.m_CustomVerticalShift = m_pDS->fv("VerticalShift").get_asFloat();
3532       settings.m_Gamma = m_pDS->fv("Gamma").get_asFloat();
3533       settings.m_SubtitleDelay = m_pDS->fv("SubtitleDelay").get_asFloat();
3534       settings.m_SubtitleOn = m_pDS->fv("SubtitlesOn").get_asBool();
3535       settings.m_SubtitleStream = m_pDS->fv("SubtitleStream").get_asInt();
3536       settings.m_ViewMode = m_pDS->fv("ViewMode").get_asInt();
3537       settings.m_ResumeTime = m_pDS->fv("ResumeTime").get_asInt();
3538       settings.m_Crop = m_pDS->fv("Crop").get_asBool();
3539       settings.m_CropLeft = m_pDS->fv("CropLeft").get_asInt();
3540       settings.m_CropRight = m_pDS->fv("CropRight").get_asInt();
3541       settings.m_CropTop = m_pDS->fv("CropTop").get_asInt();
3542       settings.m_CropBottom = m_pDS->fv("CropBottom").get_asInt();
3543       settings.m_DeinterlaceMode = (EDEINTERLACEMODE)m_pDS->fv("DeinterlaceMode").get_asInt();
3544       settings.m_InterlaceMethod = (EINTERLACEMETHOD)m_pDS->fv("Deinterlace").get_asInt();
3545       settings.m_VolumeAmplification = m_pDS->fv("VolumeAmplification").get_asFloat();
3546       settings.m_OutputToAllSpeakers = m_pDS->fv("OutputToAllSpeakers").get_asBool();
3547       settings.m_ScalingMethod = (ESCALINGMETHOD)m_pDS->fv("ScalingMethod").get_asInt();
3548       settings.m_SubtitleCached = false;
3549       m_pDS->close();
3550       return true;
3551     }
3552     m_pDS->close();
3553   }
3554   catch (...)
3555   {
3556     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
3557   }
3558   return false;
3559 }
3560
3561 /// \brief Sets the settings for a particular video file
3562 void CVideoDatabase::SetVideoSettings(const CStdString& strFilenameAndPath, const CVideoSettings &setting)
3563 {
3564   try
3565   {
3566     if (NULL == m_pDB.get()) return ;
3567     if (NULL == m_pDS.get()) return ;
3568     int idFile = AddFile(strFilenameAndPath);
3569     if (idFile < 0)
3570       return;
3571     CStdString strSQL;
3572     strSQL.Format("select * from settings where idFile=%i", idFile);
3573     m_pDS->query( strSQL.c_str() );
3574     if (m_pDS->num_rows() > 0)
3575     {
3576       m_pDS->close();
3577       // update the item
3578       strSQL=PrepareSQL("update settings set Deinterlace=%i,ViewMode=%i,ZoomAmount=%f,PixelRatio=%f,VerticalShift=%f,"
3579                        "AudioStream=%i,SubtitleStream=%i,SubtitleDelay=%f,SubtitlesOn=%i,Brightness=%f,Contrast=%f,Gamma=%f,"
3580                        "VolumeAmplification=%f,AudioDelay=%f,OutputToAllSpeakers=%i,Sharpness=%f,NoiseReduction=%f,NonLinStretch=%i,PostProcess=%i,ScalingMethod=%i,"
3581                        "DeinterlaceMode=%i,",
3582                        setting.m_InterlaceMethod, setting.m_ViewMode, setting.m_CustomZoomAmount, setting.m_CustomPixelRatio, setting.m_CustomVerticalShift,
3583                        setting.m_AudioStream, setting.m_SubtitleStream, setting.m_SubtitleDelay, setting.m_SubtitleOn,
3584                        setting.m_Brightness, setting.m_Contrast, setting.m_Gamma, setting.m_VolumeAmplification, setting.m_AudioDelay,
3585                        setting.m_OutputToAllSpeakers,setting.m_Sharpness,setting.m_NoiseReduction,setting.m_CustomNonLinStretch,setting.m_PostProcess,setting.m_ScalingMethod,
3586                        setting.m_DeinterlaceMode);
3587       CStdString strSQL2;
3588       strSQL2=PrepareSQL("ResumeTime=%i,Crop=%i,CropLeft=%i,CropRight=%i,CropTop=%i,CropBottom=%i where idFile=%i\n", setting.m_ResumeTime, setting.m_Crop, setting.m_CropLeft, setting.m_CropRight, setting.m_CropTop, setting.m_CropBottom, idFile);
3589       strSQL += strSQL2;
3590       m_pDS->exec(strSQL.c_str());
3591       return ;
3592     }
3593     else
3594     { // add the items
3595       m_pDS->close();
3596       strSQL= "INSERT INTO settings (idFile,Deinterlace,ViewMode,ZoomAmount,PixelRatio, VerticalShift, "
3597                 "AudioStream,SubtitleStream,SubtitleDelay,SubtitlesOn,Brightness,"
3598                 "Contrast,Gamma,VolumeAmplification,AudioDelay,OutputToAllSpeakers,"
3599                 "ResumeTime,Crop,CropLeft,CropRight,CropTop,CropBottom,"
3600                 "Sharpness,NoiseReduction,NonLinStretch,PostProcess,ScalingMethod,DeinterlaceMode) "
3601               "VALUES ";
3602       strSQL += PrepareSQL("(%i,%i,%i,%f,%f,%f,%i,%i,%f,%i,%f,%f,%f,%f,%f,%i,%i,%i,%i,%i,%i,%i,%f,%f,%i,%i,%i,%i)",
3603                            idFile, setting.m_InterlaceMethod, setting.m_ViewMode, setting.m_CustomZoomAmount, setting.m_CustomPixelRatio, setting.m_CustomVerticalShift,
3604                            setting.m_AudioStream, setting.m_SubtitleStream, setting.m_SubtitleDelay, setting.m_SubtitleOn, setting.m_Brightness,
3605                            setting.m_Contrast, setting.m_Gamma, setting.m_VolumeAmplification, setting.m_AudioDelay, setting.m_OutputToAllSpeakers,
3606                            setting.m_ResumeTime, setting.m_Crop, setting.m_CropLeft, setting.m_CropRight, setting.m_CropTop, setting.m_CropBottom,
3607                            setting.m_Sharpness, setting.m_NoiseReduction, setting.m_CustomNonLinStretch, setting.m_PostProcess, setting.m_ScalingMethod,
3608                            setting.m_DeinterlaceMode);
3609       m_pDS->exec(strSQL.c_str());
3610     }
3611   }
3612   catch (...)
3613   {
3614     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
3615   }
3616 }
3617
3618 void CVideoDatabase::SetArtForItem(int mediaId, const string &mediaType, const map<string, string> &art)
3619 {
3620   for (map<string, string>::const_iterator i = art.begin(); i != art.end(); ++i)
3621     SetArtForItem(mediaId, mediaType, i->first, i->second);
3622 }
3623
3624 void CVideoDatabase::SetArtForItem(int mediaId, const string &mediaType, const string &artType, const string &url)
3625 {
3626   try
3627   {
3628     if (NULL == m_pDB.get()) return;
3629     if (NULL == m_pDS.get()) return;
3630
3631     // don't set <foo>.<bar> art types - these are derivative types from parent items
3632     if (artType.find('.') != string::npos)
3633       return;
3634
3635     CStdString sql = PrepareSQL("SELECT art_id FROM art WHERE media_id=%i AND media_type='%s' AND type='%s'", mediaId, mediaType.c_str(), artType.c_str());
3636     m_pDS->query(sql.c_str());
3637     if (!m_pDS->eof())
3638     { // update
3639       int artId = m_pDS->fv(0).get_asInt();
3640       m_pDS->close();
3641       sql = PrepareSQL("UPDATE art SET url='%s' where art_id=%d", url.c_str(), artId);
3642       m_pDS->exec(sql.c_str());
3643     }
3644     else
3645     { // insert
3646       m_pDS->close();
3647       sql = PrepareSQL("INSERT INTO art(media_id, media_type, type, url) VALUES (%d, '%s', '%s', '%s')", mediaId, mediaType.c_str(), artType.c_str(), url.c_str());
3648       m_pDS->exec(sql.c_str());
3649     }
3650   }
3651   catch (...)
3652   {
3653     CLog::Log(LOGERROR, "%s(%d, '%s', '%s', '%s') failed", __FUNCTION__, mediaId, mediaType.c_str(), artType.c_str(), url.c_str());
3654   }
3655 }
3656
3657 bool CVideoDatabase::GetArtForItem(int mediaId, const string &mediaType, map<string, string> &art)
3658 {
3659   try
3660   {
3661     if (NULL == m_pDB.get()) return false;
3662     if (NULL == m_pDS2.get()) return false; // using dataset 2 as we're likely called in loops on dataset 1
3663
3664     CStdString sql = PrepareSQL("SELECT type,url FROM art WHERE media_id=%i AND media_type='%s'", mediaId, mediaType.c_str());
3665     m_pDS2->query(sql.c_str());
3666     while (!m_pDS2->eof())
3667     {
3668       art.insert(make_pair(m_pDS2->fv(0).get_asString(), m_pDS2->fv(1).get_asString()));
3669       m_pDS2->next();
3670     }
3671     m_pDS2->close();
3672     return !art.empty();
3673   }
3674   catch (...)
3675   {
3676     CLog::Log(LOGERROR, "%s(%d) failed", __FUNCTION__, mediaId);
3677   }
3678   return false;
3679 }
3680
3681 string CVideoDatabase::GetArtForItem(int mediaId, const string &mediaType, const string &artType)
3682 {
3683   std::string query = PrepareSQL("SELECT url FROM art WHERE media_id=%i AND media_type='%s' AND type='%s'", mediaId, mediaType.c_str(), artType.c_str());
3684   return GetSingleValue(query, m_pDS2);
3685 }
3686
3687 bool CVideoDatabase::GetTvShowSeasonArt(int showId, map<int, map<string, string> > &seasonArt)
3688 {
3689   try
3690   {
3691     if (NULL == m_pDB.get()) return false;
3692     if (NULL == m_pDS2.get()) return false; // using dataset 2 as we're likely called in loops on dataset 1
3693
3694     // get all seasons for this show
3695     CStdString sql = PrepareSQL("select idSeason,season from seasons where idShow=%i", showId);
3696     m_pDS2->query(sql.c_str());
3697
3698     vector< pair<int, int> > seasons;
3699     while (!m_pDS2->eof())
3700     {
3701       seasons.push_back(make_pair(m_pDS2->fv(0).get_asInt(), m_pDS2->fv(1).get_asInt()));
3702       m_pDS2->next();
3703     }
3704     m_pDS2->close();
3705
3706     for (vector< pair<int,int> >::const_iterator i = seasons.begin(); i != seasons.end(); ++i)
3707     {
3708       map<string, string> art;
3709       GetArtForItem(i->first, "season", art);
3710       seasonArt.insert(make_pair(i->second,art));
3711     }
3712     return true;
3713   }
3714   catch (...)
3715   {
3716     CLog::Log(LOGERROR, "%s(%d) failed", __FUNCTION__, showId);
3717   }
3718   return false;
3719 }
3720
3721 /// \brief GetStackTimes() obtains any saved video times for the stacked file
3722 /// \retval Returns true if the stack times exist, false otherwise.
3723 bool CVideoDatabase::GetStackTimes(const CStdString &filePath, vector<int> &times)
3724 {
3725   try
3726   {
3727     // obtain the FileID (if it exists)
3728     int idFile = GetFileId(filePath);
3729     if (idFile < 0) return false;
3730     if (NULL == m_pDB.get()) return false;
3731     if (NULL == m_pDS.get()) return false;
3732     // ok, now obtain the settings for this file
3733     CStdString strSQL=PrepareSQL("select times from stacktimes where idFile=%i\n", idFile);
3734     m_pDS->query( strSQL.c_str() );
3735     if (m_pDS->num_rows() > 0)
3736     { // get the video settings info
3737       CStdStringArray timeString;
3738       int timeTotal = 0;
3739       StringUtils::SplitString(m_pDS->fv("times").get_asString(), ",", timeString);
3740       times.clear();
3741       for (unsigned int i = 0; i < timeString.size(); i++)
3742       {
3743         times.push_back(atoi(timeString[i].c_str()));
3744         timeTotal += atoi(timeString[i].c_str());
3745       }
3746       m_pDS->close();
3747       return (timeTotal > 0);
3748     }
3749     m_pDS->close();
3750   }
3751   catch (...)
3752   {
3753     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
3754   }
3755   return false;
3756 }
3757
3758 /// \brief Sets the stack times for a particular video file
3759 void CVideoDatabase::SetStackTimes(const CStdString& filePath, vector<int> &times)
3760 {
3761   try
3762   {
3763     if (NULL == m_pDB.get()) return ;
3764     if (NULL == m_pDS.get()) return ;
3765     int idFile = AddFile(filePath);
3766     if (idFile < 0)
3767       return;
3768
3769     // delete any existing items
3770     m_pDS->exec( PrepareSQL("delete from stacktimes where idFile=%i", idFile) );
3771
3772     // add the items
3773     CStdString timeString;
3774     timeString.Format("%i", times[0]);
3775     for (unsigned int i = 1; i < times.size(); i++)
3776     {
3777       CStdString time;
3778       time.Format(",%i", times[i]);
3779       timeString += time;
3780     }
3781     m_pDS->exec( PrepareSQL("insert into stacktimes (idFile,times) values (%i,'%s')\n", idFile, timeString.c_str()) );
3782   }
3783   catch (...)
3784   {
3785     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, filePath.c_str());
3786   }
3787 }
3788
3789 void CVideoDatabase::RemoveContentForPath(const CStdString& strPath, CGUIDialogProgress *progress /* = NULL */)
3790 {
3791   if(URIUtils::IsMultiPath(strPath))
3792   {
3793     vector<CStdString> paths;
3794     CMultiPathDirectory::GetPaths(strPath, paths);
3795
3796     for(unsigned i=0;i<paths.size();i++)
3797       RemoveContentForPath(paths[i], progress);
3798   }
3799
3800   try
3801   {
3802     if (NULL == m_pDB.get()) return ;
3803     if (NULL == m_pDS.get()) return ;
3804
3805     if (progress)
3806     {
3807       progress->SetHeading(700);
3808       progress->SetLine(0, "");
3809       progress->SetLine(1, 313);
3810       progress->SetLine(2, 330);
3811       progress->SetPercentage(0);
3812       progress->StartModal();
3813       progress->ShowProgressBar(true);
3814     }
3815     vector< pair<int,string> > paths;
3816     GetSubPaths(strPath, paths);
3817     int iCurr = 0;
3818     for (vector< pair<int, string> >::const_iterator i = paths.begin(); i != paths.end(); ++i)
3819     {
3820       bool bMvidsChecked=false;
3821       if (progress)
3822       {
3823         progress->SetPercentage((int)((float)(iCurr++)/paths.size()*100.f));
3824         progress->Progress();
3825       }
3826
3827       if (HasTvShowInfo(i->second))
3828         DeleteTvShow(i->second);
3829       else
3830       {
3831         CStdString strSQL = PrepareSQL("select files.strFilename from files join movie on movie.idFile=files.idFile where files.idPath=%i", i->first);
3832         m_pDS2->query(strSQL.c_str());
3833         if (m_pDS2->eof())
3834         {
3835           strSQL = PrepareSQL("select files.strFilename from files join musicvideo on musicvideo.idFile=files.idFile where files.idPath=%i", i->first);
3836           m_pDS2->query(strSQL.c_str());
3837           bMvidsChecked = true;
3838         }
3839         while (!m_pDS2->eof())
3840         {
3841           CStdString strMoviePath;
3842           CStdString strFileName = m_pDS2->fv("files.strFilename").get_asString();
3843           ConstructPath(strMoviePath, i->second, strFileName);
3844           if (HasMovieInfo(strMoviePath))
3845             DeleteMovie(strMoviePath);
3846           if (HasMusicVideoInfo(strMoviePath))
3847             DeleteMusicVideo(strMoviePath);
3848           m_pDS2->next();
3849           if (m_pDS2->eof() && !bMvidsChecked)
3850           {
3851             strSQL =PrepareSQL("select files.strFilename from files join musicvideo on musicvideo.idFile=files.idFile where files.idPath=%i", i->first);
3852             m_pDS2->query(strSQL.c_str());
3853             bMvidsChecked = true;
3854           }
3855         }
3856         m_pDS2->close();
3857         m_pDS2->exec(PrepareSQL("update path set strContent='', strScraper='', strHash='',strSettings='',useFolderNames=0,scanRecursive=0 where idPath=%i", i->first));
3858       }
3859     }
3860   }
3861   catch (...)
3862   {
3863     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strPath.c_str());
3864   }
3865   if (progress)
3866     progress->Close();
3867 }
3868
3869 void CVideoDatabase::SetScraperForPath(const CStdString& filePath, const ScraperPtr& scraper, const VIDEO::SScanSettings& settings)
3870 {
3871   // if we have a multipath, set scraper for all contained paths too
3872   if(URIUtils::IsMultiPath(filePath))
3873   {
3874     vector<CStdString> paths;
3875     CMultiPathDirectory::GetPaths(filePath, paths);
3876
3877     for(unsigned i=0;i<paths.size();i++)
3878       SetScraperForPath(paths[i],scraper,settings);
3879   }
3880
3881   try
3882   {
3883     if (NULL == m_pDB.get()) return ;
3884     if (NULL == m_pDS.get()) return ;
3885
3886     int idPath = AddPath(filePath);
3887     if (idPath < 0)
3888       return;
3889
3890     // Update
3891     CStdString strSQL;
3892     if (settings.exclude)
3893     { //NB See note in ::GetScraperForPath about strContent=='none'
3894       strSQL=PrepareSQL("update path set strContent='', strScraper='', scanRecursive=0, useFolderNames=0, strSettings='', noUpdate=0 , exclude=1 where idPath=%i", idPath);
3895     }
3896     else if(!scraper)
3897     { // catch clearing content, but not excluding
3898       strSQL=PrepareSQL("update path set strContent='', strScraper='', scanRecursive=0, useFolderNames=0, strSettings='', noUpdate=0, exclude=0 where idPath=%i", idPath);
3899     }
3900     else
3901     {
3902       CStdString content = TranslateContent(scraper->Content());
3903       strSQL=PrepareSQL("update path set strContent='%s', strScraper='%s', scanRecursive=%i, useFolderNames=%i, strSettings='%s', noUpdate=%i, exclude=0 where idPath=%i", content.c_str(), scraper->ID().c_str(),settings.recurse,settings.parent_name,scraper->GetPathSettings().c_str(),settings.noupdate, idPath);
3904     }
3905     m_pDS->exec(strSQL.c_str());
3906   }
3907   catch (...)
3908   {
3909     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, filePath.c_str());
3910   }
3911 }
3912
3913 bool CVideoDatabase::ScraperInUse(const CStdString &scraperID) const
3914 {
3915   try
3916   {
3917     if (NULL == m_pDB.get()) return false;
3918     if (NULL == m_pDS.get()) return false;
3919
3920     CStdString sql = PrepareSQL("select count(1) from path where strScraper='%s'", scraperID.c_str());
3921     if (!m_pDS->query(sql.c_str()) || m_pDS->num_rows() == 0)
3922       return false;
3923     bool found = m_pDS->fv(0).get_asInt() > 0;
3924     m_pDS->close();
3925     return found;
3926   }
3927   catch (...)
3928   {
3929     CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, scraperID.c_str());
3930   }
3931   return false;
3932 }
3933
3934 class CArtItem
3935 {
3936 public:
3937   CArtItem() { art_id = 0; media_id = 0; };
3938   int art_id;
3939   string art_type;
3940   string art_url;
3941   int media_id;
3942   string media_type;
3943 };
3944
3945 bool CVideoDatabase::UpdateOldVersion(int iVersion)
3946 {
3947   if (iVersion < 43)
3948   {
3949     m_pDS->exec("ALTER TABLE settings ADD VerticalShift float");
3950   }
3951   if (iVersion < 44)
3952   {
3953     // only if MySQL is used and default character set is not utf8
3954     // string data needs to be converted to proper utf8
3955     CStdString charset = m_pDS->getDatabase()->getDefaultCharset();
3956     if (!m_sqlite && !charset.empty() && charset != "utf8")
3957     {
3958       map<CStdString, CStdStringArray> tables;
3959       map<CStdString, CStdStringArray>::iterator itt;
3960       CStdStringArray::iterator itc;
3961
3962       // columns that need to be converted
3963       // content columns
3964       CStdStringArray c_columns;
3965       for (int i = 0; i < 22; i++)
3966       {
3967         CStdString c;
3968         c.Format("c%02d", i);
3969         c_columns.push_back(c);
3970       }
3971
3972       tables.insert(pair<CStdString, CStdStringArray> ("episode", c_columns));
3973       tables.insert(pair<CStdString, CStdStringArray> ("movie", c_columns));
3974       tables.insert(pair<CStdString, CStdStringArray> ("musicvideo", c_columns));
3975       tables.insert(pair<CStdString, CStdStringArray> ("tvshow", c_columns));
3976
3977       //common columns
3978       CStdStringArray c1;
3979       c1.push_back("strRole");
3980       tables.insert(pair<CStdString, CStdStringArray> ("actorlinkepisode", c1));
3981       tables.insert(pair<CStdString, CStdStringArray> ("actorlinkmovie", c1));
3982       tables.insert(pair<CStdString, CStdStringArray> ("actorlinktvshow", c1));
3983
3984       //remaining columns
3985       CStdStringArray c2;
3986       c2.push_back("strActor");
3987       tables.insert(pair<CStdString, CStdStringArray> ("actors", c2));
3988
3989       CStdStringArray c3;
3990       c3.push_back("strCountry");
3991       tables.insert(pair<CStdString, CStdStringArray> ("country", c3));
3992
3993       CStdStringArray c4;
3994       c4.push_back("strFilename");
3995       tables.insert(pair<CStdString, CStdStringArray> ("files", c4));
3996
3997       CStdStringArray c5;
3998       c5.push_back("strGenre");
3999       tables.insert(pair<CStdString, CStdStringArray> ("genre", c5));
4000
4001       CStdStringArray c6;
4002       c6.push_back("strSet");
4003       tables.insert(pair<CStdString, CStdStringArray> ("sets", c6));
4004
4005       CStdStringArray c7;
4006       c7.push_back("strStudio");
4007       tables.insert(pair<CStdString, CStdStringArray> ("studio", c7));
4008
4009       CStdStringArray c8;
4010       c8.push_back("strPath");
4011       tables.insert(pair<CStdString, CStdStringArray> ("path", c8));
4012
4013       for (itt = tables.begin(); itt != tables.end(); ++itt)
4014       {
4015         CStdString q;
4016         q = PrepareSQL("UPDATE `%s` SET", itt->first.c_str());
4017         for (itc = itt->second.begin(); itc != itt->second.end(); ++itc)
4018         {
4019           q += PrepareSQL(" `%s` = CONVERT(CAST(CONVERT(`%s` USING %s) AS BINARY) USING utf8)",
4020                           itc->c_str(), itc->c_str(), charset.c_str());
4021           if (*itc != itt->second.back())
4022           {
4023             q += ",";
4024           }
4025         }
4026         m_pDS->exec(q);
4027       }
4028     }
4029   }
4030   if (iVersion < 45)
4031   {
4032     m_pDS->exec("ALTER TABLE movie ADD c22 text");
4033     m_pDS->exec("ALTER TABLE episode ADD c22 text");
4034     m_pDS->exec("ALTER TABLE musicvideo ADD c22 text");
4035     m_pDS->exec("ALTER TABLE tvshow ADD c22 text");
4036     // Now update our tables
4037     UpdateBasePath("movie", "idMovie", VIDEODB_ID_BASEPATH);
4038     UpdateBasePath("musicvideo", "idMVideo", VIDEODB_ID_MUSICVIDEO_BASEPATH);
4039     UpdateBasePath("episode", "idEpisode", VIDEODB_ID_EPISODE_BASEPATH);
4040     UpdateBasePath("tvshow", "idShow", VIDEODB_ID_TV_BASEPATH, true);
4041   }
4042   if (iVersion < 46)
4043   { // add indices for dir entry lookups
4044     m_pDS->exec("CREATE INDEX ixMovieBasePath ON movie ( c22(255) )");
4045     m_pDS->exec("CREATE INDEX ixMusicVideoBasePath ON musicvideo ( c13(255) )");
4046     m_pDS->exec("CREATE INDEX ixEpisodeBasePath ON episode ( c18(255) )");
4047     m_pDS->exec("CREATE INDEX ixTVShowBasePath ON tvshow ( c16(255) )");
4048   }
4049   if (iVersion < 50)
4050   {
4051     m_pDS->exec("ALTER TABLE settings ADD ScalingMethod integer");
4052     m_pDS->exec(PrepareSQL("UPDATE settings set ScalingMethod=%i", g_settings.m_defaultVideoSettings.m_ScalingMethod));
4053   }
4054   if (iVersion < 51)
4055   {
4056     // Add iOrder fields to actorlink* tables to be able to list
4057     // actors by importance
4058     m_pDS->exec("ALTER TABLE actorlinkmovie ADD iOrder integer");
4059     m_pDS->exec("ALTER TABLE actorlinktvshow ADD iOrder integer");
4060     m_pDS->exec("ALTER TABLE actorlinkepisode ADD iOrder integer");
4061   }
4062   if (iVersion < 52)
4063   { // Add basepath link to path table for faster content retrieval, and indicies
4064     m_pDS->exec("ALTER TABLE movie ADD c23 text");
4065     m_pDS->exec("ALTER TABLE episode ADD c23 text");
4066     m_pDS->exec("ALTER TABLE musicvideo ADD c23 text");
4067     m_pDS->exec("ALTER TABLE tvshow ADD c23 text");
4068     m_pDS->dropIndex("movie", "ixMovieBasePath");
4069     m_pDS->dropIndex("musicvideo", "ixMusicVideoBasePath");
4070     m_pDS->dropIndex("episode", "ixEpisodeBasePath");
4071     m_pDS->dropIndex("tvshow", "ixTVShowBasePath");
4072     m_pDS->exec("CREATE INDEX ixMovieBasePath ON movie ( c23(12) )");
4073     m_pDS->exec("CREATE INDEX ixMusicVideoBasePath ON musicvideo ( c14(12) )");
4074     m_pDS->exec("CREATE INDEX ixEpisodeBasePath ON episode ( c19(12) )");
4075     m_pDS->exec("CREATE INDEX ixTVShowBasePath ON tvshow ( c17(12) )");
4076     // now update the base path links
4077     UpdateBasePathID("movie", "idMovie", VIDEODB_ID_BASEPATH, VIDEODB_ID_PARENTPATHID);
4078     UpdateBasePathID("musicvideo", "idMVideo", VIDEODB_ID_MUSICVIDEO_BASEPATH, VIDEODB_ID_MUSICVIDEO_PARENTPATHID);
4079     UpdateBasePathID("episode", "idEpisode", VIDEODB_ID_EPISODE_BASEPATH, VIDEODB_ID_EPISODE_PARENTPATHID);
4080     UpdateBasePathID("tvshow", "idShow", VIDEODB_ID_TV_BASEPATH, VIDEODB_ID_TV_PARENTPATHID);
4081   }
4082   if (iVersion < 54)
4083   { // Change INDEX for bookmark table
4084     m_pDS->dropIndex("bookmark", "ix_bookmark");
4085     m_pDS->exec("CREATE INDEX ix_bookmark ON bookmark (idFile, type)");
4086   }
4087   if (iVersion < 55)
4088   {
4089     m_pDS->exec("ALTER TABLE settings ADD DeinterlaceMode integer");
4090     m_pDS->exec("UPDATE settings SET DeinterlaceMode = 2 WHERE Deinterlace NOT IN (0,1)"); // anything other than none: method auto => mode force
4091     m_pDS->exec("UPDATE settings SET DeinterlaceMode = 1 WHERE Deinterlace = 1"); // method auto => mode auto
4092     m_pDS->exec("UPDATE settings SET DeinterlaceMode = 0, Deinterlace = 1 WHERE Deinterlace = 0"); // method none => mode off, method auto
4093   }
4094
4095   if (iVersion < 59)
4096   { // base paths for video_ts and bdmv files was wrong (and inconsistent depending on where and when they were scanned)
4097     CStdString where = PrepareSQL(" WHERE files.strFileName LIKE 'VIDEO_TS.IFO' or files.strFileName LIKE 'index.BDMV'");
4098     UpdateBasePath("movie", "idMovie", VIDEODB_ID_BASEPATH, false, where);
4099     UpdateBasePath("musicvideo", "idMVideo", VIDEODB_ID_MUSICVIDEO_BASEPATH, false, where);
4100     UpdateBasePath("episode", "idEpisode", VIDEODB_ID_EPISODE_BASEPATH, false, where);
4101     UpdateBasePathID("movie", "idMovie", VIDEODB_ID_BASEPATH, VIDEODB_ID_PARENTPATHID);
4102     UpdateBasePathID("musicvideo", "idMVideo", VIDEODB_ID_MUSICVIDEO_BASEPATH, VIDEODB_ID_MUSICVIDEO_PARENTPATHID);
4103     UpdateBasePathID("episode", "idEpisode", VIDEODB_ID_EPISODE_BASEPATH, VIDEODB_ID_EPISODE_PARENTPATHID);
4104   }
4105   if (iVersion < 61)
4106   {
4107     m_pDS->exec("ALTER TABLE path ADD dateAdded text");
4108     m_pDS->exec("ALTER TABLE files ADD dateAdded text");
4109   }
4110   if (iVersion < 62)
4111   { // add seasons table
4112     m_pDS->exec("CREATE TABLE seasons ( idSeason integer primary key, idShow integer, season integer)");
4113     m_pDS->exec("CREATE INDEX ix_seasons ON seasons (idShow, season)");
4114     // insert all seasons for each show
4115     m_pDS->query("SELECT idShow FROM tvshow");
4116     while (!m_pDS->eof())
4117     {
4118       CStdString sql = PrepareSQL("INSERT INTO seasons (idShow,season)"
4119                                   "  SELECT DISTINCT"
4120                                   "    idShow,c%02d"
4121                                   "  FROM"
4122                                   "    episodeview"
4123                                   "  WHERE idShow=%i", VIDEODB_ID_EPISODE_SEASON, m_pDS->fv(0).get_asInt());
4124       m_pDS2->exec(sql.c_str());
4125       // and the "all seasons node"
4126       sql = PrepareSQL("INSERT INTO seasons (idShow,season) VALUES(%i,-1)", m_pDS->fv(0).get_asInt());
4127       m_pDS2->exec(sql.c_str());
4128       m_pDS->next();
4129     }
4130   }
4131   if (iVersion < 63)
4132   { // add art table
4133     m_pDS->exec("CREATE TABLE art(art_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, type TEXT, url TEXT)");
4134     m_pDS->exec("CREATE INDEX ix_art ON art(media_id, media_type(20), type(20))");
4135     m_pDS->exec("CREATE TRIGGER delete_movie AFTER DELETE ON movie FOR EACH ROW BEGIN DELETE FROM art WHERE media_id=old.idMovie AND media_type='movie'; END");
4136     m_pDS->exec("CREATE TRIGGER delete_tvshow AFTER DELETE ON tvshow FOR EACH ROW BEGIN DELETE FROM art WHERE media_id=old.idShow AND media_type='tvshow'; END");
4137     m_pDS->exec("CREATE TRIGGER delete_musicvideo AFTER DELETE ON musicvideo FOR EACH ROW BEGIN DELETE FROM art WHERE media_id=old.idMVideo AND media_type='musicvideo'; END");
4138     m_pDS->exec("CREATE TRIGGER delete_episode AFTER DELETE ON episode FOR EACH ROW BEGIN DELETE FROM art WHERE media_id=old.idEpisode AND media_type='episode'; END");
4139     m_pDS->exec("CREATE TRIGGER delete_season AFTER DELETE ON seasons FOR EACH ROW BEGIN DELETE FROM art WHERE media_id=old.idSeason AND media_type='season'; END");
4140     m_pDS->exec("CREATE TRIGGER delete_set AFTER DELETE ON sets FOR EACH ROW BEGIN DELETE FROM art WHERE media_id=old.idSet AND media_type='set'; END");
4141     m_pDS->exec("CREATE TRIGGER delete_person AFTER DELETE ON actors FOR EACH ROW BEGIN DELETE FROM art WHERE media_id=old.idActor AND media_type IN ('actor','artist','writer','director'); END");
4142
4143     g_settings.m_videoNeedsUpdate = 63;
4144     g_settings.Save();
4145   }
4146   if (iVersion < 64)
4147   { // add idShow to episode table
4148     m_pDS->exec("ALTER TABLE episode ADD idShow integer");
4149     m_pDS->query("SELECT idEpisode FROM episode");
4150     while (!m_pDS->eof())
4151     {
4152       int idEpisode = m_pDS->fv(0).get_asInt();
4153       CStdString update = PrepareSQL("UPDATE episode SET idShow=(SELECT idShow FROM tvshowlinkepisode WHERE idEpisode=%d) WHERE idEpisode=%d", idEpisode, idEpisode);
4154       m_pDS2->exec(update.c_str());
4155       m_pDS->next();
4156     }
4157     m_pDS->exec("DROP TABLE tvshowlinkepisode");
4158     m_pDS->exec("CREATE INDEX ix_episode_show1 on episode(idEpisode,idShow)");
4159     m_pDS->exec("CREATE INDEX ix_episode_show2 on episode(idShow,idEpisode)");
4160   }
4161   if (iVersion < 67)
4162   {
4163     m_pDS->exec("CREATE TABLE tag (idTag integer primary key, strTag text)");
4164     m_pDS->exec("CREATE UNIQUE INDEX ix_tag_1 ON tag (strTag(255))");
4165
4166     m_pDS->exec("CREATE TABLE taglinks (idTag integer, idMedia integer, media_type TEXT)");
4167     m_pDS->exec("CREATE UNIQUE INDEX ix_taglinks_1 ON taglinks (idTag, media_type(20), idMedia)");
4168     m_pDS->exec("CREATE UNIQUE INDEX ix_taglinks_2 ON taglinks (idMedia, media_type(20), idTag)");
4169     m_pDS->exec("CREATE INDEX ix_taglinks_3 ON taglinks (media_type(20))");
4170   }
4171   if (iVersion < 68)
4172   { // add idSet to movie table
4173     m_pDS->exec("ALTER TABLE movie ADD idSet integer");
4174     m_pDS->query("SELECT idMovie FROM movie");
4175     while (!m_pDS->eof())
4176     {
4177       int idMovie = m_pDS->fv(0).get_asInt();
4178       CStdString sql = PrepareSQL("UPDATE movie SET idSet=(SELECT idSet FROM setlinkmovie WHERE idMovie = %d LIMIT 1) WHERE idMovie = %d", idMovie, idMovie);
4179       m_pDS2->exec(sql.c_str());
4180       m_pDS->next();
4181     }
4182     m_pDS->exec("DROP TABLE IF EXISTS setlinkmovie");
4183   }
4184   if (iVersion < 70)
4185   { // update old art URLs
4186     m_pDS->query("select art_id,url from art where url like 'image://%%'");
4187     vector< pair<int, string> > art;
4188     while (!m_pDS->eof())
4189     {
4190       art.push_back(make_pair(m_pDS->fv(0).get_asInt(), CURL(m_pDS->fv(1).get_asString()).Get()));
4191       m_pDS->next();
4192     }
4193     m_pDS->close();
4194     for (vector< pair<int, string> >::iterator i = art.begin(); i != art.end(); ++i)
4195       m_pDS->exec(PrepareSQL("update art set url='%s' where art_id=%d", i->second.c_str(), i->first));
4196   }
4197   if (iVersion < 71)
4198   { // update URL encoded paths
4199     m_pDS->query("select idFile, strFilename from files");
4200     vector< pair<int, string> > files;
4201     while (!m_pDS->eof())
4202     {
4203       files.push_back(make_pair(m_pDS->fv(0).get_asInt(), m_pDS->fv(1).get_asString()));
4204       m_pDS->next();
4205     }
4206     m_pDS->close();
4207
4208     for (vector< pair<int, string> >::iterator i = files.begin(); i != files.end(); ++i)
4209     {
4210       std::string filename = i->second;
4211       bool update = URIUtils::UpdateUrlEncoding(filename) &&
4212                     (!m_pDS->query(PrepareSQL("SELECT idFile FROM files WHERE strFilename = '%s'", filename.c_str())) || m_pDS->num_rows() <= 0);
4213       m_pDS->close();
4214
4215       if (update)
4216         m_pDS->exec(PrepareSQL("UPDATE files SET strFilename='%s' WHERE idFile=%d", filename.c_str(), i->first));
4217     }
4218   }
4219   if (iVersion < 72)
4220   { // Update thumb to poster or banner as applicable
4221     CTextureDatabase db;
4222     if (db.Open())
4223     {
4224       m_pDS->query("select art_id,url,media_id,media_type from art where type='thumb' and media_type in ('movie', 'musicvideo', 'tvshow', 'season', 'set')");
4225       vector<CArtItem> art;
4226       while (!m_pDS->eof())
4227       {
4228         CTextureDetails details;
4229         if (db.GetCachedTexture(m_pDS->fv(1).get_asString(), details))
4230         {
4231           CArtItem item;
4232           item.art_id = m_pDS->fv(0).get_asInt();
4233           item.art_url = m_pDS->fv(1).get_asString();
4234           item.art_type = CVideoInfoScanner::GetArtTypeFromSize(details.width, details.height);
4235           item.media_id = m_pDS->fv(2).get_asInt();
4236           item.media_type = m_pDS->fv(3).get_asString();
4237           if (item.art_type != "thumb")
4238             art.push_back(item);
4239         }
4240         m_pDS->next();
4241       }
4242       m_pDS->close();
4243       for (vector<CArtItem>::iterator i = art.begin(); i != art.end(); ++i)
4244       {
4245         if (GetArtForItem(i->media_id, i->media_type, i->art_type).empty())
4246           m_pDS->exec(PrepareSQL("update art set type='%s' where art_id=%d", i->art_type.c_str(), i->art_id));
4247         else
4248           m_pDS->exec(PrepareSQL("delete from art where art_id=%d", i->art_id));
4249       }
4250     }
4251   }
4252   if (iVersion < 73)
4253   {
4254     m_pDS->exec("DROP TRIGGER IF EXISTS delete_movie");
4255     m_pDS->exec("DROP TRIGGER IF EXISTS delete_tvshow");
4256     m_pDS->exec("DROP TRIGGER IF EXISTS delete_musicvideo");
4257     m_pDS->exec("DROP TRIGGER IF EXISTS delete_episode");
4258     m_pDS->exec("DROP TRIGGER IF EXISTS delete_season");
4259     m_pDS->exec("DROP TRIGGER IF EXISTS delete_set");
4260     m_pDS->exec("DROP TRIGGER IF EXISTS delete_person");
4261
4262     m_pDS->exec("CREATE TRIGGER delete_movie AFTER DELETE ON movie FOR EACH ROW BEGIN "
4263                 "DELETE FROM art WHERE media_id=old.idMovie AND media_type='movie'; "
4264                 "DELETE FROM taglinks WHERE idMedia=old.idMovie AND media_type='movie'; "
4265                 "END");
4266     m_pDS->exec("CREATE TRIGGER delete_tvshow AFTER DELETE ON tvshow FOR EACH ROW BEGIN "
4267                 "DELETE FROM art WHERE media_id=old.idShow AND media_type='tvshow'; "
4268                 "DELETE FROM taglinks WHERE idMedia=old.idShow AND media_type='tvshow'; "
4269                 "END");
4270     m_pDS->exec("CREATE TRIGGER delete_musicvideo AFTER DELETE ON musicvideo FOR EACH ROW BEGIN "
4271                 "DELETE FROM art WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
4272                 "DELETE FROM taglinks WHERE idMedia=old.idMVideo AND media_type='musicvideo'; "
4273                 "END");
4274     m_pDS->exec("CREATE TRIGGER delete_episode AFTER DELETE ON episode FOR EACH ROW BEGIN "
4275                 "DELETE FROM art WHERE media_id=old.idEpisode AND media_type='episode'; "
4276                 "END");
4277     m_pDS->exec("CREATE TRIGGER delete_season AFTER DELETE ON seasons FOR EACH ROW BEGIN "
4278                 "DELETE FROM art WHERE media_id=old.idSeason AND media_type='season'; "
4279                 "END");
4280     m_pDS->exec("CREATE TRIGGER delete_set AFTER DELETE ON sets FOR EACH ROW BEGIN "
4281                 "DELETE FROM art WHERE media_id=old.idSet AND media_type='set'; "
4282                 "END");
4283     m_pDS->exec("CREATE TRIGGER delete_person AFTER DELETE ON actors FOR EACH ROW BEGIN "
4284                 "DELETE FROM art WHERE media_id=old.idActor AND media_type IN ('actor','artist','writer','director'); "
4285                 "END");
4286     m_pDS->exec("CREATE TRIGGER delete_tag AFTER DELETE ON taglinks FOR EACH ROW BEGIN "
4287                 "DELETE FROM tag WHERE idTag=old.idTag AND idTag NOT IN (SELECT DISTINCT idTag FROM taglinks); "
4288                 "END");
4289   }
4290   if (iVersion < 74)
4291   { // update the runtime columns
4292     vector< pair<string, int> > tables;
4293     tables.push_back(make_pair("movie", VIDEODB_ID_RUNTIME));
4294     tables.push_back(make_pair("episode", VIDEODB_ID_EPISODE_RUNTIME));
4295     tables.push_back(make_pair("mvideo", VIDEODB_ID_MUSICVIDEO_RUNTIME));
4296     for (vector< pair<string, int> >::iterator i = tables.begin(); i != tables.end(); ++i)
4297     {
4298       CStdString sql = PrepareSQL("select id%s,c%02d from %s where c%02d != ''", i->first.c_str(), i->second, (i->first=="mvideo")?"musicvideo":i->first.c_str(), i->second);
4299       m_pDS->query(sql.c_str());
4300       vector< pair<int, int> > videos;
4301       while (!m_pDS->eof())
4302       {
4303         int duration = CVideoInfoTag::GetDurationFromMinuteString(m_pDS->fv(1).get_asString());
4304         if (duration)
4305           videos.push_back(make_pair(m_pDS->fv(0).get_asInt(), duration));
4306         m_pDS->next();
4307       }
4308       m_pDS->close();
4309       for (vector< pair<int, int> >::iterator j = videos.begin(); j != videos.end(); ++j)
4310         m_pDS->exec(PrepareSQL("update %s set c%02d=%d where id%s=%d", (i->first=="mvideo")?"musicvideo":i->first.c_str(), i->second, j->second, i->first.c_str(), j->first));
4311     }
4312   }
4313   if (iVersion < 75)
4314   { // make indices on path, file non-unique (mysql has a prefix index, and prefixes aren't necessarily unique)
4315     m_pDS->dropIndex("path", "ix_path");
4316     m_pDS->dropIndex("files", "ix_files");
4317     m_pDS->exec("CREATE INDEX ix_path ON path ( strPath(255) )");
4318     m_pDS->exec("CREATE INDEX ix_files ON files ( idPath, strFilename(255) )");
4319   }
4320   // always recreate the view after any table change
4321   CreateViews();
4322   return true;
4323 }
4324
4325 int CVideoDatabase::GetMinVersion() const
4326 {
4327   return 75;
4328 }
4329
4330 bool CVideoDatabase::LookupByFolders(const CStdString &path, bool shows)
4331 {
4332   SScanSettings settings;
4333   bool foundDirectly = false;
4334   ScraperPtr scraper = GetScraperForPath(path, settings, foundDirectly);
4335   if (scraper && scraper->Content() == CONTENT_TVSHOWS && !shows)
4336     return false; // episodes
4337   return settings.parent_name_root; // shows, movies, musicvids
4338 }
4339
4340 void CVideoDatabase::UpdateBasePath(const char *table, const char *id, int column, bool shows, const CStdString &where)
4341 {
4342   CStdString query;
4343   if (shows)
4344     query = PrepareSQL("SELECT idShow,path.strPath from tvshowlinkpath join path on tvshowlinkpath.idPath=path.idPath");
4345   else
4346     query = PrepareSQL("SELECT %s.%s,path.strPath,files.strFileName from %s join files on %s.idFile=files.idFile join path on files.idPath=path.idPath", table, id, table, table);
4347   query += where;
4348
4349   map<CStdString, bool> paths;
4350   m_pDS2->query(query.c_str());
4351   while (!m_pDS2->eof())
4352   {
4353     CStdString path(m_pDS2->fv(1).get_asString());
4354     map<CStdString, bool>::iterator i = paths.find(path);
4355     if (i == paths.end())
4356     {
4357       paths.insert(make_pair(path, LookupByFolders(path, shows)));
4358       i = paths.find(path);
4359     }
4360     CStdString filename;
4361     if (!shows)
4362       ConstructPath(filename, path, m_pDS2->fv(2).get_asString());
4363     else
4364       filename = path;
4365     CFileItem item(filename, shows);
4366     path = item.GetBaseMoviePath(i->second);
4367     CStdString sql = PrepareSQL("UPDATE %s set c%02d='%s' where %s.%s=%i", table, column, path.c_str(), table, id, m_pDS2->fv(0).get_asInt());
4368     m_pDS->exec(sql.c_str());
4369     m_pDS2->next();
4370   }
4371   m_pDS2->close();
4372 }
4373
4374 void CVideoDatabase::UpdateBasePathID(const char *table, const char *id, int column, int idColumn)
4375 {
4376   CStdString query = PrepareSQL("SELECT %s,c%02d from %s", id, column, table);
4377   m_pDS2->query(query.c_str());
4378   while (!m_pDS2->eof())
4379   {
4380     int rowID = m_pDS2->fv(0).get_asInt();
4381     CStdString path(m_pDS2->fv(1).get_asString());
4382     // find the parent path of this item
4383     int pathID = AddPath(URIUtils::GetParentPath(path));
4384     if (pathID >= 0)
4385     {
4386       CStdString sql = PrepareSQL("UPDATE %s SET c%02d=%d WHERE %s=%d", table, idColumn, pathID, id, rowID);
4387       m_pDS->exec(sql.c_str());
4388     }
4389     m_pDS2->next();
4390   }
4391   m_pDS2->close();
4392 }
4393
4394 bool CVideoDatabase::GetPlayCounts(const CStdString &strPath, CFileItemList &items)
4395 {
4396   if(URIUtils::IsMultiPath(strPath))
4397   {
4398     vector<CStdString> paths;
4399     CMultiPathDirectory::GetPaths(strPath, paths);
4400
4401     bool ret = false;
4402     for(unsigned i=0;i<paths.size();i++)
4403       ret |= GetPlayCounts(paths[i], items);
4404
4405     return ret;
4406   }
4407   int pathID;
4408   if (URIUtils::IsPlugin(strPath))
4409   {
4410     CURL url(strPath);
4411     pathID = GetPathId(url.GetWithoutFilename());
4412   }
4413   else
4414     pathID = GetPathId(strPath);
4415   if (pathID < 0)
4416     return false; // path (and thus files) aren't in the database
4417
4418   try
4419   {
4420     // error!
4421     if (NULL == m_pDB.get()) return false;
4422     if (NULL == m_pDS.get()) return false;
4423
4424     // TODO: also test a single query for the above and below
4425     CStdString sql = PrepareSQL(
4426       "SELECT"
4427       "  files.strFilename, files.playCount,"
4428       "  bookmark.timeInSeconds, bookmark.totalTimeInSeconds "
4429       "FROM files"
4430       "  LEFT JOIN bookmark ON"
4431       "    files.idFile = bookmark.idFile AND bookmark.type = %i"
4432       "  WHERE files.idPath=%i", (int)CBookmark::RESUME, pathID);
4433
4434     if (RunQuery(sql) <= 0)
4435       return false;
4436
4437     items.SetFastLookup(true); // note: it's possibly quicker the other way around (map on db returned items)?
4438     while (!m_pDS->eof())
4439     {
4440       CStdString path;
4441       ConstructPath(path, strPath, m_pDS->fv(0).get_asString());
4442       CFileItemPtr item = items.Get(path);
4443       if (item)
4444       {
4445         item->GetVideoInfoTag()->m_playCount = m_pDS->fv(1).get_asInt();
4446         if (!item->GetVideoInfoTag()->m_resumePoint.IsSet())
4447         {
4448           item->GetVideoInfoTag()->m_resumePoint.timeInSeconds = m_pDS->fv(2).get_asInt();
4449           item->GetVideoInfoTag()->m_resumePoint.totalTimeInSeconds = m_pDS->fv(3).get_asInt();
4450           item->GetVideoInfoTag()->m_resumePoint.type = CBookmark::RESUME;
4451         }
4452       }
4453       m_pDS->next();
4454     }
4455     return true;
4456   }
4457   catch (...)
4458   {
4459     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
4460   }
4461   return false;
4462 }
4463
4464 int CVideoDatabase::GetPlayCount(const CFileItem &item)
4465 {
4466   int id = GetFileId(item);
4467   if (id < 0)
4468     return 0;  // not in db, so not watched
4469
4470   try
4471   {
4472     // error!
4473     if (NULL == m_pDB.get()) return -1;
4474     if (NULL == m_pDS.get()) return -1;
4475
4476     CStdString strSQL = PrepareSQL("select playCount from files WHERE idFile=%i", id);
4477     int count = 0;
4478     if (m_pDS->query(strSQL.c_str()))
4479     {
4480       // there should only ever be one row returned
4481       if (m_pDS->num_rows() == 1)
4482         count = m_pDS->fv(0).get_asInt();
4483       m_pDS->close();
4484     }
4485     return count;
4486   }
4487   catch (...)
4488   {
4489     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
4490   }
4491   return -1;
4492 }
4493
4494 void CVideoDatabase::UpdateFanart(const CFileItem &item, VIDEODB_CONTENT_TYPE type)
4495 {
4496   if (NULL == m_pDB.get()) return;
4497   if (NULL == m_pDS.get()) return;
4498   if (!item.HasVideoInfoTag() || item.GetVideoInfoTag()->m_iDbId < 0) return;
4499
4500   CStdString exec;
4501   if (type == VIDEODB_CONTENT_TVSHOWS)
4502     exec = PrepareSQL("UPDATE tvshow set c%02d='%s' WHERE idShow=%i", VIDEODB_ID_TV_FANART, item.GetVideoInfoTag()->m_fanart.m_xml.c_str(), item.GetVideoInfoTag()->m_iDbId);
4503   else if (type == VIDEODB_CONTENT_MOVIES)
4504     exec = PrepareSQL("UPDATE movie set c%02d='%s' WHERE idMovie=%i", VIDEODB_ID_FANART, item.GetVideoInfoTag()->m_fanart.m_xml.c_str(), item.GetVideoInfoTag()->m_iDbId);
4505
4506   try
4507   {
4508     m_pDS->exec(exec.c_str());
4509
4510     if (type == VIDEODB_CONTENT_TVSHOWS)
4511       AnnounceUpdate("tvshow", item.GetVideoInfoTag()->m_iDbId);
4512     else if (type == VIDEODB_CONTENT_MOVIES)
4513       AnnounceUpdate("movie", item.GetVideoInfoTag()->m_iDbId);
4514   }
4515   catch (...)
4516   {
4517     CLog::Log(LOGERROR, "%s - error updating fanart for %s", __FUNCTION__, item.GetPath().c_str());
4518   }
4519 }
4520
4521 void CVideoDatabase::SetPlayCount(const CFileItem &item, int count, const CDateTime &date)
4522 {
4523   int id;
4524   if (item.HasProperty("original_listitem_url") &&
4525       URIUtils::IsPlugin(item.GetProperty("original_listitem_url").asString()))
4526   {
4527     CFileItem item2(item);
4528     item2.SetPath(item.GetProperty("original_listitem_url").asString());
4529     id = AddFile(item2);
4530   }
4531   else
4532     id = AddFile(item);
4533   if (id < 0)
4534     return;
4535
4536   // and mark as watched
4537   try
4538   {
4539     if (NULL == m_pDB.get()) return ;
4540     if (NULL == m_pDS.get()) return ;
4541
4542     CStdString strSQL;
4543     if (count)
4544     {
4545       if (!date.IsValid())
4546         strSQL = PrepareSQL("update files set playCount=%i,lastPlayed='%s' where idFile=%i", count, CDateTime::GetCurrentDateTime().GetAsDBDateTime().c_str(), id);
4547       else
4548         strSQL = PrepareSQL("update files set playCount=%i,lastPlayed='%s' where idFile=%i", count, date.GetAsDBDateTime().c_str(), id);
4549     }
4550     else
4551     {
4552       if (!date.IsValid())
4553         strSQL = PrepareSQL("update files set playCount=NULL,lastPlayed=NULL where idFile=%i", id);
4554       else
4555         strSQL = PrepareSQL("update files set playCount=NULL,lastPlayed='%s' where idFile=%i", date.GetAsDBDateTime().c_str(), id);
4556     }
4557
4558     m_pDS->exec(strSQL.c_str());
4559
4560     // We only need to announce changes to video items in the library
4561     if (item.HasVideoInfoTag() && item.GetVideoInfoTag()->m_iDbId > 0)
4562     {
4563       // Only provide the "playcount" value if it has actually changed
4564       if (item.GetVideoInfoTag()->m_playCount != count)
4565       {
4566         CVariant data;
4567         data["playcount"] = count;
4568         ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::VideoLibrary, "xbmc", "OnUpdate", CFileItemPtr(new CFileItem(item)), data);
4569       }
4570       else
4571         ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::VideoLibrary, "xbmc", "OnUpdate", CFileItemPtr(new CFileItem(item)));
4572     }
4573   }
4574   catch (...)
4575   {
4576     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
4577   }
4578 }
4579
4580 void CVideoDatabase::IncrementPlayCount(const CFileItem &item)
4581 {
4582   SetPlayCount(item, GetPlayCount(item) + 1);
4583 }
4584
4585 void CVideoDatabase::UpdateLastPlayed(const CFileItem &item)
4586 {
4587   SetPlayCount(item, GetPlayCount(item), CDateTime::GetCurrentDateTime());
4588 }
4589
4590 void CVideoDatabase::UpdateMovieTitle(int idMovie, const CStdString& strNewMovieTitle, VIDEODB_CONTENT_TYPE iType)
4591 {
4592   try
4593   {
4594     if (NULL == m_pDB.get()) return ;
4595     if (NULL == m_pDS.get()) return ;
4596     CStdString content;
4597     CStdString strSQL;
4598     if (iType == VIDEODB_CONTENT_MOVIES)
4599     {
4600       CLog::Log(LOGINFO, "Changing Movie:id:%i New Title:%s", idMovie, strNewMovieTitle.c_str());
4601       strSQL = PrepareSQL("UPDATE movie SET c%02d='%s' WHERE idMovie=%i", VIDEODB_ID_TITLE, strNewMovieTitle.c_str(), idMovie );
4602       content = "movie";
4603     }
4604     else if (iType == VIDEODB_CONTENT_EPISODES)
4605     {
4606       CLog::Log(LOGINFO, "Changing Episode:id:%i New Title:%s", idMovie, strNewMovieTitle.c_str());
4607       strSQL = PrepareSQL("UPDATE episode SET c%02d='%s' WHERE idEpisode=%i", VIDEODB_ID_EPISODE_TITLE, strNewMovieTitle.c_str(), idMovie );
4608       content = "episode";
4609     }
4610     else if (iType == VIDEODB_CONTENT_TVSHOWS)
4611     {
4612       CLog::Log(LOGINFO, "Changing TvShow:id:%i New Title:%s", idMovie, strNewMovieTitle.c_str());
4613       strSQL = PrepareSQL("UPDATE tvshow SET c%02d='%s' WHERE idShow=%i", VIDEODB_ID_TV_TITLE, strNewMovieTitle.c_str(), idMovie );
4614       content = "tvshow";
4615     }
4616     else if (iType == VIDEODB_CONTENT_MUSICVIDEOS)
4617     {
4618       CLog::Log(LOGINFO, "Changing MusicVideo:id:%i New Title:%s", idMovie, strNewMovieTitle.c_str());
4619       strSQL = PrepareSQL("UPDATE musicvideo SET c%02d='%s' WHERE idMVideo=%i", VIDEODB_ID_MUSICVIDEO_TITLE, strNewMovieTitle.c_str(), idMovie );
4620       content = "musicvideo";
4621     }
4622     else if (iType == VIDEODB_CONTENT_MOVIE_SETS)
4623     {
4624       CLog::Log(LOGINFO, "Changing Movie set:id:%i New Title:%s", idMovie, strNewMovieTitle.c_str());
4625       strSQL = PrepareSQL("UPDATE sets SET strSet='%s' WHERE idSet=%i", strNewMovieTitle.c_str(), idMovie );
4626     }
4627     m_pDS->exec(strSQL.c_str());
4628
4629     if (content.size() > 0)
4630       AnnounceUpdate(content, idMovie);
4631   }
4632   catch (...)
4633   {
4634     CLog::Log(LOGERROR, "%s (int idMovie, const CStdString& strNewMovieTitle) failed on MovieID:%i and Title:%s", __FUNCTION__, idMovie, strNewMovieTitle.c_str());
4635   }
4636 }
4637
4638 /// \brief EraseVideoSettings() Erases the videoSettings table and reconstructs it
4639 void CVideoDatabase::EraseVideoSettings()
4640 {
4641   try
4642   {
4643     CLog::Log(LOGINFO, "Deleting settings information for all movies");
4644     m_pDS->exec("delete from settings");
4645   }
4646   catch (...)
4647   {
4648     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
4649   }
4650 }
4651
4652 bool CVideoDatabase::GetGenresNav(const CStdString& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
4653 {
4654   return GetNavCommon(strBaseDir, items, "genre", idContent, filter, countOnly);
4655 }
4656
4657 bool CVideoDatabase::GetCountriesNav(const CStdString& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
4658 {
4659   return GetNavCommon(strBaseDir, items, "country", idContent, filter, countOnly);
4660 }
4661
4662 bool CVideoDatabase::GetStudiosNav(const CStdString& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
4663 {
4664   return GetNavCommon(strBaseDir, items, "studio", idContent, filter, countOnly);
4665 }
4666
4667 bool CVideoDatabase::GetNavCommon(const CStdString& strBaseDir, CFileItemList& items, const CStdString &type, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
4668 {
4669   try
4670   {
4671     if (NULL == m_pDB.get()) return false;
4672     if (NULL == m_pDS.get()) return false;
4673
4674     CStdString strSQL;
4675     Filter extFilter = filter;
4676     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
4677     {
4678       if (idContent == VIDEODB_CONTENT_MOVIES)
4679       {
4680         strSQL = "select %s " + PrepareSQL("from %s ", type.c_str());
4681         extFilter.fields = PrepareSQL("%s.id%s, %s.str%s, path.strPath, files.playCount", type.c_str(), type.c_str(), type.c_str(), type.c_str());
4682         extFilter.AppendJoin(PrepareSQL("join %slinkmovie on %s.id%s = %slinkmovie.id%s join movieview on %slinkmovie.idMovie = movieview.idMovie join files on files.idFile = movieview.idFile join path on path.idPath = files.idPath",
4683                                         type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str()));
4684       }
4685       else if (idContent == VIDEODB_CONTENT_TVSHOWS) //this will not get tvshows with 0 episodes
4686       {
4687         strSQL = "select %s " + PrepareSQL("from %s ", type.c_str());
4688         extFilter.fields = PrepareSQL("%s.id%s, %s.str%s, path.strPath", type.c_str(), type.c_str(), type.c_str(), type.c_str());
4689         extFilter.AppendJoin(PrepareSQL("join %slinktvshow on %s.id%s = %slinktvshow.id%s join episodeview on %slinktvshow.idShow = episodeview.idShow join files on files.idFile = episodeview.idFile join path on path.idPath = files.idPath",
4690                                         type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str()));
4691       }
4692       else if (idContent == VIDEODB_CONTENT_MUSICVIDEOS)
4693       {
4694         strSQL = "select %s " + PrepareSQL("from %s ", type.c_str());
4695         extFilter.fields = PrepareSQL("%s.id%s, %s.str%s, path.strPath, files.playCount", type.c_str(), type.c_str(), type.c_str(), type.c_str());
4696         extFilter.AppendJoin(PrepareSQL("join %slinkmusicvideo on %s.id%s = %slinkmusicvideo.id%s join musicvideoview on %slinkmusicvideo.idMVideo = musicvideoview.idMVideo join files on files.idFile = musicvideoview.idFile join path on path.idPath = files.idPath",
4697                                         type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str()));
4698       }
4699       else
4700         return false;
4701     }
4702     else
4703     {
4704       if (idContent == VIDEODB_CONTENT_MOVIES)
4705       {
4706         strSQL = "select %s " + PrepareSQL("from %s ", type.c_str());
4707         extFilter.fields = PrepareSQL("%s.id%s, %s.str%s, count(1), count(files.playCount)", type.c_str(), type.c_str(), type.c_str(), type.c_str());
4708         extFilter.AppendJoin(PrepareSQL("join %slinkmovie on %s.id%s = %slinkmovie.id%s join movieview on %slinkmovie.idMovie = movieview.idMovie join files on files.idFile = movieview.idFile",
4709                                         type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str()));
4710         extFilter.AppendGroup(PrepareSQL("%s.id%s", type.c_str(), type.c_str()));
4711       }
4712       else if (idContent == VIDEODB_CONTENT_TVSHOWS)
4713       {
4714         strSQL = "select %s " + PrepareSQL("from %s ", type.c_str());
4715         extFilter.fields = PrepareSQL("distinct %s.id%s, %s.str%s", type.c_str(), type.c_str(), type.c_str(), type.c_str());
4716         extFilter.AppendJoin(PrepareSQL("join %slinktvshow on %s.id%s = %slinktvshow.id%s join tvshowview on %slinktvshow.idShow = tvshowview.idShow",
4717                                         type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str()));
4718       }
4719       else if (idContent == VIDEODB_CONTENT_MUSICVIDEOS)
4720       {
4721         strSQL = "select %s " + PrepareSQL("from %s ", type.c_str());
4722         extFilter.fields = PrepareSQL("%s.id%s, %s.str%s, count(1), count(files.playCount)", type.c_str(), type.c_str(), type.c_str(), type.c_str());
4723         extFilter.AppendJoin(PrepareSQL("join %slinkmusicvideo on %s.id%s = %slinkmusicvideo.id%s join musicvideoview on %slinkmusicvideo.idMVideo = musicvideoview.idMVideo join files on files.idFile = musicvideoview.idFile",
4724                                         type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str(), type.c_str()));
4725         extFilter.AppendGroup(PrepareSQL("%s.id%s", type.c_str(), type.c_str()));
4726       }
4727       else
4728         return false;
4729     }
4730
4731     if (countOnly)
4732     {
4733       extFilter.fields = PrepareSQL("COUNT(DISTINCT %s.id%s)", type.c_str(), type.c_str());
4734       extFilter.group.clear();
4735       extFilter.order.clear();
4736     }
4737     strSQL.Format(strSQL.c_str(), !extFilter.fields.empty() ? extFilter.fields.c_str() : "*");
4738
4739     CVideoDbUrl videoUrl;
4740     if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl))
4741       return false;
4742
4743     int iRowsFound = RunQuery(strSQL);
4744     if (iRowsFound <= 0)
4745       return iRowsFound == 0;
4746
4747     if (countOnly)
4748     {
4749       CFileItemPtr pItem(new CFileItem());
4750       pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound);
4751       items.Add(pItem);
4752
4753       m_pDS->close();
4754       return true;
4755     }
4756
4757     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
4758     {
4759       map<int, pair<CStdString,int> > mapItems;
4760       map<int, pair<CStdString,int> >::iterator it;
4761       while (!m_pDS->eof())
4762       {
4763         int id = m_pDS->fv(0).get_asInt();
4764         CStdString str = m_pDS->fv(1).get_asString();
4765
4766         // was this already found?
4767         it = mapItems.find(id);
4768         if (it == mapItems.end())
4769         {
4770           // check path
4771           if (g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv(2).get_asString()),g_settings.m_videoSources))
4772           {
4773             if (idContent == VIDEODB_CONTENT_MOVIES || idContent == VIDEODB_CONTENT_MUSICVIDEOS)
4774               mapItems.insert(pair<int, pair<CStdString,int> >(id, pair<CStdString,int>(str,m_pDS->fv(3).get_asInt()))); //fv(3) is file.playCount
4775             else if (idContent == VIDEODB_CONTENT_TVSHOWS)
4776               mapItems.insert(pair<int, pair<CStdString,int> >(id, pair<CStdString,int>(str,0)));
4777           }
4778         }
4779         m_pDS->next();
4780       }
4781       m_pDS->close();
4782
4783       for (it = mapItems.begin(); it != mapItems.end(); ++it)
4784       {
4785         CFileItemPtr pItem(new CFileItem(it->second.first));
4786         pItem->GetVideoInfoTag()->m_iDbId = it->first;
4787         pItem->GetVideoInfoTag()->m_type = type;
4788
4789         CVideoDbUrl itemUrl = videoUrl;
4790         CStdString path; path.Format("%ld/", it->first);
4791         itemUrl.AppendPath(path);
4792         pItem->SetPath(itemUrl.ToString());
4793
4794         pItem->m_bIsFolder = true;
4795         if (idContent == VIDEODB_CONTENT_MOVIES || idContent == VIDEODB_CONTENT_MUSICVIDEOS)
4796           pItem->GetVideoInfoTag()->m_playCount = it->second.second;
4797         if (!items.Contains(pItem->GetPath()))
4798         {
4799           pItem->SetLabelPreformated(true);
4800           items.Add(pItem);
4801         }
4802       }
4803     }
4804     else
4805     {
4806       while (!m_pDS->eof())
4807       {
4808         CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
4809         pItem->GetVideoInfoTag()->m_iDbId = m_pDS->fv(0).get_asInt();
4810         pItem->GetVideoInfoTag()->m_type = type;
4811
4812         CVideoDbUrl itemUrl = videoUrl;
4813         CStdString path; path.Format("%ld/", m_pDS->fv(0).get_asInt());
4814         itemUrl.AppendPath(path);
4815         pItem->SetPath(itemUrl.ToString());
4816
4817         pItem->m_bIsFolder = true;
4818         pItem->SetLabelPreformated(true);
4819         if (idContent == VIDEODB_CONTENT_MOVIES || idContent == VIDEODB_CONTENT_MUSICVIDEOS)
4820         { // fv(3) is the number of videos watched, fv(2) is the total number.  We set the playcount
4821           // only if the number of videos watched is equal to the total number (i.e. every video watched)
4822           pItem->GetVideoInfoTag()->m_playCount = (m_pDS->fv(3).get_asInt() == m_pDS->fv(2).get_asInt()) ? 1 : 0;
4823         }
4824         items.Add(pItem);
4825         m_pDS->next();
4826       }
4827       m_pDS->close();
4828     }
4829     return true;
4830   }
4831   catch (...)
4832   {
4833     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
4834   }
4835   return false;
4836 }
4837
4838 bool CVideoDatabase::GetTagsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
4839 {
4840   CStdString mediaType;
4841   if (idContent == VIDEODB_CONTENT_MOVIES)
4842     mediaType = "movie";
4843   else if (idContent == VIDEODB_CONTENT_TVSHOWS)
4844     mediaType = "tvshow";
4845   else if (idContent == VIDEODB_CONTENT_MUSICVIDEOS)
4846     mediaType = "musicvideo";
4847   else
4848     return false;
4849
4850   try
4851   {
4852     if (NULL == m_pDB.get()) return false;
4853     if (NULL == m_pDS.get()) return false;
4854
4855     CStdString strSQL = "SELECT %s FROM taglinks ";
4856
4857     Filter extFilter = filter;
4858     extFilter.fields = "tag.idTag, tag.strTag";
4859     extFilter.AppendJoin("JOIN tag ON tag.idTag = taglinks.idTag");
4860
4861     if (idContent == (int)VIDEODB_CONTENT_MOVIES)
4862       extFilter.AppendJoin("JOIN movieview ON movieview.idMovie = taglinks.idMedia");
4863
4864     extFilter.AppendWhere(PrepareSQL("taglinks.media_type = '%s'", mediaType.c_str()));
4865     extFilter.AppendGroup("taglinks.idTag");
4866
4867     if (countOnly)
4868     {
4869       extFilter.fields = "COUNT(DISTINCT taglinks.idTag)";
4870       extFilter.group.clear();
4871       extFilter.order.clear();
4872     }
4873     strSQL.Format(strSQL.c_str(), !extFilter.fields.empty() ? extFilter.fields.c_str() : "*");
4874
4875     // parse the base path to get additional filters
4876     CVideoDbUrl videoUrl;
4877     if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl))
4878       return false;
4879
4880     int iRowsFound = RunQuery(strSQL);
4881     if (iRowsFound <= 0)
4882       return iRowsFound == 0;
4883
4884     if (countOnly)
4885     {
4886       CFileItemPtr pItem(new CFileItem());
4887       pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound);
4888       items.Add(pItem);
4889
4890       m_pDS->close();
4891       return true;
4892     }
4893
4894     while (!m_pDS->eof())
4895     {
4896       int idTag = m_pDS->fv(0).get_asInt();
4897
4898       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
4899       pItem->m_bIsFolder = true;
4900       pItem->GetVideoInfoTag()->m_iDbId = idTag;
4901       pItem->GetVideoInfoTag()->m_type = "tag";
4902
4903       CVideoDbUrl itemUrl = videoUrl;
4904       CStdString path; path.Format("%ld/", idTag);
4905       itemUrl.AppendPath(path);
4906       pItem->SetPath(itemUrl.ToString());
4907
4908       if (!items.Contains(pItem->GetPath()))
4909       {
4910         pItem->SetLabelPreformated(true);
4911         items.Add(pItem);
4912       }
4913
4914       m_pDS->next();
4915     }
4916     m_pDS->close();
4917
4918     return true;
4919   }
4920   catch (...)
4921   {
4922     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
4923   }
4924   return false;
4925 }
4926
4927 bool CVideoDatabase::GetSetsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent, bool ignoreSingleMovieSets /* = false */)
4928 {
4929   if (idContent != VIDEODB_CONTENT_MOVIES)
4930     return false;
4931
4932   Filter filter;
4933   return GetSetsByWhere(strBaseDir, filter, items, ignoreSingleMovieSets);
4934 }
4935
4936 bool CVideoDatabase::GetSetsByWhere(const CStdString& strBaseDir, const Filter &filter, CFileItemList& items, bool ignoreSingleMovieSets /* = false */)
4937 {
4938   try
4939   {
4940     if (NULL == m_pDB.get()) return false;
4941     if (NULL == m_pDS.get()) return false;
4942
4943     CVideoDbUrl videoUrl;
4944     if (!videoUrl.FromString(strBaseDir))
4945       return false;
4946
4947     Filter setFilter = filter;
4948     setFilter.join += " JOIN sets ON movieview.idSet = sets.idSet";
4949     if (!setFilter.order.empty())
4950       setFilter.order += ",";
4951     setFilter.order += "sets.idSet";
4952
4953     if (!GetMoviesByWhere(strBaseDir, setFilter, items))
4954       return false;
4955
4956     CFileItemList sets;
4957     if (!GroupUtils::Group(GroupBySet, strBaseDir, items, sets))
4958       return false;
4959
4960     items.ClearItems();
4961     items.Append(sets);
4962
4963     return true;
4964   }
4965   catch (...)
4966   {
4967     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
4968   }
4969   return false;
4970 }
4971
4972 bool CVideoDatabase::GetMusicVideoAlbumsNav(const CStdString& strBaseDir, CFileItemList& items, int idArtist /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
4973 {
4974   try
4975   {
4976     if (NULL == m_pDB.get()) return false;
4977     if (NULL == m_pDS.get()) return false;
4978
4979     CStdString strSQL = "select %s from musicvideoview ";
4980     Filter extFilter = filter;
4981     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
4982     {
4983       extFilter.fields = PrepareSQL("musicvideoview.c%02d, musicvideoview.idMVideo, actors.strActor, path.strPath", VIDEODB_ID_MUSICVIDEO_ALBUM);
4984       extFilter.AppendJoin("join artistlinkmusicvideo on artistlinkmusicvideo.idMVideo = musicvideoview.idMVideo join actors on actors.idActor = artistlinkmusicvideo.idArtist join files on files.idFile = musicvideoview.idFile join path on path.idPath = files.idPath");
4985     }
4986     else
4987     {
4988       extFilter.fields = PrepareSQL("musicvideoview.c%02d, musicvideoview.idMVideo, actors.strActor", VIDEODB_ID_MUSICVIDEO_ALBUM);
4989       extFilter.AppendJoin("join artistlinkmusicvideo on artistlinkmusicvideo.idMVideo = musicvideoview.idMVideo join actors on actors.idActor = artistlinkmusicvideo.idArtist");
4990     }
4991     if (idArtist > -1)
4992       extFilter.AppendWhere(PrepareSQL("artistlinkmusicvideo.idArtist = %i", idArtist));
4993
4994     extFilter.AppendGroup(PrepareSQL("musicvideoview.c%02d", VIDEODB_ID_MUSICVIDEO_ALBUM));
4995
4996     if (countOnly)
4997     {
4998       extFilter.fields = "COUNT(1)";
4999       extFilter.group.clear();
5000       extFilter.order.clear();
5001     }
5002     strSQL.Format(strSQL.c_str(), !extFilter.fields.empty() ? extFilter.fields.c_str() : "*");
5003
5004     CVideoDbUrl videoUrl;
5005     if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl))
5006       return false;
5007
5008     int iRowsFound = RunQuery(strSQL);
5009     if (iRowsFound <= 0)
5010       return iRowsFound == 0;
5011
5012     if (countOnly)
5013     {
5014       CFileItemPtr pItem(new CFileItem());
5015       pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound);
5016       items.Add(pItem);
5017
5018       m_pDS->close();
5019       return true;
5020     }
5021
5022     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
5023     {
5024       map<int, pair<CStdString,CStdString> > mapAlbums;
5025       map<int, pair<CStdString,CStdString> >::iterator it;
5026       while (!m_pDS->eof())
5027       {
5028         int lidMVideo = m_pDS->fv(1).get_asInt();
5029         CStdString strAlbum = m_pDS->fv(0).get_asString();
5030         it = mapAlbums.find(lidMVideo);
5031         // was this genre already found?
5032         if (it == mapAlbums.end())
5033         {
5034           // check path
5035           if (g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv("path.strPath").get_asString()),g_settings.m_videoSources))
5036             mapAlbums.insert(make_pair(lidMVideo, make_pair(strAlbum,m_pDS->fv(2).get_asString())));
5037         }
5038         m_pDS->next();
5039       }
5040       m_pDS->close();
5041
5042       for (it=mapAlbums.begin();it != mapAlbums.end();++it)
5043       {
5044         if (!it->second.first.IsEmpty())
5045         {
5046           CFileItemPtr pItem(new CFileItem(it->second.first));
5047
5048           CVideoDbUrl itemUrl = videoUrl;
5049           CStdString path; path.Format("%ld/", it->first);
5050           itemUrl.AppendPath(path);
5051           pItem->SetPath(itemUrl.ToString());
5052
5053           pItem->m_bIsFolder=true;
5054           pItem->SetLabelPreformated(true);
5055           if (!items.Contains(pItem->GetPath()))
5056           {
5057             pItem->GetVideoInfoTag()->m_artist.push_back(it->second.second);
5058             items.Add(pItem);
5059           }
5060         }
5061       }
5062     }
5063     else
5064     {
5065       while (!m_pDS->eof())
5066       {
5067         if (!m_pDS->fv(0).get_asString().empty())
5068         {
5069           CFileItemPtr pItem(new CFileItem(m_pDS->fv(0).get_asString()));
5070
5071           CVideoDbUrl itemUrl = videoUrl;
5072           CStdString path; path.Format("%ld/", m_pDS->fv(1).get_asInt());
5073           itemUrl.AppendPath(path);
5074           pItem->SetPath(itemUrl.ToString());
5075
5076           pItem->m_bIsFolder=true;
5077           pItem->SetLabelPreformated(true);
5078           if (!items.Contains(pItem->GetPath()))
5079           {
5080             pItem->GetVideoInfoTag()->m_artist.push_back(m_pDS->fv(2).get_asString());
5081             items.Add(pItem);
5082           }
5083         }
5084         m_pDS->next();
5085       }
5086       m_pDS->close();
5087     }
5088
5089 //    CLog::Log(LOGDEBUG, __FUNCTION__" Time: %d ms", XbmcThreads::SystemClockMillis() - time);
5090     return true;
5091   }
5092   catch (...)
5093   {
5094     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
5095   }
5096   return false;
5097 }
5098
5099 bool CVideoDatabase::GetWritersNav(const CStdString& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
5100 {
5101   return GetPeopleNav(strBaseDir, items, "writer", idContent, filter, countOnly);
5102 }
5103
5104 bool CVideoDatabase::GetDirectorsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
5105 {
5106   return GetPeopleNav(strBaseDir, items, "director", idContent, filter, countOnly);
5107 }
5108
5109 bool CVideoDatabase::GetActorsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
5110 {
5111   if (GetPeopleNav(strBaseDir, items, (idContent == VIDEODB_CONTENT_MUSICVIDEOS) ? "artist" : "actor", idContent, filter, countOnly))
5112   { // set thumbs - ideally this should be in the normal thumb setting routines
5113     for (int i = 0; i < items.Size() && !countOnly; i++)
5114     {
5115       CFileItemPtr pItem = items[i];
5116       if (idContent == VIDEODB_CONTENT_MUSICVIDEOS)
5117         pItem->SetIconImage("DefaultArtist.png");
5118       else
5119         pItem->SetIconImage("DefaultActor.png");
5120     }
5121     return true;
5122   }
5123   return false;
5124 }
5125
5126 bool CVideoDatabase::GetPeopleNav(const CStdString& strBaseDir, CFileItemList& items, const CStdString &type, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
5127 {
5128   if (NULL == m_pDB.get()) return false;
5129   if (NULL == m_pDS.get()) return false;
5130
5131   try
5132   {
5133     // TODO: This routine (and probably others at this same level) use playcount as a reference to filter on at a later
5134     //       point.  This means that we *MUST* filter these levels as you'll get double ups.  Ideally we'd allow playcount
5135     //       to filter through as we normally do for tvshows to save this happening.
5136     //       Also, we apply this same filtering logic to the locked or unlocked paths to prevent these from showing.
5137     //       Whether or not this should happen is a tricky one - it complicates all the high level categories (everything
5138     //       above titles).
5139
5140     // General routine that the other actor/director/writer routines call
5141
5142     // get primary genres for movies
5143     CStdString strSQL;
5144     Filter extFilter = filter;
5145     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
5146     {
5147       if (idContent == VIDEODB_CONTENT_MOVIES)
5148       {
5149         strSQL = "select %s from actors ";
5150         extFilter.fields = "actors.idActor, actors.strActor, actors.strThumb, path.strPath, files.playCount";
5151         extFilter.AppendJoin(PrepareSQL("join %slinkmovie on actors.idActor = %slinkmovie.id%s join movieview on %slinkmovie.idMovie = movieview.idMovie join files on files.idFile = movieview.idFile join path on path.idPath = files.idPath",
5152                                         type.c_str(), type.c_str(), type.c_str(), type.c_str()));
5153       }
5154       else if (idContent == VIDEODB_CONTENT_TVSHOWS)
5155       {
5156         strSQL = "select %s from actors ";
5157         extFilter.fields = "actors.idActor, actors.strActor, actors.strThumb, path.strPath";
5158         extFilter.AppendJoin(PrepareSQL("join %slinktvshow on actors.idActor = %slinktvshow.id%s join episodeview on %slinktvshow.idShow = episodeview.idShow join files on files.idFile = episodeview.idFile join path on path.idPath = files.idPath",
5159                                         type.c_str(), type.c_str(), type.c_str(), type.c_str()));
5160       }
5161       else if (idContent == VIDEODB_CONTENT_EPISODES)
5162       {
5163         strSQL = "select %s from actors ";
5164         extFilter.fields = "actors.idActor, actors.strActor, actors.strThumb, path.strPath, files.playCount";
5165         extFilter.AppendJoin(PrepareSQL("join %slinkepisode on actors.idActor = %slinkepisode.id%s join episodeview on %slinkepisode.idEpisode = episodeview.idEpisode join files on files.idFile = episodeview.idFile join path on path.idPath = files.idPath",
5166                                         type.c_str(), type.c_str(), type.c_str(), type.c_str()));
5167       }
5168       else if (idContent == VIDEODB_CONTENT_MUSICVIDEOS)
5169       {
5170         strSQL = "select %s from actors ";
5171         extFilter.fields = "actors.idActor, actors.strActor, actors.strThumb, path.strPath, files.playCount";
5172         extFilter.AppendJoin(PrepareSQL("join %slinkmusicvideo on actors.idActor = %slinkmusicvideo.id%s join musicvideoview on %slinkmusicvideo.idMVideo = musicvideoview.idMVideo join files on files.idFile = musicvideoview.idFile join path on path.idPath = files.idPath",
5173                                         type.c_str(), type.c_str(), type.c_str(), type.c_str()));
5174       }
5175       else
5176         return false;
5177     }
5178     else
5179     {
5180       if (idContent == VIDEODB_CONTENT_MOVIES)
5181       {
5182         strSQL ="select %s from actors ";
5183         extFilter.fields = "actors.idActor, actors.strActor, actors.strThumb, count(1), count(files.playCount)";
5184         extFilter.AppendJoin(PrepareSQL("join %slinkmovie on actors.idActor = %slinkmovie.id%s join movieview on %slinkmovie.idMovie = movieview.idMovie join files on files.idFile = movieview.idFile",
5185                                         type.c_str(), type.c_str(), type.c_str(), type.c_str()));
5186         extFilter.AppendGroup("actors.idActor");
5187       }
5188       else if (idContent == VIDEODB_CONTENT_TVSHOWS)
5189       {
5190         strSQL = "select %s " + PrepareSQL("from actors, %slinktvshow, tvshowview ", type.c_str());
5191         extFilter.fields = "distinct actors.idActor, actors.strActor, actors.strThumb";
5192         extFilter.AppendWhere(PrepareSQL("actors.idActor = %slinktvshow.id%s and %slinktvshow.idShow = tvshowview.idShow",
5193                                          type.c_str(), type.c_str(), type.c_str()));
5194       }
5195       else if (idContent == VIDEODB_CONTENT_EPISODES)
5196       {
5197         strSQL = "select %s " + PrepareSQL("from %slinkepisode, actors, episodeview, files ", type.c_str());
5198         extFilter.fields = "actors.idActor, actors.strActor, actors.strThumb, count(1), count(files.playCount)";
5199         extFilter.AppendWhere(PrepareSQL("actors.idActor = %slinkepisode.id%s and %slinkepisode.idEpisode = episodeview.idEpisode and files.idFile = episodeview.idFile",
5200                                          type.c_str(), type.c_str(), type.c_str()));
5201         extFilter.AppendGroup("actors.idActor");
5202       }
5203       else if (idContent == VIDEODB_CONTENT_MUSICVIDEOS)
5204       {
5205         strSQL = "select %s from actors ";
5206         extFilter.fields = "actors.idActor, actors.strActor, actors.strThumb, count(1), count(files.playCount)";
5207         extFilter.AppendJoin(PrepareSQL("join %slinkmusicvideo on actors.idActor = %slinkmusicvideo.id%s join musicvideoview on %slinkmusicvideo.idMVideo = musicvideoview.idMVideo join files on files.idFile = musicvideoview.idFile",
5208                                         type.c_str(), type.c_str(), type.c_str(), type.c_str()));
5209         extFilter.AppendGroup("actors.idActor");
5210       }
5211       else
5212         return false;
5213     }
5214
5215     if (countOnly)
5216     {
5217       extFilter.fields = "COUNT(1)";
5218       extFilter.group.clear();
5219       extFilter.order.clear();
5220     }
5221     strSQL.Format(strSQL.c_str(), !extFilter.fields.empty() ? extFilter.fields.c_str() : "*");
5222
5223     CVideoDbUrl videoUrl;
5224     if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl))
5225       return false;
5226
5227     // run query
5228     unsigned int time = XbmcThreads::SystemClockMillis();
5229     if (!m_pDS->query(strSQL.c_str())) return false;
5230     CLog::Log(LOGDEBUG, "%s -  query took %i ms",
5231               __FUNCTION__, XbmcThreads::SystemClockMillis() - time); time = XbmcThreads::SystemClockMillis();
5232     int iRowsFound = m_pDS->num_rows();
5233     if (iRowsFound == 0)
5234     {
5235       m_pDS->close();
5236       return true;
5237     }
5238
5239     if (countOnly)
5240     {
5241       CFileItemPtr pItem(new CFileItem());
5242       pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound);
5243       items.Add(pItem);
5244
5245       m_pDS->close();
5246       return true;
5247     }
5248
5249     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
5250     {
5251       map<int, CActor> mapActors;
5252       map<int, CActor>::iterator it;
5253
5254       while (!m_pDS->eof())
5255       {
5256         int idActor = m_pDS->fv(0).get_asInt();
5257         CActor actor;
5258         actor.name = m_pDS->fv(1).get_asString();
5259         actor.thumb = m_pDS->fv(2).get_asString();
5260         if (idContent != VIDEODB_CONTENT_TVSHOWS)
5261           actor.playcount = m_pDS->fv(3).get_asInt();
5262         it = mapActors.find(idActor);
5263         // is this actor already known?
5264         if (it == mapActors.end())
5265         {
5266           // check path
5267           if (g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv("path.strPath").get_asString()),g_settings.m_videoSources))
5268             mapActors.insert(pair<int, CActor>(idActor, actor));
5269         }
5270         m_pDS->next();
5271       }
5272       m_pDS->close();
5273
5274       for (it=mapActors.begin();it != mapActors.end();++it)
5275       {
5276         CFileItemPtr pItem(new CFileItem(it->second.name));
5277
5278         CVideoDbUrl itemUrl = videoUrl;
5279         CStdString path; path.Format("%ld/", it->first);
5280         itemUrl.AppendPath(path);
5281         pItem->SetPath(itemUrl.ToString());
5282
5283         pItem->m_bIsFolder=true;
5284         pItem->GetVideoInfoTag()->m_playCount = it->second.playcount;
5285         pItem->GetVideoInfoTag()->m_strPictureURL.ParseString(it->second.thumb);
5286         pItem->GetVideoInfoTag()->m_iDbId = it->first;
5287         pItem->GetVideoInfoTag()->m_type = type;
5288         items.Add(pItem);
5289       }
5290     }
5291     else
5292     {
5293       while (!m_pDS->eof())
5294       {
5295         try
5296         {
5297           CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
5298
5299           CVideoDbUrl itemUrl = videoUrl;
5300           CStdString path; path.Format("%ld/", m_pDS->fv(0).get_asInt());
5301           itemUrl.AppendPath(path);
5302           pItem->SetPath(itemUrl.ToString());
5303
5304           pItem->m_bIsFolder=true;
5305           pItem->GetVideoInfoTag()->m_strPictureURL.ParseString(m_pDS->fv(2).get_asString());
5306           pItem->GetVideoInfoTag()->m_iDbId = m_pDS->fv(0).get_asInt();
5307           pItem->GetVideoInfoTag()->m_type = type;
5308           if (idContent != VIDEODB_CONTENT_TVSHOWS)
5309           {
5310             // fv(4) is the number of videos watched, fv(3) is the total number.  We set the playcount
5311             // only if the number of videos watched is equal to the total number (i.e. every video watched)
5312             pItem->GetVideoInfoTag()->m_playCount = (m_pDS->fv(4).get_asInt() == m_pDS->fv(3).get_asInt()) ? 1 : 0;
5313           }
5314           if (idContent == VIDEODB_CONTENT_MUSICVIDEOS)
5315             pItem->GetVideoInfoTag()->m_artist.push_back(pItem->GetLabel());
5316           items.Add(pItem);
5317           m_pDS->next();
5318         }
5319         catch (...)
5320         {
5321           m_pDS->close();
5322           CLog::Log(LOGERROR, "%s: out of memory - retrieved %i items", __FUNCTION__, items.Size());
5323           return items.Size() > 0;
5324         }
5325       }
5326       m_pDS->close();
5327     }
5328     CLog::Log(LOGDEBUG, "%s item retrieval took %i ms",
5329               __FUNCTION__, XbmcThreads::SystemClockMillis() - time); time = XbmcThreads::SystemClockMillis();
5330
5331     return true;
5332   }
5333   catch (...)
5334   {
5335     m_pDS->close();
5336     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
5337   }
5338   return false;
5339 }
5340
5341 bool CVideoDatabase::GetYearsNav(const CStdString& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */)
5342 {
5343   try
5344   {
5345     if (NULL == m_pDB.get()) return false;
5346     if (NULL == m_pDS.get()) return false;
5347
5348     CStdString strSQL;
5349     Filter extFilter = filter;
5350     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
5351     {
5352       if (idContent == VIDEODB_CONTENT_MOVIES)
5353       {
5354         strSQL = PrepareSQL("select movieview.c%02d, path.strPath, files.playCount from movieview ", VIDEODB_ID_YEAR);
5355         extFilter.AppendJoin("join files on files.idFile = movieview.idFile join path on files.idPath = path.idPath");
5356       }
5357       else if (idContent == VIDEODB_CONTENT_TVSHOWS)
5358       {
5359         strSQL = PrepareSQL("select tvshowview.c%02d, path.strPath from tvshowview ", VIDEODB_ID_TV_PREMIERED);
5360         extFilter.AppendJoin("join episodeview on episodeview.idShow = tvshowview.idShow join files on files.idFile = episodeview.idFile join path on files.idPath = path.idPath");
5361       }
5362       else if (idContent == VIDEODB_CONTENT_MUSICVIDEOS)
5363       {
5364         strSQL = PrepareSQL("select musicvideoview.c%02d, path.strPath, files.playCount from musicvideoview ", VIDEODB_ID_MUSICVIDEO_YEAR);
5365         extFilter.AppendJoin("join files on files.idFile = musicvideoview.idFile join path on files.idPath = path.idPath");
5366       }
5367       else
5368         return false;
5369     }
5370     else
5371     {
5372       CStdString group;
5373       if (idContent == VIDEODB_CONTENT_MOVIES)
5374       {
5375         strSQL = PrepareSQL("select movieview.c%02d, count(1), count(files.playCount) from movieview ", VIDEODB_ID_YEAR);
5376         extFilter.AppendJoin("join files on files.idFile = movieview.idFile");
5377         extFilter.AppendGroup(PrepareSQL("movieview.c%02d", VIDEODB_ID_YEAR));
5378       }
5379       else if (idContent == VIDEODB_CONTENT_TVSHOWS)
5380         strSQL = PrepareSQL("select distinct tvshowview.c%02d from tvshowview", VIDEODB_ID_TV_PREMIERED);
5381       else if (idContent == VIDEODB_CONTENT_MUSICVIDEOS)
5382       {
5383         strSQL = PrepareSQL("select musicvideoview.c%02d, count(1), count(files.playCount) from musicvideoview ", VIDEODB_ID_MUSICVIDEO_YEAR);
5384         extFilter.AppendJoin("join files on files.idFile = musicvideoview.idFile");
5385         extFilter.AppendGroup(PrepareSQL("musicvideoview.c%02d", VIDEODB_ID_MUSICVIDEO_YEAR));
5386       }
5387       else
5388         return false;
5389     }
5390
5391     CVideoDbUrl videoUrl;
5392     if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl))
5393       return false;
5394
5395     int iRowsFound = RunQuery(strSQL);
5396     if (iRowsFound <= 0)
5397       return iRowsFound == 0;
5398
5399     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
5400     {
5401       map<int, pair<CStdString,int> > mapYears;
5402       map<int, pair<CStdString,int> >::iterator it;
5403       while (!m_pDS->eof())
5404       {
5405         int lYear = 0;
5406         if (idContent == VIDEODB_CONTENT_TVSHOWS)
5407         {
5408           CDateTime time;
5409           time.SetFromDateString(m_pDS->fv(0).get_asString());
5410           lYear = time.GetYear();
5411         }
5412         else if (idContent == VIDEODB_CONTENT_MOVIES || idContent == VIDEODB_CONTENT_MUSICVIDEOS)
5413           lYear = m_pDS->fv(0).get_asInt();
5414         it = mapYears.find(lYear);
5415         if (it == mapYears.end())
5416         {
5417           // check path
5418           if (g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv("path.strPath").get_asString()),g_settings.m_videoSources))
5419           {
5420             CStdString year;
5421             year.Format("%d", lYear);
5422             if (idContent == VIDEODB_CONTENT_MOVIES || idContent == VIDEODB_CONTENT_MUSICVIDEOS)
5423               mapYears.insert(pair<int, pair<CStdString,int> >(lYear, pair<CStdString,int>(year,m_pDS->fv(2).get_asInt())));
5424             else
5425               mapYears.insert(pair<int, pair<CStdString,int> >(lYear, pair<CStdString,int>(year,0)));
5426           }
5427         }
5428         m_pDS->next();
5429       }
5430       m_pDS->close();
5431
5432       for (it=mapYears.begin();it != mapYears.end();++it)
5433       {
5434         if (it->first == 0)
5435           continue;
5436         CFileItemPtr pItem(new CFileItem(it->second.first));
5437
5438         CVideoDbUrl itemUrl = videoUrl;
5439         CStdString path; path.Format("%ld/", it->first);
5440         itemUrl.AppendPath(path);
5441         pItem->SetPath(itemUrl.ToString());
5442
5443         pItem->m_bIsFolder=true;
5444         if (idContent == VIDEODB_CONTENT_MOVIES || idContent == VIDEODB_CONTENT_MUSICVIDEOS)
5445           pItem->GetVideoInfoTag()->m_playCount = it->second.second;
5446         items.Add(pItem);
5447       }
5448     }
5449     else
5450     {
5451       while (!m_pDS->eof())
5452       {
5453         int lYear = 0;
5454         CStdString strLabel;
5455         if (idContent == VIDEODB_CONTENT_TVSHOWS)
5456         {
5457           CDateTime time;
5458           time.SetFromDateString(m_pDS->fv(0).get_asString());
5459           lYear = time.GetYear();
5460           strLabel.Format("%i",lYear);
5461         }
5462         else if (idContent == VIDEODB_CONTENT_MOVIES || idContent == VIDEODB_CONTENT_MUSICVIDEOS)
5463         {
5464           lYear = m_pDS->fv(0).get_asInt();
5465           strLabel = m_pDS->fv(0).get_asString();
5466         }
5467         if (lYear == 0)
5468         {
5469           m_pDS->next();
5470           continue;
5471         }
5472         CFileItemPtr pItem(new CFileItem(strLabel));
5473
5474         CVideoDbUrl itemUrl = videoUrl;
5475         CStdString path; path.Format("%ld/", lYear);
5476         itemUrl.AppendPath(path);
5477         pItem->SetPath(itemUrl.ToString());
5478
5479         pItem->m_bIsFolder=true;
5480         if (idContent == VIDEODB_CONTENT_MOVIES || idContent == VIDEODB_CONTENT_MUSICVIDEOS)
5481         {
5482           // fv(2) is the number of videos watched, fv(1) is the total number.  We set the playcount
5483           // only if the number of videos watched is equal to the total number (i.e. every video watched)
5484           pItem->GetVideoInfoTag()->m_playCount = (m_pDS->fv(2).get_asInt() == m_pDS->fv(1).get_asInt()) ? 1 : 0;
5485         }
5486
5487         // take care of dupes ..
5488         if (!items.Contains(pItem->GetPath()))
5489           items.Add(pItem);
5490
5491         m_pDS->next();
5492       }
5493       m_pDS->close();
5494     }
5495
5496     return true;
5497   }
5498   catch (...)
5499   {
5500     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
5501   }
5502   return false;
5503 }
5504
5505 bool CVideoDatabase::GetStackedTvShowList(int idShow, CStdString& strIn) const
5506 {
5507   try
5508   {
5509     if (NULL == m_pDB.get()) return false;
5510     if (NULL == m_pDS.get()) return false;
5511
5512     // look for duplicate show titles and stack them into a list
5513     if (idShow == -1)
5514       return false;
5515     CStdString strSQL = PrepareSQL("select idShow from tvshow where c00 like (select c00 from tvshow where idShow=%i) order by idShow", idShow);
5516     CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str());
5517     if (!m_pDS->query(strSQL.c_str())) return false;
5518     int iRows = m_pDS->num_rows();
5519     if (iRows == 0) return false; // this should never happen!
5520     if (iRows > 1)
5521     { // more than one show, so stack them up
5522       strIn = "IN (";
5523       while (!m_pDS->eof())
5524       {
5525         strIn += PrepareSQL("%i,", m_pDS->fv(0).get_asInt());
5526         m_pDS->next();
5527       }
5528       strIn[strIn.GetLength() - 1] = ')'; // replace last , with )
5529     }
5530     m_pDS->close();
5531     return true;
5532   }
5533   catch (...)
5534   {
5535     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
5536   }
5537   return false;
5538 }
5539
5540 bool CVideoDatabase::GetSeasonsNav(const CStdString& strBaseDir, CFileItemList& items, int idActor, int idDirector, int idGenre, int idYear, int idShow, bool getLinkedMovies /* = true */)
5541 {
5542   try
5543   {
5544     if (NULL == m_pDB.get()) return false;
5545     if (NULL == m_pDS.get()) return false;
5546
5547     // parse the base path to get additional filters
5548     CVideoDbUrl videoUrl;
5549     if (!videoUrl.FromString(strBaseDir))
5550       return false;
5551
5552     CStdString strIn = PrepareSQL("= %i", idShow);
5553     GetStackedTvShowList(idShow, strIn);
5554
5555     CStdString strSQL = PrepareSQL("SELECT episodeview.c%02d, "
5556                                           "path.strPath, "
5557                                           "tvshowview.c%02d, tvshowview.c%02d, tvshowview.c%02d, tvshowview.c%02d, tvshowview.c%02d, tvshowview.c%02d, "
5558                                           "seasons.idSeason, "
5559                                           "count(1), count(files.playCount) "
5560                                           "FROM episodeview ", VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_TV_TITLE, VIDEODB_ID_TV_PLOT, VIDEODB_ID_TV_PREMIERED, VIDEODB_ID_TV_GENRE, VIDEODB_ID_TV_STUDIOS, VIDEODB_ID_TV_MPAA);
5561     
5562     Filter filter;
5563     filter.join = PrepareSQL("JOIN tvshowview ON tvshowview.idShow = episodeview.idShow "
5564                              "JOIN seasons ON (seasons.idShow = tvshowview.idShow AND seasons.season = episodeview.c%02d) "
5565                              "JOIN files ON files.idFile = episodeview.idFile "
5566                              "JOIN tvshowlinkpath ON tvshowlinkpath.idShow = tvshowview.idShow "
5567                              "JOIN path ON path.idPath = tvshowlinkpath.idPath", VIDEODB_ID_EPISODE_SEASON);
5568     filter.where = PrepareSQL("tvshowview.idShow %s", strIn.c_str());
5569     filter.group = PrepareSQL("episodeview.c%02d", VIDEODB_ID_EPISODE_SEASON);
5570
5571     videoUrl.AddOption("tvshowid", idShow);
5572
5573     if (idActor != -1)
5574       videoUrl.AddOption("actorid", idActor);
5575     else if (idDirector != -1)
5576       videoUrl.AddOption("directorid", idDirector);
5577     else if (idGenre != -1)
5578       videoUrl.AddOption("genreid", idGenre);
5579     else if (idYear != -1)
5580       videoUrl.AddOption("year", idYear);
5581
5582     if (!BuildSQL(strBaseDir, strSQL, filter, strSQL, videoUrl))
5583       return false;
5584
5585     int iRowsFound = RunQuery(strSQL);
5586     if (iRowsFound <= 0)
5587       return iRowsFound == 0;
5588
5589     // show titles, plots, day of premiere, studios and mpaa ratings will be the same
5590     CStdString showTitle = m_pDS->fv(2).get_asString();
5591     CStdString showPlot = m_pDS->fv(3).get_asString();
5592     CStdString showPremiered = m_pDS->fv(4).get_asString();
5593     CStdString showStudio = m_pDS->fv(6).get_asString();
5594     CStdString showMPAARating = m_pDS->fv(7).get_asString();
5595
5596     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
5597     {
5598       map<int, CSeason> mapSeasons;
5599       map<int, CSeason>::iterator it;
5600       while (!m_pDS->eof())
5601       {
5602         int iSeason = m_pDS->fv(0).get_asInt();
5603         it = mapSeasons.find(iSeason);
5604         // check path
5605         if (!g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv("path.strPath").get_asString()),g_settings.m_videoSources))
5606         {
5607           m_pDS->next();
5608           continue;
5609         }
5610         if (it == mapSeasons.end())
5611         {
5612           CSeason season;
5613           season.path = m_pDS->fv(1).get_asString();
5614           season.genre = StringUtils::Split(m_pDS->fv(5).get_asString(), g_advancedSettings.m_videoItemSeparator);
5615           season.id = m_pDS->fv(8).get_asInt();
5616           season.numEpisodes = m_pDS->fv(9).get_asInt();
5617           season.numWatched = m_pDS->fv(10).get_asInt();
5618           mapSeasons.insert(make_pair(iSeason, season));
5619         }
5620         m_pDS->next();
5621       }
5622       m_pDS->close();
5623
5624       for (it=mapSeasons.begin();it != mapSeasons.end();++it)
5625       {
5626         int iSeason = it->first;
5627         CStdString strLabel;
5628         if (iSeason == 0)
5629           strLabel = g_localizeStrings.Get(20381);
5630         else
5631           strLabel.Format(g_localizeStrings.Get(20358),iSeason);
5632         CFileItemPtr pItem(new CFileItem(strLabel));
5633
5634         CVideoDbUrl itemUrl = videoUrl;
5635         CStdString strDir; strDir.Format("%ld/", it->first);
5636         itemUrl.AppendPath(strDir);
5637         pItem->SetPath(itemUrl.ToString());
5638
5639         pItem->m_bIsFolder=true;
5640         pItem->GetVideoInfoTag()->m_strTitle = strLabel;
5641         pItem->GetVideoInfoTag()->m_iSeason = iSeason;
5642         pItem->GetVideoInfoTag()->m_iDbId = it->second.id;
5643         pItem->GetVideoInfoTag()->m_type = "season";
5644         pItem->GetVideoInfoTag()->m_strPath = it->second.path;
5645         pItem->GetVideoInfoTag()->m_genre = it->second.genre;
5646         pItem->GetVideoInfoTag()->m_studio = StringUtils::Split(showStudio, g_advancedSettings.m_videoItemSeparator);
5647         pItem->GetVideoInfoTag()->m_strMPAARating = showMPAARating;
5648         pItem->GetVideoInfoTag()->m_iIdShow = idShow;
5649         pItem->GetVideoInfoTag()->m_strShowTitle = showTitle;
5650         pItem->GetVideoInfoTag()->m_strPlot = showPlot;
5651         pItem->GetVideoInfoTag()->m_premiered.SetFromDBDate(showPremiered);
5652         pItem->GetVideoInfoTag()->m_iEpisode = it->second.numEpisodes;
5653         pItem->SetProperty("totalepisodes", it->second.numEpisodes);
5654         pItem->SetProperty("numepisodes", it->second.numEpisodes); // will be changed later to reflect watchmode setting
5655         pItem->SetProperty("watchedepisodes", it->second.numWatched);
5656         pItem->SetProperty("unwatchedepisodes", it->second.numEpisodes - it->second.numWatched);
5657         if (iSeason == 0) pItem->SetProperty("isspecial", true);
5658         pItem->GetVideoInfoTag()->m_playCount = (it->second.numEpisodes == it->second.numWatched) ? 1 : 0;
5659         pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, (pItem->GetVideoInfoTag()->m_playCount > 0) && (pItem->GetVideoInfoTag()->m_iEpisode > 0));
5660         items.Add(pItem);
5661       }
5662     }
5663     else
5664     {
5665       while (!m_pDS->eof())
5666       {
5667         int iSeason = m_pDS->fv(0).get_asInt();
5668         CStdString strLabel;
5669         if (iSeason == 0)
5670           strLabel = g_localizeStrings.Get(20381);
5671         else
5672           strLabel.Format(g_localizeStrings.Get(20358),iSeason);
5673         CFileItemPtr pItem(new CFileItem(strLabel));
5674
5675         CVideoDbUrl itemUrl = videoUrl;
5676         CStdString strDir; strDir.Format("%ld/", iSeason);
5677         itemUrl.AppendPath(strDir);
5678         pItem->SetPath(itemUrl.ToString());
5679
5680         pItem->m_bIsFolder=true;
5681         pItem->GetVideoInfoTag()->m_strTitle = strLabel;
5682         pItem->GetVideoInfoTag()->m_iSeason = iSeason;
5683         pItem->GetVideoInfoTag()->m_iDbId = m_pDS->fv(8).get_asInt();
5684         pItem->GetVideoInfoTag()->m_type = "season";
5685         pItem->GetVideoInfoTag()->m_strPath = m_pDS->fv(1).get_asString();
5686         pItem->GetVideoInfoTag()->m_genre = StringUtils::Split(m_pDS->fv(5).get_asString(), g_advancedSettings.m_videoItemSeparator);
5687         pItem->GetVideoInfoTag()->m_studio = StringUtils::Split(showStudio, g_advancedSettings.m_videoItemSeparator);
5688         pItem->GetVideoInfoTag()->m_strMPAARating = showMPAARating;
5689         pItem->GetVideoInfoTag()->m_iIdShow = idShow;
5690         pItem->GetVideoInfoTag()->m_strShowTitle = showTitle;
5691         pItem->GetVideoInfoTag()->m_strPlot = showPlot;
5692         pItem->GetVideoInfoTag()->m_premiered.SetFromDBDate(showPremiered);
5693         int totalEpisodes = m_pDS->fv(9).get_asInt();
5694         int watchedEpisodes = m_pDS->fv(10).get_asInt();
5695         pItem->GetVideoInfoTag()->m_iEpisode = totalEpisodes;
5696         pItem->SetProperty("totalepisodes", totalEpisodes);
5697         pItem->SetProperty("numepisodes", totalEpisodes); // will be changed later to reflect watchmode setting
5698         pItem->SetProperty("watchedepisodes", watchedEpisodes);
5699         pItem->SetProperty("unwatchedepisodes", totalEpisodes - watchedEpisodes);
5700         if (iSeason == 0) pItem->SetProperty("isspecial", true);
5701         pItem->GetVideoInfoTag()->m_playCount = (totalEpisodes == watchedEpisodes) ? 1 : 0;
5702         pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, (pItem->GetVideoInfoTag()->m_playCount > 0) && (pItem->GetVideoInfoTag()->m_iEpisode > 0));
5703         items.Add(pItem);
5704         m_pDS->next();
5705       }
5706       m_pDS->close();
5707     }
5708
5709     // now add any linked movies
5710     if (getLinkedMovies)
5711     {
5712       Filter movieFilter;
5713       movieFilter.join  = PrepareSQL("join movielinktvshow on movielinktvshow.idMovie=movieview.idMovie");
5714       movieFilter.where = PrepareSQL("movielinktvshow.idShow %s", strIn.c_str());
5715       CFileItemList movieItems;
5716       GetMoviesByWhere("videodb://1/2/", movieFilter, movieItems);
5717
5718       if (movieItems.Size() > 0)
5719         items.Append(movieItems);
5720     }
5721
5722     return true;
5723   }
5724   catch (...)
5725   {
5726     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
5727   }
5728   return false;
5729 }
5730
5731 bool CVideoDatabase::GetSortedVideos(MediaType mediaType, const CStdString& strBaseDir, const SortDescription &sortDescription, CFileItemList& items, const Filter &filter /* = Filter() */)
5732 {
5733   if (NULL == m_pDB.get() || NULL == m_pDS.get())
5734     return false;
5735
5736   if (mediaType != MediaTypeMovie && mediaType != MediaTypeTvShow && mediaType != MediaTypeEpisode && mediaType != MediaTypeMusicVideo)
5737     return false;
5738
5739   SortDescription sorting = sortDescription;
5740   if (sortDescription.sortBy == SortByFile ||
5741       sortDescription.sortBy == SortByTitle ||
5742       sortDescription.sortBy == SortBySortTitle ||
5743       sortDescription.sortBy == SortByLabel ||
5744       sortDescription.sortBy == SortByDateAdded ||
5745       sortDescription.sortBy == SortByRating ||
5746       sortDescription.sortBy == SortByYear ||
5747       sortDescription.sortBy == SortByLastPlayed ||
5748       sortDescription.sortBy == SortByPlaycount)
5749     sorting.sortAttributes = (SortAttribute)(sortDescription.sortAttributes | SortAttributeIgnoreFolders);
5750
5751   bool success = false;
5752   switch (mediaType)
5753   {
5754   case MediaTypeMovie:
5755     success = GetMoviesByWhere(strBaseDir, filter, items, sorting);
5756     break;
5757       
5758   case MediaTypeTvShow:
5759     success = GetTvShowsByWhere(strBaseDir, filter, items, sorting);
5760     break;
5761       
5762   case MediaTypeEpisode:
5763     success = GetEpisodesByWhere(strBaseDir, filter, items, true, sorting);
5764     break;
5765       
5766   case MediaTypeMusicVideo:
5767     success = GetMusicVideosByWhere(strBaseDir, filter, items, true, sorting);
5768     break;
5769
5770   default:
5771     return false;
5772   }
5773
5774   items.SetContent(DatabaseUtils::MediaTypeToString(mediaType) + "s");
5775   return success;
5776 }
5777
5778 bool CVideoDatabase::GetMoviesNav(const CStdString& strBaseDir, CFileItemList& items,
5779                                   int idGenre /* = -1 */, int idYear /* = -1 */, int idActor /* = -1 */, int idDirector /* = -1 */,
5780                                   int idStudio /* = -1 */, int idCountry /* = -1 */, int idSet /* = -1 */, int idTag /* = -1 */,
5781                                   const SortDescription &sortDescription /* = SortDescription() */)
5782 {
5783   CVideoDbUrl videoUrl;
5784   if (!videoUrl.FromString(strBaseDir))
5785     return false;
5786
5787   if (idGenre > 0)
5788     videoUrl.AddOption("genreid", idGenre);
5789   else if (idCountry > 0)
5790     videoUrl.AddOption("countryid", idCountry);
5791   else if (idStudio > 0)
5792     videoUrl.AddOption("studioid", idStudio);
5793   else if (idDirector > 0)
5794     videoUrl.AddOption("directorid", idDirector);
5795   else if (idYear > 0)
5796     videoUrl.AddOption("year", idYear);
5797   else if (idActor > 0)
5798     videoUrl.AddOption("actorid", idActor);
5799   else if (idSet > 0)
5800     videoUrl.AddOption("setid", idSet);
5801   else if (idTag > 0)
5802     videoUrl.AddOption("tagid", idTag);
5803
5804   Filter filter;
5805   return GetMoviesByWhere(videoUrl.ToString(), filter, items, sortDescription);
5806 }
5807
5808 bool CVideoDatabase::GetMoviesByWhere(const CStdString& strBaseDir, const Filter &filter, CFileItemList& items, const SortDescription &sortDescription /* = SortDescription() */)
5809 {
5810   try
5811   {
5812     movieTime = 0;
5813     castTime = 0;
5814
5815     if (NULL == m_pDB.get()) return false;
5816     if (NULL == m_pDS.get()) return false;
5817
5818     // parse the base path to get additional filters
5819     CVideoDbUrl videoUrl;
5820     Filter extFilter = filter;
5821     SortDescription sorting = sortDescription;
5822     if (!videoUrl.FromString(strBaseDir) || !GetFilter(videoUrl, extFilter, sorting))
5823       return false;
5824
5825     int total = -1;
5826
5827     CStdString strSQL = "select %s from movieview ";
5828     CStdString strSQLExtra;
5829     if (!CDatabase::BuildSQL(strSQLExtra, extFilter, strSQLExtra))
5830       return false;
5831
5832     // Apply the limiting directly here if there's no special sorting but limiting
5833     if (extFilter.limit.empty() &&
5834         sorting.sortBy == SortByNone &&
5835        (sorting.limitStart > 0 || sorting.limitEnd > 0))
5836     {
5837       total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
5838       strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
5839     }
5840
5841     strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
5842
5843     int iRowsFound = RunQuery(strSQL);
5844     if (iRowsFound <= 0)
5845       return iRowsFound == 0;
5846
5847     // store the total value of items as a property
5848     if (total < iRowsFound)
5849       total = iRowsFound;
5850     items.SetProperty("total", total);
5851     
5852     DatabaseResults results;
5853     results.reserve(iRowsFound);
5854
5855     if (!SortUtils::SortFromDataset(sortDescription, MediaTypeMovie, m_pDS, results))
5856       return false;
5857
5858     // get data from returned rows
5859     items.Reserve(results.size());
5860     const query_data &data = m_pDS->get_result_set().records;
5861     for (DatabaseResults::const_iterator it = results.begin(); it != results.end(); it++)
5862     {
5863       unsigned int targetRow = (unsigned int)it->at(FieldRow).asInteger();
5864       const dbiplus::sql_record* const record = data.at(targetRow);
5865
5866       CVideoInfoTag movie = GetDetailsForMovie(record);
5867       if (g_settings.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE ||
5868           g_passwordManager.bMasterUser                                   ||
5869           g_passwordManager.IsDatabasePathUnlocked(movie.m_strPath, g_settings.m_videoSources))
5870       {
5871         CFileItemPtr pItem(new CFileItem(movie));
5872
5873         CVideoDbUrl itemUrl = videoUrl;
5874         CStdString path; path.Format("%ld", movie.m_iDbId);
5875         itemUrl.AppendPath(path);
5876         pItem->SetPath(itemUrl.ToString());
5877
5878         pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED,movie.m_playCount > 0);
5879         items.Add(pItem);
5880       }
5881     }
5882
5883     // cleanup
5884     m_pDS->close();
5885     return true;
5886   }
5887   catch (...)
5888   {
5889     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
5890   }
5891   return false;
5892 }
5893
5894 bool CVideoDatabase::GetTvShowsNav(const CStdString& strBaseDir, CFileItemList& items,
5895                                   int idGenre /* = -1 */, int idYear /* = -1 */, int idActor /* = -1 */, int idDirector /* = -1 */, int idStudio /* = -1 */, int idTag /* = -1 */,
5896                                   const SortDescription &sortDescription /* = SortDescription() */)
5897 {
5898   CVideoDbUrl videoUrl;
5899   if (!videoUrl.FromString(strBaseDir))
5900     return false;
5901
5902   if (idGenre != -1)
5903     videoUrl.AddOption("genreid", idGenre);
5904   else if (idStudio != -1)
5905     videoUrl.AddOption("studioid", idStudio);
5906   else if (idDirector != -1)
5907     videoUrl.AddOption("directorid", idDirector);
5908   else if (idYear != -1)
5909     videoUrl.AddOption("year", idYear);
5910   else if (idActor != -1)
5911     videoUrl.AddOption("actorid", idActor);
5912   else if (idTag != -1)
5913     videoUrl.AddOption("tagid", idTag);
5914
5915   Filter filter;
5916   return GetTvShowsByWhere(videoUrl.ToString(), filter, items, sortDescription);
5917 }
5918
5919 bool CVideoDatabase::GetTvShowsByWhere(const CStdString& strBaseDir, const Filter &filter, CFileItemList& items, const SortDescription &sortDescription /* = SortDescription() */)
5920 {
5921   try
5922   {
5923     movieTime = 0;
5924
5925     if (NULL == m_pDB.get()) return false;
5926     if (NULL == m_pDS.get()) return false;
5927
5928     int total = -1;
5929     
5930     CStdString strSQL = "SELECT %s FROM tvshowview ";
5931     CVideoDbUrl videoUrl;
5932     CStdString strSQLExtra;
5933     Filter extFilter = filter;
5934     SortDescription sorting = sortDescription;
5935     if (!BuildSQL(strBaseDir, strSQLExtra, extFilter, strSQLExtra, videoUrl, sorting))
5936       return false;
5937
5938     // Apply the limiting directly here if there's no special sorting but limiting
5939       if (extFilter.limit.empty() &&
5940         sorting.sortBy == SortByNone &&
5941         (sorting.limitStart > 0 || sorting.limitEnd > 0))
5942     {
5943       total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
5944       strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
5945     }
5946
5947     strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
5948
5949     int iRowsFound = RunQuery(strSQL);
5950     if (iRowsFound <= 0)
5951       return iRowsFound == 0;
5952
5953     // store the total value of items as a property
5954     if (total < iRowsFound)
5955       total = iRowsFound;
5956     items.SetProperty("total", total);
5957     
5958     DatabaseResults results;
5959     results.reserve(iRowsFound);
5960     if (!SortUtils::SortFromDataset(sorting, MediaTypeTvShow, m_pDS, results))
5961       return false;
5962
5963     // get data from returned rows
5964     items.Reserve(results.size());
5965     const query_data &data = m_pDS->get_result_set().records;
5966     for (DatabaseResults::const_iterator it = results.begin(); it != results.end(); it++)
5967     {
5968       unsigned int targetRow = (unsigned int)it->at(FieldRow).asInteger();
5969       const dbiplus::sql_record* const record = data.at(targetRow);
5970       
5971       CVideoInfoTag movie = GetDetailsForTvShow(record, false);
5972       if ((g_settings.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE ||
5973            g_passwordManager.bMasterUser                                     ||
5974            g_passwordManager.IsDatabasePathUnlocked(movie.m_strPath, g_settings.m_videoSources)) &&
5975           (!g_advancedSettings.m_bVideoLibraryHideEmptySeries || movie.m_iEpisode > 0))
5976       {
5977         CFileItemPtr pItem(new CFileItem(movie));
5978
5979         CVideoDbUrl itemUrl = videoUrl;
5980         CStdString path; path.Format("%ld/", record->at(0).get_asInt());
5981         itemUrl.AppendPath(path);
5982         pItem->SetPath(itemUrl.ToString());
5983
5984         pItem->m_dateTime = movie.m_premiered;
5985         pItem->GetVideoInfoTag()->m_iYear = pItem->m_dateTime.GetYear();
5986         pItem->SetProperty("totalseasons", record->at(VIDEODB_DETAILS_TVSHOW_NUM_SEASONS).get_asInt());
5987         pItem->SetProperty("totalepisodes", movie.m_iEpisode);
5988         pItem->SetProperty("numepisodes", movie.m_iEpisode); // will be changed later to reflect watchmode setting
5989         pItem->SetProperty("watchedepisodes", movie.m_playCount);
5990         pItem->SetProperty("unwatchedepisodes", movie.m_iEpisode - movie.m_playCount);
5991         pItem->GetVideoInfoTag()->m_playCount = (movie.m_iEpisode == movie.m_playCount) ? 1 : 0;
5992         pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, (pItem->GetVideoInfoTag()->m_playCount > 0) && (pItem->GetVideoInfoTag()->m_iEpisode > 0));
5993         items.Add(pItem);
5994       }
5995     }
5996
5997     Stack(items, VIDEODB_CONTENT_TVSHOWS, !filter.order.empty() || sorting.sortBy != SortByNone);
5998
5999     // cleanup
6000     m_pDS->close();
6001     return true;
6002   }
6003   catch (...)
6004   {
6005     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
6006   }
6007   return false;
6008 }
6009
6010 void CVideoDatabase::Stack(CFileItemList& items, VIDEODB_CONTENT_TYPE type, bool maintainSortOrder /* = false */)
6011 {
6012   if (maintainSortOrder)
6013   {
6014     // save current sort order
6015     for (int i = 0; i < items.Size(); i++)
6016       items[i]->m_iprogramCount = i;
6017   }
6018
6019   switch (type)
6020   {
6021     case VIDEODB_CONTENT_TVSHOWS:
6022     {
6023       // sort by Title
6024       items.Sort(SORT_METHOD_VIDEO_TITLE, SortOrderAscending);
6025
6026       int i = 0;
6027       while (i < items.Size())
6028       {
6029         CFileItemPtr pItem = items.Get(i);
6030         CStdString strTitle = pItem->GetVideoInfoTag()->m_strTitle;
6031         CStdString strFanArt = pItem->GetArt("fanart");
6032
6033         int j = i + 1;
6034         bool bStacked = false;
6035         while (j < items.Size())
6036         {
6037           CFileItemPtr jItem = items.Get(j);
6038
6039           // matching title? append information
6040           if (jItem->GetVideoInfoTag()->m_strTitle.Equals(strTitle))
6041           {
6042             if (jItem->GetVideoInfoTag()->m_premiered != 
6043                 pItem->GetVideoInfoTag()->m_premiered)
6044             {
6045               j++;
6046               continue;
6047             }
6048             bStacked = true;
6049
6050             // increment episode counts
6051             pItem->GetVideoInfoTag()->m_iEpisode += jItem->GetVideoInfoTag()->m_iEpisode;
6052             pItem->IncrementProperty("totalepisodes", (int)jItem->GetProperty("totalepisodes").asInteger());
6053             pItem->IncrementProperty("numepisodes", (int)jItem->GetProperty("numepisodes").asInteger()); // will be changed later to reflect watchmode setting
6054             pItem->IncrementProperty("watchedepisodes", (int)jItem->GetProperty("watchedepisodes").asInteger());
6055             pItem->IncrementProperty("unwatchedepisodes", (int)jItem->GetProperty("unwatchedepisodes").asInteger());
6056
6057             // adjust lastplayed
6058             if (jItem->GetVideoInfoTag()->m_lastPlayed > pItem->GetVideoInfoTag()->m_lastPlayed)
6059               pItem->GetVideoInfoTag()->m_lastPlayed = jItem->GetVideoInfoTag()->m_lastPlayed;
6060
6061             // check for fanart if not already set
6062             if (strFanArt.IsEmpty())
6063               strFanArt = jItem->GetArt("fanart");
6064
6065             // remove duplicate entry
6066             items.Remove(j);
6067           }
6068           // no match? exit loop
6069           else
6070             break;
6071         }
6072         // update playcount and fanart
6073         if (bStacked)
6074         {
6075           pItem->GetVideoInfoTag()->m_playCount = (pItem->GetVideoInfoTag()->m_iEpisode == (int)pItem->GetProperty("watchedepisodes").asInteger()) ? 1 : 0;
6076           pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, (pItem->GetVideoInfoTag()->m_playCount > 0) && (pItem->GetVideoInfoTag()->m_iEpisode > 0));
6077           if (!strFanArt.IsEmpty())
6078             pItem->SetArt("fanart", strFanArt);
6079         }
6080         // increment i to j which is the next item
6081         i = j;
6082       }
6083     }
6084     break;
6085     // We currently don't stack episodes (No call here in GetEpisodesByWhere()), but this code is left
6086     // so that if we eventually want to stack during scan we can utilize it.
6087     /*
6088     case VIDEODB_CONTENT_EPISODES:
6089     {
6090       // sort by ShowTitle, Episode, Filename
6091       items.Sort(SORT_METHOD_EPISODE, SortOrderAscending);
6092
6093       int i = 0;
6094       while (i < items.Size())
6095       {
6096         CFileItemPtr pItem = items.Get(i);
6097         CStdString strPath = pItem->GetVideoInfoTag()->m_strPath;
6098         int iSeason = pItem->GetVideoInfoTag()->m_iSeason;
6099         int iEpisode = pItem->GetVideoInfoTag()->m_iEpisode;
6100         //CStdString strFanArt = pItem->GetArt("fanart");
6101
6102         // do we have a dvd folder, ie foo/VIDEO_TS.IFO or foo/VIDEO_TS/VIDEO_TS.IFO
6103         CStdString strFileNameAndPath = pItem->GetVideoInfoTag()->m_strFileNameAndPath;
6104         bool bDvdFolder = strFileNameAndPath.Right(12).Equals("VIDEO_TS.IFO");
6105
6106         vector<CStdString> paths;
6107         paths.push_back(strFileNameAndPath);
6108         CLog::Log(LOGDEBUG, "Stack episode (%i,%i):[%s]", iSeason, iEpisode, paths[0].c_str());
6109
6110         int j = i + 1;
6111         int iPlayCount = pItem->GetVideoInfoTag()->m_playCount;
6112         while (j < items.Size())
6113         {
6114           CFileItemPtr jItem = items.Get(j);
6115           const CVideoInfoTag *jTag = jItem->GetVideoInfoTag();
6116           CStdString jFileNameAndPath = jTag->m_strFileNameAndPath;
6117
6118           CLog::Log(LOGDEBUG, " *testing (%i,%i):[%s]", jTag->m_iSeason, jTag->m_iEpisode, jFileNameAndPath.c_str());
6119           // compare path, season, episode
6120           if (
6121             jTag &&
6122             jTag->m_strPath.Equals(strPath) &&
6123             jTag->m_iSeason == iSeason &&
6124             jTag->m_iEpisode == iEpisode
6125             )
6126           {
6127             // keep checking to see if this is dvd folder
6128             if (!bDvdFolder)
6129             {
6130               bDvdFolder = jFileNameAndPath.Right(12).Equals("VIDEO_TS.IFO");
6131               // if we have a dvd folder, we stack differently
6132               if (bDvdFolder)
6133               {
6134                 // remove all the other items and ONLY show the VIDEO_TS.IFO file
6135                 paths.empty();
6136                 paths.push_back(jFileNameAndPath);
6137               }
6138               else
6139               {
6140                 // increment playcount
6141                 iPlayCount += jTag->m_playCount;
6142
6143                 // episodes dont have fanart yet
6144                 //if (strFanArt.IsEmpty())
6145                 //  strFanArt = jItem->GetArt("fanart");
6146
6147                 paths.push_back(jFileNameAndPath);
6148               }
6149             }
6150
6151             // remove duplicate entry
6152             jTag = NULL;
6153             items.Remove(j);
6154           }
6155           // no match? exit loop
6156           else
6157             break;
6158         }
6159         // update playcount and fanart if we have a stacked entry
6160         if (paths.size() > 1)
6161         {
6162           CStackDirectory dir;
6163           CStdString strStack;
6164           dir.ConstructStackPath(paths, strStack);
6165           pItem->GetVideoInfoTag()->m_strFileNameAndPath = strStack;
6166           pItem->GetVideoInfoTag()->m_playCount = iPlayCount;
6167           pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, (pItem->GetVideoInfoTag()->m_playCount > 0) && (pItem->GetVideoInfoTag()->m_iEpisode > 0));
6168
6169           // episodes dont have fanart yet
6170           //if (!strFanArt.IsEmpty())
6171           //  pItem->SetArt("fanart", strFanArt);
6172         }
6173         // increment i to j which is the next item
6174         i = j;
6175       }
6176     }
6177     break;*/
6178
6179     // stack other types later
6180     default:
6181       break;
6182   }
6183   if (maintainSortOrder)
6184   {
6185     // restore original sort order - essential for smartplaylists
6186     items.Sort(SORT_METHOD_PROGRAM_COUNT, SortOrderAscending);
6187   }
6188 }
6189
6190 bool CVideoDatabase::GetEpisodesNav(const CStdString& strBaseDir, CFileItemList& items, int idGenre, int idYear, int idActor, int idDirector, int idShow, int idSeason, const SortDescription &sortDescription /* = SortDescription() */)
6191 {
6192   CVideoDbUrl videoUrl;
6193   if (!videoUrl.FromString(strBaseDir))
6194     return false;
6195
6196   CStdString strIn;
6197   if (idShow != -1)
6198   {
6199     strIn = PrepareSQL("= %i", idShow);
6200     GetStackedTvShowList(idShow, strIn);
6201
6202     videoUrl.AddOption("tvshowid", idShow);
6203     if (idSeason >= 0)
6204       videoUrl.AddOption("season", idSeason);
6205
6206     if (idGenre != -1)
6207       videoUrl.AddOption("genreid", idGenre);
6208     else if (idYear !=-1)
6209       videoUrl.AddOption("year", idYear);
6210     else if (idActor != -1)
6211       videoUrl.AddOption("actorid", idActor);
6212   }
6213   else if (idYear != -1)
6214     videoUrl.AddOption("year", idYear);
6215   
6216   if (idDirector != -1)
6217     videoUrl.AddOption("directorid", idDirector);
6218
6219   Filter filter;
6220   bool ret = GetEpisodesByWhere(videoUrl.ToString(), filter, items, false, sortDescription);
6221
6222   if (idSeason == -1 && idShow != -1)
6223   { // add any linked movies
6224     Filter movieFilter;
6225     movieFilter.join  = PrepareSQL("join movielinktvshow on movielinktvshow.idMovie=movieview.idMovie");
6226     movieFilter.where = PrepareSQL("movielinktvshow.idShow %s", strIn.c_str());
6227     CFileItemList movieItems;
6228     GetMoviesByWhere("videodb://1/2/", movieFilter, movieItems);
6229
6230     if (movieItems.Size() > 0)
6231       items.Append(movieItems);
6232   }
6233
6234   return ret;
6235 }
6236
6237 bool CVideoDatabase::GetEpisodesByWhere(const CStdString& strBaseDir, const Filter &filter, CFileItemList& items, bool appendFullShowPath /* = true */, const SortDescription &sortDescription /* = SortDescription() */)
6238 {
6239   try
6240   {
6241     movieTime = 0;
6242     castTime = 0;
6243
6244     if (NULL == m_pDB.get()) return false;
6245     if (NULL == m_pDS.get()) return false;
6246
6247     int total = -1;
6248     
6249     CStdString strSQL = "select %s from episodeview ";
6250     CVideoDbUrl videoUrl;
6251     CStdString strSQLExtra;
6252     Filter extFilter = filter;
6253     SortDescription sorting = sortDescription;
6254     if (!BuildSQL(strBaseDir, strSQLExtra, extFilter, strSQLExtra, videoUrl, sorting))
6255       return false;
6256
6257     // Apply the limiting directly here if there's no special sorting but limiting
6258     if (extFilter.limit.empty() &&
6259       sorting.sortBy == SortByNone &&
6260       (sorting.limitStart > 0 || sorting.limitEnd > 0))
6261     {
6262       total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
6263       strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
6264     }
6265
6266     strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
6267
6268     int iRowsFound = RunQuery(strSQL);
6269     if (iRowsFound <= 0)
6270       return iRowsFound == 0;
6271
6272     // store the total value of items as a property
6273     if (total < iRowsFound)
6274       total = iRowsFound;
6275     items.SetProperty("total", total);
6276     
6277     DatabaseResults results;
6278     results.reserve(iRowsFound);
6279     if (!SortUtils::SortFromDataset(sorting, MediaTypeEpisode, m_pDS, results))
6280       return false;
6281     
6282     // get data from returned rows
6283     items.Reserve(results.size());
6284     CLabelFormatter formatter("%H. %T", "");
6285
6286     const query_data &data = m_pDS->get_result_set().records;
6287     for (DatabaseResults::const_iterator it = results.begin(); it != results.end(); it++)
6288     {
6289       unsigned int targetRow = (unsigned int)it->at(FieldRow).asInteger();
6290       const dbiplus::sql_record* const record = data.at(targetRow);
6291
6292       CVideoInfoTag movie = GetDetailsForEpisode(record);
6293       if (g_settings.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE ||
6294           g_passwordManager.bMasterUser                                     ||
6295           g_passwordManager.IsDatabasePathUnlocked(movie.m_strPath, g_settings.m_videoSources))
6296       {
6297         CFileItemPtr pItem(new CFileItem(movie));
6298         formatter.FormatLabel(pItem.get());
6299       
6300         int idEpisode = record->at(0).get_asInt();
6301
6302         CVideoDbUrl itemUrl = videoUrl;
6303         CStdString path;
6304         if (appendFullShowPath && videoUrl.GetItemType() != "episodes")
6305           path.Format("%ld/%ld/%ld", record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_ID).get_asInt(), movie.m_iSeason, idEpisode);
6306         else
6307           path.Format("%ld", idEpisode);
6308         itemUrl.AppendPath(path);
6309         pItem->SetPath(itemUrl.ToString());
6310
6311         pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, movie.m_playCount > 0);
6312         pItem->m_dateTime = movie.m_firstAired;
6313         pItem->GetVideoInfoTag()->m_iYear = pItem->m_dateTime.GetYear();
6314         items.Add(pItem);
6315       }
6316     }
6317
6318     // cleanup
6319     m_pDS->close();
6320     return true;
6321   }
6322   catch (...)
6323   {
6324     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
6325   }
6326   return false;
6327 }
6328
6329 bool CVideoDatabase::GetMusicVideosNav(const CStdString& strBaseDir, CFileItemList& items, int idGenre, int idYear, int idArtist, int idDirector, int idStudio, int idAlbum, int idTag /* = -1 */, const SortDescription &sortDescription /* = SortDescription() */)
6330 {
6331   CVideoDbUrl videoUrl;
6332   if (!videoUrl.FromString(strBaseDir))
6333     return false;
6334
6335   if (idGenre != -1)
6336     videoUrl.AddOption("genreid", idGenre);
6337   else if (idStudio != -1)
6338     videoUrl.AddOption("studioid", idStudio);
6339   else if (idDirector != -1)
6340     videoUrl.AddOption("directorid", idDirector);
6341   else if (idYear !=-1)
6342     videoUrl.AddOption("year", idYear);
6343   else if (idArtist != -1)
6344     videoUrl.AddOption("artistid", idArtist);
6345   else if (idTag != -1)
6346     videoUrl.AddOption("tagid", idTag);
6347   if (idAlbum != -1)
6348     videoUrl.AddOption("albumid", idAlbum);
6349
6350   Filter filter;
6351   return GetMusicVideosByWhere(videoUrl.ToString(), filter, items, true, sortDescription);
6352 }
6353
6354 bool CVideoDatabase::GetRecentlyAddedMoviesNav(const CStdString& strBaseDir, CFileItemList& items, unsigned int limit)
6355 {
6356   Filter filter;
6357   filter.order = "dateAdded desc, idMovie desc";
6358   filter.limit = PrepareSQL("%u", limit ? limit : g_advancedSettings.m_iVideoLibraryRecentlyAddedItems);
6359   return GetMoviesByWhere(strBaseDir, filter, items);
6360 }
6361
6362 bool CVideoDatabase::GetRecentlyAddedEpisodesNav(const CStdString& strBaseDir, CFileItemList& items, unsigned int limit)
6363 {
6364   Filter filter;
6365   filter.order = "dateAdded desc, idEpisode desc";
6366   filter.limit = PrepareSQL("%u", limit ? limit : g_advancedSettings.m_iVideoLibraryRecentlyAddedItems);
6367   return GetEpisodesByWhere(strBaseDir, filter, items, false);
6368 }
6369
6370 bool CVideoDatabase::GetRecentlyAddedMusicVideosNav(const CStdString& strBaseDir, CFileItemList& items, unsigned int limit)
6371 {
6372   Filter filter;
6373   filter.order = "dateAdded desc, idMVideo desc";
6374   filter.limit = PrepareSQL("%u", limit ? limit : g_advancedSettings.m_iVideoLibraryRecentlyAddedItems);
6375   return GetMusicVideosByWhere(strBaseDir, filter, items);
6376 }
6377
6378 CStdString CVideoDatabase::GetGenreById(int id)
6379 {
6380   return GetSingleValue("genre", "strGenre", PrepareSQL("idGenre=%i", id));
6381 }
6382
6383 CStdString CVideoDatabase::GetCountryById(int id)
6384 {
6385   return GetSingleValue("country", "strCountry", PrepareSQL("idCountry=%i", id));
6386 }
6387
6388 CStdString CVideoDatabase::GetSetById(int id)
6389 {
6390   return GetSingleValue("sets", "strSet", PrepareSQL("idSet=%i", id));
6391 }
6392
6393 CStdString CVideoDatabase::GetTagById(int id)
6394 {
6395   return GetSingleValue("tag", "strTag", PrepareSQL("idTag = %i", id));
6396 }
6397
6398 CStdString CVideoDatabase::GetPersonById(int id)
6399 {
6400   return GetSingleValue("actors", "strActor", PrepareSQL("idActor=%i", id));
6401 }
6402
6403 CStdString CVideoDatabase::GetStudioById(int id)
6404 {
6405   return GetSingleValue("studio", "strStudio", PrepareSQL("idStudio=%i", id));
6406 }
6407
6408 CStdString CVideoDatabase::GetTvShowTitleById(int id)
6409 {
6410   return GetSingleValue("tvshow", PrepareSQL("c%02d", VIDEODB_ID_TV_TITLE), PrepareSQL("idShow=%i", id));
6411 }
6412
6413 CStdString CVideoDatabase::GetMusicVideoAlbumById(int id)
6414 {
6415   return GetSingleValue("musicvideo", PrepareSQL("c%02d", VIDEODB_ID_MUSICVIDEO_ALBUM), PrepareSQL("idMVideo=%i", id));
6416 }
6417
6418 bool CVideoDatabase::HasSets() const
6419 {
6420   try
6421   {
6422     if (NULL == m_pDB.get()) return false;
6423     if (NULL == m_pDS.get()) return false;
6424
6425     m_pDS->query("SELECT movieview.idSet,COUNT(1) AS c FROM movieview "
6426                  "JOIN sets ON sets.idSet = movieview.idSet "
6427                  "GROUP BY movieview.idSet HAVING c>1");
6428
6429     bool bResult = (m_pDS->num_rows() > 0);
6430     m_pDS->close();
6431     return bResult;
6432   }
6433   catch (...)
6434   {
6435     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
6436   }
6437   return false;
6438 }
6439
6440 int CVideoDatabase::GetTvShowForEpisode(int idEpisode)
6441 {
6442   try
6443   {
6444     if (NULL == m_pDB.get()) return false;
6445     if (NULL == m_pDS2.get()) return false;
6446
6447     // make sure we use m_pDS2, as this is called in loops using m_pDS
6448     CStdString strSQL=PrepareSQL("select idShow from episode where idEpisode=%i", idEpisode);
6449     m_pDS2->query( strSQL.c_str() );
6450
6451     int result=-1;
6452     if (!m_pDS2->eof())
6453       result=m_pDS2->fv(0).get_asInt();
6454     m_pDS2->close();
6455
6456     return result;
6457   }
6458   catch (...)
6459   {
6460     CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, idEpisode);
6461   }
6462   return false;
6463 }
6464
6465 int CVideoDatabase::GetSeasonForEpisode(int idEpisode)
6466 {
6467   char column[5];
6468   sprintf(column, "c%0d", VIDEODB_ID_EPISODE_SEASON);
6469   CStdString id = GetSingleValue("episode", column, PrepareSQL("idEpisode=%i", idEpisode));
6470   if (id.IsEmpty())
6471     return -1;
6472   return atoi(id.c_str());
6473 }
6474
6475 bool CVideoDatabase::HasContent()
6476 {
6477   return (HasContent(VIDEODB_CONTENT_MOVIES) ||
6478           HasContent(VIDEODB_CONTENT_TVSHOWS) ||
6479           HasContent(VIDEODB_CONTENT_MUSICVIDEOS));
6480 }
6481
6482 bool CVideoDatabase::HasContent(VIDEODB_CONTENT_TYPE type)
6483 {
6484   bool result = false;
6485   try
6486   {
6487     if (NULL == m_pDB.get()) return false;
6488     if (NULL == m_pDS.get()) return false;
6489
6490     CStdString sql;
6491     if (type == VIDEODB_CONTENT_MOVIES)
6492       sql = "select count(1) from movie";
6493     else if (type == VIDEODB_CONTENT_TVSHOWS)
6494       sql = "select count(1) from tvshow";
6495     else if (type == VIDEODB_CONTENT_MUSICVIDEOS)
6496       sql = "select count(1) from musicvideo";
6497     m_pDS->query( sql.c_str() );
6498
6499     if (!m_pDS->eof())
6500       result = (m_pDS->fv(0).get_asInt() > 0);
6501
6502     m_pDS->close();
6503   }
6504   catch (...)
6505   {
6506     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
6507   }
6508   return result;
6509 }
6510
6511 int CVideoDatabase::GetMusicVideoCount(const CStdString& strWhere)
6512 {
6513   try
6514   {
6515     if (NULL == m_pDB.get()) return 0;
6516     if (NULL == m_pDS.get()) return 0;
6517
6518     CStdString strSQL;
6519     strSQL.Format("select count(1) as nummovies from musicvideoview where %s",strWhere.c_str());
6520     m_pDS->query( strSQL.c_str() );
6521
6522     int iResult = 0;
6523     if (!m_pDS->eof())
6524       iResult = m_pDS->fv("nummovies").get_asInt();
6525
6526     m_pDS->close();
6527     return iResult;
6528   }
6529   catch (...)
6530   {
6531     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
6532   }
6533   return 0;
6534 }
6535
6536 ScraperPtr CVideoDatabase::GetScraperForPath( const CStdString& strPath )
6537 {
6538   SScanSettings settings;
6539   return GetScraperForPath(strPath, settings);
6540 }
6541
6542 ScraperPtr CVideoDatabase::GetScraperForPath(const CStdString& strPath, SScanSettings& settings)
6543 {
6544   bool dummy;
6545   return GetScraperForPath(strPath, settings, dummy);
6546 }
6547
6548 ScraperPtr CVideoDatabase::GetScraperForPath(const CStdString& strPath, SScanSettings& settings, bool& foundDirectly)
6549 {
6550   foundDirectly = false;
6551   try
6552   {
6553     if (strPath.IsEmpty() || !m_pDB.get() || !m_pDS.get()) return ScraperPtr();
6554
6555     ScraperPtr scraper;
6556     CStdString strPath1;
6557     CStdString strPath2(strPath);
6558
6559     if (URIUtils::IsMultiPath(strPath))
6560       strPath2 = CMultiPathDirectory::GetFirstPath(strPath);
6561
6562     URIUtils::GetDirectory(strPath2,strPath1);
6563     int idPath = GetPathId(strPath1);
6564
6565     if (idPath > -1)
6566     {
6567       CStdString strSQL=PrepareSQL("select path.strContent,path.strScraper,path.scanRecursive,path.useFolderNames,path.strSettings,path.noUpdate,path.exclude from path where path.idPath=%i",idPath);
6568       m_pDS->query( strSQL.c_str() );
6569     }
6570
6571     int iFound = 1;
6572     CONTENT_TYPE content = CONTENT_NONE;
6573     if (!m_pDS->eof())
6574     { // path is stored in db
6575
6576       if (m_pDS->fv("path.exclude").get_asBool())
6577       {
6578         settings.exclude = true;
6579         m_pDS->close();
6580         return ScraperPtr();
6581       }
6582       settings.exclude = false;
6583
6584       // try and ascertain scraper for this path
6585       CStdString strcontent = m_pDS->fv("path.strContent").get_asString();
6586       strcontent.ToLower();
6587       content = TranslateContent(strcontent);
6588
6589       //FIXME paths stored should not have empty strContent
6590       //assert(content != CONTENT_NONE);
6591       CStdString scraperID = m_pDS->fv("path.strScraper").get_asString();
6592
6593       AddonPtr addon;
6594       if (!scraperID.empty() &&
6595         CAddonMgr::Get().GetAddon(scraperID, addon))
6596       {
6597         scraper = boost::dynamic_pointer_cast<CScraper>(addon->Clone(addon));
6598         if (!scraper)
6599           return ScraperPtr();
6600
6601         // store this path's content & settings
6602         scraper->SetPathSettings(content, m_pDS->fv("path.strSettings").get_asString());
6603         settings.parent_name = m_pDS->fv("path.useFolderNames").get_asBool();
6604         settings.recurse = m_pDS->fv("path.scanRecursive").get_asInt();
6605         settings.noupdate = m_pDS->fv("path.noUpdate").get_asBool();
6606       }
6607     }
6608
6609     if (content == CONTENT_NONE)
6610     { // this path is not saved in db
6611       // we must drill up until a scraper is configured
6612       CStdString strParent;
6613       while (URIUtils::GetParentPath(strPath1, strParent))
6614       {
6615         iFound++;
6616
6617         CStdString strSQL=PrepareSQL("select path.strContent,path.strScraper,path.scanRecursive,path.useFolderNames,path.strSettings,path.noUpdate, path.exclude from path where strPath='%s'",strParent.c_str());
6618         m_pDS->query(strSQL.c_str());
6619
6620         CONTENT_TYPE content = CONTENT_NONE;
6621         if (!m_pDS->eof())
6622         {
6623
6624           CStdString strcontent = m_pDS->fv("path.strContent").get_asString();
6625           strcontent.ToLower();
6626           if (m_pDS->fv("path.exclude").get_asBool())
6627           {
6628             settings.exclude = true;
6629             scraper.reset();
6630             m_pDS->close();
6631             break;
6632           }
6633
6634           content = TranslateContent(strcontent);
6635
6636           AddonPtr addon;
6637           if (content != CONTENT_NONE &&
6638               CAddonMgr::Get().GetAddon(m_pDS->fv("path.strScraper").get_asString(), addon))
6639           {
6640             scraper = boost::dynamic_pointer_cast<CScraper>(addon->Clone(addon));
6641             scraper->SetPathSettings(content, m_pDS->fv("path.strSettings").get_asString());
6642             settings.parent_name = m_pDS->fv("path.useFolderNames").get_asBool();
6643             settings.recurse = m_pDS->fv("path.scanRecursive").get_asInt();
6644             settings.noupdate = m_pDS->fv("path.noUpdate").get_asBool();
6645             settings.exclude = false;
6646             break;
6647           }
6648         }
6649         strPath1 = strParent;
6650       }
6651     }
6652     m_pDS->close();
6653
6654     if (!scraper || scraper->Content() == CONTENT_NONE)
6655       return ScraperPtr();
6656
6657     if (scraper->Content() == CONTENT_TVSHOWS)
6658     {
6659       settings.recurse = 0;
6660       if(settings.parent_name) // single show
6661       {
6662         settings.parent_name_root = settings.parent_name = (iFound == 1);
6663       }
6664       else // show root
6665       {
6666         settings.parent_name_root = settings.parent_name = (iFound == 2);
6667       }
6668     }
6669     else if (scraper->Content() == CONTENT_MOVIES)
6670     {
6671       settings.recurse = settings.recurse - (iFound-1);
6672       settings.parent_name_root = settings.parent_name && (!settings.recurse || iFound > 1);
6673     }
6674     else if (scraper->Content() == CONTENT_MUSICVIDEOS)
6675     {
6676       settings.recurse = settings.recurse - (iFound-1);
6677       settings.parent_name_root = settings.parent_name && (!settings.recurse || iFound > 1);
6678     }
6679     else
6680     {
6681       iFound = 0;
6682       return ScraperPtr();
6683     }
6684     foundDirectly = (iFound == 1);
6685     return scraper;
6686   }
6687   catch (...)
6688   {
6689     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
6690   }
6691   return ScraperPtr();
6692 }
6693
6694 CStdString CVideoDatabase::GetContentForPath(const CStdString& strPath)
6695 {
6696   SScanSettings settings;
6697   bool foundDirectly = false;
6698   ScraperPtr scraper = GetScraperForPath(strPath, settings, foundDirectly);
6699   if (scraper)
6700   {
6701     if (scraper->Content() == CONTENT_TVSHOWS)
6702     { // check for episodes or seasons.  Assumptions are:
6703       // 1. if episodes are in the path then we're in episodes.
6704       // 2. if no episodes are found, and content was set directly on this path, then we're in shows.
6705       // 3. if no episodes are found, and content was not set directly on this path, we're in seasons (assumes tvshows/seasons/episodes)
6706       CStdString sql = PrepareSQL("select count(1) from episodeview where strPath = '%s' limit 1", strPath.c_str());
6707       m_pDS->query( sql.c_str() );
6708       if (m_pDS->num_rows() && m_pDS->fv(0).get_asInt() > 0)
6709         return "episodes";
6710       return foundDirectly ? "tvshows" : "seasons";
6711     }
6712     return TranslateContent(scraper->Content());
6713   }
6714   return "";
6715 }
6716
6717 void CVideoDatabase::GetMovieGenresByName(const CStdString& strSearch, CFileItemList& items)
6718 {
6719   CStdString strSQL;
6720
6721   try
6722   {
6723     if (NULL == m_pDB.get()) return;
6724     if (NULL == m_pDS.get()) return;
6725
6726     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6727       strSQL=PrepareSQL("select genre.idGenre,genre.strGenre,path.strPath from genre,genrelinkmovie,movie,path,files where genre.idGenre=genrelinkmovie.idGenre and genrelinkmovie.idMovie=movie.idMovie and files.idFile=movie.idFile and path.idPath=files.idPath and genre.strGenre like '%%%s%%'",strSearch.c_str());
6728     else
6729       strSQL=PrepareSQL("select distinct genre.idGenre,genre.strGenre from genre,genrelinkmovie where genrelinkmovie.idGenre=genre.idGenre and strGenre like '%%%s%%'", strSearch.c_str());
6730     m_pDS->query( strSQL.c_str() );
6731
6732     while (!m_pDS->eof())
6733     {
6734       if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6735         if (!g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv("path.strPath").get_asString()),
6736                                                       g_settings.m_videoSources))
6737         {
6738           m_pDS->next();
6739           continue;
6740         }
6741
6742       CFileItemPtr pItem(new CFileItem(m_pDS->fv("genre.strGenre").get_asString()));
6743       CStdString strDir;
6744       strDir.Format("%ld/", m_pDS->fv("genre.idGenre").get_asInt());
6745       pItem->SetPath("videodb://1/1/"+ strDir);
6746       pItem->m_bIsFolder=true;
6747       items.Add(pItem);
6748       m_pDS->next();
6749     }
6750     m_pDS->close();
6751   }
6752   catch (...)
6753   {
6754     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
6755   }
6756 }
6757
6758 void CVideoDatabase::GetMovieCountriesByName(const CStdString& strSearch, CFileItemList& items)
6759 {
6760   CStdString strSQL;
6761
6762   try
6763   {
6764     if (NULL == m_pDB.get()) return;
6765     if (NULL == m_pDS.get()) return;
6766
6767     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6768       strSQL=PrepareSQL("select country.idCountry,country.strCountry,path.strPath from country,countrylinkmovie,movie,path,files where country.idCountry=countrylinkmovie.idCountry and countrylinkmovie.idMovie=movie.idMovie and files.idFile=movie.idFile and path.idPath=files.idPath and country.strCountry like '%%%s%%'",strSearch.c_str());
6769     else
6770       strSQL=PrepareSQL("select distinct country.idCountry,country.strCountry from country,countrylinkmovie where countrylinkmovie.idCountry=country.idCountry and strCountry like '%%%s%%'", strSearch.c_str());
6771     m_pDS->query( strSQL.c_str() );
6772
6773     while (!m_pDS->eof())
6774     {
6775       if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6776         if (!g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv("path.strPath").get_asString()),
6777                                                       g_settings.m_videoSources))
6778         {
6779           m_pDS->next();
6780           continue;
6781         }
6782
6783       CFileItemPtr pItem(new CFileItem(m_pDS->fv("country.strCountry").get_asString()));
6784       CStdString strDir;
6785       strDir.Format("%ld/", m_pDS->fv("country.idCountry").get_asInt());
6786       pItem->SetPath("videodb://1/1/"+ strDir);
6787       pItem->m_bIsFolder=true;
6788       items.Add(pItem);
6789       m_pDS->next();
6790     }
6791     m_pDS->close();
6792   }
6793   catch (...)
6794   {
6795     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
6796   }
6797 }
6798
6799 void CVideoDatabase::GetTvShowGenresByName(const CStdString& strSearch, CFileItemList& items)
6800 {
6801   CStdString strSQL;
6802
6803   try
6804   {
6805     if (NULL == m_pDB.get()) return;
6806     if (NULL == m_pDS.get()) return;
6807
6808     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6809       strSQL=PrepareSQL("select genre.idGenre,genre.strGenre,path.strPath from genre,genrelinktvshow,tvshow,path,tvshowlinkpath where genre.idGenre=genrelinktvshow.idGenre and genrelinktvshow.idShow=tvshow.idShow and path.idPath=tvshowlinkpath.idPath and tvshowlinkpath.idShow=tvshow.idShow and genre.strGenre like '%%%s%%'",strSearch.c_str());
6810     else
6811       strSQL=PrepareSQL("select distinct genre.idGenre,genre.strGenre from genre,genrelinktvshow where genrelinktvshow.idGenre=genre.idGenre and strGenre like '%%%s%%'", strSearch.c_str());
6812     m_pDS->query( strSQL.c_str() );
6813
6814     while (!m_pDS->eof())
6815     {
6816       if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6817         if (!g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv("path.strPath").get_asString()),g_settings.m_videoSources))
6818         {
6819           m_pDS->next();
6820           continue;
6821         }
6822
6823       CFileItemPtr pItem(new CFileItem(m_pDS->fv("genre.strGenre").get_asString()));
6824       CStdString strDir;
6825       strDir.Format("%ld/", m_pDS->fv("genre.idGenre").get_asInt());
6826       pItem->SetPath("videodb://2/1/"+ strDir);
6827       pItem->m_bIsFolder=true;
6828       items.Add(pItem);
6829       m_pDS->next();
6830     }
6831     m_pDS->close();
6832   }
6833   catch (...)
6834   {
6835     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
6836   }
6837 }
6838
6839 void CVideoDatabase::GetMovieActorsByName(const CStdString& strSearch, CFileItemList& items)
6840 {
6841   CStdString strSQL;
6842
6843   try
6844   {
6845     if (NULL == m_pDB.get()) return;
6846     if (NULL == m_pDS.get()) return;
6847
6848     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6849       strSQL=PrepareSQL("select actors.idActor,actors.strActor,path.strPath from actorlinkmovie,actors,movie,files,path where actors.idActor=actorlinkmovie.idActor and actorlinkmovie.idMovie=movie.idMovie and files.idFile=movie.idFile and files.idPath=path.idPath and actors.strActor like '%%%s%%'",strSearch.c_str());
6850     else
6851       strSQL=PrepareSQL("select distinct actors.idActor,actors.strActor from actorlinkmovie,actors,movie where actors.idActor=actorlinkmovie.idActor and actorlinkmovie.idMovie=movie.idMovie and actors.strActor like '%%%s%%'",strSearch.c_str());
6852     m_pDS->query( strSQL.c_str() );
6853
6854     while (!m_pDS->eof())
6855     {
6856       if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6857         if (!g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv("path.strPath").get_asString()),g_settings.m_videoSources))
6858         {
6859           m_pDS->next();
6860           continue;
6861         }
6862
6863       CFileItemPtr pItem(new CFileItem(m_pDS->fv("actors.strActor").get_asString()));
6864       CStdString strDir;
6865       strDir.Format("%ld/", m_pDS->fv("actors.idActor").get_asInt());
6866       pItem->SetPath("videodb://1/4/"+ strDir);
6867       pItem->m_bIsFolder=true;
6868       items.Add(pItem);
6869       m_pDS->next();
6870     }
6871     m_pDS->close();
6872   }
6873   catch (...)
6874   {
6875     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
6876   }
6877 }
6878
6879 void CVideoDatabase::GetTvShowsActorsByName(const CStdString& strSearch, CFileItemList& items)
6880 {
6881   CStdString strSQL;
6882
6883   try
6884   {
6885     if (NULL == m_pDB.get()) return;
6886     if (NULL == m_pDS.get()) return;
6887
6888     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6889       strSQL=PrepareSQL("select actors.idActor,actors.strActor,path.strPath from actorlinktvshow,actors,tvshow,path,tvshowlinkpath where actors.idActor=actorlinktvshow.idActor and actorlinktvshow.idShow=tvshow.idShow and tvshowlinkpath.idPath=tvshow.idShow and tvshowlinkpath.idPath=path.idPath and actors.strActor like '%%%s%%'",strSearch.c_str());
6890     else
6891       strSQL=PrepareSQL("select distinct actors.idActor,actors.strActor from actorlinktvshow,actors,tvshow where actors.idActor=actorlinktvshow.idActor and actorlinktvshow.idShow=tvshow.idShow and actors.strActor like '%%%s%%'",strSearch.c_str());
6892     m_pDS->query( strSQL.c_str() );
6893
6894     while (!m_pDS->eof())
6895     {
6896       if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6897         if (!g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv("path.strPath").get_asString()),g_settings.m_videoSources))
6898         {
6899           m_pDS->next();
6900           continue;
6901         }
6902
6903       CFileItemPtr pItem(new CFileItem(m_pDS->fv("actors.strActor").get_asString()));
6904       CStdString strDir;
6905       strDir.Format("%ld/", m_pDS->fv("actors.idActor").get_asInt());
6906       pItem->SetPath("videodb://2/4/"+ strDir);
6907       pItem->m_bIsFolder=true;
6908       items.Add(pItem);
6909       m_pDS->next();
6910     }
6911     m_pDS->close();
6912   }
6913   catch (...)
6914   {
6915     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
6916   }
6917 }
6918
6919 void CVideoDatabase::GetMusicVideoArtistsByName(const CStdString& strSearch, CFileItemList& items)
6920 {
6921   CStdString strSQL;
6922
6923   try
6924   {
6925     if (NULL == m_pDB.get()) return;
6926     if (NULL == m_pDS.get()) return;
6927
6928     CStdString strLike;
6929     if (!strSearch.IsEmpty())
6930       strLike = "and actors.strActor like '%%%s%%'";
6931     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6932       strSQL=PrepareSQL("select actors.idActor,actors.strActor,path.strPath from artistlinkmusicvideo,actors,musicvideo,files,path where actors.idActor=artistlinkmusicvideo.idArtist and artistlinkmusicvideo.idMVideo=musicvideo.idMVideo and files.idFile=musicvideo.idFile and files.idPath=path.idPath "+strLike,strSearch.c_str());
6933     else
6934       strSQL=PrepareSQL("select distinct actors.idActor,actors.strActor from artistlinkmusicvideo,actors where actors.idActor=artistlinkmusicvideo.idArtist "+strLike,strSearch.c_str());
6935     m_pDS->query( strSQL.c_str() );
6936
6937     while (!m_pDS->eof())
6938     {
6939       if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6940         if (!g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv("path.strPath").get_asString()),g_settings.m_videoSources))
6941         {
6942           m_pDS->next();
6943           continue;
6944         }
6945
6946       CFileItemPtr pItem(new CFileItem(m_pDS->fv("actors.strActor").get_asString()));
6947       CStdString strDir;
6948       strDir.Format("%ld/", m_pDS->fv("actors.idActor").get_asInt());
6949       pItem->SetPath("videodb://3/4/"+ strDir);
6950       pItem->m_bIsFolder=true;
6951       items.Add(pItem);
6952       m_pDS->next();
6953     }
6954     m_pDS->close();
6955   }
6956   catch (...)
6957   {
6958     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
6959   }
6960 }
6961
6962 void CVideoDatabase::GetMusicVideoGenresByName(const CStdString& strSearch, CFileItemList& items)
6963 {
6964   CStdString strSQL;
6965
6966   try
6967   {
6968     if (NULL == m_pDB.get()) return;
6969     if (NULL == m_pDS.get()) return;
6970
6971     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6972       strSQL=PrepareSQL("select genre.idGenre,genre.strGenre,path.strPath from genre,genrelinkmusicvideo,musicvideo,path,files where genre.idGenre=genrelinkmusicvideo.idGenre and genrelinkmusicvideo.idMVideo = musicvideo.idMVideo and files.idFile=musicvideo.idFile and path.idPath=files.idPath and genre.strGenre like '%%%s%%'",strSearch.c_str());
6973     else
6974       strSQL=PrepareSQL("select distinct genre.idGenre,genre.strGenre from genre,genrelinkmusicvideo where genrelinkmusicvideo.idGenre=genre.idGenre and genre.strGenre like '%%%s%%'", strSearch.c_str());
6975     m_pDS->query( strSQL.c_str() );
6976
6977     while (!m_pDS->eof())
6978     {
6979       if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6980         if (!g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv("path.strPath").get_asString()),g_settings.m_videoSources))
6981         {
6982           m_pDS->next();
6983           continue;
6984         }
6985
6986       CFileItemPtr pItem(new CFileItem(m_pDS->fv("genre.strGenre").get_asString()));
6987       CStdString strDir;
6988       strDir.Format("%ld/", m_pDS->fv("genre.idGenre").get_asInt());
6989       pItem->SetPath("videodb://3/1/"+ strDir);
6990       pItem->m_bIsFolder=true;
6991       items.Add(pItem);
6992       m_pDS->next();
6993     }
6994     m_pDS->close();
6995   }
6996   catch (...)
6997   {
6998     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
6999   }
7000 }
7001
7002 void CVideoDatabase::GetMusicVideoAlbumsByName(const CStdString& strSearch, CFileItemList& items)
7003 {
7004   CStdString strSQL;
7005
7006   try
7007   {
7008     if (NULL == m_pDB.get()) return;
7009     if (NULL == m_pDS.get()) return;
7010
7011     CStdString strLike;
7012     if (!strSearch.IsEmpty())
7013     {
7014       strLike.Format("and musicvideo.c%02d",VIDEODB_ID_MUSICVIDEO_ALBUM);
7015       strLike += "like '%%s%%%'";
7016     }
7017     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7018       strSQL=PrepareSQL("select distinct musicvideo.c%02d,musicvideo.idMVideo,path.strPath from musicvideo,files,path where files.idFile=musicvideo.idFile and files.idPath=path.idPath"+strLike,VIDEODB_ID_MUSICVIDEO_ALBUM,strSearch.c_str());
7019     else
7020     {
7021       if (!strLike.IsEmpty())
7022         strLike = "where "+strLike.Mid(4);
7023       strSQL=PrepareSQL("select distinct musicvideo.c%02d,musicvideo.idMVideo from musicvideo"+strLike,VIDEODB_ID_MUSICVIDEO_ALBUM,strSearch.c_str());
7024     }
7025     m_pDS->query( strSQL.c_str() );
7026
7027     while (!m_pDS->eof())
7028     {
7029       if (m_pDS->fv(0).get_asString().empty())
7030       {
7031         m_pDS->next();
7032         continue;
7033       }
7034
7035       if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7036         if (!g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv("path.strPath").get_asString()),g_settings.m_videoSources))
7037         {
7038           m_pDS->next();
7039           continue;
7040         }
7041
7042       CFileItemPtr pItem(new CFileItem(m_pDS->fv(0).get_asString()));
7043       CStdString strDir;
7044       strDir.Format("%ld", m_pDS->fv(1).get_asInt());
7045       pItem->SetPath("videodb://3/2/"+ strDir);
7046       pItem->m_bIsFolder=false;
7047       items.Add(pItem);
7048       m_pDS->next();
7049     }
7050     m_pDS->close();
7051   }
7052   catch (...)
7053   {
7054     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
7055   }
7056 }
7057
7058 void CVideoDatabase::GetMusicVideosByAlbum(const CStdString& strSearch, CFileItemList& items)
7059 {
7060   CStdString strSQL;
7061
7062   try
7063   {
7064     if (NULL == m_pDB.get()) return;
7065     if (NULL == m_pDS.get()) return;
7066
7067     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7068       strSQL = PrepareSQL("select musicvideo.idMVideo,musicvideo.c%02d,musicvideo.c%02d,path.strPath from musicvideo,files,path where files.idFile=musicvideo.idFile and files.idPath=path.idPath and musicvideo.c%02d like '%%%s%%'",VIDEODB_ID_MUSICVIDEO_ALBUM,VIDEODB_ID_MUSICVIDEO_TITLE,VIDEODB_ID_MUSICVIDEO_ALBUM,strSearch.c_str());
7069     else
7070       strSQL = PrepareSQL("select musicvideo.idMVideo,musicvideo.c%02d,musicvideo.c%02d from musicvideo where musicvideo.c%02d like '%%%s%%'",VIDEODB_ID_MUSICVIDEO_ALBUM,VIDEODB_ID_MUSICVIDEO_TITLE,VIDEODB_ID_MUSICVIDEO_ALBUM,strSearch.c_str());
7071     m_pDS->query( strSQL.c_str() );
7072
7073     while (!m_pDS->eof())
7074     {
7075       if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7076         if (!g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv("path.strPath").get_asString()),g_settings.m_videoSources))
7077         {
7078           m_pDS->next();
7079           continue;
7080         }
7081
7082       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()+" - "+m_pDS->fv(2).get_asString()));
7083       CStdString strDir;
7084       strDir.Format("3/2/%ld",m_pDS->fv("musicvideo.idMVideo").get_asInt());
7085
7086       pItem->SetPath("videodb://"+ strDir);
7087       pItem->m_bIsFolder=false;
7088       items.Add(pItem);
7089       m_pDS->next();
7090     }
7091     m_pDS->close();
7092   }
7093   catch (...)
7094   {
7095     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
7096   }
7097 }
7098
7099 bool CVideoDatabase::GetMusicVideosByWhere(const CStdString &baseDir, const Filter &filter, CFileItemList &items, bool checkLocks /*= true*/, const SortDescription &sortDescription /* = SortDescription() */)
7100 {
7101   try
7102   {
7103     movieTime = 0;
7104     castTime = 0;
7105
7106     if (NULL == m_pDB.get()) return false;
7107     if (NULL == m_pDS.get()) return false;
7108
7109     int total = -1;
7110     
7111     CStdString strSQL = "select %s from musicvideoview ";
7112     CVideoDbUrl videoUrl;
7113     CStdString strSQLExtra;
7114     Filter extFilter = filter;
7115     SortDescription sorting = sortDescription;
7116     if (!BuildSQL(baseDir, strSQLExtra, extFilter, strSQLExtra, videoUrl, sorting))
7117       return false;
7118
7119     // Apply the limiting directly here if there's no special sorting but limiting
7120     if (extFilter.limit.empty() &&
7121       sorting.sortBy == SortByNone &&
7122       (sorting.limitStart > 0 || sorting.limitEnd > 0))
7123     {
7124       total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
7125       strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
7126     }
7127
7128     strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
7129
7130     int iRowsFound = RunQuery(strSQL);
7131     if (iRowsFound <= 0)
7132       return iRowsFound == 0;
7133
7134     // store the total value of items as a property
7135     if (total < iRowsFound)
7136       total = iRowsFound;
7137     items.SetProperty("total", total);
7138     
7139     DatabaseResults results;
7140     results.reserve(iRowsFound);
7141     if (!SortUtils::SortFromDataset(sorting, MediaTypeMusicVideo, m_pDS, results))
7142       return false;
7143     
7144     // get data from returned rows
7145     items.Reserve(results.size());
7146     // get songs from returned subtable
7147     const query_data &data = m_pDS->get_result_set().records;
7148     for (DatabaseResults::const_iterator it = results.begin(); it != results.end(); it++)
7149     {
7150       unsigned int targetRow = (unsigned int)it->at(FieldRow).asInteger();
7151       const dbiplus::sql_record* const record = data.at(targetRow);
7152       
7153       CVideoInfoTag musicvideo = GetDetailsForMusicVideo(record);
7154       if (!checkLocks || g_settings.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE || g_passwordManager.bMasterUser ||
7155           g_passwordManager.IsDatabasePathUnlocked(musicvideo.m_strPath, g_settings.m_videoSources))
7156       {
7157         CFileItemPtr item(new CFileItem(musicvideo));
7158
7159         CVideoDbUrl itemUrl = videoUrl;
7160         CStdString path; path.Format("%ld", record->at(0).get_asInt());
7161         itemUrl.AppendPath(path);
7162         item->SetPath(itemUrl.ToString());
7163
7164         item->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, musicvideo.m_playCount > 0);
7165         items.Add(item);
7166       }
7167     }
7168
7169     // cleanup
7170     m_pDS->close();
7171     return true;
7172   }
7173   catch (...)
7174   {
7175     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
7176   }
7177   return false;
7178 }
7179
7180 unsigned int CVideoDatabase::GetMusicVideoIDs(const CStdString& strWhere, vector<pair<int,int> > &songIDs)
7181 {
7182   try
7183   {
7184     if (NULL == m_pDB.get()) return 0;
7185     if (NULL == m_pDS.get()) return 0;
7186
7187     CStdString strSQL = "select distinct idMVideo from musicvideoview " + strWhere;
7188     if (!m_pDS->query(strSQL.c_str())) return 0;
7189     songIDs.clear();
7190     if (m_pDS->num_rows() == 0)
7191     {
7192       m_pDS->close();
7193       return 0;
7194     }
7195     songIDs.reserve(m_pDS->num_rows());
7196     while (!m_pDS->eof())
7197     {
7198       songIDs.push_back(make_pair<int,int>(2,m_pDS->fv(0).get_asInt()));
7199       m_pDS->next();
7200     }    // cleanup
7201     m_pDS->close();
7202     return songIDs.size();
7203   }
7204   catch (...)
7205   {
7206     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strWhere.c_str());
7207   }
7208   return 0;
7209 }
7210
7211 bool CVideoDatabase::GetRandomMusicVideo(CFileItem* item, int& idSong, const CStdString& strWhere)
7212 {
7213   try
7214   {
7215     idSong = -1;
7216
7217     int iCount = GetMusicVideoCount(strWhere);
7218     if (iCount <= 0)
7219       return false;
7220     int iRandom = rand() % iCount;
7221
7222     if (NULL == m_pDB.get()) return false;
7223     if (NULL == m_pDS.get()) return false;
7224
7225     // We don't use PrepareSQL here, as the WHERE clause is already formatted.
7226     CStdString strSQL;
7227     strSQL.Format("select * from musicvideoview where %s order by idMVideo limit 1 offset %i",strWhere.c_str(),iRandom);
7228     CLog::Log(LOGDEBUG, "%s query = %s", __FUNCTION__, strSQL.c_str());
7229     // run query
7230     if (!m_pDS->query(strSQL.c_str()))
7231       return false;
7232     int iRowsFound = m_pDS->num_rows();
7233     if (iRowsFound != 1)
7234     {
7235       m_pDS->close();
7236       return false;
7237     }
7238     *item->GetVideoInfoTag() = GetDetailsForMusicVideo(m_pDS);
7239     CStdString path; path.Format("videodb://3/2/%ld",item->GetVideoInfoTag()->m_iDbId);
7240     item->SetPath(path);
7241     idSong = m_pDS->fv("idMVideo").get_asInt();
7242     item->SetLabel(item->GetVideoInfoTag()->m_strTitle);
7243     m_pDS->close();
7244     return true;
7245   }
7246   catch(...)
7247   {
7248     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strWhere.c_str());
7249   }
7250   return false;
7251 }
7252
7253 int CVideoDatabase::GetMatchingMusicVideo(const CStdString& strArtist, const CStdString& strAlbum, const CStdString& strTitle)
7254 {
7255   try
7256   {
7257     if (NULL == m_pDB.get()) return -1;
7258     if (NULL == m_pDS.get()) return -1;
7259
7260     CStdString strSQL;
7261     if (strAlbum.IsEmpty() && strTitle.IsEmpty())
7262     { // we want to return matching artists only
7263       if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7264         strSQL=PrepareSQL("select distinct actors.idActor,path.strPath from artistlinkmusicvideo,actors,musicvideo,files,path where actors.idActor=artistlinkmusicvideo.idArtist and artistlinkmusicvideo.idMVideo=musicvideo.idMVideo and files.idFile=musicvideo.idFile and files.idPath=path.idPath and actors.strActor like '%s'",strArtist.c_str());
7265       else
7266         strSQL=PrepareSQL("select distinct actors.idActor from artistlinkmusicvideo,actors where actors.idActor=artistlinkmusicvideo.idArtist and actors.strActor like '%s'",strArtist.c_str());
7267     }
7268     else
7269     { // we want to return the matching musicvideo
7270       if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7271         strSQL = PrepareSQL("select musicvideo.idMVideo from musicvideo,files,path,artistlinkmusicvideo,actors where files.idFile=musicvideo.idFile and files.idPath=path.idPath and musicvideo.%c02d like '%s' and musicvideo.%c02d like '%s' and artistlinkmusicvideo.idMVideo=musicvideo.idMVideo and artistlinkmusicvideo.idArtist=actors.idActors and actors.strActor like '%s'",VIDEODB_ID_MUSICVIDEO_ALBUM,strAlbum.c_str(),VIDEODB_ID_MUSICVIDEO_TITLE,strTitle.c_str(),strArtist.c_str());
7272       else
7273         strSQL = PrepareSQL("select musicvideo.idMVideo from musicvideo join artistlinkmusicvideo on artistlinkmusicvideo.idMVideo=musicvideo.idMVideo join actors on actors.idActor=artistlinkmusicvideo.idArtist where musicvideo.c%02d like '%s' and musicvideo.c%02d like '%s' and actors.strActor like '%s'",VIDEODB_ID_MUSICVIDEO_ALBUM,strAlbum.c_str(),VIDEODB_ID_MUSICVIDEO_TITLE,strTitle.c_str(),strArtist.c_str());
7274     }
7275     m_pDS->query( strSQL.c_str() );
7276
7277     if (m_pDS->eof())
7278       return -1;
7279
7280     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7281       if (!g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv("path.strPath").get_asString()),g_settings.m_videoSources))
7282       {
7283         m_pDS->close();
7284         return -1;
7285       }
7286
7287     int lResult = m_pDS->fv(0).get_asInt();
7288     m_pDS->close();
7289     return lResult;
7290   }
7291   catch (...)
7292   {
7293     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
7294   }
7295   return -1;
7296 }
7297
7298 void CVideoDatabase::GetMoviesByName(const CStdString& strSearch, CFileItemList& items)
7299 {
7300   CStdString strSQL;
7301
7302   try
7303   {
7304     if (NULL == m_pDB.get()) return;
7305     if (NULL == m_pDS.get()) return;
7306
7307     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7308       strSQL = PrepareSQL("select movie.idMovie,movie.c%02d,path.strPath, movie.idSet from movie,files,path where files.idFile=movie.idFile and files.idPath=path.idPath and movie.c%02d like '%%%s%%'",VIDEODB_ID_TITLE,VIDEODB_ID_TITLE,strSearch.c_str());
7309     else
7310       strSQL = PrepareSQL("select movie.idMovie,movie.c%02d, movie.idSet from movie where movie.c%02d like '%%%s%%'",VIDEODB_ID_TITLE,VIDEODB_ID_TITLE,strSearch.c_str());
7311     m_pDS->query( strSQL.c_str() );
7312
7313     while (!m_pDS->eof())
7314     {
7315       if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7316         if (!g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv("path.strPath").get_asString()),g_settings.m_videoSources))
7317         {
7318           m_pDS->next();
7319           continue;
7320         }
7321
7322       int movieId = m_pDS->fv("movie.idMovie").get_asInt();
7323       int setId = m_pDS->fv("movie.idSet").get_asInt();
7324       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
7325       CStdString path;
7326       if (setId <= 0)
7327         path.Format("videodb://1/2/%i", movieId);
7328       else
7329         path.Format("videodb://1/7/%i/%i", setId, movieId);
7330       pItem->SetPath(path);
7331       pItem->m_bIsFolder=false;
7332       items.Add(pItem);
7333       m_pDS->next();
7334     }
7335     m_pDS->close();
7336   }
7337   catch (...)
7338   {
7339     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
7340   }
7341 }
7342
7343 void CVideoDatabase::GetTvShowsByName(const CStdString& strSearch, CFileItemList& items)
7344 {
7345   CStdString strSQL;
7346
7347   try
7348   {
7349     if (NULL == m_pDB.get()) return;
7350     if (NULL == m_pDS.get()) return;
7351
7352     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7353       strSQL = PrepareSQL("select tvshow.idShow,tvshow.c%02d,path.strPath from tvshow,path,tvshowlinkpath where tvshowlinkpath.idPath=path.idPath and tvshowlinkpath.idShow=tvshow.idShow and tvshow.c%02d like '%%%s%%'",VIDEODB_ID_TV_TITLE,VIDEODB_ID_TV_TITLE,strSearch.c_str());
7354     else
7355       strSQL = PrepareSQL("select tvshow.idShow,tvshow.c%02d from tvshow where tvshow.c%02d like '%%%s%%'",VIDEODB_ID_TV_TITLE,VIDEODB_ID_TV_TITLE,strSearch.c_str());
7356     m_pDS->query( strSQL.c_str() );
7357
7358     while (!m_pDS->eof())
7359     {
7360       if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7361         if (!g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv("path.strPath").get_asString()),g_settings.m_videoSources))
7362         {
7363           m_pDS->next();
7364           continue;
7365         }
7366
7367       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
7368       CStdString strDir;
7369       strDir.Format("2/2/%ld/", m_pDS->fv("tvshow.idShow").get_asInt());
7370
7371       pItem->SetPath("videodb://"+ strDir);
7372       pItem->m_bIsFolder=true;
7373       pItem->GetVideoInfoTag()->m_iDbId = m_pDS->fv("tvshow.idShow").get_asInt();
7374       items.Add(pItem);
7375       m_pDS->next();
7376     }
7377     m_pDS->close();
7378   }
7379   catch (...)
7380   {
7381     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
7382   }
7383 }
7384
7385 void CVideoDatabase::GetEpisodesByName(const CStdString& strSearch, CFileItemList& items)
7386 {
7387   CStdString strSQL;
7388
7389   try
7390   {
7391     if (NULL == m_pDB.get()) return;
7392     if (NULL == m_pDS.get()) return;
7393
7394     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7395       strSQL = PrepareSQL("select episode.idEpisode,episode.c%02d,episode.c%02d,episode.idShow,tvshow.c%02d,path.strPath from episode,files,path,tvshow where files.idFile=episode.idFile and episode.idShow=tvshow.idShow and files.idPath=path.idPath and episode.c%02d like '%%%s%%'",VIDEODB_ID_EPISODE_TITLE,VIDEODB_ID_EPISODE_SEASON,VIDEODB_ID_TV_TITLE,VIDEODB_ID_EPISODE_TITLE,strSearch.c_str());
7396     else
7397       strSQL = PrepareSQL("select episode.idEpisode,episode.c%02d,episode.c%02d,episode.idShow,tvshow.c%02d from episode,tvshow where tvshow.idShow=episode.idShow and episode.c%02d like '%%%s%%'",VIDEODB_ID_EPISODE_TITLE,VIDEODB_ID_EPISODE_SEASON,VIDEODB_ID_TV_TITLE,VIDEODB_ID_EPISODE_TITLE,strSearch.c_str());
7398     m_pDS->query( strSQL.c_str() );
7399
7400     while (!m_pDS->eof())
7401     {
7402       if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7403         if (!g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv("path.strPath").get_asString()),g_settings.m_videoSources))
7404         {
7405           m_pDS->next();
7406           continue;
7407         }
7408
7409       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()+" ("+m_pDS->fv(4).get_asString()+")"));
7410       CStdString path; path.Format("videodb://2/2/%ld/%ld/%ld",m_pDS->fv("episode.idShow").get_asInt(),m_pDS->fv(2).get_asInt(),m_pDS->fv(0).get_asInt());
7411       pItem->SetPath(path);
7412       pItem->m_bIsFolder=false;
7413       items.Add(pItem);
7414       m_pDS->next();
7415     }
7416     m_pDS->close();
7417   }
7418   catch (...)
7419   {
7420     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
7421   }
7422 }
7423
7424 void CVideoDatabase::GetMusicVideosByName(const CStdString& strSearch, CFileItemList& items)
7425 {
7426 // Alternative searching - not quite as fast though due to
7427 // retrieving all information
7428 //  Filter filter(PrepareSQL("c%02d like '%s%%' or c%02d like '%% %s%%'", VIDEODB_ID_MUSICVIDEO_TITLE, strSearch.c_str(), VIDEODB_ID_MUSICVIDEO_TITLE, strSearch.c_str()));
7429 //  GetMusicVideosByWhere("videodb://3/2/", filter, items);
7430   CStdString strSQL;
7431
7432   try
7433   {
7434     if (NULL == m_pDB.get()) return;
7435     if (NULL == m_pDS.get()) return;
7436
7437     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7438       strSQL = PrepareSQL("select musicvideo.idMVideo,musicvideo.c%02d,path.strPath from musicvideo,files,path where files.idFile=musicvideo.idFile and files.idPath=path.idPath and musicvideo.c%02d like '%%%s%%'",VIDEODB_ID_MUSICVIDEO_TITLE,VIDEODB_ID_MUSICVIDEO_TITLE,strSearch.c_str());
7439     else
7440       strSQL = PrepareSQL("select musicvideo.idMVideo,musicvideo.c%02d from musicvideo where musicvideo.c%02d like '%%%s%%'",VIDEODB_ID_MUSICVIDEO_TITLE,VIDEODB_ID_MUSICVIDEO_TITLE,strSearch.c_str());
7441     m_pDS->query( strSQL.c_str() );
7442
7443     while (!m_pDS->eof())
7444     {
7445       if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7446         if (!g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv("path.strPath").get_asString()),g_settings.m_videoSources))
7447         {
7448           m_pDS->next();
7449           continue;
7450         }
7451
7452       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
7453       CStdString strDir;
7454       strDir.Format("3/2/%ld",m_pDS->fv("musicvideo.idMVideo").get_asInt());
7455
7456       pItem->SetPath("videodb://"+ strDir);
7457       pItem->m_bIsFolder=false;
7458       items.Add(pItem);
7459       m_pDS->next();
7460     }
7461     m_pDS->close();
7462   }
7463   catch (...)
7464   {
7465     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
7466   }
7467 }
7468
7469 void CVideoDatabase::GetEpisodesByPlot(const CStdString& strSearch, CFileItemList& items)
7470 {
7471 // Alternative searching - not quite as fast though due to
7472 // retrieving all information
7473 //  Filter filter;
7474 //  filter.where = PrepareSQL("c%02d like '%s%%' or c%02d like '%% %s%%'", VIDEODB_ID_EPISODE_PLOT, strSearch.c_str(), VIDEODB_ID_EPISODE_PLOT, strSearch.c_str());
7475 //  filter.where += PrepareSQL("or c%02d like '%s%%' or c%02d like '%% %s%%'", VIDEODB_ID_EPISODE_TITLE, strSearch.c_str(), VIDEODB_ID_EPISODE_TITLE, strSearch.c_str());
7476 //  GetEpisodesByWhere("videodb://2/2/", filter, items);
7477 //  return;
7478   CStdString strSQL;
7479
7480   try
7481   {
7482     if (NULL == m_pDB.get()) return;
7483     if (NULL == m_pDS.get()) return;
7484
7485     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7486       strSQL = PrepareSQL("select episode.idEpisode,episode.c%02d,episode.c%02d,episode.idShow,tvshow.c%02d,path.strPath from episode,files,path,tvshow where files.idFile=episode.idFile and files.idPath=path.idPath and tvshow.idShow=episode.idShow and episode.c%02d like '%%%s%%'",VIDEODB_ID_EPISODE_TITLE,VIDEODB_ID_EPISODE_SEASON,VIDEODB_ID_TV_TITLE,VIDEODB_ID_EPISODE_PLOT,strSearch.c_str());
7487     else
7488       strSQL = PrepareSQL("select episode.idEpisode,episode.c%02d,episode.c%02d,episode.idShow,tvshow.c%02d from episode,tvshow where tvshow.idShow=episode.idShow and episode.c%02d like '%%%s%%'",VIDEODB_ID_EPISODE_TITLE,VIDEODB_ID_EPISODE_SEASON,VIDEODB_ID_TV_TITLE,VIDEODB_ID_EPISODE_PLOT,strSearch.c_str());
7489     m_pDS->query( strSQL.c_str() );
7490
7491     while (!m_pDS->eof())
7492     {
7493       if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7494         if (!g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv("path.strPath").get_asString()),g_settings.m_videoSources))
7495         {
7496           m_pDS->next();
7497           continue;
7498         }
7499
7500       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()+" ("+m_pDS->fv(4).get_asString()+")"));
7501       CStdString path; path.Format("videodb://2/2/%ld/%ld/%ld",m_pDS->fv("episode.idShow").get_asInt(),m_pDS->fv(2).get_asInt(),m_pDS->fv(0).get_asInt());
7502       pItem->SetPath(path);
7503       pItem->m_bIsFolder=false;
7504       items.Add(pItem);
7505       m_pDS->next();
7506     }
7507     m_pDS->close();
7508   }
7509   catch (...)
7510   {
7511     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
7512   }
7513 }
7514
7515 void CVideoDatabase::GetMoviesByPlot(const CStdString& strSearch, CFileItemList& items)
7516 {
7517   CStdString strSQL;
7518
7519   try
7520   {
7521     if (NULL == m_pDB.get()) return;
7522     if (NULL == m_pDS.get()) return;
7523
7524     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7525       strSQL = PrepareSQL("select movie.idMovie, movie.c%02d, path.strPath from movie,files,path where files.idFile=movie.idFile and files.idPath=path.idPath and (movie.c%02d like '%%%s%%' or movie.c%02d like '%%%s%%' or movie.c%02d like '%%%s%%')",VIDEODB_ID_TITLE,VIDEODB_ID_PLOT,strSearch.c_str(),VIDEODB_ID_PLOTOUTLINE,strSearch.c_str(),VIDEODB_ID_TAGLINE,strSearch.c_str());
7526     else
7527       strSQL = PrepareSQL("select movie.idMovie, movie.c%02d from movie where (movie.c%02d like '%%%s%%' or movie.c%02d like '%%%s%%' or movie.c%02d like '%%%s%%')",VIDEODB_ID_TITLE,VIDEODB_ID_PLOT,strSearch.c_str(),VIDEODB_ID_PLOTOUTLINE,strSearch.c_str(),VIDEODB_ID_TAGLINE,strSearch.c_str());
7528
7529     m_pDS->query( strSQL.c_str() );
7530
7531     while (!m_pDS->eof())
7532     {
7533       if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7534         if (!g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv(2).get_asString()),g_settings.m_videoSources))
7535         {
7536           m_pDS->next();
7537           continue;
7538         }
7539
7540       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
7541       CStdString path; path.Format("videodb://1/2/%ld", m_pDS->fv(0).get_asInt());
7542       pItem->SetPath(path);
7543       pItem->m_bIsFolder=false;
7544
7545       items.Add(pItem);
7546       m_pDS->next();
7547     }
7548     m_pDS->close();
7549
7550   }
7551   catch (...)
7552   {
7553     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
7554   }
7555 }
7556
7557 void CVideoDatabase::GetMovieDirectorsByName(const CStdString& strSearch, CFileItemList& items)
7558 {
7559   CStdString strSQL;
7560
7561   try
7562   {
7563     if (NULL == m_pDB.get()) return;
7564     if (NULL == m_pDS.get()) return;
7565
7566     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7567       strSQL = PrepareSQL("select distinct directorlinkmovie.idDirector,actors.strActor,path.strPath from movie,files,path,actors,directorlinkmovie where files.idFile=movie.idFile and files.idPath=path.idPath and directorlinkmovie.idMovie=movie.idMovie and directorlinkmovie.idDirector=actors.idActor and actors.strActor like '%%%s%%'",strSearch.c_str());
7568     else
7569       strSQL = PrepareSQL("select distinct directorlinkmovie.idDirector,actors.strActor from movie,actors,directorlinkmovie where directorlinkmovie.idMovie=movie.idMovie and directorlinkmovie.idDirector=actors.idActor and actors.strActor like '%%%s%%'",strSearch.c_str());
7570
7571     m_pDS->query( strSQL.c_str() );
7572
7573     while (!m_pDS->eof())
7574     {
7575       if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7576         if (!g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv("path.strPath").get_asString()),g_settings.m_videoSources))
7577         {
7578           m_pDS->next();
7579           continue;
7580         }
7581
7582       CStdString strDir;
7583       strDir.Format("%ld/", m_pDS->fv("directorlinkmovie.idDirector").get_asInt());
7584       CFileItemPtr pItem(new CFileItem(m_pDS->fv("actors.strActor").get_asString()));
7585
7586       pItem->SetPath("videodb://1/5/"+ strDir);
7587       pItem->m_bIsFolder=true;
7588       items.Add(pItem);
7589       m_pDS->next();
7590     }
7591     m_pDS->close();
7592   }
7593   catch (...)
7594   {
7595     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
7596   }
7597 }
7598
7599 void CVideoDatabase::GetTvShowsDirectorsByName(const CStdString& strSearch, CFileItemList& items)
7600 {
7601   CStdString strSQL;
7602
7603   try
7604   {
7605     if (NULL == m_pDB.get()) return;
7606     if (NULL == m_pDS.get()) return;
7607
7608     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7609       strSQL = PrepareSQL("select distinct directorlinktvshow.idDirector,actors.strActor,path.strPath from tvshow,path,actors,directorlinktvshow,tvshowlinkpath where tvshowlinkpath.idPath=path.idPath and tvshowlinkpath.idShow=tvshow.idShow and directorlinktvshow.idShow=tvshow.idShow and directorlinktvshow.idDirector=actors.idActor and actors.strActor like '%%%s%%'",strSearch.c_str());
7610     else
7611       strSQL = PrepareSQL("select distinct directorlinktvshow.idDirector,actors.strActor from tvshow,actors,directorlinktvshow where directorlinktvshow.idShow=tvshow.idShow and directorlinktvshow.idDirector=actors.idActor and actors.strActor like '%%%s%%'",strSearch.c_str());
7612
7613     m_pDS->query( strSQL.c_str() );
7614
7615     while (!m_pDS->eof())
7616     {
7617       if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7618         if (!g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv("path.strPath").get_asString()),g_settings.m_videoSources))
7619         {
7620           m_pDS->next();
7621           continue;
7622         }
7623
7624       CStdString strDir;
7625       strDir.Format("%ld/", m_pDS->fv("directorlinktvshow.idDirector").get_asInt());
7626       CFileItemPtr pItem(new CFileItem(m_pDS->fv("actors.strActor").get_asString()));
7627
7628       pItem->SetPath("videodb://2/5/"+ strDir);
7629       pItem->m_bIsFolder=true;
7630       items.Add(pItem);
7631       m_pDS->next();
7632     }
7633     m_pDS->close();
7634   }
7635   catch (...)
7636   {
7637     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
7638   }
7639 }
7640
7641 void CVideoDatabase::GetMusicVideoDirectorsByName(const CStdString& strSearch, CFileItemList& items)
7642 {
7643   CStdString strSQL;
7644
7645   try
7646   {
7647     if (NULL == m_pDB.get()) return;
7648     if (NULL == m_pDS.get()) return;
7649
7650     if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7651       strSQL = PrepareSQL("select distinct directorlinkmusicvideo.idDirector,actors.strActor,path.strPath from musicvideo,files,path,actors,directorlinkmusicvideo where files.idFile=musicvideo.idFile and files.idPath=path.idPath and directorlinkmusicvideo.idMVideo=musicvideo.idMVideo and directorlinkmusicvideo.idDirector=actors.idActor and actors.strActor like '%%%s%%'",strSearch.c_str());
7652     else
7653       strSQL = PrepareSQL("select distinct directorlinkmusicvideo.idDirector,actors.strActor from musicvideo,actors,directorlinkmusicvideo where directorlinkmusicvideo.idMVideo=musicvideo.idMVideo and directorlinkmusicvideo.idDirector=actors.idActor and actors.strActor like '%%%s%%'",strSearch.c_str());
7654
7655     m_pDS->query( strSQL.c_str() );
7656
7657     while (!m_pDS->eof())
7658     {
7659       if (g_settings.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7660         if (!g_passwordManager.IsDatabasePathUnlocked(CStdString(m_pDS->fv("path.strPath").get_asString()),g_settings.m_videoSources))
7661         {
7662           m_pDS->next();
7663           continue;
7664         }
7665
7666       CStdString strDir;
7667       strDir.Format("%ld/", m_pDS->fv("directorlinkmusicvideo.idDirector").get_asInt());
7668       CFileItemPtr pItem(new CFileItem(m_pDS->fv("actors.strActor").get_asString()));
7669
7670       pItem->SetPath("videodb://3/5/"+ strDir);
7671       pItem->m_bIsFolder=true;
7672       items.Add(pItem);
7673       m_pDS->next();
7674     }
7675     m_pDS->close();
7676   }
7677   catch (...)
7678   {
7679     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
7680   }
7681 }
7682
7683 void CVideoDatabase::CleanDatabase(CGUIDialogProgressBarHandle* handle, const set<int>* paths, bool showProgress)
7684 {
7685   CGUIDialogProgress *progress=NULL;
7686   try
7687   {
7688     if (NULL == m_pDB.get()) return;
7689     if (NULL == m_pDS.get()) return;
7690
7691     unsigned int time = XbmcThreads::SystemClockMillis();
7692     CLog::Log(LOGNOTICE, "%s: Starting videodatabase cleanup ..", __FUNCTION__);
7693     ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::VideoLibrary, "xbmc", "OnCleanStarted");
7694
7695     BeginTransaction();
7696
7697     // find all the files
7698     CStdString sql;
7699     if (paths)
7700     {
7701       if (paths->size() == 0)
7702       {
7703         RollbackTransaction();
7704         ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::VideoLibrary, "xbmc", "OnCleanFinished");
7705         return;
7706       }
7707
7708       CStdString strPaths;
7709       for (std::set<int>::const_iterator i = paths->begin(); i != paths->end(); ++i)
7710         strPaths.AppendFormat(",%i",*i);
7711       sql = PrepareSQL("select * from files,path where files.idPath=path.idPath and path.idPath in (%s)",strPaths.Mid(1).c_str());
7712     }
7713     else
7714       sql = "select * from files, path where files.idPath = path.idPath";
7715
7716     m_pDS->query(sql.c_str());
7717     if (m_pDS->num_rows() == 0) return;
7718
7719     if (handle)
7720     {
7721       handle->SetTitle(g_localizeStrings.Get(700));
7722       handle->SetText("");
7723     }
7724     else if (showProgress)
7725     {
7726       progress = (CGUIDialogProgress *)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
7727       if (progress)
7728       {
7729         progress->SetHeading(700);
7730         progress->SetLine(0, "");
7731         progress->SetLine(1, 313);
7732         progress->SetLine(2, 330);
7733         progress->SetPercentage(0);
7734         progress->StartModal();
7735         progress->ShowProgressBar(true);
7736       }
7737     }
7738
7739     CStdString filesToDelete = "";
7740     CStdString moviesToDelete = "";
7741     CStdString episodesToDelete = "";
7742     CStdString musicVideosToDelete = "";
7743
7744     std::vector<int> movieIDs;
7745     std::vector<int> episodeIDs;
7746     std::vector<int> musicVideoIDs;
7747
7748     int total = m_pDS->num_rows();
7749     int current = 0;
7750
7751     bool bIsSource;
7752     VECSOURCES *pShares = g_settings.GetSourcesFromType("video");
7753
7754     while (!m_pDS->eof())
7755     {
7756       CStdString path = m_pDS->fv("path.strPath").get_asString();
7757       CStdString fileName = m_pDS->fv("files.strFileName").get_asString();
7758       CStdString fullPath;
7759       ConstructPath(fullPath,path,fileName);
7760
7761       // get the first stacked file
7762       if (URIUtils::IsStack(fullPath))
7763         fullPath = CStackDirectory::GetFirstStackedFile(fullPath);
7764
7765       // check if we have a internet related file that is part of a media source
7766       if (URIUtils::IsInternetStream(fullPath, true) && CUtil::GetMatchingSource(fullPath, *pShares, bIsSource) > -1)
7767       {
7768         if (!CFile::Exists(fullPath, false))
7769           filesToDelete += m_pDS->fv("files.idFile").get_asString() + ",";
7770       }
7771       else
7772       {
7773         // remove optical, internet related and non-existing files
7774         // note: this will also remove entries from previously existing media sources
7775         if (URIUtils::IsOnDVD(fullPath) || URIUtils::IsInternetStream(fullPath, true) || !CFile::Exists(fullPath, false))
7776           filesToDelete += m_pDS->fv("files.idFile").get_asString() + ",";
7777       }
7778
7779       if (!handle)
7780       {
7781         if (progress)
7782         {
7783           progress->SetPercentage(current * 100 / total);
7784           progress->Progress();
7785           if (progress->IsCanceled())
7786           {
7787             progress->Close();
7788             m_pDS->close();
7789             ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::VideoLibrary, "xbmc", "OnCleanFinished");
7790             return;
7791           }
7792         }
7793       }
7794       else
7795         handle->SetPercentage(current/(float)total*100);
7796
7797       m_pDS->next();
7798       current++;
7799     }
7800     m_pDS->close();
7801
7802     // Add any files that don't have a valid idPath entry to the filesToDelete list.
7803     sql = "select files.idFile from files where idPath not in (select idPath from path)";
7804     m_pDS->query(sql.c_str());
7805     while (!m_pDS->eof())
7806     {
7807       filesToDelete += m_pDS->fv("files.idFile").get_asString() + ",";
7808       m_pDS->next();
7809     }
7810     m_pDS->close();
7811
7812     if ( ! filesToDelete.IsEmpty() )
7813     {
7814       filesToDelete.TrimRight(",");
7815       // now grab them movies
7816       sql = PrepareSQL("select idMovie from movie where idFile in (%s)",filesToDelete.c_str());
7817       m_pDS->query(sql.c_str());
7818       while (!m_pDS->eof())
7819       {
7820         movieIDs.push_back(m_pDS->fv(0).get_asInt());
7821         moviesToDelete += m_pDS->fv(0).get_asString() + ",";
7822         m_pDS->next();
7823       }
7824       m_pDS->close();
7825       // now grab them episodes
7826       sql = PrepareSQL("select idEpisode from episode where idFile in (%s)",filesToDelete.c_str());
7827        m_pDS->query(sql.c_str());
7828       while (!m_pDS->eof())
7829       {
7830         episodeIDs.push_back(m_pDS->fv(0).get_asInt());
7831         episodesToDelete += m_pDS->fv(0).get_asString() + ",";
7832         m_pDS->next();
7833       }
7834       m_pDS->close();
7835
7836       // now grab them musicvideos
7837       sql = PrepareSQL("select idMVideo from musicvideo where idFile in (%s)",filesToDelete.c_str());
7838       m_pDS->query(sql.c_str());
7839       while (!m_pDS->eof())
7840       {
7841         musicVideoIDs.push_back(m_pDS->fv(0).get_asInt());
7842         musicVideosToDelete += m_pDS->fv(0).get_asString() + ",";
7843         m_pDS->next();
7844       }
7845       m_pDS->close();
7846     }
7847
7848     if (progress)
7849     {
7850       progress->SetPercentage(100);
7851       progress->Progress();
7852     }
7853
7854     if ( ! filesToDelete.IsEmpty() )
7855     {
7856       filesToDelete = "(" + filesToDelete + ")";
7857       CLog::Log(LOGDEBUG, "%s: Cleaning files table", __FUNCTION__);
7858       sql = "delete from files where idFile in " + filesToDelete;
7859       m_pDS->exec(sql.c_str());
7860
7861       CLog::Log(LOGDEBUG, "%s: Cleaning streamdetails table", __FUNCTION__);
7862       sql = "delete from streamdetails where idFile in " + filesToDelete;
7863       m_pDS->exec(sql.c_str());
7864
7865       CLog::Log(LOGDEBUG, "%s: Cleaning bookmark table", __FUNCTION__);
7866       sql = "delete from bookmark where idFile in " + filesToDelete;
7867       m_pDS->exec(sql.c_str());
7868
7869       CLog::Log(LOGDEBUG, "%s: Cleaning settings table", __FUNCTION__);
7870       sql = "delete from settings where idFile in " + filesToDelete;
7871       m_pDS->exec(sql.c_str());
7872
7873       CLog::Log(LOGDEBUG, "%s: Cleaning stacktimes table", __FUNCTION__);
7874       sql = "delete from stacktimes where idFile in " + filesToDelete;
7875       m_pDS->exec(sql.c_str());
7876     }
7877
7878     if ( ! moviesToDelete.IsEmpty() )
7879     {
7880       moviesToDelete = "(" + moviesToDelete.TrimRight(",") + ")";
7881
7882       CLog::Log(LOGDEBUG, "%s: Cleaning movie table", __FUNCTION__);
7883       sql = "delete from movie where idMovie in " + moviesToDelete;
7884       m_pDS->exec(sql.c_str());
7885
7886       CLog::Log(LOGDEBUG, "%s: Cleaning actorlinkmovie table", __FUNCTION__);
7887       sql = "delete from actorlinkmovie where idMovie in " + moviesToDelete;
7888       m_pDS->exec(sql.c_str());
7889
7890       CLog::Log(LOGDEBUG, "%s: Cleaning directorlinkmovie table", __FUNCTION__);
7891       sql = "delete from directorlinkmovie where idMovie in " + moviesToDelete;
7892       m_pDS->exec(sql.c_str());
7893
7894       CLog::Log(LOGDEBUG, "%s: Cleaning writerlinkmovie table", __FUNCTION__);
7895       sql = "delete from writerlinkmovie where idMovie in " + moviesToDelete;
7896       m_pDS->exec(sql.c_str());
7897
7898       CLog::Log(LOGDEBUG, "%s: Cleaning genrelinkmovie table", __FUNCTION__);
7899       sql = "delete from genrelinkmovie where idMovie in " + moviesToDelete;
7900       m_pDS->exec(sql.c_str());
7901
7902       CLog::Log(LOGDEBUG, "%s: Cleaning countrylinkmovie table", __FUNCTION__);
7903       sql = "delete from countrylinkmovie where idMovie in " + moviesToDelete;
7904       m_pDS->exec(sql.c_str());
7905
7906       CLog::Log(LOGDEBUG, "%s: Cleaning studiolinkmovie table", __FUNCTION__);
7907       sql = "delete from studiolinkmovie where idMovie in " + moviesToDelete;
7908       m_pDS->exec(sql.c_str());
7909     }
7910
7911     if ( ! episodesToDelete.IsEmpty() )
7912     {
7913       episodesToDelete = "(" + episodesToDelete.TrimRight(",") + ")";
7914
7915       CLog::Log(LOGDEBUG, "%s: Cleaning episode table", __FUNCTION__);
7916       sql = "delete from episode where idEpisode in " + episodesToDelete;
7917       m_pDS->exec(sql.c_str());
7918
7919       CLog::Log(LOGDEBUG, "%s: Cleaning actorlinkepisode table", __FUNCTION__);
7920       sql = "delete from actorlinkepisode where idEpisode in " + episodesToDelete;
7921       m_pDS->exec(sql.c_str());
7922
7923       CLog::Log(LOGDEBUG, "%s: Cleaning directorlinkepisode table", __FUNCTION__);
7924       sql = "delete from directorlinkepisode where idEpisode in " + episodesToDelete;
7925       m_pDS->exec(sql.c_str());
7926
7927       CLog::Log(LOGDEBUG, "%s: Cleaning writerlinkepisode table", __FUNCTION__);
7928       sql = "delete from writerlinkepisode where idEpisode in " + episodesToDelete;
7929       m_pDS->exec(sql.c_str());
7930     }
7931
7932     CLog::Log(LOGDEBUG, "%s: Cleaning paths that don't exist and have content set...", __FUNCTION__);
7933     sql = "select * from path where not (strContent='' and strSettings='' and strHash='' and exclude!=1)";
7934     m_pDS->query(sql.c_str());
7935     CStdString strIds;
7936     while (!m_pDS->eof())
7937     {
7938       if (!CDirectory::Exists(m_pDS->fv("path.strPath").get_asString()))
7939         strIds.AppendFormat("%i,", m_pDS->fv("path.idPath").get_asInt());
7940       m_pDS->next();
7941     }
7942     m_pDS->close();
7943     if (!strIds.IsEmpty())
7944     {
7945       strIds.TrimRight(",");
7946       sql = PrepareSQL("delete from path where idPath in (%s)",strIds.c_str());
7947       m_pDS->exec(sql.c_str());
7948       sql = PrepareSQL("delete from tvshowlinkpath where idPath in (%s)",strIds.c_str());
7949       m_pDS->exec(sql.c_str());
7950     }
7951     sql = "delete from tvshowlinkpath where idPath not in (select idPath from path)";
7952     m_pDS->exec(sql.c_str());
7953
7954     CLog::Log(LOGDEBUG, "%s: Cleaning tvshow table", __FUNCTION__);
7955     sql = "delete from tvshow where idShow not in (select idShow from tvshowlinkpath)";
7956     m_pDS->exec(sql.c_str());
7957
7958     std::vector<int> tvshowIDs;
7959     CStdString showsToDelete;
7960     sql = "select tvshow.idShow from tvshow "
7961             "join tvshowlinkpath on tvshow.idShow=tvshowlinkpath.idShow "
7962             "join path on path.idPath=tvshowlinkpath.idPath "
7963           "where tvshow.idShow not in (select idShow from episode) "
7964             "and path.strContent=''";
7965     m_pDS->query(sql.c_str());
7966     while (!m_pDS->eof())
7967     {
7968       tvshowIDs.push_back(m_pDS->fv(0).get_asInt());
7969       showsToDelete += m_pDS->fv(0).get_asString() + ",";
7970       m_pDS->next();
7971     }
7972     m_pDS->close();
7973     if (!showsToDelete.IsEmpty())
7974     {
7975       sql = "delete from tvshow where idShow in (" + showsToDelete.TrimRight(",") + ")";
7976       m_pDS->exec(sql.c_str());
7977     }
7978
7979     CLog::Log(LOGDEBUG, "%s: Cleaning actorlinktvshow table", __FUNCTION__);
7980     sql = "delete from actorlinktvshow where idShow not in (select idShow from tvshow)";
7981     m_pDS->exec(sql.c_str());
7982
7983     CLog::Log(LOGDEBUG, "%s: Cleaning directorlinktvshow table", __FUNCTION__);
7984     sql = "delete from directorlinktvshow where idShow not in (select idShow from tvshow)";
7985     m_pDS->exec(sql.c_str());
7986
7987     CLog::Log(LOGDEBUG, "%s: Cleaning tvshowlinkpath table", __FUNCTION__);
7988     sql = "delete from tvshowlinkpath where idShow not in (select idShow from tvshow)";
7989     m_pDS->exec(sql.c_str());
7990
7991     CLog::Log(LOGDEBUG, "%s: Cleaning genrelinktvshow table", __FUNCTION__);
7992     sql = "delete from genrelinktvshow where idShow not in (select idShow from tvshow)";
7993     m_pDS->exec(sql.c_str());
7994
7995     CLog::Log(LOGDEBUG, "%s: Cleaning seasons table", __FUNCTION__);
7996     sql = "delete from seasons where idShow not in (select idShow from tvshow)";
7997     m_pDS->exec(sql.c_str());
7998
7999     CLog::Log(LOGDEBUG, "%s: Cleaning movielinktvshow table", __FUNCTION__);
8000     sql = "delete from movielinktvshow where idShow not in (select idShow from tvshow)";
8001     m_pDS->exec(sql.c_str());
8002     sql = "delete from movielinktvshow where idMovie not in (select distinct idMovie from movie)";
8003     m_pDS->exec(sql.c_str());
8004
8005     if ( ! musicVideosToDelete.IsEmpty() )
8006     {
8007       musicVideosToDelete = "(" + musicVideosToDelete.TrimRight(",") + ")";
8008
8009       CLog::Log(LOGDEBUG, "%s: Cleaning musicvideo table", __FUNCTION__);
8010       sql = "delete from musicvideo where idMVideo in " + musicVideosToDelete;
8011       m_pDS->exec(sql.c_str());
8012
8013       CLog::Log(LOGDEBUG, "%s: Cleaning artistlinkmusicvideo table", __FUNCTION__);
8014       sql = "delete from artistlinkmusicvideo where idMVideo in " + musicVideosToDelete;
8015       m_pDS->exec(sql.c_str());
8016
8017       CLog::Log(LOGDEBUG, "%s: Cleaning directorlinkmusicvideo table" ,__FUNCTION__);
8018       sql = "delete from directorlinkmusicvideo where idMVideo in " + musicVideosToDelete;
8019       m_pDS->exec(sql.c_str());
8020
8021       CLog::Log(LOGDEBUG, "%s: Cleaning genrelinkmusicvideo table" ,__FUNCTION__);
8022       sql = "delete from genrelinkmusicvideo where idMVideo in " + musicVideosToDelete;
8023       m_pDS->exec(sql.c_str());
8024
8025       CLog::Log(LOGDEBUG, "%s: Cleaning studiolinkmusicvideo table", __FUNCTION__);
8026       sql = "delete from studiolinkmusicvideo where idMVideo in " + musicVideosToDelete;
8027       m_pDS->exec(sql.c_str());
8028     }
8029
8030     CLog::Log(LOGDEBUG, "%s: Cleaning path table", __FUNCTION__);
8031     sql.Format("delete from path where strContent='' and strSettings='' and strHash='' and exclude!=1 "
8032                                   "and idPath not in (select distinct idPath from files) "
8033                                   "and idPath not in (select distinct idPath from tvshowlinkpath) "
8034                                   "and idPath not in (select distinct c%02d from movie) "
8035                                   "and idPath not in (select distinct c%02d from tvshow) "
8036                                   "and idPath not in (select distinct c%02d from episode) "
8037                                   "and idPath not in (select distinct c%02d from musicvideo)"
8038                 , VIDEODB_ID_PARENTPATHID, VIDEODB_ID_TV_PARENTPATHID, VIDEODB_ID_EPISODE_PARENTPATHID, VIDEODB_ID_MUSICVIDEO_PARENTPATHID );
8039     m_pDS->exec(sql.c_str());
8040
8041     CLog::Log(LOGDEBUG, "%s: Cleaning genre table", __FUNCTION__);
8042     sql = "delete from genre where idGenre not in (select distinct idGenre from genrelinkmovie) and idGenre not in (select distinct idGenre from genrelinktvshow) and idGenre not in (select distinct idGenre from genrelinkmusicvideo)";
8043     m_pDS->exec(sql.c_str());
8044
8045     CLog::Log(LOGDEBUG, "%s: Cleaning country table", __FUNCTION__);
8046     sql = "delete from country where idCountry not in (select distinct idCountry from countrylinkmovie)";
8047     m_pDS->exec(sql.c_str());
8048
8049     CLog::Log(LOGDEBUG, "%s: Cleaning actor table of actors, directors and writers", __FUNCTION__);
8050     sql = "delete from actors where idActor not in (select distinct idActor from actorlinkmovie) and idActor not in (select distinct idDirector from directorlinkmovie) and idActor not in (select distinct idWriter from writerlinkmovie) and idActor not in (select distinct idActor from actorlinktvshow) and idActor not in (select distinct idActor from actorlinkepisode) and idActor not in (select distinct idDirector from directorlinktvshow) and idActor not in (select distinct idDirector from directorlinkepisode) and idActor not in (select distinct idWriter from writerlinkepisode) and idActor not in (select distinct idArtist from artistlinkmusicvideo) and idActor not in (select distinct idDirector from directorlinkmusicvideo)";
8051     m_pDS->exec(sql.c_str());
8052
8053     CLog::Log(LOGDEBUG, "%s: Cleaning studio table", __FUNCTION__);
8054     sql = "delete from studio where idStudio not in (select distinct idStudio from studiolinkmovie) and idStudio not in (select distinct idStudio from studiolinkmusicvideo) and idStudio not in (select distinct idStudio from studiolinktvshow)";
8055     m_pDS->exec(sql.c_str());
8056
8057     CLog::Log(LOGDEBUG, "%s: Cleaning set table", __FUNCTION__);
8058     sql = "delete from sets where idSet not in (select distinct idSet from movie)";
8059     m_pDS->exec(sql.c_str());
8060
8061     CommitTransaction();
8062
8063     if (handle)
8064       handle->SetTitle(g_localizeStrings.Get(331));
8065
8066     Compress(false);
8067
8068     CUtil::DeleteVideoDatabaseDirectoryCache();
8069
8070     time = XbmcThreads::SystemClockMillis() - time;
8071     CLog::Log(LOGNOTICE, "%s: Cleaning videodatabase done. Operation took %s", __FUNCTION__, StringUtils::SecondsToTimeString(time / 1000).c_str());
8072
8073     for (unsigned int i = 0; i < movieIDs.size(); i++)
8074       AnnounceRemove("movie", movieIDs[i]);
8075
8076     for (unsigned int i = 0; i < episodeIDs.size(); i++)
8077       AnnounceRemove("episode", episodeIDs[i]);
8078
8079     for (unsigned int i = 0; i < tvshowIDs.size(); i++)
8080       AnnounceRemove("tvshow", tvshowIDs[i]);
8081
8082     for (unsigned int i = 0; i < musicVideoIDs.size(); i++)
8083       AnnounceRemove("musicvideo", musicVideoIDs[i]);
8084   }
8085   catch (...)
8086   {
8087     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
8088   }
8089   if (progress)
8090     progress->Close();
8091
8092   ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::VideoLibrary, "xbmc", "OnCleanFinished");
8093 }
8094
8095 void CVideoDatabase::DumpToDummyFiles(const CStdString &path)
8096 {
8097   // get all tvshows
8098   CFileItemList items;
8099   GetTvShowsByWhere("videodb://2/2/", "", items);
8100   CStdString showPath = URIUtils::AddFileToFolder(path, "shows");
8101   CDirectory::Create(showPath);
8102   for (int i = 0; i < items.Size(); i++)
8103   {
8104     // create a folder in this directory
8105     CStdString showName = CUtil::MakeLegalFileName(items[i]->GetVideoInfoTag()->m_strShowTitle);
8106     CStdString TVFolder;
8107     URIUtils::AddFileToFolder(showPath, showName, TVFolder);
8108     if (CDirectory::Create(TVFolder))
8109     { // right - grab the episodes and dump them as well
8110       CFileItemList episodes;
8111       Filter filter(PrepareSQL("idShow=%i", items[i]->GetVideoInfoTag()->m_iDbId));
8112       GetEpisodesByWhere("videodb://2/2/", filter, episodes);
8113       for (int i = 0; i < episodes.Size(); i++)
8114       {
8115         CVideoInfoTag *tag = episodes[i]->GetVideoInfoTag();
8116         CStdString episode;
8117         episode.Format("%s.s%02de%02d.avi", showName.c_str(), tag->m_iSeason, tag->m_iEpisode);
8118         // and make a file
8119         CStdString episodePath;
8120         URIUtils::AddFileToFolder(TVFolder, episode, episodePath);
8121         CFile file;
8122         if (file.OpenForWrite(episodePath))
8123           file.Close();
8124       }
8125     }
8126   }
8127   // get all movies
8128   items.Clear();
8129   GetMoviesByWhere("videodb://1/2/", "", items);
8130   CStdString moviePath = URIUtils::AddFileToFolder(path, "movies");
8131   CDirectory::Create(moviePath);
8132   for (int i = 0; i < items.Size(); i++)
8133   {
8134     CVideoInfoTag *tag = items[i]->GetVideoInfoTag();
8135     CStdString movie;
8136     movie.Format("%s.avi", tag->m_strTitle.c_str());
8137     CFile file;
8138     if (file.OpenForWrite(URIUtils::AddFileToFolder(moviePath, movie)))
8139       file.Close();
8140   }
8141 }
8142
8143 void CVideoDatabase::ExportToXML(const CStdString &path, bool singleFiles /* = false */, bool images /* = false */, bool actorThumbs /* false */, bool overwrite /*=false*/)
8144 {
8145   CGUIDialogProgress *progress=NULL;
8146   try
8147   {
8148     if (NULL == m_pDB.get()) return;
8149     if (NULL == m_pDS.get()) return;
8150     if (NULL == m_pDS2.get()) return;
8151
8152     // create a 3rd dataset as well as GetEpisodeDetails() etc. uses m_pDS2, and we need to do 3 nested queries on tv shows
8153     auto_ptr<Dataset> pDS;
8154     pDS.reset(m_pDB->CreateDataset());
8155     if (NULL == pDS.get()) return;
8156
8157     auto_ptr<Dataset> pDS2;
8158     pDS2.reset(m_pDB->CreateDataset());
8159     if (NULL == pDS2.get()) return;
8160
8161     // if we're exporting to a single folder, we export thumbs as well
8162     CStdString exportRoot = URIUtils::AddFileToFolder(path, "xbmc_videodb_" + CDateTime::GetCurrentDateTime().GetAsDBDate());
8163     CStdString xmlFile = URIUtils::AddFileToFolder(exportRoot, "videodb.xml");
8164     CStdString actorsDir = URIUtils::AddFileToFolder(exportRoot, "actors");
8165     CStdString moviesDir = URIUtils::AddFileToFolder(exportRoot, "movies");
8166     CStdString musicvideosDir = URIUtils::AddFileToFolder(exportRoot, "musicvideos");
8167     CStdString tvshowsDir = URIUtils::AddFileToFolder(exportRoot, "tvshows");
8168     if (!singleFiles)
8169     {
8170       images = true;
8171       overwrite = false;
8172       actorThumbs = true;
8173       CDirectory::Remove(exportRoot);
8174       CDirectory::Create(exportRoot);
8175       CDirectory::Create(actorsDir);
8176       CDirectory::Create(moviesDir);
8177       CDirectory::Create(musicvideosDir);
8178       CDirectory::Create(tvshowsDir);
8179     }
8180
8181     progress = (CGUIDialogProgress *)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
8182     // find all movies
8183     CStdString sql = "select * from movieview";
8184
8185     m_pDS->query(sql.c_str());
8186
8187     if (progress)
8188     {
8189       progress->SetHeading(647);
8190       progress->SetLine(0, 650);
8191       progress->SetLine(1, "");
8192       progress->SetLine(2, "");
8193       progress->SetPercentage(0);
8194       progress->StartModal();
8195       progress->ShowProgressBar(true);
8196     }
8197
8198     int total = m_pDS->num_rows();
8199     int current = 0;
8200
8201     // create our xml document
8202     CXBMCTinyXML xmlDoc;
8203     TiXmlDeclaration decl("1.0", "UTF-8", "yes");
8204     xmlDoc.InsertEndChild(decl);
8205     TiXmlNode *pMain = NULL;
8206     if (singleFiles)
8207       pMain = &xmlDoc;
8208     else
8209     {
8210       TiXmlElement xmlMainElement("videodb");
8211       pMain = xmlDoc.InsertEndChild(xmlMainElement);
8212       XMLUtils::SetInt(pMain,"version", GetExportVersion());
8213     }
8214
8215     while (!m_pDS->eof())
8216     {
8217       CVideoInfoTag movie = GetDetailsForMovie(m_pDS, true);
8218       // strip paths to make them relative
8219       if (movie.m_strTrailer.Mid(0,movie.m_strPath.size()).Equals(movie.m_strPath))
8220         movie.m_strTrailer = movie.m_strTrailer.Mid(movie.m_strPath.size());
8221       map<string, string> artwork;
8222       if (GetArtForItem(movie.m_iDbId, movie.m_type, artwork) && !singleFiles)
8223       {
8224         TiXmlElement additionalNode("art");
8225         for (map<string, string>::const_iterator i = artwork.begin(); i != artwork.end(); ++i)
8226           XMLUtils::SetString(&additionalNode, i->first.c_str(), i->second);
8227         movie.Save(pMain, "movie", true, &additionalNode);
8228       }
8229       else
8230         movie.Save(pMain, "movie", !singleFiles);
8231
8232       // reset old skip state
8233       bool bSkip = false;
8234
8235       if (progress)
8236       {
8237         progress->SetLine(1, movie.m_strTitle);
8238         progress->SetPercentage(current * 100 / total);
8239         progress->Progress();
8240         if (progress->IsCanceled())
8241         {
8242           progress->Close();
8243           m_pDS->close();
8244           return;
8245         }
8246       }
8247
8248       CFileItem item(movie.m_strFileNameAndPath,false);
8249       if (singleFiles && CUtil::SupportsWriteFileOperations(movie.m_strFileNameAndPath))
8250       {
8251         if (!item.Exists(false))
8252         {
8253           CLog::Log(LOGDEBUG, "%s - Not exporting item %s as it does not exist", __FUNCTION__, movie.m_strFileNameAndPath.c_str());
8254           bSkip = true;
8255         }
8256         else
8257         {
8258           CStdString nfoFile(URIUtils::ReplaceExtension(item.GetTBNFile(), ".nfo"));
8259
8260           if (item.IsOpticalMediaFile())
8261           {
8262             nfoFile = URIUtils::GetParentFolderURI(nfoFile, true);
8263           }
8264
8265           if (overwrite || !CFile::Exists(nfoFile, false))
8266           {
8267             if(!xmlDoc.SaveFile(nfoFile))
8268             {
8269               CLog::Log(LOGERROR, "%s: Movie nfo export failed! ('%s')", __FUNCTION__, nfoFile.c_str());
8270               bSkip = ExportSkipEntry(nfoFile);
8271               if (!bSkip)
8272               {
8273                 if (progress)
8274                 {
8275                   progress->Close();
8276                   m_pDS->close();
8277                   return;
8278                 }
8279               }
8280             }
8281           }
8282         }
8283
8284         xmlDoc.Clear();
8285         TiXmlDeclaration decl("1.0", "UTF-8", "yes");
8286         xmlDoc.InsertEndChild(decl);
8287       }
8288
8289       if (images && !bSkip)
8290       {
8291         if (!singleFiles)
8292         {
8293           CStdString strFileName(movie.m_strTitle);
8294           if (movie.m_iYear > 0)
8295             strFileName.AppendFormat("_%i", movie.m_iYear);
8296           item.SetPath(GetSafeFile(moviesDir, strFileName) + ".avi");
8297         }
8298         for (map<string, string>::const_iterator i = artwork.begin(); i != artwork.end(); ++i)
8299         {
8300           CStdString savedThumb = item.GetLocalArt(i->first, false);
8301           CTextureCache::Get().Export(i->second, savedThumb, overwrite);
8302         }
8303         if (actorThumbs)
8304           ExportActorThumbs(actorsDir, movie, singleFiles, overwrite);
8305       }
8306       m_pDS->next();
8307       current++;
8308     }
8309     m_pDS->close();
8310
8311     // find all musicvideos
8312     sql = "select * from musicvideoview";
8313
8314     m_pDS->query(sql.c_str());
8315
8316     total = m_pDS->num_rows();
8317     current = 0;
8318
8319     while (!m_pDS->eof())
8320     {
8321       CVideoInfoTag movie = GetDetailsForMusicVideo(m_pDS, true);
8322       map<string, string> artwork;
8323       if (GetArtForItem(movie.m_iDbId, movie.m_type, artwork) && !singleFiles)
8324       {
8325         TiXmlElement additionalNode("art");
8326         for (map<string, string>::const_iterator i = artwork.begin(); i != artwork.end(); ++i)
8327           XMLUtils::SetString(&additionalNode, i->first.c_str(), i->second);
8328         movie.Save(pMain, "musicvideo", true, &additionalNode);
8329       }
8330       else
8331         movie.Save(pMain, "musicvideo", !singleFiles);
8332
8333       // reset old skip state
8334       bool bSkip = false;
8335
8336       if (progress)
8337       {
8338         progress->SetLine(1, movie.m_strTitle);
8339         progress->SetPercentage(current * 100 / total);
8340         progress->Progress();
8341         if (progress->IsCanceled())
8342         {
8343           progress->Close();
8344           m_pDS->close();
8345           return;
8346         }
8347       }
8348
8349       CFileItem item(movie.m_strFileNameAndPath,false);
8350       if (singleFiles && CUtil::SupportsWriteFileOperations(movie.m_strFileNameAndPath))
8351       {
8352         if (!item.Exists(false))
8353         {
8354           CLog::Log(LOGDEBUG, "%s - Not exporting item %s as it does not exist", __FUNCTION__, movie.m_strFileNameAndPath.c_str());
8355           bSkip = true;
8356         }
8357         else
8358         {
8359           CStdString nfoFile(URIUtils::ReplaceExtension(item.GetTBNFile(), ".nfo"));
8360
8361           if (overwrite || !CFile::Exists(nfoFile, false))
8362           {
8363             if(!xmlDoc.SaveFile(nfoFile))
8364             {
8365               CLog::Log(LOGERROR, "%s: Musicvideo nfo export failed! ('%s')", __FUNCTION__, nfoFile.c_str());
8366               bSkip = ExportSkipEntry(nfoFile);
8367               if (!bSkip)
8368               {
8369                 if (progress)
8370                 {
8371                   progress->Close();
8372                   m_pDS->close();
8373                   return;
8374                 }
8375               }
8376             }
8377           }
8378         }
8379
8380         xmlDoc.Clear();
8381         TiXmlDeclaration decl("1.0", "UTF-8", "yes");
8382         xmlDoc.InsertEndChild(decl);
8383       }
8384       if (images && !bSkip)
8385       {
8386         if (!singleFiles)
8387         {
8388           CStdString strFileName(StringUtils::Join(movie.m_artist, g_advancedSettings.m_videoItemSeparator) + "." + movie.m_strTitle);
8389           if (movie.m_iYear > 0)
8390             strFileName.AppendFormat("_%i", movie.m_iYear);
8391           item.SetPath(GetSafeFile(moviesDir, strFileName) + ".avi");
8392         }
8393         for (map<string, string>::const_iterator i = artwork.begin(); i != artwork.end(); ++i)
8394         {
8395           CStdString savedThumb = item.GetLocalArt(i->first, false);
8396           CTextureCache::Get().Export(i->second, savedThumb, overwrite);
8397         }
8398       }
8399       m_pDS->next();
8400       current++;
8401     }
8402     m_pDS->close();
8403
8404     // repeat for all tvshows
8405     sql = "SELECT * FROM tvshowview";
8406     m_pDS->query(sql.c_str());
8407
8408     total = m_pDS->num_rows();
8409     current = 0;
8410
8411     while (!m_pDS->eof())
8412     {
8413       CVideoInfoTag tvshow = GetDetailsForTvShow(m_pDS, true);
8414
8415       map<int, map<string, string> > seasonArt;
8416       GetTvShowSeasonArt(tvshow.m_iDbId, seasonArt);
8417
8418       map<string, string> artwork;
8419       if (GetArtForItem(tvshow.m_iDbId, tvshow.m_type, artwork) && !singleFiles)
8420       {
8421         TiXmlElement additionalNode("art");
8422         for (map<string, string>::const_iterator i = artwork.begin(); i != artwork.end(); ++i)
8423           XMLUtils::SetString(&additionalNode, i->first.c_str(), i->second);
8424         for (map<int, map<string, string> >::const_iterator i = seasonArt.begin(); i != seasonArt.end(); ++i)
8425         {
8426           TiXmlElement seasonNode("season");
8427           seasonNode.SetAttribute("num", i->first);
8428           for (map<string, string>::const_iterator j = i->second.begin(); j != i->second.end(); ++j)
8429             XMLUtils::SetString(&seasonNode, j->first.c_str(), j->second);
8430           additionalNode.InsertEndChild(seasonNode);
8431         }
8432         tvshow.Save(pMain, "tvshow", true, &additionalNode);
8433       }
8434       else
8435         tvshow.Save(pMain, "tvshow", !singleFiles);
8436
8437       // reset old skip state
8438       bool bSkip = false;
8439
8440       if (progress)
8441       {
8442         progress->SetLine(1, tvshow.m_strTitle);
8443         progress->SetPercentage(current * 100 / total);
8444         progress->Progress();
8445         if (progress->IsCanceled())
8446         {
8447           progress->Close();
8448           m_pDS->close();
8449           return;
8450         }
8451       }
8452
8453       // tvshow paths can be multipaths, and writing to a multipath is indeterminate.
8454       if (URIUtils::IsMultiPath(tvshow.m_strPath))
8455         tvshow.m_strPath = CMultiPathDirectory::GetFirstPath(tvshow.m_strPath);
8456
8457       CFileItem item(tvshow.m_strPath, true);
8458       if (singleFiles && CUtil::SupportsWriteFileOperations(tvshow.m_strPath))
8459       {
8460         if (!item.Exists(false))
8461         {
8462           CLog::Log(LOGDEBUG, "%s - Not exporting item %s as it does not exist", __FUNCTION__, tvshow.m_strPath.c_str());
8463           bSkip = true;
8464         }
8465         else
8466         {
8467           CStdString nfoFile;
8468           URIUtils::AddFileToFolder(tvshow.m_strPath, "tvshow.nfo", nfoFile);
8469
8470           if (overwrite || !CFile::Exists(nfoFile, false))
8471           {
8472             if(!xmlDoc.SaveFile(nfoFile))
8473             {
8474               CLog::Log(LOGERROR, "%s: TVShow nfo export failed! ('%s')", __FUNCTION__, nfoFile.c_str());
8475               bSkip = ExportSkipEntry(nfoFile);
8476               if (!bSkip)
8477               {
8478                 if (progress)
8479                 {
8480                   progress->Close();
8481                   m_pDS->close();
8482                   return;
8483                 }
8484               }
8485             }
8486           }
8487         }
8488
8489         xmlDoc.Clear();
8490         TiXmlDeclaration decl("1.0", "UTF-8", "yes");
8491         xmlDoc.InsertEndChild(decl);
8492       }
8493       if (images && !bSkip)
8494       {
8495         if (!singleFiles)
8496           item.SetPath(GetSafeFile(tvshowsDir, tvshow.m_strTitle));
8497
8498         for (map<string, string>::const_iterator i = artwork.begin(); i != artwork.end(); ++i)
8499         {
8500           CStdString savedThumb = item.GetLocalArt(i->first, true);
8501           CTextureCache::Get().Export(i->second, savedThumb, overwrite);
8502         }
8503
8504         if (actorThumbs)
8505           ExportActorThumbs(actorsDir, tvshow, singleFiles, overwrite);
8506
8507         // export season thumbs
8508         for (map<int, map<string, string> >::const_iterator i = seasonArt.begin(); i != seasonArt.end(); ++i)
8509         {
8510           string seasonThumb;
8511           if (i->first == -1)
8512             seasonThumb = "season-all";
8513           else if (i->first == 0)
8514             seasonThumb = "season-specials";
8515           else
8516             seasonThumb = StringUtils::Format("season%02i", i->first);
8517           for (map<string, string>::const_iterator j = i->second.begin(); j != i->second.end(); j++)
8518           {
8519             CStdString savedThumb(item.GetLocalArt(seasonThumb + "-" + j->first, true));
8520             if (!i->second.empty())
8521               CTextureCache::Get().Export(j->second, savedThumb, overwrite);
8522           }
8523         }
8524       }
8525
8526       // now save the episodes from this show
8527       sql = PrepareSQL("select * from episodeview where idShow=%i order by strFileName, idEpisode",tvshow.m_iDbId);
8528       pDS->query(sql.c_str());
8529       CStdString showDir(item.GetPath());
8530
8531       while (!pDS->eof())
8532       {
8533         CVideoInfoTag episode = GetDetailsForEpisode(pDS, true);
8534         map<string, string> artwork;
8535         if (GetArtForItem(episode.m_iDbId, "episode", artwork) && !singleFiles)
8536         {
8537           TiXmlElement additionalNode("art");
8538           for (map<string, string>::const_iterator i = artwork.begin(); i != artwork.end(); ++i)
8539             XMLUtils::SetString(&additionalNode, i->first.c_str(), i->second);
8540           episode.Save(pMain->LastChild(), "episodedetails", true, &additionalNode);
8541         }
8542         else if (!singleFiles)
8543           episode.Save(pMain->LastChild(), "episodedetails", !singleFiles);
8544         else
8545           episode.Save(pMain, "episodedetails", !singleFiles);
8546         pDS->next();
8547         // multi-episode files need dumping to the same XML
8548         while (singleFiles && !pDS->eof() &&
8549                episode.m_iFileId == pDS->fv("idFile").get_asInt())
8550         {
8551           episode = GetDetailsForEpisode(pDS, true);
8552           episode.Save(pMain, "episodedetails", !singleFiles);
8553           pDS->next();
8554         }
8555
8556         // reset old skip state
8557         bool bSkip = false;
8558
8559         CFileItem item(episode.m_strFileNameAndPath, false);
8560         if (singleFiles && CUtil::SupportsWriteFileOperations(episode.m_strFileNameAndPath))
8561         {
8562           if (!item.Exists(false))
8563           {
8564             CLog::Log(LOGDEBUG, "%s - Not exporting item %s as it does not exist", __FUNCTION__, episode.m_strFileNameAndPath.c_str());
8565             bSkip = true;
8566           }
8567           else
8568           {
8569             CStdString nfoFile(URIUtils::ReplaceExtension(item.GetTBNFile(), ".nfo"));
8570
8571             if (overwrite || !CFile::Exists(nfoFile, false))
8572             {
8573               if(!xmlDoc.SaveFile(nfoFile))
8574               {
8575                 CLog::Log(LOGERROR, "%s: Episode nfo export failed! ('%s')", __FUNCTION__, nfoFile.c_str());
8576                 bSkip = ExportSkipEntry(nfoFile);
8577                 if (!bSkip)
8578                 {
8579                   if (progress)
8580                   {
8581                     progress->Close();
8582                     m_pDS->close();
8583                     return;
8584                   }
8585                 }
8586               }
8587             }
8588           }
8589
8590           xmlDoc.Clear();
8591           TiXmlDeclaration decl("1.0", "UTF-8", "yes");
8592           xmlDoc.InsertEndChild(decl);
8593         }
8594
8595         if (images && !bSkip)
8596         {
8597           if (!singleFiles)
8598           {
8599             CStdString epName;
8600             epName.Format("s%02ie%02i.avi", episode.m_iSeason, episode.m_iEpisode);
8601             item.SetPath(URIUtils::AddFileToFolder(showDir, epName));
8602           }
8603           for (map<string, string>::const_iterator i = artwork.begin(); i != artwork.end(); ++i)
8604           {
8605             CStdString savedThumb = item.GetLocalArt(i->first, false);
8606             CTextureCache::Get().Export(i->second, savedThumb, overwrite);
8607           }
8608           if (actorThumbs)
8609             ExportActorThumbs(actorsDir, episode, singleFiles, overwrite);
8610         }
8611       }
8612       pDS->close();
8613       m_pDS->next();
8614       current++;
8615     }
8616     m_pDS->close();
8617
8618     if (singleFiles && progress)
8619     {
8620       progress->SetPercentage(100);
8621       progress->Progress();
8622     }
8623
8624     if (!singleFiles)
8625     {
8626       // now dump path info
8627       set<CStdString> paths;
8628       GetPaths(paths);
8629       TiXmlElement xmlPathElement("paths");
8630       TiXmlNode *pPaths = pMain->InsertEndChild(xmlPathElement);
8631       for( set<CStdString>::iterator iter = paths.begin(); iter != paths.end(); ++iter)
8632       {
8633         bool foundDirectly = false;
8634         SScanSettings settings;
8635         ScraperPtr info = GetScraperForPath(*iter, settings, foundDirectly);
8636         if (info && foundDirectly)
8637         {
8638           TiXmlElement xmlPathElement2("path");
8639           TiXmlNode *pPath = pPaths->InsertEndChild(xmlPathElement2);
8640           XMLUtils::SetString(pPath,"url", *iter);
8641           XMLUtils::SetInt(pPath,"scanrecursive", settings.recurse);
8642           XMLUtils::SetBoolean(pPath,"usefoldernames", settings.parent_name);
8643           XMLUtils::SetString(pPath,"content", TranslateContent(info->Content()));
8644           XMLUtils::SetString(pPath,"scraperpath", info->ID());
8645         }
8646       }
8647       xmlDoc.SaveFile(xmlFile);
8648     }
8649   }
8650   catch (...)
8651   {
8652     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
8653   }
8654
8655   if (progress)
8656     progress->Close();
8657 }
8658
8659 void CVideoDatabase::ExportActorThumbs(const CStdString &strDir, const CVideoInfoTag &tag, bool singleFiles, bool overwrite /*=false*/)
8660 {
8661   CStdString strPath(strDir);
8662   if (singleFiles)
8663   {
8664     strPath = URIUtils::AddFileToFolder(tag.m_strPath, ".actors");
8665     if (!CDirectory::Exists(strPath))
8666     {
8667       CDirectory::Create(strPath);
8668       CFile::SetHidden(strPath, true);
8669     }
8670   }
8671
8672   for (CVideoInfoTag::iCast iter = tag.m_cast.begin();iter != tag.m_cast.end();++iter)
8673   {
8674     CFileItem item;
8675     item.SetLabel(iter->strName);
8676     if (!iter->thumb.IsEmpty())
8677     {
8678       CStdString thumbFile(GetSafeFile(strPath, iter->strName));
8679       CTextureCache::Get().Export(iter->thumb, thumbFile, overwrite);
8680     }
8681   }
8682 }
8683
8684 bool CVideoDatabase::ExportSkipEntry(const CStdString &nfoFile)
8685 {
8686   CStdString strParent;
8687   URIUtils::GetParentPath(nfoFile,strParent);
8688   CLog::Log(LOGERROR, "%s: Unable to write to '%s'!", __FUNCTION__, strParent.c_str());
8689
8690   bool bSkip = CGUIDialogYesNo::ShowAndGetInput(g_localizeStrings.Get(647), g_localizeStrings.Get(20302), strParent.c_str(), g_localizeStrings.Get(20303));
8691
8692   if (bSkip)
8693     CLog::Log(LOGERROR, "%s: Skipping export of '%s' as requested", __FUNCTION__, nfoFile.c_str());
8694   else
8695     CLog::Log(LOGERROR, "%s: Export failed! Canceling as requested", __FUNCTION__);
8696
8697   return bSkip;
8698 }
8699
8700 void CVideoDatabase::ImportFromXML(const CStdString &path)
8701 {
8702   CGUIDialogProgress *progress=NULL;
8703   try
8704   {
8705     if (NULL == m_pDB.get()) return;
8706     if (NULL == m_pDS.get()) return;
8707
8708     CXBMCTinyXML xmlDoc;
8709     if (!xmlDoc.LoadFile(URIUtils::AddFileToFolder(path, "videodb.xml")))
8710       return;
8711
8712     TiXmlElement *root = xmlDoc.RootElement();
8713     if (!root) return;
8714
8715     progress = (CGUIDialogProgress *)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
8716     if (progress)
8717     {
8718       progress->SetHeading(648);
8719       progress->SetLine(0, 649);
8720       progress->SetLine(1, 330);
8721       progress->SetLine(2, "");
8722       progress->SetPercentage(0);
8723       progress->StartModal();
8724       progress->ShowProgressBar(true);
8725     }
8726
8727     int iVersion = 0;
8728     XMLUtils::GetInt(root, "version", iVersion);
8729
8730     CLog::Log(LOGDEBUG, "%s: Starting import (export version = %i)", __FUNCTION__, iVersion);
8731
8732     TiXmlElement *movie = root->FirstChildElement();
8733     int current = 0;
8734     int total = 0;
8735     // first count the number of items...
8736     while (movie)
8737     {
8738       if (strnicmp(movie->Value(), "movie", 5)==0 ||
8739           strnicmp(movie->Value(), "tvshow", 6)==0 ||
8740           strnicmp(movie->Value(), "musicvideo",10)==0 )
8741         total++;
8742       movie = movie->NextSiblingElement();
8743     }
8744
8745     CStdString actorsDir(URIUtils::AddFileToFolder(path, "actors"));
8746     CStdString moviesDir(URIUtils::AddFileToFolder(path, "movies"));
8747     CStdString musicvideosDir(URIUtils::AddFileToFolder(path, "musicvideos"));
8748     CStdString tvshowsDir(URIUtils::AddFileToFolder(path, "tvshows"));
8749     CVideoInfoScanner scanner;
8750     // add paths first (so we have scraper settings available)
8751     TiXmlElement *path = root->FirstChildElement("paths");
8752     path = path->FirstChildElement();
8753     while (path)
8754     {
8755       CStdString strPath;
8756       if (XMLUtils::GetString(path,"url",strPath))
8757         AddPath(strPath);
8758
8759       CStdString content;
8760       if (XMLUtils::GetString(path,"content", content))
8761       { // check the scraper exists, if so store the path
8762         AddonPtr addon;
8763         CStdString id;
8764         XMLUtils::GetString(path,"scraperpath",id);
8765         if (CAddonMgr::Get().GetAddon(id, addon))
8766         {
8767           SScanSettings settings;
8768           ScraperPtr scraper = boost::dynamic_pointer_cast<CScraper>(addon);
8769           // FIXME: scraper settings are not exported?
8770           scraper->SetPathSettings(TranslateContent(content), "");
8771           XMLUtils::GetInt(path,"scanrecursive",settings.recurse);
8772           XMLUtils::GetBoolean(path,"usefoldernames",settings.parent_name);
8773           SetScraperForPath(strPath,scraper,settings);
8774         }
8775       }
8776       path = path->NextSiblingElement();
8777     }
8778     movie = root->FirstChildElement();
8779     while (movie)
8780     {
8781       CVideoInfoTag info;
8782       if (strnicmp(movie->Value(), "movie", 5) == 0)
8783       {
8784         info.Load(movie);
8785         CFileItem item(info);
8786         bool useFolders = info.m_basePath.IsEmpty() ? LookupByFolders(item.GetPath()) : false;
8787         CStdString filename = info.m_strTitle;
8788         if (info.m_iYear > 0)
8789           filename.AppendFormat("_%i", info.m_iYear);
8790         CFileItem artItem(item);
8791         artItem.SetPath(GetSafeFile(moviesDir, filename) + ".avi");
8792         scanner.GetArtwork(&artItem, CONTENT_MOVIES, useFolders, true, actorsDir);
8793         item.SetArt(artItem.GetArt());
8794         scanner.AddVideo(&item, CONTENT_MOVIES, useFolders, true, NULL, true);
8795         current++;
8796       }
8797       else if (strnicmp(movie->Value(), "musicvideo", 10) == 0)
8798       {
8799         info.Load(movie);
8800         CFileItem item(info);
8801         bool useFolders = info.m_basePath.IsEmpty() ? LookupByFolders(item.GetPath()) : false;
8802         CStdString filename = StringUtils::Join(info.m_artist, g_advancedSettings.m_videoItemSeparator) + "." + info.m_strTitle;
8803         if (info.m_iYear > 0)
8804           filename.AppendFormat("_%i", info.m_iYear);
8805         CFileItem artItem(item);
8806         artItem.SetPath(GetSafeFile(musicvideosDir, filename) + ".avi");
8807         scanner.GetArtwork(&artItem, CONTENT_MOVIES, useFolders, true, actorsDir);
8808         item.SetArt(artItem.GetArt());
8809         scanner.AddVideo(&item, CONTENT_MUSICVIDEOS, useFolders, true, NULL, true);
8810         current++;
8811       }
8812       else if (strnicmp(movie->Value(), "tvshow", 6) == 0)
8813       {
8814         // load the TV show in.  NOTE: This deletes all episodes under the TV Show, which may not be
8815         // what we desire.  It may make better sense to only delete (or even better, update) the show information
8816         info.Load(movie);
8817         URIUtils::AddSlashAtEnd(info.m_strPath);
8818         DeleteTvShow(info.m_strPath);
8819         CFileItem showItem(info);
8820         bool useFolders = info.m_basePath.IsEmpty() ? LookupByFolders(showItem.GetPath(), true) : false;
8821         CFileItem artItem(showItem);
8822         CStdString artPath(GetSafeFile(tvshowsDir, info.m_strTitle));
8823         artItem.SetPath(artPath);
8824         scanner.GetArtwork(&artItem, CONTENT_MOVIES, useFolders, true, actorsDir);
8825         showItem.SetArt(artItem.GetArt());
8826         int showID = scanner.AddVideo(&showItem, CONTENT_TVSHOWS, useFolders, true, NULL, true);
8827         // season artwork
8828         map<int, map<string, string> > seasonArt;
8829         artItem.GetVideoInfoTag()->m_strPath = artPath;
8830         scanner.GetSeasonThumbs(*artItem.GetVideoInfoTag(), seasonArt, CVideoThumbLoader::GetArtTypes("season"), true);
8831         for (map<int, map<string, string> >::iterator i = seasonArt.begin(); i != seasonArt.end(); ++i)
8832         {
8833           int seasonID = AddSeason(showID, i->first);
8834           SetArtForItem(seasonID, "season", i->second);
8835         }
8836         current++;
8837         // now load the episodes
8838         TiXmlElement *episode = movie->FirstChildElement("episodedetails");
8839         while (episode)
8840         {
8841           // no need to delete the episode info, due to the above deletion
8842           CVideoInfoTag info;
8843           info.Load(episode);
8844           CFileItem item(info);
8845           CStdString filename;
8846           filename.Format("s%02ie%02i.avi", info.m_iSeason, info.m_iEpisode);
8847           CFileItem artItem(item);
8848           artItem.SetPath(GetSafeFile(artPath, filename));
8849           scanner.GetArtwork(&artItem, CONTENT_MOVIES, useFolders, true, actorsDir);
8850           item.SetArt(artItem.GetArt());
8851           scanner.AddVideo(&item,CONTENT_TVSHOWS, false, false, showItem.GetVideoInfoTag(), true);
8852           episode = episode->NextSiblingElement("episodedetails");
8853         }
8854       }
8855       movie = movie->NextSiblingElement();
8856       if (progress && total)
8857       {
8858         progress->SetPercentage(current * 100 / total);
8859         progress->SetLine(2, info.m_strTitle);
8860         progress->Progress();
8861         if (progress->IsCanceled())
8862         {
8863           progress->Close();
8864           RollbackTransaction();
8865           return;
8866         }
8867       }
8868     }
8869   }
8870   catch (...)
8871   {
8872     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
8873   }
8874   if (progress)
8875     progress->Close();
8876 }
8877
8878 bool CVideoDatabase::ImportArtFromXML(const TiXmlNode *node, map<string, string> &artwork)
8879 {
8880   if (!node) return false;
8881   const TiXmlNode *art = node->FirstChild();
8882   while (art && art->FirstChild())
8883   {
8884     artwork.insert(make_pair(art->ValueStr(), art->FirstChild()->ValueStr()));
8885     art = art->NextSibling();
8886   }
8887   return !artwork.empty();
8888 }
8889
8890 void CVideoDatabase::ConstructPath(CStdString& strDest, const CStdString& strPath, const CStdString& strFileName)
8891 {
8892   if (URIUtils::IsStack(strFileName) || 
8893       URIUtils::IsInArchive(strFileName) || URIUtils::IsPlugin(strPath))
8894     strDest = strFileName;
8895   else
8896     URIUtils::AddFileToFolder(strPath, strFileName, strDest);
8897 }
8898
8899 void CVideoDatabase::SplitPath(const CStdString& strFileNameAndPath, CStdString& strPath, CStdString& strFileName)
8900 {
8901   if (URIUtils::IsStack(strFileNameAndPath) || strFileNameAndPath.Mid(0,6).Equals("rar://") || strFileNameAndPath.Mid(0,6).Equals("zip://"))
8902   {
8903     URIUtils::GetParentPath(strFileNameAndPath,strPath);
8904     strFileName = strFileNameAndPath;
8905   }
8906   else if (URIUtils::IsPlugin(strFileNameAndPath))
8907   {
8908     CURL url(strFileNameAndPath);
8909     strPath = url.GetWithoutFilename();
8910     strFileName = strFileNameAndPath;
8911   }
8912   else
8913     URIUtils::Split(strFileNameAndPath,strPath, strFileName);
8914 }
8915
8916 void CVideoDatabase::InvalidatePathHash(const CStdString& strPath)
8917 {
8918   SScanSettings settings;
8919   bool foundDirectly;
8920   ScraperPtr info = GetScraperForPath(strPath,settings,foundDirectly);
8921   SetPathHash(strPath,"");
8922   if (!info)
8923     return;
8924   if (info->Content() == CONTENT_TVSHOWS || (info->Content() == CONTENT_MOVIES && !foundDirectly)) // if we scan by folder name we need to invalidate parent as well
8925   {
8926     if (info->Content() == CONTENT_TVSHOWS || settings.parent_name_root)
8927     {
8928       CStdString strParent;
8929       URIUtils::GetParentPath(strPath,strParent);
8930       SetPathHash(strParent,"");
8931     }
8932   }
8933 }
8934
8935 bool CVideoDatabase::CommitTransaction()
8936 {
8937   if (CDatabase::CommitTransaction())
8938   { // number of items in the db has likely changed, so recalculate
8939     g_infoManager.SetLibraryBool(LIBRARY_HAS_MOVIES, HasContent(VIDEODB_CONTENT_MOVIES));
8940     g_infoManager.SetLibraryBool(LIBRARY_HAS_TVSHOWS, HasContent(VIDEODB_CONTENT_TVSHOWS));
8941     g_infoManager.SetLibraryBool(LIBRARY_HAS_MUSICVIDEOS, HasContent(VIDEODB_CONTENT_MUSICVIDEOS));
8942     return true;
8943   }
8944   return false;
8945 }
8946
8947 void CVideoDatabase::SetDetail(const CStdString& strDetail, int id, int field,
8948                                VIDEODB_CONTENT_TYPE type)
8949 {
8950   try
8951   {
8952     if (NULL == m_pDB.get()) return;
8953     if (NULL == m_pDS.get()) return;
8954
8955     CStdString strTable, strField;
8956     if (type == VIDEODB_CONTENT_MOVIES)
8957     {
8958       strTable = "movie";
8959       strField = "idMovie";
8960     }
8961     if (type == VIDEODB_CONTENT_TVSHOWS)
8962     {
8963       strTable = "tvshow";
8964       strField = "idShow";
8965     }
8966     if (type == VIDEODB_CONTENT_MUSICVIDEOS)
8967     {
8968       strTable = "musicvideo";
8969       strField = "idMVideo";
8970     }
8971
8972     if (strTable.IsEmpty())
8973       return;
8974
8975     CStdString strSQL = PrepareSQL("update %s set c%02u='%s' where %s=%u",
8976                                   strTable.c_str(), field, strDetail.c_str(), strField.c_str(), id);
8977     m_pDS->exec(strSQL.c_str());
8978   }
8979   catch (...)
8980   {
8981     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
8982   }
8983 }
8984
8985 CStdString CVideoDatabase::GetSafeFile(const CStdString &dir, const CStdString &name) const
8986 {
8987   CStdString safeThumb(name);
8988   safeThumb.Replace(' ', '_');
8989   return URIUtils::AddFileToFolder(dir, CUtil::MakeLegalFileName(safeThumb));
8990 }
8991
8992 void CVideoDatabase::AnnounceRemove(std::string content, int id)
8993 {
8994   CVariant data;
8995   data["type"] = content;
8996   data["id"] = id;
8997   ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::VideoLibrary, "xbmc", "OnRemove", data);
8998 }
8999
9000 void CVideoDatabase::AnnounceUpdate(std::string content, int id)
9001 {
9002   CVariant data;
9003   data["type"] = content;
9004   data["id"] = id;
9005   ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::VideoLibrary, "xbmc", "OnUpdate", data);
9006 }
9007
9008 bool CVideoDatabase::GetItemsForPath(const CStdString &content, const CStdString &strPath, CFileItemList &items)
9009 {
9010   CStdString path(strPath);
9011   
9012   if(URIUtils::IsMultiPath(path))
9013   {
9014     vector<CStdString> paths;
9015     CMultiPathDirectory::GetPaths(path, paths);
9016
9017     for(unsigned i=0;i<paths.size();i++)
9018       GetItemsForPath(content, paths[i], items);
9019
9020     return items.Size() > 0;
9021   }
9022   
9023   int pathID = GetPathId(path);
9024   if (pathID < 0)
9025     return false;
9026
9027   if (content == "movies")
9028   {
9029     Filter filter(PrepareSQL("c%02d=%d", VIDEODB_ID_PARENTPATHID, pathID));
9030     GetMoviesByWhere("videodb://1/2/", filter, items);
9031   }
9032   else if (content == "episodes")
9033   {
9034     Filter filter(PrepareSQL("c%02d=%d", VIDEODB_ID_EPISODE_PARENTPATHID, pathID));
9035     GetEpisodesByWhere("videodb://2/2/", filter, items);
9036   }
9037   else if (content == "tvshows")
9038   {
9039     Filter filter(PrepareSQL("c%02d=%d", VIDEODB_ID_TV_PARENTPATHID, pathID));
9040     GetTvShowsByWhere("videodb://2/2/", filter, items);
9041   }
9042   else if (content == "musicvideos")
9043   {
9044     Filter filter(PrepareSQL("c%02d=%d", VIDEODB_ID_MUSICVIDEO_PARENTPATHID, pathID));
9045     GetMusicVideosByWhere("videodb://3/2/", filter, items);
9046   }
9047   for (int i = 0; i < items.Size(); i++)
9048     items[i]->SetPath(items[i]->GetVideoInfoTag()->m_basePath);
9049   return items.Size() > 0;
9050 }
9051
9052 bool CVideoDatabase::GetFilter(CDbUrl &videoUrl, Filter &filter, SortDescription &sorting)
9053 {
9054   if (!videoUrl.IsValid())
9055     return false;
9056
9057   std::string type = videoUrl.GetType();
9058   std::string itemType = ((const CVideoDbUrl &)videoUrl).GetItemType();
9059   const CUrlOptions::UrlOptions& options = videoUrl.GetOptions();
9060   CUrlOptions::UrlOptions::const_iterator option;
9061
9062   if (type == "movies")
9063   {
9064     option = options.find("genreid");
9065     if (option != options.end())
9066     {
9067       filter.AppendJoin(PrepareSQL("join genrelinkmovie on genrelinkmovie.idMovie = movieview.idMovie"));
9068       filter.AppendWhere(PrepareSQL("genrelinkmovie.idGenre = %i", (int)option->second.asInteger()));
9069     }
9070
9071     option = options.find("genre");
9072     if (option != options.end())
9073     {
9074       filter.AppendJoin(PrepareSQL("join genrelinkmovie on genrelinkmovie.idMovie = movieview.idMovie join genre on genre.idGenre = genrelinkmovie.idGenre"));
9075       filter.AppendWhere(PrepareSQL("genre.strGenre like '%s'", option->second.asString().c_str()));
9076     }
9077
9078     option = options.find("countryid");
9079     if (option != options.end())
9080     {
9081       filter.AppendJoin(PrepareSQL("join countrylinkmovie on countrylinkmovie.idMovie = movieview.idMovie"));
9082       filter.AppendWhere(PrepareSQL("countrylinkmovie.idCountry = %i", (int)option->second.asInteger()));
9083     }
9084
9085     option = options.find("country");
9086     if (option != options.end())
9087     {
9088       filter.AppendJoin(PrepareSQL("join countrylinkmovie on countrylinkmovie.idMovie = movieview.idMovie join country on country.idCountry = countrylinkmovie.idCountry"));
9089       filter.AppendWhere(PrepareSQL("country.strCountry like '%s'", option->second.asString().c_str()));
9090     }
9091
9092     option = options.find("studioid");
9093     if (option != options.end())
9094     {
9095       filter.AppendJoin(PrepareSQL("join studiolinkmovie on studiolinkmovie.idMovie = movieview.idMovie"));
9096       filter.AppendWhere(PrepareSQL("studiolinkmovie.idStudio = %i", (int)option->second.asInteger()));
9097     }
9098
9099     option = options.find("studio");
9100     if (option != options.end())
9101     {
9102       filter.AppendJoin(PrepareSQL("join studiolinkmovie on studiolinkmovie.idMovie = movieview.idMovie join studio on studio.idStudio = studiolinkmovie.idStudio"));
9103       filter.AppendWhere(PrepareSQL("studio.strStudio like '%s'", option->second.asString().c_str()));
9104     }
9105
9106     option = options.find("directorid");
9107     if (option != options.end())
9108     {
9109       filter.AppendJoin(PrepareSQL("join directorlinkmovie on directorlinkmovie.idMovie = movieview.idMovie"));
9110       filter.AppendWhere(PrepareSQL("directorlinkmovie.idDirector = %i", (int)option->second.asInteger()));
9111     }
9112
9113     option = options.find("director");
9114     if (option != options.end())
9115     {
9116       filter.AppendJoin(PrepareSQL("join directorlinkmovie on directorlinkmovie.idMovie = movieview.idMovie join actors on actors.idActor = directorlinkmovie.idDirector"));
9117       filter.AppendWhere(PrepareSQL("actors.strActor like '%s'", option->second.asString().c_str()));
9118     }
9119
9120     option = options.find("year");
9121     if (option != options.end())
9122       filter.AppendWhere(PrepareSQL("movieview.c%02d = '%i'", VIDEODB_ID_YEAR, (int)option->second.asInteger()));
9123
9124     option = options.find("actorid");
9125     if (option != options.end())
9126     {
9127       filter.AppendJoin(PrepareSQL("join actorlinkmovie on actorlinkmovie.idMovie = movieview.idMovie"));
9128       filter.AppendWhere(PrepareSQL("actorlinkmovie.idActor = %i", (int)option->second.asInteger()));
9129     }
9130
9131     option = options.find("actor");
9132     if (option != options.end())
9133     {
9134       filter.AppendJoin(PrepareSQL("join actorlinkmovie on actorlinkmovie.idMovie = movieview.idMovie join actors on actors.idActor = actorlinkmovie.idActor"));
9135       filter.AppendWhere(PrepareSQL("actors.strActor like '%s'", option->second.asString().c_str()));
9136     }
9137
9138     option = options.find("setid");
9139     if (option != options.end())
9140       filter.AppendWhere(PrepareSQL("movieview.idSet = %i", (int)option->second.asInteger()));
9141
9142     option = options.find("set");
9143     if (option != options.end())
9144     {
9145       filter.AppendJoin(PrepareSQL("join setlinkmovie on setlinkmovie.idMovie = movieview.idMovie join sets on sets.idSet = setlinkmovie.idSet"));
9146       filter.AppendWhere(PrepareSQL("sets.strSet like '%s'", option->second.asString().c_str()));
9147     }
9148
9149     option = options.find("tagid");
9150     if (option != options.end())
9151     {
9152       filter.AppendJoin(PrepareSQL("join taglinks on taglinks.idMedia = movieview.idMovie AND taglinks.media_type = 'movie'"));
9153       filter.AppendWhere(PrepareSQL("taglinks.idTag = %i", (int)option->second.asInteger()));
9154     }
9155
9156     option = options.find("tag");
9157     if (option != options.end())
9158     {
9159       filter.AppendJoin(PrepareSQL("join taglinks on taglinks.idMedia = movieview.idMovie AND taglinks.media_type = 'movie' join tag on tag.idTag = taglinks.idTag"));
9160       filter.AppendWhere(PrepareSQL("tag.strTag like '%s'", option->second.asString().c_str()));
9161     }
9162   }
9163   else if (type == "tvshows")
9164   {
9165     if (itemType == "tvshows")
9166     {
9167       option = options.find("genreid");
9168       if (option != options.end())
9169       {
9170         filter.AppendJoin(PrepareSQL("join genrelinktvshow on genrelinktvshow.idShow = tvshowview.idShow"));
9171         filter.AppendWhere(PrepareSQL("genrelinktvshow.idGenre = %i", (int)option->second.asInteger()));
9172       }
9173
9174       option = options.find("genre");
9175       if (option != options.end())
9176       {
9177         filter.AppendJoin(PrepareSQL("join genrelinktvshow on genrelinktvshow.idShow = tvshowview.idShow join genre on genre.idGenre = genrelinktvshow.idGenre"));
9178         filter.AppendWhere(PrepareSQL("genre.strGenre like '%s'", option->second.asString().c_str()));
9179       }
9180
9181       option = options.find("studioid");
9182       if (option != options.end())
9183       {
9184         filter.AppendJoin(PrepareSQL("join studiolinktvshow on studiolinktvshow.idShow = tvshowview.idShow"));
9185         filter.AppendWhere(PrepareSQL("studiolinktvshow.idStudio = %i", (int)option->second.asInteger()));
9186       }
9187
9188       option = options.find("studio");
9189       if (option != options.end())
9190       {
9191         filter.AppendJoin(PrepareSQL("join studiolinktvshow on studiolinktvshow.idShow = tvshowview.idShow join studio on studio.idStudio = studiolinktvshow.idStudio"));
9192         filter.AppendWhere(PrepareSQL("studio.strStudio like '%s'", option->second.asString().c_str()));
9193       }
9194
9195       option = options.find("directorid");
9196       if (option != options.end())
9197       {
9198         filter.AppendJoin(PrepareSQL("join directorlinktvshow on directorlinktvshow.idShow = tvshowview.idShow"));
9199         filter.AppendWhere(PrepareSQL("directorlinktvshow.idDirector = %i", (int)option->second.asInteger()));
9200       }
9201
9202       option = options.find("year");
9203       if (option != options.end())
9204         filter.AppendWhere(PrepareSQL("tvshowview.c%02d like '%%%i%%'", VIDEODB_ID_TV_PREMIERED, (int)option->second.asInteger()));
9205
9206       option = options.find("actorid");
9207       if (option != options.end())
9208       {
9209         filter.AppendJoin(PrepareSQL("join actorlinktvshow on actorlinktvshow.idShow = tvshowview.idShow"));
9210         filter.AppendWhere(PrepareSQL("actorlinktvshow.idActor = %i", (int)option->second.asInteger()));
9211       }
9212
9213       option = options.find("actor");
9214       if (option != options.end())
9215       {
9216         filter.AppendJoin(PrepareSQL("join actorlinktvshow on actorlinktvshow.idShow = tvshowview.idShow join actors on actors.idActor = actorlinktvshow.idActor"));
9217         filter.AppendWhere(PrepareSQL("actors.strActor like '%s'", option->second.asString().c_str()));
9218       }
9219
9220       option = options.find("tagid");
9221       if (option != options.end())
9222       {
9223         filter.AppendJoin(PrepareSQL("join taglinks on taglinks.idMedia = tvshowview.idShow AND taglinks.media_type = 'tvshow'"));
9224         filter.AppendWhere(PrepareSQL("taglinks.idTag = %i", (int)option->second.asInteger()));
9225       }
9226
9227       option = options.find("tag");
9228       if (option != options.end())
9229       {
9230         filter.AppendJoin(PrepareSQL("join taglinks on taglinks.idMedia = tvshowview.idShow AND taglinks.media_type = 'tvshow' join tag on tag.idTag = taglinks.idTag"));
9231         filter.AppendWhere(PrepareSQL("tag.strTag like '%s'", option->second.asString().c_str()));
9232       }
9233     }
9234     else if (itemType == "seasons")
9235     {
9236       option = options.find("genreid");
9237       if (option != options.end())
9238       {
9239         filter.AppendJoin(PrepareSQL("join genrelinktvshow on genrelinktvshow.idShow = tvshowview.idShow"));
9240         filter.AppendWhere(PrepareSQL("genrelinktvshow.idGenre = %i", (int)option->second.asInteger()));
9241       }
9242
9243       option = options.find("directorid");
9244       if (option != options.end())
9245       {
9246         filter.AppendJoin(PrepareSQL("join directorlinktvshow on directorlinktvshow.idShow = tvshowview.idShow"));
9247         filter.AppendWhere(PrepareSQL("directorlinktvshow.idDirector = %i", (int)option->second.asInteger()));
9248       }
9249       
9250       option = options.find("year");
9251       if (option != options.end())
9252         filter.AppendWhere(PrepareSQL("tvshowview.c%02d like '%%%i%%'", VIDEODB_ID_TV_PREMIERED, (int)option->second.asInteger()));
9253
9254       option = options.find("actorid");
9255       if (option != options.end())
9256       {
9257         filter.AppendJoin(PrepareSQL("join actorlinktvshow on actorlinktvshow.idShow = tvshowview.idShow"));
9258         filter.AppendWhere(PrepareSQL("actorlinktvshow.idActor = %i", (int)option->second.asInteger()));
9259       }
9260     }
9261     else if (itemType == "episodes")
9262     {
9263       int idShow = -1;
9264       option = options.find("tvshowid");
9265       if (option != options.end())
9266         idShow = (int)option->second.asInteger();
9267
9268       int season = -1;
9269       option = options.find("season");
9270       if (option != options.end())
9271         season = (int)option->second.asInteger();
9272
9273       CStdString strIn = PrepareSQL("= %i", idShow);
9274       GetStackedTvShowList(idShow, strIn);
9275
9276       if (idShow > -1)
9277       {
9278         bool condition = false;
9279
9280         option = options.find("genreid");
9281         if (option != options.end())
9282         {
9283           condition = true;
9284           filter.AppendJoin(PrepareSQL("join genrelinktvshow on genrelinktvshow.idShow = episodeview.idShow"));
9285           filter.AppendWhere(PrepareSQL("episodeview.idShow = %i and genrelinktvshow.idGenre = %i", idShow, (int)option->second.asInteger()));
9286         }
9287
9288         option = options.find("genre");
9289         if (option != options.end())
9290         {
9291           condition = true;
9292           filter.AppendJoin(PrepareSQL("join genrelinktvshow on genrelinktvshow.idShow = episodeview.idShow join genre on genre.idGenre = genrelinktvshow.idGenre"));
9293           filter.AppendWhere(PrepareSQL("episodeview.idShow = %i and genre.strGenre like '%s'", idShow, option->second.asString().c_str()));
9294         }
9295
9296         option = options.find("directorid");
9297         if (option != options.end())
9298         {
9299           condition = true;
9300           filter.AppendJoin(PrepareSQL("join directorlinktvshow on directorlinktvshow.idShow = episodeview.idShow"));
9301           filter.AppendWhere(PrepareSQL("episodeview.idShow = %i and directorlinktvshow.idDirector = %i", idShow, (int)option->second.asInteger()));
9302         }
9303
9304         option = options.find("director");
9305         if (option != options.end())
9306         {
9307           condition = true;
9308           filter.AppendJoin(PrepareSQL("join directorlinktvshow on directorlinktvshow.idShow = episodeview.idShow join actors on actors.idActor = directorlinktvshow.idDirector"));
9309           filter.AppendWhere(PrepareSQL("episodeview.idShow = %i and actors.strActor like '%s'", idShow, option->second.asString().c_str()));
9310         }
9311       
9312         option = options.find("year");
9313         if (option != options.end())
9314         {
9315           condition = true;
9316           filter.AppendWhere(PrepareSQL("episodeview.idShow = %i and episodeview.premiered like '%%%i%%'", idShow, (int)option->second.asInteger()));
9317         }
9318
9319         option = options.find("actorid");
9320         if (option != options.end())
9321         {
9322           condition = true;
9323           filter.AppendJoin(PrepareSQL("join actorlinktvshow on actorlinktvshow.idShow = episodeview.idShow"));
9324           filter.AppendWhere(PrepareSQL("episodeview.idShow = %i and actorlinktvshow.idActor = %i", idShow, (int)option->second.asInteger()));
9325         }
9326
9327         option = options.find("actor");
9328         if (option != options.end())
9329         {
9330           condition = true;
9331           filter.AppendJoin(PrepareSQL("join actorlinktvshow on actorlinktvshow.idShow = episodeview.idShow join actors on actors.idActor = actorlinktvshow.idActor"));
9332           filter.AppendWhere(PrepareSQL("episodeview.idShow = %i and actors.strActor = '%s'", idShow, option->second.asString().c_str()));
9333         }
9334
9335         if (!condition)
9336           filter.AppendWhere(PrepareSQL("episodeview.idShow %s", strIn.c_str()));
9337
9338         if (season > -1)
9339         {
9340           if (season == 0) // season = 0 indicates a special - we grab all specials here (see below)
9341             filter.AppendWhere(PrepareSQL("episodeview.c%02d = %i", VIDEODB_ID_EPISODE_SEASON, season));
9342           else
9343             filter.AppendWhere(PrepareSQL("(episodeview.c%02d = %i or (episodeview.c%02d = 0 and (episodeview.c%02d = 0 or episodeview.c%02d = %i)))",
9344               VIDEODB_ID_EPISODE_SEASON, season, VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_EPISODE_SORTSEASON, VIDEODB_ID_EPISODE_SORTSEASON, season));
9345         }
9346       }
9347       else
9348       {
9349         option = options.find("year");
9350         if (option != options.end())
9351           filter.AppendWhere(PrepareSQL("episodeview.premiered like '%%%i%%'", (int)option->second.asInteger()));
9352
9353         option = options.find("directorid");
9354         if (option != options.end())
9355         {
9356           filter.AppendJoin(PrepareSQL("join directorlinkepisode on directorlinkepisode.idEpisode = episodeview.idEpisode"));
9357           filter.AppendWhere(PrepareSQL("directorlinkepisode.idDirector = %i", (int)option->second.asInteger()));
9358         }
9359
9360         option = options.find("director");
9361         if (option != options.end())
9362         {
9363           filter.AppendJoin(PrepareSQL("join directorlinkepisode on directorlinkepisode.idEpisode = episodeview.idEpisode join actors on actors.idActor = directorlinktvshow.idDirector"));
9364           filter.AppendWhere(PrepareSQL("actors.strActor = %s", option->second.asString().c_str()));
9365         }
9366       }
9367     }
9368   }
9369   else if (type == "musicvideos")
9370   {
9371     option = options.find("genreid");
9372     if (option != options.end())
9373     {
9374       filter.AppendJoin(PrepareSQL("join genrelinkmusicvideo on genrelinkmusicvideo.idMVideo = musicvideoview.idMVideo"));
9375       filter.AppendWhere(PrepareSQL("genrelinkmusicvideo.idGenre = %i", (int)option->second.asInteger()));
9376     }
9377
9378     option = options.find("genre");
9379     if (option != options.end())
9380     {
9381       filter.AppendJoin(PrepareSQL("join genrelinkmusicvideo on genrelinkmusicvideo.idMVideo = musicvideoview.idMVideo join genre on genre.idGenre = genrelinkmusicvideo.idGenre"));
9382       filter.AppendWhere(PrepareSQL("genre.strGenre like '%s'", option->second.asString().c_str()));
9383     }
9384
9385     option = options.find("studioid");
9386     if (option != options.end())
9387     {
9388       filter.AppendJoin(PrepareSQL("join studiolinkmusicvideo on studiolinkmusicvideo.idMVideo = musicvideoview.idMVideo"));
9389       filter.AppendWhere(PrepareSQL("studiolinkmusicvideo.idStudio = %i", (int)option->second.asInteger()));
9390     }
9391
9392     option = options.find("studio");
9393     if (option != options.end())
9394     {
9395       filter.AppendJoin(PrepareSQL("join studiolinkmusicvideo on studiolinkmusicvideo.idMVideo = musicvideoview.idMVideo join studio on studio.idStudio = studiolinkmusicvideo.idStudio"));
9396       filter.AppendWhere(PrepareSQL("studio.strStudio like '%s'", option->second.asString().c_str()));
9397     }
9398
9399     option = options.find("directorid");
9400     if (option != options.end())
9401     {
9402       filter.AppendJoin(PrepareSQL("join directorlinkmusicvideo on directorlinkmusicvideo.idMVideo = musicvideoview.idMVideo"));
9403       filter.AppendWhere(PrepareSQL("directorlinkmusicvideo.idDirector = %i", (int)option->second.asInteger()));
9404     }
9405
9406     option = options.find("director");
9407     if (option != options.end())
9408     {
9409       filter.AppendJoin(PrepareSQL("join directorlinkmusicvideo on directorlinkmusicvideo.idMVideo = musicvideoview.idMVideo join actors on actors.idActor = directorlinkmusicvideo.idDirector"));
9410       filter.AppendWhere(PrepareSQL("actors.strActor like '%s'", option->second.asString().c_str()));
9411     }
9412
9413     option = options.find("year");
9414     if (option != options.end())
9415       filter.AppendWhere(PrepareSQL("musicvideoview.c%02d = '%i'",VIDEODB_ID_MUSICVIDEO_YEAR, (int)option->second.asInteger()));
9416
9417     option = options.find("artistid");
9418     if (option != options.end())
9419     {
9420       filter.AppendJoin(PrepareSQL("join artistlinkmusicvideo on artistlinkmusicvideo.idMVideo = musicvideoview.idMVideo"));
9421       filter.AppendWhere(PrepareSQL("artistlinkmusicvideo.idArtist = %i", (int)option->second.asInteger()));
9422     }
9423
9424     option = options.find("artist");
9425     if (option != options.end())
9426     {
9427       filter.AppendJoin(PrepareSQL("join artistlinkmusicvideo on artistlinkmusicvideo.idMVideo = musicvideoview.idMVideo join actors on actors.idActor = artistlinkmusicvideo.idArtist"));
9428       filter.AppendWhere(PrepareSQL("actors.strActor like '%s'", option->second.asString().c_str()));
9429     }
9430
9431     option = options.find("albumid");
9432     if (option != options.end())
9433       filter.AppendWhere(PrepareSQL("musicvideoview.c%02d = (select c%02d from musicvideo where idMVideo = %i)", VIDEODB_ID_MUSICVIDEO_ALBUM, VIDEODB_ID_MUSICVIDEO_ALBUM, (int)option->second.asInteger()));
9434
9435     option = options.find("tagid");
9436     if (option != options.end())
9437     {
9438       filter.AppendJoin(PrepareSQL("join taglinks on taglinks.idMedia = musicvideoview.idMVideo AND taglinks.media_type = 'musicvideo'"));
9439       filter.AppendWhere(PrepareSQL("taglinks.idTag = %i", (int)option->second.asInteger()));
9440     }
9441
9442     option = options.find("tag");
9443     if (option != options.end())
9444     {
9445       filter.AppendJoin(PrepareSQL("join taglinks on taglinks.idMedia = musicvideoview.idMVideo AND taglinks.media_type = 'musicvideo' join tag on tag.idTag = taglinks.idTag"));
9446       filter.AppendWhere(PrepareSQL("tag.strTag like '%s'", option->second.asString().c_str()));
9447     }
9448   }
9449   else
9450     return false;
9451
9452   option = options.find("xsp");
9453   if (option != options.end())
9454   {
9455     CSmartPlaylist xsp;
9456     if (!xsp.LoadFromJson(option->second.asString()))
9457       return false;
9458
9459     // check if the filter playlist matches the item type
9460     if (xsp.GetType() == itemType ||
9461         // handle episode listings with videodb://2/2/ which get the rest
9462         // of the path (season and episodeid) appended later
9463         (xsp.GetType() == "episodes" && itemType == "tvshows"))
9464     {
9465       std::set<CStdString> playlists;
9466       filter.AppendWhere(xsp.GetWhereClause(*this, playlists));
9467
9468       if (xsp.GetLimit() > 0)
9469         sorting.limitEnd = xsp.GetLimit();
9470       if (xsp.GetOrder() != SortByNone)
9471         sorting.sortBy = xsp.GetOrder();
9472       if (xsp.GetOrderDirection() != SortOrderNone)
9473         sorting.sortOrder = xsp.GetOrderDirection();
9474       if (g_guiSettings.GetBool("filelists.ignorethewhensorting"))
9475         sorting.sortAttributes = SortAttributeIgnoreArticle;
9476     }
9477   }
9478
9479   option = options.find("filter");
9480   if (option != options.end())
9481   {
9482     CSmartPlaylist xspFilter;
9483     if (!xspFilter.LoadFromJson(option->second.asString()))
9484       return false;
9485
9486     // check if the filter playlist matches the item type
9487     if (xspFilter.GetType() == itemType)
9488     {
9489       std::set<CStdString> playlists;
9490       filter.AppendWhere(xspFilter.GetWhereClause(*this, playlists));
9491     }
9492     // remove the filter if it doesn't match the item type
9493     else
9494       videoUrl.RemoveOption("filter");
9495   }
9496
9497   return true;
9498 }