diff options
| author | manuel <manuel@mausz.at> | 2015-03-03 16:53:59 +0100 |
|---|---|---|
| committer | manuel <manuel@mausz.at> | 2015-03-03 16:53:59 +0100 |
| commit | ffca21f2743a7b367fa212799c6e2fea6190dd5d (patch) | |
| tree | 0608ea3a29cf644ec9ab204e2b4bb9bfaae1c381 /xbmc/addons/Repository.cpp | |
| download | kodi-pvr-build-ffca21f2743a7b367fa212799c6e2fea6190dd5d.tar.gz kodi-pvr-build-ffca21f2743a7b367fa212799c6e2fea6190dd5d.tar.bz2 kodi-pvr-build-ffca21f2743a7b367fa212799c6e2fea6190dd5d.zip | |
initial commit for kodi master
Diffstat (limited to 'xbmc/addons/Repository.cpp')
| -rw-r--r-- | xbmc/addons/Repository.cpp | 395 |
1 files changed, 395 insertions, 0 deletions
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 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (C) 2005-2013 Team XBMC | ||
| 3 | * http://xbmc.org | ||
| 4 | * | ||
| 5 | * This Program is free software; you can redistribute it and/or modify | ||
| 6 | * it under the terms of the GNU General Public License as published by | ||
| 7 | * the Free Software Foundation; either version 2, or (at your option) | ||
| 8 | * any later version. | ||
| 9 | * | ||
| 10 | * This Program is distributed in the hope that it will be useful, | ||
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 13 | * GNU General Public License for more details. | ||
| 14 | * | ||
| 15 | * You should have received a copy of the GNU General Public License | ||
| 16 | * along with XBMC; see the file COPYING. If not, see | ||
| 17 | * <http://www.gnu.org/licenses/>. | ||
| 18 | * | ||
| 19 | */ | ||
| 20 | |||
| 21 | #include "Repository.h" | ||
| 22 | #include "addons/AddonDatabase.h" | ||
| 23 | #include "addons/AddonInstaller.h" | ||
| 24 | #include "addons/AddonManager.h" | ||
| 25 | #include "dialogs/GUIDialogYesNo.h" | ||
| 26 | #include "dialogs/GUIDialogKaiToast.h" | ||
| 27 | #include "filesystem/File.h" | ||
| 28 | #include "filesystem/PluginDirectory.h" | ||
| 29 | #include "settings/Settings.h" | ||
| 30 | #include "utils/log.h" | ||
| 31 | #include "utils/JobManager.h" | ||
| 32 | #include "utils/StringUtils.h" | ||
| 33 | #include "utils/URIUtils.h" | ||
| 34 | #include "utils/XBMCTinyXML.h" | ||
| 35 | #include "FileItem.h" | ||
| 36 | #include "TextureDatabase.h" | ||
| 37 | #include "URL.h" | ||
| 38 | |||
| 39 | using namespace std; | ||
| 40 | using namespace XFILE; | ||
| 41 | using namespace ADDON; | ||
| 42 | |||
| 43 | AddonPtr CRepository::Clone() const | ||
| 44 | { | ||
| 45 | return AddonPtr(new CRepository(*this)); | ||
| 46 | } | ||
| 47 | |||
| 48 | CRepository::CRepository(const AddonProps& props) : | ||
| 49 | CAddon(props) | ||
| 50 | { | ||
| 51 | } | ||
| 52 | |||
| 53 | CRepository::CRepository(const cp_extension_t *ext) | ||
| 54 | : CAddon(ext) | ||
| 55 | { | ||
| 56 | // read in the other props that we need | ||
| 57 | if (ext) | ||
| 58 | { | ||
| 59 | AddonVersion version("0.0.0"); | ||
| 60 | AddonPtr addonver; | ||
| 61 | if (CAddonMgr::Get().GetAddon("xbmc.addon", addonver)) | ||
| 62 | version = addonver->Version(); | ||
| 63 | for (size_t i = 0; i < ext->configuration->num_children; ++i) | ||
| 64 | { | ||
| 65 | if(ext->configuration->children[i].name && | ||
| 66 | strcmp(ext->configuration->children[i].name, "dir") == 0) | ||
| 67 | { | ||
| 68 | AddonVersion min_version(CAddonMgr::Get().GetExtValue(&ext->configuration->children[i], "@minversion")); | ||
| 69 | if (min_version <= version) | ||
| 70 | { | ||
| 71 | DirInfo dir; | ||
| 72 | dir.version = min_version; | ||
| 73 | dir.checksum = CAddonMgr::Get().GetExtValue(&ext->configuration->children[i], "checksum"); | ||
| 74 | dir.compressed = CAddonMgr::Get().GetExtValue(&ext->configuration->children[i], "info@compressed") == "true"; | ||
| 75 | dir.info = CAddonMgr::Get().GetExtValue(&ext->configuration->children[i], "info"); | ||
| 76 | dir.datadir = CAddonMgr::Get().GetExtValue(&ext->configuration->children[i], "datadir"); | ||
| 77 | dir.zipped = CAddonMgr::Get().GetExtValue(&ext->configuration->children[i], "datadir@zip") == "true"; | ||
| 78 | dir.hashes = CAddonMgr::Get().GetExtValue(&ext->configuration->children[i], "hashes") == "true"; | ||
| 79 | m_dirs.push_back(dir); | ||
| 80 | } | ||
| 81 | } | ||
| 82 | } | ||
| 83 | // backward compatibility | ||
| 84 | if (!CAddonMgr::Get().GetExtValue(ext->configuration, "info").empty()) | ||
| 85 | { | ||
| 86 | DirInfo info; | ||
| 87 | info.checksum = CAddonMgr::Get().GetExtValue(ext->configuration, "checksum"); | ||
| 88 | info.compressed = CAddonMgr::Get().GetExtValue(ext->configuration, "info@compressed") == "true"; | ||
| 89 | info.info = CAddonMgr::Get().GetExtValue(ext->configuration, "info"); | ||
| 90 | info.datadir = CAddonMgr::Get().GetExtValue(ext->configuration, "datadir"); | ||
| 91 | info.zipped = CAddonMgr::Get().GetExtValue(ext->configuration, "datadir@zip") == "true"; | ||
| 92 | info.hashes = CAddonMgr::Get().GetExtValue(ext->configuration, "hashes") == "true"; | ||
| 93 | m_dirs.push_back(info); | ||
| 94 | } | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | CRepository::CRepository(const CRepository &rhs) | ||
| 99 | : CAddon(rhs), m_dirs(rhs.m_dirs) | ||
| 100 | { | ||
| 101 | } | ||
| 102 | |||
| 103 | CRepository::~CRepository() | ||
| 104 | { | ||
| 105 | } | ||
| 106 | |||
| 107 | string CRepository::FetchChecksum(const string& url) | ||
| 108 | { | ||
| 109 | CFile file; | ||
| 110 | try | ||
| 111 | { | ||
| 112 | if (file.Open(url)) | ||
| 113 | { | ||
| 114 | // we intentionally avoid using file.GetLength() for | ||
| 115 | // Transfer-Encoding: chunked servers. | ||
| 116 | std::stringstream str; | ||
| 117 | char temp[1024]; | ||
| 118 | int read; | ||
| 119 | while ((read=file.Read(temp, sizeof(temp))) > 0) | ||
| 120 | str.write(temp, read); | ||
| 121 | return str.str(); | ||
| 122 | } | ||
| 123 | return ""; | ||
| 124 | } | ||
| 125 | catch (...) | ||
| 126 | { | ||
| 127 | return ""; | ||
| 128 | } | ||
| 129 | } | ||
| 130 | |||
| 131 | string CRepository::GetAddonHash(const AddonPtr& addon) const | ||
| 132 | { | ||
| 133 | string checksum; | ||
| 134 | DirList::const_iterator it; | ||
| 135 | for (it = m_dirs.begin();it != m_dirs.end(); ++it) | ||
| 136 | if (URIUtils::IsInPath(addon->Path(), it->datadir)) | ||
| 137 | break; | ||
| 138 | if (it != m_dirs.end() && it->hashes) | ||
| 139 | { | ||
| 140 | checksum = FetchChecksum(addon->Path()+".md5"); | ||
| 141 | size_t pos = checksum.find_first_of(" \n"); | ||
| 142 | if (pos != string::npos) | ||
| 143 | return checksum.substr(0, pos); | ||
| 144 | } | ||
| 145 | return checksum; | ||
| 146 | } | ||
| 147 | |||
| 148 | #define SET_IF_NOT_EMPTY(x,y) \ | ||
| 149 | { \ | ||
| 150 | if (!x.empty()) \ | ||
| 151 | x = y; \ | ||
| 152 | } | ||
| 153 | |||
| 154 | bool CRepository::Parse(const DirInfo& dir, VECADDONS &result) | ||
| 155 | { | ||
| 156 | string file = dir.info; | ||
| 157 | if (dir.compressed) | ||
| 158 | { | ||
| 159 | CURL url(dir.info); | ||
| 160 | string opts = url.GetProtocolOptions(); | ||
| 161 | if (!opts.empty()) | ||
| 162 | opts += "&"; | ||
| 163 | url.SetProtocolOptions(opts+"Encoding=gzip"); | ||
| 164 | file = url.Get(); | ||
| 165 | } | ||
| 166 | |||
| 167 | CXBMCTinyXML doc; | ||
| 168 | if (doc.LoadFile(file) && doc.RootElement() && | ||
| 169 | CAddonMgr::Get().AddonsFromRepoXML(doc.RootElement(), result)) | ||
| 170 | { | ||
| 171 | for (IVECADDONS i = result.begin(); i != result.end(); ++i) | ||
| 172 | { | ||
| 173 | AddonPtr addon = *i; | ||
| 174 | if (dir.zipped) | ||
| 175 | { | ||
| 176 | string file = StringUtils::Format("%s/%s-%s.zip", addon->ID().c_str(), addon->ID().c_str(), addon->Version().asString().c_str()); | ||
| 177 | addon->Props().path = URIUtils::AddFileToFolder(dir.datadir,file); | ||
| 178 | SET_IF_NOT_EMPTY(addon->Props().icon,URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/icon.png")) | ||
| 179 | file = StringUtils::Format("%s/changelog-%s.txt", addon->ID().c_str(), addon->Version().asString().c_str()); | ||
| 180 | SET_IF_NOT_EMPTY(addon->Props().changelog,URIUtils::AddFileToFolder(dir.datadir,file)) | ||
| 181 | SET_IF_NOT_EMPTY(addon->Props().fanart,URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/fanart.jpg")) | ||
| 182 | } | ||
| 183 | else | ||
| 184 | { | ||
| 185 | addon->Props().path = URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/"); | ||
| 186 | SET_IF_NOT_EMPTY(addon->Props().icon,URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/icon.png")) | ||
| 187 | SET_IF_NOT_EMPTY(addon->Props().changelog,URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/changelog.txt")) | ||
| 188 | SET_IF_NOT_EMPTY(addon->Props().fanart,URIUtils::AddFileToFolder(dir.datadir,addon->ID()+"/fanart.jpg")) | ||
| 189 | } | ||
| 190 | } | ||
| 191 | return true; | ||
| 192 | } | ||
| 193 | return false; | ||
| 194 | } | ||
| 195 | |||
| 196 | void CRepository::OnPostInstall(bool restart, bool update, bool modal) | ||
| 197 | { | ||
| 198 | VECADDONS addons; | ||
| 199 | AddonPtr repo(new CRepository(*this)); | ||
| 200 | addons.push_back(repo); | ||
| 201 | CJobManager::GetInstance().AddJob(new CRepositoryUpdateJob(addons), &CAddonInstaller::Get()); | ||
| 202 | } | ||
| 203 | |||
| 204 | void CRepository::OnPostUnInstall() | ||
| 205 | { | ||
| 206 | CAddonDatabase database; | ||
| 207 | database.Open(); | ||
| 208 | database.DeleteRepository(ID()); | ||
| 209 | } | ||
| 210 | |||
| 211 | CRepositoryUpdateJob::CRepositoryUpdateJob(const VECADDONS &repos) | ||
| 212 | : m_repos(repos) | ||
| 213 | { | ||
| 214 | } | ||
| 215 | |||
| 216 | void MergeAddons(map<string, AddonPtr> &addons, const VECADDONS &new_addons) | ||
| 217 | { | ||
| 218 | for (VECADDONS::const_iterator it = new_addons.begin(); it != new_addons.end(); ++it) | ||
| 219 | { | ||
| 220 | map<string, AddonPtr>::iterator existing = addons.find((*it)->ID()); | ||
| 221 | if (existing != addons.end()) | ||
| 222 | { // already got it - replace if we have a newer version | ||
| 223 | if (existing->second->Version() < (*it)->Version()) | ||
| 224 | existing->second = *it; | ||
| 225 | } | ||
| 226 | else | ||
| 227 | addons.insert(make_pair((*it)->ID(), *it)); | ||
| 228 | } | ||
| 229 | } | ||
| 230 | |||
| 231 | bool CRepositoryUpdateJob::DoWork() | ||
| 232 | { | ||
| 233 | map<string, AddonPtr> addons; | ||
| 234 | for (VECADDONS::const_iterator i = m_repos.begin(); i != m_repos.end(); ++i) | ||
| 235 | { | ||
| 236 | if (ShouldCancel(0, 0)) | ||
| 237 | return false; | ||
| 238 | const RepositoryPtr repo = std::dynamic_pointer_cast<CRepository>(*i); | ||
| 239 | VECADDONS newAddons; | ||
| 240 | if (GrabAddons(repo, newAddons)) | ||
| 241 | MergeAddons(addons, newAddons); | ||
| 242 | } | ||
| 243 | if (addons.empty()) | ||
| 244 | return true; //Nothing to do | ||
| 245 | |||
| 246 | // check for updates | ||
| 247 | CAddonDatabase database; | ||
| 248 | database.Open(); | ||
| 249 | database.BeginMultipleExecute(); | ||
| 250 | |||
| 251 | CTextureDatabase textureDB; | ||
| 252 | textureDB.Open(); | ||
| 253 | textureDB.BeginMultipleExecute(); | ||
| 254 | VECADDONS notifications; | ||
| 255 | for (map<string, AddonPtr>::const_iterator i = addons.begin(); i != addons.end(); ++i) | ||
| 256 | { | ||
| 257 | // manager told us to feck off | ||
| 258 | if (ShouldCancel(0,0)) | ||
| 259 | break; | ||
| 260 | |||
| 261 | AddonPtr newAddon = i->second; | ||
| 262 | bool deps_met = CAddonInstaller::Get().CheckDependencies(newAddon, &database); | ||
| 263 | if (!deps_met && newAddon->Props().broken.empty()) | ||
| 264 | newAddon->Props().broken = "DEPSNOTMET"; | ||
| 265 | |||
| 266 | // invalidate the art associated with this item | ||
| 267 | if (!newAddon->Props().fanart.empty()) | ||
| 268 | textureDB.InvalidateCachedTexture(newAddon->Props().fanart); | ||
| 269 | if (!newAddon->Props().icon.empty()) | ||
| 270 | textureDB.InvalidateCachedTexture(newAddon->Props().icon); | ||
| 271 | |||
| 272 | AddonPtr addon; | ||
| 273 | CAddonMgr::Get().GetAddon(newAddon->ID(),addon); | ||
| 274 | if (addon && newAddon->Version() > addon->Version() && | ||
| 275 | !database.IsAddonBlacklisted(newAddon->ID(),newAddon->Version().asString()) && | ||
| 276 | deps_met) | ||
| 277 | { | ||
| 278 | if (CSettings::Get().GetInt("general.addonupdates") == AUTO_UPDATES_ON) | ||
| 279 | { | ||
| 280 | string referer; | ||
| 281 | if (URIUtils::IsInternetStream(newAddon->Path())) | ||
| 282 | referer = StringUtils::Format("Referer=%s-%s.zip",addon->ID().c_str(),addon->Version().asString().c_str()); | ||
| 283 | |||
| 284 | if (newAddon->CanInstall(referer)) | ||
| 285 | CAddonInstaller::Get().Install(addon->ID(), true, referer); | ||
| 286 | } | ||
| 287 | else | ||
| 288 | notifications.push_back(addon); | ||
| 289 | } | ||
| 290 | |||
| 291 | // Check if we should mark the add-on as broken. We may have a newer version | ||
| 292 | // of this add-on in the database or installed - if so, we keep it unbroken. | ||
| 293 | bool haveNewer = (addon && addon->Version() > newAddon->Version()) || | ||
| 294 | database.GetAddonVersion(newAddon->ID()) > newAddon->Version(); | ||
| 295 | if (!haveNewer) | ||
| 296 | { | ||
| 297 | if (!newAddon->Props().broken.empty()) | ||
| 298 | { | ||
| 299 | if (database.IsAddonBroken(newAddon->ID()).empty()) | ||
| 300 | { | ||
| 301 | std::string line = g_localizeStrings.Get(24096); | ||
| 302 | if (newAddon->Props().broken == "DEPSNOTMET") | ||
| 303 | line = g_localizeStrings.Get(24104); | ||
| 304 | if (addon && CGUIDialogYesNo::ShowAndGetInput(newAddon->Name(), | ||
| 305 | line, | ||
| 306 | g_localizeStrings.Get(24097), | ||
| 307 | "")) | ||
| 308 | CAddonMgr::Get().DisableAddon(newAddon->ID()); | ||
| 309 | } | ||
| 310 | } | ||
| 311 | database.BreakAddon(newAddon->ID(), newAddon->Props().broken); | ||
| 312 | } | ||
| 313 | } | ||
| 314 | database.CommitMultipleExecute(); | ||
| 315 | textureDB.CommitMultipleExecute(); | ||
| 316 | if (!notifications.empty() && CSettings::Get().GetBool("general.addonnotifications")) | ||
| 317 | { | ||
| 318 | if (notifications.size() == 1) | ||
| 319 | CGUIDialogKaiToast::QueueNotification(notifications[0]->Icon(), | ||
| 320 | g_localizeStrings.Get(24061), | ||
| 321 | notifications[0]->Name(),TOAST_DISPLAY_TIME,false,TOAST_DISPLAY_TIME); | ||
| 322 | else | ||
| 323 | CGUIDialogKaiToast::QueueNotification("", | ||
| 324 | g_localizeStrings.Get(24001), | ||
| 325 | g_localizeStrings.Get(24061),TOAST_DISPLAY_TIME,false,TOAST_DISPLAY_TIME); | ||
| 326 | } | ||
| 327 | |||
| 328 | return true; | ||
| 329 | } | ||
| 330 | |||
| 331 | bool CRepositoryUpdateJob::GrabAddons(const RepositoryPtr& repo, VECADDONS& addons) | ||
| 332 | { | ||
| 333 | CAddonDatabase database; | ||
| 334 | database.Open(); | ||
| 335 | string oldReposum; | ||
| 336 | if (!database.GetRepoChecksum(repo->ID(), oldReposum)) | ||
| 337 | oldReposum = ""; | ||
| 338 | |||
| 339 | string reposum; | ||
| 340 | for (CRepository::DirList::const_iterator it = repo->m_dirs.begin(); it != repo->m_dirs.end(); ++it) | ||
| 341 | { | ||
| 342 | if (ShouldCancel(0, 0)) | ||
| 343 | return false; | ||
| 344 | if (!it->checksum.empty()) | ||
| 345 | { | ||
| 346 | const string dirsum = CRepository::FetchChecksum(it->checksum); | ||
| 347 | if (dirsum.empty()) | ||
| 348 | { | ||
| 349 | CLog::Log(LOGERROR, "Failed to fetch checksum for directory listing %s for repository %s. ", (*it).info.c_str(), repo->ID().c_str()); | ||
| 350 | return false; | ||
| 351 | } | ||
| 352 | reposum += dirsum; | ||
| 353 | } | ||
| 354 | } | ||
| 355 | |||
| 356 | if (oldReposum != reposum || oldReposum.empty()) | ||
| 357 | { | ||
| 358 | map<string, AddonPtr> uniqueAddons; | ||
| 359 | for (CRepository::DirList::const_iterator it = repo->m_dirs.begin(); it != repo->m_dirs.end(); ++it) | ||
| 360 | { | ||
| 361 | if (ShouldCancel(0, 0)) | ||
| 362 | return false; | ||
| 363 | VECADDONS addons; | ||
| 364 | if (!CRepository::Parse(*it, addons)) | ||
| 365 | { //TODO: Hash is invalid and should not be saved, but should we fail? | ||
| 366 | //We can still report a partial addon listing. | ||
| 367 | CLog::Log(LOGERROR, "Failed to read directory listing %s for repository %s. ", (*it).info.c_str(), repo->ID().c_str()); | ||
| 368 | return false; | ||
| 369 | } | ||
| 370 | MergeAddons(uniqueAddons, addons); | ||
| 371 | } | ||
| 372 | |||
| 373 | bool add = true; | ||
| 374 | if (!repo->Props().libname.empty()) | ||
| 375 | { | ||
| 376 | CFileItemList dummy; | ||
| 377 | string s = StringUtils::Format("plugin://%s/?action=update", repo->ID().c_str()); | ||
| 378 | add = CDirectory::GetDirectory(s, dummy); | ||
| 379 | } | ||
| 380 | if (add) | ||
| 381 | { | ||
| 382 | for (map<string, AddonPtr>::const_iterator i = uniqueAddons.begin(); i != uniqueAddons.end(); ++i) | ||
| 383 | addons.push_back(i->second); | ||
| 384 | database.AddRepository(repo->ID(), addons, reposum, repo->Version()); | ||
| 385 | } | ||
| 386 | } | ||
| 387 | else | ||
| 388 | { | ||
| 389 | CLog::Log(LOGDEBUG, "Checksum for repository %s not changed.", repo->ID().c_str()); | ||
| 390 | database.GetRepository(repo->ID(), addons); | ||
| 391 | database.SetRepoTimestamp(repo->ID(), CDateTime::GetCurrentDateTime().GetAsDBDateTime(), repo->Version()); | ||
| 392 | } | ||
| 393 | return true; | ||
| 394 | } | ||
| 395 | |||
