diff options
Diffstat (limited to '')
-rw-r--r-- | xbmc/video/jobs/VideoLibraryRefreshingJob.cpp | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/xbmc/video/jobs/VideoLibraryRefreshingJob.cpp b/xbmc/video/jobs/VideoLibraryRefreshingJob.cpp new file mode 100644 index 0000000..dbac5ed --- /dev/null +++ b/xbmc/video/jobs/VideoLibraryRefreshingJob.cpp @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2014-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "VideoLibraryRefreshingJob.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "TextureDatabase.h" +#include "URL.h" +#include "addons/Scraper.h" +#include "dialogs/GUIDialogSelect.h" +#include "dialogs/GUIDialogYesNo.h" +#include "filesystem/PluginDirectory.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIKeyboardFactory.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "media/MediaType.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/log.h" +#include "video/VideoDatabase.h" +#include "video/VideoInfoDownloader.h" +#include "video/VideoInfoScanner.h" +#include "video/tags/IVideoInfoTagLoader.h" +#include "video/tags/VideoInfoTagLoaderFactory.h" +#include "video/tags/VideoTagLoaderPlugin.h" + +#include <utility> + +using namespace KODI::MESSAGING; +using namespace VIDEO; + +CVideoLibraryRefreshingJob::CVideoLibraryRefreshingJob(std::shared_ptr<CFileItem> item, + bool forceRefresh, + bool refreshAll, + bool ignoreNfo /* = false */, + const std::string& searchTitle /* = "" */) + : CVideoLibraryProgressJob(nullptr), + m_item(std::move(item)), + m_forceRefresh(forceRefresh), + m_refreshAll(refreshAll), + m_ignoreNfo(ignoreNfo), + m_searchTitle(searchTitle) +{ } + +CVideoLibraryRefreshingJob::~CVideoLibraryRefreshingJob() = default; + +bool CVideoLibraryRefreshingJob::operator==(const CJob* job) const +{ + if (strcmp(job->GetType(), GetType()) != 0) + return false; + + const CVideoLibraryRefreshingJob* refreshingJob = dynamic_cast<const CVideoLibraryRefreshingJob*>(job); + if (refreshingJob == nullptr) + return false; + + return m_item->GetPath() == refreshingJob->m_item->GetPath(); +} + +bool CVideoLibraryRefreshingJob::Work(CVideoDatabase &db) +{ + if (m_item == nullptr) + return false; + + // determine the scraper for the item's path + VIDEO::SScanSettings scanSettings; + ADDON::ScraperPtr scraper = db.GetScraperForPath(m_item->GetPath(), scanSettings); + if (scraper == nullptr) + return false; + + if (URIUtils::IsPlugin(m_item->GetPath()) && !XFILE::CPluginDirectory::IsMediaLibraryScanningAllowed(ADDON::TranslateContent(scraper->Content()), m_item->GetPath())) + { + CLog::Log(LOGINFO, + "CVideoLibraryRefreshingJob: Plugin '{}' does not support media library scanning and " + "refreshing", + CURL::GetRedacted(m_item->GetPath())); + return false; + } + + // copy the scraper in case we need it again + ADDON::ScraperPtr originalScraper(scraper); + + // get the item's correct title + std::string itemTitle = m_searchTitle; + if (itemTitle.empty()) + itemTitle = m_item->GetMovieName(scanSettings.parent_name); + + CScraperUrl scraperUrl; + bool needsRefresh = m_forceRefresh; + bool hasDetails = false; + bool ignoreNfo = m_ignoreNfo; + + // run this in a loop in case we need to refresh again + bool failure = false; + do + { + std::unique_ptr<CVideoInfoTag> pluginTag; + std::unique_ptr<CGUIListItem::ArtMap> pluginArt; + + if (!ignoreNfo) + { + std::unique_ptr<IVideoInfoTagLoader> loader; + loader.reset(CVideoInfoTagLoaderFactory::CreateLoader(*m_item, scraper, + scanSettings.parent_name_root, m_forceRefresh)); + // check if there's an NFO for the item + CInfoScanner::INFO_TYPE nfoResult = CInfoScanner::NO_NFO; + if (loader) + { + std::unique_ptr<CVideoInfoTag> tag(new CVideoInfoTag()); + nfoResult = loader->Load(*tag, false); + if (nfoResult == CInfoScanner::FULL_NFO && m_item->IsPlugin() && scraper->ID() == "metadata.local") + { + // get video info and art from plugin source with metadata.local scraper + if (scraper->Content() == CONTENT_TVSHOWS && !m_item->m_bIsFolder && tag->m_iIdShow < 0) + // preserve show_id for episode + tag->m_iIdShow = m_item->GetVideoInfoTag()->m_iIdShow; + pluginTag = std::move(tag); + CVideoTagLoaderPlugin* nfo = dynamic_cast<CVideoTagLoaderPlugin*>(loader.get()); + if (nfo && nfo->GetArt()) + pluginArt = std::move(nfo->GetArt()); + } + else if (nfoResult == CInfoScanner::URL_NFO) + scraperUrl = loader->ScraperUrl(); + } + + // if there's no NFO remember it in case we have to refresh again + if (nfoResult == CInfoScanner::ERROR_NFO) + ignoreNfo = true; + else if (nfoResult != CInfoScanner::NO_NFO) + hasDetails = true; + + // if we are performing a forced refresh ask the user to choose between using a valid NFO and a valid scraper + if (needsRefresh && IsModal() && !scraper->IsNoop() + && nfoResult != CInfoScanner::ERROR_NFO) + { + int heading = 20159; + if (scraper->Content() == CONTENT_MOVIES) + heading = 13346; + else if (scraper->Content() == CONTENT_TVSHOWS) + heading = m_item->m_bIsFolder ? 20351 : 20352; + else if (scraper->Content() == CONTENT_MUSICVIDEOS) + heading = 20393; + if (CGUIDialogYesNo::ShowAndGetInput(heading, 20446)) + { + hasDetails = false; + ignoreNfo = true; + scraperUrl.Clear(); + scraper = originalScraper; + } + } + } + + // no need to re-fetch the episode guide for episodes + if (scraper->Content() == CONTENT_TVSHOWS && !m_item->m_bIsFolder) + hasDetails = true; + + // if we don't have an url or need to refresh anyway do the web search + if (!hasDetails && (needsRefresh || !scraperUrl.HasUrls())) + { + SetTitle(StringUtils::Format(g_localizeStrings.Get(197), scraper->Name())); + SetText(itemTitle); + SetProgress(0); + + // clear any cached data from the scraper + scraper->ClearCache(); + + // create the info downloader for the scraper + CVideoInfoDownloader infoDownloader(scraper); + + // try to find a matching item + MOVIELIST itemResultList; + int result = infoDownloader.FindMovie(itemTitle, -1, itemResultList, GetProgressDialog()); + + // close the progress dialog + MarkFinished(); + + if (result > 0) + { + // there are multiple matches for the item + if (!itemResultList.empty()) + { + // choose the first match + if (!IsModal()) + scraperUrl = itemResultList.at(0); + else + { + // ask the user what to do + CGUIDialogSelect* selectDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT); + selectDialog->Reset(); + selectDialog->SetHeading(scraper->Content() == CONTENT_TVSHOWS ? 20356 : 196); + for (const auto& itemResult : itemResultList) + selectDialog->Add(itemResult.GetTitle()); + selectDialog->EnableButton(true, 413); // "Manual" + selectDialog->Open(); + + // check if the user has chosen one of the results + int selectedItem = selectDialog->GetSelectedItem(); + if (selectedItem >= 0) + scraperUrl = itemResultList.at(selectedItem); + // the user hasn't chosen one of the results and but has chosen to manually enter a title to use + else if (selectDialog->IsButtonPressed()) + { + // ask the user to input a title to use + if (!CGUIKeyboardFactory::ShowAndGetInput(itemTitle, g_localizeStrings.Get(scraper->Content() == CONTENT_TVSHOWS ? 20357 : 16009), false)) + return false; + + // go through the whole process again + needsRefresh = true; + continue; + } + // nothing else we can do + else + return false; + } + + CLog::Log(LOGDEBUG, "CVideoLibraryRefreshingJob: user selected item '{}' with URL '{}'", + scraperUrl.GetTitle(), scraperUrl.GetFirstThumbUrl()); + } + } + else if (result < 0 || !VIDEO::CVideoInfoScanner::DownloadFailed(GetProgressDialog())) + { + failure = true; + break; + } + } + + // if the URL is still empty, check whether or not we're allowed + // to prompt and ask the user to input a new search title + if (!hasDetails && !scraperUrl.HasUrls()) + { + if (IsModal()) + { + // ask the user to input a title to use + if (!CGUIKeyboardFactory::ShowAndGetInput(itemTitle, g_localizeStrings.Get(scraper->Content() == CONTENT_TVSHOWS ? 20357 : 16009), false)) + return false; + + // go through the whole process again + needsRefresh = true; + continue; + } + + // nothing else we can do + failure = true; + break; + } + + // before we start downloading all the necessary information cleanup any existing artwork and hashes + CTextureDatabase textureDb; + if (textureDb.Open()) + { + for (const auto& artwork : m_item->GetArt()) + textureDb.InvalidateCachedTexture(artwork.second); + + textureDb.Close(); + } + m_item->ClearArt(); + + // put together the list of items to refresh + std::string path = m_item->GetPath(); + CFileItemList items; + if (m_item->HasVideoInfoTag() && m_item->GetVideoInfoTag()->m_iDbId > 0) + { + // for a tvshow we need to handle all paths of it + std::vector<std::string> tvshowPaths; + if (CMediaTypes::IsMediaType(m_item->GetVideoInfoTag()->m_type, MediaTypeTvShow) && m_refreshAll && + db.GetPathsLinkedToTvShow(m_item->GetVideoInfoTag()->m_iDbId, tvshowPaths)) + { + for (const auto& tvshowPath : tvshowPaths) + { + CFileItemPtr tvshowItem(new CFileItem(*m_item->GetVideoInfoTag())); + tvshowItem->SetPath(tvshowPath); + items.Add(tvshowItem); + } + } + // otherwise just add a copy of the item + else + items.Add(CFileItemPtr(new CFileItem(*m_item->GetVideoInfoTag()))); + + // update the path to the real path (instead of a videodb:// one) + path = m_item->GetVideoInfoTag()->m_strPath; + } + else + items.Add(CFileItemPtr(new CFileItem(*m_item))); + + // set the proper path of the list of items to lookup + items.SetPath(m_item->m_bIsFolder ? URIUtils::GetParentPath(path) : URIUtils::GetDirectory(path)); + + int headingLabel = 198; + if (scraper->Content() == CONTENT_TVSHOWS) + { + if (m_item->m_bIsFolder) + headingLabel = 20353; + else + headingLabel = 20361; + } + else if (scraper->Content() == CONTENT_MUSICVIDEOS) + headingLabel = 20394; + + // prepare the progress dialog for downloading all the necessary information + SetTitle(g_localizeStrings.Get(headingLabel)); + SetText(scraperUrl.GetTitle()); + SetProgress(0); + + // remove any existing data for the item we're going to refresh + if (m_item->GetVideoInfoTag()->m_iDbId > 0) + { + int dbId = m_item->GetVideoInfoTag()->m_iDbId; + if (scraper->Content() == CONTENT_MOVIES) + db.DeleteMovie(dbId); + else if (scraper->Content() == CONTENT_MUSICVIDEOS) + db.DeleteMusicVideo(dbId); + else if (scraper->Content() == CONTENT_TVSHOWS) + { + if (!m_item->m_bIsFolder) + db.DeleteEpisode(dbId); + else if (m_refreshAll) + db.DeleteTvShow(dbId); + else + db.DeleteDetailsForTvShow(dbId); + } + } + + if (pluginTag || pluginArt) + // set video info and art from plugin source with metadata.local scraper to items + for (auto &i: items) + { + if (pluginTag) + *i->GetVideoInfoTag() = *pluginTag; + if (pluginArt) + i->SetArt(*pluginArt); + } + + // finally download the information for the item + CVideoInfoScanner scanner; + if (!scanner.RetrieveVideoInfo(items, scanSettings.parent_name, + scraper->Content(), !ignoreNfo, + scraperUrl.HasUrls() ? &scraperUrl : nullptr, + m_refreshAll, GetProgressDialog())) + { + // something went wrong + MarkFinished(); + + // check if the user cancelled + if (!IsCancelled() && IsModal()) + HELPERS::ShowOKDialogText(CVariant{195}, CVariant{itemTitle}); + + return false; + } + + // retrieve the updated information from the database + if (scraper->Content() == CONTENT_MOVIES) + db.GetMovieInfo(m_item->GetPath(), *m_item->GetVideoInfoTag()); + else if (scraper->Content() == CONTENT_MUSICVIDEOS) + db.GetMusicVideoInfo(m_item->GetPath(), *m_item->GetVideoInfoTag()); + else if (scraper->Content() == CONTENT_TVSHOWS) + { + // update tvshow info to get updated episode numbers + if (m_item->m_bIsFolder) + db.GetTvShowInfo(m_item->GetPath(), *m_item->GetVideoInfoTag()); + else + db.GetEpisodeInfo(m_item->GetPath(), *m_item->GetVideoInfoTag()); + } + + // we're finally done + MarkFinished(); + break; + } while (needsRefresh); + + if (failure && IsModal()) + HELPERS::ShowOKDialogText(CVariant{195}, CVariant{itemTitle}); + + return true; +} |