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