Implement background download for addons pack

This commit is contained in:
Benau
2021-08-11 10:50:27 +08:00
parent e823852a21
commit 2dffb78679
6 changed files with 223 additions and 125 deletions

View File

@@ -2583,6 +2583,7 @@ int main(int argc, char *argv[])
if (STKHost::existHost())
STKHost::get()->shutdown();
ClientLobby::destroyBackgroundDownload();
cleanSuperTuxKart();
NetworkConfig::destroy();

View File

@@ -55,9 +55,12 @@
#include "network/server_config.hpp"
#include "network/stk_host.hpp"
#include "network/stk_peer.hpp"
#include "race/grand_prix_manager.hpp"
#include "replay/replay_play.hpp"
#include "online/online_profile.hpp"
#include "online/xml_request.hpp"
#include "states_screens/dialogs/addons_pack.hpp"
#include "states_screens/dialogs/message_dialog.hpp"
#include "states_screens/online/networking_lobby.hpp"
#include "states_screens/online/network_kart_selection.hpp"
#include "states_screens/online/tracks_screen.hpp"
@@ -77,6 +80,21 @@ extern "C"
}
#endif
// ============================================================================
std::thread ClientLobby::m_background_download;
std::shared_ptr<Online::HTTPRequest> ClientLobby::m_download_request;
//-----------------------------------------------------------------------------
void ClientLobby::destroyBackgroundDownload()
{
if (m_download_request)
{
m_download_request->cancel();
m_download_request = nullptr;
}
if (m_background_download.joinable())
m_background_download.join();
}
// ============================================================================
/** The protocol that manages starting a race with the server. It uses a
* finite state machine:
@@ -483,6 +501,17 @@ void ClientLobby::update(int ticks)
start.addUInt8(LobbyProtocol::LE_REQUEST_BEGIN);
STKHost::get()->sendToServer(&start, true);
}
if (m_background_download.joinable())
{
if (m_download_request && (m_download_request->isCancelled() ||
m_download_request->isDone()))
{
m_background_download.join();
if (m_download_request->isDone())
doInstallAddonsPack();
m_download_request = nullptr;
}
}
case SELECTING_ASSETS:
case RACING:
case EXITING:
@@ -1611,7 +1640,26 @@ void ClientLobby::handleClientCommand(const std::string& cmd)
auto argv = StringUtils::split(cmd, ' ');
if (argv.size() == 0)
return;
if (argv[0] == "installaddon" && argv.size() == 2)
if (argv[0] == "addondownloadprogress")
{
float progress = 0.0f;
if (m_download_request)
progress = m_download_request->getProgress();
core::stringw msg = L"Background download progress: ";
msg += progress;
NetworkingLobby::getInstance()->addMoreServerInfo(msg);
}
else if (argv[0] == "stopaddondownload")
{
if (m_download_request)
{
m_download_request->cancel();
m_download_request = nullptr;
}
if (m_background_download.joinable())
m_background_download.join();
}
else if (argv[0] == "installaddon" && argv.size() == 2)
AddonsPack::install(argv[1]);
else if (argv[0] == "uninstalladdon" && argv.size() == 2)
AddonsPack::uninstall(argv[1]);
@@ -1822,3 +1870,116 @@ void ClientLobby::updateAssetsToServer()
sendToServer(ns, /*reliable*/true);
delete ns;
} // updateAssetsToServer
// ----------------------------------------------------------------------------
void ClientLobby::downloadAddonsPack(std::shared_ptr<Online::HTTPRequest> r)
{
if (startedDownloadAddonsPack())
return;
m_download_request = r;
m_background_download = std::thread([r](){ r->executeNow(); });
} // downloadAddonsPack
// ----------------------------------------------------------------------------
/** Called when the asynchronous download of the addon finished.
*/
void ClientLobby::doInstallAddonsPack()
{
#ifndef SERVER_ONLY
core::stringw msg;
if (!m_download_request->isCancelled() &&
m_download_request->hadDownloadError())
{
// 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())
{
new MessageDialog(msg);
}
else
{
std::set<std::string> result;
std::string tmp_extract = file_manager->getAddonsFile("tmp_extract");
file_manager->listFiles(result, tmp_extract);
tmp_extract += "/";
bool addon_kart_installed = false;
bool addon_track_installed = false;
bool track_installed = false;
for (auto& r : result)
{
if (r == ".." || r == ".")
continue;
std::string addon_id = Addon::createAddonId(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_kart_installed = true;
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_track_installed = true;
addon->setInstalled(true);
}
}
}
}
if (addon_kart_installed || addon_track_installed)
addons_manager->saveInstalled();
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();
}
updateAssetsToServer();
}
#endif
} // doInstall

