From: Christoph Sommer Date: Thu, 25 Jan 2007 23:58:00 +0000 (+0000) Subject: Experiments with Addon Manager. To try it out, install libcurl-dev and re-run ./autog... X-Git-Url: https://git.verplant.org/?a=commitdiff_plain;h=7ee27c4c9687de567f6cc9e811b6b962d6a62f39;p=supertux.git Experiments with Addon Manager. To try it out, install libcurl-dev and re-run ./autogen.sh and ./configure --enable-debug... SVN-Revision: 4669 --- diff --git a/configure.ac b/configure.ac index efc413698..ae42eab1e 100644 --- a/configure.ac +++ b/configure.ac @@ -107,6 +107,12 @@ AM_ICONV AC_SUBST([ICONV_LIBS], [$LIBICONV]) dnl =========================================================================== +dnl Check for libcurl + +LIBCURL_CHECK_CONFIG +AC_SUBST([LIBCURL_LIBS], [$LIBCURL]) + +dnl =========================================================================== dnl Check for SDL SDL_VERSION=1.2.4 AM_PATH_SDL($SDL_VERSION, diff --git a/data/locale/messages.pot b/data/locale/messages.pot index a155757ff..da219ee24 100644 --- a/data/locale/messages.pot +++ b/data/locale/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2007-01-23 17:49+0100\n" +"POT-Creation-Date: 2007-01-26 00:54+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -62,7 +62,7 @@ msgstr "" #: src/control/joystickkeyboardcontroller.cpp:603 #: src/control/joystickkeyboardcontroller.cpp:697 src/options_menu.cpp:62 -#: src/title.cpp:88 src/title.cpp:139 src/title.cpp:190 +#: src/title.cpp:90 src/title.cpp:141 src/title.cpp:192 src/title.cpp:267 msgid "Back" msgstr "" @@ -147,7 +147,7 @@ msgstr "" msgid "Continue" msgstr "" -#: src/game_session.cpp:104 src/options_menu.cpp:49 src/title.cpp:277 +#: src/game_session.cpp:104 src/options_menu.cpp:49 src/title.cpp:362 #: src/worldmap/worldmap.cpp:152 msgid "Options" msgstr "" @@ -260,23 +260,35 @@ msgstr "" msgid "Time" msgstr "" -#: src/title.cpp:82 src/title.cpp:275 +#: src/title.cpp:84 src/title.cpp:359 msgid "Start Game" msgstr "" -#: src/title.cpp:117 src/title.cpp:276 +#: src/title.cpp:119 src/title.cpp:360 msgid "Contrib Levels" msgstr "" -#: src/title.cpp:278 +#: src/title.cpp:253 src/title.cpp:361 +msgid "Add-ons" +msgstr "" + +#: src/title.cpp:260 +msgid "Remove" +msgstr "" + +#: src/title.cpp:262 +msgid "Install" +msgstr "" + +#: src/title.cpp:363 msgid "Credits" msgstr "" -#: src/title.cpp:279 +#: src/title.cpp:364 msgid "Quit" msgstr "" -#: src/title.cpp:318 +#: src/title.cpp:403 msgid "" "Copyright (c) 2006 SuperTux Devel Team\n" "This game comes with ABSOLUTELY NO WARRANTY. This is free software, and you " @@ -284,11 +296,11 @@ msgid "" "redistribute it under certain conditions; see the file COPYING for details.\n" msgstr "" -#: src/title.cpp:420 +#: src/title.cpp:512 msgid "Free" msgstr "" -#: src/title.cpp:420 src/title.cpp:425 +#: src/title.cpp:512 src/title.cpp:517 msgid "Slot" msgstr "" diff --git a/src/Jamfile b/src/Jamfile index 6d29ef0bf..ef1370683 100644 --- a/src/Jamfile +++ b/src/Jamfile @@ -27,7 +27,7 @@ TRANSLATABLE_SOURCES += [ SearchSource $(sources) ] ; Application supertux : $(sources) $(wrapper_objects) : linkerfile ; C++Flags supertux : -DAPPDATADIR=\'\"$(appdatadir)\"\' ; LinkWith supertux : squirrel ; -ExternalLibs supertux : SDL SDLIMAGE GL OPENAL VORBIS VORBISFILE OGG ICONV PHYSFS BINRELOC ; +ExternalLibs supertux : SDL SDLIMAGE GL OPENAL VORBIS VORBISFILE OGG ICONV PHYSFS BINRELOC LIBCURL ; Help supertux : "Build the supertux executable" ; IncludeDir supertux : squirrel/include squirrel ; diff --git a/src/addon.cpp b/src/addon.cpp new file mode 100644 index 000000000..9f0235723 --- /dev/null +++ b/src/addon.cpp @@ -0,0 +1,38 @@ +// $Id$ +// +// SuperTux - Add-on +// Copyright (C) 2007 Christoph Sommer +// +// 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 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, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// + +#include "addon.hpp" +#include "addon_manager.hpp" + +void +Addon::install() +{ + AddonManager& adm = AddonManager::get_instance(); + adm.install(*this); +} + +void +Addon::remove() +{ + AddonManager& adm = AddonManager::get_instance(); + adm.remove(*this); +} + diff --git a/src/addon.hpp b/src/addon.hpp new file mode 100644 index 000000000..9e9bcc1b0 --- /dev/null +++ b/src/addon.hpp @@ -0,0 +1,51 @@ +// $Id$ +// +// SuperTux - Add-on +// Copyright (C) 2007 Christoph Sommer +// +// 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 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, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +#ifndef ADDON_H +#define ADDON_H + +#include +#include + +/** + * Represents an (available or installed) Add-on, e.g. a level set + */ +class Addon +{ +public: + std::string title; + std::string url; + std::string fname; + bool isInstalled; + + /** + * Download and install Add-on + */ + void install(); + + /** + * Physically delete Add-on + */ + void remove(); + +}; + +#endif + diff --git a/src/addon_manager.cpp b/src/addon_manager.cpp new file mode 100644 index 000000000..3181bb77c --- /dev/null +++ b/src/addon_manager.cpp @@ -0,0 +1,238 @@ +// $Id$ +// +// SuperTux - Add-on Manager +// Copyright (C) 2007 Christoph Sommer +// +// 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 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, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// + +#include +#include +#include +#include +#include "addon_manager.hpp" +#include "config.h" +#include "log.hpp" + +#ifdef HAVE_LIBCURL +#include +#include +#include +#endif + +#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; + } + + 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; + } + +} +#endif + +AddonManager& +AddonManager::get_instance() +{ + static AddonManager instance; + return instance; +} + +AddonManager::AddonManager() +{ +#ifdef HAVE_LIBCURL + curl_global_init(CURL_GLOBAL_ALL); +#endif +} + +AddonManager::~AddonManager() +{ +#ifdef HAVE_LIBCURL + curl_global_cleanup(); +#endif +} + +std::vector +AddonManager::get_addons() const +{ + std::vector addons; + + // first step: search for installed addons + + // iterate over complete search path (i.e. directories and archives) + char **i = PHYSFS_getSearchPath(); + if (!i) throw std::runtime_error("Could not query physfs search path"); + for (; *i != NULL; i++) { + + // get filename of potential archive + std::string fileName = *i; + + // 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 (fileName.compare(fileName.length()-archiveExt.length(), archiveExt.length(), archiveExt) != 0) continue; + + // make sure it exists + struct stat stats; + if (stat(fileName.c_str(), &stats) != 0) continue; + + // make sure it's an actual file + if (!S_ISREG(stats.st_mode)) continue; + + // extract nice title + static const char* dirSep = PHYSFS_getDirSeparator(); + std::string::size_type n = fileName.rfind(dirSep) + 1; + if (n == std::string::npos) n = 0; + std::string title = fileName.substr(n, fileName.length() - n - archiveExt.length()); + + Addon addon; + addon.title = title; + addon.fname = fileName; + addon.isInstalled = true; + + addons.push_back(addon); + } + +#ifdef HAVE_LIBCURL + // second step: search for available addons + + // FIXME: This URL is just for testing! + const char* baseUrl = "http://www.deltadevelopment.de/users/christoph/supertux/addons/"; + std::string html = ""; + + 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, &html); + curl_easy_perform(curl_handle); + curl_easy_cleanup(curl_handle); + + //std::string html = "BlubbCoconut Fortress\nFoobarAnotherBaz"; + static const std::string startToken = "href=\""; + static const std::string endToken = "\""; + + // extract urls: for each startToken found... + std::string::size_type n = 0; + while ((n = html.find(startToken)) != std::string::npos) { + + // strip everything up to and including token + html.erase(0, n + startToken.length()); + + // find end token + std::string::size_type n2 = html.find(endToken); + if (n2 == std::string::npos) break; + + // extract url: it's the string inbetween + std::string url = html.substr(0, n2); + + // strip everything up to and including endToken + html.erase(0, n2 + endToken.length()); + + // make absolute url + url = std::string(baseUrl) + url; + + // make sure url looks like it points to an archive + static const std::string archiveExt = ".zip"; + if (url.compare(url.length()-archiveExt.length(), archiveExt.length(), archiveExt) != 0) continue; + + // extract nice title + std::string::size_type n = url.rfind('/') + 1; + if (n == std::string::npos) n = 0; + std::string title = url.substr(n, url.length() - n - archiveExt.length()); + + // construct file name + std::string fname = url.substr(n); + + // make sure it does not contain weird characters + if (fname.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) continue; + + Addon addon; + addon.title = title; + addon.fname = fname; + addon.url = url; + addon.isInstalled = false; + + addons.push_back(addon); + } +#endif + + return addons; +} + + +void +AddonManager::install(const Addon& + #ifdef HAVE_LIBCURL + addon + #endif + ) +{ + +#ifdef HAVE_LIBCURL + + char* url = (char*)malloc(addon.url.length() + 1); + strncpy(url, addon.url.c_str(), addon.url.length() + 1); + + PHYSFS_file* f = PHYSFS_openWrite(addon.fname.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_perform(curl_handle); + curl_easy_cleanup(curl_handle); + + PHYSFS_close(f); + + free(url); + + static const std::string writeDir = PHYSFS_getWriteDir(); + static const std::string dirSep = PHYSFS_getDirSeparator(); + std::string fullFilename = writeDir + dirSep + addon.fname; + log_debug << "Finished downloading \"" << fullFilename << "\"" << std::endl; + PHYSFS_addToSearchPath(fullFilename.c_str(), 1); +#endif + +} + +void +AddonManager::remove(const Addon& addon) +{ + PHYSFS_removeFromSearchPath(addon.fname.c_str()); + unlink(addon.fname.c_str()); +} + diff --git a/src/addon_manager.hpp b/src/addon_manager.hpp new file mode 100644 index 000000000..f56e65aec --- /dev/null +++ b/src/addon_manager.hpp @@ -0,0 +1,57 @@ +// $Id$ +// +// SuperTux - Add-on Manager +// Copyright (C) 2007 Christoph Sommer +// +// 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 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, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +#ifndef ADDON_MANAGER_H +#define ADDON_MANAGER_H + +#include +#include +#include "addon.hpp" + +/** + * Checks for, installs and removes Add-ons + */ +class AddonManager +{ +public: + /** + * returns a list of (available or installed) Add-ons + */ + std::vector get_addons() const; + + /** + * Download and install Add-on + */ + void install(const Addon& addon); + + /** + * Physically delete Add-on + */ + void remove(const Addon& addon); + + static AddonManager& get_instance(); + +protected: + AddonManager(); + ~AddonManager(); +}; + +#endif + diff --git a/src/title.cpp b/src/title.cpp index 569ebfd13..a2c576494 100644 --- a/src/title.cpp +++ b/src/title.cpp @@ -64,10 +64,12 @@ #include "options_menu.hpp" #include "console.hpp" #include "random_generator.hpp" +#include "addon_manager.hpp" enum MainMenuIDs { MNID_STARTGAME, MNID_LEVELS_CONTRIB, + MNID_ADDONS, MNID_OPTIONMENU, MNID_LEVELEDITOR, MNID_CREDITS, @@ -207,6 +209,88 @@ TitleScreen::check_contrib_world_menu() } } +namespace { + bool generate_addons_menu_sorter(const Addon& a1, const Addon& a2) + { + return a1.title < a2.title; + } +} + +void +TitleScreen::generate_addons_menu() +{ + AddonManager& adm = AddonManager::get_instance(); + addons = adm.get_addons(); + + // sort addons + std::sort(addons.begin(), addons.end(), generate_addons_menu_sorter); + + // hide installed addons from installation menu + std::vector::iterator it2 = addons.begin(); + while (it2 != addons.end()) { + Addon addon = *it2; + if (addon.isInstalled) { + bool restart = false; + for (std::vector::iterator it = addons.begin(); it != addons.end(); ++it) { + Addon addon2 = *it; + if ((addon2.title == addon.title) && (!addon2.isInstalled)) { + addons.erase(it); + restart = true; + break; + } + } + if (restart) { + it2 = addons.begin(); + continue; + } + } + it2++; + } + + free_addons_menu(); + addons_menu.reset(new Menu()); + + addons_menu->add_label(_("Add-ons")); + addons_menu->add_hl(); + + int i = 0; + for (std::vector::iterator it = addons.begin(); it != addons.end(); ++it) { + Addon addon = *it; + if (addon.isInstalled) { + addons_menu->add_entry(i++, std::string(_("Remove")) + " \"" + addon.title + "\""); + } else { + addons_menu->add_entry(i++, std::string(_("Install")) + " \"" + addon.title + "\""); + } + } + + addons_menu->add_hl(); + addons_menu->add_back(_("Back")); +} + +void +TitleScreen::check_addons_menu() +{ + int index = addons_menu->check(); + if (index == -1) return; + + //AddonManager& adm = AddonManager::get_instance(); + Addon addon = addons[index]; + if (!addon.isInstalled) { + addon.install(); + generate_addons_menu(); + Menu::set_current(addons_menu.get()); + } else { + addon.remove(); + generate_addons_menu(); + Menu::set_current(addons_menu.get()); + } +} + +void +TitleScreen::free_addons_menu() +{ +} + void TitleScreen::make_tux_jump() { @@ -274,6 +358,7 @@ TitleScreen::TitleScreen() main_menu->set_pos(SCREEN_WIDTH/2, SCREEN_HEIGHT/2 + 35); main_menu->add_entry(MNID_STARTGAME, _("Start Game")); main_menu->add_entry(MNID_LEVELS_CONTRIB, _("Contrib Levels")); + main_menu->add_entry(MNID_ADDONS, _("Add-ons")); main_menu->add_submenu(_("Options"), get_options_menu()); main_menu->add_entry(MNID_CREDITS, _("Credits")); main_menu->add_entry(MNID_QUITMAINMENU, _("Quit")); @@ -353,6 +438,11 @@ TitleScreen::update(float elapsed_time) generate_contrib_menu(); Menu::push_current(contrib_menu.get()); break; + case MNID_ADDONS: + // Add-ons Menu + generate_addons_menu(); + Menu::push_current(addons_menu.get()); + break; case MNID_CREDITS: main_loop->push_screen(new TextScroller("credits.txt"), new FadeOut(0.5)); @@ -381,6 +471,8 @@ TitleScreen::update(float elapsed_time) process_load_game_menu(); } else if(menu == contrib_menu.get()) { check_levels_contrib_menu(); + } else if(menu == addons_menu.get()) { + check_addons_menu(); } else if (menu == contrib_world_menu.get()) { check_contrib_world_menu(); } diff --git a/src/title.hpp b/src/title.hpp index 39d0abf08..7de0a4379 100644 --- a/src/title.hpp +++ b/src/title.hpp @@ -25,6 +25,7 @@ #include #include "screen.hpp" #include "game_session.hpp" +#include "addon.hpp" class Menu; class World; @@ -53,6 +54,9 @@ private: void check_levels_contrib_menu(); void check_contrib_world_menu(); void free_contrib_menu(); + void generate_addons_menu(); + void check_addons_menu(); + void free_addons_menu(); std::auto_ptr main_menu; std::auto_ptr load_game_menu; @@ -60,6 +64,8 @@ private: std::auto_ptr contrib_world_menu; std::auto_ptr main_world; std::vector contrib_worlds; + std::auto_ptr addons_menu; + std::vector addons; World* current_world; std::auto_ptr controller;