Add-on Manager now uses .nfo files for meta information
authorChristoph Sommer <mail@christoph-sommer.de>
Wed, 4 Apr 2007 20:01:53 +0000 (20:01 +0000)
committerChristoph Sommer <mail@christoph-sommer.de>
Wed, 4 Apr 2007 20:01:53 +0000 (20:01 +0000)
SVN-Revision: 4966

src/addon.cpp
src/addon.hpp
src/addon_manager.cpp
src/title.cpp

index 4057502..1b6dbdc 100644 (file)
@@ -19,6 +19,8 @@
 //  02111-1307, USA.
 //
 
+#include <sstream>
+#include <stdexcept>
 #include "addon.hpp"
 #include "addon_manager.hpp"
 
@@ -35,3 +37,66 @@ Addon::remove()
   AddonManager& adm = AddonManager::get_instance();
   adm.remove(*this);
 }
+  
+void
+Addon::parse(const lisp::Lisp& lisp)
+{
+  try {
+    lisp.get("kind", kind);  
+    lisp.get("title", title);
+    lisp.get("author", author);
+    lisp.get("license", license);
+    lisp.get("http-url", http_url);
+    lisp.get("file", file);
+    lisp.get("md5", md5);
+  } catch(std::exception& e) {
+    std::stringstream msg;
+    msg << "Problem when parsing addoninfo: " << e.what();
+    throw std::runtime_error(msg.str());
+  }
+}
+
+void
+Addon::parse(std::string fname)
+{
+  try {
+    lisp::Parser parser;
+    const lisp::Lisp* root = parser.parse(fname);
+    const lisp::Lisp* addon = root->get_lisp("supertux-addoninfo");
+    if(!addon) throw std::runtime_error("file is not a supertux-addoninfo file.");
+    parse(*addon);
+  } catch(std::exception& e) {
+    std::stringstream msg;
+    msg << "Problem when reading addoninfo '" << fname << "': " << e.what();
+    throw std::runtime_error(msg.str());
+  }
+}
+
+void
+Addon::write(lisp::Writer& writer) const
+{
+  writer.start_list("supertux-addoninfo");
+  if (kind != "") writer.write_string("kind", kind);  
+  if (title != "") writer.write_string("title", title);
+  if (author != "") writer.write_string("author", author);
+  if (license != "") writer.write_string("license", license);
+  if (http_url != "") writer.write_string("http-url", http_url);
+  if (file != "") writer.write_string("file", file);
+  if (md5 != "") writer.write_string("md5", md5);
+  writer.end_list("supertux-addoninfo");
+}
+
+void 
+Addon::write(std::string fname) const
+{
+  lisp::Writer writer(fname);
+  write(writer);
+}
+
+bool 
+Addon::equals(const Addon& addon2) const
+{
+  if ((this->md5 == "") || (addon2.md5 == "")) return (this->title == addon2.title);
+  return (this->md5 == addon2.md5);
+}
+
index 2261512..b65efe7 100644 (file)
@@ -23,6 +23,9 @@
 
 #include <string>
 #include <vector>
+#include "lisp/parser.hpp"
+#include "lisp/lisp.hpp"
+#include "lisp/writer.hpp"
 
 /**
  * Represents an (available or installed) Add-on, e.g. a level set
 class Addon
 {
 public:
+  std::string kind;
   std::string title;
-  std::string url;
-  std::string fname;
+  std::string author;
+  std::string license;
+  std::string http_url;
+  std::string file;
+  std::string md5;
+
   bool isInstalled;
 
   /**
@@ -45,6 +53,32 @@ public:
    */
   void remove();
 
+  /**
+   * Read additional information from given contents of a (supertux-addoninfo ...) block
+   */
+  void parse(const lisp::Lisp& lisp);
+
+  /**
+   * Read additional information from given file
+   */
+  void parse(std::string fname);
+
+  /**
+   * Writes out Add-on metainformation to a Lisp Writer
+   */
+  void write(lisp::Writer& writer) const;
+
+  /**
+   * Writes out Add-on metainformation to a file
+   */
+  void write(std::string fname) const;
+
+  /**
+   * Checks if Add-on is the same as given one. 
+   * If available, checks MD5 sum, else relies on title alone.
+   */
+  bool equals(const Addon& addon2) const;
+
 };
 
 #endif
index a2308ae..8c1c1e4 100644 (file)
@@ -19,6 +19,7 @@
 //  02111-1307, USA.
 //
 
+#include <sstream>
 #include <stdexcept>
 #include <list>
 #include <physfs.h>
@@ -27,6 +28,9 @@
 #include "addon_manager.hpp"
 #include "config.h"
 #include "log.hpp"
+#include "lisp/parser.hpp"
+#include "lisp/lisp.hpp"
+#include "lisp/list_iterator.hpp"
 
 #ifdef HAVE_LIBCURL
 #include <curl/curl.h>
@@ -106,17 +110,27 @@ AddonManager::get_installed_addons() const
     // make sure it's an actual file
     if (!S_ISREG(stats.st_mode)) continue;
 
-    // extract nice title
+    Addon addon;
+
+    // extract nice title as fallback for when the Add-on has no addoninfo file
     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.title = fileName.substr(n, fileName.length() - n - archiveExt.length());
