1 // SuperTux - Add-on Manager
2 // Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
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.
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.
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/>.
17 #include "addon/addon_manager.hpp"
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"
41 const char* allowed_characters = "-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
46 AddonManager::AddonManager(const std::string& addon_directory,
47 std::vector<std::string>& ignored_addon_filenames) :
49 m_addon_directory(addon_directory),
51 m_ignored_addon_filenames(ignored_addon_filenames)
55 AddonManager::~AddonManager()
60 AddonManager::get_addon(int id)
62 if (0 <= id && id < static_cast<int>(m_addons.size()))
68 throw std::runtime_error("AddonManager::get_addon(): id out of range: " + std::to_string(id));
72 const std::vector<std::unique_ptr<Addon> >&
73 AddonManager::get_addons() const
76 for (std::vector<Addon>::iterator it = installed_addons.begin(); it != installed_addons.end(); ++it) {
78 if (addon.md5.empty()) addon.md5 = calculate_md5(addon);
85 AddonManager::has_online_support() const
95 AddonManager::check_online()
97 const char* baseUrl = "http://addons.supertux.googlecode.com/git/index-0_3_5.nfo";
98 std::string addoninfos = m_downloader.download(baseUrl);
100 AddonList::parse(addoninfos);
104 AddonManager::install(Addon& addon)
108 throw std::runtime_error("Tried installing installed Add-on");
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)
114 throw std::runtime_error("Add-on has unsafe file name (\""+addon.suggested_filename+"\")");
117 std::string filename = FileSystem::join(m_addon_directory, addon.suggested_filename);
119 // make sure its file doesn't already exist
120 if (PHYSFS_exists(filename.c_str()))
122 filename = FileSystem::join(m_addon_directory, addon.stored_md5 + "_" + addon.suggested_filename);
123 if (PHYSFS_exists(filename.c_str()))
125 throw std::runtime_error("Add-on of suggested filename already exists (\"" +
126 addon.suggested_filename + "\", \"" + filename + "\")");
130 m_downloader.download(addon.http_url, filename);
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;
138 if (addon.get_md5() != addon.stored_md5)
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);
146 log_debug << "Finished downloading \"" << addon.installed_absolute_filename << "\". Enabling Add-on." << std::endl;
152 AddonManager::remove(Addon& addon)
154 if (!addon.installed)
156 throw std::runtime_error("Tried removing non-installed Add-on");
158 else if (addon.installed_physfs_filename.find_first_not_of(allowed_characters) != std::string::npos)
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+"\")");
167 log_debug << "deleting file \"" << addon.installed_absolute_filename << "\"" << std::endl;
168 PHYSFS_delete(addon.installed_absolute_filename.c_str());
169 addon.installed = false;
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
176 AddonManager::disable(Addon& addon)
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())
184 m_ignored_addon_filenames.push_back(filename);
189 AddonManager::enable(Addon& addon)
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())
197 m_ignored_addon_filenames.erase(it);
202 AddonManager::unload(Addon& addon)
204 if (!addon.installed)
206 throw std::runtime_error("Tried unloading non-installed Add-on");
208 else if (!addon.loaded)
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;
220 addon.loaded = false;
225 AddonManager::load(Addon& addon)
227 if (!addon.installed)
229 throw std::runtime_error("Tried loading non-installed Add-on");
231 else if (addon.loaded)
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;
248 AddonManager::load_addons()
250 PHYSFS_mkdir(m_addon_directory.c_str());
252 // unload all Addons and forget about them
253 for (auto& addon : m_addons)
255 if (addon->installed && addon->loaded)
262 // Search for archives and add them to the search path
263 char** rc = PHYSFS_enumerateFiles(m_addon_directory.c_str());
265 for(char** i = rc; *i != 0; ++i)
267 // get filename of potential archive
268 std::string filename = *i;
270 std::cout << m_addon_directory << " -> " << filename << std::endl;
272 const std::string archiveDir = PHYSFS_getRealDir(filename.c_str());
273 std::string fullFilename = FileSystem::join(archiveDir, filename);
276 // make sure it's in the writeDir
277 std::string writeDir = PHYSFS_getWriteDir();
278 if (filename.compare(0, writeDir.length(), writeDir) != 0) continue;
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)
289 // make sure it exists
291 if (stat(fullFilename.c_str(), &stats) != 0) continue;
293 // make sure it's an actual file
294 if (!S_ISREG(stats.st_mode)) continue;
296 log_debug << "Found archive \"" << fullFilename << "\"" << std::endl;
298 // add archive to search path
299 PHYSFS_addToSearchPath(fullFilename.c_str(), 0);
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)
306 // get filename of potential infoFile
307 std::string potentialInfoFileName = *j;
309 // make sure it looks like an infoFile
310 static const std::string infoExt = ".nfo";
311 if (potentialInfoFileName.length() <= infoExt.length())
314 if (potentialInfoFileName.compare(potentialInfoFileName.length()-infoExt.length(), infoExt.length(), infoExt) != 0)
317 // make sure it's in the current archive
318 std::string infoFileDir = PHYSFS_getRealDir(potentialInfoFileName.c_str());
319 if (infoFileDir == fullFilename)
321 // found infoFileName
322 infoFileName = potentialInfoFileName;
326 PHYSFS_freeList(rc2);
328 // if we have an infoFile, it's an Addon
329 if (!infoFileName.empty())
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;
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())
346 m_addons.push_back(std::move(addon));
348 catch (const std::runtime_error& e)
350 log_warning << "Could not load add-on info for " << fullFilename << ", loading as unmanaged:" << e.what() << std::endl;