/* * Copyright (C) 2011-2013 Team XBMC * http://xbmc.org * * This Program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This Program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with XBMC; see the file COPYING. If not, see * . * */ #include "AddonInstaller.h" #include "utils/log.h" #include "utils/FileUtils.h" #include "utils/URIUtils.h" #include "Util.h" #include "guilib/LocalizeStrings.h" #include "filesystem/Directory.h" #include "settings/AdvancedSettings.h" #include "settings/Settings.h" #include "ApplicationMessenger.h" #include "filesystem/FavouritesDirectory.h" #include "utils/JobManager.h" #include "dialogs/GUIDialogYesNo.h" #include "addons/AddonManager.h" #include "addons/Repository.h" #include "guilib/GUIWindowManager.h" // for callback #include "GUIUserMessages.h" // for callback #include "utils/StringUtils.h" #include "dialogs/GUIDialogKaiToast.h" #include "dialogs/GUIDialogOK.h" #include "dialogs/GUIDialogProgress.h" #include "URL.h" #include using namespace std; using namespace XFILE; using namespace ADDON; struct find_map : public binary_function { bool operator() (CAddonInstaller::JobMap::value_type t, unsigned int id) const { return (t.second.jobID == id); } }; CAddonInstaller::CAddonInstaller() : m_repoUpdateJob(0) { } CAddonInstaller::~CAddonInstaller() { } CAddonInstaller &CAddonInstaller::Get() { static CAddonInstaller addonInstaller; return addonInstaller; } void CAddonInstaller::OnJobComplete(unsigned int jobID, bool success, CJob* job) { if (success) CAddonMgr::Get().FindAddons(); CSingleLock lock(m_critSection); if (strncmp(job->GetType(), "repoupdate", 10) == 0) { // repo job finished m_repoUpdateDone.Set(); m_repoUpdateJob = 0; lock.Leave(); } else { // download job JobMap::iterator i = find_if(m_downloadJobs.begin(), m_downloadJobs.end(), bind2nd(find_map(), jobID)); if (i != m_downloadJobs.end()) m_downloadJobs.erase(i); lock.Leave(); PrunePackageCache(); } CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE); g_windowManager.SendThreadMessage(msg); } void CAddonInstaller::OnJobProgress(unsigned int jobID, unsigned int progress, unsigned int total, const CJob *job) { CSingleLock lock(m_critSection); JobMap::iterator i = find_if(m_downloadJobs.begin(), m_downloadJobs.end(), bind2nd(find_map(), jobID)); if (i != m_downloadJobs.end()) { // update job progress i->second.progress = progress; CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM); msg.SetStringParam(i->first); lock.Leave(); g_windowManager.SendThreadMessage(msg); } } bool CAddonInstaller::IsDownloading() const { CSingleLock lock(m_critSection); return !m_downloadJobs.empty(); } void CAddonInstaller::GetInstallList(VECADDONS &addons) const { CSingleLock lock(m_critSection); vector addonIDs; for (JobMap::const_iterator i = m_downloadJobs.begin(); i != m_downloadJobs.end(); ++i) { if (i->second.jobID) addonIDs.push_back(i->first); } lock.Leave(); CAddonDatabase database; database.Open(); for (vector::iterator it = addonIDs.begin(); it != addonIDs.end(); ++it) { AddonPtr addon; if (database.GetAddon(*it, addon)) addons.push_back(addon); } } bool CAddonInstaller::GetProgress(const std::string &addonID, unsigned int &percent) const { CSingleLock lock(m_critSection); JobMap::const_iterator i = m_downloadJobs.find(addonID); if (i != m_downloadJobs.end()) { percent = i->second.progress; return true; } return false; } bool CAddonInstaller::Cancel(const std::string &addonID) { CSingleLock lock(m_critSection); JobMap::iterator i = m_downloadJobs.find(addonID); if (i != m_downloadJobs.end()) { CJobManager::GetInstance().CancelJob(i->second.jobID); m_downloadJobs.erase(i); return true; } return false; } bool CAddonInstaller::InstallModal(const std::string &addonID, ADDON::AddonPtr &addon, bool promptForInstall /* = true */) { if (!g_passwordManager.CheckMenuLock(WINDOW_ADDON_BROWSER)) return false; // we assume that addons that are enabled don't get to this routine (i.e. that GetAddon() has been called) if (CAddonMgr::Get().GetAddon(addonID, addon, ADDON_UNKNOWN, false)) return false; // addon is installed but disabled, and the user has specifically activated something that needs // the addon - should we enable it? // check we have it available CAddonDatabase database; database.Open(); if (!database.GetAddon(addonID, addon)) return false; // if specified ask the user if he wants it installed if (promptForInstall && !CGUIDialogYesNo::ShowAndGetInput(g_localizeStrings.Get(24076), g_localizeStrings.Get(24100), addon->Name().c_str(), g_localizeStrings.Get(24101))) return false; if (!Install(addonID, true, "", false, true)) return false; return CAddonMgr::Get().GetAddon(addonID, addon); } bool CAddonInstaller::Install(const std::string &addonID, bool force /* = false */, const std::string &referer /* = "" */, bool background /* = true */, bool modal /* = false */) { AddonPtr addon; bool addonInstalled = CAddonMgr::Get().GetAddon(addonID, addon, ADDON_UNKNOWN, false); if (addonInstalled && !force) return true; if (referer.empty()) { if (!g_passwordManager.CheckMenuLock(WINDOW_ADDON_BROWSER)) return false; } // check whether we have it available in a repository std::string hash; if (!CAddonInstallJob::GetAddonWithHash(addonID, addon, hash)) return false; return DoInstall(addon, hash, addonInstalled, referer, background, modal); } bool CAddonInstaller::DoInstall(const AddonPtr &addon, const std::string &hash /* = "" */, bool update /* = false */, const std::string &referer /* = "" */, bool background /* = true */, bool modal /* = false */) { // check whether we already have the addon installing CSingleLock lock(m_critSection); if (m_downloadJobs.find(addon->ID()) != m_downloadJobs.end()) return false; CAddonInstallJob* installJob = new CAddonInstallJob(addon, hash, update, referer); if (background) { unsigned int jobID = CJobManager::GetInstance().AddJob(installJob, this); m_downloadJobs.insert(make_pair(addon->ID(), CDownloadJob(jobID))); return true; } m_downloadJobs.insert(make_pair(addon->ID(), CDownloadJob(0))); lock.Leave(); bool result = false; if (modal) result = installJob->DoModal(); else result = installJob->DoWork(); delete installJob; lock.Enter(); JobMap::iterator i = m_downloadJobs.find(addon->ID()); m_downloadJobs.erase(i); return result; } bool CAddonInstaller::InstallFromZip(const std::string &path) { if (!g_passwordManager.CheckMenuLock(WINDOW_ADDON_BROWSER)) return false; // grab the descriptive XML document from the zip, and read it in CFileItemList items; // BUG: some zip files return a single item (root folder) that we think is stored, so we don't use the zip:// protocol CURL pathToUrl(path); CURL zipDir = URIUtils::CreateArchivePath("zip", pathToUrl, ""); if (!CDirectory::GetDirectory(zipDir, items) || items.Size() != 1 || !items[0]->m_bIsFolder) { CGUIDialogKaiToast::QueueNotification("", path, g_localizeStrings.Get(24045), TOAST_DISPLAY_TIME, false); return false; } // TODO: possibly add support for github generated zips here? std::string archive = URIUtils::AddFileToFolder(items[0]->GetPath(), "addon.xml"); CXBMCTinyXML xml; AddonPtr addon; if (xml.LoadFile(archive) && CAddonMgr::Get().LoadAddonDescriptionFromMemory(xml.RootElement(), addon)) { // set the correct path addon->Props().path = items[0]->GetPath(); addon->Props().icon = URIUtils::AddFileToFolder(items[0]->GetPath(), "icon.png"); // install the addon return DoInstall(addon); } CGUIDialogKaiToast::QueueNotification("", path, g_localizeStrings.Get(24045), TOAST_DISPLAY_TIME, false); return false; } void CAddonInstaller::InstallFromXBMCRepo(const set &addonIDs) { // first check we have the our repositories up to date (and wait until we do) UpdateRepos(false, true); // now install the addons for (set::const_iterator i = addonIDs.begin(); i != addonIDs.end(); ++i) Install(*i); } bool CAddonInstaller::CheckDependencies(const AddonPtr &addon, CAddonDatabase *database /* = NULL */) { std::vector preDeps; preDeps.push_back(addon->ID()); CAddonDatabase localDB; if (!database) database = &localDB; return CheckDependencies(addon, preDeps, *database); } bool CAddonInstaller::CheckDependencies(const AddonPtr &addon, std::vector& preDeps, CAddonDatabase &database) { if (addon == NULL) return true; // a NULL addon has no dependencies if (!database.Open()) return false; ADDONDEPS deps = addon->GetDeps(); for (ADDONDEPS::const_iterator i = deps.begin(); i != deps.end(); ++i) { const std::string &addonID = i->first; const AddonVersion &version = i->second.first; bool optional = i->second.second; AddonPtr dep; bool haveAddon = CAddonMgr::Get().GetAddon(addonID, dep); if ((haveAddon && !dep->MeetsVersion(version)) || (!haveAddon && !optional)) { // we have it but our version isn't good enough, or we don't have it and we need it if (!database.GetAddon(addonID, dep) || !dep->MeetsVersion(version)) { // we don't have it in a repo, or we have it but the version isn't good enough, so dep isn't satisfied. CLog::Log(LOGDEBUG, "CAddonInstallJob[%s]: requires %s version %s which is not available", addon->ID().c_str(), addonID.c_str(), version.asString().c_str()); database.Close(); return false; } } // at this point we have our dep, or the dep is optional (and we don't have it) so check that it's OK as well // TODO: should we assume that installed deps are OK? if (dep && std::find(preDeps.begin(), preDeps.end(), dep->ID()) == preDeps.end()) { if (!CheckDependencies(dep, preDeps, database)) { database.Close(); preDeps.push_back(dep->ID()); return false; } } } database.Close(); return true; } CDateTime CAddonInstaller::LastRepoUpdate() const { CDateTime update; CAddonDatabase database; if (!database.Open()) return update; VECADDONS addons; CAddonMgr::Get().GetAddons(ADDON_REPOSITORY, addons); for (unsigned int i = 0; i < addons.size(); i++) { CDateTime lastUpdate = database.GetRepoTimestamp(addons[i]->ID()); if (lastUpdate.IsValid() && lastUpdate > update) update = lastUpdate; } return update; } void CAddonInstaller::UpdateRepos(bool force, bool wait) { CSingleLock lock(m_critSection); if (m_repoUpdateJob) { if (wait) { // wait for our job to complete lock.Leave(); CLog::Log(LOGDEBUG, "%s - waiting for repository update job to finish...", __FUNCTION__); m_repoUpdateDone.Wait(); } return; } // don't run repo update jobs while on the login screen which runs under the master profile if((g_windowManager.GetActiveWindow() & WINDOW_ID_MASK) == WINDOW_LOGIN_SCREEN) return; if (!force && m_repoUpdateWatch.IsRunning() && m_repoUpdateWatch.GetElapsedSeconds() < 600) return; CAddonDatabase database; if (!database.Open()) return; m_repoUpdateWatch.StartZero(); VECADDONS addons; if (CAddonMgr::Get().GetAddons(ADDON_REPOSITORY, addons)) { for (const auto& repo : addons) { CDateTime lastUpdate = database.GetRepoTimestamp(repo->ID()); if (force || !lastUpdate.IsValid() || lastUpdate + CDateTimeSpan(0, 24, 0, 0) < CDateTime::GetCurrentDateTime() || repo->Version() != database.GetRepoVersion(repo->ID())) { CLog::Log(LOGDEBUG, "Checking repositories for updates (triggered by %s)", repo->Name().c_str()); m_repoUpdateJob = CJobManager::GetInstance().AddJob(new CRepositoryUpdateJob(addons), this); if (wait) { // wait for our job to complete lock.Leave(); CLog::Log(LOGDEBUG, "%s - waiting for this repository update job to finish...", __FUNCTION__); m_repoUpdateDone.Wait(); } return; } } } } bool CAddonInstaller::HasJob(const std::string& ID) const { CSingleLock lock(m_critSection); return m_downloadJobs.find(ID) != m_downloadJobs.end(); } void CAddonInstaller::PrunePackageCache() { std::map packs; int64_t size = EnumeratePackageFolder(packs); int64_t limit = (int64_t)g_advancedSettings.m_addonPackageFolderSize * 1024 * 1024; if (size < limit) return; // Prune packages // 1. Remove the largest packages, leaving at least 2 for each add-on CFileItemList items; CAddonDatabase db; db.Open(); for (std::map::const_iterator it = packs.begin(); it != packs.end(); ++it) { it->second->Sort(SortByLabel, SortOrderDescending); for (int j = 2; j < it->second->Size(); j++) items.Add(CFileItemPtr(new CFileItem(*it->second->Get(j)))); } items.Sort(SortBySize, SortOrderDescending); int i = 0; while (size > limit && i < items.Size()) { size -= items[i]->m_dwSize; db.RemovePackage(items[i]->GetPath()); CFileUtils::DeleteItem(items[i++], true); } if (size > limit) { // 2. Remove the oldest packages (leaving least 1 for each add-on) items.Clear(); for (std::map::iterator it = packs.begin(); it != packs.end(); ++it) { if (it->second->Size() > 1) items.Add(CFileItemPtr(new CFileItem(*it->second->Get(1)))); } items.Sort(SortByDate, SortOrderAscending); i = 0; while (size > limit && i < items.Size()) { size -= items[i]->m_dwSize; db.RemovePackage(items[i]->GetPath()); CFileUtils::DeleteItem(items[i++],true); } } // clean up our mess for (std::map::iterator it = packs.begin(); it != packs.end(); ++it) delete it->second; } int64_t CAddonInstaller::EnumeratePackageFolder(std::map& result) { CFileItemList items; CDirectory::GetDirectory("special://home/addons/packages/",items,".zip",DIR_FLAG_NO_FILE_DIRS); int64_t size = 0; for (int i = 0; i < items.Size(); i++) { if (items[i]->m_bIsFolder) continue; size += items[i]->m_dwSize; std::string pack,dummy; AddonVersion::SplitFileName(pack, dummy, items[i]->GetLabel()); if (result.find(pack) == result.end()) result[pack] = new CFileItemList; result[pack]->Add(CFileItemPtr(new CFileItem(*items[i]))); } return size; } CAddonInstallJob::CAddonInstallJob(const AddonPtr &addon, const std::string &hash /* = "" */, bool update /* = false */, const std::string &referer /* = "" */) : m_addon(addon), m_hash(hash), m_update(update), m_referer(referer) { } AddonPtr CAddonInstallJob::GetRepoForAddon(const AddonPtr& addon) { AddonPtr repoPtr; CAddonDatabase database; if (!database.Open()) return repoPtr; std::string repo; if (!database.GetRepoForAddon(addon->ID(), repo)) return repoPtr; if (!CAddonMgr::Get().GetAddon(repo, repoPtr)) return repoPtr; if (std::dynamic_pointer_cast(repoPtr) == NULL) { repoPtr.reset(); return repoPtr; } return repoPtr; } bool CAddonInstallJob::GetAddonWithHash(const std::string& addonID, ADDON::AddonPtr& addon, std::string& hash) { CAddonDatabase database; if (!database.Open()) return false; if (!database.GetAddon(addonID, addon)) return false; AddonPtr ptr = GetRepoForAddon(addon); if (ptr == NULL) return false; RepositoryPtr repo = std::dynamic_pointer_cast(ptr); if (repo == NULL) return false; hash = repo->GetAddonHash(addon); return true; } bool CAddonInstallJob::DoWork() { SetTitle(StringUtils::Format(g_localizeStrings.Get(24057).c_str(), m_addon->Name().c_str())); SetProgress(0); // check whether all the dependencies are available or not SetText(g_localizeStrings.Get(24058)); if (!CAddonInstaller::Get().CheckDependencies(m_addon)) { CLog::Log(LOGERROR, "CAddonInstallJob[%s]: dependency check failed", m_addon->ID().c_str()); ReportInstallError(m_addon->ID(), m_addon->ID(), g_localizeStrings.Get(24044)); return false; } AddonPtr repoPtr = GetRepoForAddon(m_addon); std::string installFrom; if (!repoPtr || repoPtr->Props().libname.empty()) { // Addons are installed by downloading the .zip package on the server to the local // packages folder, then extracting from the local .zip package into the addons folder // Both these functions are achieved by "copying" using the vfs. std::string dest = "special://home/addons/packages/"; std::string package = URIUtils::AddFileToFolder("special://home/addons/packages/", URIUtils::GetFileName(m_addon->Path())); if (URIUtils::HasSlashAtEnd(m_addon->Path())) { // passed in a folder - all we need do is copy it across installFrom = m_addon->Path(); } else { CAddonDatabase db; if (!db.Open()) { CLog::Log(LOGERROR, "CAddonInstallJob[%s]: failed to open database", m_addon->ID().c_str()); ReportInstallError(m_addon->ID(), m_addon->ID()); return false; } std::string md5; // check that we don't already have a valid copy if (!m_hash.empty() && CFile::Exists(package)) { if (db.GetPackageHash(m_addon->ID(), package, md5) && m_hash != md5) { db.RemovePackage(package); CFile::Delete(package); } } // zip passed in - download + extract if (!CFile::Exists(package)) { std::string path(m_addon->Path()); if (!m_referer.empty() && URIUtils::IsInternetStream(path)) { CURL url(path); url.SetProtocolOptions(m_referer); path = url.Get(); } if (!DownloadPackage(path, dest)) { CFile::Delete(package); CLog::Log(LOGERROR, "CAddonInstallJob[%s]: failed to download %s", m_addon->ID().c_str(), package.c_str()); ReportInstallError(m_addon->ID(), URIUtils::GetFileName(package)); return false; } } // at this point we have the package - check that it is valid SetText(g_localizeStrings.Get(24077)); if (!m_hash.empty()) { md5 = CUtil::GetFileMD5(package); if (!StringUtils::EqualsNoCase(md5, m_hash)) { CFile::Delete(package); CLog::Log(LOGERROR, "CAddonInstallJob[%s]: MD5 mismatch after download %s", m_addon->ID().c_str(), package.c_str()); ReportInstallError(m_addon->ID(), URIUtils::GetFileName(package)); return false; } db.AddPackage(m_addon->ID(), package, md5); } // check the archive as well - should have just a single folder in the root CURL archive = URIUtils::CreateArchivePath("zip", CURL(package), ""); CFileItemList archivedFiles; CDirectory::GetDirectory(archive, archivedFiles); if (archivedFiles.Size() != 1 || !archivedFiles[0]->m_bIsFolder) { // invalid package db.RemovePackage(package); CFile::Delete(package); CLog::Log(LOGERROR, "CAddonInstallJob[%s]: invalid package %s", m_addon->ID().c_str(), package.c_str()); ReportInstallError(m_addon->ID(), URIUtils::GetFileName(package)); return false; } installFrom = archivedFiles[0]->GetPath(); } repoPtr.reset(); } // run any pre-install functions bool reloadAddon = OnPreInstall(); // perform install if (!Install(installFrom, repoPtr)) return false; // run any post-install guff OnPostInstall(reloadAddon); // and we're done! MarkFinished(); return true; } bool CAddonInstallJob::DownloadPackage(const std::string &path, const std::string &dest) { if (ShouldCancel(0, 1)) return false; SetText(g_localizeStrings.Get(24078)); // need to download/copy the package first CFileItemList list; list.Add(CFileItemPtr(new CFileItem(path, false))); list[0]->Select(true); return DoFileOperation(CFileOperationJob::ActionReplace, list, dest, true); } bool CAddonInstallJob::DoFileOperation(FileAction action, CFileItemList &items, const std::string &file, bool useSameJob /* = true */) { bool result = false; if (useSameJob) { SetFileOperation(action, items, file); // temporarily disable auto-closing so not to close the current progress indicator bool autoClose = GetAutoClose(); if (autoClose) SetAutoClose(false); // temporarily disable updating title or text bool updateInformation = GetUpdateInformation(); if (updateInformation) SetUpdateInformation(false); result = CFileOperationJob::DoWork(); SetUpdateInformation(updateInformation); SetAutoClose(autoClose); } else { CFileOperationJob job(action, items, file); // pass our progress indicators to the temporary job and only allow it to // show progress updates (no title or text changes) job.SetProgressIndicators(GetProgressBar(), GetProgressDialog(), GetUpdateProgress(), false); result = job.DoWork(); } return result; } bool CAddonInstallJob::OnPreInstall() { return m_addon->OnPreInstall(); } bool CAddonInstallJob::DeleteAddon(const std::string &addonFolder) { CFileItemList list; list.Add(CFileItemPtr(new CFileItem(addonFolder, true))); list[0]->Select(true); return DoFileOperation(CFileOperationJob::ActionDelete, list, "", false); } bool CAddonInstallJob::Install(const std::string &installFrom, const AddonPtr& repo) { SetText(g_localizeStrings.Get(24079)); ADDONDEPS deps = m_addon->GetDeps(); unsigned int totalSteps = static_cast(deps.size()); if (ShouldCancel(0, totalSteps)) return false; // The first thing we do is install dependencies std::string referer = StringUtils::Format("Referer=%s-%s.zip",m_addon->ID().c_str(),m_addon->Version().asString().c_str()); for (ADDONDEPS::iterator it = deps.begin(); it != deps.end(); ++it) { if (it->first != "xbmc.metadata") { const std::string &addonID = it->first; const AddonVersion &version = it->second.first; bool optional = it->second.second; AddonPtr dependency; bool haveAddon = CAddonMgr::Get().GetAddon(addonID, dependency); if ((haveAddon && !dependency->MeetsVersion(version)) || (!haveAddon && !optional)) { // we have it but our version isn't good enough, or we don't have it and we need it bool force = dependency != NULL; // dependency is already queued up for install - ::Install will fail // instead we wait until the Job has finished. note that we // recall install on purpose in case prior installation failed if (CAddonInstaller::Get().HasJob(addonID)) { while (CAddonInstaller::Get().HasJob(addonID)) Sleep(50); force = false; if (!CAddonMgr::Get().IsAddonInstalled(addonID)) { CLog::Log(LOGERROR, "CAddonInstallJob[%s]: failed to install dependency %s", m_addon->ID().c_str(), addonID.c_str()); ReportInstallError(m_addon->ID(), m_addon->ID(), g_localizeStrings.Get(24085)); return false; } } // don't have the addon or the addon isn't new enough - grab it (no new job for these) else if (IsModal()) { AddonPtr addon; std::string hash; if (!CAddonInstallJob::GetAddonWithHash(addonID, addon, hash)) { CLog::Log(LOGERROR, "CAddonInstallJob[%s]: failed to find dependency %s", m_addon->ID().c_str(), addonID.c_str()); ReportInstallError(m_addon->ID(), m_addon->ID(), g_localizeStrings.Get(24085)); return false; } CAddonInstallJob dependencyJob(addon, hash, force, referer); // pass our progress indicators to the temporary job and don't allow it to // show progress or information updates (no progress, title or text changes) dependencyJob.SetProgressIndicators(GetProgressBar(), GetProgressDialog(), false, false); if (!dependencyJob.DoModal()) { CLog::Log(LOGERROR, "CAddonInstallJob[%s]: failed to install dependency %s", m_addon->ID().c_str(), addonID.c_str()); ReportInstallError(m_addon->ID(), m_addon->ID(), g_localizeStrings.Get(24085)); return false; } } else if (!CAddonInstaller::Get().Install(addonID, force, referer, false)) { CLog::Log(LOGERROR, "CAddonInstallJob[%s]: failed to install dependency %s", m_addon->ID().c_str(), addonID.c_str()); ReportInstallError(m_addon->ID(), m_addon->ID(), g_localizeStrings.Get(24085)); return false; } } } if (ShouldCancel(std::distance(deps.begin(), it), totalSteps)) return false; } SetText(g_localizeStrings.Get(24086)); SetProgress(0); // now that we have all our dependencies, we can install our add-on if (repo != NULL) { CFileItemList dummy; std::string s = StringUtils::Format("plugin://%s/?action=install&package=%s&version=%s", repo->ID().c_str(), m_addon->ID().c_str(), m_addon->Version().asString().c_str()); if (!CDirectory::GetDirectory(s, dummy)) { CLog::Log(LOGERROR, "CAddonInstallJob[%s]: installation of repository failed", m_addon->ID().c_str()); ReportInstallError(m_addon->ID(), m_addon->ID()); return false; } } else { std::string addonFolder = installFrom; URIUtils::RemoveSlashAtEnd(addonFolder); addonFolder = URIUtils::AddFileToFolder("special://home/addons/", URIUtils::GetFileName(addonFolder)); CFileItemList install; install.Add(CFileItemPtr(new CFileItem(installFrom, true))); install[0]->Select(true); AddonPtr addon; if (!DoFileOperation(CFileOperationJob::ActionReplace, install, "special://home/addons/", false) || !CAddonMgr::Get().LoadAddonDescription(addonFolder, addon)) { // failed extraction or failed to load addon description DeleteAddon(addonFolder); std::string addonID = URIUtils::GetFileName(addonFolder); CLog::Log(LOGERROR, "CAddonInstallJob[%s]: could not read addon description of %s", addonID.c_str(), addonFolder.c_str()); ReportInstallError(addonID, addonID); return false; } // Update the addon manager so that it has the newly installed add-on. CAddonMgr::Get().FindAddons(); } SetProgress(100); return true; } void CAddonInstallJob::OnPostInstall(bool reloadAddon) { if (!IsModal() && CSettings::Get().GetBool("general.addonnotifications")) CGUIDialogKaiToast::QueueNotification(m_addon->Icon(), m_addon->Name(), g_localizeStrings.Get(m_update ? 24065 : 24064), TOAST_DISPLAY_TIME, false, TOAST_DISPLAY_TIME); m_addon->OnPostInstall(reloadAddon, m_update, IsModal()); } void CAddonInstallJob::ReportInstallError(const std::string& addonID, const std::string& fileName, const std::string& message /* = "" */) { AddonPtr addon; CAddonDatabase database; if (database.Open()) { database.GetAddon(addonID, addon); database.Close(); } MarkFinished(); std::string msg = message; if (addon != NULL) { AddonPtr addon2; CAddonMgr::Get().GetAddon(addonID, addon2); if (msg.empty()) msg = g_localizeStrings.Get(addon2 != NULL ? 113 : 114); if (IsModal()) CGUIDialogOK::ShowAndGetInput(m_addon->Name(), msg); else CGUIDialogKaiToast::QueueNotification(addon->Icon(), addon->Name(), msg, TOAST_DISPLAY_TIME, false); } else { if (msg.empty()) msg = g_localizeStrings.Get(114); if (IsModal()) CGUIDialogOK::ShowAndGetInput(fileName, msg); else CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, fileName, msg, TOAST_DISPLAY_TIME, false); } } std::string CAddonInstallJob::AddonID() const { return m_addon ? m_addon->ID() : ""; } CAddonUnInstallJob::CAddonUnInstallJob(const AddonPtr &addon) : m_addon(addon) { } bool CAddonUnInstallJob::DoWork() { m_addon->OnPreUnInstall(); AddonPtr repoPtr = CAddonInstallJob::GetRepoForAddon(m_addon); RepositoryPtr therepo = std::dynamic_pointer_cast(repoPtr); if (therepo != NULL && !therepo->Props().libname.empty()) { CFileItemList dummy; std::string s = StringUtils::Format("plugin://%s/?action=uninstall&package=%s", therepo->ID().c_str(), m_addon->ID().c_str()); if (!CDirectory::GetDirectory(s, dummy)) return false; } else if (!DeleteAddon(m_addon->Path())) return false; OnPostUnInstall(); return true; } bool CAddonUnInstallJob::DeleteAddon(const std::string &addonFolder) { CFileItemList list; list.Add(CFileItemPtr(new CFileItem(addonFolder, true))); list[0]->Select(true); SetFileOperation(CFileOperationJob::ActionDelete, list, ""); return CFileOperationJob::DoWork(); } void CAddonUnInstallJob::OnPostUnInstall() { bool bSave = false; CFileItemList items; XFILE::CFavouritesDirectory::Load(items); for (int i = 0; i < items.Size(); i++) { if (items[i]->GetPath().find(m_addon->ID()) != std::string::npos) { items.Remove(items[i].get()); bSave = true; } } if (bSave) CFavouritesDirectory::Save(items); m_addon->OnPostUnInstall(); }