Some more AddonManager refactoring
authorIngo Ruhnke <grumbel@gmail.com>
Tue, 19 Aug 2014 20:45:24 +0000 (22:45 +0200)
committerIngo Ruhnke <grumbel@gmail.com>
Mon, 25 Aug 2014 07:52:11 +0000 (09:52 +0200)
src/addon/addon.hpp
src/addon/addon_list.cpp [new file with mode: 0644]
src/addon/addon_list.hpp [new file with mode: 0644]
src/addon/addon_manager.cpp
src/addon/addon_manager.hpp
src/addon/downloader.cpp [new file with mode: 0644]
src/addon/downloader.hpp [new file with mode: 0644]
src/supertux/main.cpp

index e78990a..58314d0 100644 (file)
 
 #include "util/reader_fwd.hpp"
 
-/** Represents an (available or installed) Add-on, e.g. a level set */
-class Addon
+class AddonDescription
 {
 public:
-  int id;
   std::string kind;
   std::string title;
   std::string author;
   std::string license;
   std::string http_url;
-
   /** filename suggested by addon author, e.g. "pak0.zip" */
   std::string suggested_filename;
 
+  AddonDescription() :
+    kind(),
+    title(),
+    author(),
+    license(),
+    http_url(),
+    suggested_filename()
+  {}
+};
+
+/** Represents an (available or installed) Add-on, e.g. a level set */
+class Addon : public AddonDescription
+{
+public:
+  int id;
+
   /** PhysFS filename on disk, e.g. "pak0.zip" */
   std::string installed_physfs_filename;
 
@@ -58,19 +71,13 @@ public:
       MD5 sum, else relies on kind, author and title alone. */
   bool operator==(const Addon& addon2) const;
 
-protected:
+public:
   friend class AddonManager;
 
   mutable std::string calculated_md5;
 
   Addon(int id_) :
     id(id_),
-    kind(),
-    title(),
-    author(),
-    license(),
-    http_url(),
-    suggested_filename(),
     installed_physfs_filename(),
     installed_absolute_filename(),
     stored_md5(),
diff --git a/src/addon/addon_list.cpp b/src/addon/addon_list.cpp
new file mode 100644 (file)
index 0000000..442b41a
--- /dev/null
@@ -0,0 +1,96 @@
+//  SuperTux
+//  Copyright (C) 2014 Ingo Ruhnke <grumbel@gmail.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "addon/addon_list.hpp"
+
+#include <memory>
+#include <sstream>
+#include <stdexcept>
+
+#include "addon/addon.hpp"
+#include "lisp/lisp.hpp"
+#include "lisp/list_iterator.hpp"
+#include "lisp/parser.hpp"
+#include "util/log.hpp"
+
+static const char* allowed_characters = "-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
+
+std::vector<std::unique_ptr<Addon> >
+AddonList::parse(const std::string& addoninfos)
+{
+  std::vector<std::unique_ptr<Addon> > m_addons;
+
+  try
+  {
+    lisp::Parser parser;
+    std::stringstream addoninfos_stream(addoninfos);
+    const lisp::Lisp* root = parser.parse(addoninfos_stream, "supertux-addons");
+
+    const lisp::Lisp* addons_lisp = root->get_lisp("supertux-addons");
+    if(!addons_lisp)
+    {
+      throw std::runtime_error("Downloaded file is not an Add-on list");
+    }
+
+    lisp::ListIterator iter(addons_lisp);
+    while(iter.next())
+    {
+      const std::string& token = iter.item();
+      if(token != "supertux-addoninfo")
+      {
+        log_warning << "Unknown token '" << token << "' in Add-on list" << std::endl;
+        continue;
+      }
+      std::unique_ptr<Addon> addon(new Addon(m_addons.size()));
+      addon->parse(*(iter.lisp()));
+      addon->installed = false;
+      addon->loaded = false;
+
+      // make sure the list of known Add-ons does not already contain this one
+      bool exists = false;
+      for (auto i = m_addons.begin(); i != m_addons.end(); ++i) {
+        if (**i == *addon) {
+          exists = true;
+          break;
+        }
+      }
+
+      if (exists)
+      {
+        // do nothing
+      }
+      else if (addon->suggested_filename.find_first_not_of(allowed_characters) != std::string::npos)
+      {
+        // make sure the Add-on's file name does not contain weird characters
+        log_warning << "Add-on \"" << addon->title << "\" contains unsafe file name. Skipping." << std::endl;
+      }
+      else
+      {
+        m_addons.push_back(std::move(addon));
+      }
+    }
+
+    return m_addons;
+  }
+  catch(std::exception& e)
+  {
+    std::stringstream msg;
+    msg << "Problem when reading Add-on list: " << e.what();
+    throw std::runtime_error(msg.str());
+  }
+}
+
+/* EOF */
diff --git a/src/addon/addon_list.hpp b/src/addon/addon_list.hpp
new file mode 100644 (file)
index 0000000..723382b
--- /dev/null
@@ -0,0 +1,43 @@
+//  SuperTux
+//  Copyright (C) 2014 Ingo Ruhnke <grumbel@gmail.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#ifndef HEADER_SUPERTUX_ADDON_ADDON_LIST_HPP
+#define HEADER_SUPERTUX_ADDON_ADDON_LIST_HPP
+
+#include <string>
+#include <vector>
+#include <memory>
+
+#include "addon/addon.hpp"
+
+class AddonList
+{
+private:
+public:
+  static std::vector<std::unique_ptr<Addon> > parse(const std::string& str);
+
+public:
+  AddonList();
+
+
+private:
+  AddonList(const AddonList&) = delete;
+  AddonList& operator=(const AddonList&) = delete;
+};
+
+#endif
+
+/* EOF */
index e752ff2..98fc81f 100644 (file)
@@ -18,6 +18,7 @@
 
 #include <config.h>
 #include <version.h>
+#include <iostream>
 
 #include <algorithm>
 #include <memory>
 #endif
 
 #include "addon/addon.hpp"
+#include "addon/addon_list.hpp"
 #include "lisp/list_iterator.hpp"
 #include "lisp/parser.hpp"
+#include "util/file_system.hpp"
+#include "util/log.hpp"
 #include "util/reader.hpp"
 #include "util/writer.hpp"
-#include "util/log.hpp"
 
-#ifdef HAVE_LIBCURL
 namespace {
 
-size_t my_curl_string_append(void *ptr, size_t size, size_t nmemb, void *string_ptr)
-{
-  std::string& s = *static_cast<std::string*>(string_ptr);
-  std::string buf(static_cast<char*>(ptr), size * nmemb);
-  s += buf;
-  log_debug << "read " << size * nmemb << " bytes of data..." << std::endl;
-  return size * nmemb;
-}
+const char* allowed_characters = "-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
 
-size_t my_curl_physfs_write(void *ptr, size_t size, size_t nmemb, void *f_p)
-{
-  PHYSFS_file* f = static_cast<PHYSFS_file*>(f_p);
-  PHYSFS_sint64 written = PHYSFS_write(f, ptr, size, nmemb);
-  log_debug << "read " << size * nmemb << " bytes of data..." << std::endl;
-  return size * written;
-}
+} // namespace
 
-}
-#endif
 
