X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=src%2Faddon%2Faddon_manager.cpp;h=7266c3e5eef751d52cf93e187f71235c0ab6721d;hb=9410759b4897ceeaaca7da00a060b5c85a6fe6dd;hp=695e181e77df5454c4dd881f49f0ff5504bdca8a;hpb=c686b6e6bc389edb08cef2215b0882b2b0ff4b4b;p=supertux.git diff --git a/src/addon/addon_manager.cpp b/src/addon/addon_manager.cpp index 695e181e7..7266c3e5e 100644 --- a/src/addon/addon_manager.cpp +++ b/src/addon/addon_manager.cpp @@ -1,12 +1,11 @@ -// $Id$ -// // SuperTux - Add-on Manager // Copyright (C) 2007 Christoph Sommer +// 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 2 -// of the License, or (at your option) any later version. +// 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 @@ -14,425 +13,589 @@ // 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, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA -// 02111-1307, USA. -// +// along with this program. If not, see . + +#include "addon/addon_manager.hpp" + +#include +#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include #include -#include "addon/addon_manager.hpp" -#include "config.h" -#include "log.hpp" -#include "lisp/parser.hpp" -#include "lisp/lisp.hpp" -#include "lisp/list_iterator.hpp" -#include "physfs/physfs_stream.hpp" +#include -#ifdef HAVE_LIBCURL -#include -#include -#include -#endif +#include "addon/addon.hpp" +#include "addon/md5.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" -#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; - } +MD5 md5_from_file(const std::string& filename) +{ + // TODO: this does not work as expected for some files -- IFileStream seems to not always behave like an ifstream. + //IFileStream ifs(installed_physfs_filename); + //std::string md5 = MD5(ifs).hex_digest(); - size_t my_curl_physfs_write(void *ptr, size_t size, size_t nmemb, void *f_p) + MD5 md5; + + PHYSFS_file* file = PHYSFS_openRead(filename.c_str()); + if (!file) { - 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; + std::ostringstream out; + out << "PHYSFS_openRead() failed: " << PHYSFS_getLastError(); + throw std::runtime_error(out.str()); } + else + { + while (true) + { + unsigned char buffer[1024]; + PHYSFS_sint64 len = PHYSFS_read(file, buffer, 1, sizeof(buffer)); + if (len <= 0) break; + md5.update(buffer, len); + } + PHYSFS_close(file); + return md5; + } } -#endif -AddonManager& -AddonManager::get_instance() +bool has_suffix(const std::string& str, const std::string& suffix) { - static AddonManager instance; - return instance; + if (str.length() >= suffix.length()) + return str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0; + else + return false; } -AddonManager::AddonManager() +} // namespace + +AddonManager::AddonManager(const std::string& addon_directory, + std::vector& addon_config) : + m_downloader(), + m_addon_directory(addon_directory), + m_repository_url("https://raw.githubusercontent.com/SuperTuxTeam/addons/master/index-0_4_0.nfo"), + m_addon_config(addon_config), + m_installed_addons(), + m_repository_addons(), + m_has_been_updated(false), + m_transfer_status() { -#ifdef HAVE_LIBCURL - curl_global_init(CURL_GLOBAL_ALL); -#endif -} + PHYSFS_mkdir(m_addon_directory.c_str()); -AddonManager::~AddonManager() -{ -#ifdef HAVE_LIBCURL - curl_global_cleanup(); -#endif + add_installed_addons(); - for (std::vector::iterator i = addons.begin(); i != addons.end(); i++) delete *i; -} + // FIXME: We should also restore the order here + for(auto& addon : m_addon_config) + { + if (addon.enabled) + { + try + { + enable_addon(addon.id); + } + catch(const std::exception& err) + { + log_warning << "failed to enable addon from config: " << err.what() << std::endl; + } + } + } -std::vector -AddonManager::get_addons() -{ -/* - for (std::vector::iterator it = installed_addons.begin(); it != installed_addons.end(); ++it) { - Addon& addon = *it; - if (addon.md5 == "") addon.md5 = calculate_md5(addon); + try + { + m_repository_addons = parse_addon_infos("/addons/repository.nfo"); + } + catch(const std::exception& err) + { + log_warning << "parsing repository.nfo failed: " << err.what() << std::endl; } -*/ - return addons; } -void -AddonManager::check_online() +AddonManager::~AddonManager() { -#ifdef HAVE_LIBCURL - char error_buffer[CURL_ERROR_SIZE+1]; - - const char* baseUrl = "http://supertux.berlios.de/addons/index.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); + // 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()}); } +} - 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; - } - Addon* addon_ptr = new Addon(); - Addon& addon = *addon_ptr; - 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 (std::vector::const_iterator i = addons.begin(); i != addons.end(); i++) { - if (**i == addon) { - exists = true; - break; - } - } - if (exists) { - delete addon_ptr; - continue; - } - - // 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) { - log_warning << "Add-on \"" << addon.title << "\" contains unsafe file name. Skipping." << std::endl; - delete addon_ptr; - continue; - } +Addon& +AddonManager::get_repository_addon(const AddonId& id) +{ + auto it = std::find_if(m_repository_addons.begin(), m_repository_addons.end(), + [&id](const std::unique_ptr& addon) + { + return addon->get_id() == id; + }); - addons.push_back(addon_ptr); - } - } catch(std::exception& e) { - std::stringstream msg; - msg << "Problem when reading Add-on list: " << e.what(); - throw std::runtime_error(msg.str()); + if (it != m_repository_addons.end()) + { + return **it; + } + else + { + throw std::runtime_error("Couldn't find repository Addon with id: " + id); } - -#endif } - -void -AddonManager::install(Addon* addon) +Addon& +AddonManager::get_installed_addon(const AddonId& id) { -#ifdef HAVE_LIBCURL + auto it = std::find_if(m_installed_addons.begin(), m_installed_addons.end(), + [&id](const std::unique_ptr& addon) + { + return addon->get_id() == id; + }); - 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) { - throw std::runtime_error("Add-on has unsafe file name (\""+addon->suggested_filename+"\")"); + if (it != m_installed_addons.end()) + { + return **it; } - - std::string fileName = 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+"\")"); - } + else + { + throw std::runtime_error("Couldn't find installed Addon with id: " + id); } +} - char error_buffer[CURL_ERROR_SIZE+1]; +std::vector +AddonManager::get_repository_addons() const +{ + std::vector results; + results.reserve(m_repository_addons.size()); + std::transform(m_repository_addons.begin(), m_repository_addons.end(), + std::back_inserter(results), + [](const std::unique_ptr& addon) + { + return addon->get_id(); + }); + return results; +} - 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()); +std::vector +AddonManager::get_installed_addons() const +{ + std::vector results; + results.reserve(m_installed_addons.size()); + std::transform(m_installed_addons.begin(), m_installed_addons.end(), + std::back_inserter(results), + [](const std::unique_ptr& addon) + { + return addon->get_id(); + }); + return results; +} - log_debug << "Downloading \"" << url << "\"" << std::endl; +bool +AddonManager::has_online_support() const +{ + return true; +} - 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); +bool +AddonManager::has_been_updated() const +{ + return m_has_been_updated; +} - PHYSFS_close(f); +TransferStatusPtr +AddonManager::request_check_online() +{ + if (m_transfer_status) + { + throw std::runtime_error("only async request can be made to AddonManager at a time"); + } + else + { + m_transfer_status = m_downloader.request_download(m_repository_url, "/addons/repository.nfo"); - free(url); + m_transfer_status->then( + [this](bool success) + { + m_transfer_status = {}; - 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); - } + if (success) + { + m_repository_addons = parse_addon_infos("/addons/repository.nfo"); + m_has_been_updated = true; + } + }); - 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->loaded = false; - - if (addon->get_md5() != addon->stored_md5) { - addon->installed = false; - PHYSFS_delete(fileName.c_str()); - std::string why = "MD5 checksums differ"; - throw std::runtime_error("Downloading Add-on failed: " + why); + return m_transfer_status; } +} - log_debug << "Finished downloading \"" << addon->installed_absolute_filename << "\". Enabling Add-on." << std::endl; +void +AddonManager::check_online() +{ + m_downloader.download(m_repository_url, "/addons/repository.nfo"); + m_repository_addons = parse_addon_infos("/addons/repository.nfo"); + m_has_been_updated = true; +} - enable(addon); +TransferStatusPtr +AddonManager::request_install_addon(const AddonId& addon_id) +{ + if (m_transfer_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) + { + 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; + } + } -#else - (void) addon; -#endif + Addon& addon = get_repository_addon(addon_id); + + std::string install_filename = FileSystem::join(m_addon_directory, addon.get_filename()); + + m_transfer_status = m_downloader.request_download(addon.get_url(), install_filename); + + m_transfer_status->then( + [this, install_filename, addon_id](bool success) + { + m_transfer_status = {}; + + if (success) + { + // complete the addon install + Addon& repository_addon = get_repository_addon(addon_id); + + MD5 md5 = md5_from_file(install_filename); + if (repository_addon.get_md5() != md5.hex_digest()) + { + if (PHYSFS_delete(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(install_filename.c_str()); + if (!realdir) + { + throw std::runtime_error("PHYSFS_getRealDir failed: " + install_filename); + } + else + { + add_installed_archive(install_filename, md5.hex_digest()); + } + } + } + }); + return m_transfer_status; + } } void -AddonManager::remove(Addon* addon) +AddonManager::install_addon(const AddonId& addon_id) { - if (!addon->installed) throw std::runtime_error("Tried removing non-installed Add-on"); + { // 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) + { + 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; + } + } - //FIXME: more checks + Addon& repository_addon = get_repository_addon(addon_id); - // 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) { - throw std::runtime_error("Add-on has unsafe file name (\""+addon->installed_physfs_filename+"\")"); - } + std::string install_filename = FileSystem::join(m_addon_directory, repository_addon.get_filename()); - unload(addon); + m_downloader.download(repository_addon.get_url(), install_filename); - log_debug << "deleting file \"" << addon->installed_absolute_filename << "\"" << std::endl; - PHYSFS_delete(addon->installed_absolute_filename.c_str()); - addon->installed = false; + MD5 md5 = md5_from_file(install_filename); + if (repository_addon.get_md5() != md5.hex_digest()) + { + if (PHYSFS_delete(install_filename.c_str()) == 0) + { + log_warning << "PHYSFS_delete failed: " << PHYSFS_getLastError() << std::endl; + } - // FIXME: As we don't know anything more about it (e.g. where to get it), remove it from list of known Add-ons + throw std::runtime_error("Downloading Add-on failed: MD5 checksums differ"); + } + else + { + const char* realdir = PHYSFS_getRealDir(install_filename.c_str()); + if (!realdir) + { + throw std::runtime_error("PHYSFS_getRealDir failed: " + install_filename); + } + else + { + add_installed_archive(install_filename, md5.hex_digest()); + } + } } void -AddonManager::disable(Addon* addon) +AddonManager::uninstall_addon(const AddonId& addon_id) { - unload(addon); - - std::string fileName = addon->installed_physfs_filename; - if (std::find(ignored_addon_filenames.begin(), ignored_addon_filenames.end(), fileName) == ignored_addon_filenames.end()) { - ignored_addon_filenames.push_back(fileName); + log_debug << "uninstalling addon " << addon_id << std::endl; + Addon& addon = get_installed_addon(addon_id); + if (addon.is_enabled()) + { + disable_addon(addon_id); } + log_debug << "deleting file \"" << addon.get_install_filename() << "\"" << std::endl; + PHYSFS_delete(addon.get_install_filename().c_str()); + m_installed_addons.erase(std::remove_if(m_installed_addons.begin(), m_installed_addons.end(), + [&addon](const std::unique_ptr& rhs) + { + return addon.get_id() == rhs->get_id(); + }), + m_installed_addons.end()); } void -AddonManager::enable(Addon* addon) +AddonManager::enable_addon(const AddonId& addon_id) { - load(addon); - - std::string fileName = addon->installed_physfs_filename; - std::vector::iterator i = std::find(ignored_addon_filenames.begin(), ignored_addon_filenames.end(), fileName); - if (i != ignored_addon_filenames.end()) { - ignored_addon_filenames.erase(i); + log_debug << "enabling addon " << addon_id << std::endl; + Addon& addon = get_installed_addon(addon_id); + if (addon.is_enabled()) + { + log_warning << "Tried enabling already enabled Add-on" << std::endl; + } + else + { + log_debug << "Adding archive \"" << addon.get_install_filename() << "\" to search path" << std::endl; + //int PHYSFS_mount(addon.installed_install_filename.c_str(), "addons/", 0) + if (PHYSFS_addToSearchPath(addon.get_install_filename().c_str(), 0) == 0) + { + log_warning << "Could not add " << addon.get_install_filename() << " to search path: " + << PHYSFS_getLastError() << std::endl; + } + else + { + addon.set_enabled(true); + } } } void -AddonManager::unload(Addon* addon) +AddonManager::disable_addon(const AddonId& addon_id) { - 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; + log_debug << "disabling addon " << addon_id << std::endl; + Addon& addon = get_installed_addon(addon_id); + if (!addon.is_enabled()) + { + log_warning << "Tried disabling already disabled Add-On" << std::endl; + } + else + { + log_debug << "Removing archive \"" << addon.get_install_filename() << "\" from search path" << std::endl; + if (PHYSFS_removeFromSearchPath(addon.get_install_filename().c_str()) == 0) + { + log_warning << "Could not remove " << addon.get_install_filename() << " from search path: " + << PHYSFS_getLastError() << std::endl; + } + else + { + addon.set_enabled(false); + } } - - addon->loaded = false; } -void -AddonManager::load(Addon* addon) +std::vector +AddonManager::scan_for_archives() const { - if (!addon->installed) throw std::runtime_error("Tried loading non-installed Add-on"); - if (addon->loaded) return; + std::vector archives; - 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; + // Search for archives and add them to the search path + std::unique_ptr + rc(PHYSFS_enumerateFiles(m_addon_directory.c_str()), + PHYSFS_freeList); + for(char** i = rc.get(); *i != 0; ++i) + { + if (has_suffix(*i, ".zip")) + { + std::string archive = FileSystem::join(m_addon_directory, *i); + if (PHYSFS_exists(archive.c_str())) + { + archives.push_back(archive); + } + } } - addon->loaded = true; + return archives; } -void -AddonManager::load_addons() +std::string +AddonManager::scan_for_info(const std::string& archive_os_path) const { - // unload all Addons and forget about them - for (std::vector::iterator i = addons.begin(); i != addons.end(); i++) { - if ((*i)->installed && (*i)->loaded) unload(*i); - delete *i; + std::unique_ptr + rc2(PHYSFS_enumerateFiles("/"), + PHYSFS_freeList); + for(char** j = rc2.get(); *j != 0; ++j) + { + if (has_suffix(*j, ".nfo")) + { + std::string nfo_filename = FileSystem::join("/", *j); + + // make sure it's in the current archive_os_path + const char* realdir = PHYSFS_getRealDir(nfo_filename.c_str()); + if (!realdir) + { + log_warning << "PHYSFS_getRealDir() failed for " << nfo_filename << ": " << PHYSFS_getLastError() << std::endl; + } + else + { + if (realdir == archive_os_path) + { + return nfo_filename; + } + } + } } - addons.clear(); - - // Search for archives and add them to the search path - char** rc = PHYSFS_enumerateFiles("/"); - - for(char** i = rc; *i != 0; ++i) { - - // get filename of potential archive - std::string fileName = *i; - static const std::string archiveDir = PHYSFS_getRealDir(fileName.c_str()); - static const std::string dirSep = PHYSFS_getDirSeparator(); - std::string fullFilename = archiveDir + dirSep + fileName; - - /* - // make sure it's in the writeDir - static const 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; + return std::string(); +} - // make sure it exists - struct stat stats; - if (stat(fullFilename.c_str(), &stats) != 0) continue; +void +AddonManager::add_installed_archive(const std::string& archive, const std::string& md5) +{ + const char* realdir = PHYSFS_getRealDir(archive.c_str()); + if (!realdir) + { + log_warning << "PHYSFS_getRealDir() failed for " << archive << ": " + << PHYSFS_getLastError() << std::endl; + } + else + { + std::string os_path = FileSystem::join(realdir, archive); - // make sure it's an actual file - if (!S_ISREG(stats.st_mode)) continue; + PHYSFS_addToSearchPath(os_path.c_str(), 0); - log_debug << "Found archive \"" << fullFilename << "\"" << std::endl; + std::string nfo_filename = scan_for_info(os_path); - // add archive to search path - PHYSFS_addToSearchPath(fullFilename.c_str(), 0); + if (nfo_filename.empty()) + { + log_warning << "Couldn't find .nfo file for " << os_path << std::endl; + } + else + { + try + { + std::unique_ptr addon = Addon::parse(nfo_filename); + addon->set_install_filename(os_path, md5); + m_installed_addons.push_back(std::move(addon)); + } + catch (const std::runtime_error& e) + { + log_warning << "Could not load add-on info for " << archive << ": " << e.what() << std::endl; + } + } - // Search for infoFiles - std::string infoFileName = ""; - char** rc2 = PHYSFS_enumerateFiles("/"); - for(char** i = rc2; *i != 0; ++i) { + PHYSFS_removeFromSearchPath(os_path.c_str()); + } +} - // get filename of potential infoFile - std::string potentialInfoFileName = *i; +void +AddonManager::add_installed_addons() +{ + auto archives = scan_for_archives(); - // make sure it looks like an infoFile - static const std::string infoExt = ".nfo"; - if (potentialInfoFileName.compare(potentialInfoFileName.length()-infoExt.length(), infoExt.length(), infoExt) != 0) continue; + for(auto archive : archives) + { + MD5 md5 = md5_from_file(archive); + add_installed_archive(archive, md5.hex_digest()); + } +} - // make sure it's in the current archive - std::string infoFileDir = PHYSFS_getRealDir(potentialInfoFileName.c_str()); - if (infoFileDir != fullFilename) continue; +AddonManager::AddonList +AddonManager::parse_addon_infos(const std::string& filename) const +{ + AddonList m_addons; - // found infoFileName - infoFileName = potentialInfoFileName; - break; + try + { + lisp::Parser parser; + const lisp::Lisp* root = parser.parse(filename); + 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"); } - PHYSFS_freeList(rc2); - - // if we have an infoFile, it's an Addon - if (infoFileName != "") { - try { - Addon* addon = new Addon(); - addon->parse(infoFileName); - addon->installed = true; - addon->installed_physfs_filename = fileName; - addon->installed_absolute_filename = fullFilename; - addon->loaded = true; - addons.push_back(addon); - - // check if the Addon is disabled - if (std::find(ignored_addon_filenames.begin(), ignored_addon_filenames.end(), fileName) != ignored_addon_filenames.end()) { - unload(addon); + else + { + 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; + } + else + { + std::unique_ptr addon = Addon::parse(*iter.lisp()); + m_addons.push_back(std::move(addon)); } - - } catch (const std::runtime_error& e) { - log_warning << "Could not load add-on info for " << fullFilename << ", loading as unmanaged:" << e.what() << std::endl; } - } + return m_addons; + } + } + catch(const std::exception& e) + { + std::stringstream msg; + msg << "Problem when reading Add-on list: " << e.what(); + throw std::runtime_error(msg.str()); } - PHYSFS_freeList(rc); -} - - -void -AddonManager::read_config(const lisp::Lisp& lisp) -{ - lisp.get_vector("disabled-addons", ignored_addon_filenames); + return m_addons; } void -AddonManager::write_config(lisp::Writer& writer) +AddonManager::update() { - writer.write_string_vector("disabled-addons", ignored_addon_filenames); + m_downloader.update(); } +/* EOF */