12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022 |
- // SuperTux - Add-on Manager
- // Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
- // 2014 Ingo Ruhnke <grumbel@gmail.com>
- //
- // This program is free software: you can redistribute it and/or modify
- // it under the terms of the GNU General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // This program is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU General Public License for more details.
- //
- // You should have received a copy of the GNU General Public License
- // along with this program. If not, see <http://www.gnu.org/licenses/>.
- #include "addon/addon_manager.hpp"
- #include <physfs.h>
- #include <fmt/format.h>
- #include <sstream>
- #include "addon/addon.hpp"
- #include "addon/md5.hpp"
- #include "gui/dialog.hpp"
- #include "physfs/util.hpp"
- #include "supertux/globals.hpp"
- #include "supertux/menu/addon_menu.hpp"
- #include "supertux/menu/menu_storage.hpp"
- #include "util/file_system.hpp"
- #include "util/gettext.hpp"
- #include "util/log.hpp"
- #include "util/reader.hpp"
- #include "util/reader_collection.hpp"
- #include "util/reader_document.hpp"
- #include "util/reader_mapping.hpp"
- #include "util/string_util.hpp"
- namespace {
- static const char* ADDON_INFO_PATH = "/addons/repository.nfo";
- static const char* ADDON_REPOSITORY_URL = "https://raw.githubusercontent.com/SuperTux/addons/master/index-0_6.nfo";
- MD5 md5_from_file(const std::string& filename)
- {
- // TODO: This does not work as expected for some files -- IFileStream seems to not always behave like an ifstream.
- //IFileStream ifs(installed_physfs_filename);
- //std::string md5 = MD5(ifs).hex_digest();
- MD5 md5;
- auto file = PHYSFS_openRead(filename.c_str());
- if (!file)
- {
- std::ostringstream out;
- out << "PHYSFS_openRead() failed: " << physfsutil::get_last_error();
- throw std::runtime_error(out.str());
- }
- else
- {
- while (true)
- {
- unsigned char buffer[1024];
- PHYSFS_sint64 len = PHYSFS_readBytes(file, buffer, sizeof(buffer));
- if (len <= 0) break;
- md5.update(buffer, static_cast<unsigned int>(len));
- }
- PHYSFS_close(file);
- return md5;
- }
- }
- MD5 md5_from_archive(const std::string& filename)
- {
- if (physfsutil::is_directory(filename)) {
- return MD5();
- } else {
- return md5_from_file(filename);
- }
- }
- static Addon& get_addon(const AddonManager::AddonMap& list, const AddonId& id,
- bool installed)
- {
- auto it = list.find(id);
- if (it != list.end())
- {
- return *(it->second);
- }
- else
- {
- std::string type = installed ? "installed" : "repository";
- throw std::runtime_error("Couldn't find " + type + " addon with id: " + id);
- }
- }
- static std::vector<AddonId> get_addons(const AddonManager::AddonMap& list)
- {
- // Use a map for storing sorted add-on titles with their respective IDs.
- std::map<std::string, AddonId> sorted_titles;
- for (const auto& [id, addon] : list)
- {
- sorted_titles.insert({addon->get_title(), id});
- }
- std::vector<AddonId> results;
- results.reserve(sorted_titles.size());
- std::transform(sorted_titles.begin(), sorted_titles.end(),
- std::back_inserter(results),
- [](const auto& title_and_id)
- {
- return title_and_id.second;
- });
- return results;
- }
- static PHYSFS_EnumerateCallbackResult add_to_dictionary_path(void *data, const char *origdir, const char *fname)
- {
- std::string full_path = FileSystem::join(origdir, fname);
- if (physfsutil::is_directory(full_path))
- {
- log_debug << "Adding \"" << full_path << "\" to dictionary search path" << std::endl;
- // We want translations from add-ons to have precedence.
- g_dictionary_manager->add_directory(full_path, true);
- }
- return PHYSFS_ENUM_OK;
- }
- static PHYSFS_EnumerateCallbackResult remove_from_dictionary_path(void *data, const char *origdir, const char *fname)
- {
- std::string full_path = FileSystem::join(origdir, fname);
- if (physfsutil::is_directory(full_path))
- {
- g_dictionary_manager->remove_directory(full_path);
- }
- return PHYSFS_ENUM_OK;
- }
- } // namespace
- AddonManager::AddonManager(const std::string& addon_directory,
- std::vector<Config::Addon>& addon_config) :
- m_downloader(),
- m_addon_directory(addon_directory),
- m_cache_directory(FileSystem::join(m_addon_directory, "cache")),
- m_screenshots_cache_directory(FileSystem::join(m_cache_directory, "screenshots")),
- m_repository_url(ADDON_REPOSITORY_URL),
- m_addon_config(addon_config),
- m_installed_addons(),
- m_repository_addons(),
- m_initialized(false),
- m_has_been_updated(false),
- m_transfer_statuses(new TransferStatusList)
- {
- if (!PHYSFS_mkdir(m_addon_directory.c_str()))
- {
- std::ostringstream msg;
- msg << "Couldn't create directory for addons '"
- << m_addon_directory << "': " << physfsutil::get_last_error();
- throw std::runtime_error(msg.str());
- }
- add_installed_addons();
- // FIXME: We should also restore the order here.
- for (auto& addon : m_addon_config)
- {
- if (addon.enabled)
- {
- try
- {
- enable_addon(addon.id);
- }
- catch(const std::exception& err)
- {
- log_warning << "Failed to enable addon '" << addon.id << "' from config: " << err.what() << std::endl;
- }
- }
- }
- if (PHYSFS_exists(ADDON_INFO_PATH))
- {
- try
- {
- m_repository_addons = parse_addon_infos(ADDON_INFO_PATH);
- }
- catch(const std::exception& err)
- {
- log_warning << "parsing repository.nfo failed: " << err.what() << std::endl;
- }
- }
- else
- {
- log_info << "repository.nfo doesn't exist, not loading" << std::endl;
- }
- if (!g_config->repository_url.empty() &&
- g_config->repository_url != m_repository_url)
- {
- m_repository_url = g_config->repository_url;
- }
- // Create the add-on cache directory, if it doesn't exist.
- if (!PHYSFS_exists(m_cache_directory.c_str()))
- {
- PHYSFS_mkdir(m_cache_directory.c_str());
- }
- else
- {
- empty_cache_directory();
- }
- m_initialized = true;
- }
- AddonManager::~AddonManager()
- {
- // Sync enabled/disabled add-ons into the config for saving.
- m_addon_config.clear();
- for (const auto& [id, addon] : m_installed_addons)
- {
- m_addon_config.push_back({id, addon->is_enabled()});
- }
- // Delete the add-on cache directory, if it exists.
- physfsutil::remove_with_content(m_cache_directory);
- }
- void
- AddonManager::empty_cache_directory()
- {
- physfsutil::remove_content(m_cache_directory);
- }
- Addon&
- AddonManager::get_repository_addon(const AddonId& id) const
- {
- return get_addon(m_repository_addons, id, false);
- }
- Addon&
- AddonManager::get_installed_addon(const AddonId& id) const
- {
- return get_addon(m_installed_addons, id, true);
- }
- std::vector<AddonId>
- AddonManager::get_repository_addons() const
- {
- return get_addons(m_repository_addons);
- }
- std::vector<AddonId>
- AddonManager::get_installed_addons() const
- {
- return get_addons(m_installed_addons);
- }
- bool
- AddonManager::has_online_support() const
- {
- return true;
- }
- bool
- AddonManager::has_been_updated() const
- {
- return m_has_been_updated;
- }
- TransferStatusPtr
- AddonManager::request_check_online()
- {
- empty_cache_directory();
- TransferStatusPtr status = m_downloader.request_download(m_repository_url, ADDON_INFO_PATH);
- status->then(
- [this](bool success)
- {
- if (success)
- {
- m_repository_addons = parse_addon_infos(ADDON_INFO_PATH);
- m_has_been_updated = true;
- }
- });
- return status;
- }
- void
- AddonManager::check_online()
- {
- empty_cache_directory();
- m_downloader.download(m_repository_url, ADDON_INFO_PATH);
- m_repository_addons = parse_addon_infos(ADDON_INFO_PATH);
- m_has_been_updated = true;
- }
- TransferStatusListPtr
- AddonManager::request_install_addon(const AddonId& addon_id)
- {
- // Remove add-on if it already exists.
- auto it = m_installed_addons.find(addon_id);
- if (it != m_installed_addons.end())
- {
- log_debug << "Reinstalling add-on " << addon_id << std::endl;
- if (it->second->is_enabled())
- {
- disable_addon(it->first);
- }
- m_installed_addons.erase(it);
- }
- else
- {
- log_debug << "Installing add-on " << addon_id << std::endl;
- }
- auto& addon = get_repository_addon(addon_id);
- std::string install_filename = FileSystem::join(m_addon_directory, addon.get_filename());
- // Install add-on dependencies, if any.
- request_install_addon_dependencies(addon);
- // Install the add-on.
- TransferStatusPtr status = m_downloader.request_download(addon.get_url(), install_filename);
- status->then(
- [this, install_filename, addon_id](bool success)
- {
- if (success)
- {
- // Complete the add-on installation.
- Addon& repository_addon = get_repository_addon(addon_id);
- MD5 md5 = md5_from_file(install_filename);
- if (repository_addon.get_md5() != md5.hex_digest())
- {
- if (PHYSFS_delete(install_filename.c_str()) == 0)
- {
- log_warning << "PHYSFS_delete failed: " << physfsutil::get_last_error() << std::endl;
- }
- throw std::runtime_error("Downloading Add-on failed: MD5 checksums differ");
- }
- else
- {
- const char* realdir = PHYSFS_getRealDir(install_filename.c_str());
- if (!realdir)
- {
- throw std::runtime_error("PHYSFS_getRealDir failed: " + install_filename);
- }
- add_installed_archive(install_filename, md5.hex_digest());
- // Attempt to enable the add-on.
- try
- {
- enable_addon(repository_addon.get_id());
- }
- catch (const std::exception& err)
- {
- log_warning << "Enabling add-on failed: " << err.what() << std::endl;
- }
- }
- }
- });
- m_transfer_statuses->push(status);
- return m_transfer_statuses;
- }
- TransferStatusListPtr
- AddonManager::request_install_addon_dependencies(const Addon& addon)
- {
- for (const std::string& id : addon.get_dependencies())
- {
- if (is_addon_installed(id))
- continue; // Don't attempt to install add-ons that are already installed.
- try
- {
- get_repository_addon(id);
- }
- catch (...)
- {
- continue; // Don't attempt to install add-ons that are not available.
- }
- request_install_addon(id);
- }
- return m_transfer_statuses;
- }
- TransferStatusListPtr
- AddonManager::request_install_addon_dependencies(const AddonId& addon_id)
- {
- return request_install_addon_dependencies(get_repository_addon(addon_id));
- }
- void
- AddonManager::install_addon(const AddonId& addon_id)
- {
- { // remove addon if it already exists.
- auto it = m_installed_addons.find(addon_id);
- if (it != m_installed_addons.end())
- {
- log_debug << "Reinstalling add-on " << addon_id << std::endl;
- if (it->second->is_enabled())
- {
- disable_addon(it->first);
- }
- m_installed_addons.erase(it);
- }
- else
- {
- log_debug << "Installing add-on " << addon_id << std::endl;
- }
- }
- auto& repository_addon = get_repository_addon(addon_id);
- std::string install_filename = FileSystem::join(m_addon_directory, repository_addon.get_filename());
- m_downloader.download(repository_addon.get_url(), install_filename);
- MD5 md5 = md5_from_file(install_filename);
- if (repository_addon.get_md5() != md5.hex_digest())
- {
- if (PHYSFS_delete(install_filename.c_str()) == 0)
- {
- log_warning << "PHYSFS_delete failed: " << physfsutil::get_last_error() << std::endl;
- }
- throw std::runtime_error("Downloading Add-on failed: MD5 checksums differ");
- }
- else
- {
- const char* realdir = PHYSFS_getRealDir(install_filename.c_str());
- if (!realdir)
- {
- throw std::runtime_error("PHYSFS_getRealDir failed: " + install_filename);
- }
- else
- {
- add_installed_archive(install_filename, md5.hex_digest());
- }
- }
- }
- void
- AddonManager::install_addon_from_local_file(const std::string& filename)
- {
- const std::string& source_filename = FileSystem::basename(filename);
- if(!StringUtil::has_suffix(source_filename, ".zip"))
- return;
- const std::string& target_directory = FileSystem::join(PHYSFS_getRealDir(m_addon_directory.c_str()), m_addon_directory);
- const std::string& target_filename = FileSystem::join(target_directory, source_filename);
- const std::string& physfs_target_filename = FileSystem::join(m_addon_directory, source_filename);
- FileSystem::copy(filename, target_filename);
- MD5 target_md5 = md5_from_file(physfs_target_filename);
- add_installed_archive(physfs_target_filename, target_md5.hex_digest(), true);
- }
- void
- AddonManager::uninstall_addon(const AddonId& addon_id)
- {
- log_debug << "Uninstalling add-on " << addon_id << std::endl;
- auto& addon = get_installed_addon(addon_id);
- if (addon.is_enabled())
- {
- disable_addon(addon_id);
- }
- log_debug << "Deleting file \"" << addon.get_install_filename() << "\"" << std::endl;
- const auto it = m_installed_addons.find(addon.get_id());
- if (it != m_installed_addons.end())
- {
- if (PHYSFS_delete(FileSystem::join(m_addon_directory, addon.get_filename()).c_str()) == 0)
- {
- throw std::runtime_error(_("Error deleting addon .zip file: \"PHYSFS_delete\" failed: ") + std::string(physfsutil::get_last_error()));
- }
- m_installed_addons.erase(it);
- }
- else
- {
- throw std::runtime_error("Error uninstalling add-on: Addon with id " + addon_id + " not found.");
- }
- }
- TransferStatusListPtr
- AddonManager::request_download_addon_screenshots(const AddonId& addon_id)
- {
- // Create the add-on screenshots cache directory, if it doesn't exist.
- if (!PHYSFS_exists(m_screenshots_cache_directory.c_str()))
- {
- PHYSFS_mkdir(m_screenshots_cache_directory.c_str());
- }
- const auto& screenshots = get_repository_addon(addon_id).get_screenshots();
- for (size_t i = 0; i < screenshots.size(); i++)
- {
- const std::string filename = addon_id + "_" + std::to_string(i + 1) + FileSystem::extension(screenshots[i]);
- const std::string filepath = FileSystem::join(m_screenshots_cache_directory, filename);
- if (PHYSFS_exists(filepath.c_str())) continue; // Do not re-download existing screenshots.
- TransferStatusPtr status;
- try
- {
- status = m_downloader.request_download(screenshots[i], filepath);
- }
- catch (std::exception& err)
- {
- log_warning << "Error downloading add-on screenshot from URL '" << screenshots[i]
- << "' to file '" << filename << "': " << err.what() << std::endl;
- continue;
- }
- m_transfer_statuses->push(status);
- }
- return m_transfer_statuses;
- }
- std::vector<std::string>
- AddonManager::get_local_addon_screenshots(const AddonId& addon_id)
- {
- std::vector<std::string> screenshots;
- physfsutil::enumerate_files(m_screenshots_cache_directory, [&screenshots, &addon_id, this](const std::string& filename) {
- // Push any files from the cache directory, starting with the ID of the add-on.
- if (StringUtil::starts_with(filename, addon_id))
- {
- screenshots.push_back(FileSystem::join(m_screenshots_cache_directory, filename));
- }
- });
- return screenshots;
- }
- void
- AddonManager::enable_addon(const AddonId& addon_id)
- {
- log_debug << "enabling addon " << addon_id << std::endl;
- auto& addon = get_installed_addon(addon_id);
- if (addon.is_enabled())
- {
- throw std::runtime_error("Tried enabling already enabled add-on.");
- }
- else
- {
- if (addon.get_type() == Addon::RESOURCEPACK)
- {
- for (const auto& [id, installed_addon] : m_installed_addons)
- {
- if (installed_addon->get_type() == Addon::RESOURCEPACK &&
- installed_addon->is_enabled())
- {
- throw std::runtime_error(_("Only one resource pack is allowed to be enabled at a time."));
- }
- }
- }
- std::string mountpoint;
- switch (addon.get_format()) {
- case Addon::ORIGINAL:
- mountpoint = "";
- break;
- default:
- mountpoint = "custom/" + addon_id;
- break;
- }
- // Only mount resource packs on startup (AddonManager initialization).
- if (addon.get_type() == Addon::RESOURCEPACK && m_initialized)
- {
- addon.set_enabled(true);
- return;
- }
- log_debug << "Adding archive \"" << addon.get_install_filename() << "\" to search path" << std::endl;
- if (PHYSFS_mount(addon.get_install_filename().c_str(), mountpoint.c_str(), !addon.overrides_data()) == 0)
- {
- std::stringstream err;
- err << "Could not add " << addon.get_install_filename() << " to search path: "
- << physfsutil::get_last_error() << std::endl;
- throw std::runtime_error(err.str());
- }
- else
- {
- if (addon.get_type() == Addon::LANGUAGEPACK)
- {
- PHYSFS_enumerate(addon.get_id().c_str(), add_to_dictionary_path, nullptr);
- }
- addon.set_enabled(true);
- }
- }
- }
- void
- AddonManager::disable_addon(const AddonId& addon_id)
- {
- log_debug << "disabling addon " << addon_id << std::endl;
- auto& addon = get_installed_addon(addon_id);
- if (!addon.is_enabled())
- {
- throw std::runtime_error("Tried disabling already disabled add-on.");
- }
- else
- {
- // Don't unmount resource packs. Disabled resource packs will not be mounted on next startup.
- if (addon.get_type() == Addon::RESOURCEPACK)
- {
- addon.set_enabled(false);
- return;
- }
- log_debug << "Removing archive \"" << addon.get_install_filename() << "\" from search path" << std::endl;
- if (PHYSFS_unmount(addon.get_install_filename().c_str()) == 0)
- {
- std::stringstream err;
- err << "Could not remove " << addon.get_install_filename() << " from search path: "
- << physfsutil::get_last_error() << std::endl;
- throw std::runtime_error(err.str());
- }
- else
- {
- if (addon.get_type() == Addon::LANGUAGEPACK)
- {
- PHYSFS_enumerate(addon.get_id().c_str(), remove_from_dictionary_path, nullptr);
- }
- addon.set_enabled(false);
- }
- }
- }
- bool
- AddonManager::is_old_enabled_addon(const std::unique_ptr<Addon>& addon) const
- {
- return addon->get_format() == Addon::ORIGINAL &&
- addon->get_type() != Addon::LANGUAGEPACK &&
- addon->is_enabled();
- }
- bool
- AddonManager::is_old_addon_enabled() const {
- auto it = std::find_if(m_installed_addons.begin(), m_installed_addons.end(),
- [this](const auto& addon)
- {
- return is_old_enabled_addon(addon.second);
- });
- return it != m_installed_addons.end();
- }
- void
- AddonManager::disable_old_addons()
- {
- for (auto& [id, addon] : m_installed_addons) {
- if (is_old_enabled_addon(addon)) {
- disable_addon(id);
- }
- }
- }
- void
- AddonManager::mount_old_addons()
- {
- std::string mountpoint;
- for (auto& [id, addon] : m_installed_addons) {
- if (is_old_enabled_addon(addon)) {
- if (PHYSFS_mount(addon->get_install_filename().c_str(), mountpoint.c_str(), !addon->overrides_data()) == 0)
- {
- log_warning << "Could not add " << addon->get_install_filename() << " to search path: "
- << physfsutil::get_last_error() << std::endl;
- }
- }
- }
- }
- void
- AddonManager::unmount_old_addons()
- {
- for (auto& [id, addon] : m_installed_addons) {
- if (is_old_enabled_addon(addon)) {
- if (PHYSFS_unmount(addon->get_install_filename().c_str()) == 0)
- {
- log_warning << "Could not remove " << addon->get_install_filename() << " from search path: "
- << physfsutil::get_last_error() << std::endl;
- }
- }
- }
- }
- bool
- AddonManager::is_from_old_addon(const std::string& filename) const
- {
- std::string real_path = PHYSFS_getRealDir(filename.c_str());
- for (auto& [id, addon] : m_installed_addons) {
- if (is_old_enabled_addon(addon) &&
- addon->get_install_filename() == real_path) {
- return true;
- }
- }
- return false;
- }
- bool
- AddonManager::is_addon_installed(const std::string& id) const
- {
- const auto installed_addons = get_installed_addons();
- return std::any_of(installed_addons.begin(), installed_addons.end(),
- [id] (const auto& installed_addon) {
- return installed_addon == id;
- });
- }
- std::vector<AddonId>
- AddonManager::get_depending_addons(const std::string& id) const
- {
- std::vector<AddonId> addons;
- for (auto& [addon_id, addon] : m_installed_addons)
- {
- const auto& dependencies = addon->get_dependencies();
- if (std::find(dependencies.begin(), dependencies.end(), addon_id) != dependencies.end())
- addons.push_back(addon_id);
- }
- return addons;
- }
- std::vector<std::string>
- AddonManager::scan_for_archives() const
- {
- std::vector<std::string> archives;
- // Search for archives and add them to the search path.
- physfsutil::enumerate_files(m_addon_directory, [this, &archives](const std::string& filename) {
- const std::string fullpath = FileSystem::join(m_addon_directory, filename);
- if (physfsutil::is_directory(fullpath))
- {
- // ignore dot files (e.g. '.git/'), as well as the addon cache directory.
- if (filename[0] != '.' && fullpath != m_cache_directory) {
- archives.push_back(fullpath);
- }
- }
- else
- {
- if (StringUtil::has_suffix(StringUtil::tolower(filename), ".zip")) {
- if (PHYSFS_exists(fullpath.c_str())) {
- archives.push_back(fullpath);
- }
- }
- }
- });
- return archives;
- }
- std::string
- AddonManager::scan_for_info(const std::string& archive_os_path) const
- {
- std::string nfoFilename = "";
- physfsutil::enumerate_files("/", [archive_os_path, &nfoFilename](const std::string& file) {
- if (StringUtil::has_suffix(file, ".nfo"))
- {
- std::string nfo_filename = FileSystem::join("/", file);
- // Make sure it's in the current archive_os_path.
- const char* realdir = PHYSFS_getRealDir(nfo_filename.c_str());
- if (!realdir)
- {
- log_warning << "PHYSFS_getRealDir() failed for " << nfo_filename << ": " << physfsutil::get_last_error() << std::endl;
- }
- else
- {
- if (realdir == archive_os_path)
- {
- nfoFilename = nfo_filename;
- }
- }
- }
- });
- return nfoFilename;
- }
- void
- AddonManager::add_installed_archive(const std::string& archive, const std::string& md5, bool user_install)
- {
- const char* realdir = PHYSFS_getRealDir(archive.c_str());
- if (!realdir)
- {
- log_warning << "PHYSFS_getRealDir() failed for " << archive << ": "
- << physfsutil::get_last_error() << std::endl;
- }
- else
- {
- bool has_error = false;
- std::string os_path = FileSystem::join(realdir, archive);
- PHYSFS_mount(os_path.c_str(), nullptr, 1);
- std::string nfo_filename = scan_for_info(os_path);
- if (nfo_filename.empty())
- {
- log_warning << "Couldn't find .nfo file for " << os_path << std::endl;
- has_error = true;
- }
- else
- {
- try
- {
- std::unique_ptr<Addon> addon = Addon::parse(nfo_filename);
- addon->set_install_filename(os_path, md5);
- const auto& addon_id = addon->get_id();
- try
- {
- get_installed_addon(addon_id);
- if(user_install)
- {
- Dialog::show_message(fmt::format(_("Add-on {} by {} is already installed."),
- addon->get_title(), addon->get_author()));
- }
- }
- catch(...)
- {
- // Save add-on title and author on stack before std::move.
- const std::string addon_title = addon->get_title();
- const std::string addon_author = addon->get_author();
- m_installed_addons[addon_id] = std::move(addon);
- if(user_install)
- {
- try
- {
- enable_addon(addon_id);
- }
- catch(const std::exception& err)
- {
- log_warning << "Failed to enable add-on archive '" << addon_id << "': " << err.what() << std::endl;
- }
- Dialog::show_message(fmt::format(_("Add-on {} by {} successfully installed."),
- addon_title, addon_author));
- // If currently opened menu is add-ons menu refresh it.
- AddonMenu* addon_menu = dynamic_cast<AddonMenu*>(MenuManager::instance().current_menu());
- if (addon_menu)
- addon_menu->refresh();
- }
- }
- }
- catch (const std::runtime_error& e)
- {
- log_warning << "Could not load add-on info for " << archive << ": " << e.what() << std::endl;
- has_error = true;
- }
- }
- if(!user_install || has_error)
- {
- PHYSFS_unmount(os_path.c_str());
- }
- }
- }
- void
- AddonManager::add_installed_addons()
- {
- auto archives = scan_for_archives();
- for (const auto& archive : archives)
- {
- MD5 md5 = md5_from_archive(archive);
- add_installed_archive(archive, md5.hex_digest());
- }
- }
- AddonManager::AddonMap
- AddonManager::parse_addon_infos(const std::string& filename) const
- {
- AddonMap m_addons;
- try
- {
- register_translation_directory(filename);
- auto doc = ReaderDocument::from_file(filename);
- auto root = doc.get_root();
- if (root.get_name() != "supertux-addons")
- {
- throw std::runtime_error("Downloaded file is not an Add-on list");
- }
- else
- {
- auto addon_collection = root.get_collection();
- for (auto const& addon_node : addon_collection.get_objects())
- {
- if (addon_node.get_name() != "supertux-addoninfo")
- {
- log_warning << "Unknown token '" << addon_node.get_name() << "' in Add-on list" << std::endl;
- }
- else
- {
- try
- {
- std::unique_ptr<Addon> addon = Addon::parse(addon_node.get_mapping());
- m_addons[addon->get_id()] = std::move(addon);
- }
- catch(const std::exception& e)
- {
- log_warning << "Problem when reading Add-on entry: " << e.what() << std::endl;
- }
- }
- }
- return m_addons;
- }
- }
- catch(const std::exception& e)
- {
- std::stringstream msg;
- msg << "Problem when reading Add-on list: " << e.what();
- throw std::runtime_error(msg.str());
- }
- }
- void
- AddonManager::update()
- {
- m_downloader.update();
- }
- void
- AddonManager::check_for_langpack_updates()
- {
- const std::string& language = g_dictionary_manager->get_language().get_language();
- if (language == "en")
- return;
- try
- {
- check_online();
- try
- {
- const std::string& addon_id = "language-pack";
- log_debug << "Looking for language add-on with ID " << addon_id << "..." << std::endl;
- Addon& langpack = get_repository_addon(addon_id);
- try
- {
- auto& installed_langpack = get_installed_addon(addon_id);
- if (installed_langpack.get_md5() == langpack.get_md5() ||
- installed_langpack.get_version() > langpack.get_version())
- {
- log_debug << "Language add-on " << addon_id << " is already the latest version." << std::endl;
- return;
- }
- // Langpack update available. Let's install it!
- install_addon(addon_id);
- try
- {
- enable_addon(addon_id);
- }
- catch(const std::exception& err)
- {
- log_warning << "Failed to enable language pack '" << addon_id << "' after update: " << err.what() << std::endl;
- }
- }
- catch(const std::exception&)
- {
- log_debug << "Language addon " << addon_id << " is not installed. Installing..." << std::endl;
- install_addon(addon_id);
- try
- {
- enable_addon(addon_id);
- }
- catch(const std::exception& err)
- {
- log_warning << "Failed to enable language pack '" << addon_id << "' after install: " << err.what() << std::endl;
- }
- }
- }
- catch(std::exception&)
- {
- log_debug << "Language add-on for current locale not found." << std::endl;
- }
- }
- catch(...)
- {
- // If anything fails here, just silently ignore.
- }
- }
- #ifdef EMSCRIPTEN
- void
- AddonManager::onDownloadProgress(int id, int loaded, int total)
- {
- m_downloader.onDownloadProgress(id, loaded, total);
- }
- void
- AddonManager::onDownloadFinished(int id)
- {
- m_downloader.onDownloadFinished(id);
- }
- void
- AddonManager::onDownloadError(int id)
- {
- m_downloader.onDownloadError(id);
- }
- void
- AddonManager::onDownloadAborted(int id)
- {
- m_downloader.onDownloadAborted(id);
- }
- #endif
- /* EOF */
|