diff options
Diffstat (limited to 'xbmc/addons/AddonInstaller.cpp')
| -rw-r--r-- | xbmc/addons/AddonInstaller.cpp | 974 |
1 files changed, 974 insertions, 0 deletions
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 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (C) 2011-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 "AddonInstaller.h" | ||
| 22 | #include "utils/log.h" | ||
| 23 | #include "utils/FileUtils.h" | ||
| 24 | #include "utils/URIUtils.h" | ||
| 25 | #include "Util.h" | ||
| 26 | #include "guilib/LocalizeStrings.h" | ||
| 27 | #include "filesystem/Directory.h" | ||
| 28 | #include "settings/AdvancedSettings.h" | ||
| 29 | #include "settings/Settings.h" | ||
| 30 | #include "ApplicationMessenger.h" | ||
| 31 | #include "filesystem/FavouritesDirectory.h" | ||
| 32 | #include "utils/JobManager.h" | ||
| 33 | #include "dialogs/GUIDialogYesNo.h" | ||
| 34 | #include "addons/AddonManager.h" | ||
| 35 | #include "addons/Repository.h" | ||
| 36 | #include "guilib/GUIWindowManager.h" // for callback | ||
| 37 | #include "GUIUserMessages.h" // for callback | ||
| 38 | #include "utils/StringUtils.h" | ||
| 39 | #include "dialogs/GUIDialogKaiToast.h" | ||
| 40 | #include "dialogs/GUIDialogOK.h" | ||
| 41 | #include "dialogs/GUIDialogProgress.h" | ||
| 42 | #include "URL.h" | ||
| 43 | |||
| 44 | #include <functional> | ||
| 45 | |||
| 46 | using namespace std; | ||
| 47 | using namespace XFILE; | ||
| 48 | using namespace ADDON; | ||
| 49 | |||
| 50 | |||
| 51 | struct find_map : public binary_function<CAddonInstaller::JobMap::value_type, unsigned int, bool> | ||
| 52 | { | ||
| 53 | bool operator() (CAddonInstaller::JobMap::value_type t, unsigned int id) const | ||
| 54 | { | ||
| 55 | return (t.second.jobID == id); | ||
| 56 | } | ||
| 57 | }; | ||
| 58 | |||
| 59 | CAddonInstaller::CAddonInstaller() | ||
| 60 | : m_repoUpdateJob(0) | ||
| 61 | { } | ||
| 62 | |||
| 63 | CAddonInstaller::~CAddonInstaller() | ||
| 64 | { } | ||
| 65 | |||
| 66 | CAddonInstaller &CAddonInstaller::Get() | ||
| 67 | { | ||
| 68 | static CAddonInstaller addonInstaller; | ||
| 69 | return addonInstaller; | ||
| 70 | } | ||
| 71 | |||
| 72 | void CAddonInstaller::OnJobComplete(unsigned int jobID, bool success, CJob* job) | ||
| 73 | { | ||
| 74 | if (success) | ||
| 75 | CAddonMgr::Get().FindAddons(); | ||
| 76 | |||
| 77 | CSingleLock lock(m_critSection); | ||
| 78 | if (strncmp(job->GetType(), "repoupdate", 10) == 0) | ||
| 79 | { | ||
| 80 | // repo job finished | ||
| 81 | m_repoUpdateDone.Set(); | ||
| 82 | m_repoUpdateJob = 0; | ||
| 83 | lock.Leave(); | ||
| 84 | } | ||
| 85 | else | ||
| 86 | { | ||
| 87 | // download job | ||
| 88 | JobMap::iterator i = find_if(m_downloadJobs.begin(), m_downloadJobs.end(), bind2nd(find_map(), jobID)); | ||
| 89 | if (i != m_downloadJobs.end()) | ||
| 90 | m_downloadJobs.erase(i); | ||
| 91 | lock.Leave(); | ||
| 92 | PrunePackageCache(); | ||
| 93 | } | ||
| 94 | |||
| 95 | CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE); | ||
| 96 | g_windowManager.SendThreadMessage(msg); | ||
| 97 | } | ||
| 98 | |||
| 99 | void CAddonInstaller::OnJobProgress(unsigned int jobID, unsigned int progress, unsigned int total, const CJob *job) | ||
| 100 | { | ||
| 101 | CSingleLock lock(m_critSection); | ||
| 102 | JobMap::iterator i = find_if(m_downloadJobs.begin(), m_downloadJobs.end(), bind2nd(find_map(), jobID)); | ||
| 103 | if (i != m_downloadJobs.end()) | ||
| 104 | { | ||
| 105 | // update job progress | ||
| 106 | i->second.progress = progress; | ||
| 107 | CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM); | ||
| 108 | msg.SetStringParam(i->first); | ||
| 109 | lock.Leave(); | ||
| 110 | g_windowManager.SendThreadMessage(msg); | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | bool CAddonInstaller::IsDownloading() const | ||
| 115 | { | ||
| 116 | CSingleLock lock(m_critSection); | ||
| 117 | return !m_downloadJobs.empty(); | ||
| 118 | } | ||
| 119 | |||
| 120 | void CAddonInstaller::GetInstallList(VECADDONS &addons) const | ||
| 121 | { | ||
| 122 | CSingleLock lock(m_critSection); | ||
| 123 | vector<std::string> addonIDs; | ||
| 124 | for (JobMap::const_iterator i = m_downloadJobs.begin(); i != m_downloadJobs.end(); ++i) | ||
| 125 | { | ||
| 126 | if (i->second.jobID) | ||
| 127 | addonIDs.push_back(i->first); | ||
| 128 | } | ||
| 129 | lock.Leave(); | ||
| 130 | |||
| 131 | CAddonDatabase database; | ||
| 132 | database.Open(); | ||
| 133 | for (vector<std::string>::iterator it = addonIDs.begin(); it != addonIDs.end(); ++it) | ||
| 134 | { | ||
| 135 | AddonPtr addon; | ||
| 136 | if (database.GetAddon(*it, addon)) | ||
| 137 | addons.push_back(addon); | ||
| 138 | } | ||
| 139 | } | ||
| 140 | |||
| 141 | bool CAddonInstaller::GetProgress(const std::string &addonID, unsigned int &percent) const | ||
| 142 | { | ||
| 143 | CSingleLock lock(m_critSection); | ||
| 144 | JobMap::const_iterator i = m_downloadJobs.find(addonID); | ||
| 145 | if (i != m_downloadJobs.end()) | ||
| 146 | { | ||
| 147 | percent = i->second.progress; | ||
| 148 | return true; | ||
| 149 | } | ||
| 150 | return false; | ||
| 151 | } | ||
| 152 | |||
| 153 | bool CAddonInstaller::Cancel(const std::string &addonID) | ||
| 154 | { | ||
| 155 | CSingleLock lock(m_critSection); | ||
| 156 | JobMap::iterator i = m_downloadJobs.find(addonID); | ||
| 157 | if (i != m_downloadJobs.end()) | ||
| 158 | { | ||
| 159 | CJobManager::GetInstance().CancelJob(i->second.jobID); | ||
| 160 | m_downloadJobs.erase(i); | ||
| 161 | return true; | ||
| 162 | } | ||
| 163 | |||
| 164 | return false; | ||
| 165 | } | ||
| 166 | |||
| 167 | bool CAddonInstaller::InstallModal(const std::string &addonID, ADDON::AddonPtr &addon, bool promptForInstall /* = true */) | ||
| 168 | { | ||
| 169 | if (!g_passwordManager.CheckMenuLock(WINDOW_ADDON_BROWSER)) | ||
| 170 | return false; | ||
| 171 | |||
| 172 | // we assume that addons that are enabled don't get to this routine (i.e. that GetAddon() has been called) | ||
| 173 | if (CAddonMgr::Get().GetAddon(addonID, addon, ADDON_UNKNOWN, false)) | ||
| 174 | return false; // addon is installed but disabled, and the user has specifically activated something that needs | ||
| 175 | // the addon - should we enable it? | ||
| 176 | |||
| 177 | // check we have it available | ||
| 178 | CAddonDatabase database; | ||
| 179 | database.Open(); | ||
| 180 | if (!database.GetAddon(addonID, addon)) | ||
| 181 | return false; | ||
| 182 | |||
| 183 | // if specified ask the user if he wants it installed | ||
| 184 | if (promptForInstall && | ||
| 185 | !CGUIDialogYesNo::ShowAndGetInput(g_localizeStrings.Get(24076), g_localizeStrings.Get(24100), | ||
| 186 | addon->Name().c_str(), g_localizeStrings.Get(24101))) | ||
| 187 | return false; | ||
| 188 | |||
| 189 | if (!Install(addonID, true, "", false, true)) | ||
| 190 | return false; | ||
| 191 | |||
| 192 | return CAddonMgr::Get().GetAddon(addonID, addon); | ||
| 193 | } | ||
| 194 | |||
| 195 | bool CAddonInstaller::Install(const std::string &addonID, bool force /* = false */, const std::string &referer /* = "" */, bool background /* = true */, bool modal /* = false */) | ||
| 196 | { | ||
| 197 | AddonPtr addon; | ||
| 198 | bool addonInstalled = CAddonMgr::Get().GetAddon(addonID, addon, ADDON_UNKNOWN, false); | ||
| 199 | if (addonInstalled && !force) | ||
| 200 | return true; | ||
| 201 | |||
| 202 | if (referer.empty()) | ||
| 203 | { | ||
| 204 | if (!g_passwordManager.CheckMenuLock(WINDOW_ADDON_BROWSER)) | ||
| 205 | return false; | ||
| 206 | } | ||
| 207 | |||
| 208 | // check whether we have it available in a repository | ||
| 209 | std::string hash; | ||
| 210 | if (!CAddonInstallJob::GetAddonWithHash(addonID, addon, hash)) | ||
| 211 | return false; | ||
| 212 | |||
| 213 | return DoInstall(addon, hash, addonInstalled, referer, background, modal); | ||
| 214 | } | ||
| 215 | |||
| 216 | bool CAddonInstaller::DoInstall(const AddonPtr &addon, const std::string &hash /* = "" */, bool update /* = false */, const std::string &referer /* = "" */, bool background /* = true */, bool modal /* = false */) | ||
| 217 | { | ||
| 218 | // check whether we already have the addon installing | ||
| 219 | CSingleLock lock(m_critSection); | ||
| 220 | if (m_downloadJobs.find(addon->ID()) != m_downloadJobs.end()) | ||
| 221 | return false; | ||
| 222 | |||
| 223 | CAddonInstallJob* installJob = new CAddonInstallJob(addon, hash, update, referer); | ||
| 224 | if (background) | ||
| 225 | { | ||
| 226 | unsigned int jobID = CJobManager::GetInstance().AddJob(installJob, this); | ||
| 227 | m_downloadJobs.insert(make_pair(addon->ID(), CDownloadJob(jobID))); | ||
| 228 | return true; | ||
| 229 | } | ||
| 230 | |||
| 231 | m_downloadJobs.insert(make_pair(addon->ID(), CDownloadJob(0))); | ||
| 232 | lock.Leave(); | ||
| 233 | |||
| 234 | bool result = false; | ||
| 235 | if (modal) | ||
| 236 | result = installJob->DoModal(); | ||
| 237 | else | ||
| 238 | result = installJob->DoWork(); | ||
| 239 | delete installJob; | ||
| 240 | |||
| 241 | lock.Enter(); | ||
| 242 | JobMap::iterator i = m_downloadJobs.find(addon->ID()); | ||
| 243 | m_downloadJobs.erase(i); | ||
| 244 | |||
| 245 | return result; | ||
| 246 | } | ||
| 247 | |||
| 248 | bool CAddonInstaller::InstallFromZip(const std::string &path) | ||
| 249 | { | ||
| 250 | if (!g_passwordManager.CheckMenuLock(WINDOW_ADDON_BROWSER)) | ||
| 251 | return false; | ||
| 252 | |||
| 253 | // grab the descriptive XML document from the zip, and read it in | ||
| 254 | CFileItemList items; | ||
| 255 | // BUG: some zip files return a single item (root folder) that we think is stored, so we don't use the zip:// protocol | ||
| 256 | CURL pathToUrl(path); | ||
| 257 | CURL zipDir = URIUtils::CreateArchivePath("zip", pathToUrl, ""); | ||
| 258 | if (!CDirectory::GetDirectory(zipDir, items) || items.Size() != 1 || !items[0]->m_bIsFolder) | ||
| 259 | { | ||
| 260 | CGUIDialogKaiToast::QueueNotification("", path, g_localizeStrings.Get(24045), TOAST_DISPLAY_TIME, false); | ||
| 261 | return false; | ||
| 262 | } | ||
| 263 | |||
| 264 | // TODO: possibly add support for github generated zips here? | ||
| 265 | std::string archive = URIUtils::AddFileToFolder(items[0]->GetPath(), "addon.xml"); | ||
| 266 | |||
| 267 | CXBMCTinyXML xml; | ||
| 268 | AddonPtr addon; | ||
| 269 | if (xml.LoadFile(archive) && CAddonMgr::Get().LoadAddonDescriptionFromMemory(xml.RootElement(), addon)) | ||
| 270 | { | ||
| 271 | // set the correct path | ||
| 272 | addon->Props().path = items[0]->GetPath(); | ||
| 273 | addon->Props().icon = URIUtils::AddFileToFolder(items[0]->GetPath(), "icon.png"); | ||
| 274 | |||
| 275 | // install the addon | ||
| 276 | return DoInstall(addon); | ||
| 277 | } | ||
| 278 | |||
| 279 | CGUIDialogKaiToast::QueueNotification("", path, g_localizeStrings.Get(24045), TOAST_DISPLAY_TIME, false); | ||
| 280 | return false; | ||
| 281 | } | ||
| 282 | |||
| 283 | void CAddonInstaller::InstallFromXBMCRepo(const set<std::string> &addonIDs) | ||
| 284 | { | ||
| 285 | // first check we have the our repositories up to date (and wait until we do) | ||
| 286 | UpdateRepos(false, true); | ||
| 287 | |||
| 288 | // now install the addons | ||
| 289 | for (set<std::string>::const_iterator i = addonIDs.begin(); i != addonIDs.end(); ++i) | ||
| 290 | Install(*i); | ||
| 291 | } | ||
| 292 | |||
| 293 | bool CAddonInstaller::CheckDependencies(const AddonPtr &addon, CAddonDatabase *database /* = NULL */) | ||
| 294 | { | ||
| 295 | std::vector<std::string> preDeps; | ||
| 296 | preDeps.push_back(addon->ID()); | ||
| 297 | CAddonDatabase localDB; | ||
| 298 | if (!database) | ||
| 299 | database = &localDB; | ||
| 300 | |||
| 301 | return CheckDependencies(addon, preDeps, *database); | ||
| 302 | } | ||
| 303 | |||
| 304 | bool CAddonInstaller::CheckDependencies(const AddonPtr &addon, | ||
| 305 | std::vector<std::string>& preDeps, CAddonDatabase &database) | ||
| 306 | { | ||
| 307 | if (addon == NULL) | ||
| 308 | return true; // a NULL addon has no dependencies | ||
| 309 | |||
| 310 | if (!database.Open()) | ||
| 311 | return false; | ||
| 312 | |||
| 313 | ADDONDEPS deps = addon->GetDeps(); | ||
| 314 | for (ADDONDEPS::const_iterator i = deps.begin(); i != deps.end(); ++i) | ||
| 315 | { | ||
| 316 | const std::string &addonID = i->first; | ||
| 317 | const AddonVersion &version = i->second.first; | ||
| 318 | bool optional = i->second.second; | ||
| 319 | AddonPtr dep; | ||
| 320 | bool haveAddon = CAddonMgr::Get().GetAddon(addonID, dep); | ||
| 321 | if ((haveAddon && !dep->MeetsVersion(version)) || (!haveAddon && !optional)) | ||
| 322 | { | ||
| 323 | // we have it but our version isn't good enough, or we don't have it and we need it | ||
| 324 | if (!database.GetAddon(addonID, dep) || !dep->MeetsVersion(version)) | ||
| 325 | { | ||
| 326 | // we don't have it in a repo, or we have it but the version isn't good enough, so dep isn't satisfied. | ||
| 327 | CLog::Log(LOGDEBUG, "CAddonInstallJob[%s]: requires %s version %s which is not available", addon->ID().c_str(), addonID.c_str(), version.asString().c_str()); | ||
| 328 | database.Close(); | ||
| 329 | return false; | ||
| 330 | } | ||
| 331 | } | ||
| 332 | |||
| 333 | // 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 | ||
| 334 | // TODO: should we assume that installed deps are OK? | ||
| 335 | if (dep && std::find(preDeps.begin(), preDeps.end(), dep->ID()) == preDeps.end()) | ||
| 336 | { | ||
| 337 | if (!CheckDependencies(dep, preDeps, database)) | ||
| 338 | { | ||
| 339 | database.Close(); | ||
| 340 | preDeps.push_back(dep->ID()); | ||
| 341 | return false; | ||
| 342 | } | ||
| 343 | } | ||
| 344 | } | ||
| 345 | database.Close(); | ||
| 346 | |||
| 347 | return true; | ||
| 348 | } | ||
| 349 | |||
| 350 | CDateTime CAddonInstaller::LastRepoUpdate() const | ||
| 351 | { | ||
| 352 | CDateTime update; | ||
| 353 | CAddonDatabase database; | ||
| 354 | if (!database.Open()) | ||
| 355 | return update; | ||
| 356 | |||
| 357 | VECADDONS addons; | ||
| 358 | CAddonMgr::Get().GetAddons(ADDON_REPOSITORY, addons); | ||
| 359 | for (unsigned int i = 0; i < addons.size(); i++) | ||
| 360 | { | ||
| 361 | CDateTime lastUpdate = database.GetRepoTimestamp(addons[i]->ID()); | ||
| 362 | if (lastUpdate.IsValid() && lastUpdate > update) | ||
| 363 | update = lastUpdate; | ||
| 364 | } | ||
| 365 | |||
| 366 | return update; | ||
| 367 | } | ||
| 368 | |||
| 369 | void CAddonInstaller::UpdateRepos(bool force, bool wait) | ||
| 370 | { | ||
| 371 | CSingleLock lock(m_critSection); | ||
| 372 | if (m_repoUpdateJob) | ||
| 373 | { | ||
| 374 | if (wait) | ||
| 375 | { | ||
| 376 | // wait for our job to complete | ||
| 377 | lock.Leave(); | ||
| 378 | CLog::Log(LOGDEBUG, "%s - waiting for repository update job to finish...", __FUNCTION__); | ||
| 379 | m_repoUpdateDone.Wait(); | ||
| 380 | } | ||
| 381 | return; | ||
| 382 | } | ||
| 383 | |||
| 384 | // don't run repo update jobs while on the login screen which runs under the master profile | ||
| 385 | if((g_windowManager.GetActiveWindow() & WINDOW_ID_MASK) == WINDOW_LOGIN_SCREEN) | ||
| 386 | return; | ||
| 387 | |||
| 388 | if (!force && m_repoUpdateWatch.IsRunning() && m_repoUpdateWatch.GetElapsedSeconds() < 600) | ||
| 389 | return; | ||
| 390 | |||
| 391 | CAddonDatabase database; | ||
| 392 | if (!database.Open()) | ||
| 393 | return; | ||
| 394 | |||
| 395 | m_repoUpdateWatch.StartZero(); | ||
| 396 | |||
| 397 | VECADDONS addons; | ||
| 398 | if (CAddonMgr::Get().GetAddons(ADDON_REPOSITORY, addons)) | ||
| 399 | { | ||
| 400 | for (const auto& repo : addons) | ||
| 401 | { | ||
| 402 | CDateTime lastUpdate = database.GetRepoTimestamp(repo->ID()); | ||
| 403 | if (force || !lastUpdate.IsValid() || lastUpdate + CDateTimeSpan(0, 24, 0, 0) < CDateTime::GetCurrentDateTime() | ||
| 404 | || repo->Version() != database.GetRepoVersion(repo->ID())) | ||
| 405 | { | ||
| 406 | CLog::Log(LOGDEBUG, "Checking repositories for updates (triggered by %s)", repo->Name().c_str()); | ||
| 407 | m_repoUpdateJob = CJobManager::GetInstance().AddJob(new CRepositoryUpdateJob(addons), this); | ||
| 408 | if (wait) | ||
| 409 | { | ||
| 410 | // wait for our job to complete | ||
| 411 | lock.Leave(); | ||
| 412 | CLog::Log(LOGDEBUG, "%s - waiting for this repository update job to finish...", __FUNCTION__); | ||
| 413 | m_repoUpdateDone.Wait(); | ||
| 414 | } | ||
| 415 | |||
| 416 | return; | ||
| 417 | } | ||
| 418 | } | ||
| 419 | } | ||
| 420 | } | ||
| 421 | |||
| 422 | bool CAddonInstaller::HasJob(const std::string& ID) const | ||
| 423 | { | ||
| 424 | CSingleLock lock(m_critSection); | ||
| 425 | return m_downloadJobs.find(ID) != m_downloadJobs.end(); | ||
| 426 | } | ||
| 427 | |||
| 428 | void CAddonInstaller::PrunePackageCache() | ||
| 429 | { | ||
| 430 | std::map<std::string,CFileItemList*> packs; | ||
| 431 | int64_t size = EnumeratePackageFolder(packs); | ||
| 432 | int64_t limit = (int64_t)g_advancedSettings.m_addonPackageFolderSize * 1024 * 1024; | ||
| 433 | if (size < limit) | ||
| 434 | return; | ||
| 435 | |||
| 436 | // Prune packages | ||
| 437 | // 1. Remove the largest packages, leaving at least 2 for each add-on | ||
| 438 | CFileItemList items; | ||
| 439 | CAddonDatabase db; | ||
| 440 | db.Open(); | ||
| 441 | for (std::map<std::string,CFileItemList*>::const_iterator it = packs.begin(); it != packs.end(); ++it) | ||
| 442 | { | ||
| 443 | it->second->Sort(SortByLabel, SortOrderDescending); | ||
| 444 | for (int j = 2; j < it->second->Size(); j++) | ||
| 445 | items.Add(CFileItemPtr(new CFileItem(*it->second->Get(j)))); | ||
| 446 | } | ||
| 447 | |||
| 448 | items.Sort(SortBySize, SortOrderDescending); | ||
| 449 | int i = 0; | ||
| 450 | while (size > limit && i < items.Size()) | ||
| 451 | { | ||
| 452 | size -= items[i]->m_dwSize; | ||
| 453 | db.RemovePackage(items[i]->GetPath()); | ||
| 454 | CFileUtils::DeleteItem(items[i++], true); | ||
| 455 | } | ||
| 456 | |||
| 457 | if (size > limit) | ||
| 458 | { | ||
| 459 | // 2. Remove the oldest packages (leaving least 1 for each add-on) | ||
| 460 | items.Clear(); | ||
| 461 | for (std::map<std::string,CFileItemList*>::iterator it = packs.begin(); it != packs.end(); ++it) | ||
| 462 | { | ||
| 463 | if (it->second->Size() > 1) | ||
| 464 | items.Add(CFileItemPtr(new CFileItem(*it->second->Get(1)))); | ||
| 465 | } | ||
| 466 | |||
| 467 | items.Sort(SortByDate, SortOrderAscending); | ||
| 468 | i = 0; | ||
| 469 | while (size > limit && i < items.Size()) | ||
| 470 | { | ||
| 471 | size -= items[i]->m_dwSize; | ||
| 472 | db.RemovePackage(items[i]->GetPath()); | ||
| 473 | CFileUtils::DeleteItem(items[i++],true); | ||
| 474 | } | ||
| 475 | } | ||
| 476 | |||
| 477 | // clean up our mess | ||
| 478 | for (std::map<std::string,CFileItemList*>::iterator it = packs.begin(); it != packs.end(); ++it) | ||
| 479 | delete it->second; | ||
| 480 | } | ||
| 481 | |||
| 482 | int64_t CAddonInstaller::EnumeratePackageFolder(std::map<std::string,CFileItemList*>& result) | ||
| 483 | { | ||
| 484 | CFileItemList items; | ||
| 485 | CDirectory::GetDirectory("special://home/addons/packages/",items,".zip",DIR_FLAG_NO_FILE_DIRS); | ||
| 486 | int64_t size = 0; | ||
| 487 | for (int i = 0; i < items.Size(); i++) | ||
| 488 | { | ||
| 489 | if (items[i]->m_bIsFolder) | ||
| 490 | continue; | ||
| 491 | |||
| 492 | size += items[i]->m_dwSize; | ||
| 493 | std::string pack,dummy; | ||
| 494 | AddonVersion::SplitFileName(pack, dummy, items[i]->GetLabel()); | ||
| 495 | if (result.find(pack) == result.end()) | ||
| 496 | result[pack] = new CFileItemList; | ||
| 497 | result[pack]->Add(CFileItemPtr(new CFileItem(*items[i]))); | ||
| 498 | } | ||
| 499 | |||
| 500 | return size; | ||
| 501 | } | ||
| 502 | |||
| 503 | CAddonInstallJob::CAddonInstallJob(const AddonPtr &addon, const std::string &hash /* = "" */, bool update /* = false */, const std::string &referer /* = "" */) | ||
| 504 | : m_addon(addon), | ||
| 505 | m_hash(hash), | ||
| 506 | m_update(update), | ||
| 507 | m_referer(referer) | ||
| 508 | { } | ||
| 509 | |||
| 510 | AddonPtr CAddonInstallJob::GetRepoForAddon(const AddonPtr& addon) | ||
| 511 | { | ||
| 512 | AddonPtr repoPtr; | ||
| 513 | |||
| 514 | CAddonDatabase database; | ||
| 515 | if (!database.Open()) | ||
| 516 | return repoPtr; | ||
| 517 | |||
| 518 | std::string repo; | ||
| 519 | if (!database.GetRepoForAddon(addon->ID(), repo)) | ||
| 520 | return repoPtr; | ||
| 521 | |||
| 522 | if (!CAddonMgr::Get().GetAddon(repo, repoPtr)) | ||
| 523 | return repoPtr; | ||
| 524 | |||
| 525 | if (std::dynamic_pointer_cast<CRepository>(repoPtr) == NULL) | ||
| 526 | { | ||
| 527 | repoPtr.reset(); | ||
| 528 | return repoPtr; | ||
| 529 | } | ||
| 530 | |||
| 531 | return repoPtr; | ||
| 532 | } | ||
| 533 | |||
| 534 | bool CAddonInstallJob::GetAddonWithHash(const std::string& addonID, ADDON::AddonPtr& addon, std::string& hash) | ||
| 535 | { | ||
| 536 | CAddonDatabase database; | ||
| 537 | if (!database.Open()) | ||
| 538 | return false; | ||
| 539 | |||
| 540 | if (!database.GetAddon(addonID, addon)) | ||
| 541 | return false; | ||
| 542 | |||
| 543 | AddonPtr ptr = GetRepoForAddon(addon); | ||
| 544 | if (ptr == NULL) | ||
| 545 | return false; | ||
| 546 | |||
| 547 | RepositoryPtr repo = std::dynamic_pointer_cast<CRepository>(ptr); | ||
| 548 | if (repo == NULL) | ||
| 549 | return false; | ||
| 550 | |||
| 551 | hash = repo->GetAddonHash(addon); | ||
| 552 | return true; | ||
| 553 | } | ||
| 554 | |||
| 555 | bool CAddonInstallJob::DoWork() | ||
| 556 | { | ||
| 557 | SetTitle(StringUtils::Format(g_localizeStrings.Get(24057).c_str(), m_addon->Name().c_str())); | ||
| 558 | SetProgress(0); | ||
| 559 | |||
| 560 | // check whether all the dependencies are available or not | ||
| 561 | SetText(g_localizeStrings.Get(24058)); | ||
| 562 | if (!CAddonInstaller::Get().CheckDependencies(m_addon)) | ||
| 563 | { | ||
| 564 | CLog::Log(LOGERROR, "CAddonInstallJob[%s]: dependency check failed", m_addon->ID().c_str()); | ||
| 565 | ReportInstallError(m_addon->ID(), m_addon->ID(), g_localizeStrings.Get(24044)); | ||
| 566 | return false; | ||
| 567 | } | ||
| 568 | |||
| 569 | AddonPtr repoPtr = GetRepoForAddon(m_addon); | ||
| 570 | std::string installFrom; | ||
| 571 | if (!repoPtr || repoPtr->Props().libname.empty()) | ||
| 572 | { | ||
| 573 | // Addons are installed by downloading the .zip package on the server to the local | ||
| 574 | // packages folder, then extracting from the local .zip package into the addons folder | ||
| 575 | // Both these functions are achieved by "copying" using the vfs. | ||
| 576 | |||
| 577 | std::string dest = "special://home/addons/packages/"; | ||
| 578 | std::string package = URIUtils::AddFileToFolder("special://home/addons/packages/", | ||
| 579 | URIUtils::GetFileName(m_addon->Path())); | ||
| 580 | if (URIUtils::HasSlashAtEnd(m_addon->Path())) | ||
| 581 | { // passed in a folder - all we need do is copy it across | ||
| 582 | installFrom = m_addon->Path(); | ||
| 583 | } | ||
| 584 | else | ||
| 585 | { | ||
| 586 | CAddonDatabase db; | ||
| 587 | if (!db.Open()) | ||
| 588 | { | ||
| 589 | CLog::Log(LOGERROR, "CAddonInstallJob[%s]: failed to open database", m_addon->ID().c_str()); | ||
| 590 | ReportInstallError(m_addon->ID(), m_addon->ID()); | ||
| 591 | return false; | ||
| 592 | } | ||
| 593 | |||
| 594 | std::string md5; | ||
| 595 | // check that we don't already have a valid copy | ||
| 596 | if (!m_hash.empty() && CFile::Exists(package)) | ||
| 597 | { | ||
| 598 | if (db.GetPackageHash(m_addon->ID(), package, md5) && m_hash != md5) | ||
| 599 | { | ||
| 600 | db.RemovePackage(package); | ||
| 601 | CFile::Delete(package); | ||
| 602 | } | ||
| 603 | } | ||
| 604 | |||
| 605 | // zip passed in - download + extract | ||
| 606 | if (!CFile::Exists(package)) | ||
| 607 | { | ||
| 608 | std::string path(m_addon->Path()); | ||
| 609 | if (!m_referer.empty() && URIUtils::IsInternetStream(path)) | ||
| 610 | { | ||
| 611 | CURL url(path); | ||
| 612 | url.SetProtocolOptions(m_referer); | ||
| 613 | path = url.Get(); | ||
| 614 | } | ||
| 615 | |||
| 616 | if (!DownloadPackage(path, dest)) | ||
| 617 | { | ||
| 618 | CFile::Delete(package); | ||
| 619 | |||
| 620 | CLog::Log(LOGERROR, "CAddonInstallJob[%s]: failed to download %s", m_addon->ID().c_str(), package.c_str()); | ||
| 621 | ReportInstallError(m_addon->ID(), URIUtils::GetFileName(package)); | ||
| 622 | return false; | ||
| 623 | } | ||
| 624 | } | ||
| 625 | |||
| 626 | // at this point we have the package - check that it is valid | ||
| 627 | SetText(g_localizeStrings.Get(24077)); | ||
| 628 | if (!m_hash.empty()) | ||
| 629 | { | ||
| 630 | md5 = CUtil::GetFileMD5(package); | ||
| 631 | if (!StringUtils::EqualsNoCase(md5, m_hash)) | ||
| 632 | { | ||
| 633 | CFile::Delete(package); | ||
| 634 | |||
| 635 | CLog::Log(LOGERROR, "CAddonInstallJob[%s]: MD5 mismatch after download %s", m_addon->ID().c_str(), package.c_str()); | ||
| 636 | ReportInstallError(m_addon->ID(), URIUtils::GetFileName(package)); | ||
| 637 | return false; | ||
| 638 | } | ||
| 639 | |||
| 640 | db.AddPackage(m_addon->ID(), package, md5); | ||
| 641 | } | ||
| 642 | |||
| 643 | // check the archive as well - should have just a single folder in the root | ||
| 644 | CURL archive = URIUtils::CreateArchivePath("zip", CURL(package), ""); | ||
| 645 | |||
| 646 | CFileItemList archivedFiles; | ||
| 647 | CDirectory::GetDirectory(archive, archivedFiles); | ||
| 648 | |||
| 649 | if (archivedFiles.Size() != 1 || !archivedFiles[0]->m_bIsFolder) | ||
| 650 | { | ||
| 651 | // invalid package | ||
| 652 | db.RemovePackage(package); | ||
| 653 | CFile::Delete(package); | ||
| 654 | |||
| 655 | CLog::Log(LOGERROR, "CAddonInstallJob[%s]: invalid package %s", m_addon->ID().c_str(), package.c_str()); | ||
| 656 | ReportInstallError(m_addon->ID(), URIUtils::GetFileName(package)); | ||
| 657 | return false; | ||
| 658 | } | ||
| 659 | |||
| 660 | installFrom = archivedFiles[0]->GetPath(); | ||
| 661 | } | ||
| 662 | repoPtr.reset(); | ||
| 663 | } | ||
| 664 | |||
| 665 | // run any pre-install functions | ||
| 666 | bool reloadAddon = OnPreInstall(); | ||
| 667 | |||
| 668 | // perform install | ||
| 669 | if (!Install(installFrom, repoPtr)) | ||
| 670 | return false; | ||
| 671 | |||
| 672 | // run any post-install guff | ||
| 673 | OnPostInstall(reloadAddon); | ||
| 674 | |||
| 675 | // and we're done! | ||
| 676 | MarkFinished(); | ||
| 677 | return true; | ||
| 678 | } | ||
| 679 | |||
| 680 | bool CAddonInstallJob::DownloadPackage(const std::string &path, const std::string &dest) | ||
| 681 | { | ||
| 682 | if (ShouldCancel(0, 1)) | ||
| 683 | return false; | ||
| 684 | |||
| 685 | SetText(g_localizeStrings.Get(24078)); | ||
| 686 | |||
| 687 | // need to download/copy the package first | ||
| 688 | CFileItemList list; | ||
| 689 | list.Add(CFileItemPtr(new CFileItem(path, false))); | ||
| 690 | list[0]->Select(true); | ||
| 691 | |||
| 692 | return DoFileOperation(CFileOperationJob::ActionReplace, list, dest, true); | ||
| 693 | } | ||
| 694 | |||
| 695 | bool CAddonInstallJob::DoFileOperation(FileAction action, CFileItemList &items, const std::string &file, bool useSameJob /* = true */) | ||
| 696 | { | ||
| 697 | bool result = false; | ||
| 698 | if (useSameJob) | ||
| 699 | { | ||
| 700 | SetFileOperation(action, items, file); | ||
| 701 | |||
| 702 | // temporarily disable auto-closing so not to close the current progress indicator | ||
| 703 | bool autoClose = GetAutoClose(); | ||
| 704 | if (autoClose) | ||
| 705 | SetAutoClose(false); | ||
| 706 | // temporarily disable updating title or text | ||
| 707 | bool updateInformation = GetUpdateInformation(); | ||
| 708 | if (updateInformation) | ||
| 709 | SetUpdateInformation(false); | ||
| 710 | |||
| 711 | result = CFileOperationJob::DoWork(); | ||
| 712 | |||
| 713 | SetUpdateInformation(updateInformation); | ||
| 714 | SetAutoClose(autoClose); | ||
| 715 | } | ||
| 716 | else | ||
| 717 | { | ||
| 718 | CFileOperationJob job(action, items, file); | ||
| 719 | |||
| 720 | // pass our progress indicators to the temporary job and only allow it to | ||
| 721 | // show progress updates (no title or text changes) | ||
| 722 | job.SetProgressIndicators(GetProgressBar(), GetProgressDialog(), GetUpdateProgress(), false); | ||
| 723 | |||
| 724 | result = job.DoWork(); | ||
| 725 | } | ||
| 726 | |||
| 727 | return result; | ||
| 728 | } | ||
| 729 | |||
| 730 | bool CAddonInstallJob::OnPreInstall() | ||
| 731 | { | ||
| 732 | return m_addon->OnPreInstall(); | ||
| 733 | } | ||
| 734 | |||
| 735 | bool CAddonInstallJob::DeleteAddon(const std::string &addonFolder) | ||
| 736 | { | ||
| 737 | CFileItemList list; | ||
| 738 | list.Add(CFileItemPtr(new CFileItem(addonFolder, true))); | ||
| 739 | list[0]->Select(true); | ||
| 740 | |||
| 741 | return DoFileOperation(CFileOperationJob::ActionDelete, list, "", false); | ||
| 742 | } | ||
| 743 | |||
| 744 | bool CAddonInstallJob::Install(const std::string &installFrom, const AddonPtr& repo) | ||
| 745 | { | ||
| 746 | SetText(g_localizeStrings.Get(24079)); | ||
| 747 | ADDONDEPS deps = m_addon->GetDeps(); | ||
| 748 | |||
| 749 | unsigned int totalSteps = static_cast<unsigned int>(deps.size()); | ||
| 750 | if (ShouldCancel(0, totalSteps)) | ||
| 751 | return false; | ||
| 752 | |||
| 753 | // The first thing we do is install dependencies | ||
| 754 | std::string referer = StringUtils::Format("Referer=%s-%s.zip",m_addon->ID().c_str(),m_addon->Version().asString().c_str()); | ||
| 755 | for (ADDONDEPS::iterator it = deps.begin(); it != deps.end(); ++it) | ||
| 756 | { | ||
| 757 | if (it->first != "xbmc.metadata") | ||
| 758 | { | ||
| 759 | const std::string &addonID = it->first; | ||
| 760 | const AddonVersion &version = it->second.first; | ||
| 761 | bool optional = it->second.second; | ||
| 762 | AddonPtr dependency; | ||
| 763 | bool haveAddon = CAddonMgr::Get().GetAddon(addonID, dependency); | ||
| 764 | if ((haveAddon && !dependency->MeetsVersion(version)) || (!haveAddon && !optional)) | ||
| 765 | { | ||
| 766 | // we have it but our version isn't good enough, or we don't have it and we need it | ||
| 767 | bool force = dependency != NULL; | ||
| 768 | |||
| 769 | // dependency is already queued up for install - ::Install will fail | ||
| 770 | // instead we wait until the Job has finished. note that we | ||
| 771 | // recall install on purpose in case prior installation failed | ||
| 772 | if (CAddonInstaller::Get().HasJob(addonID)) | ||
| 773 | { | ||
| 774 | while (CAddonInstaller::Get().HasJob(addonID)) | ||
| 775 | Sleep(50); | ||
| 776 | force = false; | ||
| 777 | |||
| 778 | if (!CAddonMgr::Get().IsAddonInstalled(addonID)) | ||
| 779 | { | ||
| 780 | CLog::Log(LOGERROR, "CAddonInstallJob[%s]: failed to install dependency %s", m_addon->ID().c_str(), addonID.c_str()); | ||
| 781 | ReportInstallError(m_addon->ID(), m_addon->ID(), g_localizeStrings.Get(24085)); | ||
| 782 | return false; | ||
| 783 | } | ||
| 784 | } | ||
| 785 | // don't have the addon or the addon isn't new enough - grab it (no new job for these) | ||
| 786 | else if (IsModal()) | ||
| 787 | { | ||
| 788 | AddonPtr addon; | ||
| 789 | std::string hash; | ||
| 790 | if (!CAddonInstallJob::GetAddonWithHash(addonID, addon, hash)) | ||
| 791 | { | ||
| 792 | CLog::Log(LOGERROR, "CAddonInstallJob[%s]: failed to find dependency %s", m_addon->ID().c_str(), addonID.c_str()); | ||
| 793 | ReportInstallError(m_addon->ID(), m_addon->ID(), g_localizeStrings.Get(24085)); | ||
| 794 | return false; | ||
| 795 | } | ||
| 796 | |||
| 797 | CAddonInstallJob dependencyJob(addon, hash, force, referer); | ||
| 798 | |||
| 799 | // pass our progress indicators to the temporary job and don't allow it to | ||
| 800 | // show progress or information updates (no progress, title or text changes) | ||
| 801 | dependencyJob.SetProgressIndicators(GetProgressBar(), GetProgressDialog(), false, false); | ||
| 802 | |||
| 803 | if (!dependencyJob.DoModal()) | ||
| 804 | { | ||
| 805 | CLog::Log(LOGERROR, "CAddonInstallJob[%s]: failed to install dependency %s", m_addon->ID().c_str(), addonID.c_str()); | ||
| 806 | ReportInstallError(m_addon->ID(), m_addon->ID(), g_localizeStrings.Get(24085)); | ||
| 807 | return false; | ||
| 808 | } | ||
| 809 | } | ||
| 810 | else if (!CAddonInstaller::Get().Install(addonID, force, referer, false)) | ||
| 811 | { | ||
| 812 | CLog::Log(LOGERROR, "CAddonInstallJob[%s]: failed to install dependency %s", m_addon->ID().c_str(), addonID.c_str()); | ||
| 813 | ReportInstallError(m_addon->ID(), m_addon->ID(), g_localizeStrings.Get(24085)); | ||
| 814 | return false; | ||
| 815 | } | ||
| 816 | } | ||
| 817 | } | ||
| 818 | |||
| 819 | if (ShouldCancel(std::distance(deps.begin(), it), totalSteps)) | ||
| 820 | return false; | ||
| 821 | } | ||
| 822 | |||
| 823 | SetText(g_localizeStrings.Get(24086)); | ||
| 824 | SetProgress(0); | ||
| 825 | |||
| 826 | // now that we have all our dependencies, we can install our add-on | ||
| 827 | if (repo != NULL) | ||
| 828 | { | ||
| 829 | CFileItemList dummy; | ||
| 830 | std::string s = StringUtils::Format("plugin://%s/?action=install&package=%s&version=%s", repo->ID().c_str(), | ||
| 831 | m_addon->ID().c_str(), m_addon->Version().asString().c_str()); | ||
| 832 | if (!CDirectory::GetDirectory(s, dummy)) | ||
| 833 | { | ||
| 834 | CLog::Log(LOGERROR, "CAddonInstallJob[%s]: installation of repository failed", m_addon->ID().c_str()); | ||
| 835 | ReportInstallError(m_addon->ID(), m_addon->ID()); | ||
| 836 | return false; | ||
| 837 | } | ||
| 838 | } | ||
| 839 | else | ||
| 840 | { | ||
| 841 | std::string addonFolder = installFrom; | ||
| 842 | URIUtils::RemoveSlashAtEnd(addonFolder); | ||
| 843 | addonFolder = URIUtils::AddFileToFolder("special://home/addons/", URIUtils::GetFileName(addonFolder)); | ||
| 844 | |||
| 845 | CFileItemList install; | ||
| 846 | install.Add(CFileItemPtr(new CFileItem(installFrom, true))); | ||
| 847 | install[0]->Select(true); | ||
| 848 | |||
| 849 | AddonPtr addon; | ||
| 850 | if (!DoFileOperation(CFileOperationJob::ActionReplace, install, "special://home/addons/", false) || | ||
| 851 | !CAddonMgr::Get().LoadAddonDescription(addonFolder, addon)) | ||
| 852 | { | ||
| 853 | // failed extraction or failed to load addon description | ||
| 854 | DeleteAddon(addonFolder); | ||
| 855 | |||
| 856 | std::string addonID = URIUtils::GetFileName(addonFolder); | ||
| 857 | CLog::Log(LOGERROR, "CAddonInstallJob[%s]: could not read addon description of %s", addonID.c_str(), addonFolder.c_str()); | ||
| 858 | ReportInstallError(addonID, addonID); | ||
| 859 | return false; | ||
| 860 | } | ||
| 861 | |||
| 862 | // Update the addon manager so that it has the newly installed add-on. | ||
| 863 | CAddonMgr::Get().FindAddons(); | ||
| 864 | } | ||
| 865 | SetProgress(100); | ||
| 866 | |||
| 867 | return true; | ||
| 868 | } | ||
| 869 | |||
| 870 | void CAddonInstallJob::OnPostInstall(bool reloadAddon) | ||
| 871 | { | ||
| 872 | if (!IsModal() && CSettings::Get().GetBool("general.addonnotifications")) | ||
| 873 | CGUIDialogKaiToast::QueueNotification(m_addon->Icon(), m_addon->Name(), | ||
| 874 | g_localizeStrings.Get(m_update ? 24065 : 24064), | ||
| 875 | TOAST_DISPLAY_TIME, false, TOAST_DISPLAY_TIME); | ||
| 876 | |||
| 877 | m_addon->OnPostInstall(reloadAddon, m_update, IsModal()); | ||
| 878 | } | ||
| 879 | |||
| 880 | void CAddonInstallJob::ReportInstallError(const std::string& addonID, const std::string& fileName, const std::string& message /* = "" */) | ||
| 881 | { | ||
| 882 | AddonPtr addon; | ||
| 883 | CAddonDatabase database; | ||
| 884 | if (database.Open()) | ||
| 885 | { | ||
| 886 | database.GetAddon(addonID, addon); | ||
| 887 | database.Close(); | ||
| 888 | } | ||
| 889 | |||
| 890 | MarkFinished(); | ||
| 891 | |||
| 892 | std::string msg = message; | ||
| 893 | if (addon != NULL) | ||
| 894 | { | ||
| 895 | AddonPtr addon2; | ||
| 896 | CAddonMgr::Get().GetAddon(addonID, addon2); | ||
| 897 | if (msg.empty()) | ||
| 898 | msg = g_localizeStrings.Get(addon2 != NULL ? 113 : 114); | ||
| 899 | |||
| 900 | if (IsModal()) | ||
| 901 | CGUIDialogOK::ShowAndGetInput(m_addon->Name(), msg); | ||
| 902 | else | ||
| 903 | CGUIDialogKaiToast::QueueNotification(addon->Icon(), addon->Name(), msg, TOAST_DISPLAY_TIME, false); | ||
| 904 | } | ||
| 905 | else | ||
| 906 | { | ||
| 907 | if (msg.empty()) | ||
| 908 | msg = g_localizeStrings.Get(114); | ||
| 909 | if (IsModal()) | ||
| 910 | CGUIDialogOK::ShowAndGetInput(fileName, msg); | ||
| 911 | else | ||
| 912 | CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, fileName, msg, TOAST_DISPLAY_TIME, false); | ||
| 913 | } | ||
| 914 | } | ||
| 915 | |||
| 916 | std::string CAddonInstallJob::AddonID() const | ||
| 917 | { | ||
| 918 | return m_addon ? m_addon->ID() : ""; | ||
| 919 | } | ||
| 920 | |||
| 921 | CAddonUnInstallJob::CAddonUnInstallJob(const AddonPtr &addon) | ||
| 922 | : m_addon(addon) | ||
| 923 | { } | ||
| 924 | |||
| 925 | bool CAddonUnInstallJob::DoWork() | ||
| 926 | { | ||
| 927 | m_addon->OnPreUnInstall(); | ||
| 928 | |||
| 929 | AddonPtr repoPtr = CAddonInstallJob::GetRepoForAddon(m_addon); | ||
| 930 | RepositoryPtr therepo = std::dynamic_pointer_cast<CRepository>(repoPtr); | ||
| 931 | if (therepo != NULL && !therepo->Props().libname.empty()) | ||
| 932 | { | ||
| 933 | CFileItemList dummy; | ||
| 934 | std::string s = StringUtils::Format("plugin://%s/?action=uninstall&package=%s", therepo->ID().c_str(), m_addon->ID().c_str()); | ||
| 935 | if (!CDirectory::GetDirectory(s, dummy)) | ||
| 936 | return false; | ||
| 937 | } | ||
| 938 | else if (!DeleteAddon(m_addon->Path())) | ||
| 939 | return false; | ||
| 940 | |||
| 941 | OnPostUnInstall(); | ||
| 942 | |||
| 943 | return true; | ||
| 944 | } | ||
| 945 | |||
| 946 | bool CAddonUnInstallJob::DeleteAddon(const std::string &addonFolder) | ||
| 947 | { | ||
| 948 | CFileItemList list; | ||
| 949 | list.Add(CFileItemPtr(new CFileItem(addonFolder, true))); | ||
| 950 | list[0]->Select(true); | ||
| 951 | |||
| 952 | SetFileOperation(CFileOperationJob::ActionDelete, list, ""); | ||
| 953 | return CFileOperationJob::DoWork(); | ||
| 954 | } | ||
| 955 | |||
| 956 | void CAddonUnInstallJob::OnPostUnInstall() | ||
| 957 | { | ||
| 958 | bool bSave = false; | ||
| 959 | CFileItemList items; | ||
| 960 | XFILE::CFavouritesDirectory::Load(items); | ||
| 961 | for (int i = 0; i < items.Size(); i++) | ||
| 962 | { | ||
| 963 | if (items[i]->GetPath().find(m_addon->ID()) != std::string::npos) | ||
| 964 | { | ||
| 965 | items.Remove(items[i].get()); | ||
| 966 | bSave = true; | ||
| 967 | } | ||
| 968 | } | ||
| 969 | |||
| 970 | if (bSave) | ||
| 971 | CFavouritesDirectory::Save(items); | ||
| 972 | |||
| 973 | m_addon->OnPostUnInstall(); | ||
| 974 | } | ||
