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