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