Add support for installing addons pack

This commit is contained in:
Benau 2019-11-30 00:36:35 +08:00
parent 07a43e0ca3
commit 19d008d0f8
6 changed files with 367 additions and 6 deletions

View File

@ -1,5 +1,5 @@
# Modify this file to change the last-modified date when you add/remove a file.
# This will then trigger a new cmake run automatically.
# This will then trigger a new cmake run automatically.
file(GLOB_RECURSE STK_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.hpp")
file(GLOB_RECURSE STK_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.cpp")
file(GLOB_RECURSE STK_SHADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "data/shaders/*")

View File

@ -447,7 +447,7 @@ void AddonsManager::loadInstalledAddons()
* found!
* \param id The id to search for.
*/
const Addon* AddonsManager::getAddon(const std::string &id) const
Addon* AddonsManager::getAddon(const std::string &id)
{
int i = getAddonIndex(id);
return (i<0) ? NULL : &(m_addons_list.getData()[i]);

View File

@ -63,7 +63,7 @@ public:
void init(const XMLNode *xml, bool force_refresh);
void initAddons(const XMLNode *xml);
void checkInstalledAddons();
const Addon* getAddon(const std::string &id) const;
Addon* getAddon(const std::string &id);
int getAddonIndex(const std::string &id) const;
bool install(const Addon &addon);
bool uninstall(const Addon &addon);

View File

@ -57,9 +57,6 @@ namespace Online
/** The POST parameters that will be send with the request. */
std::string m_parameters;
/** Contains a filename if the data should be saved into a file
* instead of being kept in in memory. Otherwise this is "". */
std::string m_filename;
/** Pointer to the curl data structure for this request. */
CURL *m_curl_session = NULL;
@ -72,6 +69,10 @@ namespace Online
struct curl_slist* m_http_header = NULL;
protected:
/** Contains a filename if the data should be saved into a file
* instead of being kept in in memory. Otherwise this is "". */
std::string m_filename;
bool m_disable_sending_log;
/* If true, it will not call curl_easy_setopt CURLOPT_POSTFIELDS so
* it's just a GET request. */

View File

@ -0,0 +1,308 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2019 SuperTuxKart-Team
//
// 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, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifndef SERVER_ONLY
#include "states_screens/dialogs/addons_pack.hpp"
#include "addons/addons_manager.hpp"
#include "addons/zip.hpp"
#include "io/file_manager.hpp"
#include "karts/kart_properties.hpp"
#include "karts/kart_properties_manager.hpp"
#include "online/http_request.hpp"
#include "race/grand_prix_manager.hpp"
#include "replay/replay_play.hpp"
#include "states_screens/addons_screen.hpp"
#include "states_screens/dialogs/message_dialog.hpp"
#include "states_screens/state_manager.hpp"
#include "tracks/track_manager.hpp"
#include "utils/string_utils.hpp"
#include "utils/translation.hpp"
using namespace GUIEngine;
using namespace Online;
using namespace irr::gui;
// ----------------------------------------------------------------------------
class AddonsPackRequest : public HTTPRequest
{
private:
bool m_extraction_error;
virtual void afterOperation()
{
Online::HTTPRequest::afterOperation();
if (isCancelled())
return;
m_extraction_error = !file_manager->fileExists(getFileName());
if (m_extraction_error)
return;
std::string tmp_extract = file_manager->getAddonsFile("tmp_extract");
file_manager->removeDirectory(tmp_extract);
file_manager->checkAndCreateDirectory(tmp_extract);
m_extraction_error =
!extract_zip(getFileName(), tmp_extract, true/*recursive*/);
file_manager->removeFile(getFileName());
}
public:
AddonsPackRequest(const std::string& url)
: HTTPRequest(StringUtils::getBasename(url), /*manage mem*/false,
/*priority*/5)
{
m_extraction_error = true;
if (url.find("https://") != std::string::npos ||
url.find("http://") != std::string::npos)
{
setURL(url);
setDownloadAssetsRequest(true);
}
else
m_filename.clear();
}
~AddonsPackRequest()
{
if (isCancelled())
{
const std::string& zip = getFileName();
const std::string zip_part = zip + ".part";
if (file_manager->fileExists(zip))
file_manager->removeFile(zip);
if (file_manager->fileExists(zip_part))
file_manager->removeFile(zip_part);
}
file_manager->removeDirectory(
file_manager->getAddonsFile("tmp_extract"));
}
bool hadError() const { return hadDownloadError() || m_extraction_error; }
}; // DownloadAssetsRequest
// ----------------------------------------------------------------------------
/** Creates a modal dialog with given percentage of screen width and height
*/
AddonsPack::AddonsPack(const std::string& url) : ModalDialog(0.8f, 0.8f)
{
loadFromFile("addons_loading.stkgui");
getWidget<IconButtonWidget>("install")->setVisible(false);
getWidget<LabelWidget>("size")->setVisible(false);
getWidget<BubbleWidget>("description")->setText(
StringUtils::utf8ToWide(url));
IconButtonWidget* icon = getWidget<IconButtonWidget>("icon");
icon->setImage(file_manager->getAsset(FileManager::GUI_ICON, "logo.png"),
IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE);
m_progress = getWidget<ProgressBarWidget>("progress");
m_progress->setValue(0);
m_progress->setVisible(true);
GUIEngine::RibbonWidget* actions_ribbon =
getWidget<GUIEngine::RibbonWidget>("actions");
actions_ribbon->setVisible(false);
m_download_request = new AddonsPackRequest(url);
m_download_request->queue();
} // AddonsPack
// ----------------------------------------------------------------------------
void AddonsPack::beforeAddingWidgets()
{
getWidget("uninstall")->setVisible(false);
} // beforeAddingWidgets
// ----------------------------------------------------------------------------
void AddonsPack::init()
{
getWidget("rating")->setVisible(false);
} // init
// ----------------------------------------------------------------------------
bool AddonsPack::onEscapePressed()
{
stopDownload();
ModalDialog::dismiss();
return true;
} // onEscapePressed
// ----------------------------------------------------------------------------
GUIEngine::EventPropagation AddonsPack::processEvent(const std::string& event_source)
{
GUIEngine::RibbonWidget* actions_ribbon =
getWidget<GUIEngine::RibbonWidget>("actions");
if (event_source == "actions")
{
const std::string& selection =
actions_ribbon->getSelectionIDString(PLAYER_ID_GAME_MASTER);
if (selection == "back")
{
stopDownload();
dismiss();
return GUIEngine::EVENT_BLOCK;
}
}
return GUIEngine::EVENT_LET;
} // processEvent
// ----------------------------------------------------------------------------
void AddonsPack::onUpdate(float delta)
{
if (m_download_request)
{
float progress = m_download_request->getProgress();
// Last 1% for unzipping
m_progress->setValue(progress * 99.0f);
if (progress < 0)
{
// Avoid displaying '-100%' in case of an error.
m_progress->setVisible(false);
m_download_request->setManageMemory(true);
dismiss();
new MessageDialog(_("Sorry, downloading the add-on failed"));
return;
}
else if (m_download_request->isDone())
{
// No sense to update state text, since it all
// happens before the GUI is refrehsed.
doInstall();
return;
}
} // if (m_progress->isVisible())
} // onUpdate
// ----------------------------------------------------------------------------
/** This function is called when the user click on 'Back', 'Cancel' or press
* escape.
**/
void AddonsPack::stopDownload()
{
// Cancel a download only if we are installing/upgrading one
// (and not uninstalling an installed one):
if (m_download_request)
{
// In case of a cancel we can't free the memory, since the
// request manager thread is potentially working on this request. So
// in order to avoid a memory leak, we let the request manager
// free the data. This is thread safe since freeing the data is done
// when the request manager handles the result queue - and this is
// done by the main thread (i.e. this thread).
m_download_request->setManageMemory(true);
m_download_request->cancel();
m_download_request = NULL;
}
} // startDownload
// ----------------------------------------------------------------------------
/** Called when the asynchronous download of the addon finished.
*/
void AddonsPack::doInstall()
{
core::stringw msg;
if (m_download_request->hadError())
{
// Reset the download buttons so user can redownload if needed
// I18N: Shown when there is download error for assets download
// in the first run
msg = _("Failed to download assets, check your storage space or internet connection and try again later.");
}
if (!msg.empty())
{
delete m_download_request;
dismiss();
new MessageDialog(msg);
}
else
{
AddonsPackRequest* request = m_download_request;
dismiss();
std::set<std::string> result;
std::string tmp_extract = file_manager->getAddonsFile("tmp_extract");
file_manager->listFiles(result, tmp_extract);
tmp_extract += "/";
bool track_installed = false;
for (auto& r : result)
{
if (r == ".." || r == ".")
continue;
std::string addon_id = "addon_";
addon_id += r;
// We assume the addons pack the user downloaded use the latest
// revision from the stk-addons (if exists)
if (file_manager->fileExists(tmp_extract + r + "/stkskin.xml"))
{
std::string skins = file_manager->getAddonsFile("skins");
file_manager->checkAndCreateDirectoryP(skins);
if (file_manager->isDirectory(skins + "/" + r))
file_manager->removeDirectory(skins + "/" + r);
file_manager->moveDirectoryInto(tmp_extract + r, skins);
// Skin is not supported in stk-addons atm
}
else if (file_manager->fileExists(tmp_extract + r + "/kart.xml"))
{
std::string karts = file_manager->getAddonsFile("karts");
file_manager->checkAndCreateDirectoryP(karts);
if (file_manager->isDirectory(karts + "/" + r))
{
const KartProperties* prop =
kart_properties_manager->getKart(addon_id);
// If the model already exist, first remove the old kart
if (prop)
kart_properties_manager->removeKart(addon_id);
file_manager->removeDirectory(karts + "/" + r);
}
if (file_manager->moveDirectoryInto(tmp_extract + r, karts))
{
kart_properties_manager->loadKart(karts + "/" + r);
Addon* addon = addons_manager->getAddon(addon_id);
if (addon)
addon->setInstalled(true);
}
}
else if (file_manager->fileExists(tmp_extract + r + "/track.xml"))
{
track_installed = true;
std::string tracks = file_manager->getAddonsFile("tracks");
file_manager->checkAndCreateDirectoryP(tracks);
if (file_manager->isDirectory(tracks + "/" + r))
file_manager->removeDirectory(tracks + "/" + r);
if (file_manager->moveDirectoryInto(tmp_extract + r, tracks))
{
Addon* addon = addons_manager->getAddon(addon_id);
if (addon)
addon->setInstalled(true);
}
}
}
if (track_installed)
{
track_manager->loadTrackList();
// Update the replay file list to use latest track pointer
ReplayPlay::get()->loadAllReplayFile();
delete grand_prix_manager;
grand_prix_manager = new GrandPrixManager();
grand_prix_manager->checkConsistency();
}
AddonsScreen* as = dynamic_cast<AddonsScreen*>(
GUIEngine::getCurrentScreen());
if (as)
as->loadList();
delete request;
}
} // doInstall
#endif

View File

@ -0,0 +1,52 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2019 SuperTuxKart-Team
//
// 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, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifndef HEADER_ADDONS_PACK_HPP
#define HEADER_ADDONS_PACK_HPP
#include "guiengine/widgets.hpp"
#include "guiengine/modaldialog.hpp"
#include "utils/cpp2011.hpp"
class AddonsPackRequest;
/**
* \ingroup states_screens
*/
class AddonsPack : public GUIEngine::ModalDialog
{
private:
GUIEngine::ProgressBarWidget *m_progress;
void stopDownload();
void doInstall();
/** A pointer to the download request, which gives access
* to the progress of a download. */
AddonsPackRequest* m_download_request;
public:
AddonsPack(const std::string& url);
virtual GUIEngine::EventPropagation processEvent(const std::string& event_source) OVERRIDE;
virtual void beforeAddingWidgets() OVERRIDE;
virtual void init() OVERRIDE;
void onUpdate(float delta) OVERRIDE;
virtual bool onEscapePressed() OVERRIDE;
}; // DownloadAssets
#endif