Added Add-ons for Add-on manager, so online checks are voluntary
[supertux.git] / src / addon_manager.cpp
1 //  $Id$
2 //
3 //  SuperTux - Add-on Manager
4 //  Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
5 //
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.
10 //
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.
15 //
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
19 //  02111-1307, USA.
20 //
21
22 #include <stdexcept>
23 #include <list>
24 #include <physfs.h>
25 #include <sys/stat.h>
26 #include <stdio.h>
27 #include "addon_manager.hpp"
28 #include "config.h"
29 #include "log.hpp"
30
31 #ifdef HAVE_LIBCURL
32 #include <curl/curl.h>
33 #include <curl/types.h>
34 #include <curl/easy.h>
35 #endif
36
37 #ifdef HAVE_LIBCURL
38 namespace {
39
40   size_t my_curl_string_append(void *ptr, size_t size, size_t nmemb, void *string_ptr)
41   {
42     std::string& s = *static_cast<std::string*>(string_ptr);
43     std::string buf(static_cast<char*>(ptr), size * nmemb);
44     s += buf;
45     log_debug << "read " << size * nmemb << " bytes of data..." << std::endl;
46     return size * nmemb;
47   }
48
49   size_t my_curl_physfs_write(void *ptr, size_t size, size_t nmemb, void *f_p)
50   {
51     PHYSFS_file* f = static_cast<PHYSFS_file*>(f_p);
52     PHYSFS_sint64 written = PHYSFS_write(f, ptr, size, nmemb);
53     log_debug << "read " << size * nmemb << " bytes of data..." << std::endl;
54     return size * written;
55   }
56
57 }
58 #endif
59
60 AddonManager&
61 AddonManager::get_instance()
62 {
63   static AddonManager instance;
64   return instance;
65 }
66
67 AddonManager::AddonManager()
68 {
69 #ifdef HAVE_LIBCURL
70   curl_global_init(CURL_GLOBAL_ALL);
71 #endif
72 }
73
74 AddonManager::~AddonManager()
75 {
76 #ifdef HAVE_LIBCURL
77   curl_global_cleanup();
78 #endif
79 }
80
81 std::vector<Addon>
82 AddonManager::get_installed_addons() const
83 {
84   std::vector<Addon> addons;
85
86   // iterate over complete search path (i.e. directories and archives)
87   char **i = PHYSFS_getSearchPath();
88   if (!i) throw std::runtime_error("Could not query physfs search path");
89   for (; *i != NULL; i++) {
90
91     // get filename of potential archive
92     std::string fileName = *i;
93
94     // make sure it's in the writeDir
95     static const std::string writeDir = PHYSFS_getWriteDir();
96     if (fileName.compare(0, writeDir.length(), writeDir) != 0) continue;
97
98     // make sure it looks like an archive
99     static const std::string archiveExt = ".zip";
100     if (fileName.compare(fileName.length()-archiveExt.length(), archiveExt.length(), archiveExt) != 0) continue;
101
102     // make sure it exists
103     struct stat stats;
104     if (stat(fileName.c_str(), &stats) != 0) continue;
105
106     // make sure it's an actual file
107     if (!S_ISREG(stats.st_mode)) continue;
108
109     // extract nice title
110     static const char* dirSep = PHYSFS_getDirSeparator();
111     std::string::size_type n = fileName.rfind(dirSep) + 1;
112     if (n == std::string::npos) n = 0;
113     std::string title = fileName.substr(n, fileName.length() - n - archiveExt.length());
114
115     Addon addon;
116     addon.title = title;
117     addon.fname = fileName;
118     addon.isInstalled = true;
119
120     addons.push_back(addon);
121   }
122
123   return addons;
124 }
125
126 std::vector<Addon>
127 AddonManager::get_available_addons() const
128 {
129   std::vector<Addon> addons;
130
131 #ifdef HAVE_LIBCURL
132
133   // FIXME: This URL is just for testing!
134   const char* baseUrl = "http://www.deltadevelopment.de/users/christoph/supertux/addons/";
135   std::string html = "";
136
137   CURL *curl_handle;
138   curl_handle = curl_easy_init();
139   curl_easy_setopt(curl_handle, CURLOPT_URL, baseUrl);
140   curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL");
141   curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, my_curl_string_append);
142   curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &html);
143   curl_easy_perform(curl_handle);
144   curl_easy_cleanup(curl_handle);
145
146   //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";
147   static const std::string startToken = "href=\"";
148   static const std::string endToken = "\"";
149
150   // extract urls: for each startToken found...
151   std::string::size_type n = 0;
152   while ((n = html.find(startToken)) != std::string::npos) {
153
154     // strip everything up to and including token
155     html.erase(0, n + startToken.length());
156
157     // find end token
158     std::string::size_type n2 = html.find(endToken);
159     if (n2 == std::string::npos) break;
160
161     // extract url: it's the string inbetween
162     std::string url = html.substr(0, n2);
163
164     // strip everything up to and including endToken
165     html.erase(0, n2 + endToken.length());
166
167     // make absolute url
168     url = std::string(baseUrl) + url;
169
170     // make sure url looks like it points to an archive
171     static const std::string archiveExt = ".zip";
172     if (url.compare(url.length()-archiveExt.length(), archiveExt.length(), archiveExt) != 0) continue;
173
174     // extract nice title
175     std::string::size_type n = url.rfind('/') + 1;
176     if (n == std::string::npos) n = 0;
177     std::string title = url.substr(n, url.length() - n - archiveExt.length());
178
179     // construct file name
180     std::string fname = url.substr(n);
181
182     // make sure it does not contain weird characters
183     if (fname.find_first_not_of("match.quiz-proxy_gwenblvdjfks0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != std::string::npos) continue;
184
185     Addon addon;
186     addon.title = title;
187     addon.fname = fname;
188     addon.url = url;
189     addon.isInstalled = false;
190
191     addons.push_back(addon);
192   }
193 #endif
194
195   return addons;
196 }
197
198
199 void
200 AddonManager::install(const Addon& addon)
201 {
202
203 #ifdef HAVE_LIBCURL
204
205   char* url = (char*)malloc(addon.url.length() + 1);
206   strncpy(url, addon.url.c_str(), addon.url.length() + 1);
207
208   PHYSFS_file* f = PHYSFS_openWrite(addon.fname.c_str());
209
210   log_debug << "Downloading \"" << url << "\"" << std::endl;
211
212   CURL *curl_handle;
213   curl_handle = curl_easy_init();
214   curl_easy_setopt(curl_handle, CURLOPT_URL, url);
215   curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL");
216   curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, my_curl_physfs_write);
217   curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, f);
218   curl_easy_perform(curl_handle);
219   curl_easy_cleanup(curl_handle);
220
221   PHYSFS_close(f);
222
223   free(url);
224
225   static const std::string writeDir = PHYSFS_getWriteDir();
226   static const std::string dirSep = PHYSFS_getDirSeparator();
227   std::string fullFilename = writeDir + dirSep + addon.fname;
228   log_debug << "Finished downloading \"" << fullFilename << "\"" << std::endl;
229   PHYSFS_addToSearchPath(fullFilename.c_str(), 1);
230 #else
231   (void) addon;
232 #endif
233
234 }
235
236 void
237 AddonManager::remove(const Addon& addon)
238 {
239   PHYSFS_removeFromSearchPath(addon.fname.c_str());
240   PHYSFS_delete(addon.fname.c_str());
241 }