Removed unneeded curl includes
[supertux.git] / src / addon / addon_manager.cpp
1 //  SuperTux - Add-on Manager
2 //  Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
3 //
4 //  This program is free software: you can redistribute it and/or modify
5 //  it under the terms of the GNU General Public License as published by
6 //  the Free Software Foundation, either version 3 of the License, or
7 //  (at your option) any later version.
8 //
9 //  This program is distributed in the hope that it will be useful,
10 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //  GNU General Public License for more details.
13 //
14 //  You should have received a copy of the GNU General Public License
15 //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 #include "addon/addon_manager.hpp"
18
19 #include <config.h>
20 #include <version.h>
21 #include <iostream>
22
23 #include <algorithm>
24 #include <memory>
25 #include <physfs.h>
26 #include <sstream>
27 #include <stdexcept>
28 #include <sys/stat.h>
29
30 #include "addon/addon.hpp"
31 #include "addon/addon_list.hpp"
32 #include "lisp/list_iterator.hpp"
33 #include "lisp/parser.hpp"
34 #include "util/file_system.hpp"
35 #include "util/log.hpp"
36 #include "util/reader.hpp"
37 #include "util/writer.hpp"
38
39 namespace {
40
41 const char* allowed_characters = "-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
42
43 } // namespace
44
45
46 AddonManager::AddonManager(const std::string& addon_directory,
47                            std::vector<std::string>& ignored_addon_filenames) :
48   m_downloader(),
49   m_addon_directory(addon_directory),
50   m_addons(),
51   m_ignored_addon_filenames(ignored_addon_filenames)
52 {
53 }
54
55 AddonManager::~AddonManager()
56 {
57 }
58
59 Addon&
60 AddonManager::get_addon(int id)
61 {
62   if (0 <= id && id < static_cast<int>(m_addons.size()))
63   {
64     return *m_addons[id];
65   }
66   else
67   {
68     throw std::runtime_error("AddonManager::get_addon(): id out of range: " + std::to_string(id));
69   }
70 }
71
72 const std::vector<std::unique_ptr<Addon> >&
73 AddonManager::get_addons() const
74 {
75   /*
76     for (std::vector<Addon>::iterator it = installed_addons.begin(); it != installed_addons.end(); ++it) {
77     Addon& addon = *it;
78     if (addon.md5.empty()) addon.md5 = calculate_md5(addon);
79     }
80   */
81   return m_addons;
82 }
83
84 bool
85 AddonManager::has_online_support() const
86 {
87 #ifdef HAVE_LIBCURL
88   return true;
89 #else
90   return false;
91 #endif
92 }
93
94 void
95 AddonManager::check_online()
96 {
97   const char* baseUrl = "http://addons.supertux.googlecode.com/git/index-0_3_5.nfo";
98   std::string addoninfos = m_downloader.download(baseUrl);
99
100   AddonList::parse(addoninfos);
101 }
102
103 void
104 AddonManager::install(Addon& addon)
105 {
106   if (addon.installed)
107   {
108     throw std::runtime_error("Tried installing installed Add-on");
109   }
110
111   // make sure the Add-on's file name does not contain weird characters
112   if (addon.suggested_filename.find_first_not_of(allowed_characters) != std::string::npos)
113   {
114     throw std::runtime_error("Add-on has unsafe file name (\""+addon.suggested_filename+"\")");
115   }
116
117   std::string filename = FileSystem::join(m_addon_directory, addon.suggested_filename);
118
119   // make sure its file doesn't already exist
120   if (PHYSFS_exists(filename.c_str()))
121   {
122     filename = FileSystem::join(m_addon_directory, addon.stored_md5 + "_" + addon.suggested_filename);
123     if (PHYSFS_exists(filename.c_str()))
124     {
125       throw std::runtime_error("Add-on of suggested filename already exists (\"" +
126                                addon.suggested_filename + "\", \"" + filename + "\")");
127     }
128   }
129
130   m_downloader.download(addon.http_url, filename);
131
132   addon.installed = true;
133   addon.installed_physfs_filename = filename;
134   std::string writeDir = PHYSFS_getWriteDir();
135   addon.installed_absolute_filename = FileSystem::join(writeDir, filename);
136   addon.loaded = false;
137
138   if (addon.get_md5() != addon.stored_md5)
139   {
140     addon.installed = false;
141     PHYSFS_delete(filename.c_str());
142     std::string why = "MD5 checksums differ";
143     throw std::runtime_error("Downloading Add-on failed: " + why);
144   }
145
146   log_debug << "Finished downloading \"" << addon.installed_absolute_filename << "\". Enabling Add-on." << std::endl;
147
148   enable(addon);
149 }
150
151 void
152 AddonManager::remove(Addon& addon)
153 {
154   if (!addon.installed)
155   {
156     throw std::runtime_error("Tried removing non-installed Add-on");
157   }
158   else if (addon.installed_physfs_filename.find_first_not_of(allowed_characters) != std::string::npos)
159   {
160     // make sure the Add-on's file name does not contain weird characters
161     throw std::runtime_error("Add-on has unsafe file name (\""+addon.installed_physfs_filename+"\")");
162   }
163   else
164   {
165     unload(addon);
166
167     log_debug << "deleting file \"" << addon.installed_absolute_filename << "\"" << std::endl;
168     PHYSFS_delete(addon.installed_absolute_filename.c_str());
169     addon.installed = false;
170
171     // FIXME: As we don't know anything more about it (e.g. where to get it), remove it from list of known Add-ons
172   }
173 }
174
175 void
176 AddonManager::disable(Addon& addon)
177 {
178   unload(addon);
179
180   std::string filename = addon.installed_physfs_filename;
181   if (std::find(m_ignored_addon_filenames.begin(), m_ignored_addon_filenames.end(),
182                 filename) == m_ignored_addon_filenames.end())
183   {
184     m_ignored_addon_filenames.push_back(filename);
185   }
186 }
187
188 void
189 AddonManager::enable(Addon& addon)
190 {
191   load(addon);
192
193   std::string filename = addon.installed_physfs_filename;
194   auto it = std::find(m_ignored_addon_filenames.begin(), m_ignored_addon_filenames.end(), filename);
195   if (it != m_ignored_addon_filenames.end())
196   {
197     m_ignored_addon_filenames.erase(it);
198   }
199 }
200
201 void
202 AddonManager::unload(Addon& addon)
203 {
204   if (!addon.installed)
205   {
206     throw std::runtime_error("Tried unloading non-installed Add-on");
207   }
208   else if (!addon.loaded)
209   {
210     // do nothing
211   }
212   else
213   {
214     log_debug << "Removing archive \"" << addon.installed_absolute_filename << "\" from search path" << std::endl;
215     if (PHYSFS_removeFromSearchPath(addon.installed_absolute_filename.c_str()) == 0) {
216       log_warning << "Could not remove " << addon.installed_absolute_filename << " from search path. Ignoring." << std::endl;
217       return;
218     }
219
220     addon.loaded = false;
221   }
222 }
223
224 void
225 AddonManager::load(Addon& addon)
226 {
227   if (!addon.installed)
228   {
229     throw std::runtime_error("Tried loading non-installed Add-on");
230   }
231   else if (addon.loaded)
232   {
233     // do nothing
234   }
235   else
236   {
237     log_debug << "Adding archive \"" << addon.installed_absolute_filename << "\" to search path" << std::endl;
238     if (PHYSFS_addToSearchPath(addon.installed_absolute_filename.c_str(), 0) == 0) {
239       log_warning << "Could not add " << addon.installed_absolute_filename << " to search path. Ignoring." << std::endl;
240       return;
241     }
242
243     addon.loaded = true;
244   }
245 }
246
247 void
248 AddonManager::load_addons()
249 {
250   PHYSFS_mkdir(m_addon_directory.c_str());
251
252   // unload all Addons and forget about them
253   for (auto& addon : m_addons)
254   {
255     if (addon->installed && addon->loaded)
256     {
257       unload(*addon);
258     }
259   }
260   m_addons.clear();
261
262   // Search for archives and add them to the search path
263   char** rc = PHYSFS_enumerateFiles(m_addon_directory.c_str());
264
265   for(char** i = rc; *i != 0; ++i)
266   {
267     // get filename of potential archive
268     std::string filename = *i;
269
270     std::cout << m_addon_directory << " -> " << filename << std::endl;
271
272     const std::string archiveDir = PHYSFS_getRealDir(filename.c_str());
273     std::string fullFilename = FileSystem::join(archiveDir, filename);
274
275     /*
276     // make sure it's in the writeDir
277     std::string writeDir = PHYSFS_getWriteDir();
278     if (filename.compare(0, writeDir.length(), writeDir) != 0) continue;
279     */
280
281     // make sure it looks like an archive
282     std::string archiveExt = ".zip";
283     if (fullFilename.compare(fullFilename.length() - archiveExt.length(),
284                              archiveExt.length(), archiveExt) != 0)
285     {
286       continue;
287     }
288
289     // make sure it exists
290     struct stat stats;
291     if (stat(fullFilename.c_str(), &stats) != 0) continue;
292
293     // make sure it's an actual file
294     if (!S_ISREG(stats.st_mode)) continue;
295
296     log_debug << "Found archive \"" << fullFilename << "\"" << std::endl;
297
298     // add archive to search path
299     PHYSFS_addToSearchPath(fullFilename.c_str(), 0);
300
301     // Search for infoFiles
302     std::string infoFileName = "";
303     char** rc2 = PHYSFS_enumerateFiles(m_addon_directory.c_str());
304     for(char** j = rc2; *j != 0; ++j)
305     {
306       // get filename of potential infoFile
307       std::string potentialInfoFileName = *j;
308
309       // make sure it looks like an infoFile
310       static const std::string infoExt = ".nfo";
311       if (potentialInfoFileName.length() <= infoExt.length())
312         continue;
313
314       if (potentialInfoFileName.compare(potentialInfoFileName.length()-infoExt.length(), infoExt.length(), infoExt) != 0)
315         continue;
316
317       // make sure it's in the current archive
318       std::string infoFileDir = PHYSFS_getRealDir(potentialInfoFileName.c_str());
319       if (infoFileDir == fullFilename)
320       {
321         // found infoFileName
322         infoFileName = potentialInfoFileName;
323         break;
324       }
325     }
326     PHYSFS_freeList(rc2);
327
328     // if we have an infoFile, it's an Addon
329     if (!infoFileName.empty())
330     {
331       try
332       {
333         std::unique_ptr<Addon> addon(new Addon(m_addons.size()));
334         addon->parse(infoFileName);
335         addon->installed = true;
336         addon->installed_physfs_filename = filename;
337         addon->installed_absolute_filename = fullFilename;
338         addon->loaded = true;
339
340         // check if the Addon is disabled
341         if (std::find(m_ignored_addon_filenames.begin(), m_ignored_addon_filenames.end(), filename) != m_ignored_addon_filenames.end())
342         {
343           unload(*addon);
344         }
345
346         m_addons.push_back(std::move(addon));
347       }
348       catch (const std::runtime_error& e)
349       {
350         log_warning << "Could not load add-on info for " << fullFilename << ", loading as unmanaged:" << e.what() << std::endl;
351       }
352     }
353   }
354
355   PHYSFS_freeList(rc);
356 }
357
358 /* EOF */