View File

@@ -37,6 +37,11 @@ enum HandicapLevel : uint8_t;
class BareNetworkString;
class Server;
namespace Online
{
class HTTPRequest;
}
struct LobbyPlayer
{
irr::core::stringw m_user_name;
@@ -132,6 +137,10 @@ private:
irr::core::stringw m_total_players;
static std::thread m_background_download;
static std::shared_ptr<Online::HTTPRequest> m_download_request;
void liveJoinAcknowledged(Event* event);
void handleKartInfo(Event* event);
void finishLiveJoin();
@@ -140,6 +149,7 @@ private:
std::shared_ptr<STKPeer> peer = nullptr,
bool* is_spectator = NULL) const;
void getKartsTracksNetworkString(BareNetworkString* ns);
void doInstallAddonsPack();
public:
ClientLobby(std::shared_ptr<Server> s);
virtual ~ClientLobby();
@@ -184,9 +194,13 @@ public:
const std::vector<float>& getRankingChanges() const
{ return m_ranking_changes; }
void handleClientCommand(const std::string& cmd);
void updateAssetsToServer();
ClientState getCurrentState() const { return m_state.load(); }
std::shared_ptr<Server> getJoinedServer() const { return m_server; }
static bool startedDownloadAddonsPack()
{ return m_background_download.joinable() || m_download_request; }
static void downloadAddonsPack(std::shared_ptr<Online::HTTPRequest> r);
static void destroyBackgroundDownload();
void updateAssetsToServer();
};
#endif // CLIENT_LOBBY_HPP

View File

@@ -112,7 +112,7 @@ namespace Online
// ------------------------------------------------------------------------
/** Returns true if there was an error downloading the file. */
bool hadDownloadError() const { return m_curl_code != CURLE_OK; }
virtual bool hadDownloadError() const { return m_curl_code != CURLE_OK; }
// ------------------------------------------------------------------------
void setDownloadAssetsRequest(bool val)
{ m_download_assets_request = val; }

View File

