From 63ca854a3bacef4f6df088a0af5f629c89000018 Mon Sep 17 00:00:00 2001 From: Christoph Sommer Date: Wed, 4 Apr 2007 20:01:53 +0000 Subject: [PATCH] Add-on Manager now uses .nfo files for meta information SVN-Revision: 4966 --- src/addon.cpp | 65 +++++++++++++++++++++++ src/addon.hpp | 38 +++++++++++++- src/addon_manager.cpp | 139 ++++++++++++++++++++++++++++---------------------- src/title.cpp | 8 ++- 4 files changed, 185 insertions(+), 65 deletions(-) diff --git a/src/addon.cpp b/src/addon.cpp index 4057502df..1b6dbdc2c 100644 --- a/src/addon.cpp +++ b/src/addon.cpp @@ -19,6 +19,8 @@ // 02111-1307, USA. // +#include +#include #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); +} + diff --git a/src/addon.hpp b/src/addon.hpp index 22615120d..b65efe704 100644 --- a/src/addon.hpp +++ b/src/addon.hpp @@ -23,6 +23,9 @@ #include #include +#include "lisp/parser.hpp" +#include "lisp/lisp.hpp" +#include "lisp/writer.hpp" /** * Represents an (available or installed) Add-on, e.g. a level set @@ -30,9 +33,14 @@ 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 diff --git a/src/addon_manager.cpp b/src/addon_manager.cpp index a2308ae72..8c1c1e463 100644 --- a/src/addon_manager.cpp +++ b/src/addon_manager.cpp @@ -19,6 +19,7 @@ // 02111-1307, USA. // +#include #include #include #include @@ -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 @@ -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 = "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); + 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()); + } } + diff --git a/src/title.cpp b/src/title.cpp index 4490f11eb..120935b6d 100644 --- a/src/title.cpp +++ b/src/title.cpp @@ -246,7 +246,7 @@ TitleScreen::generate_addons_menu() bool restart = false; for (std::vector::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(); -- 2.11.0