-AddonManager::AddonManager(std::vector<std::string>& ignored_addon_filenames) :
+AddonManager::AddonManager(const std::string& addon_directory,
+                           std::vector<std::string>& ignored_addon_filenames) :
+  m_downloader(),
+  m_addon_directory(addon_directory),
   m_addons(),
   m_ignored_addon_filenames(ignored_addon_filenames)
 {
-#ifdef HAVE_LIBCURL
-  curl_global_init(CURL_GLOBAL_ALL);
-#endif
 }
 
 AddonManager::~AddonManager()
 {
-#ifdef HAVE_LIBCURL
-  curl_global_cleanup();
-#endif
 }
 
 Addon&
@@ -96,7 +80,7 @@ AddonManager::get_addons() const
   /*
     for (std::vector<Addon>::iterator it = installed_addons.begin(); it != installed_addons.end(); ++it) {
     Addon& addon = *it;
-    if (addon.md5 == "") addon.md5 = calculate_md5(addon);
+    if (addon.md5.empty()) addon.md5 = calculate_md5(addon);
     }
   */
   return m_addons;
@@ -115,149 +99,51 @@ AddonManager::has_online_support() const
 void
 AddonManager::check_online()
 {
-#ifdef HAVE_LIBCURL
-  char error_buffer[CURL_ERROR_SIZE+1];
-
   const char* baseUrl = "http://addons.supertux.googlecode.com/git/index-0_3_5.nfo";
-  std::string addoninfos = "";
-
-  CURL *curl_handle;
-  curl_handle = curl_easy_init();
-  curl_easy_setopt(curl_handle, CURLOPT_URL, baseUrl);
-  curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL");
-  curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, my_curl_string_append);
-  curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &addoninfos);
-  curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, error_buffer);
-  curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1);
-  curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
-  curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1);
-  curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1);
-  CURLcode result = curl_easy_perform(curl_handle);
-  curl_easy_cleanup(curl_handle);
-
-  if (result != CURLE_OK) {
-    std::string why = error_buffer[0] ? error_buffer : "unhandled error";
-    throw std::runtime_error("Downloading Add-on list failed: " + why);
-  }
-
-  try {
-    lisp::Parser parser;
-    std::stringstream addoninfos_stream(addoninfos);
-    const lisp::Lisp* root = parser.parse(addoninfos_stream, "supertux-addons");
-
-    const lisp::Lisp* addons_lisp = root->get_lisp("supertux-addons");
-    if(!addons_lisp) throw std::runtime_error("Downloaded file is not an Add-on list");
-
-    lisp::ListIterator iter(addons_lisp);
-    while(iter.next())
-    {
-      const std::string& token = iter.item();
-      if(token != "supertux-addoninfo")
-      {
-        log_warning << "Unknown token '" << token << "' in Add-on list" << std::endl;
-        continue;
-      }
-      std::unique_ptr<Addon> addon(new Addon(m_addons.size()));
-      addon->parse(*(iter.lisp()));
-      addon->installed = false;
-      addon->loaded = false;
-
-      // make sure the list of known Add-ons does not already contain this one
-      bool exists = false;
-      for (auto i = m_addons.begin(); i != m_addons.end(); ++i) {
-        if (**i == *addon) {
-          exists = true;
-          break;
-        }
-      }
+  std::string addoninfos = m_downloader.download(baseUrl);
 
-      if (exists)
-      {
-        // do nothing
-      }
-      else if (addon->suggested_filename.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos)
-      {
-        // make sure the Add-on's file name does not contain weird characters
-        log_warning << "Add-on \"" << addon->title << "\" contains unsafe file name. Skipping." << std::endl;
-      }
-      else
-      {
-        m_addons.push_back(std::move(addon));
-      }
-    }
-  } catch(std::exception& e) {
-    std::stringstream msg;
-    msg << "Problem when reading Add-on list: " << e.what();
-    throw std::runtime_error(msg.str());
-  }
-
-#endif
+  AddonList::parse(addoninfos);
 }
 
 void
 AddonManager::install(Addon& addon)
 {
-#ifdef HAVE_LIBCURL
-
-  if (addon.installed) throw std::runtime_error("Tried installing installed Add-on");
+  if (addon.installed)
+  {
+    throw std::runtime_error("Tried installing installed Add-on");
+  }
 
   // make sure the Add-on's file name does not contain weird characters
-  if (addon.suggested_filename.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) {
+  if (addon.suggested_filename.find_first_not_of(allowed_characters) != std::string::npos)
+  {
     throw std::runtime_error("Add-on has unsafe file name (\""+addon.suggested_filename+"\")");
   }
 
-  std::string fileName = addon.suggested_filename;
+  std::string filename = FileSystem::join(m_addon_directory, addon.suggested_filename);
 
   // make sure its file doesn't already exist
-  if (PHYSFS_exists(fileName.c_str())) {
-    fileName = addon.stored_md5 + "_" + addon.suggested_filename;
-    if (PHYSFS_exists(fileName.c_str())) {
-      throw std::runtime_error("Add-on of suggested filename already exists (\""+addon.suggested_filename+"\", \""+fileName+"\")");
+  if (PHYSFS_exists(filename.c_str()))
+  {
+    filename = FileSystem::join(m_addon_directory, addon.stored_md5 + "_" + addon.suggested_filename);
+    if (PHYSFS_exists(filename.c_str()))
+    {
+      throw std::runtime_error("Add-on of suggested filename already exists (\"" +
+                               addon.suggested_filename + "\", \"" + filename + "\")");
     }
   }
 
-  char error_buffer[CURL_ERROR_SIZE+1];
-
-  char* url = (char*)malloc(addon.http_url.length() + 1);
-  strncpy(url, addon.http_url.c_str(), addon.http_url.length() + 1);
-
-  PHYSFS_file* f = PHYSFS_openWrite(fileName.c_str());
-
-  log_debug << "Downloading \"" << url << "\"" << std::endl;
-
-  CURL *curl_handle;
-  curl_handle = curl_easy_init();
-  curl_easy_setopt(curl_handle, CURLOPT_URL, url);
-  curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL");
-  curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, my_curl_physfs_write);
-  curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, f);
-  curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, error_buffer);
-  curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1);
-  curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
-  curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1);
-  CURLcode result = curl_easy_perform(curl_handle);
-  curl_easy_cleanup(curl_handle);
-
-  PHYSFS_close(f);
-
-  free(url);
-
-  if (result != CURLE_OK) {
-    PHYSFS_delete(fileName.c_str());
-    std::string why = error_buffer[0] ? error_buffer : "unhandled error";
-    throw std::runtime_error("Downloading Add-on failed: " + why);
-  }
+  m_downloader.download(addon.http_url, filename);
 
   addon.installed = true;
