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
26 #include <unison/vfs/FileSystem.hpp>
27 #include <unison/vfs/sdl/Utils.hpp>
31 #include "addon_manager.hpp"
34 #include "lisp/parser.hpp"
35 #include "lisp/lisp.hpp"
36 #include "lisp/list_iterator.hpp"
39 #include <curl/curl.h>
40 #include <curl/types.h>
41 #include <curl/easy.h>
47 size_t my_curl_string_append(void *ptr, size_t size, size_t nmemb, void *string_ptr)
49 std::string& s = *static_cast<std::string*>(string_ptr);
50 std::string buf(static_cast<char*>(ptr), size * nmemb);
52 log_debug << "read " << size * nmemb << " bytes of data..." << std::endl;
56 /*size_t my_curl_physfs_write(void *ptr, size_t size, size_t nmemb, void *f_p)
58 PHYSFS_file* f = static_cast<PHYSFS_file*>(f_p);
59 PHYSFS_sint64 written = PHYSFS_write(f, ptr, size, nmemb);
60 log_debug << "read " << size * nmemb << " bytes of data..." << std::endl;
61 return size * written;
64 size_t my_curl_sdl_write(void *ptr, size_t size, size_t nmemb, void *f_p)
66 SDL_RWops* f = static_cast<SDL_RWops*>(f_p);
67 int written = SDL_RWwrite(f, ptr, size, nmemb);
68 log_debug << "wrote " << size * nmemb << " bytes of data..." << std::endl;
69 return size * written;
76 AddonManager::get_instance()
78 static AddonManager instance;
82 AddonManager::AddonManager()
85 curl_global_init(CURL_GLOBAL_ALL);
89 AddonManager::~AddonManager()
92 curl_global_cleanup();
97 AddonManager::get_installed_addons() const
99 std::vector<Addon> addons;
101 Unison::VFS::FileSystem &fs = Unison::VFS::FileSystem::get();
102 std::vector<std::string> search_path = fs.get_search_path();
103 for(std::vector<std::string>::iterator iter = search_path.begin();iter != search_path.end();++iter)
105 // get filename of potential archive
106 std::string fileName = *iter;
108 // make sure it's in the writeDir
109 static const std::string writeDir = fs.get_write_dir();
110 if (fileName.compare(0, writeDir.length(), writeDir) != 0) continue;
112 // make sure it looks like an archive
113 static const std::string archiveExt = ".zip";
114 if (fileName.compare(fileName.length()-archiveExt.length(), archiveExt.length(), archiveExt) != 0) continue;
116 // make sure it exists
118 if (stat(fileName.c_str(), &stats) != 0) continue;
120 // make sure it's an actual file
121 if (!S_ISREG(stats.st_mode)) continue;
125 // extract nice title as fallback for when the Add-on has no addoninfo file
126 static std::string dirSep = fs.get_dir_sep();
127 std::string::size_type n = fileName.rfind(dirSep) + 1;
128 if (n == std::string::npos) n = 0;
129 addon.title = fileName.substr(n, fileName.length() - n - archiveExt.length());
130 std::string shortFileName = fileName.substr(n, fileName.length() - n);
131 addon.file = shortFileName;
133 // read an accompaining .nfo file, if it exists
134 static const std::string infoExt = ".nfo";
135 std::string infoFileName = fileName.substr(n, fileName.length() - n - archiveExt.length()) + infoExt;
136 if (fs.exists(infoFileName)) {
137 addon.parse(infoFileName);
138 if (addon.file != shortFileName) {
139 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;
143 // make sure the Add-on's file name does not contain weird characters
144 if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) {
145 log_warning << "Add-on \"" << addon.title << "\" contains unsafe file name. Skipping." << std::endl;
149 addon.isInstalled = true;
150 addons.push_back(addon);
157 AddonManager::get_available_addons() const
159 std::vector<Addon> addons;
163 char error_buffer[CURL_ERROR_SIZE+1];
165 const char* baseUrl = "http://supertux.lethargik.org/addons/index.nfo";
166 std::string addoninfos = "";
169 curl_handle = curl_easy_init();
170 curl_easy_setopt(curl_handle, CURLOPT_URL, baseUrl);
171 curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL");
172 curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, my_curl_string_append);
173 curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &addoninfos);
174 curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, error_buffer);
175 curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1);
176 curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
177 curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1);
178 CURLcode result = curl_easy_perform(curl_handle);
179 curl_easy_cleanup(curl_handle);
181 if (result != CURLE_OK) {
182 std::string why = error_buffer[0] ? error_buffer : "unhandled error";
183 throw std::runtime_error("Downloading Add-on list failed: " + why);
188 std::stringstream addoninfos_stream(addoninfos);
189 const lisp::Lisp* root = parser.parse(addoninfos_stream, "supertux-addons");
191 const lisp::Lisp* addons_lisp = root->get_lisp("supertux-addons");
192 if(!addons_lisp) throw std::runtime_error("Downloaded file is not an Add-on list");
194 lisp::ListIterator iter(addons_lisp);
196 const std::string& token = iter.item();
197 if(token == "supertux-addoninfo") {
199 addon.parse(*(iter.lisp()));
201 // make sure the Add-on's file name does not contain weird characters
202 if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) {
203 log_warning << "Add-on \"" << addon.title << "\" contains unsafe file name. Skipping." << std::endl;
207 addon.isInstalled = false;
208 addons.push_back(addon);
210 log_warning << "Unknown token '" << token << "' in Add-on list" << std::endl;
213 } catch(std::exception& e) {
214 std::stringstream msg;
215 msg << "Problem when reading Add-on list: " << e.what();
216 throw std::runtime_error(msg.str());
226 AddonManager::install(const Addon& addon)
228 // make sure the Add-on's file name does not contain weird characters
229 if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) {
230 throw std::runtime_error("Add-on has unsafe file name (\""+addon.file+"\")");
233 Unison::VFS::FileSystem &fs = Unison::VFS::FileSystem::get();
237 char error_buffer[CURL_ERROR_SIZE+1];
239 char* url = (char*)malloc(addon.http_url.length() + 1);
240 strncpy(url, addon.http_url.c_str(), addon.http_url.length() + 1);
242 //PHYSFS_file* f = PHYSFS_openWrite(addon.file.c_str());
243 SDL_RWops *f = Unison::VFS::SDL::Utils::open_physfs_in(addon.file);
245 log_debug << "Downloading \"" << url << "\"" << std::endl;
248 curl_handle = curl_easy_init();
249 curl_easy_setopt(curl_handle, CURLOPT_URL, url);
250 curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL");
251 curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, my_curl_sdl_write);
252 curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, f);
253 curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, error_buffer);
254 curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1);
255 curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
256 curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1);
257 CURLcode result = curl_easy_perform(curl_handle);
258 curl_easy_cleanup(curl_handle);
265 if (result != CURLE_OK) {
266 //PHYSFS_delete(addon.file.c_str());
268 std::string why = error_buffer[0] ? error_buffer : "unhandled error";
269 throw std::runtime_error("Downloading Add-on failed: " + why);
272 // write an accompaining .nfo file
273 static const std::string archiveExt = ".zip";
274 static const std::string infoExt = ".nfo";
275 std::string infoFileName = addon.file.substr(0, addon.file.length()-archiveExt.length()) + infoExt;
276 addon.write(infoFileName);
278 //static const std::string writeDir = PHYSFS_getWriteDir();
279 //static const std::string dirSep = PHYSFS_getDirSeparator();
280 static const std::string writeDir = fs.get_write_dir();
281 static const std::string dirSep = fs.get_dir_sep();
282 std::string fullFilename = writeDir + dirSep + addon.file;
283 log_debug << "Finished downloading \"" << fullFilename << "\"" << std::endl;
284 fs.mount(fullFilename, "/", true);
285 //PHYSFS_addToSearchPath(fullFilename.c_str(), 1);
293 AddonManager::remove(const Addon& addon)
295 // make sure the Add-on's file name does not contain weird characters
296 if (addon.file.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) {
297 throw std::runtime_error("Add-on has unsafe file name (\""+addon.file+"\")");
300 log_debug << "deleting file \"" << addon.file << "\"" << std::endl;
302 Unison::VFS::FileSystem &fs = Unison::VFS::FileSystem::get();
303 fs.umount(addon.file);
306 // remove an accompaining .nfo file
307 static const std::string archiveExt = ".zip";
308 static const std::string infoExt = ".nfo";
309 std::string infoFileName = addon.file.substr(0, addon.file.length()-archiveExt.length()) + infoExt;
310 if (fs.exists(infoFileName)) {
311 log_debug << "deleting file \"" << infoFileName << "\"" << std::endl;