From ffca21f2743a7b367fa212799c6e2fea6190dd5d Mon Sep 17 00:00:00 2001 From: manuel Date: Tue, 3 Mar 2015 16:53:59 +0100 Subject: initial commit for kodi master --- xbmc/addons/AddonInstaller.cpp | 974 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 974 insertions(+) create mode 100644 xbmc/addons/AddonInstaller.cpp (limited to 'xbmc/addons/AddonInstaller.cpp') diff --git a/xbmc/addons/AddonInstaller.cpp b/xbmc/addons/AddonInstaller.cpp new file mode 100644 index 0000000..f4a241c --- /dev/null +++ b/xbmc/addons/AddonInstaller.cpp @@ -0,0 +1,974 @@ +/* + * 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(); +} -- cgit v1.2.3