Added then-callback to AddonManager and use the then-callback of Downloader
[supertux.git] / src / addon / addon_manager.cpp
index 5153592..c20764f 100644 (file)
@@ -50,15 +50,24 @@ MD5 md5_from_file(const std::string& filename)
 
   unsigned char buffer[1024];
   PHYSFS_file* file = PHYSFS_openRead(filename.c_str());
-  while (true)
+  if (!file)
   {
-    PHYSFS_sint64 len = PHYSFS_read(file, buffer, 1, sizeof(buffer));
-    if (len <= 0) break;
-    md5.update(buffer, len);
+    std::ostringstream out;
+    out << "PHYSFS_openRead() failed: " << PHYSFS_getLastError();
+    throw std::runtime_error(out.str());
   }
-  PHYSFS_close(file);
+  else
+  {
+    while (true)
+    {
+      PHYSFS_sint64 len = PHYSFS_read(file, buffer, 1, sizeof(buffer));
+      if (len <= 0) break;
+      md5.update(buffer, len);
+    }
+    PHYSFS_close(file);
 
-  return md5;
+    return md5;
+  }
 }
 
 bool has_suffix(const std::string& str, const std::string& suffix)
@@ -72,30 +81,47 @@ bool has_suffix(const std::string& str, const std::string& suffix)
 } // namespace
 
 AddonManager::AddonManager(const std::string& addon_directory,
-                           std::vector<std::string>& ignored_addon_ids) :
+                           std::vector<Config::Addon>& addon_config) :
   m_downloader(),
   m_addon_directory(addon_directory),
-  //m_repository_url("http://addons.supertux.googlecode.com/git/index-0_3_5.nfo"),
-  m_repository_url("http://localhost:8000/index-0_4_0.nfo"),
-  m_ignored_addon_ids(ignored_addon_ids),
+  m_repository_url("http://addons.supertux.googlecode.com/git/index-0_4_0.nfo"),
+  m_addon_config(addon_config),
   m_installed_addons(),
-  m_repository_addons()
+  m_repository_addons(),
+  m_has_been_updated(false),
+  m_install_request(),
+  m_install_status(),
+  m_transfer_status()
 {
   PHYSFS_mkdir(m_addon_directory.c_str());
 
   add_installed_addons();
-  for(auto& addon : m_installed_addons)
+
+  // FIXME: We should also restore the order here
+  for(auto& addon : m_addon_config)
   {
-    if (std::find(m_ignored_addon_ids.begin(), m_ignored_addon_ids.end(),
-                  addon->get_id()) != m_ignored_addon_ids.end())
+    if (addon.enabled)
     {
-      enable_addon(addon->get_id());
+      try
+      {
+        enable_addon(addon.id);
+      }
+      catch(const std::exception& err)
+      {
+        log_warning << "failed to enable addon from config: " << err.what() << std::endl;
+      }
     }
   }
 }
 
 AddonManager::~AddonManager()
 {
+  // sync enabled/disabled addons into the config for saving
+  m_addon_config.clear();
+  for(auto& addon : m_installed_addons)
+  {
+    m_addon_config.push_back({addon->get_id(), addon->is_enabled()});
+  }
 }
 
 Addon&
@@ -171,11 +197,105 @@ AddonManager::has_online_support() const
   return true;
 }
 
