1 /*
2  *  Copyright (C) 2016-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "VideoDatabase.h"
10 
11 #include "Application.h"
12 #include "FileItem.h"
13 #include "GUIInfoManager.h"
14 #include "GUIPassword.h"
15 #include "ServiceBroker.h"
16 #include "TextureCache.h"
17 #include "URL.h"
18 #include "Util.h"
19 #include "VideoInfoScanner.h"
20 #include "XBDateTime.h"
21 #include "addons/AddonManager.h"
22 #include "dbwrappers/dataset.h"
23 #include "dialogs/GUIDialogExtendedProgressBar.h"
24 #include "dialogs/GUIDialogKaiToast.h"
25 #include "dialogs/GUIDialogProgress.h"
26 #include "dialogs/GUIDialogYesNo.h"
27 #include "filesystem/Directory.h"
28 #include "filesystem/File.h"
29 #include "filesystem/MultiPathDirectory.h"
30 #include "filesystem/PluginDirectory.h"
31 #include "filesystem/StackDirectory.h"
32 #include "guilib/GUIComponent.h"
33 #include "guilib/GUIWindowManager.h"
34 #include "guilib/LocalizeStrings.h"
35 #include "guilib/guiinfo/GUIInfoLabels.h"
36 #include "interfaces/AnnouncementManager.h"
37 #include "messaging/helpers/DialogOKHelper.h"
38 #include "music/Artist.h"
39 #include "playlists/SmartPlayList.h"
40 #include "profiles/ProfileManager.h"
41 #include "settings/AdvancedSettings.h"
42 #include "settings/MediaSettings.h"
43 #include "settings/MediaSourceSettings.h"
44 #include "settings/Settings.h"
45 #include "settings/SettingsComponent.h"
46 #include "storage/MediaManager.h"
47 #include "threads/SystemClock.h"
48 #include "utils/FileUtils.h"
49 #include "utils/GroupUtils.h"
50 #include "utils/LabelFormatter.h"
51 #include "utils/StringUtils.h"
52 #include "utils/URIUtils.h"
53 #include "utils/Variant.h"
54 #include "utils/XMLUtils.h"
55 #include "utils/log.h"
56 #include "video/VideoDbUrl.h"
57 #include "video/VideoInfoTag.h"
58 #include "video/windows/GUIWindowVideoBase.h"
59 
60 #include <algorithm>
61 #include <map>
62 #include <memory>
63 #include <string>
64 #include <vector>
65 #include <unordered_set>
66 
67 using namespace dbiplus;
68 using namespace XFILE;
69 using namespace VIDEO;
70 using namespace ADDON;
71 using namespace KODI::MESSAGING;
72 using namespace KODI::GUILIB;
73 
74 //********************************************************************************************************************************
75 CVideoDatabase::CVideoDatabase(void) = default;
76 
77 //********************************************************************************************************************************
78 CVideoDatabase::~CVideoDatabase(void) = default;
79 
80 //********************************************************************************************************************************
Open()81 bool CVideoDatabase::Open()
82 {
83   return CDatabase::Open(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_databaseVideo);
84 }
85 
CreateTables()86 void CVideoDatabase::CreateTables()
87 {
88   CLog::Log(LOGINFO, "create bookmark table");
89   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");
90 
91   CLog::Log(LOGINFO, "create settings table");
92   m_pDS->exec("CREATE TABLE settings ( idFile integer, Deinterlace bool,"
93               "ViewMode integer,ZoomAmount float, PixelRatio float, VerticalShift float, AudioStream integer, SubtitleStream integer,"
94               "SubtitleDelay float, SubtitlesOn bool, Brightness float, Contrast float, Gamma float,"
95               "VolumeAmplification float, AudioDelay float, ResumeTime integer,"
96               "Sharpness float, NoiseReduction float, NonLinStretch bool, PostProcess bool,"
97               "ScalingMethod integer, DeinterlaceMode integer, StereoMode integer, StereoInvert bool, VideoStream integer,"
98               "TonemapMethod integer, TonemapParam float, Orientation integer, CenterMixLevel integer)\n");
99 
100   CLog::Log(LOGINFO, "create stacktimes table");
101   m_pDS->exec("CREATE TABLE stacktimes (idFile integer, times text)\n");
102 
103   CLog::Log(LOGINFO, "create genre table");
104   m_pDS->exec("CREATE TABLE genre ( genre_id integer primary key, name TEXT)\n");
105   m_pDS->exec("CREATE TABLE genre_link (genre_id integer, media_id integer, media_type TEXT)");
106 
107   CLog::Log(LOGINFO, "create country table");
108   m_pDS->exec("CREATE TABLE country ( country_id integer primary key, name TEXT)");
109   m_pDS->exec("CREATE TABLE country_link (country_id integer, media_id integer, media_type TEXT)");
110 
111   CLog::Log(LOGINFO, "create movie table");
112   std::string columns = "CREATE TABLE movie ( idMovie integer primary key, idFile integer";
113 
114   for (int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
115     columns += StringUtils::Format(",c%02d text", i);
116 
117   columns += ", idSet integer, userrating integer, premiered text)";
118   m_pDS->exec(columns);
119 
120   CLog::Log(LOGINFO, "create actor table");
121   m_pDS->exec("CREATE TABLE actor ( actor_id INTEGER PRIMARY KEY, name TEXT, art_urls TEXT )");
122   m_pDS->exec("CREATE TABLE actor_link(actor_id INTEGER, media_id INTEGER, media_type TEXT, role TEXT, cast_order INTEGER)");
123   m_pDS->exec("CREATE TABLE director_link(actor_id INTEGER, media_id INTEGER, media_type TEXT)");
124   m_pDS->exec("CREATE TABLE writer_link(actor_id INTEGER, media_id INTEGER, media_type TEXT)");
125 
126   CLog::Log(LOGINFO, "create path table");
127   m_pDS->exec(
128       "CREATE TABLE path ( idPath integer primary key, strPath text, strContent text, strScraper "
129       "text, strHash text, scanRecursive integer, useFolderNames bool, strSettings text, noUpdate "
130       "bool, exclude bool, allAudio bool, dateAdded text, idParentPath integer)");
131 
132   CLog::Log(LOGINFO, "create files table");
133   m_pDS->exec("CREATE TABLE files ( idFile integer primary key, idPath integer, strFilename text, playCount integer, lastPlayed text, dateAdded text)");
134 
135   CLog::Log(LOGINFO, "create tvshow table");
136   columns = "CREATE TABLE tvshow ( idShow integer primary key";
137 
138   for (int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
139     columns += StringUtils::Format(",c%02d text", i);
140 
141   columns += ", userrating integer, duration INTEGER)";
142   m_pDS->exec(columns);
143 
144   CLog::Log(LOGINFO, "create episode table");
145   columns = "CREATE TABLE episode ( idEpisode integer primary key, idFile integer";
146   for (int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
147   {
148     std::string column;
149     if ( i == VIDEODB_ID_EPISODE_SEASON || i == VIDEODB_ID_EPISODE_EPISODE || i == VIDEODB_ID_EPISODE_BOOKMARK)
150       column = StringUtils::Format(",c%02d varchar(24)", i);
151     else
152       column = StringUtils::Format(",c%02d text", i);
153 
154     columns += column;
155   }
156   columns += ", idShow integer, userrating integer, idSeason integer)";
157   m_pDS->exec(columns);
158 
159   CLog::Log(LOGINFO, "create tvshowlinkpath table");
160   m_pDS->exec("CREATE TABLE tvshowlinkpath (idShow integer, idPath integer)\n");
161 
162   CLog::Log(LOGINFO, "create movielinktvshow table");
163   m_pDS->exec("CREATE TABLE movielinktvshow ( idMovie integer, IdShow integer)\n");
164 
165   CLog::Log(LOGINFO, "create studio table");
166   m_pDS->exec("CREATE TABLE studio ( studio_id integer primary key, name TEXT)\n");
167   m_pDS->exec("CREATE TABLE studio_link (studio_id integer, media_id integer, media_type TEXT)");
168 
169   CLog::Log(LOGINFO, "create musicvideo table");
170   columns = "CREATE TABLE musicvideo ( idMVideo integer primary key, idFile integer";
171   for (int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
172     columns += StringUtils::Format(",c%02d text", i);
173 
174   columns += ", userrating integer, premiered text)";
175   m_pDS->exec(columns);
176 
177   CLog::Log(LOGINFO, "create streaminfo table");
178   m_pDS->exec("CREATE TABLE streamdetails (idFile integer, iStreamType integer, "
179     "strVideoCodec text, fVideoAspect float, iVideoWidth integer, iVideoHeight integer, "
180     "strAudioCodec text, iAudioChannels integer, strAudioLanguage text, "
181     "strSubtitleLanguage text, iVideoDuration integer, strStereoMode text, strVideoLanguage text)");
182 
183   CLog::Log(LOGINFO, "create sets table");
184   m_pDS->exec("CREATE TABLE sets ( idSet integer primary key, strSet text, strOverview text)");
185 
186   CLog::Log(LOGINFO, "create seasons table");
187   m_pDS->exec("CREATE TABLE seasons ( idSeason integer primary key, idShow integer, season integer, name text, userrating integer)");
188 
189   CLog::Log(LOGINFO, "create art table");
190   m_pDS->exec("CREATE TABLE art(art_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, type TEXT, url TEXT)");
191 
192   CLog::Log(LOGINFO, "create tag table");
193   m_pDS->exec("CREATE TABLE tag (tag_id integer primary key, name TEXT)");
194   m_pDS->exec("CREATE TABLE tag_link (tag_id integer, media_id integer, media_type TEXT)");
195 
196   CLog::Log(LOGINFO, "create rating table");
197   m_pDS->exec("CREATE TABLE rating (rating_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, rating_type TEXT, rating FLOAT, votes INTEGER)");
198 
199   CLog::Log(LOGINFO, "create uniqueid table");
200   m_pDS->exec("CREATE TABLE uniqueid (uniqueid_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, value TEXT, type TEXT)");
201 }
202 
CreateLinkIndex(const char * table)203 void CVideoDatabase::CreateLinkIndex(const char *table)
204 {
205   m_pDS->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_1 ON %s (name(255))", table, table));
206   m_pDS->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_link_1 ON %s_link (%s_id, media_type(20), media_id)", table, table, table));
207   m_pDS->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_link_2 ON %s_link (media_id, media_type(20), %s_id)", table, table, table));
208   m_pDS->exec(PrepareSQL("CREATE INDEX ix_%s_link_3 ON %s_link (media_type(20))", table, table));
209 }
210 
CreateForeignLinkIndex(const char * table,const char * foreignkey)211 void CVideoDatabase::CreateForeignLinkIndex(const char *table, const char *foreignkey)
212 {
213   m_pDS->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_link_1 ON %s_link (%s_id, media_type(20), media_id)", table, table, foreignkey));
214   m_pDS->exec(PrepareSQL("CREATE UNIQUE INDEX ix_%s_link_2 ON %s_link (media_id, media_type(20), %s_id)", table, table, foreignkey));
215   m_pDS->exec(PrepareSQL("CREATE INDEX ix_%s_link_3 ON %s_link (media_type(20))", table, table));
216 }
217 
CreateAnalytics()218 void CVideoDatabase::CreateAnalytics()
219 {
220   /* indexes should be added on any columns that are used in in  */
221   /* a where or a join. primary key on a column is the same as a */
222   /* unique index on that column, so there is no need to add any */
223   /* index if no other columns are refered                       */
224 
225   /* order of indexes are important, for an index to be considered all  */
226   /* columns up to the column in question have to have been specified   */
227   /* select * from foolink where foo_id = 1, can not take               */
228   /* advantage of a index that has been created on ( bar_id, foo_id )   */
229   /* however an index on ( foo_id, bar_id ) will be considered for use  */
230 
231   CLog::Log(LOGINFO, "%s - creating indices", __FUNCTION__);
232   m_pDS->exec("CREATE INDEX ix_bookmark ON bookmark (idFile, type)");
233   m_pDS->exec("CREATE UNIQUE INDEX ix_settings ON settings ( idFile )\n");
234   m_pDS->exec("CREATE UNIQUE INDEX ix_stacktimes ON stacktimes ( idFile )\n");
235   m_pDS->exec("CREATE INDEX ix_path ON path ( strPath(255) )");
236   m_pDS->exec("CREATE INDEX ix_path2 ON path ( idParentPath )");
237   m_pDS->exec("CREATE INDEX ix_files ON files ( idPath, strFilename(255) )");
238 
239   m_pDS->exec("CREATE UNIQUE INDEX ix_movie_file_1 ON movie (idFile, idMovie)");
240   m_pDS->exec("CREATE UNIQUE INDEX ix_movie_file_2 ON movie (idMovie, idFile)");
241 
242   m_pDS->exec("CREATE UNIQUE INDEX ix_tvshowlinkpath_1 ON tvshowlinkpath ( idShow, idPath )\n");
243   m_pDS->exec("CREATE UNIQUE INDEX ix_tvshowlinkpath_2 ON tvshowlinkpath ( idPath, idShow )\n");
244   m_pDS->exec("CREATE UNIQUE INDEX ix_movielinktvshow_1 ON movielinktvshow ( idShow, idMovie)\n");
245   m_pDS->exec("CREATE UNIQUE INDEX ix_movielinktvshow_2 ON movielinktvshow ( idMovie, idShow)\n");
246 
247   m_pDS->exec("CREATE UNIQUE INDEX ix_episode_file_1 on episode (idEpisode, idFile)");
248   m_pDS->exec("CREATE UNIQUE INDEX id_episode_file_2 on episode (idFile, idEpisode)");
249   std::string createColIndex = StringUtils::Format("CREATE INDEX ix_episode_season_episode on episode (c%02d, c%02d)", VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_EPISODE_EPISODE);
250   m_pDS->exec(createColIndex);
251   createColIndex = StringUtils::Format("CREATE INDEX ix_episode_bookmark on episode (c%02d)", VIDEODB_ID_EPISODE_BOOKMARK);
252   m_pDS->exec(createColIndex);
253   m_pDS->exec("CREATE INDEX ix_episode_show1 on episode(idEpisode,idShow)");
254   m_pDS->exec("CREATE INDEX ix_episode_show2 on episode(idShow,idEpisode)");
255 
256   m_pDS->exec("CREATE UNIQUE INDEX ix_musicvideo_file_1 on musicvideo (idMVideo, idFile)");
257   m_pDS->exec("CREATE UNIQUE INDEX ix_musicvideo_file_2 on musicvideo (idFile, idMVideo)");
258 
259   m_pDS->exec("CREATE INDEX ixMovieBasePath ON movie ( c23(12) )");
260   m_pDS->exec("CREATE INDEX ixMusicVideoBasePath ON musicvideo ( c14(12) )");
261   m_pDS->exec("CREATE INDEX ixEpisodeBasePath ON episode ( c19(12) )");
262 
263   m_pDS->exec("CREATE INDEX ix_streamdetails ON streamdetails (idFile)");
264   m_pDS->exec("CREATE INDEX ix_seasons ON seasons (idShow, season)");
265   m_pDS->exec("CREATE INDEX ix_art ON art(media_id, media_type(20), type(20))");
266 
267   m_pDS->exec("CREATE INDEX ix_rating ON rating(media_id, media_type(20))");
268 
269   m_pDS->exec("CREATE INDEX ix_uniqueid1 ON uniqueid(media_id, media_type(20), type(20))");
270   m_pDS->exec("CREATE INDEX ix_uniqueid2 ON uniqueid(media_type(20), value(20))");
271 
272   m_pDS->exec("CREATE UNIQUE INDEX ix_actor_1 ON actor (name(255))");
273   m_pDS->exec("CREATE UNIQUE INDEX ix_actor_link_1 ON "
274               "actor_link (actor_id, media_type(20), media_id, role(255))");
275   m_pDS->exec("CREATE INDEX ix_actor_link_2 ON "
276               "actor_link (media_id, media_type(20), actor_id)");
277   m_pDS->exec("CREATE INDEX ix_actor_link_3 ON actor_link (media_type(20))");
278 
279   CreateLinkIndex("tag");
280   CreateForeignLinkIndex("director", "actor");
281   CreateForeignLinkIndex("writer", "actor");
282   CreateLinkIndex("studio");
283   CreateLinkIndex("genre");
284   CreateLinkIndex("country");
285 
286   CLog::Log(LOGINFO, "%s - creating triggers", __FUNCTION__);
287   m_pDS->exec("CREATE TRIGGER delete_movie AFTER DELETE ON movie FOR EACH ROW BEGIN "
288               "DELETE FROM genre_link WHERE media_id=old.idMovie AND media_type='movie'; "
289               "DELETE FROM actor_link WHERE media_id=old.idMovie AND media_type='movie'; "
290               "DELETE FROM director_link WHERE media_id=old.idMovie AND media_type='movie'; "
291               "DELETE FROM studio_link WHERE media_id=old.idMovie AND media_type='movie'; "
292               "DELETE FROM country_link WHERE media_id=old.idMovie AND media_type='movie'; "
293               "DELETE FROM writer_link WHERE media_id=old.idMovie AND media_type='movie'; "
294               "DELETE FROM movielinktvshow WHERE idMovie=old.idMovie; "
295               "DELETE FROM art WHERE media_id=old.idMovie AND media_type='movie'; "
296               "DELETE FROM tag_link WHERE media_id=old.idMovie AND media_type='movie'; "
297               "DELETE FROM rating WHERE media_id=old.idMovie AND media_type='movie'; "
298               "DELETE FROM uniqueid WHERE media_id=old.idMovie AND media_type='movie'; "
299               "END");
300   m_pDS->exec("CREATE TRIGGER delete_tvshow AFTER DELETE ON tvshow FOR EACH ROW BEGIN "
301               "DELETE FROM actor_link WHERE media_id=old.idShow AND media_type='tvshow'; "
302               "DELETE FROM director_link WHERE media_id=old.idShow AND media_type='tvshow'; "
303               "DELETE FROM studio_link WHERE media_id=old.idShow AND media_type='tvshow'; "
304               "DELETE FROM tvshowlinkpath WHERE idShow=old.idShow; "
305               "DELETE FROM genre_link WHERE media_id=old.idShow AND media_type='tvshow'; "
306               "DELETE FROM movielinktvshow WHERE idShow=old.idShow; "
307               "DELETE FROM seasons WHERE idShow=old.idShow; "
308               "DELETE FROM art WHERE media_id=old.idShow AND media_type='tvshow'; "
309               "DELETE FROM tag_link WHERE media_id=old.idShow AND media_type='tvshow'; "
310               "DELETE FROM rating WHERE media_id=old.idShow AND media_type='tvshow'; "
311               "DELETE FROM uniqueid WHERE media_id=old.idShow AND media_type='tvshow'; "
312               "END");
313   m_pDS->exec("CREATE TRIGGER delete_musicvideo AFTER DELETE ON musicvideo FOR EACH ROW BEGIN "
314               "DELETE FROM actor_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
315               "DELETE FROM director_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
316               "DELETE FROM genre_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
317               "DELETE FROM studio_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
318               "DELETE FROM art WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
319               "DELETE FROM tag_link WHERE media_id=old.idMVideo AND media_type='musicvideo'; "
320               "END");
321   m_pDS->exec("CREATE TRIGGER delete_episode AFTER DELETE ON episode FOR EACH ROW BEGIN "
322               "DELETE FROM actor_link WHERE media_id=old.idEpisode AND media_type='episode'; "
323               "DELETE FROM director_link WHERE media_id=old.idEpisode AND media_type='episode'; "
324               "DELETE FROM writer_link WHERE media_id=old.idEpisode AND media_type='episode'; "
325               "DELETE FROM art WHERE media_id=old.idEpisode AND media_type='episode'; "
326               "DELETE FROM rating WHERE media_id=old.idEpisode AND media_type='episode'; "
327               "DELETE FROM uniqueid WHERE media_id=old.idEpisode AND media_type='episode'; "
328               "END");
329   m_pDS->exec("CREATE TRIGGER delete_season AFTER DELETE ON seasons FOR EACH ROW BEGIN "
330               "DELETE FROM art WHERE media_id=old.idSeason AND media_type='season'; "
331               "END");
332   m_pDS->exec("CREATE TRIGGER delete_set AFTER DELETE ON sets FOR EACH ROW BEGIN "
333               "DELETE FROM art WHERE media_id=old.idSet AND media_type='set'; "
334               "END");
335   m_pDS->exec("CREATE TRIGGER delete_person AFTER DELETE ON actor FOR EACH ROW BEGIN "
336               "DELETE FROM art WHERE media_id=old.actor_id AND media_type IN ('actor','artist','writer','director'); "
337               "END");
338   m_pDS->exec("CREATE TRIGGER delete_tag AFTER DELETE ON tag_link FOR EACH ROW BEGIN "
339               "DELETE FROM tag WHERE tag_id=old.tag_id AND tag_id NOT IN (SELECT DISTINCT tag_id FROM tag_link); "
340               "END");
341   m_pDS->exec("CREATE TRIGGER delete_file AFTER DELETE ON files FOR EACH ROW BEGIN "
342               "DELETE FROM bookmark WHERE idFile=old.idFile; "
343               "DELETE FROM settings WHERE idFile=old.idFile; "
344               "DELETE FROM stacktimes WHERE idFile=old.idFile; "
345               "DELETE FROM streamdetails WHERE idFile=old.idFile; "
346               "END");
347 
348   CreateViews();
349 }
350 
CreateViews()351 void CVideoDatabase::CreateViews()
352 {
353   CLog::Log(LOGINFO, "create episode_view");
354   std::string episodeview = PrepareSQL("CREATE VIEW episode_view AS SELECT "
355                                       "  episode.*,"
356                                       "  files.strFileName AS strFileName,"
357                                       "  path.strPath AS strPath,"
358                                       "  files.playCount AS playCount,"
359                                       "  files.lastPlayed AS lastPlayed,"
360                                       "  files.dateAdded AS dateAdded,"
361                                       "  tvshow.c%02d AS strTitle,"
362                                       "  tvshow.c%02d AS genre,"
363                                       "  tvshow.c%02d AS studio,"
364                                       "  tvshow.c%02d AS premiered,"
365                                       "  tvshow.c%02d AS mpaa,"
366                                       "  bookmark.timeInSeconds AS resumeTimeInSeconds, "
367                                       "  bookmark.totalTimeInSeconds AS totalTimeInSeconds, "
368                                       "  bookmark.playerState AS playerState, "
369                                       "  rating.rating AS rating, "
370                                       "  rating.votes AS votes, "
371                                       "  rating.rating_type AS rating_type, "
372                                       "  uniqueid.value AS uniqueid_value, "
373                                       "  uniqueid.type AS uniqueid_type "
374                                       "FROM episode"
375                                       "  JOIN files ON"
376                                       "    files.idFile=episode.idFile"
377                                       "  JOIN tvshow ON"
378                                       "    tvshow.idShow=episode.idShow"
379                                       "  JOIN seasons ON"
380                                       "    seasons.idSeason=episode.idSeason"
381                                       "  JOIN path ON"
382                                       "    files.idPath=path.idPath"
383                                       "  LEFT JOIN bookmark ON"
384                                       "    bookmark.idFile=episode.idFile AND bookmark.type=1"
385                                       "  LEFT JOIN rating ON"
386                                       "    rating.rating_id=episode.c%02d"
387                                       "  LEFT JOIN uniqueid ON"
388                                       "    uniqueid.uniqueid_id=episode.c%02d",
389                                       VIDEODB_ID_TV_TITLE, VIDEODB_ID_TV_GENRE,
390                                       VIDEODB_ID_TV_STUDIOS, VIDEODB_ID_TV_PREMIERED,
391                                       VIDEODB_ID_TV_MPAA, VIDEODB_ID_EPISODE_RATING_ID,
392                                       VIDEODB_ID_EPISODE_IDENT_ID);
393   m_pDS->exec(episodeview);
394 
395   CLog::Log(LOGINFO, "create tvshowcounts");
396   std::string tvshowcounts = PrepareSQL("CREATE VIEW tvshowcounts AS SELECT "
397                                        "      tvshow.idShow AS idShow,"
398                                        "      MAX(files.lastPlayed) AS lastPlayed,"
399                                        "      NULLIF(COUNT(episode.c12), 0) AS totalCount,"
400                                        "      COUNT(files.playCount) AS watchedcount,"
401                                        "      NULLIF(COUNT(DISTINCT(episode.c12)), 0) AS totalSeasons, "
402                                        "      MAX(files.dateAdded) as dateAdded "
403                                        "    FROM tvshow"
404                                        "      LEFT JOIN episode ON"
405                                        "        episode.idShow=tvshow.idShow"
406                                        "      LEFT JOIN files ON"
407                                        "        files.idFile=episode.idFile "
408                                        "GROUP BY tvshow.idShow");
409   m_pDS->exec(tvshowcounts);
410 
411   CLog::Log(LOGINFO, "create tvshowlinkpath_minview");
412   // This view only exists to workaround a limitation in MySQL <5.7 which is not able to
413   // perform subqueries in joins.
414   // Also, the correct solution is to remove the path information altogether, since a
415   // TV series can always have multiple paths. It is used in the GUI at the moment, but
416   // such usage should be removed together with this view and the path columns in tvshow_view.
417   //!@todo Remove the hacky selection of a semi-random path for tvshows from the queries and UI
418   std::string tvshowlinkpathview = PrepareSQL("CREATE VIEW tvshowlinkpath_minview AS SELECT "
419                                              "  idShow, "
420                                              "  min(idPath) AS idPath "
421                                              "FROM tvshowlinkpath "
422                                              "GROUP BY idShow");
423   m_pDS->exec(tvshowlinkpathview);
424 
425   CLog::Log(LOGINFO, "create tvshow_view");
426   std::string tvshowview = PrepareSQL("CREATE VIEW tvshow_view AS SELECT "
427                                      "  tvshow.*,"
428                                      "  path.idParentPath AS idParentPath,"
429                                      "  path.strPath AS strPath,"
430                                      "  tvshowcounts.dateAdded AS dateAdded,"
431                                      "  lastPlayed, totalCount, watchedcount, totalSeasons, "
432                                      "  rating.rating AS rating, "
433                                      "  rating.votes AS votes, "
434                                      "  rating.rating_type AS rating_type, "
435                                      "  uniqueid.value AS uniqueid_value, "
436                                      "  uniqueid.type AS uniqueid_type "
437                                      "FROM tvshow"
438                                      "  LEFT JOIN tvshowlinkpath_minview ON "
439                                      "    tvshowlinkpath_minview.idShow=tvshow.idShow"
440                                      "  LEFT JOIN path ON"
441                                      "    path.idPath=tvshowlinkpath_minview.idPath"
442                                      "  INNER JOIN tvshowcounts ON"
443                                      "    tvshow.idShow = tvshowcounts.idShow "
444                                      "  LEFT JOIN rating ON"
445                                      "    rating.rating_id=tvshow.c%02d "
446                                      "  LEFT JOIN uniqueid ON"
447                                      "    uniqueid.uniqueid_id=tvshow.c%02d ",
448                                      VIDEODB_ID_TV_RATING_ID, VIDEODB_ID_TV_IDENT_ID);
449   m_pDS->exec(tvshowview);
450 
451   CLog::Log(LOGINFO, "create season_view");
452   std::string seasonview = PrepareSQL("CREATE VIEW season_view AS SELECT "
453                                      "  seasons.idSeason AS idSeason,"
454                                      "  seasons.idShow AS idShow,"
455                                      "  seasons.season AS season,"
456                                      "  seasons.name AS name,"
457                                      "  seasons.userrating AS userrating,"
458                                      "  tvshow_view.strPath AS strPath,"
459                                      "  tvshow_view.c%02d AS showTitle,"
460                                      "  tvshow_view.c%02d AS plot,"
461                                      "  tvshow_view.c%02d AS premiered,"
462                                      "  tvshow_view.c%02d AS genre,"
463                                      "  tvshow_view.c%02d AS studio,"
464                                      "  tvshow_view.c%02d AS mpaa,"
465                                      "  count(DISTINCT episode.idEpisode) AS episodes,"
466                                      "  count(files.playCount) AS playCount,"
467                                      "  min(episode.c%02d) AS aired "
468                                      "FROM seasons"
469                                      "  JOIN tvshow_view ON"
470                                      "    tvshow_view.idShow = seasons.idShow"
471                                      "  JOIN episode ON"
472                                      "    episode.idShow = seasons.idShow AND episode.c%02d = seasons.season"
473                                      "  JOIN files ON"
474                                      "    files.idFile = episode.idFile "
475                                      "GROUP BY seasons.idSeason,"
476                                      "         seasons.idShow,"
477                                      "         seasons.season,"
478                                      "         seasons.name,"
479                                      "         seasons.userrating,"
480                                      "         tvshow_view.strPath,"
481                                      "         tvshow_view.c%02d,"
482                                      "         tvshow_view.c%02d,"
483                                      "         tvshow_view.c%02d,"
484                                      "         tvshow_view.c%02d,"
485                                      "         tvshow_view.c%02d,"
486                                      "         tvshow_view.c%02d ",
487                                      VIDEODB_ID_TV_TITLE, VIDEODB_ID_TV_PLOT, VIDEODB_ID_TV_PREMIERED,
488                                      VIDEODB_ID_TV_GENRE, VIDEODB_ID_TV_STUDIOS, VIDEODB_ID_TV_MPAA,
489                                      VIDEODB_ID_EPISODE_AIRED, VIDEODB_ID_EPISODE_SEASON,
490                                      VIDEODB_ID_TV_TITLE, VIDEODB_ID_TV_PLOT, VIDEODB_ID_TV_PREMIERED,
491                                      VIDEODB_ID_TV_GENRE, VIDEODB_ID_TV_STUDIOS, VIDEODB_ID_TV_MPAA);
492   m_pDS->exec(seasonview);
493 
494   CLog::Log(LOGINFO, "create musicvideo_view");
495   m_pDS->exec("CREATE VIEW musicvideo_view AS SELECT"
496               "  musicvideo.*,"
497               "  files.strFileName as strFileName,"
498               "  path.strPath as strPath,"
499               "  files.playCount as playCount,"
500               "  files.lastPlayed as lastPlayed,"
501               "  files.dateAdded as dateAdded, "
502               "  bookmark.timeInSeconds AS resumeTimeInSeconds, "
503               "  bookmark.totalTimeInSeconds AS totalTimeInSeconds, "
504               "  bookmark.playerState AS playerState "
505               "FROM musicvideo"
506               "  JOIN files ON"
507               "    files.idFile=musicvideo.idFile"
508               "  JOIN path ON"
509               "    path.idPath=files.idPath"
510               "  LEFT JOIN bookmark ON"
511               "    bookmark.idFile=musicvideo.idFile AND bookmark.type=1");
512 
513   CLog::Log(LOGINFO, "create movie_view");
514 
515   std::string movieview = PrepareSQL("CREATE VIEW movie_view AS SELECT"
516                                       "  movie.*,"
517                                       "  sets.strSet AS strSet,"
518                                       "  sets.strOverview AS strSetOverview,"
519                                       "  files.strFileName AS strFileName,"
520                                       "  path.strPath AS strPath,"
521                                       "  files.playCount AS playCount,"
522                                       "  files.lastPlayed AS lastPlayed, "
523                                       "  files.dateAdded AS dateAdded, "
524                                       "  bookmark.timeInSeconds AS resumeTimeInSeconds, "
525                                       "  bookmark.totalTimeInSeconds AS totalTimeInSeconds, "
526                                       "  bookmark.playerState AS playerState, "
527                                       "  rating.rating AS rating, "
528                                       "  rating.votes AS votes, "
529                                       "  rating.rating_type AS rating_type, "
530                                       "  uniqueid.value AS uniqueid_value, "
531                                       "  uniqueid.type AS uniqueid_type "
532                                       "FROM movie"
533                                       "  LEFT JOIN sets ON"
534                                       "    sets.idSet = movie.idSet"
535                                       "  JOIN files ON"
536                                       "    files.idFile=movie.idFile"
537                                       "  JOIN path ON"
538                                       "    path.idPath=files.idPath"
539                                       "  LEFT JOIN bookmark ON"
540                                       "    bookmark.idFile=movie.idFile AND bookmark.type=1"
541                                       "  LEFT JOIN rating ON"
542                                       "    rating.rating_id=movie.c%02d"
543                                       "  LEFT JOIN uniqueid ON"
544                                       "    uniqueid.uniqueid_id=movie.c%02d",
545                                       VIDEODB_ID_RATING_ID, VIDEODB_ID_IDENT_ID);
546   m_pDS->exec(movieview);
547 }
548 
549 //********************************************************************************************************************************
GetPathId(const std::string & strPath)550 int CVideoDatabase::GetPathId(const std::string& strPath)
551 {
552   std::string strSQL;
553   try
554   {
555     int idPath=-1;
556     if (nullptr == m_pDB)
557       return -1;
558     if (nullptr == m_pDS)
559       return -1;
560 
561     std::string strPath1(strPath);
562     if (URIUtils::IsStack(strPath) || StringUtils::StartsWithNoCase(strPath, "rar://") || StringUtils::StartsWithNoCase(strPath, "zip://"))
563       URIUtils::GetParentPath(strPath,strPath1);
564 
565     URIUtils::AddSlashAtEnd(strPath1);
566 
567     strSQL=PrepareSQL("select idPath from path where strPath='%s'",strPath1.c_str());
568     m_pDS->query(strSQL);
569     if (!m_pDS->eof())
570       idPath = m_pDS->fv("path.idPath").get_asInt();
571 
572     m_pDS->close();
573     return idPath;
574   }
575   catch (...)
576   {
577     CLog::Log(LOGERROR, "%s unable to getpath (%s)", __FUNCTION__, strSQL.c_str());
578   }
579   return -1;
580 }
581 
GetPaths(std::set<std::string> & paths)582 bool CVideoDatabase::GetPaths(std::set<std::string> &paths)
583 {
584   try
585   {
586     if (nullptr == m_pDB)
587       return false;
588     if (nullptr == m_pDS)
589       return false;
590 
591     paths.clear();
592 
593     // grab all paths with movie content set
594     if (!m_pDS->query("select strPath,noUpdate from path"
595                       " where (strContent = 'movies' or strContent = 'musicvideos')"
596                       " and strPath NOT like 'multipath://%%'"
597                       " order by strPath"))
598       return false;
599 
600     while (!m_pDS->eof())
601     {
602       if (!m_pDS->fv("noUpdate").get_asBool())
603         paths.insert(m_pDS->fv("strPath").get_asString());
604       m_pDS->next();
605     }
606     m_pDS->close();
607 
608     // then grab all tvshow paths
609     if (!m_pDS->query("select strPath,noUpdate from path"
610                       " where ( strContent = 'tvshows'"
611                       "       or idPath in (select idPath from tvshowlinkpath))"
612                       " and strPath NOT like 'multipath://%%'"
613                       " order by strPath"))
614       return false;
615 
616     while (!m_pDS->eof())
617     {
618       if (!m_pDS->fv("noUpdate").get_asBool())
619         paths.insert(m_pDS->fv("strPath").get_asString());
620       m_pDS->next();
621     }
622     m_pDS->close();
623 
624     // finally grab all other paths holding a movie which is not a stack or a rar archive
625     // - this isnt perfect but it should do fine in most situations.
626     // reason we need it to hold a movie is stacks from different directories (cdx folders for instance)
627     // not making mistakes must take priority
628     if (!m_pDS->query("select strPath,noUpdate from path"
629                        " where idPath in (select idPath from files join movie on movie.idFile=files.idFile)"
630                        " and idPath NOT in (select idPath from tvshowlinkpath)"
631                        " 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
632                        " and idPath NOT in (select idPath from files where strFileName like 'index.bdmv')" // bluray folders get stacked to a single item in parent folder
633                        " and strPath NOT like 'multipath://%%'"
634                        " and strContent NOT in ('movies', 'tvshows', 'None')" // these have been added above
635                        " order by strPath"))
636 
637       return false;
638     while (!m_pDS->eof())
639     {
640       if (!m_pDS->fv("noUpdate").get_asBool())
641         paths.insert(m_pDS->fv("strPath").get_asString());
642       m_pDS->next();
643     }
644     m_pDS->close();
645     return true;
646   }
647   catch (...)
648   {
649     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
650   }
651   return false;
652 }
653 
GetPathsLinkedToTvShow(int idShow,std::vector<std::string> & paths)654 bool CVideoDatabase::GetPathsLinkedToTvShow(int idShow, std::vector<std::string> &paths)
655 {
656   std::string sql;
657   try
658   {
659     sql = PrepareSQL("SELECT strPath FROM path JOIN tvshowlinkpath ON tvshowlinkpath.idPath=path.idPath WHERE idShow=%i", idShow);
660     m_pDS->query(sql);
661     while (!m_pDS->eof())
662     {
663       paths.emplace_back(m_pDS->fv(0).get_asString());
664       m_pDS->next();
665     }
666     return true;
667   }
668   catch (...)
669   {
670     CLog::Log(LOGERROR, "%s error during query: %s",__FUNCTION__, sql.c_str());
671   }
672   return false;
673 }
674 
GetPathsForTvShow(int idShow,std::set<int> & paths)675 bool CVideoDatabase::GetPathsForTvShow(int idShow, std::set<int>& paths)
676 {
677   std::string strSQL;
678   try
679   {
680     if (nullptr == m_pDB)
681       return false;
682     if (nullptr == m_pDS)
683       return false;
684 
685     // add base path
686     strSQL = PrepareSQL("SELECT strPath FROM tvshow_view WHERE idShow=%i", idShow);
687     if (m_pDS->query(strSQL))
688       paths.insert(GetPathId(m_pDS->fv(0).get_asString()));
689 
690     // add all other known paths
691     strSQL = PrepareSQL("SELECT DISTINCT idPath FROM files JOIN episode ON episode.idFile=files.idFile WHERE episode.idShow=%i",idShow);
692     m_pDS->query(strSQL);
693     while (!m_pDS->eof())
694     {
695       paths.insert(m_pDS->fv(0).get_asInt());
696       m_pDS->next();
697     }
698     m_pDS->close();
699     return true;
700   }
701   catch (...)
702   {
703     CLog::Log(LOGERROR, "%s error during query: %s",__FUNCTION__, strSQL.c_str());
704   }
705   return false;
706 }
707 
RunQuery(const std::string & sql)708 int CVideoDatabase::RunQuery(const std::string &sql)
709 {
710   unsigned int time = XbmcThreads::SystemClockMillis();
711   int rows = -1;
712   if (m_pDS->query(sql))
713   {
714     rows = m_pDS->num_rows();
715     if (rows == 0)
716       m_pDS->close();
717   }
718   CLog::Log(LOGDEBUG, LOGDATABASE, "%s took %d ms for %d items query: %s", __FUNCTION__, XbmcThreads::SystemClockMillis() - time, rows, sql.c_str());
719   return rows;
720 }
721 
GetSubPaths(const std::string & basepath,std::vector<std::pair<int,std::string>> & subpaths)722 bool CVideoDatabase::GetSubPaths(const std::string &basepath, std::vector<std::pair<int, std::string>>& subpaths)
723 {
724   std::string sql;
725   try
726   {
727     if (!m_pDB || !m_pDS)
728       return false;
729 
730     std::string path(basepath);
731     URIUtils::AddSlashAtEnd(path);
732     sql = PrepareSQL("SELECT idPath,strPath FROM path WHERE SUBSTR(strPath,1,%i)='%s'"
733                      " AND idPath NOT IN (SELECT idPath FROM files WHERE strFileName LIKE 'video_ts.ifo')"
734                      " AND idPath NOT IN (SELECT idPath FROM files WHERE strFileName LIKE 'index.bdmv')"
735                      , StringUtils::utf8_strlen(path.c_str()), path.c_str());
736 
737     m_pDS->query(sql);
738     while (!m_pDS->eof())
739     {
740       subpaths.emplace_back(m_pDS->fv(0).get_asInt(), m_pDS->fv(1).get_asString());
741       m_pDS->next();
742     }
743     m_pDS->close();
744     return true;
745   }
746   catch (...)
747   {
748     CLog::Log(LOGERROR, "%s error during query: %s",__FUNCTION__, sql.c_str());
749   }
750   return false;
751 }
752 
AddPath(const std::string & strPath,const std::string & parentPath,const CDateTime & dateAdded)753 int CVideoDatabase::AddPath(const std::string& strPath, const std::string &parentPath /*= "" */, const CDateTime& dateAdded /* = CDateTime() */)
754 {
755   std::string strSQL;
756   try
757   {
758     int idPath = GetPathId(strPath);
759     if (idPath >= 0)
760       return idPath; // already have the path
761 
762     if (nullptr == m_pDB)
763       return -1;
764     if (nullptr == m_pDS)
765       return -1;
766 
767     std::string strPath1(strPath);
768     if (URIUtils::IsStack(strPath) || StringUtils::StartsWithNoCase(strPath, "rar://") || StringUtils::StartsWithNoCase(strPath, "zip://"))
769       URIUtils::GetParentPath(strPath,strPath1);
770 
771     URIUtils::AddSlashAtEnd(strPath1);
772 
773     int idParentPath = GetPathId(parentPath.empty() ? URIUtils::GetParentPath(strPath1) : parentPath);
774 
775     // add the path
776     if (idParentPath < 0)
777     {
778       if (dateAdded.IsValid())
779         strSQL=PrepareSQL("insert into path (idPath, strPath, dateAdded) values (NULL, '%s', '%s')", strPath1.c_str(), dateAdded.GetAsDBDateTime().c_str());
780       else
781         strSQL=PrepareSQL("insert into path (idPath, strPath) values (NULL, '%s')", strPath1.c_str());
782     }
783     else
784     {
785       if (dateAdded.IsValid())
786         strSQL = PrepareSQL("insert into path (idPath, strPath, dateAdded, idParentPath) values (NULL, '%s', '%s', %i)", strPath1.c_str(), dateAdded.GetAsDBDateTime().c_str(), idParentPath);
787       else
788         strSQL=PrepareSQL("insert into path (idPath, strPath, idParentPath) values (NULL, '%s', %i)", strPath1.c_str(), idParentPath);
789     }
790     m_pDS->exec(strSQL);
791     idPath = (int)m_pDS->lastinsertid();
792     return idPath;
793   }
794   catch (...)
795   {
796     CLog::Log(LOGERROR, "%s unable to addpath (%s)", __FUNCTION__, strSQL.c_str());
797   }
798   return -1;
799 }
800 
GetPathHash(const std::string & path,std::string & hash)801 bool CVideoDatabase::GetPathHash(const std::string &path, std::string &hash)
802 {
803   try
804   {
805     if (nullptr == m_pDB)
806       return false;
807     if (nullptr == m_pDS)
808       return false;
809 
810     std::string strSQL=PrepareSQL("select strHash from path where strPath='%s'", path.c_str());
811     m_pDS->query(strSQL);
812     if (m_pDS->num_rows() == 0)
813       return false;
814     hash = m_pDS->fv("strHash").get_asString();
815     return true;
816   }
817   catch (...)
818   {
819     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, path.c_str());
820   }
821 
822   return false;
823 }
824 
GetSourcePath(const std::string & path,std::string & sourcePath)825 bool CVideoDatabase::GetSourcePath(const std::string &path, std::string &sourcePath)
826 {
827   SScanSettings dummy;
828   return GetSourcePath(path, sourcePath, dummy);
829 }
830 
GetSourcePath(const std::string & path,std::string & sourcePath,SScanSettings & settings)831 bool CVideoDatabase::GetSourcePath(const std::string &path, std::string &sourcePath, SScanSettings& settings)
832 {
833   try
834   {
835     if (path.empty() || m_pDB == nullptr || m_pDS == nullptr)
836       return false;
837 
838     std::string strPath2;
839 
840     if (URIUtils::IsMultiPath(path))
841       strPath2 = CMultiPathDirectory::GetFirstPath(path);
842     else
843       strPath2 = path;
844 
845     std::string strPath1 = URIUtils::GetDirectory(strPath2);
846     int idPath = GetPathId(strPath1);
847 
848     if (idPath > -1)
849     {
850       // check if the given path already is a source itself
851       std::string strSQL = PrepareSQL("SELECT path.useFolderNames, path.scanRecursive, path.noUpdate, path.exclude FROM path WHERE "
852                                         "path.idPath = %i AND "
853                                         "path.strContent IS NOT NULL AND path.strContent != '' AND "
854                                         "path.strScraper IS NOT NULL AND path.strScraper != ''", idPath);
855       if (m_pDS->query(strSQL) && !m_pDS->eof())
856       {
857         settings.parent_name_root = settings.parent_name = m_pDS->fv(0).get_asBool();
858         settings.recurse = m_pDS->fv(1).get_asInt();
859         settings.noupdate = m_pDS->fv(2).get_asBool();
860         settings.exclude = m_pDS->fv(3).get_asBool();
861 
862         m_pDS->close();
863         sourcePath = path;
864         return true;
865       }
866     }
867 
868     // look for parent paths until there is one which is a source
869     std::string strParent;
870     bool found = false;
871     while (URIUtils::GetParentPath(strPath1, strParent))
872     {
873       std::string strSQL = PrepareSQL("SELECT path.strContent, path.strScraper, path.scanRecursive, path.useFolderNames, path.noUpdate, path.exclude FROM path WHERE strPath = '%s'", strParent.c_str());
874       if (m_pDS->query(strSQL) && !m_pDS->eof())
875       {
876         std::string strContent = m_pDS->fv(0).get_asString();
877         std::string strScraper = m_pDS->fv(1).get_asString();
878         if (!strContent.empty() && !strScraper.empty())
879         {
880           settings.parent_name_root = settings.parent_name = m_pDS->fv(2).get_asBool();
881           settings.recurse = m_pDS->fv(3).get_asInt();
882           settings.noupdate = m_pDS->fv(4).get_asBool();
883           settings.exclude = m_pDS->fv(5).get_asBool();
884           found = true;
885           break;
886         }
887       }
888 
889       strPath1 = strParent;
890     }
891     m_pDS->close();
892 
893     if (found)
894     {
895       sourcePath = strParent;
896       return true;
897     }
898   }
899   catch (...)
900   {
901     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
902   }
903   return false;
904 }
905 
906 //********************************************************************************************************************************
AddFile(const std::string & strFileNameAndPath)907 int CVideoDatabase::AddFile(const std::string& strFileNameAndPath)
908 {
909   std::string strSQL = "";
910   try
911   {
912     int idFile;
913     if (nullptr == m_pDB)
914       return -1;
915     if (nullptr == m_pDS)
916       return -1;
917 
918     std::string strFileName, strPath;
919     SplitPath(strFileNameAndPath,strPath,strFileName);
920 
921     int idPath = AddPath(strPath);
922     if (idPath < 0)
923       return -1;
924 
925     std::string strSQL=PrepareSQL("select idFile from files where strFileName='%s' and idPath=%i", strFileName.c_str(),idPath);
926 
927     m_pDS->query(strSQL);
928     if (m_pDS->num_rows() > 0)
929     {
930       idFile = m_pDS->fv("idFile").get_asInt() ;
931       m_pDS->close();
932       return idFile;
933     }
934     m_pDS->close();
935 
936     strSQL=PrepareSQL("insert into files (idFile, idPath, strFileName) values(NULL, %i, '%s')", idPath, strFileName.c_str());
937     m_pDS->exec(strSQL);
938     idFile = (int)m_pDS->lastinsertid();
939     return idFile;
940   }
941   catch (...)
942   {
943     CLog::Log(LOGERROR, "%s unable to addfile (%s)", __FUNCTION__, strSQL.c_str());
944   }
945   return -1;
946 }
947 
AddFile(const CFileItem & item)948 int CVideoDatabase::AddFile(const CFileItem& item)
949 {
950   if (item.IsVideoDb() && item.HasVideoInfoTag())
951   {
952     if (item.GetVideoInfoTag()->m_iFileId != -1)
953       return item.GetVideoInfoTag()->m_iFileId;
954     else
955       return AddFile(item.GetVideoInfoTag()->m_strFileNameAndPath);
956   }
957   return AddFile(item.GetPath());
958 }
959 
UpdateFileDateAdded(int idFile,const std::string & strFileNameAndPath,const CDateTime & dateAdded)960 void CVideoDatabase::UpdateFileDateAdded(int idFile, const std::string& strFileNameAndPath, const CDateTime& dateAdded /* = CDateTime() */)
961 {
962   if (idFile < 0 || strFileNameAndPath.empty())
963     return;
964 
965   CDateTime finalDateAdded = dateAdded;
966   try
967   {
968     if (nullptr == m_pDB)
969       return;
970     if (nullptr == m_pDS)
971       return;
972 
973     if (!finalDateAdded.IsValid())
974     {
975       // Supress warnings if we have plugin source
976       if (!URIUtils::IsPlugin(strFileNameAndPath))
977       {
978         // 1 preferring to use the files mtime(if it's valid) and only using the file's ctime if the mtime isn't valid
979         if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryDateAdded == 1)
980           finalDateAdded = CFileUtils::GetModificationDate(strFileNameAndPath, false);
981         //2 using the newer datetime of the file's mtime and ctime
982         else if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryDateAdded == 2)
983           finalDateAdded = CFileUtils::GetModificationDate(strFileNameAndPath, true);
984       }
985       //0 using the current datetime if non of the above matches or one returns an invalid datetime
986       if (!finalDateAdded.IsValid())
987         finalDateAdded = CDateTime::GetCurrentDateTime();
988     }
989 
990     m_pDS->exec(PrepareSQL("UPDATE files SET dateAdded='%s' WHERE idFile=%d", finalDateAdded.GetAsDBDateTime().c_str(), idFile));
991   }
992   catch (...)
993   {
994     CLog::Log(LOGERROR, "%s (%s, %s) failed", __FUNCTION__, CURL::GetRedacted(strFileNameAndPath).c_str(), finalDateAdded.GetAsDBDateTime().c_str());
995   }
996 }
997 
SetPathHash(const std::string & path,const std::string & hash)998 bool CVideoDatabase::SetPathHash(const std::string &path, const std::string &hash)
999 {
1000   try
1001   {
1002     if (nullptr == m_pDB)
1003       return false;
1004     if (nullptr == m_pDS)
1005       return false;
1006 
1007     int idPath = AddPath(path);
1008     if (idPath < 0) return false;
1009 
1010     std::string strSQL=PrepareSQL("update path set strHash='%s' where idPath=%ld", hash.c_str(), idPath);
1011     m_pDS->exec(strSQL);
1012 
1013     return true;
1014   }
1015   catch (...)
1016   {
1017     CLog::Log(LOGERROR, "%s (%s, %s) failed", __FUNCTION__, path.c_str(), hash.c_str());
1018   }
1019 
1020   return false;
1021 }
1022 
LinkMovieToTvshow(int idMovie,int idShow,bool bRemove)1023 bool CVideoDatabase::LinkMovieToTvshow(int idMovie, int idShow, bool bRemove)
1024 {
1025    try
1026   {
1027     if (nullptr == m_pDB)
1028       return false;
1029     if (nullptr == m_pDS)
1030       return false;
1031 
1032     if (bRemove) // delete link
1033     {
1034       std::string strSQL=PrepareSQL("delete from movielinktvshow where idMovie=%i and idShow=%i", idMovie, idShow);
1035       m_pDS->exec(strSQL);
1036       return true;
1037     }
1038 
1039     std::string strSQL=PrepareSQL("insert into movielinktvshow (idShow,idMovie) values (%i,%i)", idShow,idMovie);
1040     m_pDS->exec(strSQL);
1041 
1042     return true;
1043   }
1044   catch (...)
1045   {
1046     CLog::Log(LOGERROR, "%s (%i, %i) failed", __FUNCTION__, idMovie, idShow);
1047   }
1048 
1049   return false;
1050 }
1051 
IsLinkedToTvshow(int idMovie)1052 bool CVideoDatabase::IsLinkedToTvshow(int idMovie)
1053 {
1054    try
1055   {
1056     if (nullptr == m_pDB)
1057       return false;
1058     if (nullptr == m_pDS)
1059       return false;
1060 
1061     std::string strSQL=PrepareSQL("select * from movielinktvshow where idMovie=%i", idMovie);
1062     m_pDS->query(strSQL);
1063     if (m_pDS->eof())
1064     {
1065       m_pDS->close();
1066       return false;
1067     }
1068 
1069     m_pDS->close();
1070     return true;
1071   }
1072   catch (...)
1073   {
1074     CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, idMovie);
1075   }
1076 
1077   return false;
1078 }
1079 
GetLinksToTvShow(int idMovie,std::vector<int> & ids)1080 bool CVideoDatabase::GetLinksToTvShow(int idMovie, std::vector<int>& ids)
1081 {
1082    try
1083   {
1084     if (nullptr == m_pDB)
1085       return false;
1086     if (nullptr == m_pDS)
1087       return false;
1088 
1089     std::string strSQL=PrepareSQL("select * from movielinktvshow where idMovie=%i", idMovie);
1090     m_pDS2->query(strSQL);
1091     while (!m_pDS2->eof())
1092     {
1093       ids.push_back(m_pDS2->fv(1).get_asInt());
1094       m_pDS2->next();
1095     }
1096 
1097     m_pDS2->close();
1098     return true;
1099   }
1100   catch (...)
1101   {
1102     CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, idMovie);
1103   }
1104 
1105   return false;
1106 }
1107 
1108 
1109 //********************************************************************************************************************************
GetFileId(const std::string & strFilenameAndPath)1110 int CVideoDatabase::GetFileId(const std::string& strFilenameAndPath)
1111 {
1112   try
1113   {
1114     if (nullptr == m_pDB)
1115       return -1;
1116     if (nullptr == m_pDS)
1117       return -1;
1118     std::string strPath, strFileName;
1119     SplitPath(strFilenameAndPath,strPath,strFileName);
1120 
1121     int idPath = GetPathId(strPath);
1122     if (idPath >= 0)
1123     {
1124       std::string strSQL;
1125       strSQL=PrepareSQL("select idFile from files where strFileName='%s' and idPath=%i", strFileName.c_str(),idPath);
1126       m_pDS->query(strSQL);
1127       if (m_pDS->num_rows() > 0)
1128       {
1129         int idFile = m_pDS->fv("files.idFile").get_asInt();
1130         m_pDS->close();
1131         return idFile;
1132       }
1133     }
1134   }
1135   catch (...)
1136   {
1137     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
1138   }
1139   return -1;
1140 }
1141 
GetFileId(const CFileItem & item)1142 int CVideoDatabase::GetFileId(const CFileItem &item)
1143 {
1144   if (item.IsVideoDb() && item.HasVideoInfoTag())
1145   {
1146     if (item.GetVideoInfoTag()->m_iFileId != -1)
1147       return item.GetVideoInfoTag()->m_iFileId;
1148     else
1149       return GetFileId(item.GetVideoInfoTag()->m_strFileNameAndPath);
1150   }
1151   return GetFileId(item.GetPath());
1152 }
1153 
1154 //********************************************************************************************************************************
GetMovieId(const std::string & strFilenameAndPath)1155 int CVideoDatabase::GetMovieId(const std::string& strFilenameAndPath)
1156 {
1157   try
1158   {
1159     if (nullptr == m_pDB)
1160       return -1;
1161     if (nullptr == m_pDS)
1162       return -1;
1163     int idMovie = -1;
1164 
1165     // needed for query parameters
1166     int idFile = GetFileId(strFilenameAndPath);
1167     int idPath=-1;
1168     std::string strPath;
1169     if (idFile < 0)
1170     {
1171       std::string strFile;
1172       SplitPath(strFilenameAndPath,strPath,strFile);
1173 
1174       // have to join movieinfo table for correct results
1175       idPath = GetPathId(strPath);
1176       if (idPath < 0 && strPath != strFilenameAndPath)
1177         return -1;
1178     }
1179 
1180     if (idFile == -1 && strPath != strFilenameAndPath)
1181       return -1;
1182 
1183     std::string strSQL;
1184     if (idFile == -1)
1185       strSQL=PrepareSQL("select idMovie from movie join files on files.idFile=movie.idFile where files.idPath=%i",idPath);
1186     else
1187       strSQL=PrepareSQL("select idMovie from movie where idFile=%i", idFile);
1188 
1189     CLog::Log(LOGDEBUG, LOGDATABASE, "%s (%s), query = %s", __FUNCTION__, CURL::GetRedacted(strFilenameAndPath).c_str(), strSQL.c_str());
1190     m_pDS->query(strSQL);
1191     if (m_pDS->num_rows() > 0)
1192       idMovie = m_pDS->fv("idMovie").get_asInt();
1193     m_pDS->close();
1194 
1195     return idMovie;
1196   }
1197   catch (...)
1198   {
1199     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
1200   }
1201   return -1;
1202 }
1203 
GetTvShowId(const std::string & strPath)1204 int CVideoDatabase::GetTvShowId(const std::string& strPath)
1205 {
1206   try
1207   {
1208     if (nullptr == m_pDB)
1209       return -1;
1210     if (nullptr == m_pDS)
1211       return -1;
1212     int idTvShow = -1;
1213 
1214     // have to join movieinfo table for correct results
1215     int idPath = GetPathId(strPath);
1216     if (idPath < 0)
1217       return -1;
1218 
1219     std::string strSQL;
1220     std::string strPath1=strPath;
1221     std::string strParent;
1222     int iFound=0;
1223 
1224     strSQL=PrepareSQL("select idShow from tvshowlinkpath where tvshowlinkpath.idPath=%i",idPath);
1225     m_pDS->query(strSQL);
1226     if (!m_pDS->eof())
1227       iFound = 1;
1228 
1229     while (iFound == 0 && URIUtils::GetParentPath(strPath1, strParent))
1230     {
1231       strSQL=PrepareSQL("SELECT idShow FROM path INNER JOIN tvshowlinkpath ON tvshowlinkpath.idPath=path.idPath WHERE strPath='%s'",strParent.c_str());
1232       m_pDS->query(strSQL);
1233       if (!m_pDS->eof())
1234       {
1235         int idShow = m_pDS->fv("idShow").get_asInt();
1236         if (idShow != -1)
1237           iFound = 2;
1238       }
1239       strPath1 = strParent;
1240     }
1241 
1242     if (m_pDS->num_rows() > 0)
1243       idTvShow = m_pDS->fv("idShow").get_asInt();
1244     m_pDS->close();
1245 
1246     return idTvShow;
1247   }
1248   catch (...)
1249   {
1250     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strPath.c_str());
1251   }
1252   return -1;
1253 }
1254 
GetEpisodeId(const std::string & strFilenameAndPath,int idEpisode,int idSeason)1255 int CVideoDatabase::GetEpisodeId(const std::string& strFilenameAndPath, int idEpisode, int idSeason) // input value is episode/season number hint - for multiparters
1256 {
1257   try
1258   {
1259     if (nullptr == m_pDB)
1260       return -1;
1261     if (nullptr == m_pDS)
1262       return -1;
1263 
1264     // need this due to the nested GetEpisodeInfo query
1265     std::unique_ptr<Dataset> pDS;
1266     pDS.reset(m_pDB->CreateDataset());
1267     if (nullptr == pDS)
1268       return -1;
1269 
1270     int idFile = GetFileId(strFilenameAndPath);
1271     if (idFile < 0)
1272       return -1;
1273 
1274     std::string strSQL=PrepareSQL("select idEpisode from episode where idFile=%i", idFile);
1275 
1276     CLog::Log(LOGDEBUG, LOGDATABASE, "%s (%s), query = %s", __FUNCTION__, CURL::GetRedacted(strFilenameAndPath).c_str(), strSQL.c_str());
1277     pDS->query(strSQL);
1278     if (pDS->num_rows() > 0)
1279     {
1280       if (idEpisode == -1)
1281         idEpisode = pDS->fv("episode.idEpisode").get_asInt();
1282       else // use the hint!
1283       {
1284         while (!pDS->eof())
1285         {
1286           CVideoInfoTag tag;
1287           int idTmpEpisode = pDS->fv("episode.idEpisode").get_asInt();
1288           GetEpisodeBasicInfo(strFilenameAndPath, tag, idTmpEpisode);
1289           if (tag.m_iEpisode == idEpisode && (idSeason == -1 || tag.m_iSeason == idSeason)) {
1290             // match on the episode hint, and there's no season hint or a season hint match
1291             idEpisode = idTmpEpisode;
1292             break;
1293           }
1294           pDS->next();
1295         }
1296         if (pDS->eof())
1297           idEpisode = -1;
1298       }
1299     }
1300     else
1301       idEpisode = -1;
1302 
1303     pDS->close();
1304 
1305     return idEpisode;
1306   }
1307   catch (...)
1308   {
1309     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
1310   }
1311   return -1;
1312 }
1313 
GetMusicVideoId(const std::string & strFilenameAndPath)1314 int CVideoDatabase::GetMusicVideoId(const std::string& strFilenameAndPath)
1315 {
1316   try
1317   {
1318     if (nullptr == m_pDB)
1319       return -1;
1320     if (nullptr == m_pDS)
1321       return -1;
1322 
1323     int idFile = GetFileId(strFilenameAndPath);
1324     if (idFile < 0)
1325       return -1;
1326 
1327     std::string strSQL=PrepareSQL("select idMVideo from musicvideo where idFile=%i", idFile);
1328 
1329     CLog::Log(LOGDEBUG, LOGDATABASE, "%s (%s), query = %s", __FUNCTION__, CURL::GetRedacted(strFilenameAndPath).c_str(), strSQL.c_str());
1330     m_pDS->query(strSQL);
1331     int idMVideo=-1;
1332     if (m_pDS->num_rows() > 0)
1333       idMVideo = m_pDS->fv("idMVideo").get_asInt();
1334     m_pDS->close();
1335 
1336     return idMVideo;
1337   }
1338   catch (...)
1339   {
1340     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
1341   }
1342   return -1;
1343 }
1344 
1345 //********************************************************************************************************************************
AddMovie(const std::string & strFilenameAndPath)1346 int CVideoDatabase::AddMovie(const std::string& strFilenameAndPath)
1347 {
1348   try
1349   {
1350     if (nullptr == m_pDB)
1351       return -1;
1352     if (nullptr == m_pDS)
1353       return -1;
1354 
1355     int idMovie = GetMovieId(strFilenameAndPath);
1356     if (idMovie < 0)
1357     {
1358       int idFile = AddFile(strFilenameAndPath);
1359       if (idFile < 0)
1360         return -1;
1361       UpdateFileDateAdded(idFile, strFilenameAndPath);
1362       std::string strSQL=PrepareSQL("insert into movie (idMovie, idFile) values (NULL, %i)", idFile);
1363       m_pDS->exec(strSQL);
1364       idMovie = (int)m_pDS->lastinsertid();
1365     }
1366 
1367     return idMovie;
1368   }
1369   catch (...)
1370   {
1371     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
1372   }
1373   return -1;
1374 }
1375 
AddPathToTvShow(int idShow,const std::string & path,const std::string & parentPath,const CDateTime & dateAdded)1376 bool CVideoDatabase::AddPathToTvShow(int idShow, const std::string &path, const std::string &parentPath, const CDateTime& dateAdded /* = CDateTime() */)
1377 {
1378   // Check if this path is already added
1379   int idPath = GetPathId(path);
1380   if (idPath < 0)
1381   {
1382     CDateTime finalDateAdded = dateAdded;
1383     // Skip looking at the files ctime/mtime if defined by the user through as.xml
1384     if (!finalDateAdded.IsValid() && CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryDateAdded > 0)
1385     {
1386       struct __stat64 buffer;
1387       if (XFILE::CFile::Stat(path, &buffer) == 0)
1388       {
1389         time_t now = time(NULL);
1390         // Make sure we have a valid date (i.e. not in the future)
1391         if ((time_t)buffer.st_ctime <= now)
1392         {
1393           struct tm *time;
1394 #ifdef HAVE_LOCALTIME_R
1395           struct tm result = {};
1396           time = localtime_r((const time_t*)&buffer.st_ctime, &result);
1397 #else
1398           time = localtime((const time_t*)&buffer.st_ctime);
1399 #endif
1400           if (time)
1401             finalDateAdded = *time;
1402         }
1403       }
1404     }
1405 
1406     if (!finalDateAdded.IsValid())
1407       finalDateAdded = CDateTime::GetCurrentDateTime();
1408 
1409     idPath = AddPath(path, parentPath, finalDateAdded);
1410   }
1411 
1412   return ExecuteQuery(PrepareSQL("REPLACE INTO tvshowlinkpath(idShow, idPath) VALUES (%i,%i)", idShow, idPath));
1413 }
1414 
AddTvShow()1415 int CVideoDatabase::AddTvShow()
1416 {
1417   if (ExecuteQuery("INSERT INTO tvshow(idShow) VALUES(NULL)"))
1418     return (int)m_pDS->lastinsertid();
1419   return -1;
1420 }
1421 
1422 //********************************************************************************************************************************
AddEpisode(int idShow,const std::string & strFilenameAndPath)1423 int CVideoDatabase::AddEpisode(int idShow, const std::string& strFilenameAndPath)
1424 {
1425   try
1426   {
1427     if (nullptr == m_pDB)
1428       return -1;
1429     if (nullptr == m_pDS)
1430       return -1;
1431 
1432     int idFile = AddFile(strFilenameAndPath);
1433     if (idFile < 0)
1434       return -1;
1435     UpdateFileDateAdded(idFile, strFilenameAndPath);
1436 
1437     std::string strSQL=PrepareSQL("insert into episode (idEpisode, idFile, idShow) values (NULL, %i, %i)", idFile, idShow);
1438     m_pDS->exec(strSQL);
1439     return (int)m_pDS->lastinsertid();
1440   }
1441   catch (...)
1442   {
1443     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
1444   }
1445   return -1;
1446 }
1447 
AddMusicVideo(const std::string & strFilenameAndPath)1448 int CVideoDatabase::AddMusicVideo(const std::string& strFilenameAndPath)
1449 {
1450   try
1451   {
1452     if (nullptr == m_pDB)
1453       return -1;
1454     if (nullptr == m_pDS)
1455       return -1;
1456 
1457     int idMVideo = GetMusicVideoId(strFilenameAndPath);
1458     if (idMVideo < 0)
1459     {
1460       int idFile = AddFile(strFilenameAndPath);
1461       if (idFile < 0)
1462         return -1;
1463       UpdateFileDateAdded(idFile, strFilenameAndPath);
1464       std::string strSQL=PrepareSQL("insert into musicvideo (idMVideo, idFile) values (NULL, %i)", idFile);
1465       m_pDS->exec(strSQL);
1466       idMVideo = (int)m_pDS->lastinsertid();
1467     }
1468 
1469     return idMVideo;
1470   }
1471   catch (...)
1472   {
1473     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
1474   }
1475   return -1;
1476 }
1477 
1478 //********************************************************************************************************************************
AddToTable(const std::string & table,const std::string & firstField,const std::string & secondField,const std::string & value)1479 int CVideoDatabase::AddToTable(const std::string& table, const std::string& firstField, const std::string& secondField, const std::string& value)
1480 {
1481   try
1482   {
1483     if (nullptr == m_pDB)
1484       return -1;
1485     if (nullptr == m_pDS)
1486       return -1;
1487 
1488     std::string strSQL = PrepareSQL("select %s from %s where %s like '%s'", firstField.c_str(), table.c_str(), secondField.c_str(), value.substr(0, 255).c_str());
1489     m_pDS->query(strSQL);
1490     if (m_pDS->num_rows() == 0)
1491     {
1492       m_pDS->close();
1493       // doesn't exists, add it
1494       strSQL = PrepareSQL("insert into %s (%s, %s) values(NULL, '%s')", table.c_str(), firstField.c_str(), secondField.c_str(), value.substr(0, 255).c_str());
1495       m_pDS->exec(strSQL);
1496       int id = (int)m_pDS->lastinsertid();
1497       return id;
1498     }
1499     else
1500     {
1501       int id = m_pDS->fv(firstField.c_str()).get_asInt();
1502       m_pDS->close();
1503       return id;
1504     }
1505   }
1506   catch (...)
1507   {
1508     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, value.c_str() );
1509   }
1510 
1511   return -1;
1512 }
1513 
UpdateRatings(int mediaId,const char * mediaType,const RatingMap & values,const std::string & defaultRating)1514 int CVideoDatabase::UpdateRatings(int mediaId, const char *mediaType, const RatingMap& values, const std::string& defaultRating)
1515 {
1516   try
1517   {
1518     if (nullptr == m_pDB)
1519       return -1;
1520     if (nullptr == m_pDS)
1521       return -1;
1522 
1523     std::string sql = PrepareSQL("DELETE FROM rating WHERE media_id=%i AND media_type='%s'", mediaId, mediaType);
1524     m_pDS->exec(sql);
1525 
1526     return AddRatings(mediaId, mediaType, values, defaultRating);
1527   }
1528   catch (...)
1529   {
1530     CLog::Log(LOGERROR, "%s unable to update ratings of  (%s)", __FUNCTION__, mediaType);
1531   }
1532   return -1;
1533 }
1534 
AddRatings(int mediaId,const char * mediaType,const RatingMap & values,const std::string & defaultRating)1535 int CVideoDatabase::AddRatings(int mediaId, const char *mediaType, const RatingMap& values, const std::string& defaultRating)
1536 {
1537   int ratingid = -1;
1538   try
1539   {
1540     if (nullptr == m_pDB)
1541       return -1;
1542     if (nullptr == m_pDS)
1543       return -1;
1544 
1545     for (const auto& i : values)
1546     {
1547       int id;
1548       std::string strSQL = PrepareSQL("SELECT rating_id FROM rating WHERE media_id=%i AND media_type='%s' AND rating_type = '%s'", mediaId, mediaType, i.first.c_str());
1549       m_pDS->query(strSQL);
1550       if (m_pDS->num_rows() == 0)
1551       {
1552         m_pDS->close();
1553         // doesn't exists, add it
1554         strSQL = PrepareSQL("INSERT INTO rating (media_id, media_type, rating_type, rating, votes) VALUES (%i, '%s', '%s', %f, %i)", mediaId, mediaType, i.first.c_str(), i.second.rating, i.second.votes);
1555         m_pDS->exec(strSQL);
1556         id = (int)m_pDS->lastinsertid();
1557       }
1558       else
1559       {
1560         id = m_pDS->fv(0).get_asInt();
1561         m_pDS->close();
1562         strSQL = PrepareSQL("UPDATE rating SET rating = %f, votes = %i WHERE rating_id = %i", i.second.rating, i.second.votes, id);
1563         m_pDS->exec(strSQL);
1564       }
1565       if (i.first == defaultRating)
1566         ratingid = id;
1567     }
1568     return ratingid;
1569 
1570   }
1571   catch (...)
1572   {
1573     CLog::Log(LOGERROR, "%s (%i - %s) failed", __FUNCTION__, mediaId, mediaType);
1574   }
1575 
1576   return ratingid;
1577 }
1578 
UpdateUniqueIDs(int mediaId,const char * mediaType,const CVideoInfoTag & details)1579 int CVideoDatabase::UpdateUniqueIDs(int mediaId, const char *mediaType, const CVideoInfoTag& details)
1580 {
1581   try
1582   {
1583     if (nullptr == m_pDB)
1584       return -1;
1585     if (nullptr == m_pDS)
1586       return -1;
1587 
1588     std::string sql = PrepareSQL("DELETE FROM uniqueid WHERE media_id=%i AND media_type='%s'", mediaId, mediaType);
1589     m_pDS->exec(sql);
1590 
1591     return AddUniqueIDs(mediaId, mediaType, details);
1592   }
1593   catch (...)
1594   {
1595     CLog::Log(LOGERROR, "%s unable to update unique ids of (%s)", __FUNCTION__, mediaType);
1596   }
1597   return -1;
1598 }
1599 
AddUniqueIDs(int mediaId,const char * mediaType,const CVideoInfoTag & details)1600 int CVideoDatabase::AddUniqueIDs(int mediaId, const char *mediaType, const CVideoInfoTag& details)
1601 {
1602   int uniqueid = -1;
1603   try
1604   {
1605     if (nullptr == m_pDB)
1606       return -1;
1607     if (nullptr == m_pDS)
1608       return -1;
1609 
1610     for (const auto& i : details.GetUniqueIDs())
1611     {
1612       int id;
1613       std::string strSQL = PrepareSQL("SELECT uniqueid_id FROM uniqueid WHERE media_id=%i AND media_type='%s' AND type = '%s'", mediaId, mediaType, i.first.c_str());
1614       m_pDS->query(strSQL);
1615       if (m_pDS->num_rows() == 0)
1616       {
1617         m_pDS->close();
1618         // doesn't exists, add it
1619         strSQL = PrepareSQL("INSERT INTO uniqueid (media_id, media_type, value, type) VALUES (%i, '%s', '%s', '%s')", mediaId, mediaType, i.second.c_str(), i.first.c_str());
1620         m_pDS->exec(strSQL);
1621         id = (int)m_pDS->lastinsertid();
1622       }
1623       else
1624       {
1625         id = m_pDS->fv(0).get_asInt();
1626         m_pDS->close();
1627         strSQL = PrepareSQL("UPDATE uniqueid SET value = '%s', type = '%s' WHERE uniqueid_id = %i", i.second.c_str(), i.first.c_str(), id);
1628         m_pDS->exec(strSQL);
1629       }
1630       if (i.first == details.GetDefaultUniqueID())
1631         uniqueid = id;
1632     }
1633     return uniqueid;
1634 
1635   }
1636   catch (...)
1637   {
1638     CLog::Log(LOGERROR, "%s (%i - %s) failed", __FUNCTION__, mediaId, mediaType);
1639   }
1640 
1641   return uniqueid;
1642 }
1643 
AddSet(const std::string & strSet,const std::string & strOverview)1644 int CVideoDatabase::AddSet(const std::string& strSet, const std::string& strOverview /* = "" */)
1645 {
1646   if (strSet.empty())
1647     return -1;
1648 
1649   try
1650   {
1651     if (m_pDB == nullptr || m_pDS == nullptr)
1652       return -1;
1653 
1654     std::string strSQL = PrepareSQL("SELECT idSet FROM sets WHERE strSet LIKE '%s'", strSet.c_str());
1655     m_pDS->query(strSQL);
1656     if (m_pDS->num_rows() == 0)
1657     {
1658       m_pDS->close();
1659       strSQL = PrepareSQL("INSERT INTO sets (idSet, strSet, strOverview) VALUES(NULL, '%s', '%s')", strSet.c_str(), strOverview.c_str());
1660       m_pDS->exec(strSQL);
1661       int id = static_cast<int>(m_pDS->lastinsertid());
1662       return id;
1663     }
1664     else
1665     {
1666       int id = m_pDS->fv("idSet").get_asInt();
1667       m_pDS->close();
1668       return id;
1669     }
1670   }
1671   catch (...)
1672   {
1673     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSet.c_str());
1674   }
1675 
1676   return -1;
1677 }
1678 
AddTag(const std::string & name)1679 int CVideoDatabase::AddTag(const std::string& name)
1680 {
1681   if (name.empty())
1682     return -1;
1683 
1684   return AddToTable("tag", "tag_id", "name", name);
1685 }
1686 
AddActor(const std::string & name,const std::string & thumbURLs,const std::string & thumb)1687 int CVideoDatabase::AddActor(const std::string& name, const std::string& thumbURLs, const std::string &thumb)
1688 {
1689   try
1690   {
1691     if (nullptr == m_pDB)
1692       return -1;
1693     if (nullptr == m_pDS)
1694       return -1;
1695     int idActor = -1;
1696 
1697     // ATTENTION: the trimming of actor names should really not be done here but after the scraping / NFO-parsing
1698     std::string trimmedName = name.c_str();
1699     StringUtils::Trim(trimmedName);
1700 
1701     std::string strSQL=PrepareSQL("select actor_id from actor where name like '%s'", trimmedName.substr(0, 255).c_str());
1702     m_pDS->query(strSQL);
1703     if (m_pDS->num_rows() == 0)
1704     {
1705       m_pDS->close();
1706       // doesn't exists, add it
1707       strSQL=PrepareSQL("insert into actor (actor_id, name, art_urls) values(NULL, '%s', '%s')", trimmedName.substr(0,255).c_str(), thumbURLs.c_str());
1708       m_pDS->exec(strSQL);
1709       idActor = (int)m_pDS->lastinsertid();
1710     }
1711     else
1712     {
1713       idActor = m_pDS->fv(0).get_asInt();
1714       m_pDS->close();
1715       // update the thumb url's
1716       if (!thumbURLs.empty())
1717       {
1718         strSQL=PrepareSQL("update actor set art_urls = '%s' where actor_id = %i", thumbURLs.c_str(), idActor);
1719         m_pDS->exec(strSQL);
1720       }
1721     }
1722     // add artwork
1723     if (!thumb.empty())
1724       SetArtForItem(idActor, "actor", "thumb", thumb);
1725     return idActor;
1726   }
1727   catch (...)
1728   {
1729     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, name.c_str() );
1730   }
1731   return -1;
1732 }
1733 
1734 
1735 
AddLinkToActor(int mediaId,const char * mediaType,int actorId,const std::string & role,int order)1736 void CVideoDatabase::AddLinkToActor(int mediaId, const char *mediaType, int actorId, const std::string &role, int order)
1737 {
1738   std::string sql = PrepareSQL("SELECT 1 FROM actor_link WHERE actor_id=%i AND "
1739                                "media_id=%i AND media_type='%s' AND role='%s'",
1740                                actorId, mediaId, mediaType, role.c_str());
1741 
1742   if (GetSingleValue(sql).empty())
1743   { // doesn't exists, add it
1744     sql = PrepareSQL("INSERT INTO actor_link (actor_id, media_id, media_type, role, cast_order) VALUES(%i,%i,'%s','%s',%i)", actorId, mediaId, mediaType, role.c_str(), order);
1745     ExecuteQuery(sql);
1746   }
1747 }
1748 
AddToLinkTable(int mediaId,const std::string & mediaType,const std::string & table,int valueId,const char * foreignKey)1749 void CVideoDatabase::AddToLinkTable(int mediaId, const std::string& mediaType, const std::string& table, int valueId, const char *foreignKey)
1750 {
1751   const char *key = foreignKey ? foreignKey : table.c_str();
1752   std::string sql = PrepareSQL("SELECT 1 FROM %s_link WHERE %s_id=%i AND media_id=%i AND media_type='%s'", table.c_str(), key, valueId, mediaId, mediaType.c_str());
1753 
1754   if (GetSingleValue(sql).empty())
1755   { // doesn't exists, add it
1756     sql = PrepareSQL("INSERT INTO %s_link (%s_id,media_id,media_type) VALUES(%i,%i,'%s')", table.c_str(), key, valueId, mediaId, mediaType.c_str());
1757     ExecuteQuery(sql);
1758   }
1759 }
1760 
RemoveFromLinkTable(int mediaId,const std::string & mediaType,const std::string & table,int valueId,const char * foreignKey)1761 void CVideoDatabase::RemoveFromLinkTable(int mediaId, const std::string& mediaType, const std::string& table, int valueId, const char *foreignKey)
1762 {
1763   const char *key = foreignKey ? foreignKey : table.c_str();
1764   std::string sql = PrepareSQL("DELETE FROM %s_link WHERE %s_id=%i AND media_id=%i AND media_type='%s'", table.c_str(), key, valueId, mediaId, mediaType.c_str());
1765 
1766   ExecuteQuery(sql);
1767 }
1768 
AddLinksToItem(int mediaId,const std::string & mediaType,const std::string & field,const std::vector<std::string> & values)1769 void CVideoDatabase::AddLinksToItem(int mediaId, const std::string& mediaType, const std::string& field, const std::vector<std::string>& values)
1770 {
1771   for (const auto &i : values)
1772   {
1773     if (!i.empty())
1774     {
1775       int idValue = AddToTable(field, field + "_id", "name", i);
1776       if (idValue > -1)
1777         AddToLinkTable(mediaId, mediaType, field, idValue);
1778     }
1779   }
1780 }
1781 
UpdateLinksToItem(int mediaId,const std::string & mediaType,const std::string & field,const std::vector<std::string> & values)1782 void CVideoDatabase::UpdateLinksToItem(int mediaId, const std::string& mediaType, const std::string& field, const std::vector<std::string>& values)
1783 {
1784   std::string sql = PrepareSQL("DELETE FROM %s_link WHERE media_id=%i AND media_type='%s'", field.c_str(), mediaId, mediaType.c_str());
1785   m_pDS->exec(sql);
1786 
1787   AddLinksToItem(mediaId, mediaType, field, values);
1788 }
1789 
AddActorLinksToItem(int mediaId,const std::string & mediaType,const std::string & field,const std::vector<std::string> & values)1790 void CVideoDatabase::AddActorLinksToItem(int mediaId, const std::string& mediaType, const std::string& field, const std::vector<std::string>& values)
1791 {
1792   for (const auto &i : values)
1793   {
1794     if (!i.empty())
1795     {
1796       int idValue = AddActor(i, "");
1797       if (idValue > -1)
1798         AddToLinkTable(mediaId, mediaType, field, idValue, "actor");
1799     }
1800   }
1801 }
1802 
UpdateActorLinksToItem(int mediaId,const std::string & mediaType,const std::string & field,const std::vector<std::string> & values)1803 void CVideoDatabase::UpdateActorLinksToItem(int mediaId, const std::string& mediaType, const std::string& field, const std::vector<std::string>& values)
1804 {
1805   std::string sql = PrepareSQL("DELETE FROM %s_link WHERE media_id=%i AND media_type='%s'", field.c_str(), mediaId, mediaType.c_str());
1806   m_pDS->exec(sql);
1807 
1808   AddActorLinksToItem(mediaId, mediaType, field, values);
1809 }
1810 
1811 //****Tags****
AddTagToItem(int media_id,int tag_id,const std::string & type)1812 void CVideoDatabase::AddTagToItem(int media_id, int tag_id, const std::string &type)
1813 {
1814   if (type.empty())
1815     return;
1816 
1817   AddToLinkTable(media_id, type, "tag", tag_id);
1818 }
1819 
RemoveTagFromItem(int media_id,int tag_id,const std::string & type)1820 void CVideoDatabase::RemoveTagFromItem(int media_id, int tag_id, const std::string &type)
1821 {
1822   if (type.empty())
1823     return;
1824 
1825   RemoveFromLinkTable(media_id, type, "tag", tag_id);
1826 }
1827 
RemoveTagsFromItem(int media_id,const std::string & type)1828 void CVideoDatabase::RemoveTagsFromItem(int media_id, const std::string &type)
1829 {
1830   if (type.empty())
1831     return;
1832 
1833   m_pDS2->exec(PrepareSQL("DELETE FROM tag_link WHERE media_id=%d AND media_type='%s'", media_id, type.c_str()));
1834 }
1835 
1836 //****Actors****
AddCast(int mediaId,const char * mediaType,const std::vector<SActorInfo> & cast)1837 void CVideoDatabase::AddCast(int mediaId, const char *mediaType, const std::vector< SActorInfo > &cast)
1838 {
1839   if (cast.empty())
1840     return;
1841 
1842   int order = std::max_element(cast.begin(), cast.end())->order;
1843   for (const auto &i : cast)
1844   {
1845     int idActor = AddActor(i.strName, i.thumbUrl.GetData(), i.thumb);
1846     AddLinkToActor(mediaId, mediaType, idActor, i.strRole, i.order >= 0 ? i.order : ++order);
1847   }
1848 }
1849 
1850 //********************************************************************************************************************************
LoadVideoInfo(const std::string & strFilenameAndPath,CVideoInfoTag & details,int getDetails)1851 bool CVideoDatabase::LoadVideoInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int getDetails /* = VideoDbDetailsAll */)
1852 {
1853   if (GetMovieInfo(strFilenameAndPath, details))
1854     return true;
1855   if (GetEpisodeInfo(strFilenameAndPath, details))
1856     return true;
1857   if (GetMusicVideoInfo(strFilenameAndPath, details))
1858     return true;
1859   if (GetFileInfo(strFilenameAndPath, details))
1860     return true;
1861 
1862   return false;
1863 }
1864 
HasMovieInfo(const std::string & strFilenameAndPath)1865 bool CVideoDatabase::HasMovieInfo(const std::string& strFilenameAndPath)
1866 {
1867   try
1868   {
1869     if (nullptr == m_pDB)
1870       return false;
1871     if (nullptr == m_pDS)
1872       return false;
1873     int idMovie = GetMovieId(strFilenameAndPath);
1874     return (idMovie > 0); // index of zero is also invalid
1875   }
1876   catch (...)
1877   {
1878     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
1879   }
1880   return false;
1881 }
1882 
HasTvShowInfo(const std::string & strPath)1883 bool CVideoDatabase::HasTvShowInfo(const std::string& strPath)
1884 {
1885   try
1886   {
1887     if (nullptr == m_pDB)
1888       return false;
1889     if (nullptr == m_pDS)
1890       return false;
1891     int idTvShow = GetTvShowId(strPath);
1892     return (idTvShow > 0); // index of zero is also invalid
1893   }
1894   catch (...)
1895   {
1896     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strPath.c_str());
1897   }
1898   return false;
1899 }
1900 
HasEpisodeInfo(const std::string & strFilenameAndPath)1901 bool CVideoDatabase::HasEpisodeInfo(const std::string& strFilenameAndPath)
1902 {
1903   try
1904   {
1905     if (nullptr == m_pDB)
1906       return false;
1907     if (nullptr == m_pDS)
1908       return false;
1909     int idEpisode = GetEpisodeId(strFilenameAndPath);
1910     return (idEpisode > 0); // index of zero is also invalid
1911   }
1912   catch (...)
1913   {
1914     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
1915   }
1916   return false;
1917 }
1918 
HasMusicVideoInfo(const std::string & strFilenameAndPath)1919 bool CVideoDatabase::HasMusicVideoInfo(const std::string& strFilenameAndPath)
1920 {
1921   try
1922   {
1923     if (nullptr == m_pDB)
1924       return false;
1925     if (nullptr == m_pDS)
1926       return false;
1927     int idMVideo = GetMusicVideoId(strFilenameAndPath);
1928     return (idMVideo > 0); // index of zero is also invalid
1929   }
1930   catch (...)
1931   {
1932     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
1933   }
1934   return false;
1935 }
1936 
DeleteDetailsForTvShow(const std::string & strPath)1937 void CVideoDatabase::DeleteDetailsForTvShow(const std::string& strPath)
1938 {
1939   int idTvShow = GetTvShowId(strPath);
1940   if (idTvShow >= 0)
1941     DeleteDetailsForTvShow(idTvShow);
1942 }
1943 
DeleteDetailsForTvShow(int idTvShow)1944 void CVideoDatabase::DeleteDetailsForTvShow(int idTvShow)
1945 {
1946   try
1947   {
1948     if (nullptr == m_pDB)
1949       return;
1950     if (nullptr == m_pDS)
1951       return;
1952 
1953     std::string strSQL;
1954     strSQL=PrepareSQL("DELETE from genre_link WHERE media_id=%i AND media_type='tvshow'", idTvShow);
1955     m_pDS->exec(strSQL);
1956 
1957     strSQL=PrepareSQL("DELETE FROM actor_link WHERE media_id=%i AND media_type='tvshow'", idTvShow);
1958     m_pDS->exec(strSQL);
1959 
1960     strSQL=PrepareSQL("DELETE FROM director_link WHERE media_id=%i AND media_type='tvshow'", idTvShow);
1961     m_pDS->exec(strSQL);
1962 
1963     strSQL=PrepareSQL("DELETE FROM studio_link WHERE media_id=%i AND media_type='tvshow'", idTvShow);
1964     m_pDS->exec(strSQL);
1965 
1966     strSQL = PrepareSQL("DELETE FROM rating WHERE media_id=%i AND media_type='tvshow'", idTvShow);
1967     m_pDS->exec(strSQL);
1968 
1969     strSQL = PrepareSQL("DELETE FROM uniqueid WHERE media_id=%i AND media_type='tvshow'", idTvShow);
1970     m_pDS->exec(strSQL);
1971 
1972     // remove all info other than the id
1973     // we do this due to the way we have the link between the file + movie tables.
1974 
1975     std::vector<std::string> ids;
1976     for (int iType = VIDEODB_ID_TV_MIN + 1; iType < VIDEODB_ID_TV_MAX; iType++)
1977       ids.emplace_back(StringUtils::Format("c%02d=NULL", iType));
1978 
1979     strSQL = "update tvshow set ";
1980     strSQL += StringUtils::Join(ids, ", ");
1981     strSQL += PrepareSQL(" where idShow=%i", idTvShow);
1982     m_pDS->exec(strSQL);
1983   }
1984   catch (...)
1985   {
1986     CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, idTvShow);
1987   }
1988 }
1989 
1990 //********************************************************************************************************************************
GetMoviesByActor(const std::string & name,CFileItemList & items)1991 void CVideoDatabase::GetMoviesByActor(const std::string& name, CFileItemList& items)
1992 {
1993   Filter filter;
1994   filter.join  = "LEFT JOIN actor_link ON actor_link.media_id=movie_view.idMovie AND actor_link.media_type='movie' "
1995                  "LEFT JOIN actor a ON a.actor_id=actor_link.actor_id "
1996                  "LEFT JOIN director_link ON director_link.media_id=movie_view.idMovie AND director_link.media_type='movie' "
1997                  "LEFT JOIN actor d ON d.actor_id=director_link.actor_id";
1998   filter.where = PrepareSQL("a.name='%s' OR d.name='%s'", name.c_str(), name.c_str());
1999   filter.group = "movie_view.idMovie";
2000   GetMoviesByWhere("videodb://movies/titles/", filter, items);
2001 }
2002 
GetTvShowsByActor(const std::string & name,CFileItemList & items)2003 void CVideoDatabase::GetTvShowsByActor(const std::string& name, CFileItemList& items)
2004 {
2005   Filter filter;
2006   filter.join  = "LEFT JOIN actor_link ON actor_link.media_id=tvshow_view.idShow AND actor_link.media_type='tvshow' "
2007                  "LEFT JOIN actor a ON a.actor_id=actor_link.actor_id "
2008                  "LEFT JOIN director_link ON director_link.media_id=tvshow_view.idShow AND director_link.media_type='tvshow' "
2009                  "LEFT JOIN actor d ON d.actor_id=director_link.actor_id";
2010   filter.where = PrepareSQL("a.name='%s' OR d.name='%s'", name.c_str(), name.c_str());
2011   GetTvShowsByWhere("videodb://tvshows/titles/", filter, items);
2012 }
2013 
GetEpisodesByActor(const std::string & name,CFileItemList & items)2014 void CVideoDatabase::GetEpisodesByActor(const std::string& name, CFileItemList& items)
2015 {
2016   Filter filter;
2017   filter.join  = "LEFT JOIN actor_link ON actor_link.media_id=episode_view.idEpisode AND actor_link.media_type='episode' "
2018                  "LEFT JOIN actor a ON a.actor_id=actor_link.actor_id "
2019                  "LEFT JOIN director_link ON director_link.media_id=episode_view.idEpisode AND director_link.media_type='episode' "
2020                  "LEFT JOIN actor d ON d.actor_id=director_link.actor_id";
2021   filter.where = PrepareSQL("a.name='%s' OR d.name='%s'", name.c_str(), name.c_str());
2022   filter.group = "episode_view.idEpisode";
2023   GetEpisodesByWhere("videodb://tvshows/titles/", filter, items);
2024 }
2025 
GetMusicVideosByArtist(const std::string & strArtist,CFileItemList & items)2026 void CVideoDatabase::GetMusicVideosByArtist(const std::string& strArtist, CFileItemList& items)
2027 {
2028   try
2029   {
2030     items.Clear();
2031     if (nullptr == m_pDB)
2032       return;
2033     if (nullptr == m_pDS)
2034       return;
2035 
2036     std::string strSQL;
2037     if (strArtist.empty())  //! @todo SMARTPLAYLISTS what is this here for???
2038       strSQL=PrepareSQL("select distinct * from musicvideo_view join actor_link on actor_link.media_id=musicvideo_view.idMVideo AND actor_link.media_type='musicvideo' join actor on actor.actor_id=actor_link.actor_id");
2039     else // same artist OR same director
2040       strSQL = PrepareSQL(
2041           "select * from musicvideo_view join actor_link on "
2042           "actor_link.media_id=musicvideo_view.idMVideo AND actor_link.media_type='musicvideo' "
2043           "join actor on actor.actor_id=actor_link.actor_id where actor.name='%s' OR "
2044           "musicvideo_view.c05='%s' GROUP BY idMVideo",
2045           strArtist.c_str(), strArtist.c_str());
2046     m_pDS->query( strSQL );
2047 
2048     while (!m_pDS->eof())
2049     {
2050       CVideoInfoTag tag = GetDetailsForMusicVideo(m_pDS);
2051       CFileItemPtr pItem(new CFileItem(tag));
2052       pItem->SetLabel(StringUtils::Join(tag.m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator));
2053       items.Add(pItem);
2054       m_pDS->next();
2055     }
2056     m_pDS->close();
2057   }
2058   catch (...)
2059   {
2060     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strArtist.c_str());
2061   }
2062 }
2063 
2064 //********************************************************************************************************************************
GetMovieInfo(const std::string & strFilenameAndPath,CVideoInfoTag & details,int idMovie,int getDetails)2065 bool CVideoDatabase::GetMovieInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idMovie /* = -1 */, int getDetails /* = VideoDbDetailsAll */)
2066 {
2067   try
2068   {
2069     if (m_pDB == nullptr || m_pDS == nullptr)
2070       return false;
2071 
2072     if (idMovie < 0)
2073       idMovie = GetMovieId(strFilenameAndPath);
2074     if (idMovie < 0) return false;
2075 
2076     std::string sql = PrepareSQL("select * from movie_view where idMovie=%i", idMovie);
2077     if (!m_pDS->query(sql))
2078       return false;
2079     details = GetDetailsForMovie(m_pDS, getDetails);
2080     return !details.IsEmpty();
2081   }
2082   catch (...)
2083   {
2084     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
2085   }
2086   return false;
2087 }
2088 
2089 //********************************************************************************************************************************
GetTvShowInfo(const std::string & strPath,CVideoInfoTag & details,int idTvShow,CFileItem * item,int getDetails)2090 bool CVideoDatabase::GetTvShowInfo(const std::string& strPath, CVideoInfoTag& details, int idTvShow /* = -1 */, CFileItem *item /* = NULL */, int getDetails /* = VideoDbDetailsAll */)
2091 {
2092   try
2093   {
2094     if (m_pDB == nullptr || m_pDS == nullptr)
2095       return false;
2096 
2097     if (idTvShow < 0)
2098       idTvShow = GetTvShowId(strPath);
2099     if (idTvShow < 0) return false;
2100 
2101     std::string sql = PrepareSQL("SELECT * FROM tvshow_view WHERE idShow=%i GROUP BY idShow", idTvShow);
2102     if (!m_pDS->query(sql))
2103       return false;
2104     details = GetDetailsForTvShow(m_pDS, getDetails, item);
2105     return !details.IsEmpty();
2106   }
2107   catch (...)
2108   {
2109     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strPath.c_str());
2110   }
2111   return false;
2112 }
2113 
GetSeasonInfo(int idSeason,CVideoInfoTag & details,bool allDetails)2114 bool CVideoDatabase::GetSeasonInfo(int idSeason, CVideoInfoTag& details, bool allDetails /* = true */)
2115 {
2116   if (idSeason < 0)
2117     return false;
2118 
2119   try
2120   {
2121     if (!m_pDB || !m_pDS)
2122       return false;
2123 
2124     std::string sql = PrepareSQL("SELECT idSeason, idShow, season, name, userrating FROM seasons WHERE idSeason=%i", idSeason);
2125     if (!m_pDS->query(sql))
2126       return false;
2127 
2128     if (m_pDS->num_rows() != 1)
2129       return false;
2130 
2131     if (allDetails)
2132     {
2133       int idShow = m_pDS->fv(1).get_asInt();
2134 
2135       // close the current result because we are going to query the season view for all details
2136       m_pDS->close();
2137 
2138       if (idShow < 0)
2139         return false;
2140 
2141       CFileItemList seasons;
2142       if (!GetSeasonsNav(StringUtils::Format("videodb://tvshows/titles/%i/", idShow), seasons, -1, -1, -1, -1, idShow, false) || seasons.Size() <= 0)
2143         return false;
2144 
2145       for (int index = 0; index < seasons.Size(); index++)
2146       {
2147         const CFileItemPtr season = seasons.Get(index);
2148         if (season->HasVideoInfoTag() && season->GetVideoInfoTag()->m_iDbId == idSeason && season->GetVideoInfoTag()->m_iIdShow == idShow)
2149         {
2150           details = *season->GetVideoInfoTag();
2151           return true;
2152         }
2153       }
2154 
2155       return false;
2156     }
2157 
2158     const int season = m_pDS->fv(2).get_asInt();
2159     std::string name = m_pDS->fv(3).get_asString();
2160 
2161     if (name.empty())
2162     {
2163       if (season == 0)
2164         name = g_localizeStrings.Get(20381);
2165       else
2166         name = StringUtils::Format(g_localizeStrings.Get(20358).c_str(), season);
2167     }
2168 
2169     details.m_strTitle = name;
2170     if (!name.empty())
2171       details.m_strSortTitle = name;
2172     details.m_iSeason = season;
2173     details.m_iDbId = m_pDS->fv(0).get_asInt();
2174     details.m_iIdSeason = details.m_iDbId;
2175     details.m_type = MediaTypeSeason;
2176     details.m_iUserRating = m_pDS->fv(4).get_asInt();
2177     details.m_iIdShow = m_pDS->fv(1).get_asInt();
2178 
2179     return true;
2180   }
2181   catch (...)
2182   {
2183     CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, idSeason);
2184   }
2185   return false;
2186 }
2187 
GetEpisodeBasicInfo(const std::string & strFilenameAndPath,CVideoInfoTag & details,int idEpisode)2188 bool CVideoDatabase::GetEpisodeBasicInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idEpisode /* = -1 */)
2189 {
2190   try
2191   {
2192     if (idEpisode < 0)
2193       idEpisode = GetEpisodeId(strFilenameAndPath);
2194 
2195     if (idEpisode < 0)
2196       return false;
2197 
2198     std::string sql = PrepareSQL("select * from episode where idEpisode=%i",idEpisode);
2199     if (!m_pDS->query(sql))
2200       return false;
2201     details = GetBasicDetailsForEpisode(m_pDS);
2202     return !details.IsEmpty();
2203   }
2204   catch (...)
2205   {
2206     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
2207   }
2208   return false;
2209 }
2210 
GetEpisodeInfo(const std::string & strFilenameAndPath,CVideoInfoTag & details,int idEpisode,int getDetails)2211 bool CVideoDatabase::GetEpisodeInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idEpisode /* = -1 */, int getDetails /* = VideoDbDetailsAll */)
2212 {
2213   try
2214   {
2215     if (m_pDB == nullptr || m_pDS == nullptr)
2216       return false;
2217 
2218     if (idEpisode < 0)
2219       idEpisode = GetEpisodeId(strFilenameAndPath, details.m_iEpisode, details.m_iSeason);
2220     if (idEpisode < 0) return false;
2221 
2222     std::string sql = PrepareSQL("select * from episode_view where idEpisode=%i",idEpisode);
2223     if (!m_pDS->query(sql))
2224       return false;
2225     details = GetDetailsForEpisode(m_pDS, getDetails);
2226     return !details.IsEmpty();
2227   }
2228   catch (...)
2229   {
2230     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
2231   }
2232   return false;
2233 }
2234 
GetMusicVideoInfo(const std::string & strFilenameAndPath,CVideoInfoTag & details,int idMVideo,int getDetails)2235 bool CVideoDatabase::GetMusicVideoInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idMVideo /* = -1 */, int getDetails /* = VideoDbDetailsAll */)
2236 {
2237   try
2238   {
2239     if (m_pDB == nullptr || m_pDS == nullptr)
2240       return false;
2241 
2242     if (idMVideo < 0)
2243       idMVideo = GetMusicVideoId(strFilenameAndPath);
2244     if (idMVideo < 0) return false;
2245 
2246     std::string sql = PrepareSQL("select * from musicvideo_view where idMVideo=%i", idMVideo);
2247     if (!m_pDS->query(sql))
2248       return false;
2249     details = GetDetailsForMusicVideo(m_pDS, getDetails);
2250     return !details.IsEmpty();
2251   }
2252   catch (...)
2253   {
2254     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
2255   }
2256   return false;
2257 }
2258 
GetSetInfo(int idSet,CVideoInfoTag & details)2259 bool CVideoDatabase::GetSetInfo(int idSet, CVideoInfoTag& details)
2260 {
2261   try
2262   {
2263     if (idSet < 0)
2264       return false;
2265 
2266     Filter filter;
2267     filter.where = PrepareSQL("sets.idSet=%d", idSet);
2268     CFileItemList items;
2269     if (!GetSetsByWhere("videodb://movies/sets/", filter, items) ||
2270         items.Size() != 1 ||
2271         !items[0]->HasVideoInfoTag())
2272       return false;
2273 
2274     details = *(items[0]->GetVideoInfoTag());
2275     return !details.IsEmpty();
2276   }
2277   catch (...)
2278   {
2279     CLog::Log(LOGERROR, "%s (%d) failed", __FUNCTION__, idSet);
2280   }
2281   return false;
2282 }
2283 
GetFileInfo(const std::string & strFilenameAndPath,CVideoInfoTag & details,int idFile)2284 bool CVideoDatabase::GetFileInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idFile /* = -1 */)
2285 {
2286   try
2287   {
2288     if (idFile < 0)
2289       idFile = GetFileId(strFilenameAndPath);
2290     if (idFile < 0)
2291       return false;
2292 
2293     std::string sql = PrepareSQL("SELECT * FROM files "
2294                                 "JOIN path ON path.idPath = files.idPath "
2295                                 "LEFT JOIN bookmark ON bookmark.idFile = files.idFile AND bookmark.type = %i "
2296                                 "WHERE files.idFile = %i", CBookmark::RESUME, idFile);
2297     if (!m_pDS->query(sql))
2298       return false;
2299 
2300     details.m_iFileId = m_pDS->fv("files.idFile").get_asInt();
2301     details.m_strPath = m_pDS->fv("path.strPath").get_asString();
2302     std::string strFileName = m_pDS->fv("files.strFilename").get_asString();
2303     ConstructPath(details.m_strFileNameAndPath, details.m_strPath, strFileName);
2304     details.SetPlayCount(std::max(details.GetPlayCount(), m_pDS->fv("files.playCount").get_asInt()));
2305     if (!details.m_lastPlayed.IsValid())
2306       details.m_lastPlayed.SetFromDBDateTime(m_pDS->fv("files.lastPlayed").get_asString());
2307     if (!details.m_dateAdded.IsValid())
2308       details.m_dateAdded.SetFromDBDateTime(m_pDS->fv("files.dateAdded").get_asString());
2309     if (!details.GetResumePoint().IsSet())
2310     {
2311       details.SetResumePoint(m_pDS->fv("bookmark.timeInSeconds").get_asDouble(),
2312                              m_pDS->fv("bookmark.totalTimeInSeconds").get_asDouble(),
2313                              m_pDS->fv("bookmark.playerState").get_asString());
2314     }
2315 
2316     // get streamdetails
2317     GetStreamDetails(details);
2318 
2319     return !details.IsEmpty();
2320   }
2321   catch (...)
2322   {
2323     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
2324   }
2325   return false;
2326 }
2327 
GetValueString(const CVideoInfoTag & details,int min,int max,const SDbTableOffsets * offsets) const2328 std::string CVideoDatabase::GetValueString(const CVideoInfoTag &details, int min, int max, const SDbTableOffsets *offsets) const
2329 {
2330   std::vector<std::string> conditions;
2331   for (int i = min + 1; i < max; ++i)
2332   {
2333     switch (offsets[i].type)
2334     {
2335     case VIDEODB_TYPE_STRING:
2336       conditions.emplace_back(PrepareSQL("c%02d='%s'", i, ((const std::string*)(((const char*)&details)+offsets[i].offset))->c_str()));
2337       break;
2338     case VIDEODB_TYPE_INT:
2339       conditions.emplace_back(PrepareSQL("c%02d='%i'", i, *(const int*)(((const char*)&details)+offsets[i].offset)));
2340       break;
2341     case VIDEODB_TYPE_COUNT:
2342       {
2343         int value = *(const int*)(((const char*)&details)+offsets[i].offset);
2344         if (value)
2345           conditions.emplace_back(PrepareSQL("c%02d=%i", i, value));
2346         else
2347           conditions.emplace_back(PrepareSQL("c%02d=NULL", i));
2348       }
2349       break;
2350     case VIDEODB_TYPE_BOOL:
2351       conditions.emplace_back(PrepareSQL("c%02d='%s'", i, *(const bool*)(((const char*)&details)+offsets[i].offset)?"true":"false"));
2352       break;
2353     case VIDEODB_TYPE_FLOAT:
2354       conditions.emplace_back(PrepareSQL("c%02d='%f'", i, *(const float*)(((const char*)&details)+offsets[i].offset)));
2355       break;
2356     case VIDEODB_TYPE_STRINGARRAY:
2357       conditions.emplace_back(PrepareSQL("c%02d='%s'", i, StringUtils::Join(*((const std::vector<std::string>*)(((const char*)&details)+offsets[i].offset)),
2358                                                                           CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator).c_str()));
2359       break;
2360     case VIDEODB_TYPE_DATE:
2361       conditions.emplace_back(PrepareSQL("c%02d='%s'", i, ((const CDateTime*)(((const char*)&details)+offsets[i].offset))->GetAsDBDate().c_str()));
2362       break;
2363     case VIDEODB_TYPE_DATETIME:
2364       conditions.emplace_back(PrepareSQL("c%02d='%s'", i, ((const CDateTime*)(((const char*)&details)+offsets[i].offset))->GetAsDBDateTime().c_str()));
2365       break;
2366     case VIDEODB_TYPE_UNUSED: // Skip the unused field to avoid populating unused data
2367       continue;
2368     }
2369   }
2370   return StringUtils::Join(conditions, ",");
2371 }
2372 
2373 //********************************************************************************************************************************
SetDetailsForItem(CVideoInfoTag & details,const std::map<std::string,std::string> & artwork)2374 int CVideoDatabase::SetDetailsForItem(CVideoInfoTag& details, const std::map<std::string, std::string> &artwork)
2375 {
2376   return SetDetailsForItem(details.m_iDbId, details.m_type, details, artwork);
2377 }
2378 
SetDetailsForItem(int id,const MediaType & mediaType,CVideoInfoTag & details,const std::map<std::string,std::string> & artwork)2379 int CVideoDatabase::SetDetailsForItem(int id, const MediaType& mediaType, CVideoInfoTag& details, const std::map<std::string, std::string> &artwork)
2380 {
2381   if (mediaType == MediaTypeNone)
2382     return -1;
2383 
2384   if (mediaType == MediaTypeMovie)
2385     return SetDetailsForMovie(details.GetPath(), details, artwork, id);
2386   else if (mediaType == MediaTypeVideoCollection)
2387     return SetDetailsForMovieSet(details, artwork, id);
2388   else if (mediaType == MediaTypeTvShow)
2389   {
2390     std::map<int, std::map<std::string, std::string> > seasonArtwork;
2391     if (!UpdateDetailsForTvShow(id, details, artwork, seasonArtwork))
2392       return -1;
2393 
2394     return id;
2395   }
2396   else if (mediaType == MediaTypeSeason)
2397     return SetDetailsForSeason(details, artwork, details.m_iIdShow, id);
2398   else if (mediaType == MediaTypeEpisode)
2399     return SetDetailsForEpisode(details.GetPath(), details, artwork, details.m_iIdShow, id);
2400   else if (mediaType == MediaTypeMusicVideo)
2401     return SetDetailsForMusicVideo(details.GetPath(), details, artwork, id);
2402 
2403   return -1;
2404 }
2405 
SetDetailsForMovie(const std::string & strFilenameAndPath,CVideoInfoTag & details,const std::map<std::string,std::string> & artwork,int idMovie)2406 int CVideoDatabase::SetDetailsForMovie(const std::string& strFilenameAndPath, CVideoInfoTag& details,
2407     const std::map<std::string, std::string> &artwork, int idMovie /* = -1 */)
2408 {
2409   try
2410   {
2411     BeginTransaction();
2412 
2413     if (idMovie < 0)
2414       idMovie = GetMovieId(strFilenameAndPath);
2415 
2416     if (idMovie > -1)
2417       DeleteMovie(idMovie, true); // true to keep the table entry
2418     else
2419     {
2420       // only add a new movie if we don't already have a valid idMovie
2421       // (DeleteMovie is called with bKeepId == true so the movie won't
2422       // be removed from the movie table)
2423       idMovie = AddMovie(strFilenameAndPath);
2424       if (idMovie < 0)
2425       {
2426         RollbackTransaction();
2427         return idMovie;
2428       }
2429     }
2430 
2431     // update dateadded if it's set
2432     if (details.m_dateAdded.IsValid())
2433     {
2434       if (details.m_iFileId <= 0)
2435         details.m_iFileId = GetFileId(strFilenameAndPath);
2436 
2437       UpdateFileDateAdded(details.m_iFileId, strFilenameAndPath, details.m_dateAdded);
2438     }
2439 
2440     AddCast(idMovie, "movie", details.m_cast);
2441     AddLinksToItem(idMovie, MediaTypeMovie, "genre", details.m_genre);
2442     AddLinksToItem(idMovie, MediaTypeMovie, "studio", details.m_studio);
2443     AddLinksToItem(idMovie, MediaTypeMovie, "country", details.m_country);
2444     AddLinksToItem(idMovie, MediaTypeMovie, "tag", details.m_tags);
2445     AddActorLinksToItem(idMovie, MediaTypeMovie, "director", details.m_director);
2446     AddActorLinksToItem(idMovie, MediaTypeMovie, "writer", details.m_writingCredits);
2447 
2448     // add ratingsu
2449     details.m_iIdRating = AddRatings(idMovie, MediaTypeMovie, details.m_ratings, details.GetDefaultRating());
2450 
2451     // add unique ids
2452     details.m_iIdUniqueID = AddUniqueIDs(idMovie, MediaTypeMovie, details);
2453 
2454     // add set...
2455     int idSet = -1;
2456     if (!details.m_set.title.empty())
2457     {
2458       idSet = AddSet(details.m_set.title, details.m_set.overview);
2459       // add art if not available
2460       if (!HasArtForItem(idSet, MediaTypeVideoCollection))
2461       {
2462         for (const auto &it : artwork)
2463         {
2464           if (StringUtils::StartsWith(it.first, "set."))
2465             SetArtForItem(idSet, MediaTypeVideoCollection, it.first.substr(4), it.second);
2466         }
2467       }
2468     }
2469 
2470     if (details.HasStreamDetails())
2471       SetStreamDetailsForFileId(details.m_streamDetails, GetFileId(strFilenameAndPath));
2472 
2473     SetArtForItem(idMovie, MediaTypeMovie, artwork);
2474 
2475     if (!details.HasUniqueID() && details.HasYear())
2476     { // query DB for any movies matching online id and year
2477       std::string strSQL = PrepareSQL("SELECT files.playCount, files.lastPlayed "
2478                                       "FROM movie "
2479                                       "  INNER JOIN files "
2480                                       "    ON files.idFile=movie.idFile "
2481                                       "  JOIN uniqueid "
2482                                       "    ON movie.idMovie=uniqueid.media_id AND uniqueid.media_type='movie' AND uniqueid.value='%s'"
2483                                       "WHERE movie.premiered LIKE '%i%%' AND movie.idMovie!=%i AND files.playCount > 0",
2484                                       details.GetUniqueID().c_str(), details.GetYear(), idMovie);
2485       m_pDS->query(strSQL);
2486 
2487       if (!m_pDS->eof())
2488       {
2489         int playCount = m_pDS->fv("files.playCount").get_asInt();
2490 
2491         CDateTime lastPlayed;
2492         lastPlayed.SetFromDBDateTime(m_pDS->fv("files.lastPlayed").get_asString());
2493 
2494         int idFile = GetFileId(strFilenameAndPath);
2495 
2496         // update with playCount and lastPlayed
2497         strSQL = PrepareSQL("update files set playCount=%i,lastPlayed='%s' where idFile=%i", playCount, lastPlayed.GetAsDBDateTime().c_str(), idFile);
2498         m_pDS->exec(strSQL);
2499       }
2500 
2501       m_pDS->close();
2502     }
2503     // update our movie table (we know it was added already above)
2504     // and insert the new row
2505     std::string sql = "UPDATE movie SET " + GetValueString(details, VIDEODB_ID_MIN, VIDEODB_ID_MAX, DbMovieOffsets);
2506     if (idSet > 0)
2507       sql += PrepareSQL(", idSet = %i", idSet);
2508     else
2509       sql += ", idSet = NULL";
2510     if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
2511       sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
2512     else
2513       sql += ", userrating = NULL";
2514     if (details.HasPremiered())
2515       sql += PrepareSQL(", premiered = '%s'", details.GetPremiered().GetAsDBDate().c_str());
2516     else
2517       sql += PrepareSQL(", premiered = '%i'", details.GetYear());
2518     sql += PrepareSQL(" where idMovie=%i", idMovie);
2519     m_pDS->exec(sql);
2520     CommitTransaction();
2521 
2522     return idMovie;
2523   }
2524   catch (...)
2525   {
2526     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
2527   }
2528   RollbackTransaction();
2529   return -1;
2530 }
2531 
UpdateDetailsForMovie(int idMovie,CVideoInfoTag & details,const std::map<std::string,std::string> & artwork,const std::set<std::string> & updatedDetails)2532 int CVideoDatabase::UpdateDetailsForMovie(int idMovie, CVideoInfoTag& details, const std::map<std::string, std::string> &artwork, const std::set<std::string> &updatedDetails)
2533 {
2534   if (idMovie < 0)
2535     return idMovie;
2536 
2537   try
2538   {
2539     CLog::Log(LOGINFO, "%s: Starting updates for movie %i", __FUNCTION__, idMovie);
2540 
2541     BeginTransaction();
2542 
2543     // process the link table updates
2544     if (updatedDetails.find("genre") != updatedDetails.end())
2545       UpdateLinksToItem(idMovie, MediaTypeMovie, "genre", details.m_genre);
2546     if (updatedDetails.find("studio") != updatedDetails.end())
2547       UpdateLinksToItem(idMovie, MediaTypeMovie, "studio", details.m_studio);
2548     if (updatedDetails.find("country") != updatedDetails.end())
2549       UpdateLinksToItem(idMovie, MediaTypeMovie, "country", details.m_country);
2550     if (updatedDetails.find("tag") != updatedDetails.end())
2551       UpdateLinksToItem(idMovie, MediaTypeMovie, "tag", details.m_tags);
2552     if (updatedDetails.find("director") != updatedDetails.end())
2553       UpdateActorLinksToItem(idMovie, MediaTypeMovie, "director", details.m_director);
2554     if (updatedDetails.find("writer") != updatedDetails.end())
2555       UpdateActorLinksToItem(idMovie, MediaTypeMovie, "writer", details.m_writingCredits);
2556     if (updatedDetails.find("art.altered") != updatedDetails.end())
2557       SetArtForItem(idMovie, MediaTypeMovie, artwork);
2558     if (updatedDetails.find("ratings") != updatedDetails.end())
2559       details.m_iIdRating = UpdateRatings(idMovie, MediaTypeMovie, details.m_ratings, details.GetDefaultRating());
2560     if (updatedDetails.find("uniqueid") != updatedDetails.end())
2561       details.m_iIdUniqueID = UpdateUniqueIDs(idMovie, MediaTypeMovie, details);
2562     if (updatedDetails.find("dateadded") != updatedDetails.end() && details.m_dateAdded.IsValid())
2563     {
2564       if (details.m_iFileId <= 0)
2565         details.m_iFileId = GetFileId(details.GetPath());
2566 
2567       UpdateFileDateAdded(details.m_iFileId, details.GetPath(), details.m_dateAdded);
2568     }
2569 
2570     // track if the set was updated
2571     int idSet = 0;
2572     if (updatedDetails.find("set") != updatedDetails.end())
2573     { // set
2574       idSet = -1;
2575       if (!details.m_set.title.empty())
2576       {
2577         idSet = AddSet(details.m_set.title, details.m_set.overview);
2578       }
2579     }
2580 
2581     if (updatedDetails.find("showlink") != updatedDetails.end())
2582     {
2583       // remove existing links
2584       std::vector<int> tvShowIds;
2585       GetLinksToTvShow(idMovie, tvShowIds);
2586       for (const auto& idTVShow : tvShowIds)
2587         LinkMovieToTvshow(idMovie, idTVShow, true);
2588 
2589       // setup links to shows if the linked shows are in the db
2590       for (const auto& showLink : details.m_showLink)
2591       {
2592         CFileItemList items;
2593         GetTvShowsByName(showLink, items);
2594         if (!items.IsEmpty())
2595           LinkMovieToTvshow(idMovie, items[0]->GetVideoInfoTag()->m_iDbId, false);
2596         else
2597           CLog::Log(LOGWARNING, "%s: Failed to link movie %s to show %s", __FUNCTION__, details.m_strTitle.c_str(), showLink.c_str());
2598       }
2599     }
2600 
2601     // and update the movie table
2602     std::string sql = "UPDATE movie SET " + GetValueString(details, VIDEODB_ID_MIN, VIDEODB_ID_MAX, DbMovieOffsets);
2603     if (idSet > 0)
2604       sql += PrepareSQL(", idSet = %i", idSet);
2605     else if (idSet < 0)
2606       sql += ", idSet = NULL";
2607     if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
2608       sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
2609     else
2610       sql += ", userrating = NULL";
2611     if (details.HasPremiered())
2612       sql += PrepareSQL(", premiered = '%s'", details.GetPremiered().GetAsDBDate().c_str());
2613     else
2614       sql += PrepareSQL(", premiered = '%i'", details.GetYear());
2615     sql += PrepareSQL(" where idMovie=%i", idMovie);
2616     m_pDS->exec(sql);
2617 
2618     CommitTransaction();
2619 
2620     CLog::Log(LOGINFO, "%s: Finished updates for movie %i", __FUNCTION__, idMovie);
2621 
2622     return idMovie;
2623   }
2624   catch (...)
2625   {
2626     CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, idMovie);
2627   }
2628   RollbackTransaction();
2629   return -1;
2630 }
2631 
SetDetailsForMovieSet(const CVideoInfoTag & details,const std::map<std::string,std::string> & artwork,int idSet)2632 int CVideoDatabase::SetDetailsForMovieSet(const CVideoInfoTag& details, const std::map<std::string, std::string> &artwork, int idSet /* = -1 */)
2633 {
2634   if (details.m_strTitle.empty())
2635     return -1;
2636 
2637   try
2638   {
2639     BeginTransaction();
2640     if (idSet < 0)
2641     {
2642       idSet = AddSet(details.m_strTitle, details.m_strPlot);
2643       if (idSet < 0)
2644       {
2645         RollbackTransaction();
2646         return -1;
2647       }
2648     }
2649 
2650     SetArtForItem(idSet, MediaTypeVideoCollection, artwork);
2651 
2652     // and insert the new row
2653     std::string sql = PrepareSQL("UPDATE sets SET strSet='%s', strOverview='%s' WHERE idSet=%i", details.m_strTitle.c_str(), details.m_strPlot.c_str(), idSet);
2654     m_pDS->exec(sql);
2655     CommitTransaction();
2656 
2657     return idSet;
2658   }
2659   catch (...)
2660   {
2661     CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, idSet);
2662   }
2663   RollbackTransaction();
2664   return -1;
2665 }
2666 
GetMatchingTvShow(const CVideoInfoTag & details)2667 int CVideoDatabase::GetMatchingTvShow(const CVideoInfoTag &details)
2668 {
2669   // first try matching on uniqueid, then on title + year
2670   int id = -1;
2671   if (!details.HasUniqueID())
2672     id = GetDbId(PrepareSQL("SELECT idShow FROM tvshow JOIN uniqueid ON uniqueid.media_id=tvshow.idShow AND uniqueid.media_type='tvshow' WHERE uniqueid.value='%s'", details.GetUniqueID().c_str()));
2673   if (id < 0)
2674     id = GetDbId(PrepareSQL("SELECT idShow FROM tvshow WHERE c%02d='%s' AND c%02d='%s'", VIDEODB_ID_TV_TITLE, details.m_strTitle.c_str(), VIDEODB_ID_TV_PREMIERED, details.GetPremiered().GetAsDBDate().c_str()));
2675   return id;
2676 }
2677 
SetDetailsForTvShow(const std::vector<std::pair<std::string,std::string>> & paths,CVideoInfoTag & details,const std::map<std::string,std::string> & artwork,const std::map<int,std::map<std::string,std::string>> & seasonArt,int idTvShow)2678 int CVideoDatabase::SetDetailsForTvShow(const std::vector<std::pair<std::string, std::string> > &paths,
2679     CVideoInfoTag& details, const std::map<std::string, std::string> &artwork,
2680     const std::map<int, std::map<std::string, std::string> > &seasonArt, int idTvShow /*= -1 */)
2681 {
2682 
2683   /*
2684    The steps are as follows.
2685    1. Check if the tvshow is found on any of the given paths.  If found, we have the show id.
2686    2. Search for a matching show.  If found, we have the show id.
2687    3. If we don't have the id, add a new show.
2688    4. Add the paths to the show.
2689    5. Add details for the show.
2690    */
2691 
2692   if (idTvShow < 0)
2693   {
2694     for (const auto &i : paths)
2695     {
2696       idTvShow = GetTvShowId(i.first);
2697       if (idTvShow > -1)
2698         break;
2699     }
2700   }
2701   if (idTvShow < 0)
2702     idTvShow = GetMatchingTvShow(details);
2703   if (idTvShow < 0)
2704   {
2705     idTvShow = AddTvShow();
2706     if (idTvShow < 0)
2707       return -1;
2708   }
2709 
2710   // add any paths to the tvshow
2711   for (const auto &i : paths)
2712     AddPathToTvShow(idTvShow, i.first, i.second, details.m_dateAdded);
2713 
2714   UpdateDetailsForTvShow(idTvShow, details, artwork, seasonArt);
2715 
2716   return idTvShow;
2717 }
2718 
UpdateDetailsForTvShow(int idTvShow,CVideoInfoTag & details,const std::map<std::string,std::string> & artwork,const std::map<int,std::map<std::string,std::string>> & seasonArt)2719 bool CVideoDatabase::UpdateDetailsForTvShow(int idTvShow, CVideoInfoTag &details,
2720     const std::map<std::string, std::string> &artwork, const std::map<int, std::map<std::string, std::string>> &seasonArt)
2721 {
2722   BeginTransaction();
2723 
2724   DeleteDetailsForTvShow(idTvShow);
2725 
2726   AddCast(idTvShow, "tvshow", details.m_cast);
2727   AddLinksToItem(idTvShow, MediaTypeTvShow, "genre", details.m_genre);
2728   AddLinksToItem(idTvShow, MediaTypeTvShow, "studio", details.m_studio);
2729   AddLinksToItem(idTvShow, MediaTypeTvShow, "tag", details.m_tags);
2730   AddActorLinksToItem(idTvShow, MediaTypeTvShow, "director", details.m_director);
2731 
2732   // add ratings
2733   details.m_iIdRating = AddRatings(idTvShow, MediaTypeTvShow, details.m_ratings, details.GetDefaultRating());
2734 
2735   // add unique ids
2736   details.m_iIdUniqueID = AddUniqueIDs(idTvShow, MediaTypeTvShow, details);
2737 
2738   // add "all seasons" - the rest are added in SetDetailsForEpisode
2739   AddSeason(idTvShow, -1);
2740 
2741   // add any named seasons
2742   for (const auto& namedSeason : details.m_namedSeasons)
2743   {
2744     // make sure the named season exists
2745     int seasonId = AddSeason(idTvShow, namedSeason.first, namedSeason.second);
2746 
2747     // get any existing details for the named season
2748     CVideoInfoTag season;
2749     if (!GetSeasonInfo(seasonId, season, false) || season.m_strSortTitle == namedSeason.second)
2750       continue;
2751 
2752     season.SetSortTitle(namedSeason.second);
2753     SetDetailsForSeason(season, std::map<std::string, std::string>(), idTvShow, seasonId);
2754   }
2755 
2756   SetArtForItem(idTvShow, MediaTypeTvShow, artwork);
2757   for (const auto &i : seasonArt)
2758   {
2759     int idSeason = AddSeason(idTvShow, i.first);
2760     if (idSeason > -1)
2761       SetArtForItem(idSeason, MediaTypeSeason, i.second);
2762   }
2763 
2764   // and insert the new row
2765   std::string sql = "UPDATE tvshow SET " + GetValueString(details, VIDEODB_ID_TV_MIN, VIDEODB_ID_TV_MAX, DbTvShowOffsets);
2766   if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
2767     sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
2768   else
2769     sql += ", userrating = NULL";
2770   if (details.GetDuration() > 0)
2771     sql += PrepareSQL(", duration = %i", details.GetDuration());
2772   else
2773     sql += ", duration = NULL";
2774   sql += PrepareSQL(" WHERE idShow=%i", idTvShow);
2775   if (ExecuteQuery(sql))
2776   {
2777     CommitTransaction();
2778     return true;
2779   }
2780   RollbackTransaction();
2781   return false;
2782 }
2783 
SetDetailsForSeason(const CVideoInfoTag & details,const std::map<std::string,std::string> & artwork,int idShow,int idSeason)2784 int CVideoDatabase::SetDetailsForSeason(const CVideoInfoTag& details, const std::map<std::string,
2785     std::string> &artwork, int idShow, int idSeason /* = -1 */)
2786 {
2787   if (idShow < 0 || details.m_iSeason < -1)
2788     return -1;
2789 
2790    try
2791   {
2792     BeginTransaction();
2793     if (idSeason < 0)
2794     {
2795       idSeason = AddSeason(idShow, details.m_iSeason);
2796       if (idSeason < 0)
2797       {
2798         RollbackTransaction();
2799         return -1;
2800       }
2801     }
2802 
2803     SetArtForItem(idSeason, MediaTypeSeason, artwork);
2804 
2805     // and insert the new row
2806     std::string sql = PrepareSQL("UPDATE seasons SET season=%i", details.m_iSeason);
2807     if (!details.m_strSortTitle.empty())
2808       sql += PrepareSQL(", name='%s'", details.m_strSortTitle.c_str());
2809     if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
2810       sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
2811     else
2812       sql += ", userrating = NULL";
2813     sql += PrepareSQL(" WHERE idSeason=%i", idSeason);
2814     m_pDS->exec(sql.c_str());
2815     CommitTransaction();
2816 
2817     return idSeason;
2818   }
2819   catch (...)
2820   {
2821     CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, idSeason);
2822   }
2823   RollbackTransaction();
2824   return -1;
2825 }
2826 
SetDetailsForEpisode(const std::string & strFilenameAndPath,CVideoInfoTag & details,const std::map<std::string,std::string> & artwork,int idShow,int idEpisode)2827 int CVideoDatabase::SetDetailsForEpisode(const std::string& strFilenameAndPath, CVideoInfoTag& details,
2828     const std::map<std::string, std::string> &artwork, int idShow, int idEpisode)
2829 {
2830   try
2831   {
2832     BeginTransaction();
2833     if (idEpisode < 0)
2834       idEpisode = GetEpisodeId(strFilenameAndPath);
2835 
2836     if (idEpisode > 0)
2837       DeleteEpisode(idEpisode, true); // true to keep the table entry
2838     else
2839     {
2840       // only add a new episode if we don't already have a valid idEpisode
2841       // (DeleteEpisode is called with bKeepId == true so the episode won't
2842       // be removed from the episode table)
2843       idEpisode = AddEpisode(idShow,strFilenameAndPath);
2844       if (idEpisode < 0)
2845       {
2846         RollbackTransaction();
2847         return -1;
2848       }
2849     }
2850 
2851     // update dateadded if it's set
2852     if (details.m_dateAdded.IsValid())
2853     {
2854       if (details.m_iFileId <= 0)
2855         details.m_iFileId = GetFileId(strFilenameAndPath);
2856 
2857       UpdateFileDateAdded(details.m_iFileId, strFilenameAndPath, details.m_dateAdded);
2858     }
2859 
2860     AddCast(idEpisode, "episode", details.m_cast);
2861     AddActorLinksToItem(idEpisode, MediaTypeEpisode, "director", details.m_director);
2862     AddActorLinksToItem(idEpisode, MediaTypeEpisode, "writer", details.m_writingCredits);
2863 
2864     // add ratings
2865     details.m_iIdRating = AddRatings(idEpisode, MediaTypeEpisode, details.m_ratings, details.GetDefaultRating());
2866 
2867     // add unique ids
2868     details.m_iIdUniqueID = AddUniqueIDs(idEpisode, MediaTypeEpisode, details);
2869 
2870     if (details.HasStreamDetails())
2871     {
2872       if (details.m_iFileId != -1)
2873         SetStreamDetailsForFileId(details.m_streamDetails, details.m_iFileId);
2874       else
2875         SetStreamDetailsForFile(details.m_streamDetails, strFilenameAndPath);
2876     }
2877 
2878     // ensure we have this season already added
2879     int idSeason = AddSeason(idShow, details.m_iSeason);
2880 
2881     SetArtForItem(idEpisode, MediaTypeEpisode, artwork);
2882 
2883     if (details.m_iEpisode != -1 && details.m_iSeason != -1)
2884     { // query DB for any episodes matching idShow, Season and Episode
2885       std::string strSQL = PrepareSQL("SELECT files.playCount, files.lastPlayed "
2886                                       "FROM episode INNER JOIN files ON files.idFile=episode.idFile "
2887                                       "WHERE episode.c%02d=%i AND episode.c%02d=%i AND episode.idShow=%i "
2888                                       "AND episode.idEpisode!=%i AND files.playCount > 0",
2889                                       VIDEODB_ID_EPISODE_SEASON, details.m_iSeason, VIDEODB_ID_EPISODE_EPISODE,
2890                                       details.m_iEpisode, idShow, idEpisode);
2891       m_pDS->query(strSQL);
2892 
2893       if (!m_pDS->eof())
2894       {
2895         int playCount = m_pDS->fv("files.playCount").get_asInt();
2896 
2897         CDateTime lastPlayed;
2898         lastPlayed.SetFromDBDateTime(m_pDS->fv("files.lastPlayed").get_asString());
2899 
2900         int idFile = GetFileId(strFilenameAndPath);
2901 
2902         // update with playCount and lastPlayed
2903         strSQL = PrepareSQL("update files set playCount=%i,lastPlayed='%s' where idFile=%i", playCount, lastPlayed.GetAsDBDateTime().c_str(), idFile);
2904         m_pDS->exec(strSQL);
2905       }
2906 
2907       m_pDS->close();
2908     }
2909     // and insert the new row
2910     std::string sql = "UPDATE episode SET " + GetValueString(details, VIDEODB_ID_EPISODE_MIN, VIDEODB_ID_EPISODE_MAX, DbEpisodeOffsets);
2911     if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
2912       sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
2913     else
2914       sql += ", userrating = NULL";
2915     sql += PrepareSQL(", idSeason = %i", idSeason);
2916     sql += PrepareSQL(" where idEpisode=%i", idEpisode);
2917     m_pDS->exec(sql);
2918     CommitTransaction();
2919 
2920     return idEpisode;
2921   }
2922   catch (...)
2923   {
2924     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
2925   }
2926   RollbackTransaction();
2927   return -1;
2928 }
2929 
GetSeasonId(int showID,int season)2930 int CVideoDatabase::GetSeasonId(int showID, int season)
2931 {
2932   std::string sql = PrepareSQL("idShow=%i AND season=%i", showID, season);
2933   std::string id = GetSingleValue("seasons", "idSeason", sql);
2934   if (id.empty())
2935     return -1;
2936   return strtol(id.c_str(), NULL, 10);
2937 }
2938 
AddSeason(int showID,int season,const std::string & name)2939 int CVideoDatabase::AddSeason(int showID, int season, const std::string& name /* = "" */)
2940 {
2941   int seasonId = GetSeasonId(showID, season);
2942   if (seasonId < 0)
2943   {
2944     if (ExecuteQuery(PrepareSQL("INSERT INTO seasons (idShow, season, name) VALUES(%i, %i, '%s')", showID, season, name.c_str())))
2945       seasonId = (int)m_pDS->lastinsertid();
2946   }
2947   return seasonId;
2948 }
2949 
SetDetailsForMusicVideo(const std::string & strFilenameAndPath,const CVideoInfoTag & details,const std::map<std::string,std::string> & artwork,int idMVideo)2950 int CVideoDatabase::SetDetailsForMusicVideo(const std::string& strFilenameAndPath, const CVideoInfoTag& details,
2951     const std::map<std::string, std::string> &artwork, int idMVideo /* = -1 */)
2952 {
2953   try
2954   {
2955     BeginTransaction();
2956 
2957     if (idMVideo < 0)
2958       idMVideo = GetMusicVideoId(strFilenameAndPath);
2959 
2960     if (idMVideo > -1)
2961       DeleteMusicVideo(idMVideo, true); // Keep id
2962     else
2963     {
2964       // only add a new musicvideo if we don't already have a valid idMVideo
2965       // (DeleteMusicVideo is called with bKeepId == true so the musicvideo won't
2966       // be removed from the musicvideo table)
2967       idMVideo = AddMusicVideo(strFilenameAndPath);
2968       if (idMVideo < 0)
2969       {
2970         RollbackTransaction();
2971         return -1;
2972       }
2973     }
2974 
2975     // update dateadded if it's set
2976     if (details.m_dateAdded.IsValid())
2977     {
2978       int idFile = details.m_iFileId;
2979       if (idFile <= 0)
2980         idFile = GetFileId(strFilenameAndPath);
2981 
2982       UpdateFileDateAdded(idFile, strFilenameAndPath, details.m_dateAdded);
2983     }
2984 
2985     AddCast(idMVideo, MediaTypeMusicVideo, details.m_cast);
2986     AddActorLinksToItem(idMVideo, MediaTypeMusicVideo, "actor", details.m_artist);
2987     AddActorLinksToItem(idMVideo, MediaTypeMusicVideo, "director", details.m_director);
2988     AddLinksToItem(idMVideo, MediaTypeMusicVideo, "genre", details.m_genre);
2989     AddLinksToItem(idMVideo, MediaTypeMusicVideo, "studio", details.m_studio);
2990     AddLinksToItem(idMVideo, MediaTypeMusicVideo, "tag", details.m_tags);
2991 
2992     if (details.HasStreamDetails())
2993       SetStreamDetailsForFileId(details.m_streamDetails, GetFileId(strFilenameAndPath));
2994 
2995     SetArtForItem(idMVideo, MediaTypeMusicVideo, artwork);
2996 
2997     // update our movie table (we know it was added already above)
2998     // and insert the new row
2999     std::string sql = "UPDATE musicvideo SET " + GetValueString(details, VIDEODB_ID_MUSICVIDEO_MIN, VIDEODB_ID_MUSICVIDEO_MAX, DbMusicVideoOffsets);
3000     if (details.m_iUserRating > 0 && details.m_iUserRating < 11)
3001       sql += PrepareSQL(", userrating = %i", details.m_iUserRating);
3002     else
3003       sql += ", userrating = NULL";
3004     if (details.HasPremiered())
3005       sql += PrepareSQL(", premiered = '%s'", details.GetPremiered().GetAsDBDate().c_str());
3006     else
3007       sql += PrepareSQL(", premiered = '%i'", details.GetYear());
3008     sql += PrepareSQL(" where idMVideo=%i", idMVideo);
3009     m_pDS->exec(sql);
3010     CommitTransaction();
3011 
3012     return idMVideo;
3013   }
3014   catch (...)
3015   {
3016     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
3017   }
3018   RollbackTransaction();
3019   return -1;
3020 }
3021 
SetStreamDetailsForFile(const CStreamDetails & details,const std::string & strFileNameAndPath)3022 void CVideoDatabase::SetStreamDetailsForFile(const CStreamDetails& details, const std::string &strFileNameAndPath)
3023 {
3024   // AddFile checks to make sure the file isn't already in the DB first
3025   int idFile = AddFile(strFileNameAndPath);
3026   if (idFile < 0)
3027     return;
3028   SetStreamDetailsForFileId(details, idFile);
3029 }
3030 
SetStreamDetailsForFileId(const CStreamDetails & details,int idFile)3031 void CVideoDatabase::SetStreamDetailsForFileId(const CStreamDetails& details, int idFile)
3032 {
3033   if (idFile < 0)
3034     return;
3035 
3036   try
3037   {
3038     BeginTransaction();
3039     m_pDS->exec(PrepareSQL("DELETE FROM streamdetails WHERE idFile = %i", idFile));
3040 
3041     for (int i=1; i<=details.GetVideoStreamCount(); i++)
3042     {
3043       m_pDS->exec(PrepareSQL("INSERT INTO streamdetails "
3044         "(idFile, iStreamType, strVideoCodec, fVideoAspect, iVideoWidth, iVideoHeight, iVideoDuration, strStereoMode, strVideoLanguage) "
3045         "VALUES (%i,%i,'%s',%f,%i,%i,%i,'%s','%s')",
3046         idFile, (int)CStreamDetail::VIDEO,
3047         details.GetVideoCodec(i).c_str(), details.GetVideoAspect(i),
3048         details.GetVideoWidth(i), details.GetVideoHeight(i), details.GetVideoDuration(i),
3049         details.GetStereoMode(i).c_str(),
3050         details.GetVideoLanguage(i).c_str()));
3051     }
3052     for (int i=1; i<=details.GetAudioStreamCount(); i++)
3053     {
3054       m_pDS->exec(PrepareSQL("INSERT INTO streamdetails "
3055         "(idFile, iStreamType, strAudioCodec, iAudioChannels, strAudioLanguage) "
3056         "VALUES (%i,%i,'%s',%i,'%s')",
3057         idFile, (int)CStreamDetail::AUDIO,
3058         details.GetAudioCodec(i).c_str(), details.GetAudioChannels(i),
3059         details.GetAudioLanguage(i).c_str()));
3060     }
3061     for (int i=1; i<=details.GetSubtitleStreamCount(); i++)
3062     {
3063       m_pDS->exec(PrepareSQL("INSERT INTO streamdetails "
3064         "(idFile, iStreamType, strSubtitleLanguage) "
3065         "VALUES (%i,%i,'%s')",
3066         idFile, (int)CStreamDetail::SUBTITLE,
3067         details.GetSubtitleLanguage(i).c_str()));
3068     }
3069 
3070     // update the runtime information, if empty
3071     if (details.GetVideoDuration())
3072     {
3073       std::vector<std::pair<std::string, int> > tables;
3074       tables.emplace_back("movie", VIDEODB_ID_RUNTIME);
3075       tables.emplace_back("episode", VIDEODB_ID_EPISODE_RUNTIME);
3076       tables.emplace_back("musicvideo", VIDEODB_ID_MUSICVIDEO_RUNTIME);
3077       for (const auto &i : tables)
3078       {
3079         std::string sql = PrepareSQL("update %s set c%02d=%d where idFile=%d and c%02d=''",
3080                                     i.first.c_str(), i.second, details.GetVideoDuration(), idFile, i.second);
3081         m_pDS->exec(sql);
3082       }
3083     }
3084 
3085     CommitTransaction();
3086   }
3087   catch (...)
3088   {
3089     RollbackTransaction();
3090     CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, idFile);
3091   }
3092 }
3093 
3094 //********************************************************************************************************************************
GetFilePathById(int idMovie,std::string & filePath,VIDEODB_CONTENT_TYPE iType)3095 void CVideoDatabase::GetFilePathById(int idMovie, std::string &filePath, VIDEODB_CONTENT_TYPE iType)
3096 {
3097   try
3098   {
3099     if (nullptr == m_pDB)
3100       return;
3101     if (nullptr == m_pDS)
3102       return;
3103 
3104     if (idMovie < 0) return ;
3105 
3106     std::string strSQL;
3107     if (iType == VIDEODB_CONTENT_MOVIES)
3108       strSQL=PrepareSQL("SELECT path.strPath, files.strFileName FROM path INNER JOIN files ON path.idPath=files.idPath INNER JOIN movie ON files.idFile=movie.idFile WHERE movie.idMovie=%i ORDER BY strFilename", idMovie );
3109     if (iType == VIDEODB_CONTENT_EPISODES)
3110       strSQL=PrepareSQL("SELECT path.strPath, files.strFileName FROM path INNER JOIN files ON path.idPath=files.idPath INNER JOIN episode ON files.idFile=episode.idFile WHERE episode.idEpisode=%i ORDER BY strFilename", idMovie );
3111     if (iType == VIDEODB_CONTENT_TVSHOWS)
3112       strSQL=PrepareSQL("SELECT path.strPath FROM path INNER JOIN tvshowlinkpath ON path.idPath=tvshowlinkpath.idPath WHERE tvshowlinkpath.idShow=%i", idMovie );
3113     if (iType ==VIDEODB_CONTENT_MUSICVIDEOS)
3114       strSQL=PrepareSQL("SELECT path.strPath, files.strFileName FROM path INNER JOIN files ON path.idPath=files.idPath INNER JOIN musicvideo ON files.idFile=musicvideo.idFile WHERE musicvideo.idMVideo=%i ORDER BY strFilename", idMovie );
3115 
3116     m_pDS->query( strSQL );
3117     if (!m_pDS->eof())
3118     {
3119       if (iType != VIDEODB_CONTENT_TVSHOWS)
3120       {
3121         std::string fileName = m_pDS->fv("files.strFilename").get_asString();
3122         ConstructPath(filePath,m_pDS->fv("path.strPath").get_asString(),fileName);
3123       }
3124       else
3125         filePath = m_pDS->fv("path.strPath").get_asString();
3126     }
3127     m_pDS->close();
3128   }
3129   catch (...)
3130   {
3131     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
3132   }
3133 }
3134 
3135 //********************************************************************************************************************************
GetBookMarksForFile(const std::string & strFilenameAndPath,VECBOOKMARKS & bookmarks,CBookmark::EType type,bool bAppend,long partNumber)3136 void CVideoDatabase::GetBookMarksForFile(const std::string& strFilenameAndPath, VECBOOKMARKS& bookmarks, CBookmark::EType type /*= CBookmark::STANDARD*/, bool bAppend, long partNumber)
3137 {
3138   try
3139   {
3140     if (URIUtils::IsStack(strFilenameAndPath) && CFileItem(CStackDirectory::GetFirstStackedFile(strFilenameAndPath),false).IsDiscImage())
3141     {
3142       CStackDirectory dir;
3143       CFileItemList fileList;
3144       const CURL pathToUrl(strFilenameAndPath);
3145       dir.GetDirectory(pathToUrl, fileList);
3146       if (!bAppend)
3147         bookmarks.clear();
3148       for (int i = fileList.Size() - 1; i >= 0; i--) // put the bookmarks of the highest part first in the list
3149         GetBookMarksForFile(fileList[i]->GetPath(), bookmarks, type, true, (i+1));
3150     }
3151     else
3152     {
3153       int idFile = GetFileId(strFilenameAndPath);
3154       if (idFile < 0) return ;
3155       if (!bAppend)
3156         bookmarks.erase(bookmarks.begin(), bookmarks.end());
3157       if (nullptr == m_pDB)
3158         return;
3159       if (nullptr == m_pDS)
3160         return;
3161 
3162       std::string strSQL=PrepareSQL("select * from bookmark where idFile=%i and type=%i order by timeInSeconds", idFile, (int)type);
3163       m_pDS->query( strSQL );
3164       while (!m_pDS->eof())
3165       {
3166         CBookmark bookmark;
3167         bookmark.timeInSeconds = m_pDS->fv("timeInSeconds").get_asDouble();
3168         bookmark.partNumber = partNumber;
3169         bookmark.totalTimeInSeconds = m_pDS->fv("totalTimeInSeconds").get_asDouble();
3170         bookmark.thumbNailImage = m_pDS->fv("thumbNailImage").get_asString();
3171         bookmark.playerState = m_pDS->fv("playerState").get_asString();
3172         bookmark.player = m_pDS->fv("player").get_asString();
3173         bookmark.type = type;
3174         if (type == CBookmark::EPISODE)
3175         {
3176           std::string 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);
3177           m_pDS2->query(strSQL2);
3178           bookmark.episodeNumber = m_pDS2->fv(0).get_asInt();
3179           bookmark.seasonNumber = m_pDS2->fv(1).get_asInt();
3180           m_pDS2->close();
3181         }
3182         bookmarks.push_back(bookmark);
3183         m_pDS->next();
3184       }
3185       //sort(bookmarks.begin(), bookmarks.end(), SortBookmarks);
3186       m_pDS->close();
3187     }
3188   }
3189   catch (...)
3190   {
3191     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
3192   }
3193 }
3194 
GetResumeBookMark(const std::string & strFilenameAndPath,CBookmark & bookmark)3195 bool CVideoDatabase::GetResumeBookMark(const std::string& strFilenameAndPath, CBookmark &bookmark)
3196 {
3197   VECBOOKMARKS bookmarks;
3198   GetBookMarksForFile(strFilenameAndPath, bookmarks, CBookmark::RESUME);
3199   if (!bookmarks.empty())
3200   {
3201     bookmark = bookmarks[0];
3202     return true;
3203   }
3204   return false;
3205 }
3206 
DeleteResumeBookMark(const CFileItem & item)3207 void CVideoDatabase::DeleteResumeBookMark(const CFileItem& item)
3208 {
3209   if (!m_pDB || !m_pDS)
3210     return;
3211 
3212   int fileID = item.GetVideoInfoTag()->m_iFileId;
3213   if (fileID < 0)
3214   {
3215     fileID = GetFileId(item.GetPath());
3216     if (fileID < 0)
3217       return;
3218   }
3219 
3220   try
3221   {
3222     std::string sql = PrepareSQL("delete from bookmark where idFile=%i and type=%i", fileID, CBookmark::RESUME);
3223     m_pDS->exec(sql);
3224 
3225     VIDEODB_CONTENT_TYPE iType = static_cast<VIDEODB_CONTENT_TYPE>(item.GetVideoContentType());
3226     std::string content;
3227     switch (iType)
3228     {
3229       case VIDEODB_CONTENT_MOVIES:
3230         content = MediaTypeMovie;
3231         break;
3232       case VIDEODB_CONTENT_EPISODES:
3233         content = MediaTypeEpisode;
3234         break;
3235       case VIDEODB_CONTENT_TVSHOWS:
3236         content = MediaTypeTvShow;
3237         break;
3238       case VIDEODB_CONTENT_MUSICVIDEOS:
3239         content = MediaTypeMusicVideo;
3240         break;
3241       default:
3242         break;
3243     }
3244 
3245     if (!content.empty())
3246     {
3247       AnnounceUpdate(content, item.GetVideoInfoTag()->m_iDbId);
3248     }
3249 
3250   }
3251   catch(...)
3252   {
3253     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, item.GetVideoInfoTag()->m_strFileNameAndPath.c_str());
3254   }
3255 }
3256 
GetEpisodesByFile(const std::string & strFilenameAndPath,std::vector<CVideoInfoTag> & episodes)3257 void CVideoDatabase::GetEpisodesByFile(const std::string& strFilenameAndPath, std::vector<CVideoInfoTag>& episodes)
3258 {
3259   try
3260   {
3261     std::string strSQL = PrepareSQL("select * from episode_view where idFile=%i order by c%02d, c%02d asc", GetFileId(strFilenameAndPath), VIDEODB_ID_EPISODE_SORTSEASON, VIDEODB_ID_EPISODE_SORTEPISODE);
3262     m_pDS->query(strSQL);
3263     while (!m_pDS->eof())
3264     {
3265       episodes.emplace_back(GetDetailsForEpisode(m_pDS));
3266       m_pDS->next();
3267     }
3268     m_pDS->close();
3269   }
3270   catch (...)
3271   {
3272     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
3273   }
3274 }
3275 
3276 //********************************************************************************************************************************
AddBookMarkToFile(const std::string & strFilenameAndPath,const CBookmark & bookmark,CBookmark::EType type)3277 void CVideoDatabase::AddBookMarkToFile(const std::string& strFilenameAndPath, const CBookmark &bookmark, CBookmark::EType type /*= CBookmark::STANDARD*/)
3278 {
3279   try
3280   {
3281     int idFile = AddFile(strFilenameAndPath);
3282     if (idFile < 0)
3283       return;
3284     if (nullptr == m_pDB)
3285       return;
3286     if (nullptr == m_pDS)
3287       return;
3288 
3289     std::string strSQL;
3290     int idBookmark=-1;
3291     if (type == CBookmark::RESUME) // get the same resume mark bookmark each time type
3292     {
3293       strSQL=PrepareSQL("select idBookmark from bookmark where idFile=%i and type=1", idFile);
3294     }
3295     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
3296     {
3297       /* get a bookmark within the same time as previous */
3298       double mintime = bookmark.timeInSeconds - 0.5f;
3299       double maxtime = bookmark.timeInSeconds + 0.5f;
3300       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());
3301     }
3302 
3303     if (type != CBookmark::EPISODE)
3304     {
3305       // get current id
3306       m_pDS->query( strSQL );
3307       if (m_pDS->num_rows() != 0)
3308         idBookmark = m_pDS->get_field_value("idBookmark").get_asInt();
3309       m_pDS->close();
3310     }
3311     // update or insert depending if it existed before
3312     if (idBookmark >= 0 )
3313       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);
3314     else
3315       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);
3316 
3317     m_pDS->exec(strSQL);
3318   }
3319   catch (...)
3320   {
3321     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
3322   }
3323 }
3324 
ClearBookMarkOfFile(const std::string & strFilenameAndPath,CBookmark & bookmark,CBookmark::EType type)3325 void CVideoDatabase::ClearBookMarkOfFile(const std::string& strFilenameAndPath, CBookmark& bookmark, CBookmark::EType type /*= CBookmark::STANDARD*/)
3326 {
3327   try
3328   {
3329     int idFile = GetFileId(strFilenameAndPath);
3330     if (idFile < 0) return ;
3331     if (nullptr == m_pDB)
3332       return;
3333     if (nullptr == m_pDS)
3334       return;
3335 
3336     /* a little bit uggly, we clear first bookmark that is within one second of given */
3337     /* should be no problem since we never add bookmarks that are closer than that   */
3338     double mintime = bookmark.timeInSeconds - 0.5f;
3339     double maxtime = bookmark.timeInSeconds + 0.5f;
3340     std::string 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);
3341 
3342     m_pDS->query( strSQL );
3343     if (m_pDS->num_rows() != 0)
3344     {
3345       int idBookmark = m_pDS->get_field_value("idBookmark").get_asInt();
3346       strSQL=PrepareSQL("delete from bookmark where idBookmark=%i",idBookmark);
3347       m_pDS->exec(strSQL);
3348       if (type == CBookmark::EPISODE)
3349       {
3350         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);
3351         m_pDS->exec(strSQL);
3352       }
3353     }
3354 
3355     m_pDS->close();
3356   }
3357   catch (...)
3358   {
3359     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strFilenameAndPath.c_str());
3360   }
3361 }
3362 
3363 //********************************************************************************************************************************
ClearBookMarksOfFile(const std::string & strFilenameAndPath,CBookmark::EType type)3364 void CVideoDatabase::ClearBookMarksOfFile(const std::string& strFilenameAndPath, CBookmark::EType type /*= CBookmark::STANDARD*/)
3365 {
3366   int idFile = GetFileId(strFilenameAndPath);
3367   if (idFile >= 0)
3368     return ClearBookMarksOfFile(idFile, type);
3369 }
3370 
ClearBookMarksOfFile(int idFile,CBookmark::EType type)3371 void CVideoDatabase::ClearBookMarksOfFile(int idFile, CBookmark::EType type /*= CBookmark::STANDARD*/)
3372 {
3373   if (idFile < 0)
3374     return;
3375 
3376   try
3377   {
3378     if (nullptr == m_pDB)
3379       return;
3380     if (nullptr == m_pDS)
3381       return;
3382 
3383     std::string strSQL=PrepareSQL("delete from bookmark where idFile=%i and type=%i", idFile, (int)type);
3384     m_pDS->exec(strSQL);
3385     if (type == CBookmark::EPISODE)
3386     {
3387       strSQL=PrepareSQL("update episode set c%02d=-1 where idFile=%i", VIDEODB_ID_EPISODE_BOOKMARK, idFile);
3388       m_pDS->exec(strSQL);
3389     }
3390   }
3391   catch (...)
3392   {
3393     CLog::Log(LOGERROR, "%s (%d) failed", __FUNCTION__, idFile);
3394   }
3395 }
3396 
3397 
GetBookMarkForEpisode(const CVideoInfoTag & tag,CBookmark & bookmark)3398 bool CVideoDatabase::GetBookMarkForEpisode(const CVideoInfoTag& tag, CBookmark& bookmark)
3399 {
3400   try
3401   {
3402     std::string 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);
3403     m_pDS2->query( strSQL );
3404     if (!m_pDS2->eof())
3405     {
3406       bookmark.timeInSeconds = m_pDS2->fv("timeInSeconds").get_asDouble();
3407       bookmark.totalTimeInSeconds = m_pDS2->fv("totalTimeInSeconds").get_asDouble();
3408       bookmark.thumbNailImage = m_pDS2->fv("thumbNailImage").get_asString();
3409       bookmark.playerState = m_pDS2->fv("playerState").get_asString();
3410       bookmark.player = m_pDS2->fv("player").get_asString();
3411       bookmark.type = (CBookmark::EType)m_pDS2->fv("type").get_asInt();
3412     }
3413     else
3414     {
3415       m_pDS2->close();
3416       return false;
3417     }
3418     m_pDS2->close();
3419   }
3420   catch (...)
3421   {
3422     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
3423     return false;
3424   }
3425   return true;
3426 }
3427 
AddBookMarkForEpisode(const CVideoInfoTag & tag,const CBookmark & bookmark)3428 void CVideoDatabase::AddBookMarkForEpisode(const CVideoInfoTag& tag, const CBookmark& bookmark)
3429 {
3430   try
3431   {
3432     int idFile = GetFileId(tag.m_strFileNameAndPath);
3433     // delete the current episode for the selected episode number
3434     std::string 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);
3435     m_pDS->exec(strSQL);
3436 
3437     AddBookMarkToFile(tag.m_strFileNameAndPath, bookmark, CBookmark::EPISODE);
3438     int idBookmark = (int)m_pDS->lastinsertid();
3439     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);
3440     m_pDS->exec(strSQL);
3441   }
3442   catch (...)
3443   {
3444     CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, tag.m_iDbId);
3445   }
3446 }
3447 
DeleteBookMarkForEpisode(const CVideoInfoTag & tag)3448 void CVideoDatabase::DeleteBookMarkForEpisode(const CVideoInfoTag& tag)
3449 {
3450   try
3451   {
3452     std::string strSQL = PrepareSQL("delete from bookmark where idBookmark in (select c%02d from episode where idEpisode=%i)", VIDEODB_ID_EPISODE_BOOKMARK, tag.m_iDbId);
3453     m_pDS->exec(strSQL);
3454     strSQL = PrepareSQL("update episode set c%02d=-1 where idEpisode=%i", VIDEODB_ID_EPISODE_BOOKMARK, tag.m_iDbId);
3455     m_pDS->exec(strSQL);
3456   }
3457   catch (...)
3458   {
3459     CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, tag.m_iDbId);
3460   }
3461 }
3462 
3463 //********************************************************************************************************************************
DeleteMovie(const std::string & strFilenameAndPath,bool bKeepId)3464 void CVideoDatabase::DeleteMovie(const std::string& strFilenameAndPath, bool bKeepId /* = false */)
3465 {
3466   int idMovie = GetMovieId(strFilenameAndPath);
3467   if (idMovie > -1)
3468     DeleteMovie(idMovie, bKeepId);
3469 }
3470 
DeleteMovie(int idMovie,bool bKeepId)3471 void CVideoDatabase::DeleteMovie(int idMovie, bool bKeepId /* = false */)
3472 {
3473   if (idMovie < 0)
3474     return;
3475 
3476   try
3477   {
3478     if (nullptr == m_pDB)
3479       return;
3480     if (nullptr == m_pDS)
3481       return;
3482 
3483     BeginTransaction();
3484 
3485     int idFile = GetDbId(PrepareSQL("SELECT idFile FROM movie WHERE idMovie=%i", idMovie));
3486     DeleteStreamDetails(idFile);
3487 
3488     // keep the movie table entry, linking to tv shows, and bookmarks
3489     // so we can update the data in place
3490     // the ancillary tables are still purged
3491     if (!bKeepId)
3492     {
3493       std::string path = GetSingleValue(PrepareSQL("SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i", idFile));
3494       if (!path.empty())
3495         InvalidatePathHash(path);
3496 
3497       std::string strSQL = PrepareSQL("delete from movie where idMovie=%i", idMovie);
3498       m_pDS->exec(strSQL);
3499     }
3500 
3501     //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
3502     if (!bKeepId)
3503       AnnounceRemove(MediaTypeMovie, idMovie);
3504 
3505     CommitTransaction();
3506 
3507   }
3508   catch (...)
3509   {
3510     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
3511     RollbackTransaction();
3512   }
3513 }
3514 
DeleteTvShow(const std::string & strPath)3515 void CVideoDatabase::DeleteTvShow(const std::string& strPath)
3516 {
3517   int idTvShow = GetTvShowId(strPath);
3518   if (idTvShow >= 0)
3519     DeleteTvShow(idTvShow);
3520 }
3521 
DeleteTvShow(int idTvShow,bool bKeepId)3522 void CVideoDatabase::DeleteTvShow(int idTvShow, bool bKeepId /* = false */)
3523 {
3524   if (idTvShow < 0)
3525     return;
3526 
3527   try
3528   {
3529     if (nullptr == m_pDB)
3530       return;
3531     if (nullptr == m_pDS)
3532       return;
3533 
3534     BeginTransaction();
3535 
3536     std::set<int> paths;
3537     GetPathsForTvShow(idTvShow, paths);
3538 
3539     std::string strSQL=PrepareSQL("SELECT episode.idEpisode FROM episode WHERE episode.idShow=%i",idTvShow);
3540     m_pDS2->query(strSQL);
3541     while (!m_pDS2->eof())
3542     {
3543       DeleteEpisode(m_pDS2->fv(0).get_asInt(), bKeepId);
3544       m_pDS2->next();
3545     }
3546 
3547     DeleteDetailsForTvShow(idTvShow);
3548 
3549     strSQL=PrepareSQL("delete from seasons where idShow=%i", idTvShow);
3550     m_pDS->exec(strSQL);
3551 
3552     // keep tvshow table and movielink table so we can update data in place
3553     if (!bKeepId)
3554     {
3555       strSQL=PrepareSQL("delete from tvshow where idShow=%i", idTvShow);
3556       m_pDS->exec(strSQL);
3557 
3558       for (const auto &i : paths)
3559       {
3560         std::string path = GetSingleValue(PrepareSQL("SELECT strPath FROM path WHERE idPath=%i", i));
3561         if (!path.empty())
3562           InvalidatePathHash(path);
3563       }
3564     }
3565 
3566     //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
3567     if (!bKeepId)
3568       AnnounceRemove(MediaTypeTvShow, idTvShow);
3569 
3570     CommitTransaction();
3571 
3572   }
3573   catch (...)
3574   {
3575     CLog::Log(LOGERROR, "%s (%d) failed", __FUNCTION__, idTvShow);
3576     RollbackTransaction();
3577   }
3578 }
3579 
DeleteSeason(int idSeason,bool bKeepId)3580 void CVideoDatabase::DeleteSeason(int idSeason, bool bKeepId /* = false */)
3581 {
3582   if (idSeason < 0)
3583     return;
3584 
3585   try
3586   {
3587     if (m_pDB == nullptr || m_pDS == nullptr || m_pDS2 == nullptr)
3588       return;
3589 
3590     BeginTransaction();
3591 
3592     std::string strSQL = PrepareSQL("SELECT episode.idEpisode FROM episode "
3593                                     "JOIN seasons ON seasons.idSeason = %d AND episode.idShow = seasons.idShow AND episode.c%02d = seasons.season ",
3594                                    idSeason, VIDEODB_ID_EPISODE_SEASON);
3595     m_pDS2->query(strSQL);
3596     while (!m_pDS2->eof())
3597     {
3598       DeleteEpisode(m_pDS2->fv(0).get_asInt(), bKeepId);
3599       m_pDS2->next();
3600     }
3601 
3602     ExecuteQuery(PrepareSQL("DELETE FROM seasons WHERE idSeason = %i", idSeason));
3603 
3604     CommitTransaction();
3605   }
3606   catch (...)
3607   {
3608     CLog::Log(LOGERROR, "%s (%d) failed", __FUNCTION__, idSeason);
3609     RollbackTransaction();
3610   }
3611 }
3612 
DeleteEpisode(const std::string & strFilenameAndPath,bool bKeepId)3613 void CVideoDatabase::DeleteEpisode(const std::string& strFilenameAndPath, bool bKeepId /* = false */)
3614 {
3615   int idEpisode = GetEpisodeId(strFilenameAndPath);
3616   if (idEpisode > -1)
3617     DeleteEpisode(idEpisode, bKeepId);
3618 }
3619 
DeleteEpisode(int idEpisode,bool bKeepId)3620 void CVideoDatabase::DeleteEpisode(int idEpisode, bool bKeepId /* = false */)
3621 {
3622   if (idEpisode < 0)
3623     return;
3624 
3625   try
3626   {
3627     if (nullptr == m_pDB)
3628       return;
3629     if (nullptr == m_pDS)
3630       return;
3631 
3632     //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
3633     if (!bKeepId)
3634       AnnounceRemove(MediaTypeEpisode, idEpisode);
3635 
3636     int idFile = GetDbId(PrepareSQL("SELECT idFile FROM episode WHERE idEpisode=%i", idEpisode));
3637     DeleteStreamDetails(idFile);
3638 
3639     // keep episode table entry and bookmarks so we can update the data in place
3640     // the ancillary tables are still purged
3641     if (!bKeepId)
3642     {
3643       std::string path = GetSingleValue(PrepareSQL("SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i", idFile));
3644       if (!path.empty())
3645         InvalidatePathHash(path);
3646 
3647       std::string strSQL = PrepareSQL("delete from episode where idEpisode=%i", idEpisode);
3648       m_pDS->exec(strSQL);
3649     }
3650 
3651   }
3652   catch (...)
3653   {
3654     CLog::Log(LOGERROR, "%s (%d) failed", __FUNCTION__, idEpisode);
3655   }
3656 }
3657 
DeleteMusicVideo(const std::string & strFilenameAndPath,bool bKeepId)3658 void CVideoDatabase::DeleteMusicVideo(const std::string& strFilenameAndPath, bool bKeepId /* = false */)
3659 {
3660   int idMVideo = GetMusicVideoId(strFilenameAndPath);
3661   if (idMVideo > -1)
3662     DeleteMusicVideo(idMVideo, bKeepId);
3663 }
3664 
DeleteMusicVideo(int idMVideo,bool bKeepId)3665 void CVideoDatabase::DeleteMusicVideo(int idMVideo, bool bKeepId /* = false */)
3666 {
3667   if (idMVideo < 0)
3668     return;
3669 
3670   try
3671   {
3672     if (nullptr == m_pDB)
3673       return;
3674     if (nullptr == m_pDS)
3675       return;
3676 
3677     BeginTransaction();
3678 
3679     int idFile = GetDbId(PrepareSQL("SELECT idFile FROM musicvideo WHERE idMVideo=%i", idMVideo));
3680     DeleteStreamDetails(idFile);
3681 
3682     // keep the music video table entry and bookmarks so we can update data in place
3683     // the ancillary tables are still purged
3684     if (!bKeepId)
3685     {
3686       std::string path = GetSingleValue(PrepareSQL("SELECT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile=%i", idFile));
3687       if (!path.empty())
3688         InvalidatePathHash(path);
3689 
3690       std::string strSQL = PrepareSQL("delete from musicvideo where idMVideo=%i", idMVideo);
3691       m_pDS->exec(strSQL);
3692     }
3693 
3694     //! @todo move this below CommitTransaction() once UPnP doesn't rely on this anymore
3695     if (!bKeepId)
3696       AnnounceRemove(MediaTypeMusicVideo, idMVideo);
3697 
3698     CommitTransaction();
3699 
3700   }
3701   catch (...)
3702   {
3703     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
3704     RollbackTransaction();
3705   }
3706 }
3707 
GetDbId(const std::string & query)3708 int CVideoDatabase::GetDbId(const std::string &query)
3709 {
3710   std::string result = GetSingleValue(query);
3711   if (!result.empty())
3712   {
3713     int idDb = strtol(result.c_str(), NULL, 10);
3714     if (idDb > 0)
3715       return idDb;
3716   }
3717   return -1;
3718 }
3719 
DeleteStreamDetails(int idFile)3720 void CVideoDatabase::DeleteStreamDetails(int idFile)
3721 {
3722   m_pDS->exec(PrepareSQL("DELETE FROM streamdetails WHERE idFile = %i", idFile));
3723 }
3724 
DeleteSet(int idSet)3725 void CVideoDatabase::DeleteSet(int idSet)
3726 {
3727   try
3728   {
3729     if (nullptr == m_pDB)
3730       return;
3731     if (nullptr == m_pDS)
3732       return;
3733 
3734     std::string strSQL;
3735     strSQL=PrepareSQL("delete from sets where idSet = %i", idSet);
3736     m_pDS->exec(strSQL);
3737     strSQL=PrepareSQL("update movie set idSet = null where idSet = %i", idSet);
3738     m_pDS->exec(strSQL);
3739   }
3740   catch (...)
3741   {
3742     CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, idSet);
3743   }
3744 }
3745 
ClearMovieSet(int idMovie)3746 void CVideoDatabase::ClearMovieSet(int idMovie)
3747 {
3748   SetMovieSet(idMovie, -1);
3749 }
3750 
SetMovieSet(int idMovie,int idSet)3751 void CVideoDatabase::SetMovieSet(int idMovie, int idSet)
3752 {
3753   if (idSet >= 0)
3754     ExecuteQuery(PrepareSQL("update movie set idSet = %i where idMovie = %i", idSet, idMovie));
3755   else
3756     ExecuteQuery(PrepareSQL("update movie set idSet = null where idMovie = %i", idMovie));
3757 }
3758 
DeleteTag(int idTag,VIDEODB_CONTENT_TYPE mediaType)3759 void CVideoDatabase::DeleteTag(int idTag, VIDEODB_CONTENT_TYPE mediaType)
3760 {
3761   try
3762   {
3763     if (m_pDB == nullptr || m_pDS == nullptr)
3764       return;
3765 
3766     std::string type;
3767     if (mediaType == VIDEODB_CONTENT_MOVIES)
3768       type = MediaTypeMovie;
3769     else if (mediaType == VIDEODB_CONTENT_TVSHOWS)
3770       type = MediaTypeTvShow;
3771     else if (mediaType == VIDEODB_CONTENT_MUSICVIDEOS)
3772       type = MediaTypeMusicVideo;
3773     else
3774       return;
3775 
3776     std::string strSQL = PrepareSQL("DELETE FROM tag_link WHERE tag_id = %i AND media_type = '%s'", idTag, type.c_str());
3777     m_pDS->exec(strSQL);
3778   }
3779   catch (...)
3780   {
3781     CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, idTag);
3782   }
3783 }
3784 
GetDetailsFromDB(std::unique_ptr<Dataset> & pDS,int min,int max,const SDbTableOffsets * offsets,CVideoInfoTag & details,int idxOffset)3785 void CVideoDatabase::GetDetailsFromDB(std::unique_ptr<Dataset> &pDS, int min, int max, const SDbTableOffsets *offsets, CVideoInfoTag &details, int idxOffset)
3786 {
3787   GetDetailsFromDB(pDS->get_sql_record(), min, max, offsets, details, idxOffset);
3788 }
3789 
GetDetailsFromDB(const dbiplus::sql_record * const record,int min,int max,const SDbTableOffsets * offsets,CVideoInfoTag & details,int idxOffset)3790 void CVideoDatabase::GetDetailsFromDB(const dbiplus::sql_record* const record, int min, int max, const SDbTableOffsets *offsets, CVideoInfoTag &details, int idxOffset)
3791 {
3792   for (int i = min + 1; i < max; i++)
3793   {
3794     switch (offsets[i].type)
3795     {
3796     case VIDEODB_TYPE_STRING:
3797       *(std::string*)(((char*)&details)+offsets[i].offset) = record->at(i+idxOffset).get_asString();
3798       break;
3799     case VIDEODB_TYPE_INT:
3800     case VIDEODB_TYPE_COUNT:
3801       *(int*)(((char*)&details)+offsets[i].offset) = record->at(i+idxOffset).get_asInt();
3802       break;
3803     case VIDEODB_TYPE_BOOL:
3804       *(bool*)(((char*)&details)+offsets[i].offset) = record->at(i+idxOffset).get_asBool();
3805       break;
3806     case VIDEODB_TYPE_FLOAT:
3807       *(float*)(((char*)&details)+offsets[i].offset) = record->at(i+idxOffset).get_asFloat();
3808       break;
3809     case VIDEODB_TYPE_STRINGARRAY:
3810     {
3811       std::string value = record->at(i+idxOffset).get_asString();
3812       if (!value.empty())
3813         *(std::vector<std::string>*)(((char*)&details)+offsets[i].offset) = StringUtils::Split(value, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
3814       break;
3815     }
3816     case VIDEODB_TYPE_DATE:
3817       ((CDateTime*)(((char*)&details)+offsets[i].offset))->SetFromDBDate(record->at(i+idxOffset).get_asString());
3818       break;
3819     case VIDEODB_TYPE_DATETIME:
3820       ((CDateTime*)(((char*)&details)+offsets[i].offset))->SetFromDBDateTime(record->at(i+idxOffset).get_asString());
3821       break;
3822     case VIDEODB_TYPE_UNUSED: // Skip the unused field to avoid populating unused data
3823       continue;
3824     }
3825   }
3826 }
3827 
3828 DWORD movieTime = 0;
3829 DWORD castTime = 0;
3830 
GetDetailsByTypeAndId(VIDEODB_CONTENT_TYPE type,int id)3831 CVideoInfoTag CVideoDatabase::GetDetailsByTypeAndId(VIDEODB_CONTENT_TYPE type, int id)
3832 {
3833   CVideoInfoTag details;
3834   details.Reset();
3835 
3836   switch (type)
3837   {
3838     case VIDEODB_CONTENT_MOVIES:
3839       GetMovieInfo("", details, id);
3840       break;
3841     case VIDEODB_CONTENT_TVSHOWS:
3842       GetTvShowInfo("", details, id);
3843       break;
3844     case VIDEODB_CONTENT_EPISODES:
3845       GetEpisodeInfo("", details, id);
3846       break;
3847     case VIDEODB_CONTENT_MUSICVIDEOS:
3848       GetMusicVideoInfo("", details, id);
3849       break;
3850     default:
3851       break;
3852   }
3853 
3854   return details;
3855 }
3856 
GetStreamDetails(CFileItem & item)3857 bool CVideoDatabase::GetStreamDetails(CFileItem& item)
3858 {
3859   // Note that this function (possibly) creates VideoInfoTags for items that don't have one yet!
3860   int fileId = -1;
3861 
3862   if (item.HasVideoInfoTag())
3863     fileId = item.GetVideoInfoTag()->m_iFileId;
3864 
3865   if (fileId < 0)
3866     fileId = GetFileId(item);
3867 
3868   if (fileId < 0)
3869     return false;
3870 
3871   // Have a file id, get stream details if available (creates tag either way)
3872   item.GetVideoInfoTag()->m_iFileId = fileId;
3873   return GetStreamDetails(*item.GetVideoInfoTag());
3874 }
3875 
GetStreamDetails(CVideoInfoTag & tag) const3876 bool CVideoDatabase::GetStreamDetails(CVideoInfoTag& tag) const
3877 {
3878   if (tag.m_iFileId < 0)
3879     return false;
3880 
3881   bool retVal = false;
3882 
3883   CStreamDetails& details = tag.m_streamDetails;
3884   details.Reset();
3885 
3886   std::unique_ptr<Dataset> pDS(m_pDB->CreateDataset());
3887   try
3888   {
3889     std::string strSQL = PrepareSQL("SELECT * FROM streamdetails WHERE idFile = %i", tag.m_iFileId);
3890     pDS->query(strSQL);
3891 
3892     while (!pDS->eof())
3893     {
3894       CStreamDetail::StreamType e = (CStreamDetail::StreamType)pDS->fv(1).get_asInt();
3895       switch (e)
3896       {
3897       case CStreamDetail::VIDEO:
3898         {
3899           CStreamDetailVideo *p = new CStreamDetailVideo();
3900           p->m_strCodec = pDS->fv(2).get_asString();
3901           p->m_fAspect = pDS->fv(3).get_asFloat();
3902           p->m_iWidth = pDS->fv(4).get_asInt();
3903           p->m_iHeight = pDS->fv(5).get_asInt();
3904           p->m_iDuration = pDS->fv(10).get_asInt();
3905           p->m_strStereoMode = pDS->fv(11).get_asString();
3906           p->m_strLanguage = pDS->fv(12).get_asString();
3907           details.AddStream(p);
3908           retVal = true;
3909           break;
3910         }
3911       case CStreamDetail::AUDIO:
3912         {
3913           CStreamDetailAudio *p = new CStreamDetailAudio();
3914           p->m_strCodec = pDS->fv(6).get_asString();
3915           if (pDS->fv(7).get_isNull())
3916             p->m_iChannels = -1;
3917           else
3918             p->m_iChannels = pDS->fv(7).get_asInt();
3919           p->m_strLanguage = pDS->fv(8).get_asString();
3920           details.AddStream(p);
3921           retVal = true;
3922           break;
3923         }
3924       case CStreamDetail::SUBTITLE:
3925         {
3926           CStreamDetailSubtitle *p = new CStreamDetailSubtitle();
3927           p->m_strLanguage = pDS->fv(9).get_asString();
3928           details.AddStream(p);
3929           retVal = true;
3930           break;
3931         }
3932       }
3933 
3934       pDS->next();
3935     }
3936 
3937     pDS->close();
3938   }
3939   catch (...)
3940   {
3941     CLog::Log(LOGERROR, "%s(%i) failed", __FUNCTION__, tag.m_iFileId);
3942   }
3943   details.DetermineBestStreams();
3944 
3945   if (details.GetVideoDuration() > 0)
3946     tag.SetDuration(details.GetVideoDuration());
3947 
3948   return retVal;
3949 }
3950 
GetResumePoint(CVideoInfoTag & tag)3951 bool CVideoDatabase::GetResumePoint(CVideoInfoTag& tag)
3952 {
3953   if (tag.m_iFileId < 0)
3954     return false;
3955 
3956   bool match = false;
3957 
3958   try
3959   {
3960     if (URIUtils::IsStack(tag.m_strFileNameAndPath) && CFileItem(CStackDirectory::GetFirstStackedFile(tag.m_strFileNameAndPath),false).IsDiscImage())
3961     {
3962       CStackDirectory dir;
3963       CFileItemList fileList;
3964       const CURL pathToUrl(tag.m_strFileNameAndPath);
3965       dir.GetDirectory(pathToUrl, fileList);
3966       tag.SetResumePoint(CBookmark());
3967       for (int i = fileList.Size() - 1; i >= 0; i--)
3968       {
3969         CBookmark bookmark;
3970         if (GetResumeBookMark(fileList[i]->GetPath(), bookmark))
3971         {
3972           bookmark.partNumber = (i+1); /* store part number in here */
3973           tag.SetResumePoint(bookmark);
3974           match = true;
3975           break;
3976         }
3977       }
3978     }
3979     else
3980     {
3981       std::string strSQL=PrepareSQL("select timeInSeconds, totalTimeInSeconds from bookmark where idFile=%i and type=%i order by timeInSeconds", tag.m_iFileId, CBookmark::RESUME);
3982       m_pDS2->query( strSQL );
3983       if (!m_pDS2->eof())
3984       {
3985         tag.SetResumePoint(m_pDS2->fv(0).get_asDouble(), m_pDS2->fv(1).get_asDouble(), "");
3986         match = true;
3987       }
3988       m_pDS2->close();
3989     }
3990   }
3991   catch (...)
3992   {
3993     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, tag.m_strFileNameAndPath.c_str());
3994   }
3995 
3996   return match;
3997 }
3998 
GetDetailsForMovie(std::unique_ptr<Dataset> & pDS,int getDetails)3999 CVideoInfoTag CVideoDatabase::GetDetailsForMovie(std::unique_ptr<Dataset> &pDS, int getDetails /* = VideoDbDetailsNone */)
4000 {
4001   return GetDetailsForMovie(pDS->get_sql_record(), getDetails);
4002 }
4003 
GetDetailsForMovie(const dbiplus::sql_record * const record,int getDetails)4004 CVideoInfoTag CVideoDatabase::GetDetailsForMovie(const dbiplus::sql_record* const record, int getDetails /* = VideoDbDetailsNone */)
4005 {
4006   CVideoInfoTag details;
4007 
4008   if (record == NULL)
4009     return details;
4010 
4011   DWORD time = XbmcThreads::SystemClockMillis();
4012   int idMovie = record->at(0).get_asInt();
4013 
4014   GetDetailsFromDB(record, VIDEODB_ID_MIN, VIDEODB_ID_MAX, DbMovieOffsets, details);
4015 
4016   details.m_iDbId = idMovie;
4017   details.m_type = MediaTypeMovie;
4018 
4019   details.m_set.id = record->at(VIDEODB_DETAILS_MOVIE_SET_ID).get_asInt();
4020   details.m_set.title = record->at(VIDEODB_DETAILS_MOVIE_SET_NAME).get_asString();
4021   details.m_set.overview = record->at(VIDEODB_DETAILS_MOVIE_SET_OVERVIEW).get_asString();
4022   details.m_iFileId = record->at(VIDEODB_DETAILS_FILEID).get_asInt();
4023   details.m_strPath = record->at(VIDEODB_DETAILS_MOVIE_PATH).get_asString();
4024   std::string strFileName = record->at(VIDEODB_DETAILS_MOVIE_FILE).get_asString();
4025   ConstructPath(details.m_strFileNameAndPath,details.m_strPath,strFileName);
4026   details.SetPlayCount(record->at(VIDEODB_DETAILS_MOVIE_PLAYCOUNT).get_asInt());
4027   details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MOVIE_LASTPLAYED).get_asString());
4028   details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MOVIE_DATEADDED).get_asString());
4029   details.SetResumePoint(record->at(VIDEODB_DETAILS_MOVIE_RESUME_TIME).get_asInt(),
4030                          record->at(VIDEODB_DETAILS_MOVIE_TOTAL_TIME).get_asInt(),
4031                          record->at(VIDEODB_DETAILS_MOVIE_PLAYER_STATE).get_asString());
4032   details.m_iUserRating = record->at(VIDEODB_DETAILS_MOVIE_USER_RATING).get_asInt();
4033   details.SetRating(record->at(VIDEODB_DETAILS_MOVIE_RATING).get_asFloat(),
4034                     record->at(VIDEODB_DETAILS_MOVIE_VOTES).get_asInt(),
4035                     record->at(VIDEODB_DETAILS_MOVIE_RATING_TYPE).get_asString(), true);
4036   details.SetUniqueID(record->at(VIDEODB_DETAILS_MOVIE_UNIQUEID_VALUE).get_asString(), record->at(VIDEODB_DETAILS_MOVIE_UNIQUEID_TYPE).get_asString() ,true);
4037   std::string premieredString = record->at(VIDEODB_DETAILS_MOVIE_PREMIERED).get_asString();
4038   if (premieredString.size() == 4)
4039     details.SetYear(record->at(VIDEODB_DETAILS_MOVIE_PREMIERED).get_asInt());
4040   else
4041     details.SetPremieredFromDBDate(premieredString);
4042   movieTime += XbmcThreads::SystemClockMillis() - time; time = XbmcThreads::SystemClockMillis();
4043 
4044   if (getDetails)
4045   {
4046       GetCast(details.m_iDbId, MediaTypeMovie, details.m_cast);
4047       castTime += XbmcThreads::SystemClockMillis() - time; time = XbmcThreads::SystemClockMillis();
4048 
4049     if (getDetails & VideoDbDetailsTag)
4050       GetTags(details.m_iDbId, MediaTypeMovie, details.m_tags);
4051 
4052     if (getDetails & VideoDbDetailsRating)
4053       GetRatings(details.m_iDbId, MediaTypeMovie, details.m_ratings);
4054 
4055     if (getDetails & VideoDbDetailsUniqueID)
4056      GetUniqueIDs(details.m_iDbId, MediaTypeMovie, details);
4057 
4058     if (getDetails & VideoDbDetailsShowLink)
4059     {
4060       // create tvshowlink string
4061       std::vector<int> links;
4062       GetLinksToTvShow(idMovie, links);
4063       for (unsigned int i = 0; i < links.size(); ++i)
4064       {
4065         std::string strSQL = PrepareSQL("select c%02d from tvshow where idShow=%i",
4066           VIDEODB_ID_TV_TITLE, links[i]);
4067         m_pDS2->query(strSQL);
4068         if (!m_pDS2->eof())
4069           details.m_showLink.emplace_back(m_pDS2->fv(0).get_asString());
4070       }
4071       m_pDS2->close();
4072     }
4073 
4074     if (getDetails & VideoDbDetailsStream)
4075       GetStreamDetails(details);
4076 
4077     details.m_parsedDetails = getDetails;
4078   }
4079   return details;
4080 }
4081 
GetDetailsForTvShow(std::unique_ptr<Dataset> & pDS,int getDetails,CFileItem * item)4082 CVideoInfoTag CVideoDatabase::GetDetailsForTvShow(std::unique_ptr<Dataset> &pDS, int getDetails /* = VideoDbDetailsNone */, CFileItem* item /* = NULL */)
4083 {
4084   return GetDetailsForTvShow(pDS->get_sql_record(), getDetails, item);
4085 }
4086 
GetDetailsForTvShow(const dbiplus::sql_record * const record,int getDetails,CFileItem * item)4087 CVideoInfoTag CVideoDatabase::GetDetailsForTvShow(const dbiplus::sql_record* const record, int getDetails /* = VideoDbDetailsNone */, CFileItem* item /* = NULL */)
4088 {
4089   CVideoInfoTag details;
4090 
4091   if (record == NULL)
4092     return details;
4093 
4094   DWORD time = XbmcThreads::SystemClockMillis();
4095   int idTvShow = record->at(0).get_asInt();
4096 
4097   GetDetailsFromDB(record, VIDEODB_ID_TV_MIN, VIDEODB_ID_TV_MAX, DbTvShowOffsets, details, 1);
4098   details.m_bHasPremiered = details.m_premiered.IsValid();
4099   details.m_iDbId = idTvShow;
4100   details.m_type = MediaTypeTvShow;
4101   details.m_strPath = record->at(VIDEODB_DETAILS_TVSHOW_PATH).get_asString();
4102   details.m_basePath = details.m_strPath;
4103   details.m_parentPathID = record->at(VIDEODB_DETAILS_TVSHOW_PARENTPATHID).get_asInt();
4104   details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_TVSHOW_DATEADDED).get_asString());
4105   details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_TVSHOW_LASTPLAYED).get_asString());
4106   details.m_iSeason = record->at(VIDEODB_DETAILS_TVSHOW_NUM_SEASONS).get_asInt();
4107   details.m_iEpisode = record->at(VIDEODB_DETAILS_TVSHOW_NUM_EPISODES).get_asInt();
4108   details.SetPlayCount(record->at(VIDEODB_DETAILS_TVSHOW_NUM_WATCHED).get_asInt());
4109   details.m_strShowTitle = details.m_strTitle;
4110   details.m_iUserRating = record->at(VIDEODB_DETAILS_TVSHOW_USER_RATING).get_asInt();
4111   details.SetRating(record->at(VIDEODB_DETAILS_TVSHOW_RATING).get_asFloat(),
4112                     record->at(VIDEODB_DETAILS_TVSHOW_VOTES).get_asInt(),
4113                     record->at(VIDEODB_DETAILS_TVSHOW_RATING_TYPE).get_asString(), true);
4114   details.SetUniqueID(record->at(VIDEODB_DETAILS_TVSHOW_UNIQUEID_VALUE).get_asString(), record->at(VIDEODB_DETAILS_TVSHOW_UNIQUEID_TYPE).get_asString(), true);
4115   details.SetDuration(record->at(VIDEODB_DETAILS_TVSHOW_DURATION).get_asInt());
4116 
4117   movieTime += XbmcThreads::SystemClockMillis() - time; time = XbmcThreads::SystemClockMillis();
4118 
4119   if (getDetails)
4120   {
4121     if (getDetails & VideoDbDetailsCast)
4122     {
4123       GetCast(details.m_iDbId, "tvshow", details.m_cast);
4124       castTime += XbmcThreads::SystemClockMillis() - time; time = XbmcThreads::SystemClockMillis();
4125     }
4126 
4127     if (getDetails & VideoDbDetailsTag)
4128       GetTags(details.m_iDbId, MediaTypeTvShow, details.m_tags);
4129 
4130     if (getDetails & VideoDbDetailsRating)
4131       GetRatings(details.m_iDbId, MediaTypeTvShow, details.m_ratings);
4132 
4133     if (getDetails & VideoDbDetailsUniqueID)
4134       GetUniqueIDs(details.m_iDbId, MediaTypeTvShow, details);
4135 
4136     details.m_parsedDetails = getDetails;
4137   }
4138 
4139   if (item != NULL)
4140   {
4141     item->m_dateTime = details.GetPremiered();
4142     item->SetProperty("totalseasons", details.m_iSeason);
4143     item->SetProperty("totalepisodes", details.m_iEpisode);
4144     item->SetProperty("numepisodes", details.m_iEpisode); // will be changed later to reflect watchmode setting
4145     item->SetProperty("watchedepisodes", details.GetPlayCount());
4146     item->SetProperty("unwatchedepisodes", details.m_iEpisode - details.GetPlayCount());
4147   }
4148   details.SetPlayCount((details.m_iEpisode <= details.GetPlayCount()) ? 1 : 0);
4149 
4150   return details;
4151 }
4152 
GetBasicDetailsForEpisode(std::unique_ptr<Dataset> & pDS)4153 CVideoInfoTag CVideoDatabase::GetBasicDetailsForEpisode(std::unique_ptr<Dataset> &pDS)
4154 {
4155   return GetBasicDetailsForEpisode(pDS->get_sql_record());
4156 }
4157 
GetBasicDetailsForEpisode(const dbiplus::sql_record * const record)4158 CVideoInfoTag CVideoDatabase::GetBasicDetailsForEpisode(const dbiplus::sql_record* const record)
4159 {
4160   CVideoInfoTag details;
4161 
4162   if (record == nullptr)
4163     return details;
4164 
4165   unsigned int time = XbmcThreads::SystemClockMillis();
4166   int idEpisode = record->at(0).get_asInt();
4167 
4168   GetDetailsFromDB(record, VIDEODB_ID_EPISODE_MIN, VIDEODB_ID_EPISODE_MAX, DbEpisodeOffsets, details);
4169   details.m_iDbId = idEpisode;
4170   details.m_type = MediaTypeEpisode;
4171   details.m_iFileId = record->at(VIDEODB_DETAILS_FILEID).get_asInt();
4172   details.m_iIdShow = record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_ID).get_asInt();
4173   details.m_iIdSeason = record->at(VIDEODB_DETAILS_EPISODE_SEASON_ID).get_asInt();
4174   details.m_iUserRating = record->at(VIDEODB_DETAILS_EPISODE_USER_RATING).get_asInt();
4175 
4176   movieTime += XbmcThreads::SystemClockMillis() - time;
4177   return details;
4178 }
4179 
GetDetailsForEpisode(std::unique_ptr<Dataset> & pDS,int getDetails)4180 CVideoInfoTag CVideoDatabase::GetDetailsForEpisode(std::unique_ptr<Dataset> &pDS, int getDetails /* = VideoDbDetailsNone */)
4181 {
4182   return GetDetailsForEpisode(pDS->get_sql_record(), getDetails);
4183 }
4184 
GetDetailsForEpisode(const dbiplus::sql_record * const record,int getDetails)4185 CVideoInfoTag CVideoDatabase::GetDetailsForEpisode(const dbiplus::sql_record* const record, int getDetails /* = VideoDbDetailsNone */)
4186 {
4187   CVideoInfoTag details;
4188 
4189   if (record == nullptr)
4190     return details;
4191 
4192   details = GetBasicDetailsForEpisode(record);
4193 
4194   unsigned int time = XbmcThreads::SystemClockMillis();
4195 
4196   details.m_strPath = record->at(VIDEODB_DETAILS_EPISODE_PATH).get_asString();
4197   std::string strFileName = record->at(VIDEODB_DETAILS_EPISODE_FILE).get_asString();
4198   ConstructPath(details.m_strFileNameAndPath,details.m_strPath,strFileName);
4199   details.SetPlayCount(record->at(VIDEODB_DETAILS_EPISODE_PLAYCOUNT).get_asInt());
4200   details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_EPISODE_LASTPLAYED).get_asString());
4201   details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_EPISODE_DATEADDED).get_asString());
4202   details.m_strMPAARating = record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_MPAA).get_asString();
4203   details.m_strShowTitle = record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_NAME).get_asString();
4204   details.m_genre = StringUtils::Split(record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_GENRE).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
4205   details.m_studio = StringUtils::Split(record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_STUDIO).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
4206   details.SetPremieredFromDBDate(record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_AIRED).get_asString());
4207 
4208   details.SetResumePoint(record->at(VIDEODB_DETAILS_EPISODE_RESUME_TIME).get_asInt(),
4209                          record->at(VIDEODB_DETAILS_EPISODE_TOTAL_TIME).get_asInt(),
4210                          record->at(VIDEODB_DETAILS_EPISODE_PLAYER_STATE).get_asString());
4211 
4212   details.SetRating(record->at(VIDEODB_DETAILS_EPISODE_RATING).get_asFloat(),
4213                     record->at(VIDEODB_DETAILS_EPISODE_VOTES).get_asInt(),
4214                     record->at(VIDEODB_DETAILS_EPISODE_RATING_TYPE).get_asString(), true);
4215   details.SetUniqueID(record->at(VIDEODB_DETAILS_EPISODE_UNIQUEID_VALUE).get_asString(), record->at(VIDEODB_DETAILS_EPISODE_UNIQUEID_TYPE).get_asString(), true);
4216   movieTime += XbmcThreads::SystemClockMillis() - time; time = XbmcThreads::SystemClockMillis();
4217 
4218   if (getDetails)
4219   {
4220     if (getDetails & VideoDbDetailsCast)
4221     {
4222       GetCast(details.m_iDbId, MediaTypeEpisode, details.m_cast);
4223       GetCast(details.m_iIdShow, MediaTypeTvShow, details.m_cast);
4224       castTime += XbmcThreads::SystemClockMillis() - time; time = XbmcThreads::SystemClockMillis();
4225     }
4226 
4227     if (getDetails & VideoDbDetailsRating)
4228       GetRatings(details.m_iDbId, MediaTypeEpisode, details.m_ratings);
4229 
4230     if (getDetails & VideoDbDetailsUniqueID)
4231       GetUniqueIDs(details.m_iDbId, MediaTypeEpisode, details);
4232 
4233     if (getDetails &  VideoDbDetailsBookmark)
4234       GetBookMarkForEpisode(details, details.m_EpBookmark);
4235 
4236     if (getDetails & VideoDbDetailsStream)
4237       GetStreamDetails(details);
4238 
4239     details.m_parsedDetails = getDetails;
4240   }
4241   return details;
4242 }
4243 
GetDetailsForMusicVideo(std::unique_ptr<Dataset> & pDS,int getDetails)4244 CVideoInfoTag CVideoDatabase::GetDetailsForMusicVideo(std::unique_ptr<Dataset> &pDS, int getDetails /* = VideoDbDetailsNone */)
4245 {
4246   return GetDetailsForMusicVideo(pDS->get_sql_record(), getDetails);
4247 }
4248 
GetDetailsForMusicVideo(const dbiplus::sql_record * const record,int getDetails)4249 CVideoInfoTag CVideoDatabase::GetDetailsForMusicVideo(const dbiplus::sql_record* const record, int getDetails /* = VideoDbDetailsNone */)
4250 {
4251   CVideoInfoTag details;
4252   CArtist artist;
4253 
4254   if (record == nullptr)
4255     return details;
4256 
4257   unsigned int time = XbmcThreads::SystemClockMillis();
4258   int idMVideo = record->at(0).get_asInt();
4259 
4260   GetDetailsFromDB(record, VIDEODB_ID_MUSICVIDEO_MIN, VIDEODB_ID_MUSICVIDEO_MAX, DbMusicVideoOffsets, details);
4261   details.m_iDbId = idMVideo;
4262   details.m_type = MediaTypeMusicVideo;
4263 
4264   details.m_iFileId = record->at(VIDEODB_DETAILS_FILEID).get_asInt();
4265   details.m_strPath = record->at(VIDEODB_DETAILS_MUSICVIDEO_PATH).get_asString();
4266   std::string strFileName = record->at(VIDEODB_DETAILS_MUSICVIDEO_FILE).get_asString();
4267   ConstructPath(details.m_strFileNameAndPath,details.m_strPath,strFileName);
4268   details.SetPlayCount(record->at(VIDEODB_DETAILS_MUSICVIDEO_PLAYCOUNT).get_asInt());
4269   details.m_lastPlayed.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MUSICVIDEO_LASTPLAYED).get_asString());
4270   details.m_dateAdded.SetFromDBDateTime(record->at(VIDEODB_DETAILS_MUSICVIDEO_DATEADDED).get_asString());
4271   details.SetResumePoint(record->at(VIDEODB_DETAILS_MUSICVIDEO_RESUME_TIME).get_asInt(),
4272                          record->at(VIDEODB_DETAILS_MUSICVIDEO_TOTAL_TIME).get_asInt(),
4273                          record->at(VIDEODB_DETAILS_MUSICVIDEO_PLAYER_STATE).get_asString());
4274   details.m_iUserRating = record->at(VIDEODB_DETAILS_MUSICVIDEO_USER_RATING).get_asInt();
4275   std::string premieredString = record->at(VIDEODB_DETAILS_MUSICVIDEO_PREMIERED).get_asString();
4276   if (premieredString.size() == 4)
4277     details.SetYear(record->at(VIDEODB_DETAILS_MUSICVIDEO_PREMIERED).get_asInt());
4278   else
4279     details.SetPremieredFromDBDate(premieredString);
4280 
4281   movieTime += XbmcThreads::SystemClockMillis() - time; time = XbmcThreads::SystemClockMillis();
4282 
4283   if (getDetails)
4284   {
4285     if (getDetails & VideoDbDetailsTag)
4286       GetTags(details.m_iDbId, MediaTypeMusicVideo, details.m_tags);
4287 
4288     if (getDetails & VideoDbDetailsStream)
4289       GetStreamDetails(details);
4290 
4291     if (getDetails & VideoDbDetailsAll)
4292     {
4293       GetCast(details.m_iDbId, "musicvideo", details.m_cast);
4294       castTime += XbmcThreads::SystemClockMillis() - time; time = XbmcThreads::SystemClockMillis();
4295     }
4296 
4297     details.m_parsedDetails = getDetails;
4298   }
4299   return details;
4300 }
4301 
GetCast(int media_id,const std::string & media_type,std::vector<SActorInfo> & cast)4302 void CVideoDatabase::GetCast(int media_id, const std::string &media_type, std::vector<SActorInfo> &cast)
4303 {
4304   try
4305   {
4306     if (!m_pDB)
4307       return;
4308     if (!m_pDS2)
4309       return;
4310 
4311     std::string sql = PrepareSQL("SELECT actor.name,"
4312                                  "  actor_link.role,"
4313                                  "  actor_link.cast_order,"
4314                                  "  actor.art_urls,"
4315                                  "  art.url "
4316                                  "FROM actor_link"
4317                                  "  JOIN actor ON"
4318                                  "    actor_link.actor_id=actor.actor_id"
4319                                  "  LEFT JOIN art ON"
4320                                  "    art.media_id=actor.actor_id AND art.media_type='actor' AND art.type='thumb' "
4321                                  "WHERE actor_link.media_id=%i AND actor_link.media_type='%s'"
4322                                  "ORDER BY actor_link.cast_order", media_id, media_type.c_str());
4323     m_pDS2->query(sql);
4324     while (!m_pDS2->eof())
4325     {
4326       SActorInfo info;
4327       info.strName = m_pDS2->fv(0).get_asString();
4328       info.strRole = m_pDS2->fv(1).get_asString();
4329 
4330       // ignore identical actors (since cast might already be prefilled)
4331       if (std::none_of(cast.begin(), cast.end(), [info](const SActorInfo& actor) {
4332             return actor.strName == info.strName && actor.strRole == info.strRole;
4333           }))
4334       {
4335         info.order = m_pDS2->fv(2).get_asInt();
4336         info.thumbUrl.ParseFromData(m_pDS2->fv(3).get_asString());
4337         info.thumb = m_pDS2->fv(4).get_asString();
4338         cast.emplace_back(std::move(info));
4339       }
4340 
4341       m_pDS2->next();
4342     }
4343     m_pDS2->close();
4344   }
4345   catch (...)
4346   {
4347     CLog::Log(LOGERROR, "%s(%i,%s) failed", __FUNCTION__, media_id, media_type.c_str());
4348   }
4349 }
4350 
GetTags(int media_id,const std::string & media_type,std::vector<std::string> & tags)4351 void CVideoDatabase::GetTags(int media_id, const std::string &media_type, std::vector<std::string> &tags)
4352 {
4353   try
4354   {
4355     if (!m_pDB)
4356       return;
4357     if (!m_pDS2)
4358       return;
4359 
4360     std::string sql = PrepareSQL("SELECT tag.name FROM tag INNER JOIN tag_link ON tag_link.tag_id = tag.tag_id WHERE tag_link.media_id = %i AND tag_link.media_type = '%s' ORDER BY tag.tag_id", media_id, media_type.c_str());
4361     m_pDS2->query(sql);
4362     while (!m_pDS2->eof())
4363     {
4364       tags.emplace_back(m_pDS2->fv(0).get_asString());
4365       m_pDS2->next();
4366     }
4367     m_pDS2->close();
4368   }
4369   catch (...)
4370   {
4371     CLog::Log(LOGERROR, "%s(%i,%s) failed", __FUNCTION__, media_id, media_type.c_str());
4372   }
4373 }
4374 
GetRatings(int media_id,const std::string & media_type,RatingMap & ratings)4375 void CVideoDatabase::GetRatings(int media_id, const std::string &media_type, RatingMap &ratings)
4376 {
4377   try
4378   {
4379     if (!m_pDB)
4380       return;
4381     if (!m_pDS2)
4382       return;
4383 
4384     std::string sql = PrepareSQL("SELECT rating.rating_type, rating.rating, rating.votes FROM rating WHERE rating.media_id = %i AND rating.media_type = '%s'", media_id, media_type.c_str());
4385     m_pDS2->query(sql);
4386     while (!m_pDS2->eof())
4387     {
4388       ratings[m_pDS2->fv(0).get_asString()] = CRating(m_pDS2->fv(1).get_asFloat(), m_pDS2->fv(2).get_asInt());
4389       m_pDS2->next();
4390     }
4391     m_pDS2->close();
4392   }
4393   catch (...)
4394   {
4395     CLog::Log(LOGERROR, "%s(%i,%s) failed", __FUNCTION__, media_id, media_type.c_str());
4396   }
4397 }
4398 
GetUniqueIDs(int media_id,const std::string & media_type,CVideoInfoTag & details)4399 void CVideoDatabase::GetUniqueIDs(int media_id, const std::string &media_type, CVideoInfoTag& details)
4400 {
4401   try
4402   {
4403     if (!m_pDB)
4404       return;
4405     if (!m_pDS2)
4406       return;
4407 
4408     std::string sql = PrepareSQL("SELECT type, value FROM uniqueid WHERE media_id = %i AND media_type = '%s'", media_id, media_type.c_str());
4409     m_pDS2->query(sql);
4410     while (!m_pDS2->eof())
4411     {
4412       details.SetUniqueID(m_pDS2->fv(1).get_asString(), m_pDS2->fv(0).get_asString());
4413       m_pDS2->next();
4414     }
4415     m_pDS2->close();
4416   }
4417   catch (...)
4418   {
4419     CLog::Log(LOGERROR, "%s(%i,%s) failed", __FUNCTION__, media_id, media_type.c_str());
4420   }
4421 }
4422 
GetVideoSettings(const CFileItem & item,CVideoSettings & settings)4423 bool CVideoDatabase::GetVideoSettings(const CFileItem &item, CVideoSettings &settings)
4424 {
4425   return GetVideoSettings(GetFileId(item), settings);
4426 }
4427 
4428 /// \brief GetVideoSettings() obtains any saved video settings for the current file.
4429 /// \retval Returns true if the settings exist, false otherwise.
GetVideoSettings(const std::string & filePath,CVideoSettings & settings)4430 bool CVideoDatabase::GetVideoSettings(const std::string &filePath, CVideoSettings &settings)
4431 {
4432   return GetVideoSettings(GetFileId(filePath), settings);
4433 }
4434 
GetVideoSettings(int idFile,CVideoSettings & settings)4435 bool CVideoDatabase::GetVideoSettings(int idFile, CVideoSettings &settings)
4436 {
4437   try
4438   {
4439     if (idFile < 0) return false;
4440     if (nullptr == m_pDB)
4441       return false;
4442     if (nullptr == m_pDS)
4443       return false;
4444 
4445     std::string strSQL=PrepareSQL("select * from settings where settings.idFile = '%i'", idFile);
4446     m_pDS->query( strSQL );
4447 
4448     if (m_pDS->num_rows() > 0)
4449     { // get the video settings info
4450       settings.m_AudioDelay = m_pDS->fv("AudioDelay").get_asFloat();
4451       settings.m_AudioStream = m_pDS->fv("AudioStream").get_asInt();
4452       settings.m_Brightness = m_pDS->fv("Brightness").get_asFloat();
4453       settings.m_Contrast = m_pDS->fv("Contrast").get_asFloat();
4454       settings.m_CustomPixelRatio = m_pDS->fv("PixelRatio").get_asFloat();
4455       settings.m_CustomNonLinStretch = m_pDS->fv("NonLinStretch").get_asBool();
4456       settings.m_NoiseReduction = m_pDS->fv("NoiseReduction").get_asFloat();
4457       settings.m_PostProcess = m_pDS->fv("PostProcess").get_asBool();
4458       settings.m_Sharpness = m_pDS->fv("Sharpness").get_asFloat();
4459       settings.m_CustomZoomAmount = m_pDS->fv("ZoomAmount").get_asFloat();
4460       settings.m_CustomVerticalShift = m_pDS->fv("VerticalShift").get_asFloat();
4461       settings.m_Gamma = m_pDS->fv("Gamma").get_asFloat();
4462       settings.m_SubtitleDelay = m_pDS->fv("SubtitleDelay").get_asFloat();
4463       settings.m_SubtitleOn = m_pDS->fv("SubtitlesOn").get_asBool();
4464       settings.m_SubtitleStream = m_pDS->fv("SubtitleStream").get_asInt();
4465       settings.m_ViewMode = m_pDS->fv("ViewMode").get_asInt();
4466       settings.m_ResumeTime = m_pDS->fv("ResumeTime").get_asInt();
4467       settings.m_InterlaceMethod = (EINTERLACEMETHOD)m_pDS->fv("Deinterlace").get_asInt();
4468       settings.m_VolumeAmplification = m_pDS->fv("VolumeAmplification").get_asFloat();
4469       settings.m_ScalingMethod = (ESCALINGMETHOD)m_pDS->fv("ScalingMethod").get_asInt();
4470       settings.m_StereoMode = m_pDS->fv("StereoMode").get_asInt();
4471       settings.m_StereoInvert = m_pDS->fv("StereoInvert").get_asBool();
4472       settings.m_VideoStream = m_pDS->fv("VideoStream").get_asInt();
4473       settings.m_ToneMapMethod = m_pDS->fv("TonemapMethod").get_asInt();
4474       settings.m_ToneMapParam = m_pDS->fv("TonemapParam").get_asFloat();
4475       settings.m_Orientation = m_pDS->fv("Orientation").get_asInt();
4476       settings.m_CenterMixLevel = m_pDS->fv("CenterMixLevel").get_asInt();
4477       m_pDS->close();
4478       return true;
4479     }
4480     m_pDS->close();
4481   }
4482   catch (...)
4483   {
4484     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
4485   }
4486   return false;
4487 }
4488 
SetVideoSettings(const CFileItem & item,const CVideoSettings & settings)4489 void CVideoDatabase::SetVideoSettings(const CFileItem &item, const CVideoSettings &settings)
4490 {
4491   int idFile = AddFile(item);
4492   SetVideoSettings(idFile, settings);
4493 }
4494 
4495 /// \brief Sets the settings for a particular video file
SetVideoSettings(int idFile,const CVideoSettings & setting)4496 void CVideoDatabase::SetVideoSettings(int idFile, const CVideoSettings &setting)
4497 {
4498   try
4499   {
4500     if (nullptr == m_pDB)
4501       return;
4502     if (nullptr == m_pDS)
4503       return;
4504     if (idFile < 0)
4505       return;
4506     std::string strSQL = PrepareSQL("select * from settings where idFile=%i", idFile);
4507     m_pDS->query( strSQL );
4508     if (m_pDS->num_rows() > 0)
4509     {
4510       m_pDS->close();
4511       // update the item
4512       strSQL=PrepareSQL("update settings set Deinterlace=%i,ViewMode=%i,ZoomAmount=%f,PixelRatio=%f,VerticalShift=%f,"
4513                        "AudioStream=%i,SubtitleStream=%i,SubtitleDelay=%f,SubtitlesOn=%i,Brightness=%f,Contrast=%f,Gamma=%f,"
4514                        "VolumeAmplification=%f,AudioDelay=%f,Sharpness=%f,NoiseReduction=%f,NonLinStretch=%i,PostProcess=%i,ScalingMethod=%i,",
4515                        setting.m_InterlaceMethod, setting.m_ViewMode, setting.m_CustomZoomAmount, setting.m_CustomPixelRatio, setting.m_CustomVerticalShift,
4516                        setting.m_AudioStream, setting.m_SubtitleStream, setting.m_SubtitleDelay, setting.m_SubtitleOn,
4517                        setting.m_Brightness, setting.m_Contrast, setting.m_Gamma, setting.m_VolumeAmplification, setting.m_AudioDelay,
4518                        setting.m_Sharpness,setting.m_NoiseReduction,setting.m_CustomNonLinStretch,setting.m_PostProcess,setting.m_ScalingMethod);
4519       std::string strSQL2;
4520 
4521       strSQL2=PrepareSQL("ResumeTime=%i,StereoMode=%i,StereoInvert=%i,VideoStream=%i,TonemapMethod=%i,TonemapParam=%f where idFile=%i\n",
4522                          setting.m_ResumeTime, setting.m_StereoMode, setting.m_StereoInvert, setting.m_VideoStream,
4523                          setting.m_ToneMapMethod, setting.m_ToneMapParam, idFile);
4524       strSQL += strSQL2;
4525       m_pDS->exec(strSQL);
4526       return ;
4527     }
4528     else
4529     { // add the items
4530       m_pDS->close();
4531       strSQL= "INSERT INTO settings (idFile,Deinterlace,ViewMode,ZoomAmount,PixelRatio, VerticalShift, "
4532                 "AudioStream,SubtitleStream,SubtitleDelay,SubtitlesOn,Brightness,"
4533                 "Contrast,Gamma,VolumeAmplification,AudioDelay,"
4534                 "ResumeTime,"
4535                 "Sharpness,NoiseReduction,NonLinStretch,PostProcess,ScalingMethod,StereoMode,StereoInvert,VideoStream,TonemapMethod,TonemapParam,Orientation,CenterMixLevel) "
4536               "VALUES ";
4537       strSQL += PrepareSQL("(%i,%i,%i,%f,%f,%f,%i,%i,%f,%i,%f,%f,%f,%f,%f,%i,%f,%f,%i,%i,%i,%i,%i,%i,%i,%f,%i,%i)",
4538                            idFile, setting.m_InterlaceMethod, setting.m_ViewMode, setting.m_CustomZoomAmount, setting.m_CustomPixelRatio, setting.m_CustomVerticalShift,
4539                            setting.m_AudioStream, setting.m_SubtitleStream, setting.m_SubtitleDelay, setting.m_SubtitleOn, setting.m_Brightness,
4540                            setting.m_Contrast, setting.m_Gamma, setting.m_VolumeAmplification, setting.m_AudioDelay,
4541                            setting.m_ResumeTime,
4542                            setting.m_Sharpness, setting.m_NoiseReduction, setting.m_CustomNonLinStretch, setting.m_PostProcess, setting.m_ScalingMethod,
4543                            setting.m_StereoMode, setting.m_StereoInvert, setting.m_VideoStream, setting.m_ToneMapMethod, setting.m_ToneMapParam, setting.m_Orientation,setting.m_CenterMixLevel);
4544       m_pDS->exec(strSQL);
4545     }
4546   }
4547   catch (...)
4548   {
4549     CLog::Log(LOGERROR, "%s (%d) failed", __FUNCTION__, idFile);
4550   }
4551 }
4552 
SetArtForItem(int mediaId,const MediaType & mediaType,const std::map<std::string,std::string> & art)4553 void CVideoDatabase::SetArtForItem(int mediaId, const MediaType &mediaType, const std::map<std::string, std::string> &art)
4554 {
4555   for (const auto &i : art)
4556     SetArtForItem(mediaId, mediaType, i.first, i.second);
4557 }
4558 
SetArtForItem(int mediaId,const MediaType & mediaType,const std::string & artType,const std::string & url)4559 void CVideoDatabase::SetArtForItem(int mediaId, const MediaType &mediaType, const std::string &artType, const std::string &url)
4560 {
4561   try
4562   {
4563     if (nullptr == m_pDB)
4564       return;
4565     if (nullptr == m_pDS)
4566       return;
4567 
4568     // don't set <foo>.<bar> art types - these are derivative types from parent items
4569     if (artType.find('.') != std::string::npos)
4570       return;
4571 
4572     std::string sql = PrepareSQL("SELECT art_id,url FROM art WHERE media_id=%i AND media_type='%s' AND type='%s'", mediaId, mediaType.c_str(), artType.c_str());
4573     m_pDS->query(sql);
4574     if (!m_pDS->eof())
4575     { // update
4576       int artId = m_pDS->fv(0).get_asInt();
4577       std::string oldUrl = m_pDS->fv(1).get_asString();
4578       m_pDS->close();
4579       if (oldUrl != url)
4580       {
4581         sql = PrepareSQL("UPDATE art SET url='%s' where art_id=%d", url.c_str(), artId);
4582         m_pDS->exec(sql);
4583       }
4584     }
4585     else
4586     { // insert
4587       m_pDS->close();
4588       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());
4589       m_pDS->exec(sql);
4590     }
4591   }
4592   catch (...)
4593   {
4594     CLog::Log(LOGERROR, "%s(%d, '%s', '%s', '%s') failed", __FUNCTION__, mediaId, mediaType.c_str(), artType.c_str(), url.c_str());
4595   }
4596 }
4597 
GetArtForItem(int mediaId,const MediaType & mediaType,std::map<std::string,std::string> & art)4598 bool CVideoDatabase::GetArtForItem(int mediaId, const MediaType &mediaType, std::map<std::string, std::string> &art)
4599 {
4600   try
4601   {
4602     if (nullptr == m_pDB)
4603       return false;
4604     if (nullptr == m_pDS2)
4605       return false; // using dataset 2 as we're likely called in loops on dataset 1
4606 
4607     std::string sql = PrepareSQL("SELECT type,url FROM art WHERE media_id=%i AND media_type='%s'", mediaId, mediaType.c_str());
4608     m_pDS2->query(sql);
4609     while (!m_pDS2->eof())
4610     {
4611       art.insert(make_pair(m_pDS2->fv(0).get_asString(), m_pDS2->fv(1).get_asString()));
4612       m_pDS2->next();
4613     }
4614     m_pDS2->close();
4615     return !art.empty();
4616   }
4617   catch (...)
4618   {
4619     CLog::Log(LOGERROR, "%s(%d) failed", __FUNCTION__, mediaId);
4620   }
4621   return false;
4622 }
4623 
GetArtForItem(int mediaId,const MediaType & mediaType,const std::string & artType)4624 std::string CVideoDatabase::GetArtForItem(int mediaId, const MediaType &mediaType, const std::string &artType)
4625 {
4626   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());
4627   return GetSingleValue(query, m_pDS2);
4628 }
4629 
RemoveArtForItem(int mediaId,const MediaType & mediaType,const std::string & artType)4630 bool CVideoDatabase::RemoveArtForItem(int mediaId, const MediaType &mediaType, const std::string &artType)
4631 {
4632   return ExecuteQuery(PrepareSQL("DELETE FROM art WHERE media_id=%i AND media_type='%s' AND type='%s'", mediaId, mediaType.c_str(), artType.c_str()));
4633 }
4634 
RemoveArtForItem(int mediaId,const MediaType & mediaType,const std::set<std::string> & artTypes)4635 bool CVideoDatabase::RemoveArtForItem(int mediaId, const MediaType &mediaType, const std::set<std::string> &artTypes)
4636 {
4637   bool result = true;
4638   for (const auto &i : artTypes)
4639     result &= RemoveArtForItem(mediaId, mediaType, i);
4640 
4641   return result;
4642 }
4643 
HasArtForItem(int mediaId,const MediaType & mediaType)4644 bool CVideoDatabase::HasArtForItem(int mediaId, const MediaType &mediaType)
4645 {
4646   try
4647   {
4648     if (nullptr == m_pDB)
4649       return false;
4650     if (nullptr == m_pDS2)
4651       return false; // using dataset 2 as we're likely called in loops on dataset 1
4652 
4653     std::string sql = PrepareSQL("SELECT 1 FROM art WHERE media_id=%i AND media_type='%s' LIMIT 1", mediaId, mediaType.c_str());
4654     m_pDS2->query(sql);
4655     bool result = !m_pDS2->eof();
4656     m_pDS2->close();
4657     return result;
4658   }
4659   catch (...)
4660   {
4661     CLog::Log(LOGERROR, "%s(%d) failed", __FUNCTION__, mediaId);
4662   }
4663   return false;
4664 }
4665 
GetTvShowSeasons(int showId,std::map<int,int> & seasons)4666 bool CVideoDatabase::GetTvShowSeasons(int showId, std::map<int, int> &seasons)
4667 {
4668   try
4669   {
4670     if (nullptr == m_pDB)
4671       return false;
4672     if (nullptr == m_pDS2)
4673       return false; // using dataset 2 as we're likely called in loops on dataset 1
4674 
4675     // get all seasons for this show
4676     std::string sql = PrepareSQL("select idSeason,season from seasons where idShow=%i", showId);
4677     m_pDS2->query(sql);
4678 
4679     seasons.clear();
4680     while (!m_pDS2->eof())
4681     {
4682       seasons.insert(std::make_pair(m_pDS2->fv(1).get_asInt(), m_pDS2->fv(0).get_asInt()));
4683       m_pDS2->next();
4684     }
4685     m_pDS2->close();
4686     return true;
4687   }
4688   catch (...)
4689   {
4690     CLog::Log(LOGERROR, "%s(%d) failed", __FUNCTION__, showId);
4691   }
4692   return false;
4693 }
4694 
GetTvShowNamedSeasons(int showId,std::map<int,std::string> & seasons)4695 bool CVideoDatabase::GetTvShowNamedSeasons(int showId, std::map<int, std::string> &seasons)
4696 {
4697   try
4698   {
4699     if (nullptr == m_pDB)
4700       return false;
4701     if (nullptr == m_pDS2)
4702       return false; // using dataset 2 as we're likely called in loops on dataset 1
4703 
4704     // get all named seasons for this show
4705     std::string sql = PrepareSQL("select season, name from seasons where season > 0 and name is not null and name <> '' and idShow = %i", showId);
4706     m_pDS2->query(sql);
4707 
4708     seasons.clear();
4709     while (!m_pDS2->eof())
4710     {
4711       seasons.insert(std::make_pair(m_pDS2->fv(0).get_asInt(), m_pDS2->fv(1).get_asString()));
4712       m_pDS2->next();
4713     }
4714     m_pDS2->close();
4715     return true;
4716   }
4717   catch (...)
4718   {
4719     CLog::Log(LOGERROR, "%s(%d) failed", __FUNCTION__, showId);
4720   }
4721   return false;
4722 }
4723 
GetTvShowSeasonArt(int showId,std::map<int,std::map<std::string,std::string>> & seasonArt)4724 bool CVideoDatabase::GetTvShowSeasonArt(int showId, std::map<int, std::map<std::string, std::string> > &seasonArt)
4725 {
4726   try
4727   {
4728     if (nullptr == m_pDB)
4729       return false;
4730     if (nullptr == m_pDS2)
4731       return false; // using dataset 2 as we're likely called in loops on dataset 1
4732 
4733     std::map<int, int> seasons;
4734     GetTvShowSeasons(showId, seasons);
4735 
4736     for (const auto &i : seasons)
4737     {
4738       std::map<std::string, std::string> art;
4739       GetArtForItem(i.second, MediaTypeSeason, art);
4740       seasonArt.insert(std::make_pair(i.first,art));
4741     }
4742     return true;
4743   }
4744   catch (...)
4745   {
4746     CLog::Log(LOGERROR, "%s(%d) failed", __FUNCTION__, showId);
4747   }
4748   return false;
4749 }
4750 
GetArtTypes(const MediaType & mediaType,std::vector<std::string> & artTypes)4751 bool CVideoDatabase::GetArtTypes(const MediaType &mediaType, std::vector<std::string> &artTypes)
4752 {
4753   try
4754   {
4755     if (nullptr == m_pDB)
4756       return false;
4757     if (nullptr == m_pDS)
4758       return false;
4759 
4760     std::string sql = PrepareSQL("SELECT DISTINCT type FROM art WHERE media_type='%s'", mediaType.c_str());
4761     int numRows = RunQuery(sql);
4762     if (numRows <= 0)
4763       return numRows == 0;
4764 
4765     while (!m_pDS->eof())
4766     {
4767       artTypes.emplace_back(m_pDS->fv(0).get_asString());
4768       m_pDS->next();
4769     }
4770     m_pDS->close();
4771     return true;
4772   }
4773   catch (...)
4774   {
4775     CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, mediaType.c_str());
4776   }
4777   return false;
4778 }
4779 
4780 namespace
4781 {
GetBasicItemAvailableArtTypes(int mediaId,VIDEODB_CONTENT_TYPE dbType,CVideoDatabase & db)4782 std::vector<std::string> GetBasicItemAvailableArtTypes(int mediaId,
4783                                                        VIDEODB_CONTENT_TYPE dbType,
4784                                                        CVideoDatabase& db)
4785 {
4786   std::vector<std::string> result;
4787   CVideoInfoTag tag = db.GetDetailsByTypeAndId(dbType, mediaId);
4788 
4789   //! @todo artwork: fanart stored separately, doesn't need to be
4790   tag.m_fanart.Unpack();
4791   if (tag.m_fanart.GetNumFanarts() && std::find(result.cbegin(), result.cend(), "fanart") == result.cend())
4792     result.emplace_back("fanart");
4793 
4794   // all other images
4795   tag.m_strPictureURL.Parse();
4796   for (const auto& urlEntry : tag.m_strPictureURL.GetUrls())
4797   {
4798     std::string artType = urlEntry.m_aspect;
4799     if (artType.empty())
4800       artType = tag.m_type == MediaTypeEpisode ? "thumb" : "poster";
4801     if (urlEntry.m_type == CScraperUrl::UrlType::General && // exclude season artwork for TV shows
4802         !StringUtils::StartsWith(artType, "set.") && // exclude movie set artwork for movies
4803         std::find(result.cbegin(), result.cend(), artType) == result.cend())
4804     {
4805       result.push_back(artType);
4806     }
4807   }
4808   return result;
4809 }
4810 
GetSeasonAvailableArtTypes(int mediaId,CVideoDatabase & db)4811 std::vector<std::string> GetSeasonAvailableArtTypes(int mediaId, CVideoDatabase& db)
4812 {
4813   CVideoInfoTag tag;
4814   db.GetSeasonInfo(mediaId, tag);
4815 
4816   std::vector<std::string> result;
4817 
4818   CVideoInfoTag sourceShow;
4819   db.GetTvShowInfo("", sourceShow, tag.m_iIdShow);
4820   sourceShow.m_strPictureURL.Parse();
4821   for (const auto& urlEntry : sourceShow.m_strPictureURL.GetUrls())
4822   {
4823     std::string artType = urlEntry.m_aspect;
4824     if (artType.empty())
4825       artType = "poster";
4826     if (urlEntry.m_type == CScraperUrl::UrlType::Season && urlEntry.m_season == tag.m_iSeason &&
4827         std::find(result.cbegin(), result.cend(), artType) == result.cend())
4828     {
4829       result.push_back(artType);
4830     }
4831   }
4832   return result;
4833 }
4834 
GetMovieSetAvailableArtTypes(int mediaId,CVideoDatabase & db)4835 std::vector<std::string> GetMovieSetAvailableArtTypes(int mediaId, CVideoDatabase& db)
4836 {
4837   std::vector<std::string> result;
4838   CFileItemList items;
4839   std::string baseDir = StringUtils::Format("videodb://movies/sets/%d", mediaId);
4840   if (db.GetMoviesNav(baseDir, items))
4841   {
4842     for (const auto& item : items)
4843     {
4844       CVideoInfoTag* pTag = item->GetVideoInfoTag();
4845       pTag->m_strPictureURL.Parse();
4846 
4847       for (const auto& urlEntry : pTag->m_strPictureURL.GetUrls())
4848       {
4849         if (!StringUtils::StartsWith(urlEntry.m_aspect, "set."))
4850           continue;
4851 
4852         std::string artType = urlEntry.m_aspect.substr(4);
4853         if (std::find(result.cbegin(), result.cend(), artType) == result.cend())
4854           result.push_back(artType);
4855       }
4856     }
4857   }
4858   return result;
4859 }
4860 
GetBasicItemAvailableArt(int mediaId,VIDEODB_CONTENT_TYPE dbType,const std::string & artType,CVideoDatabase & db)4861 std::vector<CScraperUrl::SUrlEntry> GetBasicItemAvailableArt(
4862   int mediaId, VIDEODB_CONTENT_TYPE dbType, const std::string& artType, CVideoDatabase& db)
4863 {
4864   std::vector<CScraperUrl::SUrlEntry> result;
4865   CVideoInfoTag tag = db.GetDetailsByTypeAndId(dbType, mediaId);
4866 
4867   if (artType.empty() || artType == "fanart")
4868   {
4869     tag.m_fanart.Unpack();
4870     for (unsigned int i = 0; i < tag.m_fanart.GetNumFanarts(); i++)
4871     {
4872       CScraperUrl::SUrlEntry url(tag.m_fanart.GetImageURL(i));
4873       url.m_preview = tag.m_fanart.GetPreviewURL(i);
4874       url.m_aspect = "fanart";
4875       result.push_back(url);
4876     }
4877   }
4878   tag.m_strPictureURL.Parse();
4879   for (auto urlEntry : tag.m_strPictureURL.GetUrls())
4880   {
4881     if (urlEntry.m_aspect.empty())
4882       urlEntry.m_aspect = tag.m_type == MediaTypeEpisode ? "thumb" : "poster";
4883     if ((urlEntry.m_aspect == artType ||
4884          (artType.empty() && !StringUtils::StartsWith(urlEntry.m_aspect, "set."))) &&
4885         urlEntry.m_type == CScraperUrl::UrlType::General)
4886     {
4887       result.push_back(urlEntry);
4888     }
4889   }
4890 
4891   return result;
4892 }
4893 
GetSeasonAvailableArt(int mediaId,const std::string & artType,CVideoDatabase & db)4894 std::vector<CScraperUrl::SUrlEntry> GetSeasonAvailableArt(
4895   int mediaId, const std::string& artType, CVideoDatabase& db)
4896 {
4897   CVideoInfoTag tag;
4898   db.GetSeasonInfo(mediaId, tag);
4899 
4900   std::vector<CScraperUrl::SUrlEntry> result;
4901 
4902   CVideoInfoTag sourceShow;
4903   db.GetTvShowInfo("", sourceShow, tag.m_iIdShow);
4904   sourceShow.m_strPictureURL.Parse();
4905   for (auto urlEntry : sourceShow.m_strPictureURL.GetUrls())
4906   {
4907     if (urlEntry.m_aspect.empty())
4908       urlEntry.m_aspect = "poster";
4909     if ((artType.empty() || urlEntry.m_aspect == artType) &&
4910       urlEntry.m_type == CScraperUrl::UrlType::Season &&
4911       urlEntry.m_season == tag.m_iSeason)
4912     {
4913       result.push_back(urlEntry);
4914     }
4915   }
4916   return result;
4917 }
4918 
GetMovieSetAvailableArt(int mediaId,const std::string & artType,CVideoDatabase & db)4919 std::vector<CScraperUrl::SUrlEntry> GetMovieSetAvailableArt(
4920   int mediaId, const std::string& artType, CVideoDatabase& db)
4921 {
4922   std::vector<CScraperUrl::SUrlEntry> result;
4923   CFileItemList items;
4924   std::string baseDir = StringUtils::Format("videodb://movies/sets/%d", mediaId);
4925   std::unordered_set<std::string> addedURLs;
4926   if (db.GetMoviesNav(baseDir, items))
4927   {
4928     for (const auto& item : items)
4929     {
4930       CVideoInfoTag* pTag = item->GetVideoInfoTag();
4931       pTag->m_strPictureURL.Parse();
4932 
4933       for (auto urlEntry : pTag->m_strPictureURL.GetUrls())
4934       {
4935         bool isSetArt = !artType.empty() ? urlEntry.m_aspect == "set." + artType :
4936           StringUtils::StartsWith(urlEntry.m_aspect, "set.");
4937         if (isSetArt && addedURLs.insert(urlEntry.m_url).second)
4938         {
4939           urlEntry.m_aspect = urlEntry.m_aspect.substr(4);
4940           result.push_back(urlEntry);
4941         }
4942       }
4943     }
4944   }
4945   return result;
4946 }
4947 
CovertMediaTypeToContentType(const MediaType & mediaType)4948 VIDEODB_CONTENT_TYPE CovertMediaTypeToContentType(const MediaType& mediaType)
4949 {
4950   VIDEODB_CONTENT_TYPE dbType{ VIDEODB_CONTENT_UNKNOWN };
4951   if (mediaType == MediaTypeTvShow)
4952     dbType = VIDEODB_CONTENT_TVSHOWS;
4953   else if (mediaType == MediaTypeMovie)
4954     dbType = VIDEODB_CONTENT_MOVIES;
4955   else if (mediaType == MediaTypeEpisode)
4956     dbType = VIDEODB_CONTENT_EPISODES;
4957   else if (mediaType == MediaTypeMusicVideo)
4958     dbType = VIDEODB_CONTENT_MUSICVIDEOS;
4959 
4960   return dbType;
4961 }
4962 } // namespace
4963 
GetAvailableArtForItem(int mediaId,const MediaType & mediaType,const std::string & artType)4964 std::vector<CScraperUrl::SUrlEntry> CVideoDatabase::GetAvailableArtForItem(
4965   int mediaId, const MediaType& mediaType, const std::string& artType)
4966 {
4967   VIDEODB_CONTENT_TYPE dbType = CovertMediaTypeToContentType(mediaType);
4968 
4969   if (dbType != VIDEODB_CONTENT_UNKNOWN)
4970     return GetBasicItemAvailableArt(mediaId, dbType, artType, *this);
4971   if (mediaType == MediaTypeSeason)
4972     return GetSeasonAvailableArt(mediaId, artType, *this);
4973   if (mediaType == MediaTypeVideoCollection)
4974     return GetMovieSetAvailableArt(mediaId, artType, *this);
4975   return {};
4976 }
4977 
GetAvailableArtTypesForItem(int mediaId,const MediaType & mediaType)4978 std::vector<std::string> CVideoDatabase::GetAvailableArtTypesForItem(int mediaId,
4979   const MediaType& mediaType)
4980 {
4981   VIDEODB_CONTENT_TYPE dbType = CovertMediaTypeToContentType(mediaType);
4982 
4983   if (dbType != VIDEODB_CONTENT_UNKNOWN)
4984     return GetBasicItemAvailableArtTypes(mediaId, dbType, *this);
4985   if (mediaType == MediaTypeSeason)
4986     return GetSeasonAvailableArtTypes(mediaId, *this);
4987   if (mediaType == MediaTypeVideoCollection)
4988     return GetMovieSetAvailableArtTypes(mediaId, *this);
4989   return {};
4990 }
4991 
4992 /// \brief GetStackTimes() obtains any saved video times for the stacked file
4993 /// \retval Returns true if the stack times exist, false otherwise.
GetStackTimes(const std::string & filePath,std::vector<uint64_t> & times)4994 bool CVideoDatabase::GetStackTimes(const std::string &filePath, std::vector<uint64_t> &times)
4995 {
4996   try
4997   {
4998     // obtain the FileID (if it exists)
4999     int idFile = GetFileId(filePath);
5000     if (idFile < 0) return false;
5001     if (nullptr == m_pDB)
5002       return false;
5003     if (nullptr == m_pDS)
5004       return false;
5005     // ok, now obtain the settings for this file
5006     std::string strSQL=PrepareSQL("select times from stacktimes where idFile=%i\n", idFile);
5007     m_pDS->query( strSQL );
5008     if (m_pDS->num_rows() > 0)
5009     { // get the video settings info
5010       uint64_t timeTotal = 0;
5011       std::vector<std::string> timeString = StringUtils::Split(m_pDS->fv("times").get_asString(), ",");
5012       times.clear();
5013       for (const auto &i : timeString)
5014       {
5015         uint64_t partTime = static_cast<uint64_t>(atof(i.c_str()) * 1000.0f);
5016         times.push_back(partTime); // db stores in secs, convert to msecs
5017         timeTotal += partTime;
5018       }
5019       m_pDS->close();
5020       return (timeTotal > 0);
5021     }
5022     m_pDS->close();
5023   }
5024   catch (...)
5025   {
5026     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
5027   }
5028   return false;
5029 }
5030 
5031 /// \brief Sets the stack times for a particular video file
SetStackTimes(const std::string & filePath,const std::vector<uint64_t> & times)5032 void CVideoDatabase::SetStackTimes(const std::string& filePath, const std::vector<uint64_t> &times)
5033 {
5034   try
5035   {
5036     if (nullptr == m_pDB)
5037       return;
5038     if (nullptr == m_pDS)
5039       return;
5040     int idFile = AddFile(filePath);
5041     if (idFile < 0)
5042       return;
5043 
5044     // delete any existing items
5045     m_pDS->exec( PrepareSQL("delete from stacktimes where idFile=%i", idFile) );
5046 
5047     // add the items
5048     std::string timeString = StringUtils::Format("%.3f", times[0] / 1000.0f);
5049     for (unsigned int i = 1; i < times.size(); i++)
5050       timeString += StringUtils::Format(",%.3f", times[i] / 1000.0f);
5051 
5052     m_pDS->exec( PrepareSQL("insert into stacktimes (idFile,times) values (%i,'%s')\n", idFile, timeString.c_str()) );
5053   }
5054   catch (...)
5055   {
5056     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, filePath.c_str());
5057   }
5058 }
5059 
RemoveContentForPath(const std::string & strPath,CGUIDialogProgress * progress)5060 void CVideoDatabase::RemoveContentForPath(const std::string& strPath, CGUIDialogProgress *progress /* = NULL */)
5061 {
5062   if(URIUtils::IsMultiPath(strPath))
5063   {
5064     std::vector<std::string> paths;
5065     CMultiPathDirectory::GetPaths(strPath, paths);
5066 
5067     for(unsigned i=0;i<paths.size();i++)
5068       RemoveContentForPath(paths[i], progress);
5069   }
5070 
5071   try
5072   {
5073     if (nullptr == m_pDB)
5074       return;
5075     if (nullptr == m_pDS)
5076       return;
5077 
5078     if (progress)
5079     {
5080       progress->SetHeading(CVariant{700});
5081       progress->SetLine(0, CVariant{""});
5082       progress->SetLine(1, CVariant{313});
5083       progress->SetLine(2, CVariant{330});
5084       progress->SetPercentage(0);
5085       progress->Open();
5086       progress->ShowProgressBar(true);
5087     }
5088     std::vector<std::pair<int, std::string> > paths;
5089     GetSubPaths(strPath, paths);
5090     int iCurr = 0;
5091     for (const auto &i : paths)
5092     {
5093       bool bMvidsChecked=false;
5094       if (progress)
5095       {
5096         progress->SetPercentage((int)((float)(iCurr++)/paths.size()*100.f));
5097         progress->Progress();
5098       }
5099 
5100       if (HasTvShowInfo(i.second))
5101         DeleteTvShow(i.second);
5102       else
5103       {
5104         std::string strSQL = PrepareSQL("select files.strFilename from files join movie on movie.idFile=files.idFile where files.idPath=%i", i.first);
5105         m_pDS2->query(strSQL);
5106         if (m_pDS2->eof())
5107         {
5108           strSQL = PrepareSQL("select files.strFilename from files join musicvideo on musicvideo.idFile=files.idFile where files.idPath=%i", i.first);
5109           m_pDS2->query(strSQL);
5110           bMvidsChecked = true;
5111         }
5112         while (!m_pDS2->eof())
5113         {
5114           std::string strMoviePath;
5115           std::string strFileName = m_pDS2->fv("files.strFilename").get_asString();
5116           ConstructPath(strMoviePath, i.second, strFileName);
5117           if (HasMovieInfo(strMoviePath))
5118             DeleteMovie(strMoviePath);
5119           if (HasMusicVideoInfo(strMoviePath))
5120             DeleteMusicVideo(strMoviePath);
5121           m_pDS2->next();
5122           if (m_pDS2->eof() && !bMvidsChecked)
5123           {
5124             strSQL =PrepareSQL("select files.strFilename from files join musicvideo on musicvideo.idFile=files.idFile where files.idPath=%i", i.first);
5125             m_pDS2->query(strSQL);
5126             bMvidsChecked = true;
5127           }
5128         }
5129         m_pDS2->close();
5130         m_pDS2->exec(PrepareSQL("update path set strContent='', strScraper='', strHash='',strSettings='',useFolderNames=0,scanRecursive=0 where idPath=%i", i.first));
5131       }
5132     }
5133   }
5134   catch (...)
5135   {
5136     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strPath.c_str());
5137   }
5138   if (progress)
5139     progress->Close();
5140 }
5141 
SetScraperForPath(const std::string & filePath,const ScraperPtr & scraper,const VIDEO::SScanSettings & settings)5142 void CVideoDatabase::SetScraperForPath(const std::string& filePath, const ScraperPtr& scraper, const VIDEO::SScanSettings& settings)
5143 {
5144   // if we have a multipath, set scraper for all contained paths
5145   if(URIUtils::IsMultiPath(filePath))
5146   {
5147     std::vector<std::string> paths;
5148     CMultiPathDirectory::GetPaths(filePath, paths);
5149 
5150     for(unsigned i=0;i<paths.size();i++)
5151       SetScraperForPath(paths[i],scraper,settings);
5152 
5153     return;
5154   }
5155 
5156   try
5157   {
5158     if (nullptr == m_pDB)
5159       return;
5160     if (nullptr == m_pDS)
5161       return;
5162 
5163     int idPath = AddPath(filePath);
5164     if (idPath < 0)
5165       return;
5166 
5167     // Update
5168     std::string strSQL;
5169     if (settings.exclude)
5170     { //NB See note in ::GetScraperForPath about strContent=='none'
5171       strSQL = PrepareSQL(
5172           "UPDATE path SET strContent='', strScraper='', scanRecursive=0, useFolderNames=0, "
5173           "strSettings='', noUpdate=0, exclude=1, allAudio=%i WHERE idPath=%i",
5174           settings.m_allExtAudio, idPath);
5175     }
5176     else if(!scraper)
5177     { // catch clearing content, but not excluding
5178       strSQL = PrepareSQL(
5179           "UPDATE path SET strContent='', strScraper='', scanRecursive=0, useFolderNames=0, "
5180           "strSettings='', noUpdate=0, exclude=0, allAudio=%i WHERE idPath=%i",
5181           settings.m_allExtAudio, idPath);
5182     }
5183     else
5184     {
5185       std::string content = TranslateContent(scraper->Content());
5186       strSQL = PrepareSQL(
5187           "UPDATE path SET strContent='%s', strScraper='%s', scanRecursive=%i, useFolderNames=%i, "
5188           "strSettings='%s', noUpdate=%i, exclude=0, allAudio=%i WHERE idPath=%i",
5189           content.c_str(), scraper->ID().c_str(), settings.recurse, settings.parent_name,
5190           scraper->GetPathSettings().c_str(), settings.noupdate, settings.m_allExtAudio, idPath);
5191     }
5192     m_pDS->exec(strSQL);
5193   }
5194   catch (...)
5195   {
5196     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, filePath.c_str());
5197   }
5198 }
5199 
ScraperInUse(const std::string & scraperID) const5200 bool CVideoDatabase::ScraperInUse(const std::string &scraperID) const
5201 {
5202   try
5203   {
5204     if (nullptr == m_pDB)
5205       return false;
5206     if (nullptr == m_pDS)
5207       return false;
5208 
5209     std::string sql = PrepareSQL("select count(1) from path where strScraper='%s'", scraperID.c_str());
5210     if (!m_pDS->query(sql) || m_pDS->num_rows() == 0)
5211       return false;
5212     bool found = m_pDS->fv(0).get_asInt() > 0;
5213     m_pDS->close();
5214     return found;
5215   }
5216   catch (...)
5217   {
5218     CLog::Log(LOGERROR, "%s(%s) failed", __FUNCTION__, scraperID.c_str());
5219   }
5220   return false;
5221 }
5222 
5223 class CArtItem
5224 {
5225 public:
CArtItem()5226   CArtItem() { art_id = 0; media_id = 0; };
5227   int art_id;
5228   std::string art_type;
5229   std::string art_url;
5230   int media_id;
5231   std::string media_type;
5232 };
5233 
5234 // used for database update to v83
5235 class CShowItem
5236 {
5237 public:
operator ==(const CShowItem & r) const5238   bool operator==(const CShowItem &r) const
5239   {
5240     return (!ident.empty() && ident == r.ident) || (title == r.title && year == r.year);
5241   };
5242   int    id;
5243   int    path;
5244   std::string title;
5245   std::string year;
5246   std::string ident;
5247 };
5248 
5249 // used for database update to v84
5250 class CShowLink
5251 {
5252 public:
5253   int show;
5254   int pathId;
5255   std::string path;
5256 };
5257 
UpdateTables(int iVersion)5258 void CVideoDatabase::UpdateTables(int iVersion)
5259 {
5260   // Important: DO NOT use CREATE TABLE [...] AS SELECT [...] - it does not work on MySQL with GTID consistency enforced
5261 
5262   if (iVersion < 76)
5263   {
5264     m_pDS->exec("ALTER TABLE settings ADD StereoMode integer");
5265     m_pDS->exec("ALTER TABLE settings ADD StereoInvert bool");
5266   }
5267   if (iVersion < 77)
5268     m_pDS->exec("ALTER TABLE streamdetails ADD strStereoMode text");
5269 
5270   if (iVersion < 81)
5271   { // add idParentPath to path table
5272     m_pDS->exec("ALTER TABLE path ADD idParentPath integer");
5273     std::map<std::string, int> paths;
5274     m_pDS->query("select idPath,strPath from path");
5275     while (!m_pDS->eof())
5276     {
5277       paths.insert(make_pair(m_pDS->fv(1).get_asString(), m_pDS->fv(0).get_asInt()));
5278       m_pDS->next();
5279     }
5280     m_pDS->close();
5281     // run through these paths figuring out the parent path, and add to the table if found
5282     for (const auto &i : paths)
5283     {
5284       std::string parent = URIUtils::GetParentPath(i.first);
5285       auto j = paths.find(parent);
5286       if (j != paths.end())
5287         m_pDS->exec(PrepareSQL("UPDATE path SET idParentPath=%i WHERE idPath=%i", j->second, i.second));
5288     }
5289   }
5290   if (iVersion < 82)
5291   {
5292     // drop parent path id and basePath from tvshow table
5293     m_pDS->exec("UPDATE tvshow SET c16=NULL,c17=NULL");
5294   }
5295   if (iVersion < 83)
5296   {
5297     // drop duplicates in tvshow table, and update tvshowlinkpath accordingly
5298     std::string sql = PrepareSQL("SELECT tvshow.idShow,idPath,c%02d,c%02d,c%02d FROM tvshow JOIN tvshowlinkpath ON tvshow.idShow = tvshowlinkpath.idShow", VIDEODB_ID_TV_TITLE, VIDEODB_ID_TV_PREMIERED, VIDEODB_ID_TV_IDENT_ID);
5299     m_pDS->query(sql);
5300     std::vector<CShowItem> shows;
5301     while (!m_pDS->eof())
5302     {
5303       CShowItem show;
5304       show.id    = m_pDS->fv(0).get_asInt();
5305       show.path  = m_pDS->fv(1).get_asInt();
5306       show.title = m_pDS->fv(2).get_asString();
5307       show.year  = m_pDS->fv(3).get_asString();
5308       show.ident = m_pDS->fv(4).get_asString();
5309       shows.emplace_back(std::move(show));
5310       m_pDS->next();
5311     }
5312     m_pDS->close();
5313     if (!shows.empty())
5314     {
5315       for (auto i = shows.begin() + 1; i != shows.end(); ++i)
5316       {
5317         // has this show been found before?
5318         auto j = find(shows.begin(), i, *i);
5319         if (j != i)
5320         { // this is a duplicate
5321           // update the tvshowlinkpath table
5322           m_pDS->exec(PrepareSQL("UPDATE tvshowlinkpath SET idShow = %d WHERE idShow = %d AND idPath = %d", j->id, i->id, i->path));
5323           // update episodes, seasons, movie links
5324           m_pDS->exec(PrepareSQL("UPDATE episode SET idShow = %d WHERE idShow = %d", j->id, i->id));
5325           m_pDS->exec(PrepareSQL("UPDATE seasons SET idShow = %d WHERE idShow = %d", j->id, i->id));
5326           m_pDS->exec(PrepareSQL("UPDATE movielinktvshow SET idShow = %d WHERE idShow = %d", j->id, i->id));
5327           // delete tvshow
5328           m_pDS->exec(PrepareSQL("DELETE FROM genrelinktvshow WHERE idShow=%i", i->id));
5329           m_pDS->exec(PrepareSQL("DELETE FROM actorlinktvshow WHERE idShow=%i", i->id));
5330           m_pDS->exec(PrepareSQL("DELETE FROM directorlinktvshow WHERE idShow=%i", i->id));
5331           m_pDS->exec(PrepareSQL("DELETE FROM studiolinktvshow WHERE idShow=%i", i->id));
5332           m_pDS->exec(PrepareSQL("DELETE FROM tvshow WHERE idShow = %d", i->id));
5333         }
5334       }
5335       // cleanup duplicate seasons
5336       m_pDS->exec("DELETE FROM seasons WHERE idSeason NOT IN (SELECT idSeason FROM (SELECT min(idSeason) as idSeason FROM seasons GROUP BY idShow,season) AS sub)");
5337     }
5338   }
5339   if (iVersion < 84)
5340   { // replace any multipaths in tvshowlinkpath table
5341     m_pDS->query("SELECT idShow, tvshowlinkpath.idPath, strPath FROM tvshowlinkpath JOIN path ON tvshowlinkpath.idPath=path.idPath WHERE path.strPath LIKE 'multipath://%'");
5342     std::vector<CShowLink> shows;
5343     while (!m_pDS->eof())
5344     {
5345       CShowLink link;
5346       link.show   = m_pDS->fv(0).get_asInt();
5347       link.pathId = m_pDS->fv(1).get_asInt();
5348       link.path   = m_pDS->fv(2).get_asString();
5349       shows.emplace_back(std::move(link));
5350       m_pDS->next();
5351     }
5352     m_pDS->close();
5353     // update these
5354     for (auto i = shows.begin(); i != shows.end(); ++i)
5355     {
5356       std::vector<std::string> paths;
5357       CMultiPathDirectory::GetPaths(i->path, paths);
5358       for (auto j = paths.begin(); j != paths.end(); ++j)
5359       {
5360         int idPath = AddPath(*j, URIUtils::GetParentPath(*j));
5361         /* we can't rely on REPLACE INTO here as analytics (indices) aren't online yet */
5362         if (GetSingleValue(PrepareSQL("SELECT 1 FROM tvshowlinkpath WHERE idShow=%i AND idPath=%i", i->show, idPath)).empty())
5363           m_pDS->exec(PrepareSQL("INSERT INTO tvshowlinkpath(idShow, idPath) VALUES(%i,%i)", i->show, idPath));
5364       }
5365       m_pDS->exec(PrepareSQL("DELETE FROM tvshowlinkpath WHERE idShow=%i AND idPath=%i", i->show, i->pathId));
5366     }
5367   }
5368   if (iVersion < 85)
5369   {
5370     // drop multipaths from the path table - they're not needed for anything at all
5371     m_pDS->exec("DELETE FROM path WHERE strPath LIKE 'multipath://%'");
5372   }
5373   if (iVersion < 87)
5374   { // due to the tvshow merging above, there could be orphaned season or show art
5375     m_pDS->exec("DELETE from art WHERE media_type='tvshow' AND NOT EXISTS (SELECT 1 FROM tvshow WHERE tvshow.idShow = art.media_id)");
5376     m_pDS->exec("DELETE from art WHERE media_type='season' AND NOT EXISTS (SELECT 1 FROM seasons WHERE seasons.idSeason = art.media_id)");
5377   }
5378   if (iVersion < 91)
5379   {
5380     // create actor link table
5381     m_pDS->exec("CREATE TABLE actor_link(actor_id INT, media_id INT, media_type TEXT, role TEXT, cast_order INT)");
5382     m_pDS->exec("INSERT INTO actor_link(actor_id, media_id, media_type, role, cast_order) SELECT DISTINCT idActor, idMovie, 'movie', strRole, iOrder from actorlinkmovie");
5383     m_pDS->exec("INSERT INTO actor_link(actor_id, media_id, media_type, role, cast_order) SELECT DISTINCT idActor, idShow, 'tvshow', strRole, iOrder from actorlinktvshow");
5384     m_pDS->exec("INSERT INTO actor_link(actor_id, media_id, media_type, role, cast_order) SELECT DISTINCT idActor, idEpisode, 'episode', strRole, iOrder from actorlinkepisode");
5385     m_pDS->exec("DROP TABLE IF EXISTS actorlinkmovie");
5386     m_pDS->exec("DROP TABLE IF EXISTS actorlinktvshow");
5387     m_pDS->exec("DROP TABLE IF EXISTS actorlinkepisode");
5388     m_pDS->exec("CREATE TABLE actor(actor_id INTEGER PRIMARY KEY, name TEXT, art_urls TEXT)");
5389     m_pDS->exec("INSERT INTO actor(actor_id, name, art_urls) SELECT idActor,strActor,strThumb FROM actors");
5390     m_pDS->exec("DROP TABLE IF EXISTS actors");
5391 
5392     // directors
5393     m_pDS->exec("CREATE TABLE director_link(actor_id INTEGER, media_id INTEGER, media_type TEXT)");
5394     m_pDS->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idMovie, 'movie' FROM directorlinkmovie");
5395     m_pDS->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idShow, 'tvshow' FROM directorlinktvshow");
5396     m_pDS->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idEpisode, 'episode' FROM directorlinkepisode");
5397     m_pDS->exec("INSERT INTO director_link(actor_id, media_id, media_type) SELECT DISTINCT idDirector, idMVideo, 'musicvideo' FROM directorlinkmusicvideo");
5398     m_pDS->exec("DROP TABLE IF EXISTS directorlinkmovie");
5399     m_pDS->exec("DROP TABLE IF EXISTS directorlinktvshow");
5400     m_pDS->exec("DROP TABLE IF EXISTS directorlinkepisode");
5401     m_pDS->exec("DROP TABLE IF EXISTS directorlinkmusicvideo");
5402 
5403     // writers
5404     m_pDS->exec("CREATE TABLE writer_link(actor_id INTEGER, media_id INTEGER, media_type TEXT)");
5405     m_pDS->exec("INSERT INTO writer_link(actor_id, media_id, media_type) SELECT DISTINCT idWriter, idMovie, 'movie' FROM writerlinkmovie");
5406     m_pDS->exec("INSERT INTO writer_link(actor_id, media_id, media_type) SELECT DISTINCT idWriter, idEpisode, 'episode' FROM writerlinkepisode");
5407     m_pDS->exec("DROP TABLE IF EXISTS writerlinkmovie");
5408     m_pDS->exec("DROP TABLE IF EXISTS writerlinkepisode");
5409 
5410     // music artist
5411     m_pDS->exec("INSERT INTO actor_link(actor_id, media_id, media_type) SELECT DISTINCT idArtist, idMVideo, 'musicvideo' FROM artistlinkmusicvideo");
5412     m_pDS->exec("DROP TABLE IF EXISTS artistlinkmusicvideo");
5413 
5414     // studios
5415     m_pDS->exec("CREATE TABLE studio_link(studio_id INTEGER, media_id INTEGER, media_type TEXT)");
5416     m_pDS->exec("INSERT INTO studio_link(studio_id, media_id, media_type) SELECT DISTINCT idStudio, idMovie, 'movie' FROM studiolinkmovie");
5417     m_pDS->exec("INSERT INTO studio_link(studio_id, media_id, media_type) SELECT DISTINCT idStudio, idShow, 'tvshow' FROM studiolinktvshow");
5418     m_pDS->exec("INSERT INTO studio_link(studio_id, media_id, media_type) SELECT DISTINCT idStudio, idMVideo, 'musicvideo' FROM studiolinkmusicvideo");
5419     m_pDS->exec("DROP TABLE IF EXISTS studiolinkmovie");
5420     m_pDS->exec("DROP TABLE IF EXISTS studiolinktvshow");
5421     m_pDS->exec("DROP TABLE IF EXISTS studiolinkmusicvideo");
5422     m_pDS->exec("CREATE TABLE studionew(studio_id INTEGER PRIMARY KEY, name TEXT)");
5423     m_pDS->exec("INSERT INTO studionew(studio_id, name) SELECT idStudio,strStudio FROM studio");
5424     m_pDS->exec("DROP TABLE IF EXISTS studio");
5425     m_pDS->exec("ALTER TABLE studionew RENAME TO studio");
5426 
5427     // genres
5428     m_pDS->exec("CREATE TABLE genre_link(genre_id INTEGER, media_id INTEGER, media_type TEXT)");
5429     m_pDS->exec("INSERT INTO genre_link(genre_id, media_id, media_type) SELECT DISTINCT idGenre, idMovie, 'movie' FROM genrelinkmovie");
5430     m_pDS->exec("INSERT INTO genre_link(genre_id, media_id, media_type) SELECT DISTINCT idGenre, idShow, 'tvshow' FROM genrelinktvshow");
5431     m_pDS->exec("INSERT INTO genre_link(genre_id, media_id, media_type) SELECT DISTINCT idGenre, idMVideo, 'musicvideo' FROM genrelinkmusicvideo");
5432     m_pDS->exec("DROP TABLE IF EXISTS genrelinkmovie");
5433     m_pDS->exec("DROP TABLE IF EXISTS genrelinktvshow");
5434     m_pDS->exec("DROP TABLE IF EXISTS genrelinkmusicvideo");
5435     m_pDS->exec("CREATE TABLE genrenew(genre_id INTEGER PRIMARY KEY, name TEXT)");
5436     m_pDS->exec("INSERT INTO genrenew(genre_id, name) SELECT idGenre,strGenre FROM genre");
5437     m_pDS->exec("DROP TABLE IF EXISTS genre");
5438     m_pDS->exec("ALTER TABLE genrenew RENAME TO genre");
5439 
5440     // country
5441     m_pDS->exec("CREATE TABLE country_link(country_id INTEGER, media_id INTEGER, media_type TEXT)");
5442     m_pDS->exec("INSERT INTO country_link(country_id, media_id, media_type) SELECT DISTINCT idCountry, idMovie, 'movie' FROM countrylinkmovie");
5443     m_pDS->exec("DROP TABLE IF EXISTS countrylinkmovie");
5444     m_pDS->exec("CREATE TABLE countrynew(country_id INTEGER PRIMARY KEY, name TEXT)");
5445     m_pDS->exec("INSERT INTO countrynew(country_id, name) SELECT idCountry,strCountry FROM country");
5446     m_pDS->exec("DROP TABLE IF EXISTS country");
5447     m_pDS->exec("ALTER TABLE countrynew RENAME TO country");
5448 
5449     // tags
5450     m_pDS->exec("CREATE TABLE tag_link(tag_id INTEGER, media_id INTEGER, media_type TEXT)");
5451     m_pDS->exec("INSERT INTO tag_link(tag_id, media_id, media_type) SELECT DISTINCT idTag, idMedia, media_type FROM taglinks");
5452     m_pDS->exec("DROP TABLE IF EXISTS taglinks");
5453     m_pDS->exec("CREATE TABLE tagnew(tag_id INTEGER PRIMARY KEY, name TEXT)");
5454     m_pDS->exec("INSERT INTO tagnew(tag_id, name) SELECT idTag,strTag FROM tag");
5455     m_pDS->exec("DROP TABLE IF EXISTS tag");
5456     m_pDS->exec("ALTER TABLE tagnew RENAME TO tag");
5457   }
5458 
5459   if (iVersion < 93)
5460   {
5461     // cleanup main tables
5462     std::string valuesSql;
5463     for(int i = 0; i < VIDEODB_MAX_COLUMNS; i++)
5464     {
5465       valuesSql += StringUtils::Format("c%02d = TRIM(c%02d)", i, i);
5466       if (i < VIDEODB_MAX_COLUMNS - 1)
5467         valuesSql += ",";
5468     }
5469     m_pDS->exec("UPDATE episode SET " + valuesSql);
5470     m_pDS->exec("UPDATE movie SET " + valuesSql);
5471     m_pDS->exec("UPDATE musicvideo SET " + valuesSql);
5472     m_pDS->exec("UPDATE tvshow SET " + valuesSql);
5473 
5474     // cleanup additional tables
5475     std::map<std::string, std::vector<std::string>> additionalTablesMap = {
5476       {"actor", {"actor_link", "director_link", "writer_link"}},
5477       {"studio", {"studio_link"}},
5478       {"genre", {"genre_link"}},
5479       {"country", {"country_link"}},
5480       {"tag", {"tag_link"}}
5481     };
5482     for (const auto& additionalTableEntry : additionalTablesMap)
5483     {
5484       std::string table = additionalTableEntry.first;
5485       std::string tablePk = additionalTableEntry.first + "_id";
5486       std::map<int, std::string> duplicatesMinMap;
5487       std::map<int, std::string> duplicatesMap;
5488 
5489       // cleanup name
5490       m_pDS->exec(PrepareSQL("UPDATE %s SET name = TRIM(name)",
5491                              table.c_str()));
5492 
5493       // shrink name to length 255
5494       m_pDS->exec(PrepareSQL("UPDATE %s SET name = SUBSTR(name, 1, 255) WHERE LENGTH(name) > 255",
5495                              table.c_str()));
5496 
5497       // fetch main entries
5498       m_pDS->query(PrepareSQL("SELECT MIN(%s), name FROM %s GROUP BY name HAVING COUNT(1) > 1",
5499                               tablePk.c_str(), table.c_str()));
5500 
5501       while (!m_pDS->eof())
5502       {
5503         duplicatesMinMap.insert(std::make_pair(m_pDS->fv(0).get_asInt(), m_pDS->fv(1).get_asString()));
5504         m_pDS->next();
5505       }
5506       m_pDS->close();
5507 
5508       // fetch duplicate entries
5509       for (const auto& entry : duplicatesMinMap)
5510       {
5511         m_pDS->query(PrepareSQL("SELECT %s FROM %s WHERE name = '%s' AND %s <> %i",
5512                                 tablePk.c_str(), table.c_str(),
5513                                 entry.second.c_str(), tablePk.c_str(), entry.first));
5514 
5515         std::stringstream ids;
5516         while (!m_pDS->eof())
5517         {
5518           int id = m_pDS->fv(0).get_asInt();
5519           m_pDS->next();
5520 
5521           ids << id;
5522           if (!m_pDS->eof())
5523             ids << ",";
5524         }
5525         m_pDS->close();
5526 
5527         duplicatesMap.insert(std::make_pair(entry.first, ids.str()));
5528       }
5529 
5530       // cleanup duplicates in link tables
5531       for (const auto& subTable : additionalTableEntry.second)
5532       {
5533         // create indexes to speed up things
5534         m_pDS->exec(PrepareSQL("CREATE INDEX ix_%s ON %s (%s)",
5535                                subTable.c_str(), subTable.c_str(), tablePk.c_str()));
5536 
5537         // migrate every duplicate entry to the main entry
5538         for (const auto& entry : duplicatesMap)
5539         {
5540           m_pDS->exec(PrepareSQL("UPDATE %s SET %s = %i WHERE %s IN (%s) ",
5541                                  subTable.c_str(), tablePk.c_str(), entry.first,
5542                                  tablePk.c_str(), entry.second.c_str()));
5543         }
5544 
5545         // clear all duplicates in the link tables
5546         if (subTable == "actor_link")
5547         {
5548           // as a distinct won't work because of role and cast_order and a group by kills a
5549           // low powered mysql, we de-dupe it with REPLACE INTO while using the real unique index
5550           m_pDS->exec("CREATE TABLE temp_actor_link(actor_id INT, media_id INT, media_type TEXT, role TEXT, cast_order INT)");
5551           m_pDS->exec("CREATE UNIQUE INDEX ix_temp_actor_link ON temp_actor_link (actor_id, media_type(20), media_id)");
5552           m_pDS->exec("REPLACE INTO temp_actor_link SELECT * FROM actor_link");
5553           m_pDS->exec("DROP INDEX ix_temp_actor_link ON temp_actor_link");
5554         }
5555         else
5556         {
5557           m_pDS->exec(PrepareSQL("CREATE TABLE temp_%s AS SELECT DISTINCT * FROM %s",
5558                                  subTable.c_str(), subTable.c_str()));
5559         }
5560 
5561         m_pDS->exec(PrepareSQL("DROP TABLE IF EXISTS %s",
5562                                subTable.c_str()));
5563 
5564         m_pDS->exec(PrepareSQL("ALTER TABLE temp_%s RENAME TO %s",
5565                                subTable.c_str(), subTable.c_str()));
5566       }
5567 
5568       // delete duplicates in main table
5569       for (const auto& entry : duplicatesMap)
5570       {
5571         m_pDS->exec(PrepareSQL("DELETE FROM %s WHERE %s IN (%s)",
5572                                table.c_str(), tablePk.c_str(), entry.second.c_str()));
5573       }
5574     }
5575   }
5576 
5577   if (iVersion < 96)
5578   {
5579     m_pDS->exec("ALTER TABLE movie ADD userrating integer");
5580     m_pDS->exec("ALTER TABLE episode ADD userrating integer");
5581     m_pDS->exec("ALTER TABLE tvshow ADD userrating integer");
5582     m_pDS->exec("ALTER TABLE musicvideo ADD userrating integer");
5583   }
5584 
5585   if (iVersion < 97)
5586     m_pDS->exec("ALTER TABLE sets ADD strOverview TEXT");
5587 
5588   if (iVersion < 98)
5589     m_pDS->exec("ALTER TABLE seasons ADD name text");
5590 
5591   if (iVersion < 99)
5592   {
5593     // Add idSeason to episode table, so we don't have to join via idShow and season in the future
5594     m_pDS->exec("ALTER TABLE episode ADD idSeason integer");
5595 
5596     m_pDS->query("SELECT idSeason, idShow, season FROM seasons");
5597     while (!m_pDS->eof())
5598     {
5599       m_pDS2->exec(PrepareSQL("UPDATE episode "
5600         "SET idSeason = %d "
5601         "WHERE "
5602         "episode.idShow = %d AND "
5603         "episode.c%02d = %d",
5604         m_pDS->fv(0).get_asInt(), m_pDS->fv(1).get_asInt(),
5605         VIDEODB_ID_EPISODE_SEASON, m_pDS->fv(2).get_asInt()));
5606 
5607       m_pDS->next();
5608     }
5609   }
5610   if (iVersion < 101)
5611     m_pDS->exec("ALTER TABLE seasons ADD userrating INTEGER");
5612 
5613   if (iVersion < 102)
5614   {
5615     m_pDS->exec("CREATE TABLE rating (rating_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, rating_type TEXT, rating FLOAT, votes INTEGER)");
5616 
5617     std::string sql = PrepareSQL("SELECT DISTINCT idMovie, c%02d, c%02d FROM movie", VIDEODB_ID_RATING_ID, VIDEODB_ID_VOTES);
5618     m_pDS->query(sql);
5619     while (!m_pDS->eof())
5620     {
5621       m_pDS2->exec(PrepareSQL("INSERT INTO rating(media_id, media_type, rating_type, rating, votes) VALUES (%i, 'movie', 'default', %f, %i)", m_pDS->fv(0).get_asInt(), (float)strtod(m_pDS->fv(1).get_asString().c_str(), NULL), StringUtils::ReturnDigits(m_pDS->fv(2).get_asString())));
5622       int idRating = (int)m_pDS2->lastinsertid();
5623       m_pDS2->exec(PrepareSQL("UPDATE movie SET c%02d=%i WHERE idMovie=%i", VIDEODB_ID_RATING_ID, idRating, m_pDS->fv(0).get_asInt()));
5624       m_pDS->next();
5625     }
5626     m_pDS->close();
5627 
5628     sql = PrepareSQL("SELECT DISTINCT idShow, c%02d, c%02d FROM tvshow", VIDEODB_ID_TV_RATING_ID, VIDEODB_ID_TV_VOTES);
5629     m_pDS->query(sql);
5630     while (!m_pDS->eof())
5631     {
5632       m_pDS2->exec(PrepareSQL("INSERT INTO rating(media_id, media_type, rating_type, rating, votes) VALUES (%i, 'tvshow', 'default', %f, %i)", m_pDS->fv(0).get_asInt(), (float)strtod(m_pDS->fv(1).get_asString().c_str(), NULL), StringUtils::ReturnDigits(m_pDS->fv(2).get_asString())));
5633       int idRating = (int)m_pDS2->lastinsertid();
5634       m_pDS2->exec(PrepareSQL("UPDATE tvshow SET c%02d=%i WHERE idShow=%i", VIDEODB_ID_TV_RATING_ID, idRating, m_pDS->fv(0).get_asInt()));
5635       m_pDS->next();
5636     }
5637     m_pDS->close();
5638 
5639     sql = PrepareSQL("SELECT DISTINCT idEpisode, c%02d, c%02d FROM episode", VIDEODB_ID_EPISODE_RATING_ID, VIDEODB_ID_EPISODE_VOTES);
5640     m_pDS->query(sql);
5641     while (!m_pDS->eof())
5642     {
5643       m_pDS2->exec(PrepareSQL("INSERT INTO rating(media_id, media_type, rating_type, rating, votes) VALUES (%i, 'episode', 'default', %f, %i)", m_pDS->fv(0).get_asInt(), (float)strtod(m_pDS->fv(1).get_asString().c_str(), NULL), StringUtils::ReturnDigits(m_pDS->fv(2).get_asString())));
5644       int idRating = (int)m_pDS2->lastinsertid();
5645       m_pDS2->exec(PrepareSQL("UPDATE episode SET c%02d=%i WHERE idEpisode=%i", VIDEODB_ID_EPISODE_RATING_ID, idRating, m_pDS->fv(0).get_asInt()));
5646       m_pDS->next();
5647     }
5648     m_pDS->close();
5649   }
5650 
5651   if (iVersion < 103)
5652   {
5653     m_pDS->exec("ALTER TABLE settings ADD VideoStream integer");
5654     m_pDS->exec("ALTER TABLE streamdetails ADD strVideoLanguage text");
5655   }
5656 
5657   if (iVersion < 104)
5658   {
5659     m_pDS->exec("ALTER TABLE tvshow ADD duration INTEGER");
5660 
5661     std::string sql = PrepareSQL( "SELECT episode.idShow, MAX(episode.c%02d) "
5662                                   "FROM episode "
5663 
5664                                   "LEFT JOIN streamdetails "
5665                                   "ON streamdetails.idFile = episode.idFile "
5666                                   "AND streamdetails.iStreamType = 0 " // only grab video streams
5667 
5668                                   "WHERE episode.c%02d <> streamdetails.iVideoDuration "
5669                                   "OR streamdetails.iVideoDuration IS NULL "
5670                                   "GROUP BY episode.idShow", VIDEODB_ID_EPISODE_RUNTIME, VIDEODB_ID_EPISODE_RUNTIME);
5671 
5672     m_pDS->query(sql);
5673     while (!m_pDS->eof())
5674     {
5675       m_pDS2->exec(PrepareSQL("UPDATE tvshow SET duration=%i WHERE idShow=%i", m_pDS->fv(1).get_asInt(), m_pDS->fv(0).get_asInt()));
5676       m_pDS->next();
5677     }
5678     m_pDS->close();
5679   }
5680 
5681   if (iVersion < 105)
5682   {
5683     m_pDS->exec("ALTER TABLE movie ADD premiered TEXT");
5684     m_pDS->exec(PrepareSQL("UPDATE movie SET premiered=c%02d", VIDEODB_ID_YEAR));
5685     m_pDS->exec("ALTER TABLE musicvideo ADD premiered TEXT");
5686     m_pDS->exec(PrepareSQL("UPDATE musicvideo SET premiered=c%02d", VIDEODB_ID_MUSICVIDEO_YEAR));
5687   }
5688 
5689   if (iVersion < 107)
5690   {
5691     // need this due to the nested GetScraperPath query
5692     std::unique_ptr<Dataset> pDS;
5693     pDS.reset(m_pDB->CreateDataset());
5694     if (nullptr == pDS)
5695       return;
5696 
5697     pDS->exec("CREATE TABLE uniqueid (uniqueid_id INTEGER PRIMARY KEY, media_id INTEGER, media_type TEXT, value TEXT, type TEXT)");
5698 
5699     for (int i = 0; i < 3; ++i)
5700     {
5701       std::string mediatype, columnID;
5702       int columnUniqueID;
5703       switch (i)
5704       {
5705       case (0):
5706         mediatype = "movie";
5707         columnID = "idMovie";
5708         columnUniqueID = VIDEODB_ID_IDENT_ID;
5709         break;
5710       case (1):
5711         mediatype = "tvshow";
5712         columnID = "idShow";
5713         columnUniqueID = VIDEODB_ID_TV_IDENT_ID;
5714         break;
5715       case (2):
5716         mediatype = "episode";
5717         columnID = "idEpisode";
5718         columnUniqueID = VIDEODB_ID_EPISODE_IDENT_ID;
5719         break;
5720       default:
5721         continue;
5722       }
5723       pDS->query(PrepareSQL("SELECT %s, c%02d FROM %s", columnID.c_str(), columnUniqueID, mediatype.c_str()));
5724       while (!pDS->eof())
5725       {
5726         std::string uniqueid = pDS->fv(1).get_asString();
5727         if (!uniqueid.empty())
5728         {
5729           int mediaid = pDS->fv(0).get_asInt();
5730           if (StringUtils::StartsWith(uniqueid, "tt"))
5731             m_pDS2->exec(PrepareSQL("INSERT INTO uniqueid(media_id, media_type, type, value) VALUES (%i, '%s', 'imdb', '%s')", mediaid, mediatype.c_str(), uniqueid.c_str()));
5732           else
5733             m_pDS2->exec(PrepareSQL("INSERT INTO uniqueid(media_id, media_type, type, value) VALUES (%i, '%s', 'unknown', '%s')", mediaid, mediatype.c_str(), uniqueid.c_str()));
5734           m_pDS2->exec(PrepareSQL("UPDATE %s SET c%02d='%i' WHERE %s=%i", mediatype.c_str(), columnUniqueID, (int)m_pDS2->lastinsertid(), columnID.c_str(), mediaid));
5735         }
5736         pDS->next();
5737       }
5738       pDS->close();
5739     }
5740   }
5741 
5742   if (iVersion < 109)
5743   {
5744     m_pDS->exec("ALTER TABLE settings RENAME TO settingsold");
5745     m_pDS->exec("CREATE TABLE settings ( idFile integer, Deinterlace bool,"
5746                 "ViewMode integer,ZoomAmount float, PixelRatio float, VerticalShift float, AudioStream integer, SubtitleStream integer,"
5747                 "SubtitleDelay float, SubtitlesOn bool, Brightness float, Contrast float, Gamma float,"
5748                 "VolumeAmplification float, AudioDelay float, ResumeTime integer,"
5749                 "Sharpness float, NoiseReduction float, NonLinStretch bool, PostProcess bool,"
5750                 "ScalingMethod integer, DeinterlaceMode integer, StereoMode integer, StereoInvert bool, VideoStream integer)");
5751     m_pDS->exec("INSERT INTO settings SELECT idFile, Deinterlace, ViewMode, ZoomAmount, PixelRatio, VerticalShift, AudioStream, SubtitleStream, SubtitleDelay, SubtitlesOn, Brightness, Contrast, Gamma, VolumeAmplification, AudioDelay, ResumeTime, Sharpness, NoiseReduction, NonLinStretch, PostProcess, ScalingMethod, DeinterlaceMode, StereoMode, StereoInvert, VideoStream FROM settingsold");
5752     m_pDS->exec("DROP TABLE settingsold");
5753   }
5754 
5755   if (iVersion < 110)
5756   {
5757     m_pDS->exec("ALTER TABLE settings ADD TonemapMethod integer");
5758     m_pDS->exec("ALTER TABLE settings ADD TonemapParam float");
5759   }
5760 
5761   if (iVersion < 111)
5762     m_pDS->exec("ALTER TABLE settings ADD Orientation integer");
5763 
5764   if (iVersion < 112)
5765     m_pDS->exec("ALTER TABLE settings ADD CenterMixLevel integer");
5766 
5767   if (iVersion < 113)
5768   {
5769     // fb9c25f5 and e5f6d204 changed the behavior of path splitting for plugin URIs (previously it would only use the root)
5770     // Re-split paths for plugin files in order to maintain watched state etc.
5771     m_pDS->query("SELECT files.idFile, files.strFilename, path.strPath FROM files LEFT JOIN path ON files.idPath = path.idPath WHERE files.strFilename LIKE 'plugin://%'");
5772     while (!m_pDS->eof())
5773     {
5774       std::string path, fn;
5775       SplitPath(m_pDS->fv(1).get_asString(), path, fn);
5776       if (path != m_pDS->fv(2).get_asString())
5777       {
5778         int pathid = -1;
5779         m_pDS2->query(PrepareSQL("SELECT idPath FROM path WHERE strPath='%s'", path.c_str()));
5780         if (!m_pDS2->eof())
5781           pathid = m_pDS2->fv(0).get_asInt();
5782         m_pDS2->close();
5783         if (pathid < 0)
5784         {
5785           std::string parent = URIUtils::GetParentPath(path);
5786           int parentid = -1;
5787           m_pDS2->query(PrepareSQL("SELECT idPath FROM path WHERE strPath='%s'", parent.c_str()));
5788           if (!m_pDS2->eof())
5789             parentid = m_pDS2->fv(0).get_asInt();
5790           m_pDS2->close();
5791           if (parentid < 0)
5792           {
5793             m_pDS2->exec(PrepareSQL("INSERT INTO path (strPath) VALUES ('%s')", parent.c_str()));
5794             parentid = (int)m_pDS2->lastinsertid();
5795           }
5796           m_pDS2->exec(PrepareSQL("INSERT INTO path (strPath, idParentPath) VALUES ('%s', %i)", path.c_str(), parentid));
5797           pathid = (int)m_pDS2->lastinsertid();
5798         }
5799         m_pDS2->query(PrepareSQL("SELECT idFile FROM files WHERE strFileName='%s' AND idPath=%i", fn.c_str(), pathid));
5800         bool exists = !m_pDS2->eof();
5801         m_pDS2->close();
5802         if (exists)
5803           m_pDS2->exec(PrepareSQL("DELETE FROM files WHERE idFile=%i", m_pDS->fv(0).get_asInt()));
5804         else
5805           m_pDS2->exec(PrepareSQL("UPDATE files SET idPath=%i WHERE idFile=%i", pathid, m_pDS->fv(0).get_asInt()));
5806       }
5807       m_pDS->next();
5808     }
5809     m_pDS->close();
5810   }
5811 
5812   if (iVersion < 119)
5813     m_pDS->exec("ALTER TABLE path ADD allAudio bool");
5814 }
5815 
GetSchemaVersion() const5816 int CVideoDatabase::GetSchemaVersion() const
5817 {
5818   return 119;
5819 }
5820 
LookupByFolders(const std::string & path,bool shows)5821 bool CVideoDatabase::LookupByFolders(const std::string &path, bool shows)
5822 {
5823   SScanSettings settings;
5824   bool foundDirectly = false;
5825   ScraperPtr scraper = GetScraperForPath(path, settings, foundDirectly);
5826   if (scraper && scraper->Content() == CONTENT_TVSHOWS && !shows)
5827     return false; // episodes
5828   return settings.parent_name_root; // shows, movies, musicvids
5829 }
5830 
GetPlayCounts(const std::string & strPath,CFileItemList & items)5831 bool CVideoDatabase::GetPlayCounts(const std::string &strPath, CFileItemList &items)
5832 {
5833   if(URIUtils::IsMultiPath(strPath))
5834   {
5835     std::vector<std::string> paths;
5836     CMultiPathDirectory::GetPaths(strPath, paths);
5837 
5838     bool ret = false;
5839     for(unsigned i=0;i<paths.size();i++)
5840       ret |= GetPlayCounts(paths[i], items);
5841 
5842     return ret;
5843   }
5844   int pathID = -1;
5845   if (!URIUtils::IsPlugin(strPath))
5846   {
5847     pathID = GetPathId(strPath);
5848     if (pathID < 0)
5849       return false; // path (and thus files) aren't in the database
5850   }
5851 
5852   try
5853   {
5854     // error!
5855     if (nullptr == m_pDB)
5856       return false;
5857     if (nullptr == m_pDS)
5858       return false;
5859 
5860     std::string sql =
5861       "SELECT"
5862       "  files.strFilename, files.playCount,"
5863       "  bookmark.timeInSeconds, bookmark.totalTimeInSeconds "
5864       "FROM files"
5865       "  LEFT JOIN bookmark ON"
5866       "    files.idFile = bookmark.idFile AND bookmark.type = %i ";
5867 
5868     if (URIUtils::IsPlugin(strPath))
5869     {
5870       for (auto& item : items)
5871       {
5872         if (!item || item->m_bIsFolder || !item->GetProperty("IsPlayable").asBoolean())
5873           continue;
5874 
5875         std::string path, filename;
5876         SplitPath(item->GetPath(), path, filename);
5877         m_pDS->query(PrepareSQL(sql +
5878           "INNER JOIN path ON files.idPath = path.idPath "
5879           "WHERE files.strFilename='%s' AND path.strPath='%s'",
5880           (int)CBookmark::RESUME, filename.c_str(), path.c_str()));
5881 
5882         if (!m_pDS->eof())
5883         {
5884           if (!item->GetVideoInfoTag()->IsPlayCountSet())
5885             item->GetVideoInfoTag()->SetPlayCount(m_pDS->fv(1).get_asInt());
5886           if (!item->GetVideoInfoTag()->GetResumePoint().IsSet())
5887             item->GetVideoInfoTag()->SetResumePoint(m_pDS->fv(2).get_asInt(), m_pDS->fv(3).get_asInt(), "");
5888         }
5889         m_pDS->close();
5890       }
5891     }
5892     else
5893     {
5894       //! @todo also test a single query for the above and below
5895       sql = PrepareSQL(sql + "WHERE files.idPath=%i", (int)CBookmark::RESUME, pathID);
5896 
5897       if (RunQuery(sql) <= 0)
5898         return false;
5899 
5900       items.SetFastLookup(true); // note: it's possibly quicker the other way around (map on db returned items)?
5901       while (!m_pDS->eof())
5902       {
5903         std::string path;
5904         ConstructPath(path, strPath, m_pDS->fv(0).get_asString());
5905         CFileItemPtr item = items.Get(path);
5906         if (item)
5907         {
5908           if (!items.IsPlugin() || !item->GetVideoInfoTag()->IsPlayCountSet())
5909             item->GetVideoInfoTag()->SetPlayCount(m_pDS->fv(1).get_asInt());
5910 
5911           if (!item->GetVideoInfoTag()->GetResumePoint().IsSet())
5912           {
5913             item->GetVideoInfoTag()->SetResumePoint(m_pDS->fv(2).get_asInt(), m_pDS->fv(3).get_asInt(), "");
5914           }
5915         }
5916         m_pDS->next();
5917       }
5918     }
5919 
5920     return true;
5921   }
5922   catch (...)
5923   {
5924     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
5925   }
5926   return false;
5927 }
5928 
GetPlayCount(int iFileId)5929 int CVideoDatabase::GetPlayCount(int iFileId)
5930 {
5931   if (iFileId < 0)
5932     return 0;  // not in db, so not watched
5933 
5934   try
5935   {
5936     // error!
5937     if (nullptr == m_pDB)
5938       return -1;
5939     if (nullptr == m_pDS)
5940       return -1;
5941 
5942     std::string strSQL = PrepareSQL("select playCount from files WHERE idFile=%i", iFileId);
5943     int count = 0;
5944     if (m_pDS->query(strSQL))
5945     {
5946       // there should only ever be one row returned
5947       if (m_pDS->num_rows() == 1)
5948         count = m_pDS->fv(0).get_asInt();
5949       m_pDS->close();
5950     }
5951     return count;
5952   }
5953   catch (...)
5954   {
5955     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
5956   }
5957   return -1;
5958 }
5959 
GetPlayCount(const std::string & strFilenameAndPath)5960 int CVideoDatabase::GetPlayCount(const std::string& strFilenameAndPath)
5961 {
5962   return GetPlayCount(GetFileId(strFilenameAndPath));
5963 }
5964 
GetPlayCount(const CFileItem & item)5965 int CVideoDatabase::GetPlayCount(const CFileItem &item)
5966 {
5967   return GetPlayCount(GetFileId(item));
5968 }
5969 
UpdateFanart(const CFileItem & item,VIDEODB_CONTENT_TYPE type)5970 void CVideoDatabase::UpdateFanart(const CFileItem &item, VIDEODB_CONTENT_TYPE type)
5971 {
5972   if (nullptr == m_pDB)
5973     return;
5974   if (nullptr == m_pDS)
5975     return;
5976   if (!item.HasVideoInfoTag() || item.GetVideoInfoTag()->m_iDbId < 0) return;
5977 
5978   std::string exec;
5979   if (type == VIDEODB_CONTENT_TVSHOWS)
5980     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);
5981   else if (type == VIDEODB_CONTENT_MOVIES)
5982     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);
5983 
5984   try
5985   {
5986     m_pDS->exec(exec);
5987 
5988     if (type == VIDEODB_CONTENT_TVSHOWS)
5989       AnnounceUpdate(MediaTypeTvShow, item.GetVideoInfoTag()->m_iDbId);
5990     else if (type == VIDEODB_CONTENT_MOVIES)
5991       AnnounceUpdate(MediaTypeMovie, item.GetVideoInfoTag()->m_iDbId);
5992   }
5993   catch (...)
5994   {
5995     CLog::Log(LOGERROR, "%s - error updating fanart for %s", __FUNCTION__, item.GetPath().c_str());
5996   }
5997 }
5998 
SetPlayCount(const CFileItem & item,int count,const CDateTime & date)5999 void CVideoDatabase::SetPlayCount(const CFileItem &item, int count, const CDateTime &date)
6000 {
6001   int id;
6002   if (item.HasProperty("original_listitem_url") &&
6003       URIUtils::IsPlugin(item.GetProperty("original_listitem_url").asString()))
6004   {
6005     CFileItem item2(item);
6006     item2.SetPath(item.GetProperty("original_listitem_url").asString());
6007     id = AddFile(item2);
6008   }
6009   else
6010     id = AddFile(item);
6011   if (id < 0)
6012     return;
6013 
6014   // and mark as watched
6015   try
6016   {
6017     if (nullptr == m_pDB)
6018       return;
6019     if (nullptr == m_pDS)
6020       return;
6021 
6022     std::string strSQL;
6023     if (count)
6024     {
6025       if (!date.IsValid())
6026         strSQL = PrepareSQL("update files set playCount=%i,lastPlayed='%s' where idFile=%i", count, CDateTime::GetCurrentDateTime().GetAsDBDateTime().c_str(), id);
6027       else
6028         strSQL = PrepareSQL("update files set playCount=%i,lastPlayed='%s' where idFile=%i", count, date.GetAsDBDateTime().c_str(), id);
6029     }
6030     else
6031     {
6032       if (!date.IsValid())
6033         strSQL = PrepareSQL("update files set playCount=NULL,lastPlayed=NULL where idFile=%i", id);
6034       else
6035         strSQL = PrepareSQL("update files set playCount=NULL,lastPlayed='%s' where idFile=%i", date.GetAsDBDateTime().c_str(), id);
6036     }
6037 
6038     m_pDS->exec(strSQL);
6039 
6040     // We only need to announce changes to video items in the library
6041     if (item.HasVideoInfoTag() && item.GetVideoInfoTag()->m_iDbId > 0)
6042     {
6043       CVariant data;
6044       if (g_application.IsVideoScanning())
6045         data["transaction"] = true;
6046       // Only provide the "playcount" value if it has actually changed
6047       if (item.GetVideoInfoTag()->GetPlayCount() != count)
6048         data["playcount"] = count;
6049       CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnUpdate",
6050                                                          CFileItemPtr(new CFileItem(item)), data);
6051     }
6052   }
6053   catch (...)
6054   {
6055     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
6056   }
6057 }
6058 
IncrementPlayCount(const CFileItem & item)6059 void CVideoDatabase::IncrementPlayCount(const CFileItem &item)
6060 {
6061   SetPlayCount(item, GetPlayCount(item) + 1);
6062 }
6063 
UpdateLastPlayed(const CFileItem & item)6064 void CVideoDatabase::UpdateLastPlayed(const CFileItem &item)
6065 {
6066   SetPlayCount(item, GetPlayCount(item), CDateTime::GetCurrentDateTime());
6067 }
6068 
UpdateMovieTitle(int idMovie,const std::string & strNewMovieTitle,VIDEODB_CONTENT_TYPE iType)6069 void CVideoDatabase::UpdateMovieTitle(int idMovie, const std::string& strNewMovieTitle, VIDEODB_CONTENT_TYPE iType)
6070 {
6071   try
6072   {
6073     if (nullptr == m_pDB)
6074       return;
6075     if (nullptr == m_pDS)
6076       return;
6077     std::string content;
6078     if (iType == VIDEODB_CONTENT_MOVIES)
6079     {
6080       CLog::Log(LOGINFO, "Changing Movie:id:%i New Title:%s", idMovie, strNewMovieTitle.c_str());
6081       content = MediaTypeMovie;
6082     }
6083     else if (iType == VIDEODB_CONTENT_EPISODES)
6084     {
6085       CLog::Log(LOGINFO, "Changing Episode:id:%i New Title:%s", idMovie, strNewMovieTitle.c_str());
6086       content = MediaTypeEpisode;
6087     }
6088     else if (iType == VIDEODB_CONTENT_TVSHOWS)
6089     {
6090       CLog::Log(LOGINFO, "Changing TvShow:id:%i New Title:%s", idMovie, strNewMovieTitle.c_str());
6091       content = MediaTypeTvShow;
6092     }
6093     else if (iType == VIDEODB_CONTENT_MUSICVIDEOS)
6094     {
6095       CLog::Log(LOGINFO, "Changing MusicVideo:id:%i New Title:%s", idMovie, strNewMovieTitle.c_str());
6096       content = MediaTypeMusicVideo;
6097     }
6098     else if (iType == VIDEODB_CONTENT_MOVIE_SETS)
6099     {
6100       CLog::Log(LOGINFO, "Changing Movie set:id:%i New Title:%s", idMovie, strNewMovieTitle.c_str());
6101       std::string strSQL = PrepareSQL("UPDATE sets SET strSet='%s' WHERE idSet=%i", strNewMovieTitle.c_str(), idMovie );
6102       m_pDS->exec(strSQL);
6103     }
6104 
6105     if (!content.empty())
6106     {
6107       SetSingleValue(iType, idMovie, FieldTitle, strNewMovieTitle);
6108       AnnounceUpdate(content, idMovie);
6109     }
6110   }
6111   catch (...)
6112   {
6113     CLog::Log(LOGERROR, "%s (int idMovie, const std::string& strNewMovieTitle) failed on MovieID:%i and Title:%s", __FUNCTION__, idMovie, strNewMovieTitle.c_str());
6114   }
6115 }
6116 
UpdateVideoSortTitle(int idDb,const std::string & strNewSortTitle,VIDEODB_CONTENT_TYPE iType)6117 bool CVideoDatabase::UpdateVideoSortTitle(int idDb, const std::string& strNewSortTitle, VIDEODB_CONTENT_TYPE iType /* = VIDEODB_CONTENT_MOVIES */)
6118 {
6119   try
6120   {
6121     if (nullptr == m_pDB || nullptr == m_pDS)
6122       return false;
6123     if (iType != VIDEODB_CONTENT_MOVIES && iType != VIDEODB_CONTENT_TVSHOWS)
6124       return false;
6125 
6126     std::string content = MediaTypeMovie;
6127     if (iType == VIDEODB_CONTENT_TVSHOWS)
6128       content = MediaTypeTvShow;
6129 
6130     if (SetSingleValue(iType, idDb, FieldSortTitle, strNewSortTitle))
6131     {
6132       AnnounceUpdate(content, idDb);
6133       return true;
6134     }
6135   }
6136   catch (...)
6137   {
6138     CLog::Log(LOGERROR, "%s (int idDb, const std::string& strNewSortTitle, VIDEODB_CONTENT_TYPE iType) failed on ID: %i and Sort Title: %s", __FUNCTION__, idDb, strNewSortTitle.c_str());
6139   }
6140 
6141   return false;
6142 }
6143 
6144 /// \brief EraseVideoSettings() Erases the videoSettings table and reconstructs it
EraseVideoSettings(const CFileItem & item)6145 void CVideoDatabase::EraseVideoSettings(const CFileItem &item)
6146 {
6147   int idFile = GetFileId(item);
6148   if (idFile < 0)
6149     return;
6150 
6151   try
6152   {
6153     std::string sql = PrepareSQL("DELETE FROM settings WHERE idFile=%i", idFile);
6154 
6155     CLog::Log(LOGINFO, "Deleting settings information for files %s", CURL::GetRedacted(item.GetPath()).c_str());
6156     m_pDS->exec(sql);
6157   }
6158   catch (...)
6159   {
6160     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
6161   }
6162 }
6163 
EraseAllVideoSettings()6164 void CVideoDatabase::EraseAllVideoSettings()
6165 {
6166   try
6167   {
6168     std::string sql = "DELETE FROM settings";
6169 
6170     CLog::Log(LOGINFO, "Deleting all video settings");
6171     m_pDS->exec(sql);
6172   }
6173   catch (...)
6174   {
6175     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
6176   }
6177 }
6178 
EraseAllVideoSettings(const std::string & path)6179 void CVideoDatabase::EraseAllVideoSettings(const std::string& path)
6180 {
6181   std::string itemsToDelete;
6182 
6183   try
6184   {
6185     std::string sql = PrepareSQL("SELECT files.idFile FROM files WHERE idFile IN (SELECT idFile FROM files INNER JOIN path ON path.idPath = files.idPath AND path.strPath LIKE \"%s%%\")", path.c_str());
6186     m_pDS->query(sql);
6187     while (!m_pDS->eof())
6188     {
6189       std::string file = m_pDS->fv("files.idFile").get_asString() + ",";
6190       itemsToDelete += file;
6191       m_pDS->next();
6192     }
6193     m_pDS->close();
6194 
6195     if (!itemsToDelete.empty())
6196     {
6197       itemsToDelete = "(" + StringUtils::TrimRight(itemsToDelete, ",") + ")";
6198 
6199       sql = "DELETE FROM settings WHERE idFile IN " + itemsToDelete;
6200       m_pDS->exec(sql);
6201     }
6202   }
6203   catch (...)
6204   {
6205     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
6206   }
6207 }
6208 
GetGenresNav(const std::string & strBaseDir,CFileItemList & items,int idContent,const Filter & filter,bool countOnly)6209 bool CVideoDatabase::GetGenresNav(const std::string& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
6210 {
6211   return GetNavCommon(strBaseDir, items, "genre", idContent, filter, countOnly);
6212 }
6213 
GetCountriesNav(const std::string & strBaseDir,CFileItemList & items,int idContent,const Filter & filter,bool countOnly)6214 bool CVideoDatabase::GetCountriesNav(const std::string& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
6215 {
6216   return GetNavCommon(strBaseDir, items, "country", idContent, filter, countOnly);
6217 }
6218 
GetStudiosNav(const std::string & strBaseDir,CFileItemList & items,int idContent,const Filter & filter,bool countOnly)6219 bool CVideoDatabase::GetStudiosNav(const std::string& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
6220 {
6221   return GetNavCommon(strBaseDir, items, "studio", idContent, filter, countOnly);
6222 }
6223 
GetNavCommon(const std::string & strBaseDir,CFileItemList & items,const char * type,int idContent,const Filter & filter,bool countOnly)6224 bool CVideoDatabase::GetNavCommon(const std::string& strBaseDir, CFileItemList& items, const char *type, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
6225 {
6226   try
6227   {
6228     if (nullptr == m_pDB)
6229       return false;
6230     if (nullptr == m_pDS)
6231       return false;
6232 
6233     std::string strSQL;
6234     Filter extFilter = filter;
6235     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6236     {
6237       std::string view, view_id, media_type, extraField, extraJoin;
6238       if (idContent == VIDEODB_CONTENT_MOVIES)
6239       {
6240         view       = MediaTypeMovie;
6241         view_id    = "idMovie";
6242         media_type = MediaTypeMovie;
6243         extraField = "files.playCount";
6244       }
6245       else if (idContent == VIDEODB_CONTENT_TVSHOWS) //this will not get tvshows with 0 episodes
6246       {
6247         view       = MediaTypeEpisode;
6248         view_id    = "idShow";
6249         media_type = MediaTypeTvShow;
6250         // in order to make use of FieldPlaycount in smart playlists we need an extra join
6251         if (StringUtils::EqualsNoCase(type, "tag"))
6252           extraJoin  = PrepareSQL("JOIN tvshow_view ON tvshow_view.idShow = tag_link.media_id AND tag_link.media_type='tvshow'");
6253       }
6254       else if (idContent == VIDEODB_CONTENT_MUSICVIDEOS)
6255       {
6256         view       = MediaTypeMusicVideo;
6257         view_id    = "idMVideo";
6258         media_type = MediaTypeMusicVideo;
6259         extraField = "files.playCount";
6260       }
6261       else
6262         return false;
6263 
6264       strSQL = "SELECT %s " + PrepareSQL("FROM %s ", type);
6265       extFilter.fields = PrepareSQL("%s.%s_id, %s.name, path.strPath", type, type, type);
6266       extFilter.AppendField(extraField);
6267       extFilter.AppendJoin(PrepareSQL("JOIN %s_link ON %s.%s_id = %s_link.%s_id", type, type, type, type, type));
6268       extFilter.AppendJoin(PrepareSQL("JOIN %s_view ON %s_link.media_id = %s_view.%s AND %s_link.media_type='%s'", view.c_str(), type, view.c_str(), view_id.c_str(), type, media_type.c_str()));
6269       extFilter.AppendJoin(PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str()));
6270       extFilter.AppendJoin("JOIN path ON path.idPath = files.idPath");
6271       extFilter.AppendJoin(extraJoin);
6272     }
6273     else
6274     {
6275       std::string view, view_id, media_type, extraField, extraJoin;
6276       if (idContent == VIDEODB_CONTENT_MOVIES)
6277       {
6278         view       = MediaTypeMovie;
6279         view_id    = "idMovie";
6280         media_type = MediaTypeMovie;
6281         extraField = "count(1), count(files.playCount)";
6282         extraJoin  = PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str());
6283       }
6284       else if (idContent == VIDEODB_CONTENT_TVSHOWS)
6285       {
6286         view       = MediaTypeTvShow;
6287         view_id    = "idShow";
6288         media_type = MediaTypeTvShow;
6289       }
6290       else if (idContent == VIDEODB_CONTENT_MUSICVIDEOS)
6291       {
6292         view       = MediaTypeMusicVideo;
6293         view_id    = "idMVideo";
6294         media_type = MediaTypeMusicVideo;
6295         extraField = "count(1), count(files.playCount)";
6296         extraJoin  = PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str());
6297       }
6298       else
6299         return false;
6300 
6301       strSQL = "SELECT %s " + PrepareSQL("FROM %s ", type);
6302       extFilter.fields = PrepareSQL("%s.%s_id, %s.name", type, type, type);
6303       extFilter.AppendField(extraField);
6304       extFilter.AppendJoin(PrepareSQL("JOIN %s_link ON %s.%s_id = %s_link.%s_id", type, type, type, type, type));
6305       extFilter.AppendJoin(PrepareSQL("JOIN %s_view ON %s_link.media_id = %s_view.%s AND %s_link.media_type='%s'",
6306                                       view.c_str(), type, view.c_str(), view_id.c_str(), type, media_type.c_str()));
6307       extFilter.AppendJoin(extraJoin);
6308       extFilter.AppendGroup(PrepareSQL("%s.%s_id", type, type));
6309     }
6310 
6311     if (countOnly)
6312     {
6313       extFilter.fields = PrepareSQL("COUNT(DISTINCT %s.%s_id)", type, type);
6314       extFilter.group.clear();
6315       extFilter.order.clear();
6316     }
6317     strSQL = StringUtils::Format(strSQL.c_str(), !extFilter.fields.empty() ? extFilter.fields.c_str() : "*");
6318 
6319     CVideoDbUrl videoUrl;
6320     if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl))
6321       return false;
6322 
6323     int iRowsFound = RunQuery(strSQL);
6324     if (iRowsFound <= 0)
6325       return iRowsFound == 0;
6326 
6327     if (countOnly)
6328     {
6329       CFileItemPtr pItem(new CFileItem());
6330       pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound);
6331       items.Add(pItem);
6332 
6333       m_pDS->close();
6334       return true;
6335     }
6336 
6337     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6338     {
6339       std::map<int, std::pair<std::string,int> > mapItems;
6340       while (!m_pDS->eof())
6341       {
6342         int id = m_pDS->fv(0).get_asInt();
6343         std::string str = m_pDS->fv(1).get_asString();
6344 
6345         // was this already found?
6346         auto it = mapItems.find(id);
6347         if (it == mapItems.end())
6348         {
6349           // check path
6350           if (g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv(2).get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
6351           {
6352             if (idContent == VIDEODB_CONTENT_MOVIES || idContent == VIDEODB_CONTENT_MUSICVIDEOS)
6353               mapItems.insert(std::pair<int, std::pair<std::string,int> >(id, std::pair<std::string, int>(str,m_pDS->fv(3).get_asInt()))); //fv(3) is file.playCount
6354             else if (idContent == VIDEODB_CONTENT_TVSHOWS)
6355               mapItems.insert(std::pair<int, std::pair<std::string,int> >(id, std::pair<std::string,int>(str,0)));
6356           }
6357         }
6358         m_pDS->next();
6359       }
6360       m_pDS->close();
6361 
6362       for (const auto &i : mapItems)
6363       {
6364         CFileItemPtr pItem(new CFileItem(i.second.first));
6365         pItem->GetVideoInfoTag()->m_iDbId = i.first;
6366         pItem->GetVideoInfoTag()->m_type = type;
6367 
6368         CVideoDbUrl itemUrl = videoUrl;
6369         std::string path = StringUtils::Format("%i/", i.first);
6370         itemUrl.AppendPath(path);
6371         pItem->SetPath(itemUrl.ToString());
6372 
6373         pItem->m_bIsFolder = true;
6374         if (idContent == VIDEODB_CONTENT_MOVIES || idContent == VIDEODB_CONTENT_MUSICVIDEOS)
6375           pItem->GetVideoInfoTag()->SetPlayCount(i.second.second);
6376         if (!items.Contains(pItem->GetPath()))
6377         {
6378           pItem->SetLabelPreformatted(true);
6379           items.Add(pItem);
6380         }
6381       }
6382     }
6383     else
6384     {
6385       while (!m_pDS->eof())
6386       {
6387         CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
6388         pItem->GetVideoInfoTag()->m_iDbId = m_pDS->fv(0).get_asInt();
6389         pItem->GetVideoInfoTag()->m_type = type;
6390 
6391         CVideoDbUrl itemUrl = videoUrl;
6392         std::string path = StringUtils::Format("%i/", m_pDS->fv(0).get_asInt());
6393         itemUrl.AppendPath(path);
6394         pItem->SetPath(itemUrl.ToString());
6395 
6396         pItem->m_bIsFolder = true;
6397         pItem->SetLabelPreformatted(true);
6398         if (idContent == VIDEODB_CONTENT_MOVIES || idContent == VIDEODB_CONTENT_MUSICVIDEOS)
6399         { // fv(3) is the number of videos watched, fv(2) is the total number.  We set the playcount
6400           // only if the number of videos watched is equal to the total number (i.e. every video watched)
6401           pItem->GetVideoInfoTag()->SetPlayCount((m_pDS->fv(3).get_asInt() == m_pDS->fv(2).get_asInt()) ? 1 : 0);
6402         }
6403         items.Add(pItem);
6404         m_pDS->next();
6405       }
6406       m_pDS->close();
6407     }
6408     return true;
6409   }
6410   catch (...)
6411   {
6412     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
6413   }
6414   return false;
6415 }
6416 
GetTagsNav(const std::string & strBaseDir,CFileItemList & items,int idContent,const Filter & filter,bool countOnly)6417 bool CVideoDatabase::GetTagsNav(const std::string& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
6418 {
6419   return GetNavCommon(strBaseDir, items, "tag", idContent, filter, countOnly);
6420 }
6421 
GetSetsNav(const std::string & strBaseDir,CFileItemList & items,int idContent,const Filter & filter,bool ignoreSingleMovieSets)6422 bool CVideoDatabase::GetSetsNav(const std::string& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool ignoreSingleMovieSets /* = false */)
6423 {
6424   if (idContent != VIDEODB_CONTENT_MOVIES)
6425     return false;
6426 
6427   return GetSetsByWhere(strBaseDir, filter, items, ignoreSingleMovieSets);
6428 }
6429 
GetSetsByWhere(const std::string & strBaseDir,const Filter & filter,CFileItemList & items,bool ignoreSingleMovieSets)6430 bool CVideoDatabase::GetSetsByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, bool ignoreSingleMovieSets /* = false */)
6431 {
6432   try
6433   {
6434     if (nullptr == m_pDB)
6435       return false;
6436     if (nullptr == m_pDS)
6437       return false;
6438 
6439     CVideoDbUrl videoUrl;
6440     if (!videoUrl.FromString(strBaseDir))
6441       return false;
6442 
6443     Filter setFilter = filter;
6444     setFilter.join += " JOIN sets ON movie_view.idSet = sets.idSet";
6445     if (!setFilter.order.empty())
6446       setFilter.order += ",";
6447     setFilter.order += "sets.idSet";
6448 
6449     if (!GetMoviesByWhere(strBaseDir, setFilter, items))
6450       return false;
6451 
6452     CFileItemList sets;
6453     GroupAttribute groupingAttributes;
6454     const CUrlOptions::UrlOptions& options = videoUrl.GetOptions();
6455     auto option = options.find("ignoreSingleMovieSets");
6456 
6457     if (option != options.end())
6458     {
6459       groupingAttributes =
6460           option->second.asBoolean() ? GroupAttributeIgnoreSingleItems : GroupAttributeNone;
6461     }
6462     else
6463     {
6464       groupingAttributes =
6465           ignoreSingleMovieSets ? GroupAttributeIgnoreSingleItems : GroupAttributeNone;
6466     }
6467 
6468     if (!GroupUtils::Group(GroupBySet, strBaseDir, items, sets, groupingAttributes))
6469       return false;
6470 
6471     items.ClearItems();
6472     items.Append(sets);
6473 
6474     return true;
6475   }
6476   catch (...)
6477   {
6478     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
6479   }
6480   return false;
6481 }
6482 
GetMusicVideoAlbumsNav(const std::string & strBaseDir,CFileItemList & items,int idArtist,const Filter & filter,bool countOnly)6483 bool CVideoDatabase::GetMusicVideoAlbumsNav(const std::string& strBaseDir, CFileItemList& items, int idArtist /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
6484 {
6485   try
6486   {
6487     if (nullptr == m_pDB)
6488       return false;
6489     if (nullptr == m_pDS)
6490       return false;
6491 
6492     CVideoDbUrl videoUrl;
6493     if (!videoUrl.FromString(strBaseDir))
6494       return false;
6495 
6496     std::string strSQL = "select %s from musicvideo_view ";
6497     Filter extFilter = filter;
6498     extFilter.fields = PrepareSQL("musicvideo_view.c%02d, musicvideo_view.idMVideo, actor.name, "
6499                                   "musicvideo_view.c%02d, musicvideo_view.c%02d, musicvideo_view.c%02d ",
6500                                   VIDEODB_ID_MUSICVIDEO_ALBUM, VIDEODB_ID_MUSICVIDEO_TITLE,
6501                                   VIDEODB_ID_MUSICVIDEO_PLOT, VIDEODB_ID_MUSICVIDEO_ARTIST);
6502     extFilter.AppendJoin(
6503         PrepareSQL("JOIN actor_link ON actor_link.media_id=musicvideo_view.idMVideo "));
6504     extFilter.AppendJoin(PrepareSQL("JOIN actor ON actor.actor_id = actor_link.actor_id"));
6505     extFilter.fields += ", path.strPath";
6506     extFilter.AppendJoin("join files on files.idFile = musicvideo_view.idFile join path on path.idPath = files.idPath");
6507 
6508     if (StringUtils::EndsWith(strBaseDir,"albums/"))
6509       extFilter.AppendWhere(PrepareSQL("musicvideo_view.c%02d != ''", VIDEODB_ID_MUSICVIDEO_ALBUM));
6510 
6511     if (idArtist > -1)
6512       videoUrl.AddOption("artistid", idArtist);
6513 
6514     extFilter.AppendGroup(PrepareSQL(" CASE WHEN musicvideo_view.c09 !='' THEN musicvideo_view.c09 "
6515                                      "ELSE musicvideo_view.c00 END"));
6516 
6517     if (countOnly)
6518     {
6519       extFilter.fields = "COUNT(1)";
6520       extFilter.group.clear();
6521       extFilter.order.clear();
6522     }
6523     strSQL = StringUtils::Format(strSQL.c_str(), !extFilter.fields.empty() ? extFilter.fields.c_str() : "*");
6524 
6525     if (!BuildSQL(videoUrl.ToString(), strSQL, extFilter, strSQL, videoUrl))
6526       return false;
6527 
6528     int iRowsFound = RunQuery(strSQL);
6529     /* fields returned by query are :-
6530     (0) - Album title (if any)
6531     (1) - idMVideo
6532     (2) - Artist name
6533     (3) - Music video title
6534     (4) - Music video plot
6535     (5) - Music Video artist
6536     (6) - Path to video
6537     */
6538     if (iRowsFound <= 0)
6539       return iRowsFound == 0;
6540 
6541     std::string strArtist;
6542     if (idArtist> -1)
6543       strArtist = m_pDS->fv("actor.name").get_asString();
6544 
6545     if (countOnly)
6546     {
6547       CFileItemPtr pItem(new CFileItem());
6548       pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound);
6549       items.Add(pItem);
6550 
6551       m_pDS->close();
6552       return true;
6553     }
6554 
6555     std::list <int> idMVideoList;
6556     std::list <std::pair<std::string, std::string>> idData;
6557 
6558     while (!m_pDS->eof())
6559     {
6560       bool isAlbum = true;
6561       std::string strAlbum = m_pDS->fv(0).get_asString(); //Album title
6562       int idMVideo = m_pDS->fv(1).get_asInt();
6563       if (strAlbum.empty())
6564       {
6565         strAlbum = m_pDS->fv(3).get_asString(); // video title if not an album
6566         isAlbum = false;
6567       }
6568 
6569       CFileItemPtr pItem(new CFileItem(strAlbum));
6570 
6571       CVideoDbUrl itemUrl = videoUrl;
6572       std::string path = StringUtils::Format("%i/", idMVideo);
6573       if (!isAlbum)
6574       {
6575         itemUrl.AddOption("albumid", idMVideo);
6576         path += StringUtils::Format("%i", idMVideo);
6577 
6578         strSQL = PrepareSQL(
6579             "SELECT type, url FROM art WHERE media_id = %i AND media_type = 'musicvideo'",
6580             idMVideo);
6581         m_pDS2->query(strSQL);
6582         while (!m_pDS2->eof())
6583         {
6584           pItem->SetArt(m_pDS2->fv(0).get_asString(), m_pDS2->fv(1).get_asString());
6585           m_pDS2->next();
6586         }
6587         m_pDS2->close();
6588       }
6589       itemUrl.AppendPath(path);
6590       pItem->SetPath(itemUrl.ToString());
6591       pItem->m_bIsFolder = isAlbum;
6592       pItem->SetLabelPreformatted(true);
6593 
6594       if (!items.Contains(pItem->GetPath()))
6595         if (g_passwordManager.IsDatabasePathUnlocked(
6596                 m_pDS->fv("path.strPath").get_asString(),
6597                 *CMediaSourceSettings::GetInstance().GetSources("video")))
6598         {
6599           pItem->GetVideoInfoTag()->m_artist.emplace_back(strArtist);
6600           pItem->GetVideoInfoTag()->m_iDbId = idMVideo;
6601           items.Add(pItem);
6602           idMVideoList.push_back(idMVideo);
6603           idData.push_back(make_pair(m_pDS->fv(0).get_asString(), m_pDS->fv(5).get_asString()));
6604         }
6605       m_pDS->next();
6606     }
6607     m_pDS->close();
6608 
6609     for (int i = 0; i < items.Size(); i++)
6610     {
6611       CVideoInfoTag details;
6612 
6613       if (items[i]->m_bIsFolder)
6614       {
6615         details.SetPath(items[i]->GetPath());
6616         details.m_strAlbum = idData.front().first;
6617         details.m_type = MediaTypeAlbum;
6618         details.m_artist.emplace_back(idData.front().second);
6619         details.m_iDbId = idMVideoList.front();
6620         items[i]->SetProperty("musicvideomediatype", MediaTypeAlbum);
6621         items[i]->SetLabel(idData.front().first);
6622         items[i]->SetFromVideoInfoTag(details);
6623 
6624         idMVideoList.pop_front();
6625         idData.pop_front();
6626         continue;
6627       }
6628       else
6629       {
6630         GetMusicVideoInfo("", details, idMVideoList.front());
6631         items[i]->SetFromVideoInfoTag(details);
6632         idMVideoList.pop_front();
6633         idData.pop_front();
6634       }
6635     }
6636 
6637     if (!strArtist.empty())
6638       items.SetProperty("customtitle",strArtist); // change displayed path from eg /23 to /Artist
6639 //    CLog::Log(LOGDEBUG, __FUNCTION__" Time: %d ms", XbmcThreads::SystemClockMillis() - time);
6640     return true;
6641   }
6642   catch (...)
6643   {
6644     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
6645   }
6646   return false;
6647 }
6648 
GetWritersNav(const std::string & strBaseDir,CFileItemList & items,int idContent,const Filter & filter,bool countOnly)6649 bool CVideoDatabase::GetWritersNav(const std::string& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
6650 {
6651   return GetPeopleNav(strBaseDir, items, "writer", idContent, filter, countOnly);
6652 }
6653 
GetDirectorsNav(const std::string & strBaseDir,CFileItemList & items,int idContent,const Filter & filter,bool countOnly)6654 bool CVideoDatabase::GetDirectorsNav(const std::string& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
6655 {
6656   return GetPeopleNav(strBaseDir, items, "director", idContent, filter, countOnly);
6657 }
6658 
GetActorsNav(const std::string & strBaseDir,CFileItemList & items,int idContent,const Filter & filter,bool countOnly)6659 bool CVideoDatabase::GetActorsNav(const std::string& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
6660 {
6661   if (GetPeopleNav(strBaseDir, items, "actor", idContent, filter, countOnly))
6662   { // set thumbs - ideally this should be in the normal thumb setting routines
6663     for (int i = 0; i < items.Size() && !countOnly; i++)
6664     {
6665       CFileItemPtr pItem = items[i];
6666       if (idContent == VIDEODB_CONTENT_MUSICVIDEOS)
6667         pItem->SetArt("icon", "DefaultArtist.png");
6668       else
6669         pItem->SetArt("icon", "DefaultActor.png");
6670     }
6671     return true;
6672   }
6673   return false;
6674 }
6675 
GetPeopleNav(const std::string & strBaseDir,CFileItemList & items,const char * type,int idContent,const Filter & filter,bool countOnly)6676 bool CVideoDatabase::GetPeopleNav(const std::string& strBaseDir, CFileItemList& items, const char *type, int idContent /* = -1 */, const Filter &filter /* = Filter() */, bool countOnly /* = false */)
6677 {
6678   if (nullptr == m_pDB)
6679     return false;
6680   if (nullptr == m_pDS)
6681     return false;
6682 
6683   try
6684   {
6685     //! @todo This routine (and probably others at this same level) use playcount as a reference to filter on at a later
6686     //!       point.  This means that we *MUST* filter these levels as you'll get double ups.  Ideally we'd allow playcount
6687     //!       to filter through as we normally do for tvshows to save this happening.
6688     //!       Also, we apply this same filtering logic to the locked or unlocked paths to prevent these from showing.
6689     //!       Whether or not this should happen is a tricky one - it complicates all the high level categories (everything
6690     //!       above titles).
6691 
6692     // General routine that the other actor/director/writer routines call
6693 
6694     // get primary genres for movies
6695     std::string strSQL;
6696     bool bMainArtistOnly = false;
6697     Filter extFilter = filter;
6698     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6699     {
6700       std::string view, view_id, media_type, extraField, extraJoin, group;
6701       if (idContent == VIDEODB_CONTENT_MOVIES)
6702       {
6703         view       = MediaTypeMovie;
6704         view_id    = "idMovie";
6705         media_type = MediaTypeMovie;
6706         extraField = "files.playCount";
6707       }
6708       else if (idContent == VIDEODB_CONTENT_TVSHOWS)
6709       {
6710         view       = MediaTypeEpisode;
6711         view_id    = "idShow";
6712         media_type = MediaTypeTvShow;
6713         extraField = "count(DISTINCT idShow)";
6714         group = "actor.actor_id";
6715       }
6716       else if (idContent == VIDEODB_CONTENT_EPISODES)
6717       {
6718         view       = MediaTypeEpisode;
6719         view_id    = "idEpisode";
6720         media_type = MediaTypeEpisode;
6721         extraField = "files.playCount";
6722       }
6723       else if (idContent == VIDEODB_CONTENT_MUSICVIDEOS)
6724       {
6725         bMainArtistOnly = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
6726             CSettings::SETTING_VIDEOLIBRARY_SHOWPERFORMERS);
6727         if (StringUtils::EndsWith(strBaseDir, "directors/"))
6728           // only set this to true if getting artists and show all performers is false
6729           bMainArtistOnly = false;
6730         view       = MediaTypeMusicVideo;
6731         view_id    = "idMVideo";
6732         media_type = MediaTypeMusicVideo;
6733         extraField = "count(1), count(files.playCount)";
6734         if (bMainArtistOnly)
6735           extraJoin =
6736               PrepareSQL(" WHERE actor.name IN (SELECT musicvideo_view.c10 from musicvideo_view)");
6737         group = "actor.actor_id";
6738       }
6739       else
6740         return false;
6741 
6742       strSQL = "SELECT %s FROM actor ";
6743       extFilter.fields = "actor.actor_id, actor.name, actor.art_urls, path.strPath";
6744       extFilter.AppendField(extraField);
6745       extFilter.AppendJoin(PrepareSQL("JOIN %s_link ON actor.actor_id = %s_link.actor_id", type, type));
6746       extFilter.AppendJoin(PrepareSQL("JOIN %s_view ON %s_link.media_id = %s_view.%s AND %s_link.media_type='%s'", view.c_str(), type, view.c_str(), view_id.c_str(), type, media_type.c_str()));
6747       extFilter.AppendJoin(PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str()));
6748       extFilter.AppendJoin("JOIN path ON path.idPath = files.idPath");
6749       extFilter.AppendJoin(extraJoin);
6750       extFilter.AppendGroup(group);
6751     }
6752     else
6753     {
6754       std::string view, view_id, media_type, extraField, extraJoin;
6755       if (idContent == VIDEODB_CONTENT_MOVIES)
6756       {
6757         view       = MediaTypeMovie;
6758         view_id    = "idMovie";
6759         media_type = MediaTypeMovie;
6760         extraField = "count(1), count(files.playCount)";
6761         extraJoin  = PrepareSQL(" JOIN files ON files.idFile=%s_view.idFile", view.c_str());
6762       }
6763       else if (idContent == VIDEODB_CONTENT_TVSHOWS)
6764       {
6765         view       = MediaTypeTvShow;
6766         view_id    = "idShow";
6767         media_type = MediaTypeTvShow;
6768         extraField = "count(idShow)";
6769       }
6770       else if (idContent == VIDEODB_CONTENT_EPISODES)
6771       {
6772         view       = MediaTypeEpisode;
6773         view_id    = "idEpisode";
6774         media_type = MediaTypeEpisode;
6775         extraField = "count(1), count(files.playCount)";
6776         extraJoin  = PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str());
6777       }
6778       else if (idContent == VIDEODB_CONTENT_MUSICVIDEOS)
6779       {
6780         bMainArtistOnly = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
6781             CSettings::SETTING_VIDEOLIBRARY_SHOWPERFORMERS);
6782         if (StringUtils::EndsWith(strBaseDir, "directors/"))
6783           // only set this to true if getting artists and show all performers is false
6784           bMainArtistOnly = false;
6785         view       = MediaTypeMusicVideo;
6786         view_id    = "idMVideo";
6787         media_type = MediaTypeMusicVideo;
6788         extraField = "count(1), count(files.playCount)";
6789         extraJoin  = PrepareSQL("JOIN files ON files.idFile = %s_view.idFile", view.c_str());
6790         if (bMainArtistOnly)
6791           extraJoin =
6792               extraJoin +
6793               PrepareSQL(" WHERE actor.name IN (SELECT musicvideo_view.c10 from musicvideo_view)");
6794       }
6795       else
6796         return false;
6797 
6798       strSQL ="SELECT %s FROM actor ";
6799       extFilter.fields = "actor.actor_id, actor.name, actor.art_urls";
6800       extFilter.AppendField(extraField);
6801       extFilter.AppendJoin(PrepareSQL("JOIN %s_link on actor.actor_id = %s_link.actor_id", type, type));
6802       extFilter.AppendJoin(PrepareSQL("JOIN %s_view on %s_link.media_id = %s_view.%s AND %s_link.media_type='%s'", view.c_str(), type, view.c_str(), view_id.c_str(), type, media_type.c_str()));
6803       extFilter.AppendJoin(extraJoin);
6804       extFilter.AppendGroup("actor.actor_id");
6805     }
6806 
6807     if (countOnly)
6808     {
6809       extFilter.fields = "COUNT(1)";
6810       extFilter.group.clear();
6811       extFilter.order.clear();
6812     }
6813     strSQL = StringUtils::Format(strSQL.c_str(), !extFilter.fields.empty() ? extFilter.fields.c_str() : "*");
6814 
6815     CVideoDbUrl videoUrl;
6816     if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl))
6817       return false;
6818 
6819     // run query
6820     unsigned int time = XbmcThreads::SystemClockMillis();
6821     if (!m_pDS->query(strSQL)) return false;
6822     CLog::Log(LOGDEBUG, LOGDATABASE, "%s -  query took %i ms",
6823               __FUNCTION__, XbmcThreads::SystemClockMillis() - time); time = XbmcThreads::SystemClockMillis();
6824     int iRowsFound = m_pDS->num_rows();
6825     if (iRowsFound == 0)
6826     {
6827       m_pDS->close();
6828       return true;
6829     }
6830 
6831     if (countOnly)
6832     {
6833       CFileItemPtr pItem(new CFileItem());
6834       pItem->SetProperty("total", iRowsFound == 1 ? m_pDS->fv(0).get_asInt() : iRowsFound);
6835       items.Add(pItem);
6836 
6837       m_pDS->close();
6838       return true;
6839     }
6840 
6841     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6842     {
6843       std::map<int, CActor> mapActors;
6844 
6845       while (!m_pDS->eof())
6846       {
6847         int idActor = m_pDS->fv(0).get_asInt();
6848         CActor actor;
6849         actor.name = m_pDS->fv(1).get_asString();
6850         actor.thumb = m_pDS->fv(2).get_asString();
6851         if (idContent != VIDEODB_CONTENT_TVSHOWS && idContent != VIDEODB_CONTENT_MUSICVIDEOS)
6852         {
6853           actor.playcount = m_pDS->fv(3).get_asInt();
6854           actor.appearances = 1;
6855         }
6856         else actor.appearances = m_pDS->fv(4).get_asInt();
6857         auto it = mapActors.find(idActor);
6858         // is this actor already known?
6859         if (it == mapActors.end())
6860         {
6861           // check path
6862           if (g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
6863             mapActors.insert(std::pair<int, CActor>(idActor, actor));
6864         }
6865         else if (idContent != VIDEODB_CONTENT_TVSHOWS && idContent != VIDEODB_CONTENT_MUSICVIDEOS)
6866             it->second.appearances++;
6867         m_pDS->next();
6868       }
6869       m_pDS->close();
6870 
6871       for (const auto &i : mapActors)
6872       {
6873         CFileItemPtr pItem(new CFileItem(i.second.name));
6874 
6875         CVideoDbUrl itemUrl = videoUrl;
6876         std::string path = StringUtils::Format("%i/", i.first);
6877         itemUrl.AppendPath(path);
6878         pItem->SetPath(itemUrl.ToString());
6879 
6880         pItem->m_bIsFolder=true;
6881         pItem->GetVideoInfoTag()->SetPlayCount(i.second.playcount);
6882         pItem->GetVideoInfoTag()->m_strPictureURL.ParseFromData(i.second.thumb);
6883         pItem->GetVideoInfoTag()->m_iDbId = i.first;
6884         pItem->GetVideoInfoTag()->m_type = type;
6885         pItem->GetVideoInfoTag()->m_relevance = i.second.appearances;
6886         if (idContent == VIDEODB_CONTENT_MUSICVIDEOS)
6887         {
6888           // Get artist bio from music db later if available
6889           pItem->GetVideoInfoTag()->m_artist.emplace_back(i.second.name);
6890           pItem->SetProperty("musicvideomediatype", MediaTypeArtist);
6891         }
6892         items.Add(pItem);
6893       }
6894     }
6895     else
6896     {
6897       while (!m_pDS->eof())
6898       {
6899         try
6900         {
6901           CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
6902 
6903           CVideoDbUrl itemUrl = videoUrl;
6904           std::string path = StringUtils::Format("%i/", m_pDS->fv(0).get_asInt());
6905           itemUrl.AppendPath(path);
6906           pItem->SetPath(itemUrl.ToString());
6907 
6908           pItem->m_bIsFolder=true;
6909           pItem->GetVideoInfoTag()->m_strPictureURL.ParseFromData(m_pDS->fv(2).get_asString());
6910           pItem->GetVideoInfoTag()->m_iDbId = m_pDS->fv(0).get_asInt();
6911           pItem->GetVideoInfoTag()->m_type = type;
6912           if (idContent != VIDEODB_CONTENT_TVSHOWS)
6913           {
6914             // fv(4) is the number of videos watched, fv(3) is the total number.  We set the playcount
6915             // only if the number of videos watched is equal to the total number (i.e. every video watched)
6916             pItem->GetVideoInfoTag()->SetPlayCount((m_pDS->fv(4).get_asInt() == m_pDS->fv(3).get_asInt()) ? 1 : 0);
6917           }
6918           pItem->GetVideoInfoTag()->m_relevance = m_pDS->fv(3).get_asInt();
6919           if (idContent == VIDEODB_CONTENT_MUSICVIDEOS)
6920           {
6921             pItem->GetVideoInfoTag()->m_artist.emplace_back(pItem->GetLabel());
6922             pItem->SetProperty("musicvideomediatype", MediaTypeArtist);
6923           }
6924           items.Add(pItem);
6925           m_pDS->next();
6926         }
6927         catch (...)
6928         {
6929           m_pDS->close();
6930           CLog::Log(LOGERROR, "%s: out of memory - retrieved %i items", __FUNCTION__, items.Size());
6931           return items.Size() > 0;
6932         }
6933       }
6934       m_pDS->close();
6935     }
6936     CLog::Log(LOGDEBUG, LOGDATABASE, "%s item retrieval took %i ms",
6937               __FUNCTION__, XbmcThreads::SystemClockMillis() - time); time = XbmcThreads::SystemClockMillis();
6938 
6939     return true;
6940   }
6941   catch (...)
6942   {
6943     m_pDS->close();
6944     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
6945   }
6946   return false;
6947 }
6948 
GetYearsNav(const std::string & strBaseDir,CFileItemList & items,int idContent,const Filter & filter)6949 bool CVideoDatabase::GetYearsNav(const std::string& strBaseDir, CFileItemList& items, int idContent /* = -1 */, const Filter &filter /* = Filter() */)
6950 {
6951   try
6952   {
6953     if (nullptr == m_pDB)
6954       return false;
6955     if (nullptr == m_pDS)
6956       return false;
6957 
6958     std::string strSQL;
6959     Filter extFilter = filter;
6960     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
6961     {
6962       if (idContent == VIDEODB_CONTENT_MOVIES)
6963       {
6964         strSQL = "select movie_view.premiered, path.strPath, files.playCount from movie_view ";
6965         extFilter.AppendJoin("join files on files.idFile = movie_view.idFile join path on files.idPath = path.idPath");
6966       }
6967       else if (idContent == VIDEODB_CONTENT_TVSHOWS)
6968       {
6969         strSQL = PrepareSQL("select tvshow_view.c%02d, path.strPath from tvshow_view ", VIDEODB_ID_TV_PREMIERED);
6970         extFilter.AppendJoin("join episode_view on episode_view.idShow = tvshow_view.idShow join files on files.idFile = episode_view.idFile join path on files.idPath = path.idPath");
6971       }
6972       else if (idContent == VIDEODB_CONTENT_MUSICVIDEOS)
6973       {
6974         strSQL = "select musicvideo_view.premiered, path.strPath, files.playCount from musicvideo_view ";
6975         extFilter.AppendJoin("join files on files.idFile = musicvideo_view.idFile join path on files.idPath = path.idPath");
6976       }
6977       else
6978         return false;
6979     }
6980     else
6981     {
6982       std::string group;
6983       if (idContent == VIDEODB_CONTENT_MOVIES)
6984       {
6985         strSQL = "select movie_view.premiered, count(1), count(files.playCount) from movie_view ";
6986         extFilter.AppendJoin("join files on files.idFile = movie_view.idFile");
6987         extFilter.AppendGroup("movie_view.premiered");
6988       }
6989       else if (idContent == VIDEODB_CONTENT_TVSHOWS)
6990       {
6991         strSQL = PrepareSQL("select distinct tvshow_view.c%02d from tvshow_view", VIDEODB_ID_TV_PREMIERED);
6992         extFilter.AppendGroup(PrepareSQL("tvshow_view.c%02d", VIDEODB_ID_TV_PREMIERED));
6993       }
6994       else if (idContent == VIDEODB_CONTENT_MUSICVIDEOS)
6995       {
6996         strSQL = "select musicvideo_view.premiered, count(1), count(files.playCount) from musicvideo_view ";
6997         extFilter.AppendJoin("join files on files.idFile = musicvideo_view.idFile");
6998         extFilter.AppendGroup("musicvideo_view.premiered");
6999       }
7000       else
7001         return false;
7002     }
7003 
7004     CVideoDbUrl videoUrl;
7005     if (!BuildSQL(strBaseDir, strSQL, extFilter, strSQL, videoUrl))
7006       return false;
7007 
7008     int iRowsFound = RunQuery(strSQL);
7009     if (iRowsFound <= 0)
7010       return iRowsFound == 0;
7011 
7012     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
7013     {
7014       std::map<int, std::pair<std::string,int> > mapYears;
7015       while (!m_pDS->eof())
7016       {
7017         int lYear = 0;
7018         std::string dateString = m_pDS->fv(0).get_asString();
7019         if (dateString.size() == 4)
7020           lYear = m_pDS->fv(0).get_asInt();
7021         else
7022         {
7023           CDateTime time;
7024           time.SetFromDateString(dateString);
7025           if (time.IsValid())
7026             lYear = time.GetYear();
7027         }
7028         auto it = mapYears.find(lYear);
7029         if (it == mapYears.end())
7030         {
7031           // check path
7032           if (g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
7033           {
7034             std::string year = StringUtils::Format("%d", lYear);
7035             if (idContent == VIDEODB_CONTENT_MOVIES || idContent == VIDEODB_CONTENT_MUSICVIDEOS)
7036               mapYears.insert(std::pair<int, std::pair<std::string,int> >(lYear, std::pair<std::string,int>(year,m_pDS->fv(2).get_asInt())));
7037             else
7038               mapYears.insert(std::pair<int, std::pair<std::string,int> >(lYear, std::pair<std::string,int>(year,0)));
7039           }
7040         }
7041         m_pDS->next();
7042       }
7043       m_pDS->close();
7044 
7045       for (const auto &i : mapYears)
7046       {
7047         if (i.first == 0)
7048           continue;
7049         CFileItemPtr pItem(new CFileItem(i.second.first));
7050 
7051         CVideoDbUrl itemUrl = videoUrl;
7052         std::string path = StringUtils::Format("%i/", i.first);
7053         itemUrl.AppendPath(path);
7054         pItem->SetPath(itemUrl.ToString());
7055 
7056         pItem->m_bIsFolder=true;
7057         if (idContent == VIDEODB_CONTENT_MOVIES || idContent == VIDEODB_CONTENT_MUSICVIDEOS)
7058           pItem->GetVideoInfoTag()->SetPlayCount(i.second.second);
7059         items.Add(pItem);
7060       }
7061     }
7062     else
7063     {
7064       while (!m_pDS->eof())
7065       {
7066         int lYear = 0;
7067         std::string strLabel = m_pDS->fv(0).get_asString();
7068         if (strLabel.size() == 4)
7069           lYear = m_pDS->fv(0).get_asInt();
7070         else
7071         {
7072           CDateTime time;
7073           time.SetFromDateString(strLabel);
7074           if (time.IsValid())
7075           {
7076             lYear = time.GetYear();
7077             strLabel = StringUtils::Format("%i", lYear);
7078           }
7079         }
7080         if (lYear == 0)
7081         {
7082           m_pDS->next();
7083           continue;
7084         }
7085         CFileItemPtr pItem(new CFileItem(strLabel));
7086 
7087         CVideoDbUrl itemUrl = videoUrl;
7088         std::string path = StringUtils::Format("%i/", lYear);
7089         itemUrl.AppendPath(path);
7090         pItem->SetPath(itemUrl.ToString());
7091 
7092         pItem->m_bIsFolder=true;
7093         if (idContent == VIDEODB_CONTENT_MOVIES || idContent == VIDEODB_CONTENT_MUSICVIDEOS)
7094         {
7095           // fv(2) is the number of videos watched, fv(1) is the total number.  We set the playcount
7096           // only if the number of videos watched is equal to the total number (i.e. every video watched)
7097           pItem->GetVideoInfoTag()->SetPlayCount((m_pDS->fv(2).get_asInt() == m_pDS->fv(1).get_asInt()) ? 1 : 0);
7098         }
7099 
7100         // take care of dupes ..
7101         if (!items.Contains(pItem->GetPath()))
7102           items.Add(pItem);
7103 
7104         m_pDS->next();
7105       }
7106       m_pDS->close();
7107     }
7108 
7109     return true;
7110   }
7111   catch (...)
7112   {
7113     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
7114   }
7115   return false;
7116 }
7117 
GetSeasonsNav(const std::string & strBaseDir,CFileItemList & items,int idActor,int idDirector,int idGenre,int idYear,int idShow,bool getLinkedMovies)7118 bool CVideoDatabase::GetSeasonsNav(const std::string& strBaseDir, CFileItemList& items, int idActor, int idDirector, int idGenre, int idYear, int idShow, bool getLinkedMovies /* = true */)
7119 {
7120   // parse the base path to get additional filters
7121   CVideoDbUrl videoUrl;
7122   if (!videoUrl.FromString(strBaseDir))
7123     return false;
7124 
7125   if (idShow != -1)
7126     videoUrl.AddOption("tvshowid", idShow);
7127   if (idActor != -1)
7128     videoUrl.AddOption("actorid", idActor);
7129   else if (idDirector != -1)
7130     videoUrl.AddOption("directorid", idDirector);
7131   else if (idGenre != -1)
7132     videoUrl.AddOption("genreid", idGenre);
7133   else if (idYear != -1)
7134     videoUrl.AddOption("year", idYear);
7135 
7136   if (!GetSeasonsByWhere(videoUrl.ToString(), Filter(), items, false))
7137     return false;
7138 
7139   // now add any linked movies
7140   if (getLinkedMovies && idShow != -1)
7141   {
7142     Filter movieFilter;
7143     movieFilter.join = PrepareSQL("join movielinktvshow on movielinktvshow.idMovie=movie_view.idMovie");
7144     movieFilter.where = PrepareSQL("movielinktvshow.idShow = %i", idShow);
7145     CFileItemList movieItems;
7146     GetMoviesByWhere("videodb://movies/titles/", movieFilter, movieItems);
7147 
7148     if (movieItems.Size() > 0)
7149       items.Append(movieItems);
7150   }
7151 
7152   return true;
7153 }
7154 
GetSeasonsByWhere(const std::string & strBaseDir,const Filter & filter,CFileItemList & items,bool appendFullShowPath,const SortDescription & sortDescription)7155 bool CVideoDatabase::GetSeasonsByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, bool appendFullShowPath /* = true */, const SortDescription &sortDescription /* = SortDescription() */)
7156 {
7157   try
7158   {
7159     if (nullptr == m_pDB)
7160       return false;
7161     if (nullptr == m_pDS)
7162       return false;
7163 
7164     int total = -1;
7165 
7166     std::string strSQL = "SELECT %s FROM season_view ";
7167     CVideoDbUrl videoUrl;
7168     std::string strSQLExtra;
7169     Filter extFilter = filter;
7170     SortDescription sorting = sortDescription;
7171     if (!BuildSQL(strBaseDir, strSQLExtra, extFilter, strSQLExtra, videoUrl, sorting))
7172       return false;
7173 
7174     // Apply the limiting directly here if there's no special sorting but limiting
7175     if (extFilter.limit.empty() &&
7176       sorting.sortBy == SortByNone &&
7177       (sorting.limitStart > 0 || sorting.limitEnd > 0))
7178     {
7179       total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
7180       strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
7181     }
7182 
7183     strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
7184 
7185     int iRowsFound = RunQuery(strSQL);
7186     if (iRowsFound <= 0)
7187       return iRowsFound == 0;
7188 
7189     // store the total value of items as a property
7190     if (total < iRowsFound)
7191       total = iRowsFound;
7192     items.SetProperty("total", total);
7193 
7194     std::set<std::pair<int, int>> mapSeasons;
7195     while (!m_pDS->eof())
7196     {
7197       int id = m_pDS->fv(VIDEODB_ID_SEASON_ID).get_asInt();
7198       int showId = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_ID).get_asInt();
7199       int iSeason = m_pDS->fv(VIDEODB_ID_SEASON_NUMBER).get_asInt();
7200       std::string name = m_pDS->fv(VIDEODB_ID_SEASON_NAME).get_asString();
7201       std::string path = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_PATH).get_asString();
7202 
7203       if (mapSeasons.find(std::make_pair(showId, iSeason)) == mapSeasons.end() &&
7204          (m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE || g_passwordManager.bMasterUser ||
7205           g_passwordManager.IsDatabasePathUnlocked(path, *CMediaSourceSettings::GetInstance().GetSources("video"))))
7206       {
7207         mapSeasons.insert(std::make_pair(showId, iSeason));
7208 
7209         std::string strLabel = name;
7210         if (strLabel.empty())
7211         {
7212           if (iSeason == 0)
7213             strLabel = g_localizeStrings.Get(20381);
7214           else
7215             strLabel = StringUtils::Format(g_localizeStrings.Get(20358).c_str(), iSeason);
7216         }
7217         CFileItemPtr pItem(new CFileItem(strLabel));
7218 
7219         CVideoDbUrl itemUrl = videoUrl;
7220         std::string strDir;
7221         if (appendFullShowPath)
7222           strDir += StringUtils::Format("%d/", showId);
7223         strDir += StringUtils::Format("%d/", iSeason);
7224         itemUrl.AppendPath(strDir);
7225         pItem->SetPath(itemUrl.ToString());
7226 
7227         pItem->m_bIsFolder = true;
7228         pItem->GetVideoInfoTag()->m_strTitle = strLabel;
7229         if (!name.empty())
7230           pItem->GetVideoInfoTag()->m_strSortTitle = name;
7231         pItem->GetVideoInfoTag()->m_iSeason = iSeason;
7232         pItem->GetVideoInfoTag()->m_iDbId = id;
7233         pItem->GetVideoInfoTag()->m_iIdSeason = id;
7234         pItem->GetVideoInfoTag()->m_type = MediaTypeSeason;
7235         pItem->GetVideoInfoTag()->m_strPath = path;
7236         pItem->GetVideoInfoTag()->m_strShowTitle = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_TITLE).get_asString();
7237         pItem->GetVideoInfoTag()->m_strPlot = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_PLOT).get_asString();
7238         pItem->GetVideoInfoTag()->SetPremieredFromDBDate(m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_PREMIERED).get_asString());
7239         pItem->GetVideoInfoTag()->m_firstAired.SetFromDBDate(m_pDS->fv(VIDEODB_ID_SEASON_PREMIERED).get_asString());
7240         pItem->GetVideoInfoTag()->m_iUserRating = m_pDS->fv(VIDEODB_ID_SEASON_USER_RATING).get_asInt();
7241         // season premiered date based on first episode airdate associated to the season
7242         // tvshow premiered date is used as a fallback
7243         if (pItem->GetVideoInfoTag()->m_firstAired.IsValid())
7244           pItem->GetVideoInfoTag()->SetPremiered(pItem->GetVideoInfoTag()->m_firstAired);
7245         else if (pItem->GetVideoInfoTag()->HasPremiered())
7246           pItem->GetVideoInfoTag()->SetPremiered(pItem->GetVideoInfoTag()->GetPremiered());
7247         else if (pItem->GetVideoInfoTag()->HasYear())
7248           pItem->GetVideoInfoTag()->SetYear(pItem->GetVideoInfoTag()->GetYear());
7249         pItem->GetVideoInfoTag()->m_genre = StringUtils::Split(m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_GENRE).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
7250         pItem->GetVideoInfoTag()->m_studio = StringUtils::Split(m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_STUDIO).get_asString(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator);
7251         pItem->GetVideoInfoTag()->m_strMPAARating = m_pDS->fv(VIDEODB_ID_SEASON_TVSHOW_MPAA).get_asString();
7252         pItem->GetVideoInfoTag()->m_iIdShow = showId;
7253 
7254         int totalEpisodes = m_pDS->fv(VIDEODB_ID_SEASON_EPISODES_TOTAL).get_asInt();
7255         int watchedEpisodes = m_pDS->fv(VIDEODB_ID_SEASON_EPISODES_WATCHED).get_asInt();
7256         pItem->GetVideoInfoTag()->m_iEpisode = totalEpisodes;
7257         pItem->SetProperty("totalepisodes", totalEpisodes);
7258         pItem->SetProperty("numepisodes", totalEpisodes); // will be changed later to reflect watchmode setting
7259         pItem->SetProperty("watchedepisodes", watchedEpisodes);
7260         pItem->SetProperty("unwatchedepisodes", totalEpisodes - watchedEpisodes);
7261         if (iSeason == 0)
7262           pItem->SetProperty("isspecial", true);
7263         pItem->GetVideoInfoTag()->SetPlayCount((totalEpisodes == watchedEpisodes) ? 1 : 0);
7264         pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, (pItem->GetVideoInfoTag()->GetPlayCount() > 0) && (pItem->GetVideoInfoTag()->m_iEpisode > 0));
7265 
7266         items.Add(pItem);
7267       }
7268 
7269       m_pDS->next();
7270     }
7271     m_pDS->close();
7272 
7273     return true;
7274   }
7275   catch (...)
7276   {
7277     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
7278   }
7279   return false;
7280 }
7281 
GetSortedVideos(const MediaType & mediaType,const std::string & strBaseDir,const SortDescription & sortDescription,CFileItemList & items,const Filter & filter)7282 bool CVideoDatabase::GetSortedVideos(const MediaType &mediaType, const std::string& strBaseDir, const SortDescription &sortDescription, CFileItemList& items, const Filter &filter /* = Filter() */)
7283 {
7284   if (nullptr == m_pDB || nullptr == m_pDS)
7285     return false;
7286 
7287   if (mediaType != MediaTypeMovie && mediaType != MediaTypeTvShow && mediaType != MediaTypeEpisode && mediaType != MediaTypeMusicVideo)
7288     return false;
7289 
7290   SortDescription sorting = sortDescription;
7291   if (sortDescription.sortBy == SortByFile ||
7292       sortDescription.sortBy == SortByTitle ||
7293       sortDescription.sortBy == SortBySortTitle ||
7294       sortDescription.sortBy == SortByLabel ||
7295       sortDescription.sortBy == SortByDateAdded ||
7296       sortDescription.sortBy == SortByRating ||
7297       sortDescription.sortBy == SortByUserRating ||
7298       sortDescription.sortBy == SortByYear ||
7299       sortDescription.sortBy == SortByLastPlayed ||
7300       sortDescription.sortBy == SortByPlaycount)
7301     sorting.sortAttributes = (SortAttribute)(sortDescription.sortAttributes | SortAttributeIgnoreFolders);
7302 
7303   bool success = false;
7304   if (mediaType == MediaTypeMovie)
7305     success = GetMoviesByWhere(strBaseDir, filter, items, sorting);
7306   else if (mediaType == MediaTypeTvShow)
7307     success = GetTvShowsByWhere(strBaseDir, filter, items, sorting);
7308   else if (mediaType == MediaTypeEpisode)
7309     success = GetEpisodesByWhere(strBaseDir, filter, items, true, sorting);
7310   else if (mediaType == MediaTypeMusicVideo)
7311     success = GetMusicVideosByWhere(strBaseDir, filter, items, true, sorting);
7312   else
7313     return false;
7314 
7315   items.SetContent(CMediaTypes::ToPlural(mediaType));
7316   return success;
7317 }
7318 
GetItems(const std::string & strBaseDir,CFileItemList & items,const Filter & filter,const SortDescription & sortDescription)7319 bool CVideoDatabase::GetItems(const std::string &strBaseDir, CFileItemList &items, const Filter &filter /* = Filter() */, const SortDescription &sortDescription /* = SortDescription() */)
7320 {
7321   CVideoDbUrl videoUrl;
7322   if (!videoUrl.FromString(strBaseDir))
7323     return false;
7324 
7325   return GetItems(strBaseDir, videoUrl.GetType(), videoUrl.GetItemType(), items, filter, sortDescription);
7326 }
7327 
GetItems(const std::string & strBaseDir,const std::string & mediaType,const std::string & itemType,CFileItemList & items,const Filter & filter,const SortDescription & sortDescription)7328 bool CVideoDatabase::GetItems(const std::string &strBaseDir, const std::string &mediaType, const std::string &itemType, CFileItemList &items, const Filter &filter /* = Filter() */, const SortDescription &sortDescription /* = SortDescription() */)
7329 {
7330   VIDEODB_CONTENT_TYPE contentType;
7331   if (StringUtils::EqualsNoCase(mediaType, "movies"))
7332     contentType = VIDEODB_CONTENT_MOVIES;
7333   else if (StringUtils::EqualsNoCase(mediaType, "tvshows"))
7334   {
7335     if (StringUtils::EqualsNoCase(itemType, "episodes"))
7336       contentType = VIDEODB_CONTENT_EPISODES;
7337     else
7338       contentType = VIDEODB_CONTENT_TVSHOWS;
7339   }
7340   else if (StringUtils::EqualsNoCase(mediaType, "musicvideos"))
7341     contentType = VIDEODB_CONTENT_MUSICVIDEOS;
7342   else
7343     return false;
7344 
7345   return GetItems(strBaseDir, contentType, itemType, items, filter, sortDescription);
7346 }
7347 
GetItems(const std::string & strBaseDir,VIDEODB_CONTENT_TYPE mediaType,const std::string & itemType,CFileItemList & items,const Filter & filter,const SortDescription & sortDescription)7348 bool CVideoDatabase::GetItems(const std::string &strBaseDir, VIDEODB_CONTENT_TYPE mediaType, const std::string &itemType, CFileItemList &items, const Filter &filter /* = Filter() */, const SortDescription &sortDescription /* = SortDescription() */)
7349 {
7350   if (StringUtils::EqualsNoCase(itemType, "movies") && (mediaType == VIDEODB_CONTENT_MOVIES || mediaType == VIDEODB_CONTENT_MOVIE_SETS))
7351     return GetMoviesByWhere(strBaseDir, filter, items, sortDescription);
7352   else if (StringUtils::EqualsNoCase(itemType, "tvshows") && mediaType == VIDEODB_CONTENT_TVSHOWS)
7353   {
7354     Filter extFilter = filter;
7355     if (!CServiceBroker::GetSettingsComponent()->GetSettings()->
7356         GetBool(CSettings::SETTING_VIDEOLIBRARY_SHOWEMPTYTVSHOWS))
7357       extFilter.AppendWhere("totalCount IS NOT NULL AND totalCount > 0");
7358     return GetTvShowsByWhere(strBaseDir, extFilter, items, sortDescription);
7359   }
7360   else if (StringUtils::EqualsNoCase(itemType, "musicvideos") && mediaType == VIDEODB_CONTENT_MUSICVIDEOS)
7361     return GetMusicVideosByWhere(strBaseDir, filter, items, true, sortDescription);
7362   else if (StringUtils::EqualsNoCase(itemType, "episodes") && mediaType == VIDEODB_CONTENT_EPISODES)
7363     return GetEpisodesByWhere(strBaseDir, filter, items, true, sortDescription);
7364   else if (StringUtils::EqualsNoCase(itemType, "seasons") && mediaType == VIDEODB_CONTENT_TVSHOWS)
7365     return GetSeasonsNav(strBaseDir, items);
7366   else if (StringUtils::EqualsNoCase(itemType, "genres"))
7367     return GetGenresNav(strBaseDir, items, mediaType, filter);
7368   else if (StringUtils::EqualsNoCase(itemType, "years"))
7369     return GetYearsNav(strBaseDir, items, mediaType, filter);
7370   else if (StringUtils::EqualsNoCase(itemType, "actors"))
7371     return GetActorsNav(strBaseDir, items, mediaType, filter);
7372   else if (StringUtils::EqualsNoCase(itemType, "directors"))
7373     return GetDirectorsNav(strBaseDir, items, mediaType, filter);
7374   else if (StringUtils::EqualsNoCase(itemType, "writers"))
7375     return GetWritersNav(strBaseDir, items, mediaType, filter);
7376   else if (StringUtils::EqualsNoCase(itemType, "studios"))
7377     return GetStudiosNav(strBaseDir, items, mediaType, filter);
7378   else if (StringUtils::EqualsNoCase(itemType, "sets"))
7379     return GetSetsNav(strBaseDir, items, mediaType, filter, !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOLIBRARY_GROUPSINGLEITEMSETS));
7380   else if (StringUtils::EqualsNoCase(itemType, "countries"))
7381     return GetCountriesNav(strBaseDir, items, mediaType, filter);
7382   else if (StringUtils::EqualsNoCase(itemType, "tags"))
7383     return GetTagsNav(strBaseDir, items, mediaType, filter);
7384   else if (StringUtils::EqualsNoCase(itemType, "artists") && mediaType == VIDEODB_CONTENT_MUSICVIDEOS)
7385     return GetActorsNav(strBaseDir, items, mediaType, filter);
7386   else if (StringUtils::EqualsNoCase(itemType, "albums") && mediaType == VIDEODB_CONTENT_MUSICVIDEOS)
7387     return GetMusicVideoAlbumsNav(strBaseDir, items, -1, filter);
7388 
7389   return false;
7390 }
7391 
GetItemById(const std::string & itemType,int id)7392 std::string CVideoDatabase::GetItemById(const std::string &itemType, int id)
7393 {
7394   if (StringUtils::EqualsNoCase(itemType, "genres"))
7395     return GetGenreById(id);
7396   else if (StringUtils::EqualsNoCase(itemType, "years"))
7397     return StringUtils::Format("%d", id);
7398   else if (StringUtils::EqualsNoCase(itemType, "actors") ||
7399            StringUtils::EqualsNoCase(itemType, "directors") ||
7400            StringUtils::EqualsNoCase(itemType, "artists"))
7401     return GetPersonById(id);
7402   else if (StringUtils::EqualsNoCase(itemType, "studios"))
7403     return GetStudioById(id);
7404   else if (StringUtils::EqualsNoCase(itemType, "sets"))
7405     return GetSetById(id);
7406   else if (StringUtils::EqualsNoCase(itemType, "countries"))
7407     return GetCountryById(id);
7408   else if (StringUtils::EqualsNoCase(itemType, "tags"))
7409     return GetTagById(id);
7410   else if (StringUtils::EqualsNoCase(itemType, "albums"))
7411     return GetMusicVideoAlbumById(id);
7412 
7413   return "";
7414 }
7415 
GetMoviesNav(const std::string & strBaseDir,CFileItemList & items,int idGenre,int idYear,int idActor,int idDirector,int idStudio,int idCountry,int idSet,int idTag,const SortDescription & sortDescription,int getDetails)7416 bool CVideoDatabase::GetMoviesNav(const std::string& strBaseDir, CFileItemList& items,
7417                                   int idGenre /* = -1 */, int idYear /* = -1 */, int idActor /* = -1 */, int idDirector /* = -1 */,
7418                                   int idStudio /* = -1 */, int idCountry /* = -1 */, int idSet /* = -1 */, int idTag /* = -1 */,
7419                                   const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
7420 {
7421   CVideoDbUrl videoUrl;
7422   if (!videoUrl.FromString(strBaseDir))
7423     return false;
7424 
7425   if (idGenre > 0)
7426     videoUrl.AddOption("genreid", idGenre);
7427   else if (idCountry > 0)
7428     videoUrl.AddOption("countryid", idCountry);
7429   else if (idStudio > 0)
7430     videoUrl.AddOption("studioid", idStudio);
7431   else if (idDirector > 0)
7432     videoUrl.AddOption("directorid", idDirector);
7433   else if (idYear > 0)
7434     videoUrl.AddOption("year", idYear);
7435   else if (idActor > 0)
7436     videoUrl.AddOption("actorid", idActor);
7437   else if (idSet > 0)
7438     videoUrl.AddOption("setid", idSet);
7439   else if (idTag > 0)
7440     videoUrl.AddOption("tagid", idTag);
7441 
7442   Filter filter;
7443   return GetMoviesByWhere(videoUrl.ToString(), filter, items, sortDescription, getDetails);
7444 }
7445 
GetMoviesByWhere(const std::string & strBaseDir,const Filter & filter,CFileItemList & items,const SortDescription & sortDescription,int getDetails)7446 bool CVideoDatabase::GetMoviesByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
7447 {
7448   try
7449   {
7450     movieTime = 0;
7451     castTime = 0;
7452 
7453     if (nullptr == m_pDB)
7454       return false;
7455     if (nullptr == m_pDS)
7456       return false;
7457 
7458     // parse the base path to get additional filters
7459     CVideoDbUrl videoUrl;
7460     Filter extFilter = filter;
7461     SortDescription sorting = sortDescription;
7462     if (!videoUrl.FromString(strBaseDir) || !GetFilter(videoUrl, extFilter, sorting))
7463       return false;
7464 
7465     int total = -1;
7466 
7467     std::string strSQL = "select %s from movie_view ";
7468     std::string strSQLExtra;
7469     if (!CDatabase::BuildSQL(strSQLExtra, extFilter, strSQLExtra))
7470       return false;
7471 
7472     // Apply the limiting directly here if there's no special sorting but limiting
7473     if (extFilter.limit.empty() &&
7474         sorting.sortBy == SortByNone &&
7475        (sorting.limitStart > 0 || sorting.limitEnd > 0))
7476     {
7477       total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
7478       strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
7479     }
7480 
7481     strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
7482 
7483     int iRowsFound = RunQuery(strSQL);
7484     if (iRowsFound <= 0)
7485       return iRowsFound == 0;
7486 
7487     // store the total value of items as a property
7488     if (total < iRowsFound)
7489       total = iRowsFound;
7490     items.SetProperty("total", total);
7491 
7492     DatabaseResults results;
7493     results.reserve(iRowsFound);
7494 
7495     if (!SortUtils::SortFromDataset(sortDescription, MediaTypeMovie, m_pDS, results))
7496       return false;
7497 
7498     // get data from returned rows
7499     items.Reserve(results.size());
7500     const query_data &data = m_pDS->get_result_set().records;
7501     for (const auto &i : results)
7502     {
7503       unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger();
7504       const dbiplus::sql_record* const record = data.at(targetRow);
7505 
7506       CVideoInfoTag movie = GetDetailsForMovie(record, getDetails);
7507       if (m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE ||
7508           g_passwordManager.bMasterUser                                   ||
7509           g_passwordManager.IsDatabasePathUnlocked(movie.m_strPath, *CMediaSourceSettings::GetInstance().GetSources("video")))
7510       {
7511         CFileItemPtr pItem(new CFileItem(movie));
7512 
7513         CVideoDbUrl itemUrl = videoUrl;
7514         std::string path = StringUtils::Format("%i", movie.m_iDbId);
7515         itemUrl.AppendPath(path);
7516         pItem->SetPath(itemUrl.ToString());
7517         pItem->SetDynPath(movie.m_strFileNameAndPath);
7518 
7519         pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED,movie.GetPlayCount() > 0);
7520         items.Add(pItem);
7521       }
7522     }
7523 
7524     // cleanup
7525     m_pDS->close();
7526     return true;
7527   }
7528   catch (...)
7529   {
7530     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
7531   }
7532   return false;
7533 }
7534 
GetTvShowsNav(const std::string & strBaseDir,CFileItemList & items,int idGenre,int idYear,int idActor,int idDirector,int idStudio,int idTag,const SortDescription & sortDescription,int getDetails)7535 bool CVideoDatabase::GetTvShowsNav(const std::string& strBaseDir, CFileItemList& items,
7536                                   int idGenre /* = -1 */, int idYear /* = -1 */, int idActor /* = -1 */, int idDirector /* = -1 */, int idStudio /* = -1 */, int idTag /* = -1 */,
7537                                   const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
7538 {
7539   CVideoDbUrl videoUrl;
7540   if (!videoUrl.FromString(strBaseDir))
7541     return false;
7542 
7543   if (idGenre != -1)
7544     videoUrl.AddOption("genreid", idGenre);
7545   else if (idStudio != -1)
7546     videoUrl.AddOption("studioid", idStudio);
7547   else if (idDirector != -1)
7548     videoUrl.AddOption("directorid", idDirector);
7549   else if (idYear != -1)
7550     videoUrl.AddOption("year", idYear);
7551   else if (idActor != -1)
7552     videoUrl.AddOption("actorid", idActor);
7553   else if (idTag != -1)
7554     videoUrl.AddOption("tagid", idTag);
7555 
7556   Filter filter;
7557   if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOLIBRARY_SHOWEMPTYTVSHOWS))
7558     filter.AppendWhere("totalCount IS NOT NULL AND totalCount > 0");
7559   return GetTvShowsByWhere(videoUrl.ToString(), filter, items, sortDescription, getDetails);
7560 }
7561 
GetTvShowsByWhere(const std::string & strBaseDir,const Filter & filter,CFileItemList & items,const SortDescription & sortDescription,int getDetails)7562 bool CVideoDatabase::GetTvShowsByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
7563 {
7564   try
7565   {
7566     movieTime = 0;
7567 
7568     if (nullptr == m_pDB)
7569       return false;
7570     if (nullptr == m_pDS)
7571       return false;
7572 
7573     int total = -1;
7574 
7575     std::string strSQL = "SELECT %s FROM tvshow_view ";
7576     CVideoDbUrl videoUrl;
7577     std::string strSQLExtra;
7578     Filter extFilter = filter;
7579     SortDescription sorting = sortDescription;
7580     if (!BuildSQL(strBaseDir, strSQLExtra, extFilter, strSQLExtra, videoUrl, sorting))
7581       return false;
7582 
7583     // Apply the limiting directly here if there's no special sorting but limiting
7584     if (extFilter.limit.empty() &&
7585         sorting.sortBy == SortByNone &&
7586         (sorting.limitStart > 0 || sorting.limitEnd > 0))
7587     {
7588       total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
7589       strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
7590     }
7591 
7592     strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
7593 
7594     int iRowsFound = RunQuery(strSQL);
7595     if (iRowsFound <= 0)
7596       return iRowsFound == 0;
7597 
7598     // store the total value of items as a property
7599     if (total < iRowsFound)
7600       total = iRowsFound;
7601     items.SetProperty("total", total);
7602 
7603     DatabaseResults results;
7604     results.reserve(iRowsFound);
7605     if (!SortUtils::SortFromDataset(sorting, MediaTypeTvShow, m_pDS, results))
7606       return false;
7607 
7608     // get data from returned rows
7609     items.Reserve(results.size());
7610     const query_data &data = m_pDS->get_result_set().records;
7611     for (const auto &i : results)
7612     {
7613       unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger();
7614       const dbiplus::sql_record* const record = data.at(targetRow);
7615 
7616       CFileItemPtr pItem(new CFileItem());
7617       CVideoInfoTag movie = GetDetailsForTvShow(record, getDetails, pItem.get());
7618       if (m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE ||
7619            g_passwordManager.bMasterUser                                     ||
7620            g_passwordManager.IsDatabasePathUnlocked(movie.m_strPath, *CMediaSourceSettings::GetInstance().GetSources("video")))
7621       {
7622         pItem->SetFromVideoInfoTag(movie);
7623 
7624         CVideoDbUrl itemUrl = videoUrl;
7625         std::string path = StringUtils::Format("%i/", record->at(0).get_asInt());
7626         itemUrl.AppendPath(path);
7627         pItem->SetPath(itemUrl.ToString());
7628 
7629         pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, (pItem->GetVideoInfoTag()->GetPlayCount() > 0) && (pItem->GetVideoInfoTag()->m_iEpisode > 0));
7630         items.Add(pItem);
7631       }
7632     }
7633 
7634     // cleanup
7635     m_pDS->close();
7636     return true;
7637   }
7638   catch (...)
7639   {
7640     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
7641   }
7642   return false;
7643 }
7644 
GetEpisodesNav(const std::string & strBaseDir,CFileItemList & items,int idGenre,int idYear,int idActor,int idDirector,int idShow,int idSeason,const SortDescription & sortDescription,int getDetails)7645 bool CVideoDatabase::GetEpisodesNav(const std::string& strBaseDir, CFileItemList& items, int idGenre, int idYear, int idActor, int idDirector, int idShow, int idSeason, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
7646 {
7647   CVideoDbUrl videoUrl;
7648   if (!videoUrl.FromString(strBaseDir))
7649     return false;
7650 
7651   std::string strIn;
7652   if (idShow != -1)
7653   {
7654     videoUrl.AddOption("tvshowid", idShow);
7655     if (idSeason >= 0)
7656       videoUrl.AddOption("season", idSeason);
7657 
7658     if (idGenre != -1)
7659       videoUrl.AddOption("genreid", idGenre);
7660     else if (idYear !=-1)
7661       videoUrl.AddOption("year", idYear);
7662     else if (idActor != -1)
7663       videoUrl.AddOption("actorid", idActor);
7664   }
7665   else if (idYear != -1)
7666     videoUrl.AddOption("year", idYear);
7667 
7668   if (idDirector != -1)
7669     videoUrl.AddOption("directorid", idDirector);
7670 
7671   Filter filter;
7672   bool ret = GetEpisodesByWhere(videoUrl.ToString(), filter, items, false, sortDescription, getDetails);
7673 
7674   if (idSeason == -1 && idShow != -1)
7675   { // add any linked movies
7676     Filter movieFilter;
7677     movieFilter.join  = PrepareSQL("join movielinktvshow on movielinktvshow.idMovie=movie_view.idMovie");
7678     movieFilter.where = PrepareSQL("movielinktvshow.idShow = %i", idShow);
7679     CFileItemList movieItems;
7680     GetMoviesByWhere("videodb://movies/titles/", movieFilter, movieItems);
7681 
7682     if (movieItems.Size() > 0)
7683       items.Append(movieItems);
7684   }
7685 
7686   return ret;
7687 }
7688 
GetEpisodesByWhere(const std::string & strBaseDir,const Filter & filter,CFileItemList & items,bool appendFullShowPath,const SortDescription & sortDescription,int getDetails)7689 bool CVideoDatabase::GetEpisodesByWhere(const std::string& strBaseDir, const Filter &filter, CFileItemList& items, bool appendFullShowPath /* = true */, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
7690 {
7691   try
7692   {
7693     movieTime = 0;
7694     castTime = 0;
7695 
7696     if (nullptr == m_pDB)
7697       return false;
7698     if (nullptr == m_pDS)
7699       return false;
7700 
7701     int total = -1;
7702 
7703     std::string strSQL = "select %s from episode_view ";
7704     CVideoDbUrl videoUrl;
7705     std::string strSQLExtra;
7706     Filter extFilter = filter;
7707     SortDescription sorting = sortDescription;
7708     if (!BuildSQL(strBaseDir, strSQLExtra, extFilter, strSQLExtra, videoUrl, sorting))
7709       return false;
7710 
7711     // Apply the limiting directly here if there's no special sorting but limiting
7712     if (extFilter.limit.empty() &&
7713       sorting.sortBy == SortByNone &&
7714       (sorting.limitStart > 0 || sorting.limitEnd > 0))
7715     {
7716       total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
7717       strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
7718     }
7719 
7720     strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
7721 
7722     int iRowsFound = RunQuery(strSQL);
7723     if (iRowsFound <= 0)
7724       return iRowsFound == 0;
7725 
7726     // store the total value of items as a property
7727     if (total < iRowsFound)
7728       total = iRowsFound;
7729     items.SetProperty("total", total);
7730 
7731     DatabaseResults results;
7732     results.reserve(iRowsFound);
7733     if (!SortUtils::SortFromDataset(sorting, MediaTypeEpisode, m_pDS, results))
7734       return false;
7735 
7736     // get data from returned rows
7737     items.Reserve(results.size());
7738     CLabelFormatter formatter("%H. %T", "");
7739 
7740     const query_data &data = m_pDS->get_result_set().records;
7741     for (const auto &i : results)
7742     {
7743       unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger();
7744       const dbiplus::sql_record* const record = data.at(targetRow);
7745 
7746       CVideoInfoTag episode = GetDetailsForEpisode(record, getDetails);
7747       if (m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE ||
7748           g_passwordManager.bMasterUser                                     ||
7749           g_passwordManager.IsDatabasePathUnlocked(episode.m_strPath, *CMediaSourceSettings::GetInstance().GetSources("video")))
7750       {
7751         CFileItemPtr pItem(new CFileItem(episode));
7752         formatter.FormatLabel(pItem.get());
7753 
7754         int idEpisode = record->at(0).get_asInt();
7755 
7756         CVideoDbUrl itemUrl = videoUrl;
7757         std::string path;
7758         if (appendFullShowPath && videoUrl.GetItemType() != "episodes")
7759           path = StringUtils::Format("%i/%i/%i", record->at(VIDEODB_DETAILS_EPISODE_TVSHOW_ID).get_asInt(), episode.m_iSeason, idEpisode);
7760         else
7761           path = StringUtils::Format("%i", idEpisode);
7762         itemUrl.AppendPath(path);
7763         pItem->SetPath(itemUrl.ToString());
7764         pItem->SetDynPath(episode.m_strFileNameAndPath);
7765 
7766         pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, episode.GetPlayCount() > 0);
7767         pItem->m_dateTime = episode.m_firstAired;
7768         items.Add(pItem);
7769       }
7770     }
7771 
7772     // cleanup
7773     m_pDS->close();
7774     return true;
7775   }
7776   catch (...)
7777   {
7778     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
7779   }
7780   return false;
7781 }
7782 
GetMusicVideosNav(const std::string & strBaseDir,CFileItemList & items,int idGenre,int idYear,int idArtist,int idDirector,int idStudio,int idAlbum,int idTag,const SortDescription & sortDescription,int getDetails)7783 bool CVideoDatabase::GetMusicVideosNav(const std::string& strBaseDir, CFileItemList& items, int idGenre, int idYear, int idArtist, int idDirector, int idStudio, int idAlbum, int idTag /* = -1 */, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
7784 {
7785   CVideoDbUrl videoUrl;
7786   if (!videoUrl.FromString(strBaseDir))
7787     return false;
7788 
7789   if (idGenre != -1)
7790     videoUrl.AddOption("genreid", idGenre);
7791   else if (idStudio != -1)
7792     videoUrl.AddOption("studioid", idStudio);
7793   else if (idDirector != -1)
7794     videoUrl.AddOption("directorid", idDirector);
7795   else if (idYear !=-1)
7796     videoUrl.AddOption("year", idYear);
7797   else if (idArtist != -1)
7798     videoUrl.AddOption("artistid", idArtist);
7799   else if (idTag != -1)
7800     videoUrl.AddOption("tagid", idTag);
7801   if (idAlbum != -1)
7802     videoUrl.AddOption("albumid", idAlbum);
7803 
7804   Filter filter;
7805   return GetMusicVideosByWhere(videoUrl.ToString(), filter, items, true, sortDescription, getDetails);
7806 }
7807 
GetRecentlyAddedMoviesNav(const std::string & strBaseDir,CFileItemList & items,unsigned int limit,int getDetails)7808 bool CVideoDatabase::GetRecentlyAddedMoviesNav(const std::string& strBaseDir, CFileItemList& items, unsigned int limit /* = 0 */, int getDetails /* = VideoDbDetailsNone */)
7809 {
7810   Filter filter;
7811   filter.order = "dateAdded desc, idMovie desc";
7812   filter.limit = PrepareSQL("%u", limit ? limit : CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryRecentlyAddedItems);
7813   return GetMoviesByWhere(strBaseDir, filter, items, SortDescription(), getDetails);
7814 }
7815 
GetRecentlyAddedEpisodesNav(const std::string & strBaseDir,CFileItemList & items,unsigned int limit,int getDetails)7816 bool CVideoDatabase::GetRecentlyAddedEpisodesNav(const std::string& strBaseDir, CFileItemList& items, unsigned int limit /* = 0 */, int getDetails /* = VideoDbDetailsNone */)
7817 {
7818   Filter filter;
7819   filter.order = "dateAdded desc, idEpisode desc";
7820   filter.limit = PrepareSQL("%u", limit ? limit : CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryRecentlyAddedItems);
7821   return GetEpisodesByWhere(strBaseDir, filter, items, false, SortDescription(), getDetails);
7822 }
7823 
GetRecentlyAddedMusicVideosNav(const std::string & strBaseDir,CFileItemList & items,unsigned int limit,int getDetails)7824 bool CVideoDatabase::GetRecentlyAddedMusicVideosNav(const std::string& strBaseDir, CFileItemList& items, unsigned int limit /* = 0 */, int getDetails /* = VideoDbDetailsNone */)
7825 {
7826   Filter filter;
7827   filter.order = "dateAdded desc, idMVideo desc";
7828   filter.limit = PrepareSQL("%u", limit ? limit : CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iVideoLibraryRecentlyAddedItems);
7829   return GetMusicVideosByWhere(strBaseDir, filter, items, true, SortDescription(), getDetails);
7830 }
7831 
GetInProgressTvShowsNav(const std::string & strBaseDir,CFileItemList & items,unsigned int limit,int getDetails)7832 bool CVideoDatabase::GetInProgressTvShowsNav(const std::string& strBaseDir, CFileItemList& items, unsigned int limit /* = 0 */, int getDetails /* = VideoDbDetailsNone */)
7833 {
7834   Filter filter;
7835   filter.order = PrepareSQL("c%02d", VIDEODB_ID_TV_TITLE);
7836   filter.where = "watchedCount != 0 AND totalCount != watchedCount";
7837   return GetTvShowsByWhere(strBaseDir, filter, items, SortDescription(), getDetails);
7838 }
7839 
GetGenreById(int id)7840 std::string CVideoDatabase::GetGenreById(int id)
7841 {
7842   return GetSingleValue("genre", "name", PrepareSQL("genre_id=%i", id));
7843 }
7844 
GetCountryById(int id)7845 std::string CVideoDatabase::GetCountryById(int id)
7846 {
7847   return GetSingleValue("country", "name", PrepareSQL("country_id=%i", id));
7848 }
7849 
GetSetById(int id)7850 std::string CVideoDatabase::GetSetById(int id)
7851 {
7852   return GetSingleValue("sets", "strSet", PrepareSQL("idSet=%i", id));
7853 }
7854 
GetTagById(int id)7855 std::string CVideoDatabase::GetTagById(int id)
7856 {
7857   return GetSingleValue("tag", "name", PrepareSQL("tag_id = %i", id));
7858 }
7859 
GetPersonById(int id)7860 std::string CVideoDatabase::GetPersonById(int id)
7861 {
7862   return GetSingleValue("actor", "name", PrepareSQL("actor_id=%i", id));
7863 }
7864 
GetStudioById(int id)7865 std::string CVideoDatabase::GetStudioById(int id)
7866 {
7867   return GetSingleValue("studio", "name", PrepareSQL("studio_id=%i", id));
7868 }
7869 
GetTvShowTitleById(int id)7870 std::string CVideoDatabase::GetTvShowTitleById(int id)
7871 {
7872   return GetSingleValue("tvshow", PrepareSQL("c%02d", VIDEODB_ID_TV_TITLE), PrepareSQL("idShow=%i", id));
7873 }
7874 
GetMusicVideoAlbumById(int id)7875 std::string CVideoDatabase::GetMusicVideoAlbumById(int id)
7876 {
7877   return GetSingleValue("musicvideo", PrepareSQL("c%02d", VIDEODB_ID_MUSICVIDEO_ALBUM), PrepareSQL("idMVideo=%i", id));
7878 }
7879 
HasSets() const7880 bool CVideoDatabase::HasSets() const
7881 {
7882   try
7883   {
7884     if (nullptr == m_pDB)
7885       return false;
7886     if (nullptr == m_pDS)
7887       return false;
7888 
7889     m_pDS->query("SELECT movie_view.idSet,COUNT(1) AS c FROM movie_view "
7890                  "JOIN sets ON sets.idSet = movie_view.idSet "
7891                  "GROUP BY movie_view.idSet HAVING c>1");
7892 
7893     bool bResult = (m_pDS->num_rows() > 0);
7894     m_pDS->close();
7895     return bResult;
7896   }
7897   catch (...)
7898   {
7899     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
7900   }
7901   return false;
7902 }
7903 
GetTvShowForEpisode(int idEpisode)7904 int CVideoDatabase::GetTvShowForEpisode(int idEpisode)
7905 {
7906   try
7907   {
7908     if (nullptr == m_pDB)
7909       return false;
7910     if (nullptr == m_pDS2)
7911       return false;
7912 
7913     // make sure we use m_pDS2, as this is called in loops using m_pDS
7914     std::string strSQL=PrepareSQL("select idShow from episode where idEpisode=%i", idEpisode);
7915     m_pDS2->query( strSQL );
7916 
7917     int result=-1;
7918     if (!m_pDS2->eof())
7919       result=m_pDS2->fv(0).get_asInt();
7920     m_pDS2->close();
7921 
7922     return result;
7923   }
7924   catch (...)
7925   {
7926     CLog::Log(LOGERROR, "%s (%i) failed", __FUNCTION__, idEpisode);
7927   }
7928   return false;
7929 }
7930 
GetSeasonForEpisode(int idEpisode)7931 int CVideoDatabase::GetSeasonForEpisode(int idEpisode)
7932 {
7933   char column[5];
7934   sprintf(column, "c%0d", VIDEODB_ID_EPISODE_SEASON);
7935   std::string id = GetSingleValue("episode", column, PrepareSQL("idEpisode=%i", idEpisode));
7936   if (id.empty())
7937     return -1;
7938   return atoi(id.c_str());
7939 }
7940 
HasContent()7941 bool CVideoDatabase::HasContent()
7942 {
7943   return (HasContent(VIDEODB_CONTENT_MOVIES) ||
7944           HasContent(VIDEODB_CONTENT_TVSHOWS) ||
7945           HasContent(VIDEODB_CONTENT_MUSICVIDEOS));
7946 }
7947 
HasContent(VIDEODB_CONTENT_TYPE type)7948 bool CVideoDatabase::HasContent(VIDEODB_CONTENT_TYPE type)
7949 {
7950   bool result = false;
7951   try
7952   {
7953     if (nullptr == m_pDB)
7954       return false;
7955     if (nullptr == m_pDS)
7956       return false;
7957 
7958     std::string sql;
7959     if (type == VIDEODB_CONTENT_MOVIES)
7960       sql = "select count(1) from movie";
7961     else if (type == VIDEODB_CONTENT_TVSHOWS)
7962       sql = "select count(1) from tvshow";
7963     else if (type == VIDEODB_CONTENT_MUSICVIDEOS)
7964       sql = "select count(1) from musicvideo";
7965     m_pDS->query( sql );
7966 
7967     if (!m_pDS->eof())
7968       result = (m_pDS->fv(0).get_asInt() > 0);
7969 
7970     m_pDS->close();
7971   }
7972   catch (...)
7973   {
7974     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
7975   }
7976   return result;
7977 }
7978 
GetScraperForPath(const std::string & strPath)7979 ScraperPtr CVideoDatabase::GetScraperForPath( const std::string& strPath )
7980 {
7981   SScanSettings settings;
7982   return GetScraperForPath(strPath, settings);
7983 }
7984 
GetScraperForPath(const std::string & strPath,SScanSettings & settings)7985 ScraperPtr CVideoDatabase::GetScraperForPath(const std::string& strPath, SScanSettings& settings)
7986 {
7987   bool dummy;
7988   return GetScraperForPath(strPath, settings, dummy);
7989 }
7990 
GetScraperForPath(const std::string & strPath,SScanSettings & settings,bool & foundDirectly)7991 ScraperPtr CVideoDatabase::GetScraperForPath(const std::string& strPath, SScanSettings& settings, bool& foundDirectly)
7992 {
7993   foundDirectly = false;
7994   try
7995   {
7996     if (strPath.empty() || !m_pDB || !m_pDS)
7997       return ScraperPtr();
7998 
7999     ScraperPtr scraper;
8000     std::string strPath2;
8001 
8002     if (URIUtils::IsMultiPath(strPath))
8003       strPath2 = CMultiPathDirectory::GetFirstPath(strPath);
8004     else
8005       strPath2 = strPath;
8006 
8007     std::string strPath1 = URIUtils::GetDirectory(strPath2);
8008     int idPath = GetPathId(strPath1);
8009 
8010     if (idPath > -1)
8011     {
8012       std::string strSQL = PrepareSQL(
8013           "SELECT path.strContent, path.strScraper, path.scanRecursive, path.useFolderNames, "
8014           "path.strSettings, path.noUpdate, path.exclude, path.allAudio FROM path WHERE idPath=%i",
8015           idPath);
8016       m_pDS->query( strSQL );
8017     }
8018 
8019     int iFound = 1;
8020     CONTENT_TYPE content = CONTENT_NONE;
8021     if (!m_pDS->eof())
8022     { // path is stored in db
8023 
8024       settings.m_allExtAudio = m_pDS->fv("path.allAudio").get_asBool();
8025 
8026       if (m_pDS->fv("path.exclude").get_asBool())
8027       {
8028         settings.exclude = true;
8029         m_pDS->close();
8030         return ScraperPtr();
8031       }
8032       settings.exclude = false;
8033 
8034       // try and ascertain scraper for this path
8035       std::string strcontent = m_pDS->fv("path.strContent").get_asString();
8036       StringUtils::ToLower(strcontent);
8037       content = TranslateContent(strcontent);
8038 
8039       //FIXME paths stored should not have empty strContent
8040       //assert(content != CONTENT_NONE);
8041       std::string scraperID = m_pDS->fv("path.strScraper").get_asString();
8042 
8043       AddonPtr addon;
8044       if (!scraperID.empty() &&
8045           CServiceBroker::GetAddonMgr().GetAddon(scraperID, addon, ADDON::ADDON_UNKNOWN,
8046                                                  ADDON::OnlyEnabled::YES))
8047       {
8048         scraper = std::dynamic_pointer_cast<CScraper>(addon);
8049         if (!scraper)
8050           return ScraperPtr();
8051 
8052         // store this path's content & settings
8053         scraper->SetPathSettings(content, m_pDS->fv("path.strSettings").get_asString());
8054         settings.parent_name = m_pDS->fv("path.useFolderNames").get_asBool();
8055         settings.recurse = m_pDS->fv("path.scanRecursive").get_asInt();
8056         settings.noupdate = m_pDS->fv("path.noUpdate").get_asBool();
8057       }
8058     }
8059 
8060     if (content == CONTENT_NONE)
8061     { // this path is not saved in db
8062       // we must drill up until a scraper is configured
8063       std::string strParent;
8064       while (URIUtils::GetParentPath(strPath1, strParent))
8065       {
8066         iFound++;
8067 
8068         std::string strSQL =
8069             PrepareSQL("SELECT path.strContent, path.strScraper, path.scanRecursive, "
8070                        "path.useFolderNames, path.strSettings, path.noUpdate, path.exclude, "
8071                        "path.allAudio FROM path WHERE strPath='%s'",
8072                        strParent.c_str());
8073         m_pDS->query(strSQL);
8074 
8075         CONTENT_TYPE content = CONTENT_NONE;
8076         if (!m_pDS->eof())
8077         {
8078           settings.m_allExtAudio = m_pDS->fv("path.allAudio").get_asBool();
8079           std::string strcontent = m_pDS->fv("path.strContent").get_asString();
8080           StringUtils::ToLower(strcontent);
8081           if (m_pDS->fv("path.exclude").get_asBool())
8082           {
8083             settings.exclude = true;
8084             scraper.reset();
8085             m_pDS->close();
8086             break;
8087           }
8088 
8089           content = TranslateContent(strcontent);
8090 
8091           AddonPtr addon;
8092           if (content != CONTENT_NONE && CServiceBroker::GetAddonMgr().GetAddon(
8093                                              m_pDS->fv("path.strScraper").get_asString(), addon,
8094                                              ADDON::ADDON_UNKNOWN, ADDON::OnlyEnabled::YES))
8095           {
8096             scraper = std::dynamic_pointer_cast<CScraper>(addon);
8097             scraper->SetPathSettings(content, m_pDS->fv("path.strSettings").get_asString());
8098             settings.parent_name = m_pDS->fv("path.useFolderNames").get_asBool();
8099             settings.recurse = m_pDS->fv("path.scanRecursive").get_asInt();
8100             settings.noupdate = m_pDS->fv("path.noUpdate").get_asBool();
8101             settings.exclude = false;
8102             break;
8103           }
8104         }
8105         strPath1 = strParent;
8106       }
8107     }
8108     m_pDS->close();
8109 
8110     if (!scraper || scraper->Content() == CONTENT_NONE)
8111       return ScraperPtr();
8112 
8113     if (scraper->Content() == CONTENT_TVSHOWS)
8114     {
8115       settings.recurse = 0;
8116       if(settings.parent_name) // single show
8117       {
8118         settings.parent_name_root = settings.parent_name = (iFound == 1);
8119       }
8120       else // show root
8121       {
8122         settings.parent_name_root = settings.parent_name = (iFound == 2);
8123       }
8124     }
8125     else if (scraper->Content() == CONTENT_MOVIES)
8126     {
8127       settings.recurse = settings.recurse - (iFound-1);
8128       settings.parent_name_root = settings.parent_name && (!settings.recurse || iFound > 1);
8129     }
8130     else if (scraper->Content() == CONTENT_MUSICVIDEOS)
8131     {
8132       settings.recurse = settings.recurse - (iFound-1);
8133       settings.parent_name_root = settings.parent_name && (!settings.recurse || iFound > 1);
8134     }
8135     else
8136     {
8137       iFound = 0;
8138       return ScraperPtr();
8139     }
8140     foundDirectly = (iFound == 1);
8141     return scraper;
8142   }
8143   catch (...)
8144   {
8145     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
8146   }
8147   return ScraperPtr();
8148 }
8149 
GetUseAllExternalAudioForVideo(const std::string & videoPath)8150 bool CVideoDatabase::GetUseAllExternalAudioForVideo(const std::string& videoPath)
8151 {
8152   // Find longest configured source path for given video path
8153   std::string strSQL = PrepareSQL("SELECT allAudio FROM path WHERE allAudio IS NOT NULL AND "
8154                                   "instr('%s', strPath) = 1 ORDER BY length(strPath) DESC LIMIT 1",
8155                                   videoPath.c_str());
8156   m_pDS->query(strSQL);
8157 
8158   if (!m_pDS->eof())
8159     return m_pDS->fv("allAudio").get_asBool();
8160 
8161   return false;
8162 }
8163 
GetContentForPath(const std::string & strPath)8164 std::string CVideoDatabase::GetContentForPath(const std::string& strPath)
8165 {
8166   SScanSettings settings;
8167   bool foundDirectly = false;
8168   ScraperPtr scraper = GetScraperForPath(strPath, settings, foundDirectly);
8169   if (scraper)
8170   {
8171     if (scraper->Content() == CONTENT_TVSHOWS)
8172     {
8173       // check for episodes or seasons.  Assumptions are:
8174       // 1. if episodes are in the path then we're in episodes.
8175       // 2. if no episodes are found, and content was set directly on this path, then we're in shows.
8176       // 3. if no episodes are found, and content was not set directly on this path, we're in seasons (assumes tvshows/seasons/episodes)
8177       std::string sql = "SELECT COUNT(*) FROM episode_view ";
8178 
8179       if (foundDirectly)
8180         sql += PrepareSQL("WHERE strPath = '%s'", strPath.c_str());
8181       else
8182         sql += PrepareSQL("WHERE strPath LIKE '%s%%'", strPath.c_str());
8183 
8184       m_pDS->query( sql );
8185       if (m_pDS->num_rows() && m_pDS->fv(0).get_asInt() > 0)
8186         return "episodes";
8187       return foundDirectly ? "tvshows" : "seasons";
8188     }
8189     return TranslateContent(scraper->Content());
8190   }
8191   return "";
8192 }
8193 
GetMovieGenresByName(const std::string & strSearch,CFileItemList & items)8194 void CVideoDatabase::GetMovieGenresByName(const std::string& strSearch, CFileItemList& items)
8195 {
8196   std::string strSQL;
8197 
8198   try
8199   {
8200     if (nullptr == m_pDB)
8201       return;
8202     if (nullptr == m_pDS)
8203       return;
8204 
8205     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8206       strSQL=PrepareSQL("SELECT genre.genre_id, genre.name, path.strPath FROM genre INNER JOIN genre_link ON genre_link.genre_id = genre.genre_id INNER JOIN movie ON (genre_link.media_type='movie' = genre_link.media_id=movie.idMovie) INNER JOIN files ON files.idFile=movie.idFile INNER JOIN path ON path.idPath=files.idPath WHERE genre.name LIKE '%%%s%%'",strSearch.c_str());
8207     else
8208       strSQL=PrepareSQL("SELECT DISTINCT genre.genre_id, genre.name FROM genre INNER JOIN genre_link ON genre_link.genre_id=genre.genre_id WHERE genre_link.media_type='movie' AND name LIKE '%%%s%%'", strSearch.c_str());
8209     m_pDS->query( strSQL );
8210 
8211     while (!m_pDS->eof())
8212     {
8213       if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8214         if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),
8215                                                       *CMediaSourceSettings::GetInstance().GetSources("video")))
8216         {
8217           m_pDS->next();
8218           continue;
8219         }
8220 
8221       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
8222       std::string strDir = StringUtils::Format("%i/", m_pDS->fv(0).get_asInt());
8223       pItem->SetPath("videodb://movies/genres/"+ strDir);
8224       pItem->m_bIsFolder=true;
8225       items.Add(pItem);
8226       m_pDS->next();
8227     }
8228     m_pDS->close();
8229   }
8230   catch (...)
8231   {
8232     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
8233   }
8234 }
8235 
GetMovieCountriesByName(const std::string & strSearch,CFileItemList & items)8236 void CVideoDatabase::GetMovieCountriesByName(const std::string& strSearch, CFileItemList& items)
8237 {
8238   std::string strSQL;
8239 
8240   try
8241   {
8242     if (nullptr == m_pDB)
8243       return;
8244     if (nullptr == m_pDS)
8245       return;
8246 
8247     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8248       strSQL=PrepareSQL("SELECT country.country_id, country.name, path.strPath FROM country INNER JOIN country_link ON country_link.country_id=country.country_id INNER JOIN movie ON country_link.media_id=movie.idMovie INNER JOIN files ON files.idFile=movie.idFile INNER JOIN path ON path.idPath=files.idPath WHERE country_link.media_type='movie' AND country.name LIKE '%%%s%%'", strSearch.c_str());
8249     else
8250       strSQL=PrepareSQL("SELECT DISTINCT country.country_id, country.name FROM country INNER JOIN country_link ON country_link.country_id=country.country_id WHERE country_link.media_type='movie' AND name like '%%%s%%'", strSearch.c_str());
8251     m_pDS->query( strSQL );
8252 
8253     while (!m_pDS->eof())
8254     {
8255       if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8256         if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),
8257                                                       *CMediaSourceSettings::GetInstance().GetSources("video")))
8258         {
8259           m_pDS->next();
8260           continue;
8261         }
8262 
8263       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
8264       std::string strDir = StringUtils::Format("%i/", m_pDS->fv(0).get_asInt());
8265       pItem->SetPath("videodb://movies/genres/"+ strDir);
8266       pItem->m_bIsFolder=true;
8267       items.Add(pItem);
8268       m_pDS->next();
8269     }
8270     m_pDS->close();
8271   }
8272   catch (...)
8273   {
8274     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
8275   }
8276 }
8277 
GetTvShowGenresByName(const std::string & strSearch,CFileItemList & items)8278 void CVideoDatabase::GetTvShowGenresByName(const std::string& strSearch, CFileItemList& items)
8279 {
8280   std::string strSQL;
8281 
8282   try
8283   {
8284     if (nullptr == m_pDB)
8285       return;
8286     if (nullptr == m_pDS)
8287       return;
8288 
8289     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8290       strSQL=PrepareSQL("SELECT genre.genre_id, genre.name, path.strPath FROM genre INNER JOIN genre_link ON genre_link.genre_id=genre.genre_id INNER JOIN tvshow ON genre_link.media_id=tvshow.idShow INNER JOIN tvshowlinkpath ON tvshowlinkpath.idShow=tvshow.idShow INNER JOIN path ON path.idPath=tvshowlinkpath.idPath WHERE genre_link.media_type='tvshow' AND genre.name LIKE '%%%s%%'", strSearch.c_str());
8291     else
8292       strSQL=PrepareSQL("SELECT DISTINCT genre.genre_id, genre.name FROM genre INNER JOIN genre_link ON genre_link.genre_id=genre.genre_id WHERE genre_link.media_type='tvshow' AND name LIKE '%%%s%%'", strSearch.c_str());
8293     m_pDS->query( strSQL );
8294 
8295     while (!m_pDS->eof())
8296     {
8297       if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8298         if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
8299         {
8300           m_pDS->next();
8301           continue;
8302         }
8303 
8304       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
8305       std::string strDir = StringUtils::Format("%i/", m_pDS->fv(0).get_asInt());
8306       pItem->SetPath("videodb://tvshows/genres/"+ strDir);
8307       pItem->m_bIsFolder=true;
8308       items.Add(pItem);
8309       m_pDS->next();
8310     }
8311     m_pDS->close();
8312   }
8313   catch (...)
8314   {
8315     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
8316   }
8317 }
8318 
GetMovieActorsByName(const std::string & strSearch,CFileItemList & items)8319 void CVideoDatabase::GetMovieActorsByName(const std::string& strSearch, CFileItemList& items)
8320 {
8321   std::string strSQL;
8322 
8323   try
8324   {
8325     if (nullptr == m_pDB)
8326       return;
8327     if (nullptr == m_pDS)
8328       return;
8329 
8330     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8331       strSQL=PrepareSQL("SELECT actor.actor_id, actor.name, path.strPath FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id INNER JOIN movie ON actor_link.media_id=movie.idMovie INNER JOIN files ON files.idFile=movie.idFile INNER JOIN path ON path.idPath=files.idPath WHERE actor_link.media_type='movie' AND actor.name LIKE '%%%s%%'", strSearch.c_str());
8332     else
8333       strSQL=PrepareSQL("SELECT DISTINCT actor.actor_id, actor.name FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id INNER JOIN movie ON actor_link.media_id=movie.idMovie WHERE actor_link.media_type='movie' AND actor.name LIKE '%%%s%%'", strSearch.c_str());
8334     m_pDS->query( strSQL );
8335 
8336     while (!m_pDS->eof())
8337     {
8338       if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8339         if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
8340         {
8341           m_pDS->next();
8342           continue;
8343         }
8344 
8345       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
8346       std::string strDir = StringUtils::Format("%i/", m_pDS->fv(0).get_asInt());
8347       pItem->SetPath("videodb://movies/actors/"+ strDir);
8348       pItem->m_bIsFolder=true;
8349       items.Add(pItem);
8350       m_pDS->next();
8351     }
8352     m_pDS->close();
8353   }
8354   catch (...)
8355   {
8356     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
8357   }
8358 }
8359 
GetTvShowsActorsByName(const std::string & strSearch,CFileItemList & items)8360 void CVideoDatabase::GetTvShowsActorsByName(const std::string& strSearch, CFileItemList& items)
8361 {
8362   std::string strSQL;
8363 
8364   try
8365   {
8366     if (nullptr == m_pDB)
8367       return;
8368     if (nullptr == m_pDS)
8369       return;
8370 
8371     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8372       strSQL=PrepareSQL("SELECT actor.actor_id, actor.name, path.strPath FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id INNER JOIN tvshow ON actor_link.media_id=tvshow.idShow INNER JOIN tvshowlinkpath ON tvshowlinkpath.idPath=tvshow.idShow INNER JOIN path ON path.idPath=tvshowlinkpath.idPath WHERE actor_link.media_type='tvshow' AND actor.name LIKE '%%%s%%'", strSearch.c_str());
8373     else
8374       strSQL=PrepareSQL("SELECT DISTINCT actor.actor_id, actor.name FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id INNER JOIN tvshow ON actor_link.media_id=tvshow.idShow WHERE actor_link.media_type='tvshow' AND actor.name LIKE '%%%s%%'",strSearch.c_str());
8375     m_pDS->query( strSQL );
8376 
8377     while (!m_pDS->eof())
8378     {
8379       if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8380         if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
8381         {
8382           m_pDS->next();
8383           continue;
8384         }
8385 
8386       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
8387       std::string strDir = StringUtils::Format("%i/", m_pDS->fv(0).get_asInt());
8388       pItem->SetPath("videodb://tvshows/actors/"+ strDir);
8389       pItem->m_bIsFolder=true;
8390       items.Add(pItem);
8391       m_pDS->next();
8392     }
8393     m_pDS->close();
8394   }
8395   catch (...)
8396   {
8397     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
8398   }
8399 }
8400 
GetMusicVideoArtistsByName(const std::string & strSearch,CFileItemList & items)8401 void CVideoDatabase::GetMusicVideoArtistsByName(const std::string& strSearch, CFileItemList& items)
8402 {
8403   std::string strSQL;
8404 
8405   try
8406   {
8407     if (nullptr == m_pDB)
8408       return;
8409     if (nullptr == m_pDS)
8410       return;
8411 
8412     std::string strLike;
8413     if (!strSearch.empty())
8414       strLike = "and actor.name like '%%%s%%'";
8415     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8416       strSQL=PrepareSQL("SELECT actor.actor_id, actor.name, path.strPath FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id INNER JOIN musicvideo ON actor_link.media_id=musicvideo.idMVideo INNER JOIN files ON files.idFile=musicvideo.idFile INNER JOIN path ON path.idPath=files.idPath WHERE actor_link.media_type='musicvideo' "+strLike, strSearch.c_str());
8417     else
8418       strSQL=PrepareSQL("SELECT DISTINCT actor.actor_id, actor.name from actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id WHERE actor_link.media_type='musicvideo' "+strLike,strSearch.c_str());
8419     m_pDS->query( strSQL );
8420 
8421     while (!m_pDS->eof())
8422     {
8423       if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8424         if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
8425         {
8426           m_pDS->next();
8427           continue;
8428         }
8429 
8430       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
8431       std::string strDir = StringUtils::Format("%i/", m_pDS->fv(0).get_asInt());
8432       pItem->SetPath("videodb://musicvideos/artists/"+ strDir);
8433       pItem->m_bIsFolder=true;
8434       items.Add(pItem);
8435       m_pDS->next();
8436     }
8437     m_pDS->close();
8438   }
8439   catch (...)
8440   {
8441     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
8442   }
8443 }
8444 
GetMusicVideoGenresByName(const std::string & strSearch,CFileItemList & items)8445 void CVideoDatabase::GetMusicVideoGenresByName(const std::string& strSearch, CFileItemList& items)
8446 {
8447   std::string strSQL;
8448 
8449   try
8450   {
8451     if (nullptr == m_pDB)
8452       return;
8453     if (nullptr == m_pDS)
8454       return;
8455 
8456     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8457       strSQL=PrepareSQL("SELECT genre.genre_id, genre.name, path.strPath FROM genre INNER JOIN genre_link ON genre_link.genre_id=genre.genre_id INNER JOIN musicvideo ON genre_link.media_id=musicvideo.idMVideo INNER JOIN files ON files.idFile=musicvideo.idFile INNER JOIN path ON path.idPath=files.idPath WHERE genre_link.media_type='musicvideo' AND genre.name LIKE '%%%s%%'", strSearch.c_str());
8458     else
8459       strSQL=PrepareSQL("SELECT DISTINCT genre.genre_id, genre.name FROM genre INNER JOIN genre_link ON genre_link.genre_id=genre.genre_id WHERE genre_link.media_type='musicvideo' AND genre.name LIKE '%%%s%%'", strSearch.c_str());
8460     m_pDS->query( strSQL );
8461 
8462     while (!m_pDS->eof())
8463     {
8464       if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8465         if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
8466         {
8467           m_pDS->next();
8468           continue;
8469         }
8470 
8471       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
8472       std::string strDir = StringUtils::Format("%i/", m_pDS->fv(0).get_asInt());
8473       pItem->SetPath("videodb://musicvideos/genres/"+ strDir);
8474       pItem->m_bIsFolder=true;
8475       items.Add(pItem);
8476       m_pDS->next();
8477     }
8478     m_pDS->close();
8479   }
8480   catch (...)
8481   {
8482     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
8483   }
8484 }
8485 
GetMusicVideoAlbumsByName(const std::string & strSearch,CFileItemList & items)8486 void CVideoDatabase::GetMusicVideoAlbumsByName(const std::string& strSearch, CFileItemList& items)
8487 {
8488   std::string strSQL;
8489 
8490   try
8491   {
8492     if (nullptr == m_pDB)
8493       return;
8494     if (nullptr == m_pDS)
8495       return;
8496 
8497     strSQL = StringUtils::Format("SELECT DISTINCT"
8498                                  "  musicvideo.c%02d,"
8499                                  "  musicvideo.idMVideo,"
8500                                  "  path.strPath"
8501                                  " FROM"
8502                                  "  musicvideo"
8503                                  " JOIN files ON"
8504                                  "  files.idFile=musicvideo.idFile"
8505                                  " JOIN path ON"
8506                                  "  path.idPath=files.idPath", VIDEODB_ID_MUSICVIDEO_ALBUM);
8507     if (!strSearch.empty())
8508       strSQL += PrepareSQL(" WHERE musicvideo.c%02d like '%%%s%%'",VIDEODB_ID_MUSICVIDEO_ALBUM, strSearch.c_str());
8509 
8510     m_pDS->query( strSQL );
8511 
8512     while (!m_pDS->eof())
8513     {
8514       if (m_pDS->fv(0).get_asString().empty())
8515       {
8516         m_pDS->next();
8517         continue;
8518       }
8519 
8520       if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8521         if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv(2).get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
8522         {
8523           m_pDS->next();
8524           continue;
8525         }
8526 
8527       CFileItemPtr pItem(new CFileItem(m_pDS->fv(0).get_asString()));
8528       std::string strDir = StringUtils::Format("%i", m_pDS->fv(1).get_asInt());
8529       pItem->SetPath("videodb://musicvideos/titles/"+ strDir);
8530       pItem->m_bIsFolder=false;
8531       items.Add(pItem);
8532       m_pDS->next();
8533     }
8534     m_pDS->close();
8535   }
8536   catch (...)
8537   {
8538     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
8539   }
8540 }
8541 
GetMusicVideosByAlbum(const std::string & strSearch,CFileItemList & items)8542 void CVideoDatabase::GetMusicVideosByAlbum(const std::string& strSearch, CFileItemList& items)
8543 {
8544   std::string strSQL;
8545 
8546   try
8547   {
8548     if (nullptr == m_pDB)
8549       return;
8550     if (nullptr == m_pDS)
8551       return;
8552 
8553     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8554       strSQL = PrepareSQL("SELECT musicvideo.idMVideo, musicvideo.c%02d,musicvideo.c%02d, path.strPath FROM musicvideo INNER JOIN files ON files.idFile=musicvideo.idFile INNER JOIN path ON path.idPath=files.idPath WHERE musicvideo.c%02d LIKE '%%%s%%'", VIDEODB_ID_MUSICVIDEO_ALBUM, VIDEODB_ID_MUSICVIDEO_TITLE, VIDEODB_ID_MUSICVIDEO_ALBUM, strSearch.c_str());
8555     else
8556       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());
8557     m_pDS->query( strSQL );
8558 
8559     while (!m_pDS->eof())
8560     {
8561       if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8562         if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
8563         {
8564           m_pDS->next();
8565           continue;
8566         }
8567 
8568       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()+" - "+m_pDS->fv(2).get_asString()));
8569       std::string strDir = StringUtils::Format("3/2/%i",m_pDS->fv("musicvideo.idMVideo").get_asInt());
8570 
8571       pItem->SetPath("videodb://"+ strDir);
8572       pItem->m_bIsFolder=false;
8573       items.Add(pItem);
8574       m_pDS->next();
8575     }
8576     m_pDS->close();
8577   }
8578   catch (...)
8579   {
8580     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
8581   }
8582 }
8583 
GetMusicVideosByWhere(const std::string & baseDir,const Filter & filter,CFileItemList & items,bool checkLocks,const SortDescription & sortDescription,int getDetails)8584 bool CVideoDatabase::GetMusicVideosByWhere(const std::string &baseDir, const Filter &filter, CFileItemList &items, bool checkLocks /*= true*/, const SortDescription &sortDescription /* = SortDescription() */, int getDetails /* = VideoDbDetailsNone */)
8585 {
8586   try
8587   {
8588     movieTime = 0;
8589     castTime = 0;
8590 
8591     if (nullptr == m_pDB)
8592       return false;
8593     if (nullptr == m_pDS)
8594       return false;
8595 
8596     int total = -1;
8597 
8598     std::string strSQL = "select %s from musicvideo_view ";
8599     CVideoDbUrl videoUrl;
8600     if (!videoUrl.FromString(baseDir))
8601       return false;
8602 
8603     std::string strSQLExtra;
8604     const CUrlOptions::UrlOptions& options = videoUrl.GetOptions();
8605     std::string strArtist;
8606     int idArtist = -1;
8607     // If we have an artistid then get the artist name and use that to fix up the path displayed in
8608     // the GUI (musicvideos/artist-name instead of musicvideos/artistid)
8609     auto option = options.find("artistid");
8610     if (option != options.end())
8611     {
8612       idArtist = option->second.asInteger();
8613       strArtist = GetSingleValue(
8614                       PrepareSQL("SELECT name FROM actor where actor_id = '%i'", idArtist), m_pDS)
8615                       .c_str();
8616     }
8617     Filter extFilter = filter;
8618     SortDescription sorting = sortDescription;
8619     if (!BuildSQL(baseDir, strSQLExtra, extFilter, strSQLExtra, videoUrl, sorting))
8620       return false;
8621 
8622     // Apply the limiting directly here if there's no special sorting but limiting
8623     if (extFilter.limit.empty() &&
8624       sorting.sortBy == SortByNone &&
8625       (sorting.limitStart > 0 || sorting.limitEnd > 0))
8626     {
8627       total = (int)strtol(GetSingleValue(PrepareSQL(strSQL, "COUNT(1)") + strSQLExtra, m_pDS).c_str(), NULL, 10);
8628       strSQLExtra += DatabaseUtils::BuildLimitClause(sorting.limitEnd, sorting.limitStart);
8629     }
8630 
8631     strSQL = PrepareSQL(strSQL, !extFilter.fields.empty() ? extFilter.fields.c_str() : "*") + strSQLExtra;
8632 
8633     int iRowsFound = RunQuery(strSQL);
8634     if (iRowsFound <= 0)
8635       return iRowsFound == 0;
8636 
8637     // store the total value of items as a property
8638     if (total < iRowsFound)
8639       total = iRowsFound;
8640     items.SetProperty("total", total);
8641 
8642     DatabaseResults results;
8643     results.reserve(iRowsFound);
8644     if (!SortUtils::SortFromDataset(sorting, MediaTypeMusicVideo, m_pDS, results))
8645       return false;
8646 
8647     // get data from returned rows
8648     items.Reserve(results.size());
8649     // get songs from returned subtable
8650     const query_data &data = m_pDS->get_result_set().records;
8651     for (const auto &i : results)
8652     {
8653       unsigned int targetRow = (unsigned int)i.at(FieldRow).asInteger();
8654       const dbiplus::sql_record* const record = data.at(targetRow);
8655 
8656       CVideoInfoTag musicvideo = GetDetailsForMusicVideo(record, getDetails);
8657       if (!checkLocks || m_profileManager.GetMasterProfile().getLockMode() == LOCK_MODE_EVERYONE || g_passwordManager.bMasterUser ||
8658           g_passwordManager.IsDatabasePathUnlocked(musicvideo.m_strPath, *CMediaSourceSettings::GetInstance().GetSources("video")))
8659       {
8660         CFileItemPtr item(new CFileItem(musicvideo));
8661 
8662         CVideoDbUrl itemUrl = videoUrl;
8663         std::string path = StringUtils::Format("%i", record->at(0).get_asInt());
8664         itemUrl.AppendPath(path);
8665         item->SetPath(itemUrl.ToString());
8666 
8667         item->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, musicvideo.GetPlayCount() > 0);
8668         items.Add(item);
8669       }
8670     }
8671 
8672     // cleanup
8673     m_pDS->close();
8674     if (!strArtist.empty())
8675       items.SetProperty("customtitle", strArtist);
8676     return true;
8677   }
8678   catch (...)
8679   {
8680     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
8681   }
8682   return false;
8683 }
8684 
GetRandomMusicVideoIDs(const std::string & strWhere,std::vector<std::pair<int,int>> & songIDs)8685 unsigned int CVideoDatabase::GetRandomMusicVideoIDs(const std::string& strWhere, std::vector<std::pair<int,int> > &songIDs)
8686 {
8687   try
8688   {
8689     if (nullptr == m_pDB)
8690       return 0;
8691     if (nullptr == m_pDS)
8692       return 0;
8693 
8694     std::string strSQL = "select distinct idMVideo from musicvideo_view";
8695     if (!strWhere.empty())
8696       strSQL += " where " + strWhere;
8697     strSQL += PrepareSQL(" ORDER BY RANDOM()");
8698 
8699     if (!m_pDS->query(strSQL)) return 0;
8700     songIDs.clear();
8701     if (m_pDS->num_rows() == 0)
8702     {
8703       m_pDS->close();
8704       return 0;
8705     }
8706     songIDs.reserve(m_pDS->num_rows());
8707     while (!m_pDS->eof())
8708     {
8709       songIDs.push_back(std::make_pair<int,int>(2,m_pDS->fv(0).get_asInt()));
8710       m_pDS->next();
8711     }    // cleanup
8712     m_pDS->close();
8713     return songIDs.size();
8714   }
8715   catch (...)
8716   {
8717     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strWhere.c_str());
8718   }
8719   return 0;
8720 }
8721 
GetMatchingMusicVideo(const std::string & strArtist,const std::string & strAlbum,const std::string & strTitle)8722 int CVideoDatabase::GetMatchingMusicVideo(const std::string& strArtist, const std::string& strAlbum, const std::string& strTitle)
8723 {
8724   try
8725   {
8726     if (nullptr == m_pDB)
8727       return -1;
8728     if (nullptr == m_pDS)
8729       return -1;
8730 
8731     std::string strSQL;
8732     if (strAlbum.empty() && strTitle.empty())
8733     { // we want to return matching artists only
8734       if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8735         strSQL=PrepareSQL("SELECT DISTINCT actor.actor_id, path.strPath FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id INNER JOIN musicvideo ON actor_link.media_id=musicvideo.idMVideo INNER JOIN files ON files.idFile=musicvideo.idFile INNER JOIN path ON path.idPath=files.idPath WHERE actor_link.media_type='musicvideo' AND actor.name like '%s'", strArtist.c_str());
8736       else
8737         strSQL=PrepareSQL("SELECT DISTINCT actor.actor_id FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id WHERE actor_link.media_type='musicvideo' AND actor.name LIKE '%s'", strArtist.c_str());
8738     }
8739     else
8740     { // we want to return the matching musicvideo
8741       if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8742         strSQL = PrepareSQL("SELECT musicvideo.idMVideo FROM actor INNER JOIN actor_link ON actor_link.actor_id=actor.actor_id INNER JOIN musicvideo ON actor_link.media_id=musicvideo.idMVideo INNER JOIN files ON files.idFile=musicvideo.idFile INNER JOIN path ON path.idPath=files.idPath WHERE actor_link.media_type='musicvideo' AND musicvideo.c%02d LIKE '%s' AND musicvideo.c%02d LIKE '%s' AND actor.name LIKE '%s'", VIDEODB_ID_MUSICVIDEO_ALBUM, strAlbum.c_str(), VIDEODB_ID_MUSICVIDEO_TITLE, strTitle.c_str(), strArtist.c_str());
8743       else
8744         strSQL = PrepareSQL("select musicvideo.idMVideo from musicvideo join actor_link on actor_link.media_id=musicvideo.idMVideo AND actor_link.media_type='musicvideo' join actor on actor.actor_id=actor_link.actor_id where musicvideo.c%02d like '%s' and musicvideo.c%02d like '%s' and actor.name like '%s'",VIDEODB_ID_MUSICVIDEO_ALBUM,strAlbum.c_str(),VIDEODB_ID_MUSICVIDEO_TITLE,strTitle.c_str(),strArtist.c_str());
8745     }
8746     m_pDS->query( strSQL );
8747 
8748     if (m_pDS->eof())
8749       return -1;
8750 
8751     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8752       if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
8753       {
8754         m_pDS->close();
8755         return -1;
8756       }
8757 
8758     int lResult = m_pDS->fv(0).get_asInt();
8759     m_pDS->close();
8760     return lResult;
8761   }
8762   catch (...)
8763   {
8764     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
8765   }
8766   return -1;
8767 }
8768 
GetMoviesByName(const std::string & strSearch,CFileItemList & items)8769 void CVideoDatabase::GetMoviesByName(const std::string& strSearch, CFileItemList& items)
8770 {
8771   std::string strSQL;
8772 
8773   try
8774   {
8775     if (nullptr == m_pDB)
8776       return;
8777     if (nullptr == m_pDS)
8778       return;
8779 
8780     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8781       strSQL = PrepareSQL("SELECT movie.idMovie, movie.c%02d, path.strPath, movie.idSet FROM movie "
8782                           "INNER JOIN files ON files.idFile=movie.idFile INNER JOIN path ON "
8783                           "path.idPath=files.idPath "
8784                           "WHERE movie.c%02d LIKE '%%%s%%' OR movie.c%02d LIKE '%%%s%%'",
8785                           VIDEODB_ID_TITLE, VIDEODB_ID_TITLE, strSearch.c_str(),
8786                           VIDEODB_ID_ORIGINALTITLE, strSearch.c_str());
8787     else
8788       strSQL = PrepareSQL("SELECT movie.idMovie,movie.c%02d, movie.idSet FROM movie WHERE "
8789                           "movie.c%02d like '%%%s%%' OR movie.c%02d LIKE '%%%s%%'",
8790                           VIDEODB_ID_TITLE, VIDEODB_ID_TITLE, strSearch.c_str(),
8791                           VIDEODB_ID_ORIGINALTITLE, strSearch.c_str());
8792     m_pDS->query( strSQL );
8793 
8794     while (!m_pDS->eof())
8795     {
8796       if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8797         if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
8798         {
8799           m_pDS->next();
8800           continue;
8801         }
8802 
8803       int movieId = m_pDS->fv("movie.idMovie").get_asInt();
8804       int setId = m_pDS->fv("movie.idSet").get_asInt();
8805       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
8806       std::string path;
8807       if (setId <= 0 || !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOLIBRARY_GROUPMOVIESETS))
8808         path = StringUtils::Format("videodb://movies/titles/%i", movieId);
8809       else
8810         path = StringUtils::Format("videodb://movies/sets/%i/%i", setId, movieId);
8811       pItem->SetPath(path);
8812       pItem->m_bIsFolder=false;
8813       items.Add(pItem);
8814       m_pDS->next();
8815     }
8816     m_pDS->close();
8817   }
8818   catch (...)
8819   {
8820     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
8821   }
8822 }
8823 
GetTvShowsByName(const std::string & strSearch,CFileItemList & items)8824 void CVideoDatabase::GetTvShowsByName(const std::string& strSearch, CFileItemList& items)
8825 {
8826   std::string strSQL;
8827 
8828   try
8829   {
8830     if (nullptr == m_pDB)
8831       return;
8832     if (nullptr == m_pDS)
8833       return;
8834 
8835     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8836       strSQL = PrepareSQL("SELECT tvshow.idShow, tvshow.c%02d, path.strPath FROM tvshow INNER JOIN tvshowlinkpath ON tvshowlinkpath.idShow=tvshow.idShow INNER JOIN path ON path.idPath=tvshowlinkpath.idPath WHERE tvshow.c%02d LIKE '%%%s%%'", VIDEODB_ID_TV_TITLE, VIDEODB_ID_TV_TITLE, strSearch.c_str());
8837     else
8838       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());
8839     m_pDS->query( strSQL );
8840 
8841     while (!m_pDS->eof())
8842     {
8843       if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8844         if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
8845         {
8846           m_pDS->next();
8847           continue;
8848         }
8849 
8850       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
8851       std::string strDir = StringUtils::Format("tvshows/titles/%i/", m_pDS->fv("tvshow.idShow").get_asInt());
8852 
8853       pItem->SetPath("videodb://"+ strDir);
8854       pItem->m_bIsFolder=true;
8855       pItem->GetVideoInfoTag()->m_iDbId = m_pDS->fv("tvshow.idShow").get_asInt();
8856       items.Add(pItem);
8857       m_pDS->next();
8858     }
8859     m_pDS->close();
8860   }
8861   catch (...)
8862   {
8863     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
8864   }
8865 }
8866 
GetEpisodesByName(const std::string & strSearch,CFileItemList & items)8867 void CVideoDatabase::GetEpisodesByName(const std::string& strSearch, CFileItemList& items)
8868 {
8869   std::string strSQL;
8870 
8871   try
8872   {
8873     if (nullptr == m_pDB)
8874       return;
8875     if (nullptr == m_pDS)
8876       return;
8877 
8878     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8879       strSQL = PrepareSQL("SELECT episode.idEpisode, episode.c%02d, episode.c%02d, episode.idShow, tvshow.c%02d, path.strPath FROM episode INNER JOIN tvshow ON tvshow.idShow=episode.idShow INNER JOIN files ON files.idFile=episode.idFile INNER JOIN path ON path.idPath=files.idPath WHERE episode.c%02d LIKE '%%%s%%'", VIDEODB_ID_EPISODE_TITLE, VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_TV_TITLE, VIDEODB_ID_EPISODE_TITLE, strSearch.c_str());
8880     else
8881       strSQL = PrepareSQL("SELECT episode.idEpisode, episode.c%02d, episode.c%02d, episode.idShow, tvshow.c%02d FROM episode INNER JOIN tvshow ON tvshow.idShow=episode.idShow WHERE episode.c%02d like '%%%s%%'", VIDEODB_ID_EPISODE_TITLE, VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_TV_TITLE, VIDEODB_ID_EPISODE_TITLE, strSearch.c_str());
8882     m_pDS->query( strSQL );
8883 
8884     while (!m_pDS->eof())
8885     {
8886       if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8887         if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
8888         {
8889           m_pDS->next();
8890           continue;
8891         }
8892 
8893       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()+" ("+m_pDS->fv(4).get_asString()+")"));
8894       std::string path = StringUtils::Format("videodb://tvshows/titles/%i/%i/%i",m_pDS->fv("episode.idShow").get_asInt(),m_pDS->fv(2).get_asInt(),m_pDS->fv(0).get_asInt());
8895       pItem->SetPath(path);
8896       pItem->m_bIsFolder=false;
8897       items.Add(pItem);
8898       m_pDS->next();
8899     }
8900     m_pDS->close();
8901   }
8902   catch (...)
8903   {
8904     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
8905   }
8906 }
8907 
GetMusicVideosByName(const std::string & strSearch,CFileItemList & items)8908 void CVideoDatabase::GetMusicVideosByName(const std::string& strSearch, CFileItemList& items)
8909 {
8910 // Alternative searching - not quite as fast though due to
8911 // retrieving all information
8912 //  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()));
8913 //  GetMusicVideosByWhere("videodb://musicvideos/titles/", filter, items);
8914   std::string strSQL;
8915 
8916   try
8917   {
8918     if (nullptr == m_pDB)
8919       return;
8920     if (nullptr == m_pDS)
8921       return;
8922 
8923     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8924       strSQL = PrepareSQL("SELECT musicvideo.idMVideo, musicvideo.c%02d, path.strPath FROM musicvideo INNER JOIN files ON files.idFile=musicvideo.idFile INNER JOIN path ON path.idPath=files.idPath WHERE musicvideo.c%02d LIKE '%%%s%%'", VIDEODB_ID_MUSICVIDEO_TITLE, VIDEODB_ID_MUSICVIDEO_TITLE, strSearch.c_str());
8925     else
8926       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());
8927     m_pDS->query( strSQL );
8928 
8929     while (!m_pDS->eof())
8930     {
8931       if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8932         if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
8933         {
8934           m_pDS->next();
8935           continue;
8936         }
8937 
8938       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
8939       std::string strDir = StringUtils::Format("3/2/%i",m_pDS->fv("musicvideo.idMVideo").get_asInt());
8940 
8941       pItem->SetPath("videodb://"+ strDir);
8942       pItem->m_bIsFolder=false;
8943       items.Add(pItem);
8944       m_pDS->next();
8945     }
8946     m_pDS->close();
8947   }
8948   catch (...)
8949   {
8950     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
8951   }
8952 }
8953 
GetEpisodesByPlot(const std::string & strSearch,CFileItemList & items)8954 void CVideoDatabase::GetEpisodesByPlot(const std::string& strSearch, CFileItemList& items)
8955 {
8956 // Alternative searching - not quite as fast though due to
8957 // retrieving all information
8958 //  Filter filter;
8959 //  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());
8960 //  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());
8961 //  GetEpisodesByWhere("videodb://tvshows/titles/", filter, items);
8962 //  return;
8963   std::string strSQL;
8964 
8965   try
8966   {
8967     if (nullptr == m_pDB)
8968       return;
8969     if (nullptr == m_pDS)
8970       return;
8971 
8972     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8973       strSQL = PrepareSQL("SELECT episode.idEpisode, episode.c%02d, episode.c%02d, episode.idShow, tvshow.c%02d, path.strPath FROM episode INNER JOIN tvshow ON tvshow.idShow=episode.idShow INNER JOIN files ON files.idFile=episode.idFile INNER JOIN path ON path.idPath=files.idPath WHERE episode.c%02d LIKE '%%%s%%'", VIDEODB_ID_EPISODE_TITLE, VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_TV_TITLE, VIDEODB_ID_EPISODE_PLOT, strSearch.c_str());
8974     else
8975       strSQL = PrepareSQL("SELECT episode.idEpisode, episode.c%02d, episode.c%02d, episode.idShow, tvshow.c%02d FROM episode INNER JOIN tvshow ON tvshow.idShow=episode.idShow WHERE episode.c%02d LIKE '%%%s%%'", VIDEODB_ID_EPISODE_TITLE, VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_TV_TITLE, VIDEODB_ID_EPISODE_PLOT, strSearch.c_str());
8976     m_pDS->query( strSQL );
8977 
8978     while (!m_pDS->eof())
8979     {
8980       if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
8981         if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
8982         {
8983           m_pDS->next();
8984           continue;
8985         }
8986 
8987       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()+" ("+m_pDS->fv(4).get_asString()+")"));
8988       std::string path = StringUtils::Format("videodb://tvshows/titles/%i/%i/%i",m_pDS->fv("episode.idShow").get_asInt(),m_pDS->fv(2).get_asInt(),m_pDS->fv(0).get_asInt());
8989       pItem->SetPath(path);
8990       pItem->m_bIsFolder=false;
8991       items.Add(pItem);
8992       m_pDS->next();
8993     }
8994     m_pDS->close();
8995   }
8996   catch (...)
8997   {
8998     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
8999   }
9000 }
9001 
GetMoviesByPlot(const std::string & strSearch,CFileItemList & items)9002 void CVideoDatabase::GetMoviesByPlot(const std::string& strSearch, CFileItemList& items)
9003 {
9004   std::string strSQL;
9005 
9006   try
9007   {
9008     if (nullptr == m_pDB)
9009       return;
9010     if (nullptr == m_pDS)
9011       return;
9012 
9013     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9014       strSQL = PrepareSQL("select movie.idMovie, movie.c%02d, path.strPath FROM movie INNER JOIN files ON files.idFile=movie.idFile INNER JOIN path ON path.idPath=files.idPath 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());
9015     else
9016       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());
9017 
9018     m_pDS->query( strSQL );
9019 
9020     while (!m_pDS->eof())
9021     {
9022       if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9023         if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv(2).get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9024         {
9025           m_pDS->next();
9026           continue;
9027         }
9028 
9029       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9030       std::string path = StringUtils::Format("videodb://movies/titles/%i", m_pDS->fv(0).get_asInt());
9031       pItem->SetPath(path);
9032       pItem->m_bIsFolder=false;
9033 
9034       items.Add(pItem);
9035       m_pDS->next();
9036     }
9037     m_pDS->close();
9038 
9039   }
9040   catch (...)
9041   {
9042     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
9043   }
9044 }
9045 
GetMovieDirectorsByName(const std::string & strSearch,CFileItemList & items)9046 void CVideoDatabase::GetMovieDirectorsByName(const std::string& strSearch, CFileItemList& items)
9047 {
9048   std::string strSQL;
9049 
9050   try
9051   {
9052     if (nullptr == m_pDB)
9053       return;
9054     if (nullptr == m_pDS)
9055       return;
9056 
9057     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9058       strSQL = PrepareSQL("SELECT DISTINCT director_link.actor_id, actor.name, path.strPath FROM movie INNER JOIN director_link ON (director_link.media_id=movie.idMovie AND director_link.media_type='movie') INNER JOIN actor ON actor.actor_id=director_link.actor_id INNER JOIN files ON files.idFile=movie.idFile INNER JOIN path ON path.idPath=files.idPath WHERE actor.name LIKE '%%%s%%'", strSearch.c_str());
9059     else
9060       strSQL = PrepareSQL("SELECT DISTINCT director_link.actor_id, actor.name FROM actor INNER JOIN director_link ON director_link.actor_id=actor.actor_id INNER JOIN movie ON director_link.media_id=movie.idMovie WHERE director_link.media_type='movie' AND actor.name like '%%%s%%'", strSearch.c_str());
9061 
9062     m_pDS->query( strSQL );
9063 
9064     while (!m_pDS->eof())
9065     {
9066       if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9067         if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9068         {
9069           m_pDS->next();
9070           continue;
9071         }
9072 
9073       std::string strDir = StringUtils::Format("%i/", m_pDS->fv(0).get_asInt());
9074       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9075 
9076       pItem->SetPath("videodb://movies/directors/"+ strDir);
9077       pItem->m_bIsFolder=true;
9078       items.Add(pItem);
9079       m_pDS->next();
9080     }
9081     m_pDS->close();
9082   }
9083   catch (...)
9084   {
9085     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
9086   }
9087 }
9088 
GetTvShowsDirectorsByName(const std::string & strSearch,CFileItemList & items)9089 void CVideoDatabase::GetTvShowsDirectorsByName(const std::string& strSearch, CFileItemList& items)
9090 {
9091   std::string strSQL;
9092 
9093   try
9094   {
9095     if (nullptr == m_pDB)
9096       return;
9097     if (nullptr == m_pDS)
9098       return;
9099 
9100     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9101       strSQL = PrepareSQL("SELECT DISTINCT director_link.actor_id, actor.name, path.strPath FROM actor INNER JOIN director_link ON director_link.actor_id=actor.actor_id INNER JOIN tvshow ON director_link.media_id=tvshow.idShow INNER JOIN tvshowlinkpath ON tvshowlinkpath.idShow=tvshow.idShow INNER JOIN path ON path.idPath=tvshowlinkpath.idPath WHERE director_link.media_type='tvshow' AND actor.name LIKE '%%%s%%'", strSearch.c_str());
9102     else
9103       strSQL = PrepareSQL("SELECT DISTINCT director_link.actor_id, actor.name FROM actor INNER JOIN director_link ON director_link.actor_id=actor.actor_id INNER JOIN tvshow ON director_link.media_id=tvshow.idShow WHERE director_link.media_type='tvshow' AND actor.name LIKE '%%%s%%'", strSearch.c_str());
9104 
9105     m_pDS->query( strSQL );
9106 
9107     while (!m_pDS->eof())
9108     {
9109       if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9110         if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9111         {
9112           m_pDS->next();
9113           continue;
9114         }
9115 
9116       std::string strDir = StringUtils::Format("%i/", m_pDS->fv(0).get_asInt());
9117       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9118 
9119       pItem->SetPath("videodb://tvshows/directors/"+ strDir);
9120       pItem->m_bIsFolder=true;
9121       items.Add(pItem);
9122       m_pDS->next();
9123     }
9124     m_pDS->close();
9125   }
9126   catch (...)
9127   {
9128     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
9129   }
9130 }
9131 
GetMusicVideoDirectorsByName(const std::string & strSearch,CFileItemList & items)9132 void CVideoDatabase::GetMusicVideoDirectorsByName(const std::string& strSearch, CFileItemList& items)
9133 {
9134   std::string strSQL;
9135 
9136   try
9137   {
9138     if (nullptr == m_pDB)
9139       return;
9140     if (nullptr == m_pDS)
9141       return;
9142 
9143     if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9144       strSQL = PrepareSQL("SELECT DISTINCT director_link.actor_id, actor.name, path.strPath FROM actor INNER JOIN director_link ON director_link.actor_id=actor.actor_id INNER JOIN musicvideo ON director_link.media_id=musicvideo.idMVideo INNER JOIN files ON files.idFile=musicvideo.idFile INNER JOIN path ON path.idPath=files.idPath WHERE director_link.media_type='musicvideo' AND actor.name LIKE '%%%s%%'", strSearch.c_str());
9145     else
9146       strSQL = PrepareSQL("SELECT DISTINCT director_link.actor_id, actor.name FROM actor INNER JOIN director_link ON director_link.actor_id=actor.actor_id INNER JOIN musicvideo ON director_link.media_id=musicvideo.idMVideo WHERE director_link.media_type='musicvideo' AND actor.name LIKE '%%%s%%'", strSearch.c_str());
9147 
9148     m_pDS->query( strSQL );
9149 
9150     while (!m_pDS->eof())
9151     {
9152       if (m_profileManager.GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && !g_passwordManager.bMasterUser)
9153         if (!g_passwordManager.IsDatabasePathUnlocked(m_pDS->fv("path.strPath").get_asString(),*CMediaSourceSettings::GetInstance().GetSources("video")))
9154         {
9155           m_pDS->next();
9156           continue;
9157         }
9158 
9159       std::string strDir = StringUtils::Format("%i/", m_pDS->fv(0).get_asInt());
9160       CFileItemPtr pItem(new CFileItem(m_pDS->fv(1).get_asString()));
9161 
9162       pItem->SetPath("videodb://musicvideos/albums/"+ strDir);
9163       pItem->m_bIsFolder=true;
9164       items.Add(pItem);
9165       m_pDS->next();
9166     }
9167     m_pDS->close();
9168   }
9169   catch (...)
9170   {
9171     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
9172   }
9173 }
9174 
CleanDatabase(CGUIDialogProgressBarHandle * handle,const std::set<int> & paths,bool showProgress)9175 void CVideoDatabase::CleanDatabase(CGUIDialogProgressBarHandle* handle, const std::set<int>& paths, bool showProgress)
9176 {
9177   CGUIDialogProgress *progress=NULL;
9178   try
9179   {
9180     if (nullptr == m_pDB)
9181       return;
9182     if (nullptr == m_pDS)
9183       return;
9184     if (nullptr == m_pDS2)
9185       return;
9186 
9187     unsigned int time = XbmcThreads::SystemClockMillis();
9188     CLog::Log(LOGINFO, "%s: Starting videodatabase cleanup ..", __FUNCTION__);
9189     CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary,
9190                                                        "OnCleanStarted");
9191 
9192     BeginTransaction();
9193 
9194     // find all the files
9195     std::string sql = "SELECT files.idFile, files.strFileName, path.strPath FROM files INNER JOIN path ON path.idPath=files.idPath";
9196     if (!paths.empty())
9197     {
9198       std::string strPaths;
9199       for (const auto &i : paths)
9200         strPaths += StringUtils::Format(",%i", i);
9201       sql += PrepareSQL(" AND path.idPath IN (%s)", strPaths.substr(1).c_str());
9202     }
9203 
9204     // For directory caching to work properly, we need to sort the files by path
9205     sql += " ORDER BY path.strPath";
9206 
9207     m_pDS2->query(sql);
9208     if (m_pDS2->num_rows() == 0) return;
9209 
9210     if (handle)
9211     {
9212       handle->SetTitle(g_localizeStrings.Get(700));
9213       handle->SetText("");
9214     }
9215     else if (showProgress)
9216     {
9217       progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
9218       if (progress)
9219       {
9220         progress->SetHeading(CVariant{700});
9221         progress->SetLine(0, CVariant{""});
9222         progress->SetLine(1, CVariant{313});
9223         progress->SetLine(2, CVariant{330});
9224         progress->SetPercentage(0);
9225         progress->Open();
9226         progress->ShowProgressBar(true);
9227       }
9228     }
9229 
9230     std::string filesToTestForDelete;
9231     VECSOURCES videoSources(*CMediaSourceSettings::GetInstance().GetSources("video"));
9232     CServiceBroker::GetMediaManager().GetRemovableDrives(videoSources);
9233 
9234     int total = m_pDS2->num_rows();
9235     int current = 0;
9236     std::string lastDir;
9237     bool gotDir = true;
9238 
9239     while (!m_pDS2->eof())
9240     {
9241       std::string path = m_pDS2->fv("path.strPath").get_asString();
9242       std::string fileName = m_pDS2->fv("files.strFileName").get_asString();
9243       std::string fullPath;
9244       ConstructPath(fullPath, path, fileName);
9245 
9246       // get the first stacked file
9247       if (URIUtils::IsStack(fullPath))
9248         fullPath = CStackDirectory::GetFirstStackedFile(fullPath);
9249 
9250       // get the actual archive path
9251       if (URIUtils::IsInArchive(fullPath))
9252         fullPath = CURL(fullPath).GetHostName();
9253 
9254       bool del = true;
9255       if (URIUtils::IsPlugin(fullPath))
9256       {
9257         SScanSettings settings;
9258         bool foundDirectly = false;
9259         ScraperPtr scraper = GetScraperForPath(fullPath, settings, foundDirectly);
9260         if (scraper && CPluginDirectory::CheckExists(TranslateContent(scraper->Content()), fullPath))
9261           del = false;
9262       }
9263       else
9264       {
9265         // Only consider keeping this file if not optical and belonging to a (matching) source
9266         bool bIsSource;
9267         if (!URIUtils::IsOnDVD(fullPath) &&
9268             CUtil::GetMatchingSource(fullPath, videoSources, bIsSource) >= 0)
9269         {
9270           const std::string pathDir = URIUtils::GetDirectory(fullPath);
9271 
9272           // Cache file's directory in case it's different from the previous file
9273           if (lastDir != pathDir)
9274           {
9275             lastDir = pathDir;
9276             CFileItemList items; // Dummy list
9277             gotDir = CDirectory::GetDirectory(pathDir, items, "", DIR_FLAG_NO_FILE_DIRS |
9278                                               DIR_FLAG_NO_FILE_INFO);
9279           }
9280 
9281           // Keep existing files
9282           if (gotDir && CFile::Exists(fullPath, true))
9283             del = false;
9284         }
9285       }
9286       if (del)
9287         filesToTestForDelete += m_pDS2->fv("files.idFile").get_asString() + ",";
9288 
9289       if (handle == NULL && progress != NULL)
9290       {
9291         int percentage = current * 100 / total;
9292         if (percentage > progress->GetPercentage())
9293         {
9294           progress->SetPercentage(percentage);
9295           progress->Progress();
9296         }
9297         if (progress->IsCanceled())
9298         {
9299           progress->Close();
9300           m_pDS2->close();
9301           CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary,
9302                                                              "OnCleanFinished");
9303           return;
9304         }
9305       }
9306       else if (handle != NULL)
9307         handle->SetPercentage(current * 100 / (float)total);
9308 
9309       m_pDS2->next();
9310       current++;
9311     }
9312     m_pDS2->close();
9313 
9314     std::string filesToDelete;
9315 
9316     // Add any files that don't have a valid idPath entry to the filesToDelete list.
9317     m_pDS->query("SELECT files.idFile FROM files WHERE NOT EXISTS (SELECT 1 FROM path WHERE path.idPath = files.idPath)");
9318     while (!m_pDS->eof())
9319     {
9320       std::string file = m_pDS->fv("files.idFile").get_asString() + ",";
9321       filesToTestForDelete += file;
9322       filesToDelete += file;
9323 
9324       m_pDS->next();
9325     }
9326     m_pDS->close();
9327 
9328     std::map<int, bool> pathsDeleteDecisions;
9329     std::vector<int> movieIDs;
9330     std::vector<int> tvshowIDs;
9331     std::vector<int> episodeIDs;
9332     std::vector<int> musicVideoIDs;
9333 
9334     if (!filesToTestForDelete.empty())
9335     {
9336       StringUtils::TrimRight(filesToTestForDelete, ",");
9337 
9338       movieIDs = CleanMediaType(MediaTypeMovie, filesToTestForDelete, pathsDeleteDecisions, filesToDelete, !showProgress);
9339       episodeIDs = CleanMediaType(MediaTypeEpisode, filesToTestForDelete, pathsDeleteDecisions, filesToDelete, !showProgress);
9340       musicVideoIDs = CleanMediaType(MediaTypeMusicVideo, filesToTestForDelete, pathsDeleteDecisions, filesToDelete, !showProgress);
9341     }
9342 
9343     if (progress != NULL)
9344     {
9345       progress->SetPercentage(100);
9346       progress->Progress();
9347     }
9348 
9349     if (!filesToDelete.empty())
9350     {
9351       filesToDelete = "(" + StringUtils::TrimRight(filesToDelete, ",") + ")";
9352 
9353       // Clean hashes of all paths that files are deleted from
9354       // Otherwise there is a mismatch between the path contents and the hash in the
9355       // database, leading to potentially missed items on re-scan (if deleted files are
9356       // later re-added to a source)
9357       CLog::LogF(LOGDEBUG, LOGDATABASE, "Cleaning path hashes");
9358       m_pDS->query("SELECT DISTINCT strPath FROM path JOIN files ON files.idPath=path.idPath WHERE files.idFile IN " + filesToDelete);
9359       int pathHashCount = m_pDS->num_rows();
9360       while (!m_pDS->eof())
9361       {
9362         InvalidatePathHash(m_pDS->fv("strPath").get_asString());
9363         m_pDS->next();
9364       }
9365       CLog::LogF(LOGDEBUG, LOGDATABASE, "Cleaned {} path hashes", pathHashCount);
9366 
9367       CLog::Log(LOGDEBUG, LOGDATABASE, "%s: Cleaning files table", __FUNCTION__);
9368       sql = "DELETE FROM files WHERE idFile IN " + filesToDelete;
9369       m_pDS->exec(sql);
9370     }
9371 
9372     if (!movieIDs.empty())
9373     {
9374       std::string moviesToDelete;
9375       for (const auto &i : movieIDs)
9376         moviesToDelete += StringUtils::Format("%i,", i);
9377       moviesToDelete = "(" + StringUtils::TrimRight(moviesToDelete, ",") + ")";
9378 
9379       CLog::Log(LOGDEBUG, LOGDATABASE, "%s: Cleaning movie table", __FUNCTION__);
9380       sql = "DELETE FROM movie WHERE idMovie IN " + moviesToDelete;
9381       m_pDS->exec(sql);
9382     }
9383 
9384     if (!episodeIDs.empty())
9385     {
9386       std::string episodesToDelete;
9387       for (const auto &i : episodeIDs)
9388         episodesToDelete += StringUtils::Format("%i,", i);
9389       episodesToDelete = "(" + StringUtils::TrimRight(episodesToDelete, ",") + ")";
9390 
9391       CLog::Log(LOGDEBUG, LOGDATABASE, "%s: Cleaning episode table", __FUNCTION__);
9392       sql = "DELETE FROM episode WHERE idEpisode IN " + episodesToDelete;
9393       m_pDS->exec(sql);
9394     }
9395 
9396     CLog::Log(LOGDEBUG, LOGDATABASE, "%s: Cleaning paths that don't exist and have content set...", __FUNCTION__);
9397     sql = "SELECT path.idPath, path.strPath, path.idParentPath FROM path "
9398             "WHERE NOT ((strContent IS NULL OR strContent = '') "
9399                    "AND (strSettings IS NULL OR strSettings = '') "
9400                    "AND (strHash IS NULL OR strHash = '') "
9401                    "AND (exclude IS NULL OR exclude != 1))";
9402     m_pDS2->query(sql);
9403     std::string strIds;
9404     while (!m_pDS2->eof())
9405     {
9406       auto pathsDeleteDecision = pathsDeleteDecisions.find(m_pDS2->fv(0).get_asInt());
9407       // Check if we have a decision for the parent path
9408       auto pathsDeleteDecisionByParent = pathsDeleteDecisions.find(m_pDS2->fv(2).get_asInt());
9409       std::string path = m_pDS2->fv(1).get_asString();
9410 
9411       bool exists = false;
9412       if (URIUtils::IsPlugin(path))
9413       {
9414         SScanSettings settings;
9415         bool foundDirectly = false;
9416         ScraperPtr scraper = GetScraperForPath(path, settings, foundDirectly);
9417         if (scraper && CPluginDirectory::CheckExists(TranslateContent(scraper->Content()), path))
9418           exists = true;
9419       }
9420       else
9421         exists = CDirectory::Exists(path, false);
9422 
9423       if (((pathsDeleteDecision != pathsDeleteDecisions.end() && pathsDeleteDecision->second) ||
9424            (pathsDeleteDecision == pathsDeleteDecisions.end() && !exists)) &&
9425           ((pathsDeleteDecisionByParent != pathsDeleteDecisions.end() && pathsDeleteDecisionByParent->second) ||
9426            (pathsDeleteDecisionByParent == pathsDeleteDecisions.end())))
9427         strIds += m_pDS2->fv(0).get_asString() + ",";
9428 
9429       m_pDS2->next();
9430     }
9431     m_pDS2->close();
9432 
9433     if (!strIds.empty())
9434     {
9435       sql = PrepareSQL("DELETE FROM path WHERE idPath IN (%s)", StringUtils::TrimRight(strIds, ",").c_str());
9436       m_pDS->exec(sql);
9437       sql = "DELETE FROM tvshowlinkpath WHERE NOT EXISTS (SELECT 1 FROM path WHERE path.idPath = tvshowlinkpath.idPath)";
9438       m_pDS->exec(sql);
9439     }
9440 
9441     CLog::Log(LOGDEBUG, LOGDATABASE, "%s: Cleaning tvshow table", __FUNCTION__);
9442 
9443     std::string tvshowsToDelete;
9444     sql = "SELECT idShow FROM tvshow WHERE NOT EXISTS (SELECT 1 FROM tvshowlinkpath WHERE tvshowlinkpath.idShow = tvshow.idShow)";
9445     m_pDS->query(sql);
9446     while (!m_pDS->eof())
9447     {
9448       tvshowIDs.push_back(m_pDS->fv(0).get_asInt());
9449       tvshowsToDelete += m_pDS->fv(0).get_asString() + ",";
9450       m_pDS->next();
9451     }
9452     m_pDS->close();
9453     if (!tvshowsToDelete.empty())
9454     {
9455       sql = "DELETE FROM tvshow WHERE idShow IN (" + StringUtils::TrimRight(tvshowsToDelete, ",") + ")";
9456       m_pDS->exec(sql);
9457     }
9458 
9459     if (!musicVideoIDs.empty())
9460     {
9461       std::string musicVideosToDelete;
9462       for (const auto &i : musicVideoIDs)
9463         musicVideosToDelete += StringUtils::Format("%i,", i);
9464       musicVideosToDelete = "(" + StringUtils::TrimRight(musicVideosToDelete, ",") + ")";
9465 
9466       CLog::Log(LOGDEBUG, LOGDATABASE, "%s: Cleaning musicvideo table", __FUNCTION__);
9467       sql = "DELETE FROM musicvideo WHERE idMVideo IN " + musicVideosToDelete;
9468       m_pDS->exec(sql);
9469     }
9470 
9471     CLog::Log(LOGDEBUG, LOGDATABASE, "%s: Cleaning path table", __FUNCTION__);
9472     sql = StringUtils::Format("DELETE FROM path "
9473                                 "WHERE (strContent IS NULL OR strContent = '') "
9474                                   "AND (strSettings IS NULL OR strSettings = '') "
9475                                   "AND (strHash IS NULL OR strHash = '') "
9476                                   "AND (exclude IS NULL OR exclude != 1) "
9477                                   "AND (idParentPath IS NULL OR NOT EXISTS (SELECT 1 FROM (SELECT idPath FROM path) as parentPath WHERE parentPath.idPath = path.idParentPath)) " // MySQL only fix (#5007)
9478                                   "AND NOT EXISTS (SELECT 1 FROM files WHERE files.idPath = path.idPath) "
9479                                   "AND NOT EXISTS (SELECT 1 FROM tvshowlinkpath WHERE tvshowlinkpath.idPath = path.idPath) "
9480                                   "AND NOT EXISTS (SELECT 1 FROM movie WHERE movie.c%02d = path.idPath) "
9481                                   "AND NOT EXISTS (SELECT 1 FROM episode WHERE episode.c%02d = path.idPath) "
9482                                   "AND NOT EXISTS (SELECT 1 FROM musicvideo WHERE musicvideo.c%02d = path.idPath)"
9483                 , VIDEODB_ID_PARENTPATHID, VIDEODB_ID_EPISODE_PARENTPATHID, VIDEODB_ID_MUSICVIDEO_PARENTPATHID );
9484     m_pDS->exec(sql);
9485 
9486     CLog::Log(LOGDEBUG, LOGDATABASE, "%s: Cleaning genre table", __FUNCTION__);
9487     sql = "DELETE FROM genre "
9488             "WHERE NOT EXISTS (SELECT 1 FROM genre_link WHERE genre_link.genre_id = genre.genre_id)";
9489     m_pDS->exec(sql);
9490 
9491     CLog::Log(LOGDEBUG, LOGDATABASE, "%s: Cleaning country table", __FUNCTION__);
9492     sql = "DELETE FROM country WHERE NOT EXISTS (SELECT 1 FROM country_link WHERE country_link.country_id = country.country_id)";
9493     m_pDS->exec(sql);
9494 
9495     CLog::Log(LOGDEBUG, LOGDATABASE, "%s: Cleaning actor table of actors, directors and writers", __FUNCTION__);
9496     sql = "DELETE FROM actor "
9497             "WHERE NOT EXISTS (SELECT 1 FROM actor_link WHERE actor_link.actor_id = actor.actor_id) "
9498               "AND NOT EXISTS (SELECT 1 FROM director_link WHERE director_link.actor_id = actor.actor_id) "
9499               "AND NOT EXISTS (SELECT 1 FROM writer_link WHERE writer_link.actor_id = actor.actor_id)";
9500     m_pDS->exec(sql);
9501 
9502     CLog::Log(LOGDEBUG, LOGDATABASE, "%s: Cleaning studio table", __FUNCTION__);
9503     sql = "DELETE FROM studio "
9504             "WHERE NOT EXISTS (SELECT 1 FROM studio_link WHERE studio_link.studio_id = studio.studio_id)";
9505     m_pDS->exec(sql);
9506 
9507     CLog::Log(LOGDEBUG, LOGDATABASE, "%s: Cleaning set table", __FUNCTION__);
9508     sql = "DELETE FROM sets WHERE NOT EXISTS (SELECT 1 FROM movie WHERE movie.idSet = sets.idSet)";
9509     m_pDS->exec(sql);
9510 
9511     CommitTransaction();
9512 
9513     if (handle)
9514       handle->SetTitle(g_localizeStrings.Get(331));
9515 
9516     Compress(false);
9517 
9518     CUtil::DeleteVideoDatabaseDirectoryCache();
9519 
9520     time = XbmcThreads::SystemClockMillis() - time;
9521     CLog::Log(LOGINFO, "%s: Cleaning videodatabase done. Operation took %s", __FUNCTION__,
9522               StringUtils::SecondsToTimeString(time / 1000).c_str());
9523 
9524     for (const auto &i : movieIDs)
9525       AnnounceRemove(MediaTypeMovie, i, true);
9526 
9527     for (const auto &i : episodeIDs)
9528       AnnounceRemove(MediaTypeEpisode, i, true);
9529 
9530     for (const auto &i : tvshowIDs)
9531       AnnounceRemove(MediaTypeTvShow, i, true);
9532 
9533     for (const auto &i : musicVideoIDs)
9534       AnnounceRemove(MediaTypeMusicVideo, i, true);
9535   }
9536   catch (...)
9537   {
9538     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
9539     RollbackTransaction();
9540   }
9541   if (progress)
9542     progress->Close();
9543 
9544   CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnCleanFinished");
9545 }
9546 
CleanMediaType(const std::string & mediaType,const std::string & cleanableFileIDs,std::map<int,bool> & pathsDeleteDecisions,std::string & deletedFileIDs,bool silent)9547 std::vector<int> CVideoDatabase::CleanMediaType(const std::string &mediaType, const std::string &cleanableFileIDs,
9548                                                 std::map<int, bool> &pathsDeleteDecisions, std::string &deletedFileIDs, bool silent)
9549 {
9550   std::vector<int> cleanedIDs;
9551   if (mediaType.empty() || cleanableFileIDs.empty())
9552     return cleanedIDs;
9553 
9554   const std::string& table = mediaType;
9555   std::string idField;
9556   std::string parentPathIdField;
9557   bool isEpisode = false;
9558   if (mediaType == MediaTypeMovie)
9559   {
9560     idField = "idMovie";
9561     parentPathIdField = StringUtils::Format("%s.c%02d", table.c_str(), VIDEODB_ID_PARENTPATHID);
9562   }
9563   else if (mediaType == MediaTypeEpisode)
9564   {
9565     idField = "idEpisode";
9566     parentPathIdField = "showPath.idParentPath";
9567     isEpisode = true;
9568   }
9569   else if (mediaType == MediaTypeMusicVideo)
9570   {
9571     idField = "idMVideo";
9572     parentPathIdField = StringUtils::Format("%s.c%02d", table.c_str(), VIDEODB_ID_MUSICVIDEO_PARENTPATHID);
9573   }
9574   else
9575     return cleanedIDs;
9576 
9577   // now grab them media items
9578   std::string sql = PrepareSQL("SELECT %s.%s, %s.idFile, path.idPath, parentPath.strPath FROM %s "
9579                                  "JOIN files ON files.idFile = %s.idFile "
9580                                  "JOIN path ON path.idPath = files.idPath ",
9581                                table.c_str(), idField.c_str(), table.c_str(), table.c_str(),
9582                                table.c_str());
9583 
9584   if (isEpisode)
9585     sql += "JOIN tvshowlinkpath ON tvshowlinkpath.idShow = episode.idShow JOIN path AS showPath ON showPath.idPath=tvshowlinkpath.idPath ";
9586 
9587   sql += PrepareSQL("LEFT JOIN path as parentPath ON parentPath.idPath = %s "
9588                     "WHERE %s.idFile IN (%s)",
9589                     parentPathIdField.c_str(),
9590                     table.c_str(), cleanableFileIDs.c_str());
9591 
9592   VECSOURCES videoSources(*CMediaSourceSettings::GetInstance().GetSources("video"));
9593   CServiceBroker::GetMediaManager().GetRemovableDrives(videoSources);
9594 
9595   // map of parent path ID to boolean pair (if not exists and user choice)
9596   std::map<int, std::pair<bool, bool> > sourcePathsDeleteDecisions;
9597   m_pDS2->query(sql);
9598   while (!m_pDS2->eof())
9599   {
9600     bool del = true;
9601     if (m_pDS2->fv(3).get_isNull() == false)
9602     {
9603       std::string parentPath = m_pDS2->fv(3).get_asString();
9604 
9605       // try to find the source path the parent path belongs to
9606       SScanSettings scanSettings;
9607       std::string sourcePath;
9608       GetSourcePath(parentPath, sourcePath, scanSettings);
9609 
9610       bool bIsSourceName;
9611       bool sourceNotFound = (CUtil::GetMatchingSource(parentPath, videoSources, bIsSourceName) < 0);
9612 
9613       if (sourceNotFound && sourcePath.empty())
9614         sourcePath = parentPath;
9615 
9616       int sourcePathID = GetPathId(sourcePath);
9617       auto sourcePathsDeleteDecision = sourcePathsDeleteDecisions.find(sourcePathID);
9618       if (sourcePathsDeleteDecision == sourcePathsDeleteDecisions.end())
9619       {
9620         bool sourcePathNotExists = (sourceNotFound || !CDirectory::Exists(sourcePath, false));
9621         // if the parent path exists, the file will be deleted without asking
9622         // if the parent path doesn't exist or does not belong to a valid media source,
9623         // ask the user whether to remove all items it contained
9624         if (sourcePathNotExists)
9625         {
9626           // in silent mode assume that the files are just temporarily missing
9627           if (silent)
9628             del = false;
9629           else
9630           {
9631             CGUIDialogYesNo* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogYesNo>(WINDOW_DIALOG_YES_NO);
9632             if (pDialog != NULL)
9633             {
9634               CURL sourceUrl(sourcePath);
9635               pDialog->SetHeading(CVariant{15012});
9636               pDialog->SetText(CVariant{StringUtils::Format(g_localizeStrings.Get(15013).c_str(), sourceUrl.GetWithoutUserDetails().c_str())});
9637               pDialog->SetChoice(0, CVariant{15015});
9638               pDialog->SetChoice(1, CVariant{15014});
9639               pDialog->Open();
9640 
9641               del = !pDialog->IsConfirmed();
9642             }
9643           }
9644         }
9645 
9646         sourcePathsDeleteDecisions.insert(std::make_pair(sourcePathID, std::make_pair(sourcePathNotExists, del)));
9647         pathsDeleteDecisions.insert(std::make_pair(sourcePathID, sourcePathNotExists && del));
9648       }
9649       // the only reason not to delete the file is if the parent path doesn't
9650       // exist and the user decided to delete all the items it contained
9651       else if (sourcePathsDeleteDecision->second.first &&
9652                !sourcePathsDeleteDecision->second.second)
9653         del = false;
9654 
9655       if (scanSettings.parent_name)
9656         pathsDeleteDecisions.insert(std::make_pair(m_pDS2->fv(2).get_asInt(), del));
9657     }
9658 
9659     if (del)
9660     {
9661       deletedFileIDs += m_pDS2->fv(1).get_asString() + ",";
9662       cleanedIDs.push_back(m_pDS2->fv(0).get_asInt());
9663     }
9664 
9665     m_pDS2->next();
9666   }
9667   m_pDS2->close();
9668 
9669   return cleanedIDs;
9670 }
9671 
DumpToDummyFiles(const std::string & path)9672 void CVideoDatabase::DumpToDummyFiles(const std::string &path)
9673 {
9674   // get all tvshows
9675   CFileItemList items;
9676   GetTvShowsByWhere("videodb://tvshows/titles/", CDatabase::Filter(), items);
9677   std::string showPath = URIUtils::AddFileToFolder(path, "shows");
9678   CDirectory::Create(showPath);
9679   for (int i = 0; i < items.Size(); i++)
9680   {
9681     // create a folder in this directory
9682     std::string showName = CUtil::MakeLegalFileName(items[i]->GetVideoInfoTag()->m_strShowTitle);
9683     std::string TVFolder = URIUtils::AddFileToFolder(showPath, showName);
9684     if (CDirectory::Create(TVFolder))
9685     { // right - grab the episodes and dump them as well
9686       CFileItemList episodes;
9687       Filter filter(PrepareSQL("idShow=%i", items[i]->GetVideoInfoTag()->m_iDbId));
9688       GetEpisodesByWhere("videodb://tvshows/titles/", filter, episodes);
9689       for (int i = 0; i < episodes.Size(); i++)
9690       {
9691         CVideoInfoTag *tag = episodes[i]->GetVideoInfoTag();
9692         std::string episode = StringUtils::Format("%s.s%02de%02d.avi", showName.c_str(), tag->m_iSeason, tag->m_iEpisode);
9693         // and make a file
9694         std::string episodePath = URIUtils::AddFileToFolder(TVFolder, episode);
9695         CFile file;
9696         if (file.OpenForWrite(episodePath))
9697           file.Close();
9698       }
9699     }
9700   }
9701   // get all movies
9702   items.Clear();
9703   GetMoviesByWhere("videodb://movies/titles/", CDatabase::Filter(), items);
9704   std::string moviePath = URIUtils::AddFileToFolder(path, "movies");
9705   CDirectory::Create(moviePath);
9706   for (int i = 0; i < items.Size(); i++)
9707   {
9708     CVideoInfoTag *tag = items[i]->GetVideoInfoTag();
9709     std::string movie = StringUtils::Format("%s.avi", tag->m_strTitle.c_str());
9710     CFile file;
9711     if (file.OpenForWrite(URIUtils::AddFileToFolder(moviePath, movie)))
9712       file.Close();
9713   }
9714 }
9715 
ExportToXML(const std::string & path,bool singleFile,bool images,bool actorThumbs,bool overwrite)9716 void CVideoDatabase::ExportToXML(const std::string &path, bool singleFile /* = true */, bool images /* = false */, bool actorThumbs /* false */, bool overwrite /*=false*/)
9717 {
9718   int iFailCount = 0;
9719   CGUIDialogProgress *progress=NULL;
9720   try
9721   {
9722     if (nullptr == m_pDB)
9723       return;
9724     if (nullptr == m_pDS)
9725       return;
9726     if (nullptr == m_pDS2)
9727       return;
9728 
9729     // create a 3rd dataset as well as GetEpisodeDetails() etc. uses m_pDS2, and we need to do 3 nested queries on tv shows
9730     std::unique_ptr<Dataset> pDS;
9731     pDS.reset(m_pDB->CreateDataset());
9732     if (nullptr == pDS)
9733       return;
9734 
9735     std::unique_ptr<Dataset> pDS2;
9736     pDS2.reset(m_pDB->CreateDataset());
9737     if (nullptr == pDS2)
9738       return;
9739 
9740     // if we're exporting to a single folder, we export thumbs as well
9741     std::string exportRoot = URIUtils::AddFileToFolder(path, "kodi_videodb_" + CDateTime::GetCurrentDateTime().GetAsDBDate());
9742     std::string xmlFile = URIUtils::AddFileToFolder(exportRoot, "videodb.xml");
9743     std::string actorsDir = URIUtils::AddFileToFolder(exportRoot, "actors");
9744     std::string moviesDir = URIUtils::AddFileToFolder(exportRoot, "movies");
9745     std::string movieSetsDir = URIUtils::AddFileToFolder(exportRoot, "moviesets");
9746     std::string musicvideosDir = URIUtils::AddFileToFolder(exportRoot, "musicvideos");
9747     std::string tvshowsDir = URIUtils::AddFileToFolder(exportRoot, "tvshows");
9748     if (singleFile)
9749     {
9750       images = true;
9751       overwrite = false;
9752       actorThumbs = true;
9753       CDirectory::Remove(exportRoot);
9754       CDirectory::Create(exportRoot);
9755       CDirectory::Create(actorsDir);
9756       CDirectory::Create(moviesDir);
9757       CDirectory::Create(movieSetsDir);
9758       CDirectory::Create(musicvideosDir);
9759       CDirectory::Create(tvshowsDir);
9760     }
9761 
9762     progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
9763     // find all movies
9764     std::string sql = "select * from movie_view";
9765 
9766     m_pDS->query(sql);
9767 
9768     if (progress)
9769     {
9770       progress->SetHeading(CVariant{647});
9771       progress->SetLine(0, CVariant{650});
9772       progress->SetLine(1, CVariant{""});
9773       progress->SetLine(2, CVariant{""});
9774       progress->SetPercentage(0);
9775       progress->Open();
9776       progress->ShowProgressBar(true);
9777     }
9778 
9779     int total = m_pDS->num_rows();
9780     int current = 0;
9781 
9782     // create our xml document
9783     CXBMCTinyXML xmlDoc;
9784     TiXmlDeclaration decl("1.0", "UTF-8", "yes");
9785     xmlDoc.InsertEndChild(decl);
9786     TiXmlNode *pMain = NULL;
9787     if (!singleFile)
9788       pMain = &xmlDoc;
9789     else
9790     {
9791       TiXmlElement xmlMainElement("videodb");
9792       pMain = xmlDoc.InsertEndChild(xmlMainElement);
9793       XMLUtils::SetInt(pMain,"version", GetExportVersion());
9794     }
9795 
9796     while (!m_pDS->eof())
9797     {
9798       CVideoInfoTag movie = GetDetailsForMovie(m_pDS, VideoDbDetailsAll);
9799       // strip paths to make them relative
9800       if (StringUtils::StartsWith(movie.m_strTrailer, movie.m_strPath))
9801         movie.m_strTrailer = movie.m_strTrailer.substr(movie.m_strPath.size());
9802       std::map<std::string, std::string> artwork;
9803       if (GetArtForItem(movie.m_iDbId, movie.m_type, artwork) && singleFile)
9804       {
9805         TiXmlElement additionalNode("art");
9806         for (const auto &i : artwork)
9807           XMLUtils::SetString(&additionalNode, i.first.c_str(), i.second);
9808         movie.Save(pMain, "movie", true, &additionalNode);
9809       }
9810       else
9811         movie.Save(pMain, "movie", singleFile);
9812 
9813       // reset old skip state
9814       bool bSkip = false;
9815 
9816       if (progress)
9817       {
9818         progress->SetLine(1, CVariant{movie.m_strTitle});
9819         progress->SetPercentage(current * 100 / total);
9820         progress->Progress();
9821         if (progress->IsCanceled())
9822         {
9823           progress->Close();
9824           m_pDS->close();
9825           return;
9826         }
9827       }
9828 
9829       CFileItem item(movie.m_strFileNameAndPath,false);
9830       if (!singleFile && CUtil::SupportsWriteFileOperations(movie.m_strFileNameAndPath))
9831       {
9832         if (!item.Exists(false))
9833         {
9834           CLog::Log(LOGINFO, "%s - Not exporting item %s as it does not exist", __FUNCTION__, movie.m_strFileNameAndPath.c_str());
9835           bSkip = true;
9836         }
9837         else
9838         {
9839           std::string nfoFile(URIUtils::ReplaceExtension(item.GetTBNFile(), ".nfo"));
9840 
9841           if (item.IsOpticalMediaFile())
9842           {
9843             nfoFile = URIUtils::AddFileToFolder(
9844                                     URIUtils::GetParentPath(nfoFile),
9845                                     URIUtils::GetFileName(nfoFile));
9846           }
9847 
9848           if (overwrite || !CFile::Exists(nfoFile, false))
9849           {
9850             if(!xmlDoc.SaveFile(nfoFile))
9851             {
9852               CLog::Log(LOGERROR, "%s: Movie nfo export failed! ('%s')", __FUNCTION__, nfoFile.c_str());
9853               CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(20302), nfoFile);
9854               iFailCount++;
9855             }
9856           }
9857         }
9858       }
9859       if (!singleFile)
9860       {
9861         xmlDoc.Clear();
9862         TiXmlDeclaration decl("1.0", "UTF-8", "yes");
9863         xmlDoc.InsertEndChild(decl);
9864       }
9865 
9866       if (images && !bSkip)
9867       {
9868         if (singleFile)
9869         {
9870           std::string strFileName(movie.m_strTitle);
9871           if (movie.HasYear())
9872             strFileName += StringUtils::Format("_%i", movie.GetYear());
9873           item.SetPath(GetSafeFile(moviesDir, strFileName) + ".avi");
9874         }
9875         for (const auto &i : artwork)
9876         {
9877           std::string savedThumb = item.GetLocalArt(i.first, false);
9878           CTextureCache::GetInstance().Export(i.second, savedThumb, overwrite);
9879         }
9880         if (actorThumbs)
9881           ExportActorThumbs(actorsDir, movie, !singleFile, overwrite);
9882       }
9883       m_pDS->next();
9884       current++;
9885     }
9886     m_pDS->close();
9887 
9888     if (!singleFile)
9889       movieSetsDir = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
9890           CSettings::SETTING_VIDEOLIBRARY_MOVIESETSFOLDER);
9891     if (images && !movieSetsDir.empty())
9892     {
9893       // find all movie sets
9894       sql = "select idSet, strSet from sets";
9895 
9896       m_pDS->query(sql);
9897 
9898       total = m_pDS->num_rows();
9899       current = 0;
9900 
9901       while (!m_pDS->eof())
9902       {
9903         std::string title = m_pDS->fv("strSet").get_asString();
9904 
9905         if (progress)
9906         {
9907           progress->SetLine(1, CVariant{title});
9908           progress->SetPercentage(current * 100 / total);
9909           progress->Progress();
9910           if (progress->IsCanceled())
9911           {
9912             progress->Close();
9913             m_pDS->close();
9914             return;
9915           }
9916         }
9917 
9918         std::string itemPath = URIUtils::AddFileToFolder(movieSetsDir,
9919             CUtil::MakeLegalFileName(title, LEGAL_WIN32_COMPAT));
9920         if (CDirectory::Exists(itemPath) || CDirectory::Create(itemPath))
9921         {
9922           std::map<std::string, std::string> artwork;
9923           GetArtForItem(m_pDS->fv("idSet").get_asInt(), MediaTypeVideoCollection, artwork);
9924           for (const auto& art : artwork)
9925           {
9926             std::string savedThumb = URIUtils::AddFileToFolder(itemPath, art.first);
9927             CTextureCache::GetInstance().Export(art.second, savedThumb, overwrite);
9928           }
9929         }
9930         else
9931           CLog::Log(LOGDEBUG,
9932             "CVideoDatabase::%s - Not exporting movie set '%s' as could not create folder '%s'",
9933             __FUNCTION__, title.c_str(), itemPath.c_str());
9934         m_pDS->next();
9935         current++;
9936       }
9937       m_pDS->close();
9938     }
9939 
9940     // find all musicvideos
9941     sql = "select * from musicvideo_view";
9942 
9943     m_pDS->query(sql);
9944 
9945     total = m_pDS->num_rows();
9946     current = 0;
9947 
9948     while (!m_pDS->eof())
9949     {
9950       CVideoInfoTag movie = GetDetailsForMusicVideo(m_pDS, VideoDbDetailsAll);
9951       std::map<std::string, std::string> artwork;
9952       if (GetArtForItem(movie.m_iDbId, movie.m_type, artwork) && singleFile)
9953       {
9954         TiXmlElement additionalNode("art");
9955         for (const auto &i : artwork)
9956           XMLUtils::SetString(&additionalNode, i.first.c_str(), i.second);
9957         movie.Save(pMain, "musicvideo", true, &additionalNode);
9958       }
9959       else
9960         movie.Save(pMain, "musicvideo", singleFile);
9961 
9962       // reset old skip state
9963       bool bSkip = false;
9964 
9965       if (progress)
9966       {
9967         progress->SetLine(1, CVariant{movie.m_strTitle});
9968         progress->SetPercentage(current * 100 / total);
9969         progress->Progress();
9970         if (progress->IsCanceled())
9971         {
9972           progress->Close();
9973           m_pDS->close();
9974           return;
9975         }
9976       }
9977 
9978       CFileItem item(movie.m_strFileNameAndPath,false);
9979       if (!singleFile && CUtil::SupportsWriteFileOperations(movie.m_strFileNameAndPath))
9980       {
9981         if (!item.Exists(false))
9982         {
9983           CLog::Log(LOGINFO, "%s - Not exporting item %s as it does not exist", __FUNCTION__, movie.m_strFileNameAndPath.c_str());
9984           bSkip = true;
9985         }
9986         else
9987         {
9988           std::string nfoFile(URIUtils::ReplaceExtension(item.GetTBNFile(), ".nfo"));
9989 
9990           if (overwrite || !CFile::Exists(nfoFile, false))
9991           {
9992             if(!xmlDoc.SaveFile(nfoFile))
9993             {
9994               CLog::Log(LOGERROR, "%s: Musicvideo nfo export failed! ('%s')", __FUNCTION__, nfoFile.c_str());
9995               CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(20302), nfoFile);
9996               iFailCount++;
9997             }
9998           }
9999         }
10000       }
10001       if (!singleFile)
10002       {
10003         xmlDoc.Clear();
10004         TiXmlDeclaration decl("1.0", "UTF-8", "yes");
10005         xmlDoc.InsertEndChild(decl);
10006       }
10007       if (images && !bSkip)
10008       {
10009         if (singleFile)
10010         {
10011           std::string strFileName(StringUtils::Join(movie.m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator) + "." + movie.m_strTitle);
10012           if (movie.HasYear())
10013             strFileName += StringUtils::Format("_%i", movie.GetYear());
10014           item.SetPath(GetSafeFile(musicvideosDir, strFileName) + ".avi");
10015         }
10016         for (const auto &i : artwork)
10017         {
10018           std::string savedThumb = item.GetLocalArt(i.first, false);
10019           CTextureCache::GetInstance().Export(i.second, savedThumb, overwrite);
10020         }
10021       }
10022       m_pDS->next();
10023       current++;
10024     }
10025     m_pDS->close();
10026 
10027     // repeat for all tvshows
10028     sql = "SELECT * FROM tvshow_view";
10029     m_pDS->query(sql);
10030 
10031     total = m_pDS->num_rows();
10032     current = 0;
10033 
10034     while (!m_pDS->eof())
10035     {
10036       CVideoInfoTag tvshow = GetDetailsForTvShow(m_pDS, VideoDbDetailsAll);
10037       GetTvShowNamedSeasons(tvshow.m_iDbId, tvshow.m_namedSeasons);
10038 
10039       std::map<int, std::map<std::string, std::string> > seasonArt;
10040       GetTvShowSeasonArt(tvshow.m_iDbId, seasonArt);
10041 
10042       std::map<std::string, std::string> artwork;
10043       if (GetArtForItem(tvshow.m_iDbId, tvshow.m_type, artwork) && singleFile)
10044       {
10045         TiXmlElement additionalNode("art");
10046         for (const auto &i : artwork)
10047           XMLUtils::SetString(&additionalNode, i.first.c_str(), i.second);
10048         for (const auto &i : seasonArt)
10049         {
10050           TiXmlElement seasonNode("season");
10051           seasonNode.SetAttribute("num", i.first);
10052           for (const auto &j : i.second)
10053             XMLUtils::SetString(&seasonNode, j.first.c_str(), j.second);
10054           additionalNode.InsertEndChild(seasonNode);
10055         }
10056         tvshow.Save(pMain, "tvshow", true, &additionalNode);
10057       }
10058       else
10059         tvshow.Save(pMain, "tvshow", singleFile);
10060 
10061       // reset old skip state
10062       bool bSkip = false;
10063 
10064       if (progress)
10065       {
10066         progress->SetLine(1, CVariant{tvshow.m_strTitle});
10067         progress->SetPercentage(current * 100 / total);
10068         progress->Progress();
10069         if (progress->IsCanceled())
10070         {
10071           progress->Close();
10072           m_pDS->close();
10073           return;
10074         }
10075       }
10076 
10077       CFileItem item(tvshow.m_strPath, true);
10078       if (!singleFile && CUtil::SupportsWriteFileOperations(tvshow.m_strPath))
10079       {
10080         if (!item.Exists(false))
10081         {
10082           CLog::Log(LOGINFO, "%s - Not exporting item %s as it does not exist", __FUNCTION__, tvshow.m_strPath.c_str());
10083           bSkip = true;
10084         }
10085         else
10086         {
10087           std::string nfoFile = URIUtils::AddFileToFolder(tvshow.m_strPath, "tvshow.nfo");
10088 
10089           if (overwrite || !CFile::Exists(nfoFile, false))
10090           {
10091             if(!xmlDoc.SaveFile(nfoFile))
10092             {
10093               CLog::Log(LOGERROR, "%s: TVShow nfo export failed! ('%s')", __FUNCTION__, nfoFile.c_str());
10094               CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(20302), nfoFile);
10095               iFailCount++;
10096             }
10097           }
10098         }
10099       }
10100       if (!singleFile)
10101       {
10102         xmlDoc.Clear();
10103         TiXmlDeclaration decl("1.0", "UTF-8", "yes");
10104         xmlDoc.InsertEndChild(decl);
10105       }
10106       if (images && !bSkip)
10107       {
10108         if (singleFile)
10109           item.SetPath(GetSafeFile(tvshowsDir, tvshow.m_strTitle));
10110 
10111         for (const auto &i : artwork)
10112         {
10113           std::string savedThumb = item.GetLocalArt(i.first, true);
10114           CTextureCache::GetInstance().Export(i.second, savedThumb, overwrite);
10115         }
10116 
10117         if (actorThumbs)
10118           ExportActorThumbs(actorsDir, tvshow, !singleFile, overwrite);
10119 
10120         // export season thumbs
10121         for (const auto &i : seasonArt)
10122         {
10123           std::string seasonThumb;
10124           if (i.first == -1)
10125             seasonThumb = "season-all";
10126           else if (i.first == 0)
10127             seasonThumb = "season-specials";
10128           else
10129             seasonThumb = StringUtils::Format("season%02i", i.first);
10130           for (const auto &j : i.second)
10131           {
10132             std::string savedThumb(item.GetLocalArt(seasonThumb + "-" + j.first, true));
10133             if (!i.second.empty())
10134               CTextureCache::GetInstance().Export(j.second, savedThumb, overwrite);
10135           }
10136         }
10137       }
10138 
10139       // now save the episodes from this show
10140       sql = PrepareSQL("select * from episode_view where idShow=%i order by strFileName, idEpisode",tvshow.m_iDbId);
10141       pDS->query(sql);
10142       std::string showDir(item.GetPath());
10143 
10144       while (!pDS->eof())
10145       {
10146         CVideoInfoTag episode = GetDetailsForEpisode(pDS, VideoDbDetailsAll);
10147         std::map<std::string, std::string> artwork;
10148         if (GetArtForItem(episode.m_iDbId, MediaTypeEpisode, artwork) && singleFile)
10149         {
10150           TiXmlElement additionalNode("art");
10151           for (const auto &i : artwork)
10152             XMLUtils::SetString(&additionalNode, i.first.c_str(), i.second);
10153           episode.Save(pMain->LastChild(), "episodedetails", true, &additionalNode);
10154         }
10155         else if (singleFile)
10156           episode.Save(pMain->LastChild(), "episodedetails", singleFile);
10157         else
10158           episode.Save(pMain, "episodedetails", singleFile);
10159         pDS->next();
10160         // multi-episode files need dumping to the same XML
10161         while (!singleFile && !pDS->eof() &&
10162                episode.m_iFileId == pDS->fv("idFile").get_asInt())
10163         {
10164           episode = GetDetailsForEpisode(pDS, VideoDbDetailsAll);
10165           episode.Save(pMain, "episodedetails", singleFile);
10166           pDS->next();
10167         }
10168 
10169         // reset old skip state
10170         bool bSkip = false;
10171 
10172         CFileItem item(episode.m_strFileNameAndPath, false);
10173         if (!singleFile && CUtil::SupportsWriteFileOperations(episode.m_strFileNameAndPath))
10174         {
10175           if (!item.Exists(false))
10176           {
10177             CLog::Log(LOGINFO, "%s - Not exporting item %s as it does not exist", __FUNCTION__, episode.m_strFileNameAndPath.c_str());
10178             bSkip = true;
10179           }
10180           else
10181           {
10182             std::string nfoFile(URIUtils::ReplaceExtension(item.GetTBNFile(), ".nfo"));
10183 
10184             if (overwrite || !CFile::Exists(nfoFile, false))
10185             {
10186               if(!xmlDoc.SaveFile(nfoFile))
10187               {
10188                 CLog::Log(LOGERROR, "%s: Episode nfo export failed! ('%s')", __FUNCTION__, nfoFile.c_str());
10189                 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(20302), nfoFile);
10190                 iFailCount++;
10191               }
10192             }
10193           }
10194         }
10195         if (!singleFile)
10196         {
10197           xmlDoc.Clear();
10198           TiXmlDeclaration decl("1.0", "UTF-8", "yes");
10199           xmlDoc.InsertEndChild(decl);
10200         }
10201 
10202         if (images && !bSkip)
10203         {
10204           if (singleFile)
10205           {
10206             std::string epName = StringUtils::Format("s%02ie%02i.avi", episode.m_iSeason, episode.m_iEpisode);
10207             item.SetPath(URIUtils::AddFileToFolder(showDir, epName));
10208           }
10209           for (const auto &i : artwork)
10210           {
10211             std::string savedThumb = item.GetLocalArt(i.first, false);
10212             CTextureCache::GetInstance().Export(i.second, savedThumb, overwrite);
10213           }
10214           if (actorThumbs)
10215             ExportActorThumbs(actorsDir, episode, !singleFile, overwrite);
10216         }
10217       }
10218       pDS->close();
10219       m_pDS->next();
10220       current++;
10221     }
10222     m_pDS->close();
10223 
10224     if (!singleFile && progress)
10225     {
10226       progress->SetPercentage(100);
10227       progress->Progress();
10228     }
10229 
10230     if (singleFile)
10231     {
10232       // now dump path info
10233       std::set<std::string> paths;
10234       GetPaths(paths);
10235       TiXmlElement xmlPathElement("paths");
10236       TiXmlNode *pPaths = pMain->InsertEndChild(xmlPathElement);
10237       for (const auto &i : paths)
10238       {
10239         bool foundDirectly = false;
10240         SScanSettings settings;
10241         ScraperPtr info = GetScraperForPath(i, settings, foundDirectly);
10242         if (info && foundDirectly)
10243         {
10244           TiXmlElement xmlPathElement2("path");
10245           TiXmlNode *pPath = pPaths->InsertEndChild(xmlPathElement2);
10246           XMLUtils::SetString(pPath,"url", i);
10247           XMLUtils::SetInt(pPath,"scanrecursive", settings.recurse);
10248           XMLUtils::SetBoolean(pPath,"usefoldernames", settings.parent_name);
10249           XMLUtils::SetString(pPath,"content", TranslateContent(info->Content()));
10250           XMLUtils::SetString(pPath,"scraperpath", info->ID());
10251         }
10252       }
10253       xmlDoc.SaveFile(xmlFile);
10254     }
10255     CVariant data;
10256     if (singleFile)
10257     {
10258       data["root"] = exportRoot;
10259       data["file"] = xmlFile;
10260       if (iFailCount > 0)
10261         data["failcount"] = iFailCount;
10262     }
10263     CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnExport",
10264                                                        data);
10265   }
10266   catch (...)
10267   {
10268     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
10269     iFailCount++;
10270   }
10271 
10272   if (progress)
10273     progress->Close();
10274 
10275   if (iFailCount > 0)
10276     HELPERS::ShowOKDialogText(CVariant{647}, CVariant{StringUtils::Format(g_localizeStrings.Get(15011).c_str(), iFailCount)});
10277 }
10278 
ExportActorThumbs(const std::string & strDir,const CVideoInfoTag & tag,bool singleFiles,bool overwrite)10279 void CVideoDatabase::ExportActorThumbs(const std::string &strDir, const CVideoInfoTag &tag, bool singleFiles, bool overwrite /*=false*/)
10280 {
10281   std::string strPath(strDir);
10282   if (singleFiles)
10283   {
10284     strPath = URIUtils::AddFileToFolder(tag.m_strPath, ".actors");
10285     if (!CDirectory::Exists(strPath))
10286     {
10287       CDirectory::Create(strPath);
10288       CFile::SetHidden(strPath, true);
10289     }
10290   }
10291 
10292   for (const auto &i : tag.m_cast)
10293   {
10294     CFileItem item;
10295     item.SetLabel(i.strName);
10296     if (!i.thumb.empty())
10297     {
10298       std::string thumbFile(GetSafeFile(strPath, i.strName));
10299       CTextureCache::GetInstance().Export(i.thumb, thumbFile, overwrite);
10300     }
10301   }
10302 }
10303 
ImportFromXML(const std::string & path)10304 void CVideoDatabase::ImportFromXML(const std::string &path)
10305 {
10306   CGUIDialogProgress *progress=NULL;
10307   try
10308   {
10309     if (nullptr == m_pDB)
10310       return;
10311     if (nullptr == m_pDS)
10312       return;
10313 
10314     CXBMCTinyXML xmlDoc;
10315     if (!xmlDoc.LoadFile(URIUtils::AddFileToFolder(path, "videodb.xml")))
10316       return;
10317 
10318     TiXmlElement *root = xmlDoc.RootElement();
10319     if (!root) return;
10320 
10321     progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
10322     if (progress)
10323     {
10324       progress->SetHeading(CVariant{648});
10325       progress->SetLine(0, CVariant{649});
10326       progress->SetLine(1, CVariant{330});
10327       progress->SetLine(2, CVariant{""});
10328       progress->SetPercentage(0);
10329       progress->Open();
10330       progress->ShowProgressBar(true);
10331     }
10332 
10333     int iVersion = 0;
10334     XMLUtils::GetInt(root, "version", iVersion);
10335 
10336     CLog::Log(LOGINFO, "%s: Starting import (export version = %i)", __FUNCTION__, iVersion);
10337 
10338     TiXmlElement *movie = root->FirstChildElement();
10339     int current = 0;
10340     int total = 0;
10341     // first count the number of items...
10342     while (movie)
10343     {
10344       if (StringUtils::CompareNoCase(movie->Value(), MediaTypeMovie, 5) == 0 ||
10345           StringUtils::CompareNoCase(movie->Value(), MediaTypeTvShow, 6) == 0 ||
10346           StringUtils::CompareNoCase(movie->Value(), MediaTypeMusicVideo, 10) == 0)
10347         total++;
10348       movie = movie->NextSiblingElement();
10349     }
10350 
10351     std::string actorsDir(URIUtils::AddFileToFolder(path, "actors"));
10352     std::string moviesDir(URIUtils::AddFileToFolder(path, "movies"));
10353     std::string movieSetsDir(URIUtils::AddFileToFolder(path, "moviesets"));
10354     std::string musicvideosDir(URIUtils::AddFileToFolder(path, "musicvideos"));
10355     std::string tvshowsDir(URIUtils::AddFileToFolder(path, "tvshows"));
10356     CVideoInfoScanner scanner;
10357     // add paths first (so we have scraper settings available)
10358     TiXmlElement *path = root->FirstChildElement("paths");
10359     path = path->FirstChildElement();
10360     while (path)
10361     {
10362       std::string strPath;
10363       if (XMLUtils::GetString(path,"url",strPath) && !strPath.empty())
10364         AddPath(strPath);
10365 
10366       std::string content;
10367       if (XMLUtils::GetString(path,"content", content) && !content.empty())
10368       { // check the scraper exists, if so store the path
10369         AddonPtr addon;
10370         std::string id;
10371         XMLUtils::GetString(path,"scraperpath",id);
10372         if (CServiceBroker::GetAddonMgr().GetAddon(id, addon, ADDON::ADDON_UNKNOWN,
10373                                                    ADDON::OnlyEnabled::YES))
10374         {
10375           SScanSettings settings;
10376           ScraperPtr scraper = std::dynamic_pointer_cast<CScraper>(addon);
10377           // FIXME: scraper settings are not exported?
10378           scraper->SetPathSettings(TranslateContent(content), "");
10379           XMLUtils::GetInt(path,"scanrecursive",settings.recurse);
10380           XMLUtils::GetBoolean(path,"usefoldernames",settings.parent_name);
10381           SetScraperForPath(strPath,scraper,settings);
10382         }
10383       }
10384       path = path->NextSiblingElement();
10385     }
10386     movie = root->FirstChildElement();
10387     while (movie)
10388     {
10389       CVideoInfoTag info;
10390       if (StringUtils::CompareNoCase(movie->Value(), MediaTypeMovie, 5) == 0)
10391       {
10392         info.Load(movie);
10393         CFileItem item(info);
10394         bool useFolders = info.m_basePath.empty() ? LookupByFolders(item.GetPath()) : false;
10395         std::string filename = info.m_strTitle;
10396         if (info.HasYear())
10397           filename += StringUtils::Format("_%i", info.GetYear());
10398         CFileItem artItem(item);
10399         artItem.SetPath(GetSafeFile(moviesDir, filename) + ".avi");
10400         scanner.GetArtwork(&artItem, CONTENT_MOVIES, useFolders, true, actorsDir);
10401         item.SetArt(artItem.GetArt());
10402         if (!item.GetVideoInfoTag()->m_set.title.empty())
10403         {
10404           std::string setPath = URIUtils::AddFileToFolder(movieSetsDir,
10405               CUtil::MakeLegalFileName(item.GetVideoInfoTag()->m_set.title, LEGAL_WIN32_COMPAT));
10406           if (CDirectory::Exists(setPath))
10407           {
10408             CGUIListItem::ArtMap setArt;
10409             CFileItem artItem(setPath, true);
10410             for (const auto& artType : CVideoThumbLoader::GetArtTypes(MediaTypeVideoCollection))
10411             {
10412               std::string artPath = CVideoThumbLoader::GetLocalArt(artItem, artType, true);
10413               if (!artPath.empty())
10414               {
10415                 setArt[artType] = artPath;
10416               }
10417             }
10418             item.AppendArt(setArt, "set");
10419           }
10420         }
10421         scanner.AddVideo(&item, CONTENT_MOVIES, useFolders, true, NULL, true);
10422         current++;
10423       }
10424       else if (StringUtils::CompareNoCase(movie->Value(), MediaTypeMusicVideo, 10) == 0)
10425       {
10426         info.Load(movie);
10427         CFileItem item(info);
10428         bool useFolders = info.m_basePath.empty() ? LookupByFolders(item.GetPath()) : false;
10429         std::string filename = StringUtils::Join(info.m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator) + "." + info.m_strTitle;
10430         if (info.HasYear())
10431           filename += StringUtils::Format("_%i", info.GetYear());
10432         CFileItem artItem(item);
10433         artItem.SetPath(GetSafeFile(musicvideosDir, filename) + ".avi");
10434         scanner.GetArtwork(&artItem, CONTENT_MUSICVIDEOS, useFolders, true, actorsDir);
10435         item.SetArt(artItem.GetArt());
10436         scanner.AddVideo(&item, CONTENT_MUSICVIDEOS, useFolders, true, NULL, true);
10437         current++;
10438       }
10439       else if (StringUtils::CompareNoCase(movie->Value(), MediaTypeTvShow, 6) == 0)
10440       {
10441         // load the TV show in.  NOTE: This deletes all episodes under the TV Show, which may not be
10442         // what we desire.  It may make better sense to only delete (or even better, update) the show information
10443         info.Load(movie);
10444         URIUtils::AddSlashAtEnd(info.m_strPath);
10445         DeleteTvShow(info.m_strPath);
10446         CFileItem showItem(info);
10447         bool useFolders = info.m_basePath.empty() ? LookupByFolders(showItem.GetPath(), true) : false;
10448         CFileItem artItem(showItem);
10449         std::string artPath(GetSafeFile(tvshowsDir, info.m_strTitle));
10450         artItem.SetPath(artPath);
10451         scanner.GetArtwork(&artItem, CONTENT_TVSHOWS, useFolders, true, actorsDir);
10452         showItem.SetArt(artItem.GetArt());
10453         int showID = scanner.AddVideo(&showItem, CONTENT_TVSHOWS, useFolders, true, NULL, true);
10454         // season artwork
10455         std::map<int, std::map<std::string, std::string> > seasonArt;
10456         artItem.GetVideoInfoTag()->m_strPath = artPath;
10457         scanner.GetSeasonThumbs(*artItem.GetVideoInfoTag(), seasonArt, CVideoThumbLoader::GetArtTypes(MediaTypeSeason), true);
10458         for (const auto &i : seasonArt)
10459         {
10460           int seasonID = AddSeason(showID, i.first);
10461           SetArtForItem(seasonID, MediaTypeSeason, i.second);
10462         }
10463         current++;
10464         // now load the episodes
10465         TiXmlElement *episode = movie->FirstChildElement("episodedetails");
10466         while (episode)
10467         {
10468           // no need to delete the episode info, due to the above deletion
10469           CVideoInfoTag info;
10470           info.Load(episode);
10471           CFileItem item(info);
10472           std::string filename = StringUtils::Format("s%02ie%02i.avi", info.m_iSeason, info.m_iEpisode);
10473           CFileItem artItem(item);
10474           artItem.SetPath(GetSafeFile(artPath, filename));
10475           scanner.GetArtwork(&artItem, CONTENT_TVSHOWS, useFolders, true, actorsDir);
10476           item.SetArt(artItem.GetArt());
10477           scanner.AddVideo(&item,CONTENT_TVSHOWS, false, false, showItem.GetVideoInfoTag(), true);
10478           episode = episode->NextSiblingElement("episodedetails");
10479         }
10480       }
10481       movie = movie->NextSiblingElement();
10482       if (progress && total)
10483       {
10484         progress->SetPercentage(current * 100 / total);
10485         progress->SetLine(2, CVariant{info.m_strTitle});
10486         progress->Progress();
10487         if (progress->IsCanceled())
10488         {
10489           progress->Close();
10490           return;
10491         }
10492       }
10493     }
10494   }
10495   catch (...)
10496   {
10497     CLog::Log(LOGERROR, "%s failed", __FUNCTION__);
10498   }
10499   if (progress)
10500     progress->Close();
10501 }
10502 
ImportArtFromXML(const TiXmlNode * node,std::map<std::string,std::string> & artwork)10503 bool CVideoDatabase::ImportArtFromXML(const TiXmlNode *node, std::map<std::string, std::string> &artwork)
10504 {
10505   if (!node) return false;
10506   const TiXmlNode *art = node->FirstChild();
10507   while (art && art->FirstChild())
10508   {
10509     artwork.insert(make_pair(art->ValueStr(), art->FirstChild()->ValueStr()));
10510     art = art->NextSibling();
10511   }
10512   return !artwork.empty();
10513 }
10514 
ConstructPath(std::string & strDest,const std::string & strPath,const std::string & strFileName)10515 void CVideoDatabase::ConstructPath(std::string& strDest, const std::string& strPath, const std::string& strFileName)
10516 {
10517   if (URIUtils::IsStack(strFileName) ||
10518       URIUtils::IsInArchive(strFileName) || URIUtils::IsPlugin(strPath))
10519     strDest = strFileName;
10520   else
10521     strDest = URIUtils::AddFileToFolder(strPath, strFileName);
10522 }
10523 
SplitPath(const std::string & strFileNameAndPath,std::string & strPath,std::string & strFileName)10524 void CVideoDatabase::SplitPath(const std::string& strFileNameAndPath, std::string& strPath, std::string& strFileName)
10525 {
10526   if (URIUtils::IsStack(strFileNameAndPath) || StringUtils::StartsWithNoCase(strFileNameAndPath, "rar://") || StringUtils::StartsWithNoCase(strFileNameAndPath, "zip://"))
10527   {
10528     URIUtils::GetParentPath(strFileNameAndPath,strPath);
10529     strFileName = strFileNameAndPath;
10530   }
10531   else if (URIUtils::IsPlugin(strFileNameAndPath))
10532   {
10533     CURL url(strFileNameAndPath);
10534     strPath = url.GetOptions().empty() ? url.GetWithoutFilename() : url.GetWithoutOptions();
10535     strFileName = strFileNameAndPath;
10536   }
10537   else
10538   {
10539     URIUtils::Split(strFileNameAndPath, strPath, strFileName);
10540     // Keep protocol options as part of the path
10541     if (URIUtils::IsURL(strFileNameAndPath))
10542     {
10543       CURL url(strFileNameAndPath);
10544       if (!url.GetProtocolOptions().empty())
10545         strPath += "|" + url.GetProtocolOptions();
10546     }
10547   }
10548 }
10549 
InvalidatePathHash(const std::string & strPath)10550 void CVideoDatabase::InvalidatePathHash(const std::string& strPath)
10551 {
10552   SScanSettings settings;
10553   bool foundDirectly;
10554   ScraperPtr info = GetScraperForPath(strPath,settings,foundDirectly);
10555   SetPathHash(strPath,"");
10556   if (!info)
10557     return;
10558   if (info->Content() == CONTENT_TVSHOWS || (info->Content() == CONTENT_MOVIES && !foundDirectly)) // if we scan by folder name we need to invalidate parent as well
10559   {
10560     if (info->Content() == CONTENT_TVSHOWS || settings.parent_name_root)
10561     {
10562       std::string strParent;
10563       if (URIUtils::GetParentPath(strPath, strParent) && (!URIUtils::IsPlugin(strPath) || !CURL(strParent).GetHostName().empty()))
10564         SetPathHash(strParent, "");
10565     }
10566   }
10567 }
10568 
CommitTransaction()10569 bool CVideoDatabase::CommitTransaction()
10570 {
10571   if (CDatabase::CommitTransaction())
10572   { // number of items in the db has likely changed, so recalculate
10573     GUIINFO::CLibraryGUIInfo& guiInfo = CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetLibraryInfoProvider();
10574     guiInfo.SetLibraryBool(LIBRARY_HAS_MOVIES, HasContent(VIDEODB_CONTENT_MOVIES));
10575     guiInfo.SetLibraryBool(LIBRARY_HAS_TVSHOWS, HasContent(VIDEODB_CONTENT_TVSHOWS));
10576     guiInfo.SetLibraryBool(LIBRARY_HAS_MUSICVIDEOS, HasContent(VIDEODB_CONTENT_MUSICVIDEOS));
10577     return true;
10578   }
10579   return false;
10580 }
10581 
SetSingleValue(VIDEODB_CONTENT_TYPE type,int dbId,int dbField,const std::string & strValue)10582 bool CVideoDatabase::SetSingleValue(VIDEODB_CONTENT_TYPE type, int dbId, int dbField, const std::string &strValue)
10583 {
10584   std::string strSQL;
10585   try
10586   {
10587     if (nullptr == m_pDB || nullptr == m_pDS)
10588       return false;
10589 
10590     std::string strTable, strField;
10591     if (type == VIDEODB_CONTENT_MOVIES)
10592     {
10593       strTable = "movie";
10594       strField = "idMovie";
10595     }
10596     else if (type == VIDEODB_CONTENT_TVSHOWS)
10597     {
10598       strTable = "tvshow";
10599       strField = "idShow";
10600     }
10601     else if (type == VIDEODB_CONTENT_EPISODES)
10602     {
10603       strTable = "episode";
10604       strField = "idEpisode";
10605     }
10606     else if (type == VIDEODB_CONTENT_MUSICVIDEOS)
10607     {
10608       strTable = "musicvideo";
10609       strField = "idMVideo";
10610     }
10611 
10612     if (strTable.empty())
10613       return false;
10614 
10615     return SetSingleValue(strTable, StringUtils::Format("c%02u", dbField), strValue, strField, dbId);
10616   }
10617   catch (...)
10618   {
10619     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, strSQL.c_str());
10620   }
10621   return false;
10622 }
10623 
SetSingleValue(VIDEODB_CONTENT_TYPE type,int dbId,Field dbField,const std::string & strValue)10624 bool CVideoDatabase::SetSingleValue(VIDEODB_CONTENT_TYPE type, int dbId, Field dbField, const std::string &strValue)
10625 {
10626   MediaType mediaType = DatabaseUtils::MediaTypeFromVideoContentType(type);
10627   if (mediaType == MediaTypeNone)
10628     return false;
10629 
10630   int dbFieldIndex = DatabaseUtils::GetField(dbField, mediaType);
10631   if (dbFieldIndex < 0)
10632     return false;
10633 
10634   return SetSingleValue(type, dbId, dbFieldIndex, strValue);
10635 }
10636 
SetSingleValue(const std::string & table,const std::string & fieldName,const std::string & strValue,const std::string & conditionName,int conditionValue)10637 bool CVideoDatabase::SetSingleValue(const std::string &table, const std::string &fieldName, const std::string &strValue,
10638                                     const std::string &conditionName /* = "" */, int conditionValue /* = -1 */)
10639 {
10640   if (table.empty() || fieldName.empty())
10641     return false;
10642 
10643   std::string sql;
10644   try
10645   {
10646     if (nullptr == m_pDB || nullptr == m_pDS)
10647       return false;
10648 
10649     sql = PrepareSQL("UPDATE %s SET %s='%s'", table.c_str(), fieldName.c_str(), strValue.c_str());
10650     if (!conditionName.empty())
10651       sql += PrepareSQL(" WHERE %s=%u", conditionName.c_str(), conditionValue);
10652     if (m_pDS->exec(sql) == 0)
10653       return true;
10654   }
10655   catch (...)
10656   {
10657     CLog::Log(LOGERROR, "%s (%s) failed", __FUNCTION__, sql.c_str());
10658   }
10659   return false;
10660 }
10661 
GetSafeFile(const std::string & dir,const std::string & name) const10662 std::string CVideoDatabase::GetSafeFile(const std::string &dir, const std::string &name) const
10663 {
10664   std::string safeThumb(name);
10665   StringUtils::Replace(safeThumb, ' ', '_');
10666   return URIUtils::AddFileToFolder(dir, CUtil::MakeLegalFileName(safeThumb));
10667 }
10668 
AnnounceRemove(const std::string & content,int id,bool scanning)10669 void CVideoDatabase::AnnounceRemove(const std::string& content, int id, bool scanning /* = false */)
10670 {
10671   CVariant data;
10672   data["type"] = content;
10673   data["id"] = id;
10674   if (scanning)
10675     data["transaction"] = true;
10676   CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnRemove", data);
10677 }
10678 
AnnounceUpdate(const std::string & content,int id)10679 void CVideoDatabase::AnnounceUpdate(const std::string& content, int id)
10680 {
10681   CVariant data;
10682   data["type"] = content;
10683   data["id"] = id;
10684   CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnUpdate", data);
10685 }
10686 
GetItemsForPath(const std::string & content,const std::string & strPath,CFileItemList & items)10687 bool CVideoDatabase::GetItemsForPath(const std::string &content, const std::string &strPath, CFileItemList &items)
10688 {
10689   const std::string& path(strPath);
10690 
10691   if(URIUtils::IsMultiPath(path))
10692   {
10693     std::vector<std::string> paths;
10694     CMultiPathDirectory::GetPaths(path, paths);
10695 
10696     for(unsigned i=0;i<paths.size();i++)
10697       GetItemsForPath(content, paths[i], items);
10698 
10699     return items.Size() > 0;
10700   }
10701 
10702   int pathID = GetPathId(path);
10703   if (pathID < 0)
10704     return false;
10705 
10706   if (content == "movies")
10707   {
10708     Filter filter(PrepareSQL("c%02d=%d", VIDEODB_ID_PARENTPATHID, pathID));
10709     GetMoviesByWhere("videodb://movies/titles/", filter, items);
10710   }
10711   else if (content == "episodes")
10712   {
10713     Filter filter(PrepareSQL("c%02d=%d", VIDEODB_ID_EPISODE_PARENTPATHID, pathID));
10714     GetEpisodesByWhere("videodb://tvshows/titles/", filter, items);
10715   }
10716   else if (content == "tvshows")
10717   {
10718     Filter filter(PrepareSQL("idParentPath=%d", pathID));
10719     GetTvShowsByWhere("videodb://tvshows/titles/", filter, items);
10720   }
10721   else if (content == "musicvideos")
10722   {
10723     Filter filter(PrepareSQL("c%02d=%d", VIDEODB_ID_MUSICVIDEO_PARENTPATHID, pathID));
10724     GetMusicVideosByWhere("videodb://musicvideos/titles/", filter, items);
10725   }
10726   for (int i = 0; i < items.Size(); i++)
10727     items[i]->SetPath(items[i]->GetVideoInfoTag()->m_basePath);
10728   return items.Size() > 0;
10729 }
10730 
AppendIdLinkFilter(const char * field,const char * table,const MediaType & mediaType,const char * view,const char * viewKey,const CUrlOptions::UrlOptions & options,Filter & filter)10731 void CVideoDatabase::AppendIdLinkFilter(const char* field, const char *table, const MediaType& mediaType, const char *view, const char *viewKey, const CUrlOptions::UrlOptions& options, Filter &filter)
10732 {
10733   auto option = options.find((std::string)field + "id");
10734   if (option == options.end())
10735     return;
10736 
10737   filter.AppendJoin(PrepareSQL("JOIN %s_link ON %s_link.media_id=%s_view.%s AND %s_link.media_type='%s'", field, field, view, viewKey, field, mediaType.c_str()));
10738   filter.AppendWhere(PrepareSQL("%s_link.%s_id = %i", field, table, (int)option->second.asInteger()));
10739 }
10740 
AppendLinkFilter(const char * field,const char * table,const MediaType & mediaType,const char * view,const char * viewKey,const CUrlOptions::UrlOptions & options,Filter & filter)10741 void CVideoDatabase::AppendLinkFilter(const char* field, const char *table, const MediaType& mediaType, const char *view, const char *viewKey, const CUrlOptions::UrlOptions& options, Filter &filter)
10742 {
10743   auto option = options.find(field);
10744   if (option == options.end())
10745     return;
10746 
10747   filter.AppendJoin(PrepareSQL("JOIN %s_link ON %s_link.media_id=%s_view.%s AND %s_link.media_type='%s'", field, field, view, viewKey, field, mediaType.c_str()));
10748   filter.AppendJoin(PrepareSQL("JOIN %s ON %s.%s_id=%s_link.%s_id", table, table, field, table, field));
10749   filter.AppendWhere(PrepareSQL("%s.name like '%s'", table, option->second.asString().c_str()));
10750 }
10751 
GetFilter(CDbUrl & videoUrl,Filter & filter,SortDescription & sorting)10752 bool CVideoDatabase::GetFilter(CDbUrl &videoUrl, Filter &filter, SortDescription &sorting)
10753 {
10754   if (!videoUrl.IsValid())
10755     return false;
10756 
10757   std::string type = videoUrl.GetType();
10758   std::string itemType = ((const CVideoDbUrl &)videoUrl).GetItemType();
10759   const CUrlOptions::UrlOptions& options = videoUrl.GetOptions();
10760 
10761   if (type == "movies")
10762   {
10763     AppendIdLinkFilter("genre", "genre", "movie", "movie", "idMovie", options, filter);
10764     AppendLinkFilter("genre", "genre", "movie", "movie", "idMovie", options, filter);
10765 
10766     AppendIdLinkFilter("country", "country", "movie", "movie", "idMovie", options, filter);
10767     AppendLinkFilter("country", "country", "movie", "movie", "idMovie", options, filter);
10768 
10769     AppendIdLinkFilter("studio", "studio", "movie", "movie", "idMovie", options, filter);
10770     AppendLinkFilter("studio", "studio", "movie", "movie", "idMovie", options, filter);
10771 
10772     AppendIdLinkFilter("director", "actor", "movie", "movie", "idMovie", options, filter);
10773     AppendLinkFilter("director", "actor", "movie", "movie", "idMovie", options, filter);
10774 
10775     auto option = options.find("year");
10776     if (option != options.end())
10777       filter.AppendWhere(PrepareSQL("movie_view.premiered like '%i%%'", (int)option->second.asInteger()));
10778 
10779     AppendIdLinkFilter("actor", "actor", "movie", "movie", "idMovie", options, filter);
10780     AppendLinkFilter("actor", "actor", "movie", "movie", "idMovie", options, filter);
10781 
10782     option = options.find("setid");
10783     if (option != options.end())
10784       filter.AppendWhere(PrepareSQL("movie_view.idSet = %i", (int)option->second.asInteger()));
10785 
10786     option = options.find("set");
10787     if (option != options.end())
10788       filter.AppendWhere(PrepareSQL("movie_view.strSet LIKE '%s'", option->second.asString().c_str()));
10789 
10790     AppendIdLinkFilter("tag", "tag", "movie", "movie", "idMovie", options, filter);
10791     AppendLinkFilter("tag", "tag", "movie", "movie", "idMovie", options, filter);
10792   }
10793   else if (type == "tvshows")
10794   {
10795     if (itemType == "tvshows")
10796     {
10797       AppendIdLinkFilter("genre", "genre", "tvshow", "tvshow", "idShow", options, filter);
10798       AppendLinkFilter("genre", "genre", "tvshow", "tvshow", "idShow", options, filter);
10799 
10800       AppendIdLinkFilter("studio", "studio", "tvshow", "tvshow", "idShow", options, filter);
10801       AppendLinkFilter("studio", "studio", "tvshow", "tvshow", "idShow", options, filter);
10802 
10803       AppendIdLinkFilter("director", "actor", "tvshow", "tvshow", "idShow", options, filter);
10804 
10805       auto option = options.find("year");
10806       if (option != options.end())
10807         filter.AppendWhere(PrepareSQL("tvshow_view.c%02d like '%%%i%%'", VIDEODB_ID_TV_PREMIERED, (int)option->second.asInteger()));
10808 
10809       AppendIdLinkFilter("actor", "actor", "tvshow", "tvshow", "idShow", options, filter);
10810       AppendLinkFilter("actor", "actor", "tvshow", "tvshow", "idShow", options, filter);
10811 
10812       AppendIdLinkFilter("tag", "tag", "tvshow", "tvshow", "idShow", options, filter);
10813       AppendLinkFilter("tag", "tag", "tvshow", "tvshow", "idShow", options, filter);
10814     }
10815     else if (itemType == "seasons")
10816     {
10817       auto option = options.find("tvshowid");
10818       if (option != options.end())
10819         filter.AppendWhere(PrepareSQL("season_view.idShow = %i", (int)option->second.asInteger()));
10820 
10821       AppendIdLinkFilter("genre", "genre", "tvshow", "season", "idShow", options, filter);
10822 
10823       AppendIdLinkFilter("director", "actor", "tvshow", "season", "idShow", options, filter);
10824 
10825       option = options.find("year");
10826       if (option != options.end())
10827         filter.AppendWhere(PrepareSQL("season_view.premiered like '%%%i%%'", (int)option->second.asInteger()));
10828 
10829       AppendIdLinkFilter("actor", "actor", "tvshow", "season", "idShow", options, filter);
10830     }
10831     else if (itemType == "episodes")
10832     {
10833       int idShow = -1;
10834       auto option = options.find("tvshowid");
10835       if (option != options.end())
10836         idShow = (int)option->second.asInteger();
10837 
10838       int season = -1;
10839       option = options.find("season");
10840       if (option != options.end())
10841         season = (int)option->second.asInteger();
10842 
10843       if (idShow > -1)
10844       {
10845         bool condition = false;
10846 
10847         AppendIdLinkFilter("genre", "genre", "tvshow", "episode", "idShow", options, filter);
10848         AppendLinkFilter("genre", "genre", "tvshow", "episode", "idShow", options, filter);
10849 
10850         AppendIdLinkFilter("director", "actor", "tvshow", "episode", "idShow", options, filter);
10851         AppendLinkFilter("director", "actor", "tvshow", "episode", "idShow", options, filter);
10852 
10853         option = options.find("year");
10854         if (option != options.end())
10855         {
10856           condition = true;
10857           filter.AppendWhere(PrepareSQL("episode_view.idShow = %i and episode_view.premiered like '%%%i%%'", idShow, (int)option->second.asInteger()));
10858         }
10859 
10860         AppendIdLinkFilter("actor", "actor", "tvshow", "episode", "idShow", options, filter);
10861         AppendLinkFilter("actor", "actor", "tvshow", "episode", "idShow", options, filter);
10862 
10863         if (!condition)
10864           filter.AppendWhere(PrepareSQL("episode_view.idShow = %i", idShow));
10865 
10866         if (season > -1)
10867         {
10868           if (season == 0) // season = 0 indicates a special - we grab all specials here (see below)
10869             filter.AppendWhere(PrepareSQL("episode_view.c%02d = %i", VIDEODB_ID_EPISODE_SEASON, season));
10870           else
10871             filter.AppendWhere(PrepareSQL("(episode_view.c%02d = %i or (episode_view.c%02d = 0 and (episode_view.c%02d = 0 or episode_view.c%02d = %i)))",
10872               VIDEODB_ID_EPISODE_SEASON, season, VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_EPISODE_SORTSEASON, VIDEODB_ID_EPISODE_SORTSEASON, season));
10873         }
10874       }
10875       else
10876       {
10877         option = options.find("year");
10878         if (option != options.end())
10879           filter.AppendWhere(PrepareSQL("episode_view.premiered like '%%%i%%'", (int)option->second.asInteger()));
10880 
10881         AppendIdLinkFilter("director", "actor", "episode", "episode", "idEpisode", options, filter);
10882         AppendLinkFilter("director", "actor", "episode", "episode", "idEpisode", options, filter);
10883       }
10884     }
10885   }
10886   else if (type == "musicvideos")
10887   {
10888     AppendIdLinkFilter("genre", "genre", "musicvideo", "musicvideo", "idMVideo", options, filter);
10889     AppendLinkFilter("genre", "genre", "musicvideo", "musicvideo", "idMVideo", options, filter);
10890 
10891     AppendIdLinkFilter("studio", "studio", "musicvideo", "musicvideo", "idMVideo", options, filter);
10892     AppendLinkFilter("studio", "studio", "musicvideo", "musicvideo", "idMVideo", options, filter);
10893 
10894     AppendIdLinkFilter("director", "actor", "musicvideo", "musicvideo", "idMVideo", options, filter);
10895     AppendLinkFilter("director", "actor", "musicvideo", "musicvideo", "idMVideo", options, filter);
10896 
10897     auto option = options.find("year");
10898     if (option != options.end())
10899       filter.AppendWhere(PrepareSQL("musicvideo_view.premiered like '%i%%'", (int)option->second.asInteger()));
10900 
10901     option = options.find("artistid");
10902     if (option != options.end())
10903     {
10904       if (itemType != "albums")
10905         filter.AppendJoin(PrepareSQL("JOIN actor_link ON actor_link.media_id=musicvideo_view.idMVideo AND actor_link.media_type='musicvideo'"));
10906       filter.AppendWhere(PrepareSQL("actor_link.actor_id = %i", (int)option->second.asInteger()));
10907     }
10908 
10909     option = options.find("artist");
10910     if (option != options.end())
10911     {
10912       if (itemType != "albums")
10913       {
10914         filter.AppendJoin(PrepareSQL("JOIN actor_link ON actor_link.media_id=musicvideo_view.idMVideo AND actor_link.media_type='musicvideo'"));
10915         filter.AppendJoin(PrepareSQL("JOIN actor ON actor.actor_id=actor_link.actor_id"));
10916       }
10917       filter.AppendWhere(PrepareSQL("actor.name LIKE '%s'", option->second.asString().c_str()));
10918     }
10919 
10920     option = options.find("albumid");
10921     if (option != options.end())
10922       filter.AppendWhere(PrepareSQL("musicvideo_view.c%02d = (select c%02d from musicvideo where idMVideo = %i)", VIDEODB_ID_MUSICVIDEO_ALBUM, VIDEODB_ID_MUSICVIDEO_ALBUM, (int)option->second.asInteger()));
10923 
10924     AppendIdLinkFilter("tag", "tag", "musicvideo", "musicvideo", "idMVideo", options, filter);
10925     AppendLinkFilter("tag", "tag", "musicvideo", "musicvideo", "idMVideo", options, filter);
10926   }
10927   else
10928     return false;
10929 
10930   auto option = options.find("xsp");
10931   if (option != options.end())
10932   {
10933     CSmartPlaylist xsp;
10934     if (!xsp.LoadFromJson(option->second.asString()))
10935       return false;
10936 
10937     // check if the filter playlist matches the item type
10938     if (xsp.GetType() == itemType ||
10939        (xsp.GetGroup() == itemType && !xsp.IsGroupMixed()) ||
10940         // handle episode listings with videodb://tvshows/titles/ which get the rest
10941         // of the path (season and episodeid) appended later
10942        (xsp.GetType() == "episodes" && itemType == "tvshows"))
10943     {
10944       std::set<std::string> playlists;
10945       filter.AppendWhere(xsp.GetWhereClause(*this, playlists));
10946 
10947       if (xsp.GetLimit() > 0)
10948         sorting.limitEnd = xsp.GetLimit();
10949       if (xsp.GetOrder() != SortByNone)
10950         sorting.sortBy = xsp.GetOrder();
10951       if (xsp.GetOrderDirection() != SortOrderNone)
10952         sorting.sortOrder = xsp.GetOrderDirection();
10953       if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING))
10954         sorting.sortAttributes = SortAttributeIgnoreArticle;
10955     }
10956   }
10957 
10958   option = options.find("filter");
10959   if (option != options.end())
10960   {
10961     CSmartPlaylist xspFilter;
10962     if (!xspFilter.LoadFromJson(option->second.asString()))
10963       return false;
10964 
10965     // check if the filter playlist matches the item type
10966     if (xspFilter.GetType() == itemType)
10967     {
10968       std::set<std::string> playlists;
10969       filter.AppendWhere(xspFilter.GetWhereClause(*this, playlists));
10970     }
10971     // remove the filter if it doesn't match the item type
10972     else
10973       videoUrl.RemoveOption("filter");
10974   }
10975 
10976   return true;
10977 }
10978 
SetVideoUserRating(int dbId,int rating,const MediaType & mediaType)10979 bool CVideoDatabase::SetVideoUserRating(int dbId, int rating, const MediaType& mediaType)
10980 {
10981   try
10982   {
10983     if (nullptr == m_pDB)
10984       return false;
10985     if (nullptr == m_pDS)
10986       return false;
10987 
10988     if (mediaType == MediaTypeNone)
10989       return false;
10990 
10991     std::string sql;
10992     if (mediaType == MediaTypeMovie)
10993       sql = PrepareSQL("UPDATE movie SET userrating=%i WHERE idMovie = %i", rating, dbId);
10994     else if (mediaType == MediaTypeEpisode)
10995       sql = PrepareSQL("UPDATE episode SET userrating=%i WHERE idEpisode = %i", rating, dbId);
10996     else if (mediaType == MediaTypeMusicVideo)
10997       sql = PrepareSQL("UPDATE musicvideo SET userrating=%i WHERE idMVideo = %i", rating, dbId);
10998     else if (mediaType == MediaTypeTvShow)
10999       sql = PrepareSQL("UPDATE tvshow SET userrating=%i WHERE idShow = %i", rating, dbId);
11000     else if (mediaType == MediaTypeSeason)
11001       sql = PrepareSQL("UPDATE seasons SET userrating=%i WHERE idSeason = %i", rating, dbId);
11002 
11003     m_pDS->exec(sql);
11004     return true;
11005   }
11006   catch (...)
11007   {
11008     CLog::Log(LOGERROR, "%s (%i, %s, %i) failed", __FUNCTION__, dbId, mediaType.c_str(), rating);
11009   }
11010   return false;
11011 }
11012