From 4f3d66c45b232b8c3a14f71257a2f9bbc8f2f728 Mon Sep 17 00:00:00 2001 From: Leon Styhre <leon@leonstyhre.com> Date: Mon, 31 Jul 2023 19:25:54 +0200 Subject: [PATCH] Added support for downloading the Windows and macOS packages via the application updater --- es-app/src/ApplicationUpdater.cpp | 2 +- es-app/src/guis/GuiApplicationUpdater.cpp | 219 +++++++++++++++------- es-app/src/guis/GuiApplicationUpdater.h | 4 + es-app/src/views/ViewController.cpp | 54 +++++- es-core/src/Settings.cpp | 1 + 5 files changed, 202 insertions(+), 78 deletions(-) diff --git a/es-app/src/ApplicationUpdater.cpp b/es-app/src/ApplicationUpdater.cpp index 5f4572462..0187d2861 100644 --- a/es-app/src/ApplicationUpdater.cpp +++ b/es-app/src/ApplicationUpdater.cpp @@ -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 != "") diff --git a/es-app/src/guis/GuiApplicationUpdater.cpp b/es-app/src/guis/GuiApplicationUpdater.cpp index 00aa6d02b..f0299bd2f 100644 --- a/es-app/src/guis/GuiApplicationUpdater.cpp +++ b/es-app/src/guis/GuiApplicationUpdater.cpp @@ -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; } } diff --git a/es-app/src/guis/GuiApplicationUpdater.h b/es-app/src/guis/GuiApplicationUpdater.h index 401fb6871..02c155963 100644 --- a/es-app/src/guis/GuiApplicationUpdater.h +++ b/es-app/src/guis/GuiApplicationUpdater.h @@ -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; diff --git a/es-app/src/views/ViewController.cpp b/es-app/src/views/ViewController.cpp index 9767d0831..2b160840e 100644 --- a/es-app/src/views/ViewController.cpp +++ b/es-app/src/views/ViewController.cpp @@ -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())))); diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp index a1329428d..75e0af55b 100644 --- a/es-core/src/Settings.cpp +++ b/es-core/src/Settings.cpp @@ -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};