#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&
/*
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;
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);
}
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
{
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);
}
}
{
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;
// 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;
// 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);
}
--- /dev/null
+// 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 */