Added support for downloading the Windows and macOS packages via the application updater

This commit is contained in:
Leon Styhre 2023-07-31 19:25:54 +02:00
parent 3472c6f852
commit 4f3d66c45b
5 changed files with 202 additions and 78 deletions

View file

@ -403,7 +403,7 @@ void ApplicationUpdater::compareVersions()
mResults.append("release available: ").append(releaseType->version);
}
if (mPackage.name != "LinuxAppImage" && mPackage.name != "LinuxSteamDeckAppImage")
if (mPackageType == PackageType::UNKNOWN)
mResults.append("\nFor more information visit\n").append("https://es-de.org");
if (mPackage.message != "")

View file

@ -10,16 +10,16 @@
#include "guis/GuiApplicationUpdater.h"
#include "EmulationStation.h"
#include "guis/GuiTextEditKeyboardPopup.h"
#include "utils/PlatformUtil.h"
#if !defined(__NETBSD__)
#include <filesystem>
#endif
GuiApplicationUpdater::GuiApplicationUpdater()
: mRenderer {Renderer::getInstance()}
, mBackground {":/graphics/frame.svg"}
, mGrid {glm::ivec2 {4, 11}}
, mLinuxAppImage {false}
, mAbortDownload {false}
, mDownloading {false}
, mReadyToInstall {false}
@ -30,10 +30,13 @@ GuiApplicationUpdater::GuiApplicationUpdater()
addChild(&mBackground);
addChild(&mGrid);
mPackage = ApplicationUpdater::getInstance().getPackageInfo();
LOG(LogInfo) << "Starting Application Updater";
mPackage = ApplicationUpdater::getInstance().getPackageInfo();
mLinuxAppImage =
(mPackage.name == "LinuxAppImage" || mPackage.name == "LinuxSteamDeckAppImage");
setDownloadPath();
// Set up grid.
mTitle = std::make_shared<TextComponent>("APPLICATION UPDATER", Font::get(FONT_SIZE_LARGE),
mMenuColorTitle, ALIGN_CENTER);
@ -44,17 +47,28 @@ GuiApplicationUpdater::GuiApplicationUpdater()
"INSTALLATION STEPS:", Font::get(FONT_SIZE_MINI), mMenuColorPrimary, ALIGN_LEFT);
mGrid.setEntry(mStatusHeader, glm::ivec2 {1, 1}, false, true, glm::ivec2 {2, 1});
mProcessStep1 = std::make_shared<TextComponent>(
"DOWNLOAD NEW RELEASE", Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary, ALIGN_LEFT);
const std::string step1Text {mLinuxAppImage ? "DOWNLOAD NEW RELEASE" :
"DOWNLOAD NEW RELEASE TO THIS DIRECTORY:"};
mProcessStep1 = std::make_shared<TextComponent>(step1Text, Font::get(FONT_SIZE_MEDIUM),
mMenuColorPrimary, ALIGN_LEFT);
mGrid.setEntry(mProcessStep1, glm::ivec2 {1, 2}, false, true, glm::ivec2 {2, 1});
mProcessStep2 = std::make_shared<TextComponent>("INSTALL PACKAGE", Font::get(FONT_SIZE_MEDIUM),
#if defined(_WIN64)
const std::string step2Text {Utils::String::replace(
Utils::FileSystem::getParent(mDownloadPackageFilename), , "/", "\\")};
#else
const std::string step2Text {mLinuxAppImage ?
"INSTALL PACKAGE" :
Utils::FileSystem::getParent(mDownloadPackageFilename)};
#endif
mProcessStep2 = std::make_shared<TextComponent>(step2Text, Font::get(FONT_SIZE_MEDIUM),
mMenuColorPrimary, ALIGN_LEFT);
mGrid.setEntry(mProcessStep2, glm::ivec2 {1, 3}, false, true, glm::ivec2 {2, 1});
mProcessStep3 =
std::make_shared<TextComponent>("QUIT AND MANUALLY RESTART ES-DE",
Font::get(FONT_SIZE_MEDIUM), mMenuColorPrimary, ALIGN_LEFT);
const std::string step3Text {mLinuxAppImage ? "QUIT AND MANUALLY RESTART ES-DE" :
"QUIT AND MANUALLY UPGRADE ES-DE"};
mProcessStep3 = std::make_shared<TextComponent>(step3Text, Font::get(FONT_SIZE_MEDIUM),
mMenuColorPrimary, ALIGN_LEFT);
mGrid.setEntry(mProcessStep3, glm::ivec2 {1, 4}, false, true, glm::ivec2 {2, 1});
mStatusMessageHeader = std::make_shared<TextComponent>(
@ -74,6 +88,13 @@ GuiApplicationUpdater::GuiApplicationUpdater()
mButton1 = std::make_shared<ButtonComponent>("DOWNLOAD", "download new release", [this]() {
if (!mDownloading) {
if (!mLinuxAppImage) {
if (!Utils::FileSystem::exists(
Utils::FileSystem::getParent(mDownloadPackageFilename))) {
mMessage = "Download directory does not exist";
return;
}
}
mMessage = "";
mStatusMessage->setText(mMessage);
mDownloading = true;
@ -87,7 +108,45 @@ GuiApplicationUpdater::GuiApplicationUpdater()
buttons.push_back(mButton1);
mButton2 = std::make_shared<ButtonComponent>("CANCEL", "cancel", [this]() {
if (!mLinuxAppImage) {
mButton2 = std::make_shared<ButtonComponent>(
"CHANGE DIRECTORY", "change download directory", [this]() {
if (mDownloading || mHasDownloaded)
return;
std::string currentDownloadDirectory {
Utils::FileSystem::getParent(mDownloadPackageFilename)};
mWindow->pushGui(new GuiTextEditKeyboardPopup(
getHelpStyle(), 0.0f, "ENTER DOWNLOAD DIRECTORY", currentDownloadDirectory,
[this, currentDownloadDirectory](std::string newDownloadDirectory) {
if (currentDownloadDirectory != newDownloadDirectory) {
newDownloadDirectory.erase(
// Remove trailing / and \ characters.
std::find_if(newDownloadDirectory.rbegin(),
newDownloadDirectory.rend(),
[](char c) { return c != '/' && c != '\\'; })
.base(),
newDownloadDirectory.end());
#if defined(_WIN64)
newDownloadDirectory =
Utils::String::replace(newDownloadDirectory, "/", "\\");
#else
newDownloadDirectory = Utils::String::replace(newDownloadDirectory, "\\", "/");
#endif
Settings::getInstance()->setString(
"ApplicationUpdaterDownloadDirectory",
Utils::String::trim(newDownloadDirectory));
Settings::getInstance()->saveFile();
setDownloadPath();
mProcessStep2->setValue(
Utils::FileSystem::getParent(mDownloadPackageFilename));
}
},
false));
});
buttons.push_back(mButton2);
}
mButton3 = std::make_shared<ButtonComponent>("CANCEL", "cancel", [this]() {
mAbortDownload = true;
if (mDownloading) {
mWindow->pushGui(
@ -108,7 +167,7 @@ GuiApplicationUpdater::GuiApplicationUpdater()
delete this;
});
buttons.push_back(mButton2);
buttons.push_back(mButton3);
mButtons = MenuComponent::makeButtonGrid(buttons);
mGrid.setEntry(mButtons, glm::ivec2 {0, 10}, true, false, glm::ivec2 {4, 1},
@ -144,6 +203,30 @@ GuiApplicationUpdater::~GuiApplicationUpdater()
mThread->join();
}
void GuiApplicationUpdater::setDownloadPath()
{
if (mLinuxAppImage) {
mDownloadPackageFilename = Utils::FileSystem::getParent(Utils::FileSystem::getEsBinary()) +
"/" + mPackage.filename + "_" + mPackage.version;
}
else {
#if defined(_WIN64)
const std::string downloadDirectory {Utils::String::replace(
Settings::getInstance()->getString("ApplicationUpdaterDownloadDirectory"), "\\", "/")};
#else
const std::string downloadDirectory {
Settings::getInstance()->getString("ApplicationUpdaterDownloadDirectory")};
#endif
if (downloadDirectory == "")
mDownloadPackageFilename = Utils::FileSystem::getSystemHomeDirectory() + "/Downloads/";
else
mDownloadPackageFilename = Utils::FileSystem::expandHomePath(downloadDirectory) + "/";
mDownloadPackageFilename = Utils::String::replace(mDownloadPackageFilename, "//", "/");
mDownloadPackageFilename.append(mPackage.filename);
}
}
bool GuiApplicationUpdater::downloadPackage()
{
mStatus = ASYNC_IN_PROGRESS;
@ -188,32 +271,30 @@ bool GuiApplicationUpdater::downloadPackage()
return true;
}
const std::string packageTempFile {
Utils::FileSystem::getParent(Utils::FileSystem::getEsBinary()) + "/" + mPackage.filename +
"_" + mPackage.version};
LOG(LogDebug)
<< "GuiApplicationUpdater::downloadPackage(): Package downloaded, writing it to \""
<< packageTempFile << "\"";
if (mLinuxAppImage) {
LOG(LogDebug)
<< "GuiApplicationUpdater::downloadPackage(): Package downloaded, writing it to \""
<< mDownloadPackageFilename << "\"";
if (Utils::FileSystem::isRegularFile(packageTempFile)) {
LOG(LogInfo) << "Temporary package file already exists, deleting it";
Utils::FileSystem::removeFile(packageTempFile);
if (Utils::FileSystem::exists(packageTempFile)) {
const std::string errorMessage {
"Couldn't delete temporary package file, permission problems?"};
LOG(LogError) << errorMessage;
std::unique_lock<std::mutex> lock {mMutex};
mMessage = "Error: " + errorMessage;
return true;
if (Utils::FileSystem::isRegularFile(mDownloadPackageFilename)) {
LOG(LogInfo) << "Temporary package file already exists, deleting it";
Utils::FileSystem::removeFile(mDownloadPackageFilename);
if (Utils::FileSystem::exists(mDownloadPackageFilename)) {
const std::string errorMessage {
"Couldn't delete temporary package file, permission problems?"};
LOG(LogError) << errorMessage;
std::unique_lock<std::mutex> lock {mMutex};
mMessage = "Error: " + errorMessage;
return true;
}
}
}
std::ofstream writeFile;
writeFile.open(packageTempFile.c_str(), std::ofstream::binary);
writeFile.open(mDownloadPackageFilename.c_str(), std::ofstream::binary);
if (writeFile.fail()) {
const std::string errorMessage {
"Couldn't open package file for writing, permission problems?"};
const std::string errorMessage {"Couldn't write package file, permission problems?"};
LOG(LogError) << errorMessage;
std::unique_lock<std::mutex> lock {mMutex};
mMessage = "Error: " + errorMessage;
@ -225,28 +306,28 @@ bool GuiApplicationUpdater::downloadPackage()
fileContents.clear();
#if !defined(__APPLE__) && !defined(__NETBSD__)
std::filesystem::permissions(packageTempFile, std::filesystem::perms::owner_all |
std::filesystem::perms::group_all |
std::filesystem::perms::others_read |
std::filesystem::perms::others_exec);
if (mLinuxAppImage) {
std::filesystem::permissions(
mDownloadPackageFilename,
std::filesystem::perms::owner_all | std::filesystem::perms::group_all |
std::filesystem::perms::others_read | std::filesystem::perms::others_exec);
if (std::filesystem::status(packageTempFile).permissions() !=
(std::filesystem::perms::owner_all | std::filesystem::perms::group_all |
std::filesystem::perms::others_read | std::filesystem::perms::others_exec)) {
Utils::FileSystem::removeFile(packageTempFile);
const std::string errorMessage {"Couldn't set permissions on AppImage file"};
LOG(LogError) << errorMessage;
std::unique_lock<std::mutex> lock {mMutex};
mMessage = "Error: " + errorMessage;
return true;
if (std::filesystem::status(mDownloadPackageFilename).permissions() !=
(std::filesystem::perms::owner_all | std::filesystem::perms::group_all |
std::filesystem::perms::others_read | std::filesystem::perms::others_exec)) {
Utils::FileSystem::removeFile(mDownloadPackageFilename);
const std::string errorMessage {"Couldn't set permissions on AppImage file"};
LOG(LogError) << errorMessage;
std::unique_lock<std::mutex> lock {mMutex};
mMessage = "Error: " + errorMessage;
return true;
}
}
#endif
LOG(LogInfo) << "Successfully downloaded package file \"" << packageTempFile << "\"";
LOG(LogInfo) << "Successfully downloaded package file \"" << mDownloadPackageFilename << "\"";
std::unique_lock<std::mutex> lock {mMutex};
mMessage = "Downloaded " + mPackage.filename + "_" + mPackage.version;
mMessage = "Downloaded " + Utils::FileSystem::getFileName(mDownloadPackageFilename);
mDownloading = false;
mReadyToInstall = true;
@ -261,9 +342,6 @@ bool GuiApplicationUpdater::installAppImage()
mReadyToInstall = false;
mInstalling = true;
const std::string packageTempFile {
Utils::FileSystem::getParent(Utils::FileSystem::getEsBinary()) + "/" + mPackage.filename +
"_" + mPackage.version};
const std::string packageTargetFile {Utils::FileSystem::getEsBinary()};
if (packageTargetFile !=
@ -280,7 +358,7 @@ bool GuiApplicationUpdater::installAppImage()
// Extra precaution, make sure that the file was actually correctly written to disk.
std::ifstream readFile;
readFile.open(packageTempFile.c_str(), std::ofstream::binary);
readFile.open(mDownloadPackageFilename.c_str(), std::ofstream::binary);
if (readFile.fail()) {
const std::string errorMessage {"Couldn't open AppImage update file for reading"};
@ -321,7 +399,7 @@ bool GuiApplicationUpdater::installAppImage()
LOG(LogInfo) << "Renamed running AppImage to \"" << packageOldFile << "\"";
if (Utils::FileSystem::renameFile(packageTempFile, packageTargetFile, true)) {
if (Utils::FileSystem::renameFile(mDownloadPackageFilename, packageTargetFile, true)) {
const std::string errorMessage {
"Couldn't replace running AppImage file, permission problems?"};
LOG(LogError) << errorMessage;
@ -354,7 +432,7 @@ void GuiApplicationUpdater::update(int deltaTime)
if (mDownloading)
mBusyAnim.update(deltaTime);
else if (mReadyToInstall) {
else if (mLinuxAppImage && mReadyToInstall) {
mProcessStep1->setText(ViewController::TICKMARK_CHAR + " " + mProcessStep1->getValue());
mProcessStep1->setColor(mMenuColorGreen);
mButton1->setText("INSTALL", "install package");
@ -368,21 +446,28 @@ void GuiApplicationUpdater::update(int deltaTime)
mReadyToInstall = false;
mHasDownloaded = true;
}
else if (mHasInstalled) {
mProcessStep2->setText(ViewController::TICKMARK_CHAR + " " + mProcessStep2->getValue());
mProcessStep2->setColor(mMenuColorGreen);
else if ((mLinuxAppImage && mHasInstalled) || (!mLinuxAppImage && mReadyToInstall)) {
if (mLinuxAppImage) {
mProcessStep2->setText(ViewController::TICKMARK_CHAR + " " + mProcessStep2->getValue());
mProcessStep2->setColor(mMenuColorGreen);
}
else {
mProcessStep1->setText(ViewController::TICKMARK_CHAR + " " + mProcessStep1->getValue());
mProcessStep1->setColor(mMenuColorGreen);
}
mChangelogMessage->setText("Find the detailed changelog at https://es-de.org");
mButton1->setText("DONE", "quit application");
mButton1->setPressedFunc([this] {
delete this;
Utils::Platform::quitES();
});
mButton2->setText("QUIT", "quit application");
mButton2->setPressedFunc([this] {
delete this;
Utils::Platform::quitES();
});
mGrid.removeEntry(mButtons);
mGrid.setEntry(MenuComponent::makeButtonGrid(std::vector<std::shared_ptr<ButtonComponent>> {
std::make_shared<ButtonComponent>("QUIT", "quit application",
[this]() {
delete this;
Utils::Platform::quitES();
})}),
glm::ivec2 {0, 10}, true, false, glm::ivec2 {4, 1}, GridFlags::BORDER_TOP);
mGrid.moveCursorTo(0, 10);
mReadyToInstall = false;
mHasInstalled = false;
mHasDownloaded = true;
}
}

View file

@ -28,6 +28,7 @@ public:
GuiApplicationUpdater();
~GuiApplicationUpdater();
void setDownloadPath();
bool downloadPackage();
bool installAppImage();
@ -53,6 +54,7 @@ private:
std::shared_ptr<ComponentGrid> mButtons;
std::shared_ptr<ButtonComponent> mButton1;
std::shared_ptr<ButtonComponent> mButton2;
std::shared_ptr<ButtonComponent> mButton3;
std::shared_ptr<TextComponent> mTitle;
std::shared_ptr<TextComponent> mStatusHeader;
@ -70,6 +72,8 @@ private:
std::string mMessage;
ApplicationUpdater::Package mPackage;
std::string mDownloadPackageFilename;
std::atomic<bool> mLinuxAppImage;
std::atomic<bool> mAbortDownload;
std::atomic<bool> mDownloading;
std::atomic<bool> mReadyToInstall;

View file

@ -139,12 +139,13 @@ void ViewController::setMenuColors()
void ViewController::unsafeUpgradeDialog()
{
std::string upgradeMessage {"IT SEEMS AS IF AN UNSAFE UPGRADE HAS BEEN MADE, POSSIBLY BY "
"UNPACKING THE NEW RELEASE ON TOP OF THE OLD ONE? THIS MAY CAUSE "
"VARIOUS PROBLEMS, SOME OF WHICH MAY NOT BE APPARENT IMMEDIATELY. "
"MAKE SURE TO ALWAYS FOLLOW THE UPGRADE INSTRUCTIONS IN THE "
"README.TXT FILE THAT CAN BE FOUND IN THE EMULATIONSTATION-DE "
"DIRECTORY."};
const std::string upgradeMessage {
"IT SEEMS AS IF AN UNSAFE UPGRADE HAS BEEN MADE, POSSIBLY BY "
"UNPACKING THE NEW RELEASE ON TOP OF THE OLD ONE? THIS MAY CAUSE "
"VARIOUS PROBLEMS, SOME OF WHICH MAY NOT BE APPARENT IMMEDIATELY. "
"MAKE SURE TO ALWAYS FOLLOW THE UPGRADE INSTRUCTIONS IN THE "
"README.TXT FILE THAT CAN BE FOUND IN THE EMULATIONSTATION-DE "
"DIRECTORY."};
mWindow->pushGui(new GuiMsgBox(
HelpStyle(), upgradeMessage.c_str(), "OK", [] {}, "", nullptr, "", nullptr, true, true,
(mRenderer->getIsVerticalOrientation() ?
@ -321,13 +322,46 @@ void ViewController::updateAvailableDialog()
<< "\"";
LOG(LogDebug) << "ViewController::updateAvailableDialog(): Package md5 \"" << package.md5
<< "\"";
}
if (package.name == "LinuxAppImage" || package.name == "LinuxSteamDeckAppImage") {
mWindow->pushGui(new GuiMsgBox(
getHelpStyle(), results, "UPDATE",
[this] { mWindow->pushGui(new GuiApplicationUpdater()); }, "CANCEL", [] { return; }, "",
nullptr, true, true,
[this, package] {
mWindow->pushGui(new GuiApplicationUpdater());
if (package.name != "LinuxAppImage" && package.name != "LinuxSteamDeckAppImage") {
std::string upgradeMessage;
if (package.name == "WindowsPortable") {
upgradeMessage =
"THE APPLICATION UPDATER WILL DOWNLOAD THE LATEST PORTABLE WINDOWS "
"RELEASE FOR YOU, BUT YOU WILL NEED TO MANUALLY PERFORM THE UPGRADE. "
"SEE THE README.TXT FILE INSIDE THE DOWNLOADED ZIP FILE FOR "
"INSTRUCTIONS ON HOW THIS IS ACCOMPLISHED. AS IS ALSO DESCRIBED IN "
"THAT DOCUMENT, NEVER UNPACK A NEW RELEASE ON TOP OF AN OLD "
"INSTALLATION AS THAT MAY COMPLETELY BREAK THE APPLICATION.";
}
else if (package.name == "WindowsInstaller") {
upgradeMessage =
"THE APPLICATION UPDATER WILL DOWNLOAD THE LATEST WINDOWS INSTALLER "
"RELEASE FOR YOU, BUT YOU WILL NEED TO MANUALLY RUN IT TO PERFORM "
"THE UPGRADE. WHEN DOING THIS, MAKE SURE THAT YOU ANSWER YES TO THE "
"QUESTION OF WHETHER TO UNINSTALL THE OLD VERSION, OR YOU MAY "
"END UP WITH A BROKEN SETUP.";
}
else if (package.name == "macOSApple" || package.name == "macOSIntel") {
upgradeMessage =
"THE APPLICATION UPDATER WILL DOWNLOAD THE LATEST RELEASE FOR "
"YOU, BUT YOU WILL NEED TO MANUALLY INSTALL THE DMG FILE TO PERFORM "
"THE UPGRADE.";
}
mWindow->pushGui(new GuiMsgBox(
HelpStyle(), upgradeMessage.c_str(), "OK", [] {}, "", nullptr, "", nullptr,
true, true,
(mRenderer->getIsVerticalOrientation() ?
0.85f :
0.53f * (1.778f / mRenderer->getScreenAspectRatio()))));
}
},
"CANCEL", [] { return; }, "", nullptr, true, true,
(mRenderer->getIsVerticalOrientation() ?
0.70f :
0.45f * (1.778f / mRenderer->getScreenAspectRatio()))));

View file

@ -262,6 +262,7 @@ void Settings::setDefaults()
#endif
mStringMap["SaveGamelistsMode"] = {"always", "always"};
mStringMap["ApplicationUpdaterFrequency"] = {"always", "always"};
mStringMap["ApplicationUpdaterDownloadDirectory"] = {"", ""};
mBoolMap["ApplicationUpdaterPrereleases"] = {false, false};
#if defined(_WIN64)
mBoolMap["HideTaskbar"] = {false, false};