Merge pull request #3454 from tru/fix_coreaudio_mixmapping
[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   // TODO: we currently assume that dependencies will install correctly (and each of their dependencies and so on).
244   //       it may be better to install the dependencies first to minimise the chance of an addon becoming orphaned due to
245   //       missing deps.
246   if (!CheckDependencies(addon))
247   {
248     CGUIDialogKaiToast::QueueNotification(addon->Icon(), addon->Name(), g_localizeStrings.Get(24044), TOAST_DISPLAY_TIME, false);
249     return false;
250   }
251
252   if (background)
253   {
254     unsigned int jobID = CJobManager::GetInstance().AddJob(new CAddonInstallJob(addon, hash, update, referer), this);
255     m_downloadJobs.insert(make_pair(addon->ID(), CDownloadJob(jobID)));
256   }
257   else
258   {
259     m_downloadJobs.insert(make_pair(addon->ID(), CDownloadJob(0)));
260     lock.Leave();
261     CAddonInstallJob job(addon, hash, update, referer);
262     if (!job.DoWork())
263     { // TODO: dump something to debug log?
264       return false;
265     }
266     lock.Enter();
267     JobMap::iterator i = m_downloadJobs.find(addon->ID());
268     m_downloadJobs.erase(i);
269   }
270   return true;
271 }
272
273 bool CAddonInstaller::InstallFromZip(const CStdString &path)
274 {
275   // grab the descriptive XML document from the zip, and read it in
276   CFileItemList items;
277   // BUG: some zip files return a single item (root folder) that we think is stored, so we don't use the zip:// protocol
278   CStdString zipDir;
279   URIUtils::CreateArchivePath(zipDir, "zip", path, "");
280   if (!CDirectory::GetDirectory(zipDir, items) || items.Size() != 1 || !items[0]->m_bIsFolder)
281   {
282     CGUIDialogKaiToast::QueueNotification("", path, g_localizeStrings.Get(24045), TOAST_DISPLAY_TIME, false);
283     return false;
284   }
285
286   // TODO: possibly add support for github generated zips here?
287   CStdString archive = URIUtils::AddFileToFolder(items[0]->GetPath(), "addon.xml");
288
289   CXBMCTinyXML xml;
290   AddonPtr addon;
291   if (xml.LoadFile(archive) && CAddonMgr::Get().LoadAddonDescriptionFromMemory(xml.RootElement(), addon))
292   {
293     // set the correct path
294     addon->Props().path = path;
295
296     // install the addon
297     return DoInstall(addon);
298   }
299   CGUIDialogKaiToast::QueueNotification("", path, g_localizeStrings.Get(24045), TOAST_DISPLAY_TIME, false);
300   return false;
301 }
302
303 void CAddonInstaller::InstallFromXBMCRepo(const set<CStdString> &addonIDs)
304 {
305   // first check we have the our repositories up to date (and wait until we do)
306   UpdateRepos(false, true);
307
308   // now install the addons
309   for (set<CStdString>::const_iterator i = addonIDs.begin(); i != addonIDs.end(); ++i)
310     Install(*i);
311 }
312
313 bool CAddonInstaller::CheckDependencies(const AddonPtr &addon)
314 {
315   std::vector<std::string> preDeps;
316   preDeps.push_back(addon->ID());
317   return CheckDependencies(addon, preDeps);
318 }
319
320 bool CAddonInstaller::CheckDependencies(const AddonPtr &addon,
321                                         std::vector<std::string>& preDeps)
322 {
323   if (!addon.get())
324     return true; // a NULL addon has no dependencies
325   ADDONDEPS deps = addon->GetDeps();
326   CAddonDatabase database;
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         return false;
341       }
342     }
343     // 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
344     // TODO: should we assume that installed deps are OK?
345     if (dep && std::find(preDeps.begin(), preDeps.end(), dep->ID()) == preDeps.end())
346     {
347       if (!CheckDependencies(dep, preDeps))
348         return false;
349       preDeps.push_back(dep->ID());
350     }
351   }
352   return true;
353 }
354
355 void CAddonInstaller::UpdateRepos(bool force, bool wait)
356 {
357   CSingleLock lock(m_critSection);
358   if (m_repoUpdateJob)
359   {
360     if (wait)
361     { // wait for our job to complete
362       lock.Leave();
363       CLog::Log(LOGDEBUG, "%s - waiting for repository update job to finish...", __FUNCTION__);
364       m_repoUpdateDone.Wait();
365     }
366     return;
367   }
368   // don't run repo update jobs while on the login screen which runs under the master profile
369   if((g_windowManager.GetActiveWindow() & WINDOW_ID_MASK) == WINDOW_LOGIN_SCREEN)
370     return;
371   if (!force && m_repoUpdateWatch.IsRunning() && m_repoUpdateWatch.GetElapsedSeconds() < 600)
372     return;
373   m_repoUpdateWatch.StartZero();
374   VECADDONS addons;
375   CAddonMgr::Get().GetAddons(ADDON_REPOSITORY,addons);
376   for (unsigned int i=0;i<addons.size();++i)
377   {
378     CAddonDatabase database;
379     database.Open();
380     CDateTime lastUpdate = database.GetRepoTimestamp(addons[i]->ID());
381     if (force || !lastUpdate.IsValid() || lastUpdate + CDateTimeSpan(0,6,0,0) < CDateTime::GetCurrentDateTime())
382     {
383       CLog::Log(LOGDEBUG,"Checking repositories for updates (triggered by %s)",addons[i]->Name().c_str());
384       m_repoUpdateJob = CJobManager::GetInstance().AddJob(new CRepositoryUpdateJob(addons), this);
385       if (wait)
386       { // wait for our job to complete
387         lock.Leave();
388         CLog::Log(LOGDEBUG, "%s - waiting for this repository update job to finish...", __FUNCTION__);
389         m_repoUpdateDone.Wait();
390       }
391       return;
392     }
393   }
394 }
395
396 bool CAddonInstaller::HasJob(const CStdString& ID) const
397 {
398   CSingleLock lock(m_critSection);
399   return m_downloadJobs.find(ID) != m_downloadJobs.end();
400 }
401
402 void CAddonInstaller::PrunePackageCache()
403 {
404   std::map<CStdString,CFileItemList*> packs;
405   int64_t size = EnumeratePackageFolder(packs);
406   int64_t limit = (int64_t)g_advancedSettings.m_addonPackageFolderSize*1024*1024;
407   if (size < limit)
408     return;
409
410   // Prune packages
411   // 1. Remove the largest packages, leaving at least 2 for each add-on
412   CFileItemList items;
413   for (std::map<CStdString,CFileItemList*>::const_iterator it  = packs.begin();
414                                                           it != packs.end();++it)
415   {
416     it->second->Sort(SortByLabel, SortOrderDescending);
417     for (int j=2;j<it->second->Size();++j)
418       items.Add(CFileItemPtr(new CFileItem(*it->second->Get(j))));
419   }
420   items.Sort(SortBySize, SortOrderDescending);
421   int i=0;
422   while (size > limit && i < items.Size())
423   {
424     size -= items[i]->m_dwSize;
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       CFileUtils::DeleteItem(items[i++],true);
444     }
445   }
446   // clean up our mess
447   for (std::map<CStdString,CFileItemList*>::iterator it  = packs.begin();
448                                                      it != packs.end();++it)
449     delete it->second;
450 }
451
452 int64_t CAddonInstaller::EnumeratePackageFolder(std::map<CStdString,CFileItemList*>& result)
453 {
454   CFileItemList items;
455   CDirectory::GetDirectory("special://home/addons/packages/",items,".zip",DIR_FLAG_NO_FILE_DIRS);
456   int64_t size=0;
457   for (int i=0;i<items.Size();++i)
458   {
459     if (items[i]->m_bIsFolder)
460       continue;
461     size += items[i]->m_dwSize;
462     CStdString pack,dummy;
463     AddonVersion::SplitFileName(pack,dummy,items[i]->GetLabel());
464     if (result.find(pack) == result.end())
465       result[pack] = new CFileItemList;
466     result[pack]->Add(CFileItemPtr(new CFileItem(*items[i])));
467   }
468
469   return size;
470 }
471
472 CAddonInstallJob::CAddonInstallJob(const AddonPtr &addon, const CStdString &hash, bool update, const CStdString &referer)
473 : m_addon(addon), m_hash(hash), m_update(update), m_referer(referer)
474 {
475 }
476
477 AddonPtr CAddonInstallJob::GetRepoForAddon(const AddonPtr& addon)
478 {
479   CAddonDatabase database;
480   database.Open();
481   CStdString repo;
482   database.GetRepoForAddon(addon->ID(), repo);
483   AddonPtr repoPtr;
484   CAddonMgr::Get().GetAddon(repo, repoPtr);
485
486   return repoPtr;
487 }
488
489 bool CAddonInstallJob::DoWork()
490 {
491   AddonPtr repoPtr = GetRepoForAddon(m_addon);
492   CStdString installFrom;
493   if (!repoPtr || repoPtr->Props().libname.IsEmpty())
494   {
495     // Addons are installed by downloading the .zip package on the server to the local
496     // packages folder, then extracting from the local .zip package into the addons folder
497     // Both these functions are achieved by "copying" using the vfs.
498
499     CStdString dest="special://home/addons/packages/";
500     CStdString package = URIUtils::AddFileToFolder("special://home/addons/packages/",
501                                                 URIUtils::GetFileName(m_addon->Path()));
502
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       // zip passed in - download + extract
510       CStdString path(m_addon->Path());
511       if (!m_referer.IsEmpty() && URIUtils::IsInternetStream(path))
512       {
513         CURL url(path);
514         url.SetProtocolOptions(m_referer);
515         path = url.Get();
516       }
517       if (!CFile::Exists(package) && !DownloadPackage(path, dest))
518       {
519         CFile::Delete(package);
520         return false;
521       }
522
523       // at this point we have the package - check that it is valid
524       if (!CFile::Exists(package) || !CheckHash(package))
525       {
526         CFile::Delete(package);
527         return false;
528       }
529
530       // check the archive as well - should have just a single folder in the root
531       CStdString archive;
532       URIUtils::CreateArchivePath(archive,"zip",package,"");
533
534       CFileItemList archivedFiles;
535       CDirectory::GetDirectory(archive, archivedFiles);
536
537       if (archivedFiles.Size() != 1 || !archivedFiles[0]->m_bIsFolder)
538       { // invalid package
539         CFile::Delete(package);
540         return false;
541       }
542       installFrom = archivedFiles[0]->GetPath();
543     }
544     repoPtr.reset();
545   }
546
547   // run any pre-install functions
548   bool reloadAddon = OnPreInstall();
549
550   // perform install
551   if (!Install(installFrom, repoPtr))
552     return false; // something went wrong
553
554   // run any post-install guff
555   OnPostInstall(reloadAddon);
556
557   // and we're done!
558   return true;
559 }
560
561 bool CAddonInstallJob::DownloadPackage(const CStdString &path, const CStdString &dest)
562 { // need to download/copy the package first
563   CFileItemList list;
564   list.Add(CFileItemPtr(new CFileItem(path,false)));
565   list[0]->Select(true);
566   SetFileOperation(CFileOperationJob::ActionReplace, list, dest);
567   return CFileOperationJob::DoWork();
568 }
569
570 bool CAddonInstallJob::OnPreInstall()
571 {
572   // check whether this is an active skin - we need to unload it if so
573   if (CSettings::Get().GetString("lookandfeel.skin") == m_addon->ID())
574   {
575     CApplicationMessenger::Get().ExecBuiltIn("UnloadSkin", true);
576     return true;
577   }
578
579   if (m_addon->Type() == ADDON_SERVICE)
580   {
581     bool running = !CAddonMgr::Get().IsAddonDisabled(m_addon->ID()); //grab a current state
582     CAddonMgr::Get().DisableAddon(m_addon->ID(),false); // enable it so we can remove it??
583     // regrab from manager to have the correct path set
584     AddonPtr addon;
585     ADDON::CAddonMgr::Get().GetAddon(m_addon->ID(), addon);
586     boost::shared_ptr<CService> service = boost::dynamic_pointer_cast<CService>(addon);
587     if (service)
588       service->Stop();
589     CAddonMgr::Get().RemoveAddon(m_addon->ID()); // remove it
590     return running;
591   }
592
593   if (m_addon->Type() == ADDON_PVRDLL)
594   {
595     // stop the pvr manager, so running pvr add-ons are stopped and closed
596     PVR::CPVRManager::Get().Stop();
597   }
598   return false;
599 }
600
601 bool CAddonInstallJob::DeleteAddon(const CStdString &addonFolder)
602 {
603   CFileItemList list;
604   list.Add(CFileItemPtr(new CFileItem(addonFolder, true)));
605   list[0]->Select(true);
606   CFileOperationJob job(CFileOperationJob::ActionDelete, list, "");
607   return job.DoWork();
608 }
609
610 bool CAddonInstallJob::Install(const CStdString &installFrom, const AddonPtr& repo)
611 {
612   if (repo)
613   {
614     CFileItemList dummy;
615     CStdString s;
616     s.Format("plugin://%s/?action=install"
617              "&package=%s&version=%s", repo->ID().c_str(),
618                                        m_addon->ID().c_str(),
619                                        m_addon->Version().c_str());
620     if (!CDirectory::GetDirectory(s, dummy))
621       return false;
622   }
623   else
624   {
625     CStdString addonFolder(installFrom);
626     URIUtils::RemoveSlashAtEnd(addonFolder);
627     addonFolder = URIUtils::AddFileToFolder("special://home/addons/",
628                                          URIUtils::GetFileName(addonFolder));
629
630     CFileItemList install;
631     install.Add(CFileItemPtr(new CFileItem(installFrom, true)));
632     install[0]->Select(true);
633     CFileOperationJob job(CFileOperationJob::ActionReplace, install, "special://home/addons/");
634
635     AddonPtr addon;
636     if (!job.DoWork() || !CAddonMgr::Get().LoadAddonDescription(addonFolder, addon))
637     { // failed extraction or failed to load addon description
638       CStdString addonID = URIUtils::GetFileName(addonFolder);
639       ReportInstallError(addonID, addonID);
640       CLog::Log(LOGERROR,"Could not read addon description of %s", addonID.c_str());
641       DeleteAddon(addonFolder);
642       return false;
643     }
644
645     // resolve dependencies
646     CAddonMgr::Get().FindAddons(); // needed as GetDeps() grabs directly from c-pluff via the addon manager
647     ADDONDEPS deps = addon->GetDeps();
648     CStdString referer;
649     referer.Format("Referer=%s-%s.zip",addon->ID().c_str(),addon->Version().c_str());
650     for (ADDONDEPS::iterator it  = deps.begin(); it != deps.end(); ++it)
651     {
652       if (it->first.Equals("xbmc.metadata"))
653         continue;
654
655       const CStdString &addonID = it->first;
656       const AddonVersion &version = it->second.first;
657       bool optional = it->second.second;
658       AddonPtr dependency;
659       bool haveAddon = CAddonMgr::Get().GetAddon(addonID, dependency);
660       if ((haveAddon && !dependency->MeetsVersion(version)) || (!haveAddon && !optional))
661       { // we have it but our version isn't good enough, or we don't have it and we need it
662         bool force=(dependency != NULL);
663         // dependency is already queued up for install - ::Install will fail
664         // instead we wait until the Job has finished. note that we
665         // recall install on purpose in case prior installation failed
666         if (CAddonInstaller::Get().HasJob(addonID))
667         {
668           while (CAddonInstaller::Get().HasJob(addonID))
669             Sleep(50);
670           force = false;
671         }
672         // don't have the addon or the addon isn't new enough - grab it (no new job for these)
673         if (!CAddonInstaller::Get().Install(addonID, force, referer, false))
674         {
675           DeleteAddon(addonFolder);
676           return false;
677         }
678       }
679     }
680   }
681   return true;
682 }
683
684 void CAddonInstallJob::OnPostInstall(bool reloadAddon)
685 {
686   if (m_addon->Type() < ADDON_VIZ_LIBRARY && CSettings::Get().GetBool("general.addonnotifications"))
687   {
688     CGUIDialogKaiToast::QueueNotification(m_addon->Icon(),
689                                           m_addon->Name(),
690                                           g_localizeStrings.Get(m_update ? 24065 : 24064),
691                                           TOAST_DISPLAY_TIME,false,
692                                           TOAST_DISPLAY_TIME);
693   }
694   if (m_addon->Type() == ADDON_SKIN)
695   {
696     if (reloadAddon || (!m_update && CGUIDialogYesNo::ShowAndGetInput(m_addon->Name(),
697                                                         g_localizeStrings.Get(24099),"","")))
698     {
699       CGUIDialogKaiToast *toast = (CGUIDialogKaiToast *)g_windowManager.GetWindow(WINDOW_DIALOG_KAI_TOAST);
700       if (toast)
701       {
702         toast->ResetTimer();
703         toast->Close(true);
704       }
705       CSettings::Get().SetString("lookandfeel.skin",m_addon->ID().c_str());
706     }
707   }
708
709   if (m_addon->Type() == ADDON_SERVICE)
710   {
711     CAddonMgr::Get().DisableAddon(m_addon->ID(),!reloadAddon); //return it into state it was before OnPreInstall()
712     if (reloadAddon) // reload/start it if it was running
713     {
714       // regrab from manager to have the correct path set
715       AddonPtr addon; 
716       CAddonMgr::Get().GetAddon(m_addon->ID(), addon);
717       boost::shared_ptr<CService> service = boost::dynamic_pointer_cast<CService>(addon);
718       if (service)
719         service->Start();
720     }
721   }
722
723   if (m_addon->Type() == ADDON_REPOSITORY)
724   {
725     VECADDONS addons;
726     addons.push_back(m_addon);
727     CJobManager::GetInstance().AddJob(new CRepositoryUpdateJob(addons), &CAddonInstaller::Get());
728   }
729
730   if (m_addon->Type() == ADDON_PVRDLL)
731   {
732     // (re)start the pvr manager
733     PVR::CPVRManager::Get().Start(true);
734   }
735 }
736
737 void CAddonInstallJob::ReportInstallError(const CStdString& addonID,
738                                                 const CStdString& fileName)
739 {
740   AddonPtr addon;
741   CAddonDatabase database;
742   database.Open();
743   database.GetAddon(addonID, addon);
744   if (addon)
745   {
746     AddonPtr addon2;
747     CAddonMgr::Get().GetAddon(addonID, addon2);
748     CGUIDialogKaiToast::QueueNotification(addon->Icon(),
749                                           addon->Name(),
750                                           g_localizeStrings.Get(addon2 ? 113 : 114),
751                                           TOAST_DISPLAY_TIME, false);
752   }
753   else
754   {
755     CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error,
756                                           fileName,
757                                           g_localizeStrings.Get(114),
758                                           TOAST_DISPLAY_TIME, false);
759   }
760 }
761
762 bool CAddonInstallJob::CheckHash(const CStdString& zipFile)
763 {
764   if (m_hash.IsEmpty())
765     return true;
766   CStdString md5 = CUtil::GetFileMD5(zipFile);
767   if (!md5.Equals(m_hash))
768   {
769     CFile::Delete(zipFile);
770     ReportInstallError(m_addon->ID(), URIUtils::GetFileName(zipFile));
771     CLog::Log(LOGERROR, "MD5 mismatch after download %s", zipFile.c_str());
772     return false;
773   }
774   return true;
775 }
776
777 CStdString CAddonInstallJob::AddonID() const
778 {
779   return (m_addon) ? m_addon->ID() : "";
780 }
781
782 CAddonUnInstallJob::CAddonUnInstallJob(const AddonPtr &addon)
783 : m_addon(addon) 
784 {
785 }
786
787 bool CAddonUnInstallJob::DoWork()
788 {
789   if (m_addon->Type() == ADDON_PVRDLL)
790   {
791     // stop the pvr manager, so running pvr add-ons are stopped and closed
792     PVR::CPVRManager::Get().Stop();
793   }
794   if (m_addon->Type() == ADDON_SERVICE)
795   {
796     boost::shared_ptr<CService> service = boost::dynamic_pointer_cast<CService>(m_addon);
797     if (service)
798       service->Stop();
799   }
800
801   AddonPtr repoPtr = CAddonInstallJob::GetRepoForAddon(m_addon);
802   RepositoryPtr therepo = boost::dynamic_pointer_cast<CRepository>(repoPtr);
803   if (therepo && !therepo->Props().libname.IsEmpty())
804   {
805     CFileItemList dummy;
806     CStdString s;
807     s.Format("plugin://%s/?action=uninstall"
808              "&package=%s", therepo->ID().c_str(), m_addon->ID().c_str());
809     if (!CDirectory::GetDirectory(s, dummy))
810       return false;
811   }
812   else
813   {
814     if (!CAddonInstallJob::DeleteAddon(m_addon->Path()))
815       return false;
816   }
817
818   OnPostUnInstall();
819
820   return true;
821 }
822
823 void CAddonUnInstallJob::OnPostUnInstall()
824 {
825   if (m_addon->Type() == ADDON_REPOSITORY)
826   {
827     CAddonDatabase database;
828     database.Open();
829     database.DeleteRepository(m_addon->ID());
830   }
831
832   bool bSave(false);
833   CFileItemList items;
834   XFILE::CFavouritesDirectory::Load(items);
835   for (int i=0; i < items.Size(); ++i)
836   {
837     if (items[i]->GetPath().Find(m_addon->ID()) > -1)
838     {
839       items.Remove(items[i].get());
840       bSave = true;
841     }
842   }
843
844   if (bSave)
845     CFavouritesDirectory::Save(items);
846
847   if (m_addon->Type() == ADDON_PVRDLL)
848   {
849     if (CSettings::Get().GetBool("pvrmanager.enabled"))
850       PVR::CPVRManager::Get().Start(true);
851   }
852 }