-  addon.installed_physfs_filename = fileName;
-  static const std::string writeDir = PHYSFS_getWriteDir();
-  static const std::string dirSep = PHYSFS_getDirSeparator();
-  addon.installed_absolute_filename = writeDir + dirSep + fileName;
+  addon.installed_physfs_filename = filename;
+  std::string writeDir = PHYSFS_getWriteDir();
+  addon.installed_absolute_filename = FileSystem::join(writeDir, filename);
   addon.loaded = false;
 
-  if (addon.get_md5() != addon.stored_md5) {
+  if (addon.get_md5() != addon.stored_md5)
+  {
     addon.installed = false;
-    PHYSFS_delete(fileName.c_str());
+    PHYSFS_delete(filename.c_str());
     std::string why = "MD5 checksums differ";
     throw std::runtime_error("Downloading Add-on failed: " + why);
   }
@@ -265,32 +151,30 @@ AddonManager::install(Addon& addon)
   log_debug << "Finished downloading \"" << addon.installed_absolute_filename << "\". Enabling Add-on." << std::endl;
 
   enable(addon);
-
-#else
-  (void) addon;
-#endif
-
 }
 
 void
 AddonManager::remove(Addon& addon)
 {
-  if (!addon.installed) throw std::runtime_error("Tried removing non-installed Add-on");
-
-  //FIXME: more checks
-
-  // make sure the Add-on's file name does not contain weird characters
-  if (addon.installed_physfs_filename.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) {
+  if (!addon.installed)
+  {
+    throw std::runtime_error("Tried removing non-installed Add-on");
+  }
+  else if (addon.installed_physfs_filename.find_first_not_of(allowed_characters) != std::string::npos)
+  {
+    // make sure the Add-on's file name does not contain weird characters
     throw std::runtime_error("Add-on has unsafe file name (\""+addon.installed_physfs_filename+"\")");
   }
+  else
+  {
+    unload(addon);
 
-  unload(addon);
-
-  log_debug << "deleting file \"" << addon.installed_absolute_filename << "\"" << std::endl;
-  PHYSFS_delete(addon.installed_absolute_filename.c_str());
-  addon.installed = false;
+    log_debug << "deleting file \"" << addon.installed_absolute_filename << "\"" << std::endl;
+    PHYSFS_delete(addon.installed_absolute_filename.c_str());
+    addon.installed = false;
 
-  // FIXME: As we don't know anything more about it (e.g. where to get it), remove it from list of known Add-ons
+    // FIXME: As we don't know anything more about it (e.g. where to get it), remove it from list of known Add-ons
+  }
 }
 
 void
@@ -298,9 +182,11 @@ AddonManager::disable(Addon& addon)
 {
   unload(addon);
 
-  std::string fileName = addon.installed_physfs_filename;
-  if (std::find(m_ignored_addon_filenames.begin(), m_ignored_addon_filenames.end(), fileName) == m_ignored_addon_filenames.end()) {
-    m_ignored_addon_filenames.push_back(fileName);
+  std::string filename = addon.installed_physfs_filename;
+  if (std::find(m_ignored_addon_filenames.begin(), m_ignored_addon_filenames.end(),
+                filename) == m_ignored_addon_filenames.end())
+  {
+    m_ignored_addon_filenames.push_back(filename);
   }
 }
 
@@ -309,77 +195,101 @@ AddonManager::enable(Addon& addon)
 {
   load(addon);
 
-  std::string fileName = addon.installed_physfs_filename;
-  std::vector<std::string>::iterator i = std::find(m_ignored_addon_filenames.begin(), m_ignored_addon_filenames.end(), fileName);
-  if (i != m_ignored_addon_filenames.end()) {
-    m_ignored_addon_filenames.erase(i);
+  std::string filename = addon.installed_physfs_filename;
+  auto it = std::find(m_ignored_addon_filenames.begin(), m_ignored_addon_filenames.end(), filename);
+  if (it != m_ignored_addon_filenames.end())
+  {
+    m_ignored_addon_filenames.erase(it);
   }
 }
 
 void
 AddonManager::unload(Addon& addon)
 {
-  if (!addon.installed) throw std::runtime_error("Tried unloading non-installed Add-on");
-  if (!addon.loaded) return;
-
-  log_debug << "Removing archive \"" << addon.installed_absolute_filename << "\" from search path" << std::endl;
-  if (PHYSFS_removeFromSearchPath(addon.installed_absolute_filename.c_str()) == 0) {
-    log_warning << "Could not remove " << addon.installed_absolute_filename << " from search path. Ignoring." << std::endl;
-    return;
+  if (!addon.installed)
+  {
+    throw std::runtime_error("Tried unloading non-installed Add-on");
+  }
+  else if (!addon.loaded)
+  {
+    // do nothing
   }
+  else
+  {
+    log_debug << "Removing archive \"" << addon.installed_absolute_filename << "\" from search path" << std::endl;
+    if (PHYSFS_removeFromSearchPath(addon.installed_absolute_filename.c_str()) == 0) {
+      log_warning << "Could not remove " << addon.installed_absolute_filename << " from search path. Ignoring." << std::endl;
+      return;
+    }
 
-  addon.loaded = false;
+    addon.loaded = false;
+  }
 }
 
 void
 AddonManager::load(Addon& addon)
 {
-  if (!addon.installed) throw std::runtime_error("Tried loading non-installed Add-on");
-  if (addon.loaded) return;
-
-  log_debug << "Adding archive \"" << addon.installed_absolute_filename << "\" to search path" << std::endl;
-  if (PHYSFS_addToSearchPath(addon.installed_absolute_filename.c_str(), 0) == 0) {
-    log_warning << "Could not add " << addon.installed_absolute_filename << " to search path. Ignoring." << std::endl;
-    return;
+  if (!addon.installed)
+  {
+    throw std::runtime_error("Tried loading non-installed Add-on");
+  }
+  else if (addon.loaded)
+  {
+    // do nothing
   }
+  else
+  {
+    log_debug << "Adding archive \"" << addon.installed_absolute_filename << "\" to search path" << std::endl;
+    if (PHYSFS_addToSearchPath(addon.installed_absolute_filename.c_str(), 0) == 0) {
+      log_warning << "Could not add " << addon.installed_absolute_filename << " to search path. Ignoring." << std::endl;
+      return;
+    }
 
-  addon.loaded = true;
+    addon.loaded = true;
+  }
 }
 
 void
 AddonManager::load_addons()
 {
+  PHYSFS_mkdir(m_addon_directory.c_str());
+
   // unload all Addons and forget about them
-  for (auto i = m_addons.begin(); i != m_addons.end(); ++i)
+  for (auto& addon : m_addons)
   {
-    if ((*i)->installed && (*i)->loaded)
+    if (addon->installed && addon->loaded)
     {
-      unload(**i);
+      unload(*addon);
     }
   }
   m_addons.clear();
 
   // Search for archives and add them to the search path
-  char** rc = PHYSFS_enumerateFiles("/");
-
-  for(char** i = rc; *i != 0; ++i) {
+  char** rc = PHYSFS_enumerateFiles(m_addon_directory.c_str());
 
+  for(char** i = rc; *i != 0; ++i)
+  {
     // get filename of potential archive
-    std::string fileName = *i;
+    std::string filename = *i;
+
+    std::cout << m_addon_directory << " -> " << filename << std::endl;
 
-    const std::string archiveDir = PHYSFS_getRealDir(fileName.c_str());
-    static const std::string dirSep = PHYSFS_getDirSeparator();
-    std::string fullFilename = archiveDir + dirSep + fileName;
+    const std::string archiveDir = PHYSFS_getRealDir(filename.c_str());
+    std::string fullFilename = FileSystem::join(archiveDir, filename);
 
     /*
     // make sure it's in the writeDir
-    static const std::string writeDir = PHYSFS_getWriteDir();
-    if (fileName.compare(0, writeDir.length(), writeDir) != 0) continue;
+    std::string writeDir = PHYSFS_getWriteDir();
+    if (filename.compare(0, writeDir.length(), writeDir) != 0) continue;
     */
 
     // make sure it looks like an archive
-    static const std::string archiveExt = ".zip";
-    if (fullFilename.compare(fullFilename.length()-archiveExt.length(), archiveExt.length(), archiveExt) != 0) continue;
+    std::string archiveExt = ".zip";
+    if (fullFilename.compare(fullFilename.length() - archiveExt.length(),
+                             archiveExt.length(), archiveExt) != 0)
+    {
+      continue;
+    }
 
     // make sure it exists
     struct stat stats;
@@ -395,9 +305,9 @@ AddonManager::load_addons()
 
     // Search for infoFiles
     std::string infoFileName = "";
-    char** rc2 = PHYSFS_enumerateFiles("/");
-    for(char** j = rc2; *j != 0; ++j) {
-
+    char** rc2 = PHYSFS_enumerateFiles(m_addon_directory.c_str());
+    for(char** j = rc2; *j != 0; ++j)
+    {
       // get filename of potential infoFile
       std::string potentialInfoFileName = *j;
 
@@ -411,28 +321,29 @@ AddonManager::load_addons()
 
       // make sure it's in the current archive
       std::string infoFileDir = PHYSFS_getRealDir(potentialInfoFileName.c_str());
-      if (infoFileDir != fullFilename) continue;
-
-      // found infoFileName
-      infoFileName = potentialInfoFileName;
-      break;
+      if (infoFileDir == fullFilename)
+      {
+        // found infoFileName
+        infoFileName = potentialInfoFileName;
+        break;
+      }
     }
     PHYSFS_freeList(rc2);
 
     // if we have an infoFile, it's an Addon
-    if (infoFileName != "")
+    if (!infoFileName.empty())
     {
       try
       {
         std::unique_ptr<Addon> addon(new Addon(m_addons.size()));
         addon->parse(infoFileName);
         addon->installed = true;
-        addon->installed_physfs_filename = fileName;
+        addon->installed_physfs_filename = filename;
         addon->installed_absolute_filename = fullFilename;
         addon->loaded = true;
 
         // check if the Addon is disabled
-        if (std::find(m_ignored_addon_filenames.begin(), m_ignored_addon_filenames.end(), fileName) != m_ignored_addon_filenames.end())
+        if (std::find(m_ignored_addon_filenames.begin(), m_ignored_addon_filenames.end(), filename) != m_ignored_addon_filenames.end())
         {
           unload(*addon);
         }
index 50a1fff..57ae3de 100644 (file)
@@ -21,6 +21,7 @@
 #include <string>
 #include <vector>
 
+#include "addon/downloader.hpp"
 #include "util/currenton.hpp"
 #include "util/reader_fwd.hpp"
 #include "util/writer_fwd.hpp"
@@ -33,7 +34,8 @@ typedef int AddonId;
 class AddonManager : public Currenton<AddonManager>
 {
 public:
-  AddonManager(std::vector<std::string>& ignored_addon_filenames_);
+  AddonManager(const std::string& addon_directory,
+               std::vector<std::string>& ignored_addon_filenames_);
   ~AddonManager();
 
   /** returns a list of installed Add-ons */
@@ -47,29 +49,29 @@ public:
 
   /** Download and install Add-on */
   void install(Addon& addon);
-
   /** Physically delete Add-on */
   void remove(Addon& addon);
 
-  /** Unload Add-on and mark as not to be loaded automatically */
-  void disable(Addon& addon);
-
   /** Load Add-on and mark as to be loaded automatically */
   void enable(Addon& addon);
+  /** Unload Add-on and mark as not to be loaded automatically */
+  void disable(Addon& addon);
 
-  /** Remove Add-on from search path */
-  void unload(Addon& addon);
-
-  /** Add Add-on to search path */
-  void load(Addon& addon);
+  Addon& get_addon(int id);
+  int get_num_addons() const { return static_cast<int>(m_addons.size()); }
 
   /** Loads all enabled Add-ons, i.e. adds them to the search path */
   void load_addons();
 
-  Addon& get_addon(int id);
-  int get_num_addons() const { return static_cast<int>(m_addons.size()); }
+private:
+  /** Add Add-on to search path */
+  void load(Addon& addon);
+  /** Remove Add-on from search path */
+  void unload(Addon& addon);
 
 private:
+  Downloader m_downloader;
+  std::string m_addon_directory;
   std::vector<std::unique_ptr<Addon> > m_addons;
   std::vector<std::string>& m_ignored_addon_filenames;
 
diff --git a/src/addon/downloader.cpp b/src/addon/downloader.cpp
new file mode 100644 (file)
index 0000000..3ca5388
--- /dev/null
@@ -0,0 +1,102 @@
+//  SuperTux
+//  Copyright (C) 2014 Ingo Ruhnke <grumbel@gmail.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "addon/downloader.hpp"
+
+#include <curl/curl.h>
+#include <curl/easy.h>
+#include <physfs.h>
+#include <memory>
+#include <stdexcept>
+
+#include "util/log.hpp"
+#include "version.h"
+
+namespace {
+
+size_t my_curl_string_append(void* ptr, size_t size, size_t nmemb, void* userdata)
+{
+  std::string& s = *static_cast<std::string*>(userdata);
+  std::string buf(static_cast<char*>(ptr), size * nmemb);
+  s += buf;
+  log_debug << "read " << size * nmemb << " bytes of data..." << std::endl;
+  return size * nmemb;
+}
+
+size_t my_curl_physfs_write(void* ptr, size_t size, size_t nmemb, void* userdata)
+{
+  PHYSFS_file* f = static_cast<PHYSFS_file*>(userdata);
+  PHYSFS_sint64 written = PHYSFS_write(f, ptr, size, nmemb);
+  log_debug << "read " << size * nmemb << " bytes of data..." << std::endl;
+  return size * written;
+}
+
+} // namespace
+
+Downloader::Downloader()
+{
+  curl_global_init(CURL_GLOBAL_ALL);
+}
+
+Downloader::~Downloader()
+{
+  curl_global_cleanup();
+}
+
+void
+Downloader::download(const std::string& url,
+                     size_t (*write_func)(void* ptr, size_t size, size_t nmemb, void* userdata),
+                     void* userdata)
+{
+  char error_buffer[CURL_ERROR_SIZE+1];
+
+  CURL* curl_handle = curl_easy_init();
+  curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
+  curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL");
+  curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_func);
+  curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, userdata);
+  curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, error_buffer);
+  curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1);
+  curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
+  curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1);
+  curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1);
+  CURLcode result = curl_easy_perform(curl_handle);
+  curl_easy_cleanup(curl_handle);
+
+  if (result != CURLE_OK)
+  {
+    std::string why = error_buffer[0] ? error_buffer : "unhandled error";
+    throw std::runtime_error(url + ": download failed: " + why);
+  }
+}
+
+std::string
+Downloader::download(const std::string& url)
+{
+  std::string result;
+  download(url, my_curl_string_append, &result);
+  return result;
+}
+
+void
+Downloader::download(const std::string& url, const std::string& filename)
+{
+  std::unique_ptr<PHYSFS_file, int(*)(PHYSFS_File*)> fout(PHYSFS_openWrite(filename.c_str()),
+                                                          PHYSFS_close);
+  download(url, my_curl_physfs_write, fout.get());
+}
+
+/* EOF */
diff --git a/src/addon/downloader.hpp b/src/addon/downloader.hpp
new file mode 100644 (file)
index 0000000..79c1056
--- /dev/null
@@ -0,0 +1,46 @@
+//  SuperTux
+//  Copyright (C) 2014 Ingo Ruhnke <grumbel@gmail.com>
+//
+//  This program is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#ifndef HEADER_SUPERTUX_ADDON_DOWNLOADER_HPP
+#define HEADER_SUPERTUX_ADDON_DOWNLOADER_HPP
+
+#include <string>
+
+class Downloader
+{
+private:
+public:
+  Downloader();
+  ~Downloader();
+
+  /** Download \a url and return the result as string */
+  std::string download(const std::string& url);
+
+  /** Download \a url and store the result in \a filename */
+  void download(const std::string& url, const std::string& filename);
+
+  void download(const std::string& url,
+                size_t (*write_func)(void* ptr, size_t size, size_t nmemb, void* userdata),
+                void* userdata);
+
+private:
+  Downloader(const Downloader&) = delete;
+  Downloader& operator=(const Downloader&) = delete;
+};
+
+#endif
+
+/* EOF */
index 5d3747d..ce05320 100644 (file)
@@ -326,7 +326,7 @@ Main::launch_game()
   Resources resources;
 
   timelog("addons");
-  AddonManager addon_manager(g_config->disabled_addon_filenames);
+  AddonManager addon_manager("addons", g_config->disabled_addon_filenames);
   addon_manager.load_addons();
 
   timelog(0);