summaryrefslogtreecommitdiffstats
path: root/xbmc/addons/AddonInstaller.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/addons/AddonInstaller.cpp')
-rw-r--r--xbmc/addons/AddonInstaller.cpp974
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
46using namespace std;
47using namespace XFILE;
48using namespace ADDON;
49
50
51struct 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
59CAddonInstaller::CAddonInstaller()
60 : m_repoUpdateJob(0)
61{ }
62
63CAddonInstaller::~CAddonInstaller()
64{ }
65
66CAddonInstaller &CAddonInstaller::Get()
67{
68 static CAddonInstaller addonInstaller;
69 return addonInstaller;
70}
71
72void 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
99void 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
114bool CAddonInstaller::IsDownloading() const
115{
116 CSingleLock lock(m_critSection);
117 return !m_downloadJobs.empty();
118}
119
120void 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
141bool 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
153bool 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
167bool 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
195bool 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
216bool 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
248bool 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
283void 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
293bool 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
304bool 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
350CDateTime 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
369void 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
422bool CAddonInstaller::HasJob(const std::string& ID) const
423{
424 CSingleLock lock(m_critSection);
425 return m_downloadJobs.find(ID) != m_downloadJobs.end();
426}
427
428void 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
482int64_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
503CAddonInstallJob::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
510AddonPtr 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
534bool 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
555bool 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
680bool 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
695bool 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
730bool CAddonInstallJob::OnPreInstall()
731{
732 return m_addon->OnPreInstall();
733}
734
735bool 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
744bool 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
870void 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
880void 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
916std::string CAddonInstallJob::AddonID() const
917{
918 return m_addon ? m_addon->ID() : "";
919}
920
921CAddonUnInstallJob::CAddonUnInstallJob(const AddonPtr &addon)
922 : m_addon(addon)
923{ }
924
925bool 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
946bool 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
956void 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}