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