From 0dee41ef4b863479e1dd92891ace3d048d987de7 Mon Sep 17 00:00:00 2001 From: Ingo Ruhnke Date: Tue, 19 Aug 2014 22:45:24 +0200 Subject: [PATCH] Some more AddonManager refactoring --- src/addon/addon.hpp | 29 ++-- src/addon/addon_list.cpp | 96 +++++++++++++ src/addon/addon_list.hpp | 43 ++++++ src/addon/addon_manager.cpp | 337 ++++++++++++++++---------------------------- src/addon/addon_manager.hpp | 26 ++-- src/addon/downloader.cpp | 102 ++++++++++++++ src/addon/downloader.hpp | 46 ++++++ src/supertux/main.cpp | 2 +- 8 files changed, 444 insertions(+), 237 deletions(-) create mode 100644 src/addon/addon_list.cpp create mode 100644 src/addon/addon_list.hpp create mode 100644 src/addon/downloader.cpp create mode 100644 src/addon/downloader.hpp diff --git a/src/addon/addon.hpp b/src/addon/addon.hpp index e78990a0e..58314d0ca 100644 --- a/src/addon/addon.hpp +++ b/src/addon/addon.hpp @@ -21,20 +21,33 @@ #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 index 000000000..442b41a84 --- /dev/null +++ b/src/addon/addon_list.cpp @@ -0,0 +1,96 @@ +// SuperTux +// Copyright (C) 2014 Ingo Ruhnke +// +// 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 . + +#include "addon/addon_list.hpp" + +#include +#include +#include + +#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 > +AddonList::parse(const std::string& addoninfos) +{ + std::vector > 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(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 index 000000000..723382b5e --- /dev/null +++ b/src/addon/addon_list.hpp @@ -0,0 +1,43 @@ +// SuperTux +// Copyright (C) 2014 Ingo Ruhnke +// +// 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 . + +#ifndef HEADER_SUPERTUX_ADDON_ADDON_LIST_HPP +#define HEADER_SUPERTUX_ADDON_ADDON_LIST_HPP + +#include +#include +#include + +#include "addon/addon.hpp" + +class AddonList +{ +private: +public: + static std::vector > parse(const std::string& str); + +public: + AddonList(); + + +private: + AddonList(const AddonList&) = delete; + AddonList& operator=(const AddonList&) = delete; +}; + +#endif + +/* EOF */ diff --git a/src/addon/addon_manager.cpp b/src/addon/addon_manager.cpp index e752ff20e..98fc81fae 100644 --- a/src/addon/addon_manager.cpp +++ b/src/addon/addon_manager.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -32,49 +33,32 @@ #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(string_ptr); - std::string buf(static_cast(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(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& ignored_addon_filenames) : +AddonManager::AddonManager(const std::string& addon_directory, + std::vector& 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::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(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::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(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); } diff --git a/src/addon/addon_manager.hpp b/src/addon/addon_manager.hpp index 50a1fffc7..57ae3de9e 100644 --- a/src/addon/addon_manager.hpp +++ b/src/addon/addon_manager.hpp @@ -21,6 +21,7 @@ #include #include +#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 { public: - AddonManager(std::vector& ignored_addon_filenames_); + AddonManager(const std::string& addon_directory, + std::vector& 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(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(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 > m_addons; std::vector& m_ignored_addon_filenames; diff --git a/src/addon/downloader.cpp b/src/addon/downloader.cpp new file mode 100644 index 000000000..3ca538875 --- /dev/null +++ b/src/addon/downloader.cpp @@ -0,0 +1,102 @@ +// SuperTux +// Copyright (C) 2014 Ingo Ruhnke +// +// 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 . + +#include "addon/downloader.hpp" + +#include +#include +#include +#include +#include + +#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(userdata); + std::string buf(static_cast(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(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 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 index 000000000..79c1056c6 --- /dev/null +++ b/src/addon/downloader.hpp @@ -0,0 +1,46 @@ +// SuperTux +// Copyright (C) 2014 Ingo Ruhnke +// +// 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 . + +#ifndef HEADER_SUPERTUX_ADDON_DOWNLOADER_HPP +#define HEADER_SUPERTUX_ADDON_DOWNLOADER_HPP + +#include + +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 */ diff --git a/src/supertux/main.cpp b/src/supertux/main.cpp index 5d3747d1b..ce0532015 100644 --- a/src/supertux/main.cpp +++ b/src/supertux/main.cpp @@ -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); -- 2.11.0