/* * Copyright (C) 2005-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 "GUIWindowAddonBrowser.h" #include "ContextMenuManager.h" #include "FileItem.h" #include "GUIDialogAddonInfo.h" #include "GUIUserMessages.h" #include "LangInfo.h" #include "ServiceBroker.h" #include "URL.h" #include "addons/AddonInstaller.h" #include "addons/AddonManager.h" #include "addons/AddonSystemSettings.h" #include "addons/IAddon.h" #include "addons/RepositoryUpdater.h" #include "addons/addoninfo/AddonType.h" #include "dialogs/GUIDialogBusy.h" #include "dialogs/GUIDialogFileBrowser.h" #include "dialogs/GUIDialogSelect.h" #include "dialogs/GUIDialogYesNo.h" #include "filesystem/AddonsDirectory.h" #include "guilib/GUIComponent.h" #include "guilib/GUIWindowManager.h" #include "guilib/LocalizeStrings.h" #include "input/Key.h" #include "messaging/helpers/DialogHelper.h" #include "platform/Platform.h" #include "settings/MediaSourceSettings.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "storage/MediaManager.h" #include "threads/IRunnable.h" #include "utils/StringUtils.h" #include "utils/Variant.h" #include #define CONTROL_SETTINGS 5 #define CONTROL_FOREIGNFILTER 7 #define CONTROL_BROKENFILTER 8 #define CONTROL_CHECK_FOR_UPDATES 9 using namespace ADDON; using namespace XFILE; CGUIWindowAddonBrowser::CGUIWindowAddonBrowser(void) : CGUIMediaWindow(WINDOW_ADDON_BROWSER, "AddonBrowser.xml") { } CGUIWindowAddonBrowser::~CGUIWindowAddonBrowser() = default; bool CGUIWindowAddonBrowser::OnMessage(CGUIMessage& message) { switch (message.GetMessage()) { case GUI_MSG_WINDOW_DEINIT: { CServiceBroker::GetRepositoryUpdater().Events().Unsubscribe(this); CServiceBroker::GetAddonMgr().Events().Unsubscribe(this); if (m_thumbLoader.IsLoading()) m_thumbLoader.StopThread(); } break; case GUI_MSG_WINDOW_INIT: { CServiceBroker::GetRepositoryUpdater().Events().Subscribe(this, &CGUIWindowAddonBrowser::OnEvent); CServiceBroker::GetAddonMgr().Events().Subscribe(this, &CGUIWindowAddonBrowser::OnEvent); SetProperties(); } break; case GUI_MSG_CLICKED: { int iControl = message.GetSenderId(); if (iControl == CONTROL_FOREIGNFILTER) { const std::shared_ptr settings = CServiceBroker::GetSettingsComponent()->GetSettings(); settings->ToggleBool(CSettings::SETTING_GENERAL_ADDONFOREIGNFILTER); settings->Save(); Refresh(); return true; } else if (iControl == CONTROL_BROKENFILTER) { const std::shared_ptr settings = CServiceBroker::GetSettingsComponent()->GetSettings(); settings->ToggleBool(CSettings::SETTING_GENERAL_ADDONBROKENFILTER); settings->Save(); Refresh(); return true; } else if (iControl == CONTROL_CHECK_FOR_UPDATES) { CServiceBroker::GetRepositoryUpdater().CheckForUpdates(true); return true; } else if (iControl == CONTROL_SETTINGS) { CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SETTINGS_SYSTEM, "addons"); return true; } else if (m_viewControl.HasControl(iControl)) // list/thumb control { // get selected item int iItem = m_viewControl.GetSelectedItem(); int iAction = message.GetParam1(); // iItem is checked for validity inside these routines if (iAction == ACTION_SHOW_INFO) { if (!m_vecItems->Get(iItem)->GetProperty("Addon.ID").empty()) return CGUIDialogAddonInfo::ShowForItem((*m_vecItems)[iItem]); return false; } } } break; case GUI_MSG_NOTIFY_ALL: { if (message.GetParam1() == GUI_MSG_UPDATE_ITEM && IsActive() && message.GetNumStringParams() == 1) { // update this item for (int i = 0; i < m_vecItems->Size(); ++i) { CFileItemPtr item = m_vecItems->Get(i); if (item->GetProperty("Addon.ID") == message.GetStringParam()) { UpdateStatus(item); FormatAndSort(*m_vecItems); return true; } } } else if (message.GetParam1() == GUI_MSG_UPDATE && IsActive()) SetProperties(); } break; default: break; } return CGUIMediaWindow::OnMessage(message); } void CGUIWindowAddonBrowser::SetProperties() { auto lastUpdated = CServiceBroker::GetRepositoryUpdater().LastUpdated(); SetProperty("Updated", lastUpdated.IsValid() ? lastUpdated.GetAsLocalizedDateTime() : g_localizeStrings.Get(21337)); } class UpdateAddons : public IRunnable { void Run() override { for (const auto& addon : CServiceBroker::GetAddonMgr().GetAvailableUpdates()) CAddonInstaller::GetInstance().InstallOrUpdate(addon->ID(), BackgroundJob::CHOICE_YES, ModalJob::CHOICE_NO); } }; class UpdateAllowedAddons : public IRunnable { void Run() override { for (const auto& addon : CServiceBroker::GetAddonMgr().GetAvailableUpdates()) if (CServiceBroker::GetAddonMgr().IsAutoUpdateable(addon->ID())) CAddonInstaller::GetInstance().InstallOrUpdate(addon->ID(), BackgroundJob::CHOICE_YES, ModalJob::CHOICE_NO); } }; void CGUIWindowAddonBrowser::OnEvent(const ADDON::CRepositoryUpdater::RepositoryUpdated& event) { CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE); CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); } void CGUIWindowAddonBrowser::OnEvent(const ADDON::AddonEvent& event) { CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE); CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); } void CGUIWindowAddonBrowser::InstallFromZip() { using namespace KODI::MESSAGING::HELPERS; if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( CSettings::SETTING_ADDONS_ALLOW_UNKNOWN_SOURCES)) { if (ShowYesNoDialogText(13106, 36617, 186, 10004) == DialogResponse::CHOICE_YES) CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow( WINDOW_SETTINGS_SYSTEM, CSettings::SETTING_ADDONS_ALLOW_UNKNOWN_SOURCES); } else { // pop up filebrowser to grab an installed folder VECSOURCES shares = *CMediaSourceSettings::GetInstance().GetSources("files"); CServiceBroker::GetMediaManager().GetLocalDrives(shares); CServiceBroker::GetMediaManager().GetNetworkLocations(shares); std::string path; if (CGUIDialogFileBrowser::ShowAndGetFile(shares, "*.zip", g_localizeStrings.Get(24041), path)) { CAddonInstaller::GetInstance().InstallFromZip(path); } } } bool CGUIWindowAddonBrowser::OnClick(int iItem, const std::string& player) { CFileItemPtr item = m_vecItems->Get(iItem); if (item->GetPath() == "addons://install/") { InstallFromZip(); return true; } if (item->GetPath() == "addons://update_all/") { UpdateAddons updater; CGUIDialogBusy::Wait(&updater, 100, true); return true; } if (item->GetPath() == "addons://update_allowed/") { UpdateAllowedAddons updater; CGUIDialogBusy::Wait(&updater, 100, true); return true; } if (!item->m_bIsFolder) { // cancel a downloading job if (item->HasProperty("Addon.Downloading")) { if (CGUIDialogYesNo::ShowAndGetInput(CVariant{24000}, item->GetProperty("Addon.Name"), CVariant{24066}, CVariant{""})) { if (CAddonInstaller::GetInstance().Cancel(item->GetProperty("Addon.ID").asString())) Refresh(); } return true; } CGUIDialogAddonInfo::ShowForItem(item); return true; } if (item->IsPath("addons://search/")) { Update(item->GetPath()); return true; } return CGUIMediaWindow::OnClick(iItem, player); } void CGUIWindowAddonBrowser::UpdateButtons() { const std::shared_ptr settings = CServiceBroker::GetSettingsComponent()->GetSettings(); SET_CONTROL_SELECTED(GetID(), CONTROL_FOREIGNFILTER, settings->GetBool(CSettings::SETTING_GENERAL_ADDONFOREIGNFILTER)); SET_CONTROL_SELECTED(GetID(), CONTROL_BROKENFILTER, settings->GetBool(CSettings::SETTING_GENERAL_ADDONBROKENFILTER)); CONTROL_ENABLE(CONTROL_CHECK_FOR_UPDATES); CONTROL_ENABLE(CONTROL_SETTINGS); bool allowFilter = CAddonsDirectory::IsRepoDirectory(CURL(m_vecItems->GetPath())); CONTROL_ENABLE_ON_CONDITION(CONTROL_FOREIGNFILTER, allowFilter); CONTROL_ENABLE_ON_CONDITION(CONTROL_BROKENFILTER, allowFilter); CGUIMediaWindow::UpdateButtons(); } static bool IsForeign(const std::string& languages) { if (languages.empty()) return false; for (const auto& lang : StringUtils::Split(languages, " ")) { if (lang == "en" || lang == g_langInfo.GetLocale().GetLanguageCode() || lang == g_langInfo.GetLocale().ToShortString()) return false; // for backwards compatibility if (lang == "no" && g_langInfo.GetLocale().ToShortString() == "nb_NO") return false; } return true; } bool CGUIWindowAddonBrowser::GetDirectory(const std::string& strDirectory, CFileItemList& items) { bool result = CGUIMediaWindow::GetDirectory(strDirectory, items); if (result && CAddonsDirectory::IsRepoDirectory(CURL(strDirectory))) { const std::shared_ptr settings = CServiceBroker::GetSettingsComponent()->GetSettings(); if (settings->GetBool(CSettings::SETTING_GENERAL_ADDONFOREIGNFILTER)) { int i = 0; while (i < items.Size()) { auto prop = items[i]->GetProperty("Addon.Language"); if (!prop.isNull() && IsForeign(prop.asString())) items.Remove(i); else ++i; } } if (settings->GetBool(CSettings::SETTING_GENERAL_ADDONBROKENFILTER)) { for (int i = items.Size() - 1; i >= 0; i--) { if (items[i]->GetAddonInfo() && items[i]->GetAddonInfo()->LifecycleState() == AddonLifecycleState::BROKEN) { //check if it's installed AddonPtr addon; if (!CServiceBroker::GetAddonMgr().GetAddon(items[i]->GetProperty("Addon.ID").asString(), addon, OnlyEnabled::CHOICE_YES)) items.Remove(i); } } } } for (int i = 0; i < items.Size(); ++i) UpdateStatus(items[i]); return result; } void CGUIWindowAddonBrowser::UpdateStatus(const CFileItemPtr& item) { if (!item || item->m_bIsFolder) return; unsigned int percent; bool downloadFinshed; if (CAddonInstaller::GetInstance().GetProgress(item->GetProperty("Addon.ID").asString(), percent, downloadFinshed)) { std::string progress = StringUtils::Format( !downloadFinshed ? g_localizeStrings.Get(24042) : g_localizeStrings.Get(24044), percent); item->SetProperty("Addon.Status", progress); item->SetProperty("Addon.Downloading", true); } else item->ClearProperty("Addon.Downloading"); } bool CGUIWindowAddonBrowser::Update(const std::string& strDirectory, bool updateFilterPath /* = true */) { if (m_thumbLoader.IsLoading()) m_thumbLoader.StopThread(); if (!CGUIMediaWindow::Update(strDirectory, updateFilterPath)) return false; m_thumbLoader.Load(*m_vecItems); return true; } int CGUIWindowAddonBrowser::SelectAddonID(AddonType type, std::string& addonID, bool showNone /* = false */, bool showDetails /* = true */, bool showInstalled /* = true */, bool showInstallable /*= false */, bool showMore /* = true */) { std::vector types; types.push_back(type); return SelectAddonID(types, addonID, showNone, showDetails, showInstalled, showInstallable, showMore); } int CGUIWindowAddonBrowser::SelectAddonID(AddonType type, std::vector& addonIDs, bool showNone /* = false */, bool showDetails /* = true */, bool multipleSelection /* = true */, bool showInstalled /* = true */, bool showInstallable /* = false */, bool showMore /* = true */) { std::vector types; types.push_back(type); return SelectAddonID(types, addonIDs, showNone, showDetails, multipleSelection, showInstalled, showInstallable, showMore); } int CGUIWindowAddonBrowser::SelectAddonID(const std::vector& types, std::string& addonID, bool showNone /* = false */, bool showDetails /* = true */, bool showInstalled /* = true */, bool showInstallable /* = false */, bool showMore /* = true */) { std::vector addonIDs; if (!addonID.empty()) addonIDs.push_back(addonID); int retval = SelectAddonID(types, addonIDs, showNone, showDetails, false, showInstalled, showInstallable, showMore); if (!addonIDs.empty()) addonID = addonIDs.at(0); else addonID = ""; return retval; } int CGUIWindowAddonBrowser::SelectAddonID(const std::vector& types, std::vector& addonIDs, bool showNone /* = false */, bool showDetails /* = true */, bool multipleSelection /* = true */, bool showInstalled /* = true */, bool showInstallable /* = false */, bool showMore /* = true */) { // if we shouldn't show neither installed nor installable addons the list will be empty if (!showInstalled && !showInstallable) return -1; // can't show the "Get More" button if we already show installable addons if (showInstallable) showMore = false; CGUIDialogSelect* dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow( WINDOW_DIALOG_SELECT); if (!dialog) return -1; // get rid of any invalid addon types std::vector validTypes(types.size()); std::copy_if(types.begin(), types.end(), validTypes.begin(), [](AddonType type) { return type != AddonType::UNKNOWN; }); if (validTypes.empty()) return -1; // get all addons to show VECADDONS addons; if (showInstalled) { for (std::vector::const_iterator type = validTypes.begin(); type != validTypes.end(); ++type) { VECADDONS typeAddons; if (*type == AddonType::AUDIO) CAddonsDirectory::GetScriptsAndPlugins("audio", typeAddons); else if (*type == AddonType::EXECUTABLE) CAddonsDirectory::GetScriptsAndPlugins("executable", typeAddons); else if (*type == AddonType::IMAGE) CAddonsDirectory::GetScriptsAndPlugins("image", typeAddons); else if (*type == AddonType::VIDEO) CAddonsDirectory::GetScriptsAndPlugins("video", typeAddons); else if (*type == AddonType::GAME) CAddonsDirectory::GetScriptsAndPlugins("game", typeAddons); else CServiceBroker::GetAddonMgr().GetAddons(typeAddons, *type); addons.insert(addons.end(), typeAddons.begin(), typeAddons.end()); } } if (showInstallable || showMore) { VECADDONS installableAddons; if (CServiceBroker::GetAddonMgr().GetInstallableAddons(installableAddons)) { for (auto addon = installableAddons.begin(); addon != installableAddons.end();) { AddonPtr pAddon = *addon; // check if the addon matches one of the provided addon types bool matchesType = false; for (std::vector::const_iterator type = validTypes.begin(); type != validTypes.end(); ++type) { if (pAddon->HasType(*type)) { matchesType = true; break; } } if (matchesType) { ++addon; continue; } addon = installableAddons.erase(addon); } if (showInstallable) addons.insert(addons.end(), installableAddons.begin(), installableAddons.end()); else if (showMore) showMore = !installableAddons.empty(); } } if (addons.empty() && !showNone) return -1; // turn the addons into items std::map addonMap; CFileItemList items; for (const auto& addon : addons) { const CFileItemPtr item(CAddonsDirectory::FileItemFromAddon(addon, addon->ID())); item->SetLabel2(addon->Summary()); if (!items.Contains(item->GetPath())) { items.Add(item); addonMap.insert(std::make_pair(item->GetPath(), addon)); } } if (items.IsEmpty() && !showNone) return -1; std::string heading; for (std::vector::const_iterator type = validTypes.begin(); type != validTypes.end(); ++type) { if (!heading.empty()) heading += ", "; heading += CAddonInfo::TranslateType(*type, true); } dialog->SetHeading(CVariant{std::move(heading)}); dialog->Reset(); dialog->SetUseDetails(showDetails); if (multipleSelection) { showNone = false; showMore = false; dialog->EnableButton(true, 186); } else if (showMore) dialog->EnableButton(true, 21452); if (showNone) { CFileItemPtr item(new CFileItem("", false)); item->SetLabel(g_localizeStrings.Get(231)); item->SetLabel2(g_localizeStrings.Get(24040)); item->SetArt("icon", "DefaultAddonNone.png"); item->SetSpecialSort(SortSpecialOnTop); items.Add(item); } items.Sort(SortByLabel, SortOrderAscending); if (!addonIDs.empty()) { for (std::vector::const_iterator it = addonIDs.begin(); it != addonIDs.end(); ++it) { CFileItemPtr item = items.Get(*it); if (item) item->Select(true); } } dialog->SetItems(items); dialog->SetMultiSelection(multipleSelection); dialog->Open(); // if the "Get More" button has been pressed and we haven't shown the // installable addons so far show a list of installable addons if (showMore && dialog->IsButtonPressed()) return SelectAddonID(types, addonIDs, showNone, showDetails, multipleSelection, false, true, false); if (!dialog->IsConfirmed()) return 0; addonIDs.clear(); for (int i : dialog->GetSelectedItems()) { const CFileItemPtr& item = items.Get(i); // check if one of the selected addons needs to be installed if (showInstallable) { std::map::const_iterator itAddon = addonMap.find(item->GetPath()); if (itAddon != addonMap.end()) { const AddonPtr& addon = itAddon->second; // if the addon isn't installed we need to install it if (!CServiceBroker::GetAddonMgr().IsAddonInstalled(addon->ID())) { AddonPtr installedAddon; if (!CAddonInstaller::GetInstance().InstallModal(addon->ID(), installedAddon, InstallModalPrompt::CHOICE_NO)) continue; } // if the addon is disabled we need to enable it if (CServiceBroker::GetAddonMgr().IsAddonDisabled(addon->ID())) CServiceBroker::GetAddonMgr().EnableAddon(addon->ID()); } } addonIDs.push_back(item->GetPath()); } return 1; } std::string CGUIWindowAddonBrowser::GetStartFolder(const std::string& dir) { if (StringUtils::StartsWith(dir, "addons://")) { if (StringUtils::StartsWith(dir, "addons://default_binary_addons_source/")) { const bool all = CServiceBroker::GetPlatform().SupportsUserInstalledBinaryAddons(); std::string startDir = dir; StringUtils::Replace(startDir, "/default_binary_addons_source/", all ? "/all/" : "/user/"); return startDir; } else return dir; } return CGUIMediaWindow::GetStartFolder(dir); }