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