+bool
+AddonManager::has_been_updated() const
+{
+  return m_has_been_updated;
+}
+
 void
 AddonManager::check_online()
 {
   std::string addoninfos = m_downloader.download(m_repository_url);
   m_repository_addons = parse_addon_infos(addoninfos);
+  m_has_been_updated = true;
+}
+
+AddonManager::InstallStatusPtr
+AddonManager::request_install_addon(const AddonId& addon_id)
+{
+  if (m_install_status)
+  {
+    throw std::runtime_error("only one addon install request allowed at a time");
+  }
+  else
+  {
+    { // remove addon if it already exists
+      auto it = std::find_if(m_installed_addons.begin(), m_installed_addons.end(),
+                             [&addon_id](const std::unique_ptr<Addon>& addon)
+                             {
+                               return addon->get_id() == addon_id;
+                             });
+      if (it != m_installed_addons.end())
+      {
+        log_debug << "reinstalling addon " << addon_id << std::endl;
+        if ((*it)->is_enabled())
+        {
+          disable_addon((*it)->get_id());
+        }
+        m_installed_addons.erase(it);
+      }
+      else
+      {
+        log_debug << "installing addon " << addon_id << std::endl;
+      }
+    }
+
+    {
+      Addon& repository_addon = get_repository_addon(addon_id);
+
+      m_install_request = std::make_shared<InstallRequest>();
+      m_install_request->install_filename = FileSystem::join(m_addon_directory, repository_addon.get_filename());
+      m_install_request->addon_id = addon_id;
+
+      m_transfer_status = m_downloader.request_download(repository_addon.get_url(),
+                                                        m_install_request->install_filename);
+    }
+
+    m_transfer_status->then(
+      [this]
+      {
+        // complete the addon install
+        Addon& repository_addon = get_repository_addon(m_install_request->addon_id);
+
+        MD5 md5 = md5_from_file(m_install_request->install_filename);
+        if (repository_addon.get_md5() != md5.hex_digest())
+        {
+          if (PHYSFS_delete(m_install_request->install_filename.c_str()) == 0)
+          {
+            log_warning << "PHYSFS_delete failed: " << PHYSFS_getLastError() << std::endl;
+          }
+
+          throw std::runtime_error("Downloading Add-on failed: MD5 checksums differ");
+        }
+        else
+        {
+          const char* realdir = PHYSFS_getRealDir(m_install_request->install_filename.c_str());
+          if (!realdir)
+          {
+            throw std::runtime_error("PHYSFS_getRealDir failed: " + m_install_request->install_filename);
+          }
+          else
+          {
+            add_installed_archive(m_install_request->install_filename, md5.hex_digest());
+          }
+        }
+
+        // signal that the request is done and cleanup
+        if (m_install_status->callback)
+        {
+          m_install_status->callback();
+        }
+
+        m_install_request = {};
+        m_install_status = {};
+        m_transfer_status = {};
+      });
+
+    m_install_status = std::make_shared<InstallStatus>();
+
+    return m_install_status;
+  }
 }
 
 void
@@ -206,7 +326,7 @@ AddonManager::install_addon(const AddonId& addon_id)
 
   std::string install_filename = FileSystem::join(m_addon_directory, repository_addon.get_filename());
 
-  m_downloader.download(repository_addon.get_http_url(), install_filename);
+  m_downloader.download(repository_addon.get_url(), install_filename);
 
   MD5 md5 = md5_from_file(install_filename);
   if (repository_addon.get_md5() != md5.hex_digest())
@@ -227,7 +347,7 @@ AddonManager::install_addon(const AddonId& addon_id)
     }
     else
     {
-      add_installed_archive(install_filename);
+      add_installed_archive(install_filename, md5.hex_digest());
     }
   }
 }
@@ -332,7 +452,6 @@ AddonManager::scan_for_info(const std::string& archive_os_path) const
         PHYSFS_freeList);
   for(char** j = rc2.get(); *j != 0; ++j)
   {
-    log_debug << "enumerating: " << std::string(*j) << std::endl;
     if (has_suffix(*j, ".nfo"))
     {
       std::string nfo_filename = FileSystem::join("/", *j);
@@ -345,7 +464,6 @@ AddonManager::scan_for_info(const std::string& archive_os_path) const
       }
       else
       {
-        log_debug << "compare: " << realdir << " " << archive_os_path << std::endl;
         if (realdir == archive_os_path)
         {
           return nfo_filename;
@@ -358,7 +476,7 @@ AddonManager::scan_for_info(const std::string& archive_os_path) const
 }
 
 void
-AddonManager::add_installed_archive(const std::string& archive)
+AddonManager::add_installed_archive(const std::string& archive, const std::string& md5)
 {
   const char* realdir = PHYSFS_getRealDir(archive.c_str());
   if (!realdir)
@@ -383,7 +501,7 @@ AddonManager::add_installed_archive(const std::string& archive)
       try
       {
         std::unique_ptr<Addon> addon = Addon::parse(nfo_filename);
-        addon->set_install_filename(os_path);
+        addon->set_install_filename(os_path, md5);
         m_installed_addons.push_back(std::move(addon));
       }
       catch (const std::runtime_error& e)
@@ -403,7 +521,8 @@ AddonManager::add_installed_addons()
 
   for(auto archive : archives)
   {
-    add_installed_archive(archive);
+    MD5 md5 = md5_from_file(archive);
+    add_installed_archive(archive, md5.hex_digest());
   }
 }
 
@@ -452,4 +571,28 @@ AddonManager::parse_addon_infos(const std::string& addoninfos) const
   return m_addons;
 }
 
+void
+AddonManager::update()
+{
+  m_downloader.update();
+
+  if (m_install_status)
+  {
+    m_install_status->now = m_transfer_status->dlnow;
+    m_install_status->total = m_transfer_status->dltotal;
+  }
+}
+
+void
+AddonManager::abort_install()
+{
+  log_info << "addon install aborted" << std::endl;
+
+  m_downloader.abort(m_transfer_status->id);
+
+  m_install_request = {};
+  m_install_status = {};
+  m_transfer_status = {};
+}
+
 /* EOF */