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> ×)
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> ×)
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