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