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