Merge pull request #4645 from jmarshallnz/install_from_zip_not_packages
[vuplus_xbmc] / xbmc / addons / AddonInstaller.cpp
index 59819aa..4598615 100644 (file)
@@ -80,15 +80,16 @@ void CAddonInstaller::OnJobComplete(unsigned int jobID, bool success, CJob* job)
   { // repo job finished
     m_repoUpdateDone.Set();
     m_repoUpdateJob = 0;
+    lock.Leave();
   }
   else
   { // download job
     JobMap::iterator i = find_if(m_downloadJobs.begin(), m_downloadJobs.end(), bind2nd(find_map(), jobID));
     if (i != m_downloadJobs.end())
       m_downloadJobs.erase(i);
+    lock.Leave();
     PrunePackageCache();
   }
-  lock.Leave();
 
   CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE);
   g_windowManager.SendThreadMessage(msg);
@@ -240,9 +241,6 @@ bool CAddonInstaller::DoInstall(const AddonPtr &addon, const CStdString &hash, b
     return false;
 
   // check whether all the dependencies are available or not
-  // TODO: we currently assume that dependencies will install correctly (and each of their dependencies and so on).
-  //       it may be better to install the dependencies first to minimise the chance of an addon becoming orphaned due to
-  //       missing deps.
   if (!CheckDependencies(addon))
   {
     CGUIDialogKaiToast::QueueNotification(addon->Icon(), addon->Name(), g_localizeStrings.Get(24044), TOAST_DISPLAY_TIME, false);
@@ -291,7 +289,7 @@ bool CAddonInstaller::InstallFromZip(const CStdString &path)
   if (xml.LoadFile(archive) && CAddonMgr::Get().LoadAddonDescriptionFromMemory(xml.RootElement(), addon))
   {
     // set the correct path
-    addon->Props().path = path;
+    addon->Props().path = items[0]->GetPath();
 
     // install the addon
     return DoInstall(addon);
@@ -310,20 +308,22 @@ void CAddonInstaller::InstallFromXBMCRepo(const set<CStdString> &addonIDs)
     Install(*i);
 }
 
-bool CAddonInstaller::CheckDependencies(const AddonPtr &addon)
+bool CAddonInstaller::CheckDependencies(const AddonPtr &addon, CAddonDatabase *database /* = NULL */)
 {
   std::vector<std::string> preDeps;
   preDeps.push_back(addon->ID());
-  return CheckDependencies(addon, preDeps);
+  CAddonDatabase localDB;
+  if (!database)
+    database = &localDB;
+  return CheckDependencies(addon, preDeps, *database);
 }
 
 bool CAddonInstaller::CheckDependencies(const AddonPtr &addon,
-                                        std::vector<std::string>& preDeps)
+                                        std::vector<std::string>& preDeps, CAddonDatabase &database)
 {
   if (!addon.get())
     return true; // a NULL addon has no dependencies
   ADDONDEPS deps = addon->GetDeps();
-  CAddonDatabase database;
   database.Open();
   for (ADDONDEPS::const_iterator i = deps.begin(); i != deps.end(); ++i)
   {
@@ -337,6 +337,7 @@ bool CAddonInstaller::CheckDependencies(const AddonPtr &addon,
       if (!database.GetAddon(addonID, dep) || !dep->MeetsVersion(version))
       { // we don't have it in a repo, or we have it but the version isn't good enough, so dep isn't satisfied.
         CLog::Log(LOGDEBUG, "Addon %s requires %s version %s which is not available", addon->ID().c_str(), addonID.c_str(), version.c_str());
+        database.Close();
         return false;
       }
     }
@@ -344,11 +345,15 @@ bool CAddonInstaller::CheckDependencies(const AddonPtr &addon,
     // TODO: should we assume that installed deps are OK?
     if (dep && std::find(preDeps.begin(), preDeps.end(), dep->ID()) == preDeps.end())
     {
-      if (!CheckDependencies(dep, preDeps))
+      if (!CheckDependencies(dep, preDeps, database))
+      {
+        database.Close();
         return false;
+      }
       preDeps.push_back(dep->ID());
     }
   }
+  database.Close();
   return true;
 }
 
@@ -409,7 +414,9 @@ void CAddonInstaller::PrunePackageCache()
 
   // Prune packages
   // 1. Remove the largest packages, leaving at least 2 for each add-on
-  CFileItemList items;
+  CFileItemList   items;
+  CAddonDatabase  db;
+  db.Open();
   for (std::map<CStdString,CFileItemList*>::const_iterator it  = packs.begin();
                                                           it != packs.end();++it)
   {
@@ -422,6 +429,7 @@ void CAddonInstaller::PrunePackageCache()
   while (size > limit && i < items.Size())
   {
     size -= items[i]->m_dwSize;
+    db.RemovePackage(items[i]->GetPath());
     CFileUtils::DeleteItem(items[i++],true);
   }
 
@@ -440,6 +448,7 @@ void CAddonInstaller::PrunePackageCache()
     while (size > limit && i < items.Size())
     {
       size -= items[i]->m_dwSize;
+      db.RemovePackage(items[i]->GetPath());
       CFileUtils::DeleteItem(items[i++],true);
     }
   }
@@ -490,7 +499,7 @@ bool CAddonInstallJob::DoWork()
 {
   AddonPtr repoPtr = GetRepoForAddon(m_addon);
   CStdString installFrom;
-  if (!repoPtr || repoPtr->Props().libname.IsEmpty())
+  if (!repoPtr || repoPtr->Props().libname.empty())
   {
     // Addons are installed by downloading the .zip package on the server to the local
     // packages folder, then extracting from the local .zip package into the addons folder
@@ -499,32 +508,56 @@ bool CAddonInstallJob::DoWork()
     CStdString dest="special://home/addons/packages/";
     CStdString package = URIUtils::AddFileToFolder("special://home/addons/packages/",
                                                 URIUtils::GetFileName(m_addon->Path()));
-
     if (URIUtils::HasSlashAtEnd(m_addon->Path()))
     { // passed in a folder - all we need do is copy it across
       installFrom = m_addon->Path();
     }
     else
     {
-      // zip passed in - download + extract
-      CStdString path(m_addon->Path());
-      if (!m_referer.IsEmpty() && URIUtils::IsInternetStream(path))
+      CStdString      md5;
+      CAddonDatabase  db;
+      db.Open();
+
+      // check that we don't already have a valid copy
+      if (!m_hash.empty() && CFile::Exists(package))
       {
-        CURL url(path);
-        url.SetProtocolOptions(m_referer);
-        path = url.Get();
+        if (db.GetPackageHash(m_addon->ID(), package, md5) && m_hash != md5)
+        {
+          db.RemovePackage(package);
+          CFile::Delete(package);
+        }
       }
-      if (!CFile::Exists(package) && !DownloadPackage(path, dest))
+
+      // zip passed in - download + extract
+      if (!CFile::Exists(package))
       {
-        CFile::Delete(package);
-        return false;
+        CStdString path(m_addon->Path());
+        if (!m_referer.empty() && URIUtils::IsInternetStream(path))
+        {
+          CURL url(path);
+          url.SetProtocolOptions(m_referer);
+          path = url.Get();
+        }
+
+        if (!DownloadPackage(path, dest))
+        {
+          CFile::Delete(package);
+          return false;
+        }
       }
 
       // at this point we have the package - check that it is valid
-      if (!CFile::Exists(package) || !CheckHash(package))
+      if (!m_hash.empty())
       {
-        CFile::Delete(package);
-        return false;
+        md5 = CUtil::GetFileMD5(package);
+        if (!md5.Equals(m_hash))
+        {
+          CFile::Delete(package);
+          ReportInstallError(m_addon->ID(), URIUtils::GetFileName(package));
+          CLog::Log(LOGERROR, "MD5 mismatch after download %s", package.c_str());
+          return false;
+        }
+        db.AddPackage(m_addon->ID(), package, md5);
       }
 
       // check the archive as well - should have just a single folder in the root
@@ -536,6 +569,7 @@ bool CAddonInstallJob::DoWork()
 
       if (archivedFiles.Size() != 1 || !archivedFiles[0]->m_bIsFolder)
       { // invalid package
+        db.RemovePackage(package);
         CFile::Delete(package);
         return false;
       }
@@ -609,12 +643,43 @@ bool CAddonInstallJob::DeleteAddon(const CStdString &addonFolder)
 
 bool CAddonInstallJob::Install(const CStdString &installFrom, const AddonPtr& repo)
 {
+  // The first thing we do is install dependencies
+  ADDONDEPS deps = m_addon->GetDeps();
+  CStdString referer = StringUtils::Format("Referer=%s-%s.zip",m_addon->ID().c_str(),m_addon->Version().c_str());
+  for (ADDONDEPS::iterator it  = deps.begin(); it != deps.end(); ++it)
+  {
+    if (it->first.Equals("xbmc.metadata"))
+      continue;
+
+    const CStdString &addonID = it->first;
+    const AddonVersion &version = it->second.first;
+    bool optional = it->second.second;
+    AddonPtr dependency;
+    bool haveAddon = CAddonMgr::Get().GetAddon(addonID, dependency);
+    if ((haveAddon && !dependency->MeetsVersion(version)) || (!haveAddon && !optional))
+    { // we have it but our version isn't good enough, or we don't have it and we need it
+      bool force=(dependency != NULL);
+      // dependency is already queued up for install - ::Install will fail
+      // instead we wait until the Job has finished. note that we
+      // recall install on purpose in case prior installation failed
+      if (CAddonInstaller::Get().HasJob(addonID))
+      {
+        while (CAddonInstaller::Get().HasJob(addonID))
+          Sleep(50);
+        force = false;
+      }
+      // don't have the addon or the addon isn't new enough - grab it (no new job for these)
+      if (!CAddonInstaller::Get().Install(addonID, force, referer, false))
+        return false;
+    }
+  }
+
+  // now that we have all our dependencies, we can install our add-on
   if (repo)
   {
     CFileItemList dummy;
-    CStdString s;
-    s.Format("plugin://%s/?action=install"
-             "&package=%s&version=%s", repo->ID().c_str(),
+    CStdString s = StringUtils::Format("plugin://%s/?action=install"
+                                       "&package=%s&version=%s", repo->ID().c_str(),
                                        m_addon->ID().c_str(),
                                        m_addon->Version().c_str());
     if (!CDirectory::GetDirectory(s, dummy))
@@ -642,41 +707,8 @@ bool CAddonInstallJob::Install(const CStdString &installFrom, const AddonPtr& re
       return false;
     }
 
-    // resolve dependencies
-    CAddonMgr::Get().FindAddons(); // needed as GetDeps() grabs directly from c-pluff via the addon manager
-    ADDONDEPS deps = addon->GetDeps();
-    CStdString referer;
-    referer.Format("Referer=%s-%s.zip",addon->ID().c_str(),addon->Version().c_str());
-    for (ADDONDEPS::iterator it  = deps.begin(); it != deps.end(); ++it)
-    {
-      if (it->first.Equals("xbmc.metadata"))
-        continue;
-
-      const CStdString &addonID = it->first;
-      const AddonVersion &version = it->second.first;
-      bool optional = it->second.second;
-      AddonPtr dependency;
-      bool haveAddon = CAddonMgr::Get().GetAddon(addonID, dependency);
-      if ((haveAddon && !dependency->MeetsVersion(version)) || (!haveAddon && !optional))
-      { // we have it but our version isn't good enough, or we don't have it and we need it
-        bool force=(dependency != NULL);
-        // dependency is already queued up for install - ::Install will fail
-        // instead we wait until the Job has finished. note that we
-        // recall install on purpose in case prior installation failed
-        if (CAddonInstaller::Get().HasJob(addonID))
-        {
-          while (CAddonInstaller::Get().HasJob(addonID))
-            Sleep(50);
-          force = false;
-        }
-        // don't have the addon or the addon isn't new enough - grab it (no new job for these)
-        if (!CAddonInstaller::Get().Install(addonID, force, referer, false))
-        {
-          DeleteAddon(addonFolder);
-          return false;
-        }
-      }
-    }
+    // Update the addon manager so that it has the newly installed add-on.
+    CAddonMgr::Get().FindAddons();
   }
   return true;
 }
@@ -702,7 +734,10 @@ void CAddonInstallJob::OnPostInstall(bool reloadAddon)
         toast->ResetTimer();
         toast->Close(true);
       }
-      CSettings::Get().SetString("lookandfeel.skin",m_addon->ID().c_str());
+      if (CSettings::Get().GetString("lookandfeel.skin") == m_addon->ID())
+        CApplicationMessenger::Get().ExecBuiltIn("ReloadSkin", true);
+      else
+        CSettings::Get().SetString("lookandfeel.skin",m_addon->ID().c_str());
     }
   }
 
@@ -759,21 +794,6 @@ void CAddonInstallJob::ReportInstallError(const CStdString& addonID,
   }
 }
 
-bool CAddonInstallJob::CheckHash(const CStdString& zipFile)
-{
-  if (m_hash.IsEmpty())
-    return true;
-  CStdString md5 = CUtil::GetFileMD5(zipFile);
-  if (!md5.Equals(m_hash))
-  {
-    CFile::Delete(zipFile);
-    ReportInstallError(m_addon->ID(), URIUtils::GetFileName(zipFile));
-    CLog::Log(LOGERROR, "MD5 mismatch after download %s", zipFile.c_str());
-    return false;
-  }
-  return true;
-}
-
 CStdString CAddonInstallJob::AddonID() const
 {
   return (m_addon) ? m_addon->ID() : "";
@@ -800,12 +820,11 @@ bool CAddonUnInstallJob::DoWork()
 
   AddonPtr repoPtr = CAddonInstallJob::GetRepoForAddon(m_addon);
   RepositoryPtr therepo = boost::dynamic_pointer_cast<CRepository>(repoPtr);
-  if (therepo && !therepo->Props().libname.IsEmpty())
+  if (therepo && !therepo->Props().libname.empty())
   {
     CFileItemList dummy;
-    CStdString s;
-    s.Format("plugin://%s/?action=uninstall"
-             "&package=%s", therepo->ID().c_str(), m_addon->ID().c_str());
+    CStdString s = StringUtils::Format("plugin://%s/?action=uninstall"
+                                       "&package=%s", therepo->ID().c_str(), m_addon->ID().c_str());
     if (!CDirectory::GetDirectory(s, dummy))
       return false;
   }
@@ -834,7 +853,7 @@ void CAddonUnInstallJob::OnPostUnInstall()
   XFILE::CFavouritesDirectory::Load(items);
   for (int i=0; i < items.Size(); ++i)
   {
-    if (items[i]->GetPath().Find(m_addon->ID()) > -1)
+    if (items[i]->GetPath().find(m_addon->ID()) != std::string::npos)
     {
       items.Remove(items[i].get());
       bSave = true;