PageRenderTime 48ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/xbmc/video/jobs/VideoLibraryRefreshingJob.cpp

http://github.com/xbmc/xbmc
C++ | 371 lines | 274 code | 47 blank | 50 comment | 116 complexity | 644e74af6a7f9646659b736f3076ae73 MD5 | raw file
Possible License(s): GPL-3.0, CC-BY-SA-3.0, LGPL-2.0, 0BSD, Unlicense, GPL-2.0, AGPL-1.0, BSD-3-Clause, LGPL-2.1, LGPL-3.0
  1. /*
  2. * Copyright (C) 2014-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. #include "VideoLibraryRefreshingJob.h"
  9. #include "ServiceBroker.h"
  10. #include "TextureDatabase.h"
  11. #include "addons/Scraper.h"
  12. #include "dialogs/GUIDialogSelect.h"
  13. #include "dialogs/GUIDialogYesNo.h"
  14. #include "filesystem/PluginDirectory.h"
  15. #include "guilib/GUIComponent.h"
  16. #include "guilib/GUIKeyboardFactory.h"
  17. #include "guilib/GUIWindowManager.h"
  18. #include "guilib/LocalizeStrings.h"
  19. #include "media/MediaType.h"
  20. #include "messaging/helpers/DialogOKHelper.h"
  21. #include "utils/StringUtils.h"
  22. #include "utils/URIUtils.h"
  23. #include "utils/log.h"
  24. #include "video/VideoDatabase.h"
  25. #include "video/VideoInfoDownloader.h"
  26. #include "video/VideoInfoScanner.h"
  27. #include "video/tags/IVideoInfoTagLoader.h"
  28. #include "video/tags/VideoInfoTagLoaderFactory.h"
  29. #include "video/tags/VideoTagLoaderPlugin.h"
  30. using namespace KODI::MESSAGING;
  31. using namespace VIDEO;
  32. CVideoLibraryRefreshingJob::CVideoLibraryRefreshingJob(CFileItemPtr item, bool forceRefresh, bool refreshAll, bool ignoreNfo /* = false */, const std::string& searchTitle /* = "" */)
  33. : CVideoLibraryProgressJob(nullptr),
  34. m_item(item),
  35. m_forceRefresh(forceRefresh),
  36. m_refreshAll(refreshAll),
  37. m_ignoreNfo(ignoreNfo),
  38. m_searchTitle(searchTitle)
  39. { }
  40. CVideoLibraryRefreshingJob::~CVideoLibraryRefreshingJob() = default;
  41. bool CVideoLibraryRefreshingJob::operator==(const CJob* job) const
  42. {
  43. if (strcmp(job->GetType(), GetType()) != 0)
  44. return false;
  45. const CVideoLibraryRefreshingJob* refreshingJob = dynamic_cast<const CVideoLibraryRefreshingJob*>(job);
  46. if (refreshingJob == nullptr)
  47. return false;
  48. return m_item->GetPath() == refreshingJob->m_item->GetPath();
  49. }
  50. bool CVideoLibraryRefreshingJob::Work(CVideoDatabase &db)
  51. {
  52. if (m_item == nullptr)
  53. return false;
  54. // determine the scraper for the item's path
  55. VIDEO::SScanSettings scanSettings;
  56. ADDON::ScraperPtr scraper = db.GetScraperForPath(m_item->GetPath(), scanSettings);
  57. if (scraper == nullptr)
  58. return false;
  59. if (URIUtils::IsPlugin(m_item->GetPath()) && !XFILE::CPluginDirectory::IsMediaLibraryScanningAllowed(ADDON::TranslateContent(scraper->Content()), m_item->GetPath()))
  60. {
  61. CLog::Log(LOGINFO,
  62. "CVideoLibraryRefreshingJob: Plugin '%s' does not support media library scanning and "
  63. "refreshing",
  64. CURL::GetRedacted(m_item->GetPath()).c_str());
  65. return false;
  66. }
  67. // copy the scraper in case we need it again
  68. ADDON::ScraperPtr originalScraper(scraper);
  69. // get the item's correct title
  70. std::string itemTitle = m_searchTitle;
  71. if (itemTitle.empty())
  72. itemTitle = m_item->GetMovieName(scanSettings.parent_name);
  73. CScraperUrl scraperUrl;
  74. bool needsRefresh = m_forceRefresh;
  75. bool hasDetails = false;
  76. bool ignoreNfo = m_ignoreNfo;
  77. // run this in a loop in case we need to refresh again
  78. bool failure = false;
  79. do
  80. {
  81. std::unique_ptr<CVideoInfoTag> pluginTag;
  82. std::unique_ptr<CGUIListItem::ArtMap> pluginArt;
  83. if (!ignoreNfo)
  84. {
  85. std::unique_ptr<IVideoInfoTagLoader> loader;
  86. loader.reset(CVideoInfoTagLoaderFactory::CreateLoader(*m_item, scraper,
  87. scanSettings.parent_name_root, m_forceRefresh));
  88. // check if there's an NFO for the item
  89. CInfoScanner::INFO_TYPE nfoResult = CInfoScanner::NO_NFO;
  90. if (loader)
  91. {
  92. std::unique_ptr<CVideoInfoTag> tag(new CVideoInfoTag());
  93. nfoResult = loader->Load(*tag, false);
  94. if (nfoResult == CInfoScanner::FULL_NFO && m_item->IsPlugin() && scraper->ID() == "metadata.local")
  95. {
  96. // get video info and art from plugin source with metadata.local scraper
  97. if (scraper->Content() == CONTENT_TVSHOWS && !m_item->m_bIsFolder && tag->m_iIdShow < 0)
  98. // preserve show_id for episode
  99. tag->m_iIdShow = m_item->GetVideoInfoTag()->m_iIdShow;
  100. pluginTag = std::move(tag);
  101. CVideoTagLoaderPlugin* nfo = dynamic_cast<CVideoTagLoaderPlugin*>(loader.get());
  102. if (nfo && nfo->GetArt())
  103. pluginArt = std::move(nfo->GetArt());
  104. }
  105. else if (nfoResult == CInfoScanner::URL_NFO)
  106. scraperUrl = loader->ScraperUrl();
  107. }
  108. // if there's no NFO remember it in case we have to refresh again
  109. if (nfoResult == CInfoScanner::ERROR_NFO)
  110. ignoreNfo = true;
  111. else if (nfoResult != CInfoScanner::NO_NFO)
  112. hasDetails = true;
  113. // if we are performing a forced refresh ask the user to choose between using a valid NFO and a valid scraper
  114. if (needsRefresh && IsModal() && !scraper->IsNoop()
  115. && nfoResult != CInfoScanner::ERROR_NFO)
  116. {
  117. int heading = 20159;
  118. if (scraper->Content() == CONTENT_MOVIES)
  119. heading = 13346;
  120. else if (scraper->Content() == CONTENT_TVSHOWS)
  121. heading = m_item->m_bIsFolder ? 20351 : 20352;
  122. else if (scraper->Content() == CONTENT_MUSICVIDEOS)
  123. heading = 20393;
  124. if (CGUIDialogYesNo::ShowAndGetInput(heading, 20446))
  125. {
  126. hasDetails = false;
  127. ignoreNfo = true;
  128. scraperUrl.Clear();
  129. scraper = originalScraper;
  130. }
  131. }
  132. }
  133. // no need to re-fetch the episode guide for episodes
  134. if (scraper->Content() == CONTENT_TVSHOWS && !m_item->m_bIsFolder)
  135. hasDetails = true;
  136. // if we don't have an url or need to refresh anyway do the web search
  137. if (!hasDetails && (needsRefresh || scraperUrl.m_url.empty()))
  138. {
  139. SetTitle(StringUtils::Format(g_localizeStrings.Get(197).c_str(), scraper->Name().c_str()));
  140. SetText(itemTitle);
  141. SetProgress(0);
  142. // clear any cached data from the scraper
  143. scraper->ClearCache();
  144. // create the info downloader for the scraper
  145. CVideoInfoDownloader infoDownloader(scraper);
  146. // try to find a matching item
  147. MOVIELIST itemResultList;
  148. int result = infoDownloader.FindMovie(itemTitle, -1, itemResultList, GetProgressDialog());
  149. // close the progress dialog
  150. MarkFinished();
  151. if (result > 0)
  152. {
  153. // there are multiple matches for the item
  154. if (!itemResultList.empty())
  155. {
  156. // choose the first match
  157. if (!IsModal())
  158. scraperUrl = itemResultList.at(0);
  159. else
  160. {
  161. // ask the user what to do
  162. CGUIDialogSelect* selectDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
  163. selectDialog->Reset();
  164. selectDialog->SetHeading(scraper->Content() == CONTENT_TVSHOWS ? 20356 : 196);
  165. for (const auto& itemResult : itemResultList)
  166. selectDialog->Add(itemResult.strTitle);
  167. selectDialog->EnableButton(true, 413); // "Manual"
  168. selectDialog->Open();
  169. // check if the user has chosen one of the results
  170. int selectedItem = selectDialog->GetSelectedItem();
  171. if (selectedItem >= 0)
  172. scraperUrl = itemResultList.at(selectedItem);
  173. // the user hasn't chosen one of the results and but has chosen to manually enter a title to use
  174. else if (selectDialog->IsButtonPressed())
  175. {
  176. // ask the user to input a title to use
  177. if (!CGUIKeyboardFactory::ShowAndGetInput(itemTitle, g_localizeStrings.Get(scraper->Content() == CONTENT_TVSHOWS ? 20357 : 16009), false))
  178. return false;
  179. // go through the whole process again
  180. needsRefresh = true;
  181. continue;
  182. }
  183. // nothing else we can do
  184. else
  185. return false;
  186. }
  187. CLog::Log(LOGDEBUG, "CVideoLibraryRefreshingJob: user selected item '%s' with URL '%s'", scraperUrl.strTitle.c_str(), scraperUrl.m_url.at(0).m_url.c_str());
  188. }
  189. }
  190. else if (result < 0 || !VIDEO::CVideoInfoScanner::DownloadFailed(GetProgressDialog()))
  191. {
  192. failure = true;
  193. break;
  194. }
  195. }
  196. // if the URL is still empty, check whether or not we're allowed
  197. // to prompt and ask the user to input a new search title
  198. if (!hasDetails && scraperUrl.m_url.empty())
  199. {
  200. if (IsModal())
  201. {
  202. // ask the user to input a title to use
  203. if (!CGUIKeyboardFactory::ShowAndGetInput(itemTitle, g_localizeStrings.Get(scraper->Content() == CONTENT_TVSHOWS ? 20357 : 16009), false))
  204. return false;
  205. // go through the whole process again
  206. needsRefresh = true;
  207. continue;
  208. }
  209. // nothing else we can do
  210. failure = true;
  211. break;
  212. }
  213. // before we start downloading all the necessary information cleanup any existing artwork and hashes
  214. CTextureDatabase textureDb;
  215. if (textureDb.Open())
  216. {
  217. for (const auto& artwork : m_item->GetArt())
  218. textureDb.InvalidateCachedTexture(artwork.second);
  219. textureDb.Close();
  220. }
  221. m_item->ClearArt();
  222. // put together the list of items to refresh
  223. std::string path = m_item->GetPath();
  224. CFileItemList items;
  225. if (m_item->HasVideoInfoTag() && m_item->GetVideoInfoTag()->m_iDbId > 0)
  226. {
  227. // for a tvshow we need to handle all paths of it
  228. std::vector<std::string> tvshowPaths;
  229. if (CMediaTypes::IsMediaType(m_item->GetVideoInfoTag()->m_type, MediaTypeTvShow) && m_refreshAll &&
  230. db.GetPathsLinkedToTvShow(m_item->GetVideoInfoTag()->m_iDbId, tvshowPaths))
  231. {
  232. for (const auto& tvshowPath : tvshowPaths)
  233. {
  234. CFileItemPtr tvshowItem(new CFileItem(*m_item->GetVideoInfoTag()));
  235. tvshowItem->SetPath(tvshowPath);
  236. items.Add(tvshowItem);
  237. }
  238. }
  239. // otherwise just add a copy of the item
  240. else
  241. items.Add(CFileItemPtr(new CFileItem(*m_item->GetVideoInfoTag())));
  242. // update the path to the real path (instead of a videodb:// one)
  243. path = m_item->GetVideoInfoTag()->m_strPath;
  244. }
  245. else
  246. items.Add(CFileItemPtr(new CFileItem(*m_item)));
  247. // set the proper path of the list of items to lookup
  248. items.SetPath(m_item->m_bIsFolder ? URIUtils::GetParentPath(path) : URIUtils::GetDirectory(path));
  249. int headingLabel = 198;
  250. if (scraper->Content() == CONTENT_TVSHOWS)
  251. {
  252. if (m_item->m_bIsFolder)
  253. headingLabel = 20353;
  254. else
  255. headingLabel = 20361;
  256. }
  257. else if (scraper->Content() == CONTENT_MUSICVIDEOS)
  258. headingLabel = 20394;
  259. // prepare the progress dialog for downloading all the necessary information
  260. SetTitle(g_localizeStrings.Get(headingLabel));
  261. SetText(scraperUrl.strTitle);
  262. SetProgress(0);
  263. // remove any existing data for the item we're going to refresh
  264. if (m_item->GetVideoInfoTag()->m_iDbId > 0)
  265. {
  266. int dbId = m_item->GetVideoInfoTag()->m_iDbId;
  267. if (scraper->Content() == CONTENT_MOVIES)
  268. db.DeleteMovie(dbId);
  269. else if (scraper->Content() == CONTENT_MUSICVIDEOS)
  270. db.DeleteMusicVideo(dbId);
  271. else if (scraper->Content() == CONTENT_TVSHOWS)
  272. {
  273. if (!m_item->m_bIsFolder)
  274. db.DeleteEpisode(dbId);
  275. else if (m_refreshAll)
  276. db.DeleteTvShow(dbId);
  277. else
  278. db.DeleteDetailsForTvShow(dbId);
  279. }
  280. }
  281. if (pluginTag || pluginArt)
  282. // set video info and art from plugin source with metadata.local scraper to items
  283. for (auto &i: items)
  284. {
  285. if (pluginTag)
  286. *i->GetVideoInfoTag() = *pluginTag;
  287. if (pluginArt)
  288. i->SetArt(*pluginArt);
  289. }
  290. // finally download the information for the item
  291. CVideoInfoScanner scanner;
  292. if (!scanner.RetrieveVideoInfo(items, scanSettings.parent_name,
  293. scraper->Content(), !ignoreNfo,
  294. scraperUrl.m_url.empty() ? NULL : &scraperUrl,
  295. m_refreshAll, GetProgressDialog()))
  296. {
  297. // something went wrong
  298. MarkFinished();
  299. // check if the user cancelled
  300. if (!IsCancelled() && IsModal())
  301. HELPERS::ShowOKDialogText(CVariant{195}, CVariant{itemTitle});
  302. return false;
  303. }
  304. // retrieve the updated information from the database
  305. if (scraper->Content() == CONTENT_MOVIES)
  306. db.GetMovieInfo(m_item->GetPath(), *m_item->GetVideoInfoTag());
  307. else if (scraper->Content() == CONTENT_MUSICVIDEOS)
  308. db.GetMusicVideoInfo(m_item->GetPath(), *m_item->GetVideoInfoTag());
  309. else if (scraper->Content() == CONTENT_TVSHOWS)
  310. {
  311. // update tvshow info to get updated episode numbers
  312. if (m_item->m_bIsFolder)
  313. db.GetTvShowInfo(m_item->GetPath(), *m_item->GetVideoInfoTag());
  314. else
  315. db.GetEpisodeInfo(m_item->GetPath(), *m_item->GetVideoInfoTag());
  316. }
  317. // we're finally done
  318. MarkFinished();
  319. break;
  320. } while (needsRefresh);
  321. if (failure && IsModal())
  322. HELPERS::ShowOKDialogText(CVariant{195}, CVariant{itemTitle});
  323. return true;
  324. }