+    std::string shortFileName = fileName.substr(n, fileName.length() - n);
+    addon.file = shortFileName;
+   
+    // read an accompaining .nfo file, if it exists
+    static const std::string infoExt = ".nfo";
+    std::string infoFileName = fileName.substr(n, fileName.length() - n - archiveExt.length()) + infoExt;
+    if (PHYSFS_exists(infoFileName.c_str())) {
+      addon.parse(infoFileName);
+      if (addon.file != shortFileName) {
+        log_warning << "Add-on \"" << addon.title << "\", contained in file \"" << shortFileName << "\" is accompained by an addoninfo file that specifies \"" << addon.file << "\" as the Add-on's file name. Skipping." << std::endl;
+      }
+    }
 
-    Addon addon;
-    addon.title = title;
-    addon.fname = fileName;
     addon.isInstalled = true;
-
     addons.push_back(addon);
   }
 
@@ -131,65 +145,51 @@ AddonManager::get_available_addons() const
 #ifdef HAVE_LIBCURL
 
   // FIXME: This URL is just for testing!
-  const char* baseUrl = "http://www.deltadevelopment.de/users/christoph/supertux/addons/";
-  std::string html = "";
+  const char* baseUrl = "http://www.deltadevelopment.de/users/christoph/supertux/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, &html);
+  curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &addoninfos);
   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);
+  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("file is not a supertux-addons file.");
+
+    lisp::ListIterator iter(addons_lisp);
+    while(iter.next()) {
+      const std::string& token = iter.item();
+      if(token == "supertux-addoninfo") {
+        Addon addon;
+       addon.parse(*(iter.lisp()));
+
+        // make sure the Add-on's file name does not contain weird characters
+        if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) {
+          log_warning << "Add-on \"" << addon.title << "\" contains unsafe file name. Skipping." << std::endl;
+          continue;
+        }
+
+        addon.isInstalled = false;
+        addons.push_back(addon);
+      } else {
+        log_warning << "Unknown token '" << token << "' in supertux-addons file" << std::endl;
+      }
+    }
+  } catch(std::exception& e) {
+    std::stringstream msg;
+    msg << "Problem when reading addoninfo: " << e.what();
+    throw std::runtime_error(msg.str());
   }
+
 #endif
 
   return addons;
@@ -202,10 +202,10 @@ AddonManager::install(const Addon& addon)
 
 #ifdef HAVE_LIBCURL
 
-  char* url = (char*)malloc(addon.url.length() + 1);
-  strncpy(url, addon.url.c_str(), addon.url.length() + 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(addon.fname.c_str());
+  PHYSFS_file* f = PHYSFS_openWrite(addon.file.c_str());
 
   log_debug << "Downloading \"" << url << "\"" << std::endl;
 
@@ -222,9 +222,15 @@ AddonManager::install(const Addon& addon)
 
   free(url);
 
+  // write an accompaining .nfo file
+  static const std::string archiveExt = ".zip";
+  static const std::string infoExt = ".nfo";
+  std::string infoFileName = addon.file.substr(0, addon.file.length()-archiveExt.length()) + infoExt;
+  addon.write(infoFileName);
+
   static const std::string writeDir = PHYSFS_getWriteDir();
   static const std::string dirSep = PHYSFS_getDirSeparator();
-  std::string fullFilename = writeDir + dirSep + addon.fname;
+  std::string fullFilename = writeDir + dirSep + addon.file;
   log_debug << "Finished downloading \"" << fullFilename << "\"" << std::endl;
   PHYSFS_addToSearchPath(fullFilename.c_str(), 1);
 #else
@@ -236,6 +242,17 @@ AddonManager::install(const Addon& addon)
 void
 AddonManager::remove(const Addon& addon)
 {
-  PHYSFS_removeFromSearchPath(addon.fname.c_str());
-  PHYSFS_delete(addon.fname.c_str());
+  log_debug << "deleting file \"" << addon.file << "\"" << std::endl;
+  PHYSFS_removeFromSearchPath(addon.file.c_str());
+  PHYSFS_delete(addon.file.c_str());
+
+  // remove an accompaining .nfo file
+  static const std::string archiveExt = ".zip";
+  static const std::string infoExt = ".nfo";
+  std::string infoFileName = addon.file.substr(0, addon.file.length()-archiveExt.length()) + infoExt;
+  if (PHYSFS_exists(infoFileName.c_str())) {
+    log_debug << "deleting file \"" << infoFileName << "\"" << std::endl;
+    PHYSFS_delete(infoFileName.c_str());
+  }
 }
+
index 4490f11..120935b 100644 (file)
@@ -246,7 +246,7 @@ TitleScreen::generate_addons_menu()
       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)) {
+        if ((addon2.equals(addon)) && (!addon2.isInstalled)) {
           addons.erase(it);
           restart = true;
           break;
@@ -277,7 +277,11 @@ TitleScreen::generate_addons_menu()
 
   for (unsigned int i = 0; i < addons.size(); i++) {
     Addon addon = addons[i];
-    addons_menu->add_toggle(ADDON_LIST_START_ID + i, std::string("\"") + addon.title + "\"", addon.isInstalled);
+    std::string text = "";
+    if (addon.kind != "") text += addon.kind + " ";
+    text += std::string("\"") + addon.title + "\"";
+    if (addon.author != "") text += " by \"" + addon.author + "\"";
+    addons_menu->add_toggle(ADDON_LIST_START_ID + i, text, addon.isInstalled);
   }
 
   addons_menu->add_hl();