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