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/Repository.cpp | 395 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 395 insertions(+) create mode 100644 xbmc/addons/Repository.cpp (limited to 'xbmc/addons/Repository.cpp') diff --git a/xbmc/addons/Repository.cpp b/xbmc/addons/Repository.cpp new file mode 100644 index 0000000..e65a195 --- /dev/null +++ b/xbmc/addons/Repository.cpp @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2005-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 "Repository.h" +#include "addons/AddonDatabase.h" +#include "addons/AddonInstaller.h" +#include "addons/AddonManager.h" +#include "dialogs/GUIDialogYesNo.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "filesystem/File.h" +#include "filesystem/PluginDirectory.h" +#include "settings/Settings.h" +#include "utils/log.h" +#include "utils/JobManager.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/XBMCTinyXML.h" +#include "FileItem.h" +#include "TextureDatabase.h" +#include "URL.h" + +using namespace std; +using namespace XFILE; +using namespace ADDON; + +AddonPtr CRepository::Clone() const +{ + return AddonPtr(new CRepository(*this)); +} + +CRepository::CRepository(const AddonProps& props) : + CAddon(props) +{ +} + +CRepository::CRepository(const cp_extension_t *ext) + : CAddon(ext) +{ + // read in the other props that we need + if (ext) + { + AddonVersion version("0.0.0"); + AddonPtr addonver; + if (CAddonMgr::Get().GetAddon("xbmc.addon", addonver)) + version = addonver->Version(); + for (size_t i = 0; i < ext->configuration->num_children; ++i) + { + if(ext->configuration->children[i].name && + strcmp(ext->configuration->children[i].name, "dir") == 0) + { + AddonVersion min_version(CAddonMgr::Get().GetExtValue(&ext->configuration->children[i], "@minversion")); + if (min_version <= version) + { + DirInfo dir; + dir.version = min_version; + dir.checksum = CAddonMgr::Get().GetExtValue(&ext->configuration->children[i], "checksum"); + dir.compressed = CAddonMgr::Get().GetExtValue(&ext->configuration->children[i], "info@compressed") == "true"; + dir.info = CAddonMgr::Get().GetExtValue(&ext->configuration->children[i], "info"); + dir.datadir = CAddonMgr::Get().GetExtValue(&ext->configuration->children[i], "datadir"); + dir.zipped = CAddonMgr::Get().GetExtValue(&ext->configuration->children[i], "datadir@zip") == "true"; + dir.hashes = CAddonMgr::Get().GetExtValue(&ext->configuration->children[i], "hashes") == "true"; + m_dirs.push_back(dir); + } + } + } + // backward compatibility + if (!CAddonMgr::Get().GetExtValue(ext->configuration, "info").empty()) + { + DirInfo info; + info.checksum = CAddonMgr::Get().GetExtValue(ext->configuration, "checksum"); + info.compressed = CAddonMgr::Get().GetExtValue(ext->configuration, "info@compressed") == "true"; + info.info = CAddonMgr::Get().GetExtValue(ext->configuration, "info"); + info.datadir = CAddonMgr::Get().GetExtValue(ext->configuration, "datadir"); + info.zipped = CAddonMgr::Get().GetExtValue(ext->configuration, "datadir@zip") == "true"; + info.hashes = CAddonMgr::Get().GetExtValue(ext->configuration, "hashes") == "true"; + m_dirs.push_back(info); + } + } +} + +CRepository::CRepository(const CRepository &rhs) + : CAddon(rhs), m_dirs(rhs.m_dirs) +{ +} + +CRepository::~CRepository() +{ +} + +string CRepository::FetchChecksum(const string& url) +{ + CFile file; + try + { + if (file.Open(url)) + { + // we intentionally avoid using file.GetLength() for + // Transfer-Encoding: chunked servers. + std::stringstream str; + char temp[1024]; + int read; + while ((read=file.Read(temp, sizeof(temp))) > 0) + str.write(temp, read); + return str.str(); + } + return ""; + } + catch (...) + { + return ""; + } +} + +string CRepository::GetAddonHash(const AddonPtr& addon) const +{ + string checksum; + DirList::const_iterator it; + for (it = m_dirs.begin();it != m_dirs.end(); ++it) + if (URIUtils::IsInPath(addon->Path(), it->datadir)) + break; + if (it != m_dirs.end() && it->hashes) + { + checksum = FetchChecksum(addon->Path()+".md5"); + size_t pos = checksum.find_first_of(" \n"); + if (pos != string::npos) + return checksum.substr(0, pos); + } + return checksum; +} + +#define SET_IF_NOT_EMPTY(x,y) \ + { \ + if (!x.empty()) \ + x = y; \ + } + +bool CRepository::Parse(const DirInfo& dir, VECADDONS &result) +{ + string file = dir.info; + if (dir.compressed) + { + CURL url(dir.info); + string opts = url.GetProtocolOptions(); + if (!opts.empty()) + opts += "&"; + url.SetProtocolOptions(opts+"Encoding=gzip"); + file = url.Get(); + } + + CXBMCTinyXML doc; + if (doc.LoadFile(file) && doc.RootElement() && + CAddonMgr::Get().AddonsFromRepoXML(doc.RootElement(), result)) + { + for (IVECADDONS i = result.begin(); i != result.end(); ++i) + { + AddonPtr addon = *i; + if (dir.zipped) + { + string file = StringUtils::Format("%s/%s-%s.zip", addon->ID().c_str(), addon->ID().c_str(), addon->Version().asString().c_str()); + addon->Props().path = URIUtils::AddFileToFolder(dir.datadir,file); + SET_IF_NOT_EMPTY(addon->Props().icon,URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/icon.png")) + file = StringUtils::Format("%s/changelog-%s.txt", addon->ID().c_str(), addon->Version().asString().c_str()); + SET_IF_NOT_EMPTY(addon->Props().changelog,URIUtils::AddFileToFolder(dir.datadir,file)) + SET_IF_NOT_EMPTY(addon->Props().fanart,URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/fanart.jpg")) + } + else + { + addon->Props().path = URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/"); + SET_IF_NOT_EMPTY(addon->Props().icon,URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/icon.png")) + SET_IF_NOT_EMPTY(addon->Props().changelog,URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/changelog.txt")) + SET_IF_NOT_EMPTY(addon->Props().fanart,URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/fanart.jpg")) + } + } + return true; + } + return false; +} + +void CRepository::OnPostInstall(bool restart, bool update, bool modal) +{ + VECADDONS addons; + AddonPtr repo(new CRepository(*this)); + addons.push_back(repo); + CJobManager::GetInstance().AddJob(new CRepositoryUpdateJob(addons), &CAddonInstaller::Get()); +} + +void CRepository::OnPostUnInstall() +{ + CAddonDatabase database; + database.Open(); + database.DeleteRepository(ID()); +} + +CRepositoryUpdateJob::CRepositoryUpdateJob(const VECADDONS &repos) + : m_repos(repos) +{ +} + +void MergeAddons(map &addons, const VECADDONS &new_addons) +{ + for (VECADDONS::const_iterator it = new_addons.begin(); it != new_addons.end(); ++it) + { + map::iterator existing = addons.find((*it)->ID()); + if (existing != addons.end()) + { // already got it - replace if we have a newer version + if (existing->second->Version() < (*it)->Version()) + existing->second = *it; + } + else + addons.insert(make_pair((*it)->ID(), *it)); + } +} + +bool CRepositoryUpdateJob::DoWork() +{ + map addons; + for (VECADDONS::const_iterator i = m_repos.begin(); i != m_repos.end(); ++i) + { + if (ShouldCancel(0, 0)) + return false; + const RepositoryPtr repo = std::dynamic_pointer_cast(*i); + VECADDONS newAddons; + if (GrabAddons(repo, newAddons)) + MergeAddons(addons, newAddons); + } + if (addons.empty()) + return true; //Nothing to do + + // check for updates + CAddonDatabase database; + database.Open(); + database.BeginMultipleExecute(); + + CTextureDatabase textureDB; + textureDB.Open(); + textureDB.BeginMultipleExecute(); + VECADDONS notifications; + for (map::const_iterator i = addons.begin(); i != addons.end(); ++i) + { + // manager told us to feck off + if (ShouldCancel(0,0)) + break; + + AddonPtr newAddon = i->second; + bool deps_met = CAddonInstaller::Get().CheckDependencies(newAddon, &database); + if (!deps_met && newAddon->Props().broken.empty()) + newAddon->Props().broken = "DEPSNOTMET"; + + // invalidate the art associated with this item + if (!newAddon->Props().fanart.empty()) + textureDB.InvalidateCachedTexture(newAddon->Props().fanart); + if (!newAddon->Props().icon.empty()) + textureDB.InvalidateCachedTexture(newAddon->Props().icon); + + AddonPtr addon; + CAddonMgr::Get().GetAddon(newAddon->ID(),addon); + if (addon && newAddon->Version() > addon->Version() && + !database.IsAddonBlacklisted(newAddon->ID(),newAddon->Version().asString()) && + deps_met) + { + if (CSettings::Get().GetInt("general.addonupdates") == AUTO_UPDATES_ON) + { + string referer; + if (URIUtils::IsInternetStream(newAddon->Path())) + referer = StringUtils::Format("Referer=%s-%s.zip",addon->ID().c_str(),addon->Version().asString().c_str()); + + if (newAddon->CanInstall(referer)) + CAddonInstaller::Get().Install(addon->ID(), true, referer); + } + else + notifications.push_back(addon); + } + + // Check if we should mark the add-on as broken. We may have a newer version + // of this add-on in the database or installed - if so, we keep it unbroken. + bool haveNewer = (addon && addon->Version() > newAddon->Version()) || + database.GetAddonVersion(newAddon->ID()) > newAddon->Version(); + if (!haveNewer) + { + if (!newAddon->Props().broken.empty()) + { + if (database.IsAddonBroken(newAddon->ID()).empty()) + { + std::string line = g_localizeStrings.Get(24096); + if (newAddon->Props().broken == "DEPSNOTMET") + line = g_localizeStrings.Get(24104); + if (addon && CGUIDialogYesNo::ShowAndGetInput(newAddon->Name(), + line, + g_localizeStrings.Get(24097), + "")) + CAddonMgr::Get().DisableAddon(newAddon->ID()); + } + } + database.BreakAddon(newAddon->ID(), newAddon->Props().broken); + } + } + database.CommitMultipleExecute(); + textureDB.CommitMultipleExecute(); + if (!notifications.empty() && CSettings::Get().GetBool("general.addonnotifications")) + { + if (notifications.size() == 1) + CGUIDialogKaiToast::QueueNotification(notifications[0]->Icon(), + g_localizeStrings.Get(24061), + notifications[0]->Name(),TOAST_DISPLAY_TIME,false,TOAST_DISPLAY_TIME); + else + CGUIDialogKaiToast::QueueNotification("", + g_localizeStrings.Get(24001), + g_localizeStrings.Get(24061),TOAST_DISPLAY_TIME,false,TOAST_DISPLAY_TIME); + } + + return true; +} + +bool CRepositoryUpdateJob::GrabAddons(const RepositoryPtr& repo, VECADDONS& addons) +{ + CAddonDatabase database; + database.Open(); + string oldReposum; + if (!database.GetRepoChecksum(repo->ID(), oldReposum)) + oldReposum = ""; + + string reposum; + for (CRepository::DirList::const_iterator it = repo->m_dirs.begin(); it != repo->m_dirs.end(); ++it) + { + if (ShouldCancel(0, 0)) + return false; + if (!it->checksum.empty()) + { + const string dirsum = CRepository::FetchChecksum(it->checksum); + if (dirsum.empty()) + { + CLog::Log(LOGERROR, "Failed to fetch checksum for directory listing %s for repository %s. ", (*it).info.c_str(), repo->ID().c_str()); + return false; + } + reposum += dirsum; + } + } + + if (oldReposum != reposum || oldReposum.empty()) + { + map uniqueAddons; + for (CRepository::DirList::const_iterator it = repo->m_dirs.begin(); it != repo->m_dirs.end(); ++it) + { + if (ShouldCancel(0, 0)) + return false; + VECADDONS addons; + if (!CRepository::Parse(*it, addons)) + { //TODO: Hash is invalid and should not be saved, but should we fail? + //We can still report a partial addon listing. + CLog::Log(LOGERROR, "Failed to read directory listing %s for repository %s. ", (*it).info.c_str(), repo->ID().c_str()); + return false; + } + MergeAddons(uniqueAddons, addons); + } + + bool add = true; + if (!repo->Props().libname.empty()) + { + CFileItemList dummy; + string s = StringUtils::Format("plugin://%s/?action=update", repo->ID().c_str()); + add = CDirectory::GetDirectory(s, dummy); + } + if (add) + { + for (map::const_iterator i = uniqueAddons.begin(); i != uniqueAddons.end(); ++i) + addons.push_back(i->second); + database.AddRepository(repo->ID(), addons, reposum, repo->Version()); + } + } + else + { + CLog::Log(LOGDEBUG, "Checksum for repository %s not changed.", repo->ID().c_str()); + database.GetRepository(repo->ID(), addons); + database.SetRepoTimestamp(repo->ID(), CDateTime::GetCurrentDateTime().GetAsDBDateTime(), repo->Version()); + } + return true; +} + -- cgit v1.2.3