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