@@ -25,10 +25,9 @@
#include "io/file_manager.hpp"
#include "karts/kart_properties.hpp"
#include "karts/kart_properties_manager.hpp"
#include "guiengine/message_queue.hpp"
#include "network/protocols/client_lobby.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/addons_loading.hpp"
#include "states_screens/dialogs/message_dialog.hpp"
@@ -47,7 +46,8 @@ class AddonsPackRequest : public HTTPRequest
{
private:
bool m_extraction_error;
virtual void afterOperation()
bool m_background_download;
virtual void afterOperation() OVERRIDE
{
Online::HTTPRequest::afterOperation();
if (isCancelled())
@@ -61,12 +61,18 @@ private:
file_manager->checkAndCreateDirectory(tmp_extract);
m_extraction_error =
!extract_zip(getFileName(), tmp_extract, true/*recursive*/);
if (!m_extraction_error && m_background_download)
{
core::stringw msg = _("Background download completed.");
MessageQueue::add(MessageQueue::MT_GENERIC, msg);
}
}
public:
AddonsPackRequest(const std::string& url)
: HTTPRequest(StringUtils::getBasename(url), /*priority*/5)
{
m_extraction_error = true;
m_background_download = false;
if (url.find("https://") != std::string::npos ||
url.find("http://") != std::string::npos)
{
@@ -87,7 +93,9 @@ public:
file_manager->removeDirectory(
file_manager->getAddonsFile("tmp_extract"));
}
bool hadError() const { return hadDownloadError() || m_extraction_error; }
void backgroundDownload() { m_background_download = true; }
virtual bool hadDownloadError() const OVERRIDE
{ return HTTPRequest::hadDownloadError() || m_extraction_error; }
}; // DownloadAssetsRequest
// ----------------------------------------------------------------------------
@@ -113,14 +121,26 @@ AddonsPack::AddonsPack(const std::string& url) : ModalDialog(0.8f, 0.8f)
getWidget<GUIEngine::RibbonWidget>("actions");
actions_ribbon->setFocusForPlayer(PLAYER_ID_GAME_MASTER);
actions_ribbon->select("back", PLAYER_ID_GAME_MASTER);
getWidget("install")->setVisible(false);
icon = getWidget<IconButtonWidget>("install");
icon->setImage(file_manager->getAsset(FileManager::GUI_ICON,
"blue_plus.png"), IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE);
icon->setLabel(_("Background download"));
icon->setVisible(true);
getWidget("uninstall")->setVisible(false);
icon = getWidget<IconButtonWidget>("back");
icon->setImage(file_manager->getAsset(FileManager::GUI_ICON, "remove.png"),
IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE);
icon->setLabel(_("Cancel"));
m_download_request = std::make_shared<AddonsPackRequest>(url);
m_download_request->queue();
if (ClientLobby::startedDownloadAddonsPack())
{
core::stringw msg = _("Background download has already started.");
MessageQueue::add(MessageQueue::MT_ERROR, msg);
}
else
{
m_download_request = std::make_shared<AddonsPackRequest>(url);
ClientLobby::downloadAddonsPack(m_download_request);
}
} // AddonsPack
// ----------------------------------------------------------------------------
@@ -158,7 +178,15 @@ GUIEngine::EventPropagation AddonsPack::processEvent(const std::string& event_so
{
const std::string& selection =
actions_ribbon->getSelectionIDString(PLAYER_ID_GAME_MASTER);
if (selection == "back")
if (selection == "install")
{
if (m_download_request)
m_download_request->backgroundDownload();
m_download_request = nullptr;
dismiss();
return GUIEngine::EVENT_BLOCK;
}
else if (selection == "back")
{
dismiss();
return GUIEngine::EVENT_BLOCK;
@@ -192,14 +220,17 @@ void AddonsPack::onUpdate(float delta)
new MessageDialog(_("Sorry, downloading the add-on failed"));
return;
}
else if (m_download_request->isDone())
else if (m_download_request->isDone() ||
m_download_request->isCancelled())
{
// No sense to update state text, since it all
// happens before the GUI is refrehsed.
doInstall();
dismiss();
return;
}
} // if (m_progress->isVisible())
}
else
dismiss();
} // onUpdate
// ----------------------------------------------------------------------------
@@ -212,120 +243,12 @@ void AddonsPack::stopDownload()
// (and not uninstalling an installed one):
if (m_download_request)
{
m_download_request->cancel();
if (!m_download_request->isDone())
m_download_request->cancel();
m_download_request = nullptr;
}
} // 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())
{
dismiss();
new MessageDialog(msg);
}
else
{
std::shared_ptr<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 addon_kart_installed = false;
bool addon_track_installed = false;
bool track_installed = false;
for (auto& r : result)
{
if (r == ".." || r == ".")
continue;
std::string addon_id = Addon::createAddonId(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_kart_installed = true;
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_track_installed = true;
addon->setInstalled(true);
}
}
}
}
if (addon_kart_installed || addon_track_installed)
addons_manager->saveInstalled();
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();
if (auto cl = LobbyProtocol::get<ClientLobby>())
cl->updateAssetsToServer();
}
} // doInstall
// ----------------------------------------------------------------------------
void AddonsPack::install(const std::string& name)
{

View File

@@ -35,7 +35,6 @@ private:
GUIEngine::LabelWidget* m_size;
void stopDownload();
void doInstall();
/** A pointer to the download request, which gives access
* to the progress of a download. */