Experiments with Addon Manager. To try it out, install libcurl-dev and re-run ./autog...
authorChristoph Sommer <mail@christoph-sommer.de>
Thu, 25 Jan 2007 23:58:00 +0000 (23:58 +0000)
committerChristoph Sommer <mail@christoph-sommer.de>
Thu, 25 Jan 2007 23:58:00 +0000 (23:58 +0000)
SVN-Revision: 4669

configure.ac
data/locale/messages.pot
src/Jamfile
src/addon.cpp [new file with mode: 0644]
src/addon.hpp [new file with mode: 0644]
src/addon_manager.cpp [new file with mode: 0644]
src/addon_manager.hpp [new file with mode: 0644]
src/title.cpp
src/title.hpp

index efc4136..ae42eab 100644 (file)
@@ -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,
index a155757..da219ee 100644 (file)
@@ -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 <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\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 ""
 
index 6d29ef0..ef13706 100644 (file)
@@ -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 (file)
index 0000000..9f02357
--- /dev/null
@@ -0,0 +1,38 @@
+//  $Id$
+//
+//  SuperTux - Add-on
+//  Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
+//
+//  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 (file)
index 0000000..9e9bcc1
--- /dev/null
@@ -0,0 +1,51 @@
+//  $Id$
+//
+//  SuperTux - Add-on
+//  Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
+//
+//  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 <string>
+#include <vector>
+
+/**
+ * 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 (file)
index 0000000..3181bb7
--- /dev/null
@@ -0,0 +1,238 @@
+//  $Id$
+//
+//  SuperTux - Add-on Manager
+//  Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
+//
+//  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 <stdexcept>
+#include <list>
+#include <physfs.h>
+#include <sys/stat.h>
+#include "addon_manager.hpp"
+#include "config.h"
+#include "log.hpp"
+
+#ifdef HAVE_LIBCURL
+#include <curl/curl.h>
+#include <curl/types.h>
+#include <curl/easy.h>
+#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<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;
+  }
+  
+  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;
+  }
+
+}
+#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<Addon> 
+AddonManager::get_addons() const
+{
+  std::vector<Addon> 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 = "Blubb<a href=\"http://www.deltadevelopment.de/users/christoph/supertux/addons/coconut_fortress.zip\">Coconut Fortress</a>\nFoobar<a href=\"http://www.deltadevelopment.de/users/christoph/supertux/addons/in_the_spring.zip\">Another</a>Baz";
+  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 (file)
index 0000000..f56e65a
--- /dev/null
@@ -0,0 +1,57 @@
+//  $Id$
+//
+//  SuperTux - Add-on Manager
+//  Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
+//
+//  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 <string>
+#include <vector>
+#include "addon.hpp"
+
+/**
+ * Checks for, installs and removes Add-ons
+ */
+class AddonManager
+{
+public:
+  /**
+   * returns a list of (available or installed) Add-ons
+   */
+  std::vector<Addon> 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
+
index 569ebfd..a2c5764 100644 (file)
 #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<Addon>::iterator it2 = addons.begin();
+  while (it2 != addons.end()) {
+    Addon addon = *it2;
+    if (addon.isInstalled) {
+      bool restart = false;
+      for (std::vector<Addon>::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<Addon>::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();
     }
index 39d0abf..7de0a43 100644 (file)
@@ -25,6 +25,7 @@
 #include <vector>
 #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<Menu> main_menu;
   std::auto_ptr<Menu> load_game_menu;
@@ -60,6 +64,8 @@ private:
   std::auto_ptr<Menu> contrib_world_menu;
   std::auto_ptr<World> main_world;
   std::vector<World*> contrib_worlds;
+  std::auto_ptr<Menu> addons_menu;
+  std::vector<Addon> addons;
   World* current_world;
 
   std::auto_ptr<CodeController> controller;