Multiple improvements to ApplicationUpdater.

This commit is contained in:
Leon Styhre 2023-02-26 12:50:30 +01:00
parent 90c3f8ff5e
commit c9a59994a6
5 changed files with 131 additions and 72 deletions

View file

@ -4,7 +4,7 @@
// ApplicationUpdater.cpp // ApplicationUpdater.cpp
// //
// Checks for application updates. // Checks for application updates.
// In the future updates will also be downloaded and possibly installed. // In the future updates will also be downloaded, and installed on some platforms.
// //
#include "ApplicationUpdater.h" #include "ApplicationUpdater.h"
@ -12,6 +12,7 @@
#include "EmulationStation.h" #include "EmulationStation.h"
#include "Log.h" #include "Log.h"
#include "Settings.h" #include "Settings.h"
#include "resources/ResourceManager.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
#include "utils/TimeUtil.h" #include "utils/TimeUtil.h"
@ -23,17 +24,39 @@
#include <algorithm> #include <algorithm>
#include <deque> #include <deque>
#define LOCAL_TESTING_FILE false
#define MAX_DOWNLOAD_TIME 1 #define MAX_DOWNLOAD_TIME 1
ApplicationUpdater::ApplicationUpdater() ApplicationUpdater::ApplicationUpdater()
: mTimer {0} : mPackageType {PackageType::UNKNOWN}
, mTimer {0}
, mMaxTime {0} , mMaxTime {0}
, mAbortDownload {false} , mAbortDownload {false}
, mApplicationShutdown {false} , mApplicationShutdown {false}
, mCheckedForUpdate {false} , mCheckedForUpdate {false}
, mNewVersion {false}
{ {
mUrl = "https://gitlab.com/api/v4/projects/18817634/repository/files/latest_release.json/" mUrl = "https://gitlab.com/api/v4/projects/18817634/repository/files/latest_release.json/"
"raw?ref=master"; "raw?ref=master";
#if defined(_WIN64)
if (Settings::getInstance()->getBool("PortableMode"))
mPackageType = PackageType::WINDOWS_PORTABLE;
else
mPackageType = PackageType::WINDOWS_INSTALLER;
#elif defined(MACOS_APPLE_CPU)
mPackageType = PackageType::MACOS_APPLE;
#elif defined(MACOS_INTEL_CPU)
mPackageType = PackageType::MACOS_INTEL;
#elif defined(STEAM_DECK)
mPackageType = PackageType::LINUX_STEAM_DECK_APPIMAGE;
#elif defined(APPIMAGE_BUILD)
mPackageType = PackageType::LINUX_APPIMAGE;
#elif defined(LINUX_RPM_PACKAGE)
mPackageType = PackageType::LINUX_RPM;
#elif defined(LINUX_DEB_PACKAGE)
mPackageType = PackageType::LINUX_DEB;
#endif
} }
ApplicationUpdater::~ApplicationUpdater() ApplicationUpdater::~ApplicationUpdater()
@ -44,6 +67,12 @@ ApplicationUpdater::~ApplicationUpdater()
mThread->join(); mThread->join();
} }
ApplicationUpdater& ApplicationUpdater::getInstance()
{
static ApplicationUpdater instance;
return instance;
}
void ApplicationUpdater::checkForUpdates() void ApplicationUpdater::checkForUpdates()
{ {
const std::string updateFrequency { const std::string updateFrequency {
@ -176,10 +205,25 @@ void ApplicationUpdater::update()
void ApplicationUpdater::parseFile() void ApplicationUpdater::parseFile()
{ {
assert(mRequest->status() == HttpReq::REQ_SUCCESS); assert(mRequest->status() == HttpReq::REQ_SUCCESS);
const std::string fileContents {mRequest->getContent()};
rapidjson::Document doc; rapidjson::Document doc;
#if (LOCAL_TESTING_FILE)
LOG(LogWarning) << "ApplicationUpdater: Using local \"latest_release.json\" testing file";
const std::string localReleaseFile {Utils::FileSystem::getHomePath() +
"/.emulationstation/latest_release.json"};
if (!Utils::FileSystem::exists(localReleaseFile))
throw std::runtime_error("Local testing file not found");
const ResourceData& localReleaseFileData {
ResourceManager::getInstance().getFileData(localReleaseFile)};
doc.Parse(reinterpret_cast<const char*>(localReleaseFileData.ptr.get()),
localReleaseFileData.length);
#else
const std::string fileContents {mRequest->getContent()};
doc.Parse(&fileContents[0], fileContents.length()); doc.Parse(&fileContents[0], fileContents.length());
#endif
if (doc.HasParseError()) if (doc.HasParseError())
throw std::runtime_error(rapidjson::GetParseError_En(doc.GetParseError())); throw std::runtime_error(rapidjson::GetParseError_En(doc.GetParseError()));
@ -289,14 +333,14 @@ void ApplicationUpdater::compareVersions()
#endif #endif
} }
bool newVersion {false}; mNewVersion = false;
for (auto& releaseType : releaseTypes) { for (auto& releaseType : releaseTypes) {
// If the version does not follow the semantic versioning scheme then always consider it to // If the version does not follow the semantic versioning scheme then always consider it to
// be a new release as perhaps the version scheme will be changed sometime in the future. // be a new release as perhaps the version scheme will be changed sometime in the future.
if (count_if(releaseType->version.cbegin(), releaseType->version.cend(), if (count_if(releaseType->version.cbegin(), releaseType->version.cend(),
[](char c) { return c == '.'; }) != 2) { [](char c) { return c == '.'; }) != 2) {
newVersion = true; mNewVersion = true;
} }
else { else {
std::vector<std::string> fileVersion { std::vector<std::string> fileVersion {
@ -328,45 +372,35 @@ void ApplicationUpdater::compareVersions()
++versionWeight; ++versionWeight;
if (versionWeight > 0) if (versionWeight > 0)
newVersion = true; mNewVersion = true;
} }
if (newVersion) {
std::string message;
if (mNewVersion) {
for (auto& package : releaseType->packages) { for (auto& package : releaseType->packages) {
#if defined(_WIN64) if (mPackageType == PackageType::WINDOWS_PORTABLE &&
if (Settings::getInstance()->getBool("PortableMode")) { package.name == "WindowsPortable")
if (package.name == "WindowsPortable") mPackage = package;
message = package.message; else if (mPackageType == PackageType::WINDOWS_INSTALLER &&
} package.name == "WindowsInstaller")
else { mPackage = package;
if (package.name == "WindowsInstaller") else if (mPackageType == PackageType::MACOS_APPLE && package.name == "macOSApple")
message = package.message; mPackage = package;
} else if (mPackageType == PackageType::MACOS_INTEL && package.name == "macOSIntel")
#elif defined(MACOS_APPLE_CPU) mPackage = package;
if (package.name == "macOSApple") else if (mPackageType == PackageType::LINUX_DEB && package.name == "LinuxDEB")
message = package.message; mPackage = package;
#elif defined(MACOS_INTEL_CPU) else if (mPackageType == PackageType::LINUX_RPM && package.name == "LinuxRPM")
if (package.name == "macOSIntel") mPackage = package;
message = package.message; else if (mPackageType == PackageType::LINUX_APPIMAGE &&
#elif defined(STEAM_DECK) package.name == "LinuxAppImage")
if (package.name == "LinuxSteamDeckAppImage") mPackage = package;
message = package.message; else if (mPackageType == PackageType::LINUX_STEAM_DECK_APPIMAGE &&
#elif defined(APPIMAGE_BUILD) package.name == "LinuxSteamDeckAppImage")
if (package.name == "LinuxAppImage") mPackage = package;
message = package.message;
#elif defined(LINUX_DEB_PACKAGE)
if (package.name == "LinuxDEB")
message = package.message;
#elif defined(LINUX_RPM_PACKAGE)
if (package.name == "LinuxRPM")
message = package.message;
#endif
auto tempVar = package;
} }
// Cut the message to 280 characters so we don't make the message box exceedingly large. // Cut the message to 280 characters so we don't make the message box exceedingly large.
message = message.substr(0, 280); mPackage.message = mPackage.message.substr(0, 280);
mLogInfo = "A new "; mLogInfo = "A new ";
mLogInfo.append(releaseType == &mStableRelease ? "stable release" : "prerelease") mLogInfo.append(releaseType == &mStableRelease ? "stable release" : "prerelease")
@ -387,20 +421,20 @@ void ApplicationUpdater::compareVersions()
.append("can now be downloaded from\n") .append("can now be downloaded from\n")
.append("https://es-de.org/"); .append("https://es-de.org/");
if (message != "") if (mPackage.message != "")
mResults.append("\n").append(message); mResults.append("\n").append(mPackage.message);
mResults = Utils::String::toUpper(mResults); mResults = Utils::String::toUpper(mResults);
break; break;
} }
} }
if (!newVersion) { if (!mNewVersion) {
mLogInfo = "No application updates available"; mLogInfo = "No application updates available";
} }
mCheckedForUpdate = true; mCheckedForUpdate = true;
} }
void ApplicationUpdater::getResults(std::string& results) bool ApplicationUpdater::getResults()
{ {
mAbortDownload = true; mAbortDownload = true;
@ -408,8 +442,6 @@ void ApplicationUpdater::getResults(std::string& results)
mThread->join(); mThread->join();
mThread.reset(); mThread.reset();
if (mCheckedForUpdate) { if (mCheckedForUpdate) {
if (mResults != "")
results = mResults;
Settings::getInstance()->setString( Settings::getInstance()->setString(
"ApplicationUpdaterLastCheck", "ApplicationUpdaterLastCheck",
Utils::Time::DateTime(Utils::Time::now()).getIsoString()); Utils::Time::DateTime(Utils::Time::now()).getIsoString());
@ -428,4 +460,11 @@ void ApplicationUpdater::getResults(std::string& results)
if (mLogInfo != "") { if (mLogInfo != "") {
LOG(LogInfo) << mLogInfo; LOG(LogInfo) << mLogInfo;
} }
if (mNewVersion && mPackage.name == "") {
LOG(LogWarning)
<< "ApplicationUpdater: Couldn't find a package type matching current platform";
}
return mNewVersion;
} }

View file

@ -4,7 +4,7 @@
// ApplicationUpdater.h // ApplicationUpdater.h
// //
// Checks for application updates. // Checks for application updates.
// In the future updates will also be downloaded and possibly installed. // In the future updates will also be downloaded, and installed on some platforms.
// //
#ifndef ES_APP_APPLICATION_UPDATER_H #ifndef ES_APP_APPLICATION_UPDATER_H
@ -21,8 +21,8 @@
class ApplicationUpdater : public AsyncHandle class ApplicationUpdater : public AsyncHandle
{ {
public: public:
ApplicationUpdater(); virtual ~ApplicationUpdater();
~ApplicationUpdater(); static ApplicationUpdater& getInstance();
void checkForUpdates(); void checkForUpdates();
void updaterThread(); void updaterThread();
@ -30,9 +30,8 @@ public:
void update(); void update();
void parseFile(); void parseFile();
void compareVersions(); void compareVersions();
void getResults(std::string& results); bool getResults();
private:
struct Package { struct Package {
std::string name; std::string name;
std::string filename; std::string filename;
@ -41,6 +40,12 @@ private:
std::string message; std::string message;
}; };
const std::string& getResultsString() { return mResults; }
const Package& getPackageInfo() { return mPackage; }
private:
ApplicationUpdater();
struct Release { struct Release {
std::string releaseType; std::string releaseType;
std::string version; std::string version;
@ -49,6 +54,20 @@ private:
std::vector<Package> packages; std::vector<Package> packages;
}; };
enum class PackageType {
WINDOWS_PORTABLE,
WINDOWS_INSTALLER,
MACOS_APPLE,
MACOS_INTEL,
LINUX_DEB,
LINUX_RPM,
LINUX_APPIMAGE,
LINUX_STEAM_DECK_APPIMAGE,
UNKNOWN
};
PackageType mPackageType;
Package mPackage;
std::string mUrl; std::string mUrl;
std::string mResults; std::string mResults;
std::string mLogInfo; std::string mLogInfo;
@ -59,6 +78,7 @@ private:
std::atomic<bool> mAbortDownload; std::atomic<bool> mAbortDownload;
std::atomic<bool> mApplicationShutdown; std::atomic<bool> mApplicationShutdown;
bool mCheckedForUpdate; bool mCheckedForUpdate;
bool mNewVersion;
std::unique_ptr<std::thread> mThread; std::unique_ptr<std::thread> mThread;
std::unique_ptr<HttpReq> mRequest; std::unique_ptr<HttpReq> mRequest;

View file

@ -720,11 +720,8 @@ int main(int argc, char* argv[])
} }
#if defined(APPLICATION_UPDATER) #if defined(APPLICATION_UPDATER)
std::unique_ptr<ApplicationUpdater> applicationUpdater; if (!noUpdateCheck)
if (!noUpdateCheck) { ApplicationUpdater::getInstance().checkForUpdates();
applicationUpdater = std::make_unique<ApplicationUpdater>();
applicationUpdater->checkForUpdates();
}
#endif #endif
window->pushGui(ViewController::getInstance()); window->pushGui(ViewController::getInstance());
@ -773,14 +770,8 @@ int main(int argc, char* argv[])
} }
if (!SystemData::sStartupExitSignal) { if (!SystemData::sStartupExitSignal) {
std::string updaterResults; if (loadSystemsStatus == loadSystemsReturnCode::LOADING_OK)
if (loadSystemsStatus == loadSystemsReturnCode::LOADING_OK) {
ThemeData::themeLoadedLogOutput(); ThemeData::themeLoadedLogOutput();
#if defined(APPLICATION_UPDATER)
if (!noUpdateCheck)
applicationUpdater->getResults(updaterResults);
#endif
}
if (!loadSystemsStatus) if (!loadSystemsStatus)
ViewController::getInstance()->goToStart(true); ViewController::getInstance()->goToStart(true);
@ -800,11 +791,6 @@ int main(int argc, char* argv[])
lastTime = SDL_GetTicks(); lastTime = SDL_GetTicks();
#if defined(APPLICATION_UPDATER)
if (!noUpdateCheck)
applicationUpdater.reset();
#endif
LOG(LogInfo) << "Application startup time: " LOG(LogInfo) << "Application startup time: "
<< std::chrono::duration_cast<std::chrono::milliseconds>( << std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now() - applicationStartTime) std::chrono::system_clock::now() - applicationStartTime)
@ -812,8 +798,8 @@ int main(int argc, char* argv[])
<< " ms"; << " ms";
#if defined(APPLICATION_UPDATER) #if defined(APPLICATION_UPDATER)
if (updaterResults != "") if (ApplicationUpdater::getInstance().getResults())
ViewController::getInstance()->updateAvailableDialog(updaterResults); ViewController::getInstance()->updateAvailableDialog();
#endif #endif
// Open the input configuration GUI if the force flag was passed from the command line. // Open the input configuration GUI if the force flag was passed from the command line.

View file

@ -12,6 +12,7 @@
#include "views/ViewController.h" #include "views/ViewController.h"
#include "ApplicationUpdater.h"
#include "CollectionSystemsManager.h" #include "CollectionSystemsManager.h"
#include "FileFilterIndex.h" #include "FileFilterIndex.h"
#include "InputManager.h" #include "InputManager.h"
@ -211,10 +212,23 @@ void ViewController::invalidAlternativeEmulatorDialog()
"OK", nullptr, "", nullptr, "", nullptr, true, true)); "OK", nullptr, "", nullptr, "", nullptr, true, true));
} }
void ViewController::updateAvailableDialog(const std::string& message) void ViewController::updateAvailableDialog()
{ {
cancelViewTransitions(); cancelViewTransitions();
mWindow->pushGui(new GuiMsgBox(getHelpStyle(), message, "OK", nullptr, "", nullptr, "", nullptr,
std::string results {ApplicationUpdater::getInstance().getResultsString()};
ApplicationUpdater::Package package {ApplicationUpdater::getInstance().getPackageInfo()};
if (package.name != "") {
LOG(LogDebug) << "ViewController::updateAvailableDialog(): Package filename \""
<< package.filename << "\"";
LOG(LogDebug) << "ViewController::updateAvailableDialog(): Package url \"" << package.url
<< "\"";
LOG(LogDebug) << "ViewController::updateAvailableDialog(): Package md5 \"" << package.md5
<< "\"";
}
mWindow->pushGui(new GuiMsgBox(getHelpStyle(), results, "OK", nullptr, "", nullptr, "", nullptr,
true, true, true, true,
(mRenderer->getIsVerticalOrientation() ? (mRenderer->getIsVerticalOrientation() ?
0.70f : 0.70f :

View file

@ -36,7 +36,7 @@ public:
void invalidSystemsFileDialog(); void invalidSystemsFileDialog();
void noGamesDialog(); void noGamesDialog();
void invalidAlternativeEmulatorDialog(); void invalidAlternativeEmulatorDialog();
void updateAvailableDialog(const std::string& message); void updateAvailableDialog();
// Try to completely populate the GamelistView map. // Try to completely populate the GamelistView map.
// Caches things so there's no pauses during transitions. // Caches things so there's no pauses during transitions.