3 // SuperTux - Add-on Manager
4 // Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
29 #include "addon_manager.hpp"
32 #include "lisp/parser.hpp"
33 #include "lisp/lisp.hpp"
34 #include "lisp/list_iterator.hpp"
37 #include <curl/curl.h>
38 #include <curl/types.h>
39 #include <curl/easy.h>
45 size_t my_curl_string_append(void *ptr, size_t size, size_t nmemb, void *string_ptr)
47 std::string& s = *static_cast<std::string*>(string_ptr);
48 std::string buf(static_cast<char*>(ptr), size * nmemb);
50 log_debug << "read " << size * nmemb << " bytes of data..." << std::endl;
54 size_t my_curl_physfs_write(void *ptr, size_t size, size_t nmemb, void *f_p)
56 PHYSFS_file* f = static_cast<PHYSFS_file*>(f_p);
57 PHYSFS_sint64 written = PHYSFS_write(f, ptr, size, nmemb);
58 log_debug << "read " << size * nmemb << " bytes of data..." << std::endl;
59 return size * written;
66 AddonManager::get_instance()
68 static AddonManager instance;
72 AddonManager::AddonManager()
75 curl_global_init(CURL_GLOBAL_ALL);
79 AddonManager::~AddonManager()
82 curl_global_cleanup();
87 AddonManager::get_installed_addons() const
89 std::vector<Addon> addons;
91 // iterate over complete search path (i.e. directories and archives)
92 char **i = PHYSFS_getSearchPath();
93 if (!i) throw std::runtime_error("Could not query physfs search path");
94 for (; *i != NULL; i++) {
96 // get filename of potential archive
97 std::string fileName = *i;
99 // make sure it's in the writeDir
100 static const std::string writeDir = PHYSFS_getWriteDir();
101 if (fileName.compare(0, writeDir.length(), writeDir) != 0) continue;
103 // make sure it looks like an archive
104 static const std::string archiveExt = ".zip";
105 if (fileName.compare(fileName.length()-archiveExt.length(), archiveExt.length(), archiveExt) != 0) continue;
107 // make sure it exists
109 if (stat(fileName.c_str(), &stats) != 0) continue;
111 // make sure it's an actual file
112 if (!S_ISREG(stats.st_mode)) continue;
116 // extract nice title as fallback for when the Add-on has no addoninfo file
117 static const char* dirSep = PHYSFS_getDirSeparator();
118 std::string::size_type n = fileName.rfind(dirSep) + 1;
119 if (n == std::string::npos) n = 0;
120 addon.title = fileName.substr(n, fileName.length() - n - archiveExt.length());
121 std::string shortFileName = fileName.substr(n, fileName.length() - n);
122 addon.file = shortFileName;
124 // read an accompaining .nfo file, if it exists
125 static const std::string infoExt = ".nfo";
126 std::string infoFileName = fileName.substr(n, fileName.length() - n - archiveExt.length()) + infoExt;
127 if (PHYSFS_exists(infoFileName.c_str())) {
128 addon.parse(infoFileName);
129 if (addon.file != shortFileName) {
130 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;
134 // make sure the Add-on's file name does not contain weird characters
135 if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) {
136 log_warning << "Add-on \"" << addon.title << "\" contains unsafe file name. Skipping." << std::endl;
140 addon.isInstalled = true;
141 addons.push_back(addon);
148 AddonManager::get_available_addons() const
150 std::vector<Addon> addons;
154 char error_buffer[CURL_ERROR_SIZE+1];
156 const char* baseUrl = "http://supertux.berlios.de/addons/index.nfo";
157 std::string addoninfos = "";
160 curl_handle = curl_easy_init();
161 curl_easy_setopt(curl_handle, CURLOPT_URL, baseUrl);
162 curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL");
163 curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, my_curl_string_append);
164 curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &addoninfos);
165 curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, error_buffer);
166 curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1);
167 curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
168 curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1);
169 curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1);
170 CURLcode result = curl_easy_perform(curl_handle);
171 curl_easy_cleanup(curl_handle);
173 if (result != CURLE_OK) {
174 std::string why = error_buffer[0] ? error_buffer : "unhandled error";
175 throw std::runtime_error("Downloading Add-on list failed: " + why);
180 std::stringstream addoninfos_stream(addoninfos);
181 const lisp::Lisp* root = parser.parse(addoninfos_stream, "supertux-addons");
183 const lisp::Lisp* addons_lisp = root->get_lisp("supertux-addons");
184 if(!addons_lisp) throw std::runtime_error("Downloaded file is not an Add-on list");
186 lisp::ListIterator iter(addons_lisp);
188 const std::string& token = iter.item();
189 if(token == "supertux-addoninfo") {
191 addon.parse(*(iter.lisp()));
193 // make sure the Add-on's file name does not contain weird characters
194 if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) {
195 log_warning << "Add-on \"" << addon.title << "\" contains unsafe file name. Skipping." << std::endl;
199 addon.isInstalled = false;
200 addons.push_back(addon);
202 log_warning << "Unknown token '" << token << "' in Add-on list" << std::endl;
205 } catch(std::exception& e) {
206 std::stringstream msg;
207 msg << "Problem when reading Add-on list: " << e.what();
208 throw std::runtime_error(msg.str());
218 AddonManager::install(const Addon& addon)
220 // make sure the Add-on's file name does not contain weird characters
221 if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) {
222 throw std::runtime_error("Add-on has unsafe file name (\""+addon.file+"\")");
227 char error_buffer[CURL_ERROR_SIZE+1];
229 char* url = (char*)malloc(addon.http_url.length() + 1);
230 strncpy(url, addon.http_url.c_str(), addon.http_url.length() + 1);
232 PHYSFS_file* f = PHYSFS_openWrite(addon.file.c_str());
234 log_debug << "Downloading \"" << url << "\"" << std::endl;
237 curl_handle = curl_easy_init();
238 curl_easy_setopt(curl_handle, CURLOPT_URL, url);
239 curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL");
240 curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, my_curl_physfs_write);
241 curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, f);
242 curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, error_buffer);
243 curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1);
244 curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
245 curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1);
246 CURLcode result = curl_easy_perform(curl_handle);
247 curl_easy_cleanup(curl_handle);
253 if (result != CURLE_OK) {
254 PHYSFS_delete(addon.file.c_str());
255 std::string why = error_buffer[0] ? error_buffer : "unhandled error";
256 throw std::runtime_error("Downloading Add-on failed: " + why);
259 // write an accompaining .nfo file
260 static const std::string archiveExt = ".zip";
261 static const std::string infoExt = ".nfo";
262 std::string infoFileName = addon.file.substr(0, addon.file.length()-archiveExt.length()) + infoExt;
263 addon.write(infoFileName);
265 static const std::string writeDir = PHYSFS_getWriteDir();
266 static const std::string dirSep = PHYSFS_getDirSeparator();
267 std::string fullFilename = writeDir + dirSep + addon.file;
268 log_debug << "Finished downloading \"" << fullFilename << "\"" << std::endl;
269 PHYSFS_addToSearchPath(fullFilename.c_str(), 1);
277 AddonManager::remove(const Addon& addon)
279 // make sure the Add-on's file name does not contain weird characters
280 if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) {
281 throw std::runtime_error("Add-on has unsafe file name (\""+addon.file+"\")");
284 log_debug << "deleting file \"" << addon.file << "\"" << std::endl;
285 PHYSFS_removeFromSearchPath(addon.file.c_str());
286 PHYSFS_delete(addon.file.c_str());
288 // remove an accompaining .nfo file
289 static const std::string archiveExt = ".zip";
290 static const std::string infoExt = ".nfo";
291 std::string infoFileName = addon.file.substr(0, addon.file.length()-archiveExt.length()) + infoExt;
292 if (PHYSFS_exists(infoFileName.c_str())) {
293 log_debug << "deleting file \"" << infoFileName << "\"" << std::endl;
294 PHYSFS_delete(infoFileName.c_str());