Added an application updater that checks for new releases on startup.

This commit is contained in:
Leon Styhre 2023-02-18 12:42:19 +01:00
parent 43a18146d5
commit 270351b033
12 changed files with 653 additions and 20 deletions

View file

@ -32,20 +32,22 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/CMake/Utils
${CMAKE_CURRENT_SOURCE_DIR}/CMake/Packages)
# Define the options.
option(GL "Set to ON if targeting Desktop OpenGL" ${GL})
option(GLES "Set to ON if targeting OpenGL ES" ${GLES})
option(APPIMAGE_BUILD "Set to ON when building as an AppImage" ${APPIMAGE_BUILD})
option(FLATPAK_BUILD "Set to ON when building as a Flatpak" ${FLATPAK_BUILD})
option(STEAM_DECK "Set to ON to enable a Valve Steam Deck specific build" ${STEAM_DECK})
option(RETRODECK "Set to ON to enable a RetroDECK specific build" ${RETRODECK})
option(RPI "Set to ON to enable a Raspberry Pi specific build" ${RPI})
option(BUNDLED_CERTS "Set to ON to use bundled TLS/SSL certificates" ${BUNDLED_CERTS})
option(CEC "Set to ON to enable CEC" ${CEC})
option(VIDEO_HW_DECODING "Set to ON to enable FFmpeg HW decoding" ${VIDEO_HW_DECODING})
option(CLANG_TIDY "Set to ON to build using the clang-tidy static analyzer" ${CLANG_TIDY})
option(ASAN "Set to ON to build with AddressSanitizer" ${ASAN})
option(TSAN "Set to ON to build with ThreadSanitizer" ${TSAN})
option(UBSAN "Set to ON to build with UndefinedBehaviorSanitizer" ${UBSAN})
option(GL "Set to ON if targeting Desktop OpenGL" ON)
option(GLES "Set to ON if targeting OpenGL ES" OFF)
option(APPLICATION_UPDATER "Set to OFF to build without the application updater" ON)
option(APPIMAGE_BUILD "Set to ON when building as an AppImage" OFF)
option(AUR_BUILD "Set to ON when building for the AUR" OFF)
option(FLATPAK_BUILD "Set to ON when building as a Flatpak" OFF)
option(STEAM_DECK "Set to ON to enable a Valve Steam Deck specific build" OFF)
option(RETRODECK "Set to ON to enable a RetroDECK specific build" OFF)
option(RPI "Set to ON to enable a Raspberry Pi specific build" OFF)
option(BUNDLED_CERTS "Set to ON to use bundled TLS/SSL certificates" OFF)
option(CEC "Set to ON to enable CEC" OFF)
option(VIDEO_HW_DECODING "Set to ON to enable FFmpeg HW decoding" OFF)
option(CLANG_TIDY "Set to ON to build using the clang-tidy static analyzer" OFF)
option(ASAN "Set to ON to build with AddressSanitizer" OFF)
option(TSAN "Set to ON to build with ThreadSanitizer" OFF)
option(UBSAN "Set to ON to build with UndefinedBehaviorSanitizer" OFF)
if(CLANG_TIDY)
find_program(CLANG_TIDY_BINARY NAMES clang-tidy)
@ -307,6 +309,10 @@ if(FLATPAK_BUILD)
message("-- Building as a Flatpak")
endif()
if(AUR_BUILD)
message("-- Building for the AUR")
endif()
if(STEAM_DECK AND RETRODECK)
message(FATAL_ERROR "-- STEAM_DECK and RETRODECK can't be combined")
endif()
@ -339,13 +345,44 @@ if(VIDEO_HW_DECODING)
message("-- Building with FFmpeg HW decoding")
endif()
if(AUR_BUILD OR FLATPAK_BUILD OR RETRODECK OR RPI)
set(APPLICATION_UPDATER OFF)
endif()
if(CMAKE_SYSTEM_NAME MATCHES FreeBSD OR CMAKE_SYSTEM_NAME MATCHES NetBSD OR CMAKE_SYSTEM_NAME MATCHES OpenBSD)
set(APPLICATION_UPDATER OFF)
endif()
if(APPLICATION_UPDATER)
add_compile_definitions(APPLICATION_UPDATER)
else()
message("-- Building without application updater")
endif()
# This is needed to identify the package type for the application updater.
if(CMAKE_SYSTEM_NAME MATCHES Linux)
if(LINUX_CPACK_GENERATOR MATCHES DEB)
add_compile_definitions(LINUX_DEB_PACKAGE)
elseif(LINUX_CPACK_GENERATOR MATCHES RPM)
add_compile_definitions(LINUX_RPM_PACKAGE)
endif()
endif()
if(APPLE)
if(CMAKE_SYSTEM_PROCESSOR MATCHES arm)
add_compile_definitions(MACOS_APPLE_CPU)
else()
add_compile_definitions(MACOS_INTEL_CPU)
endif()
endif()
if(APPLE AND CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS 10.14)
add_compile_definitions(LEGACY_MACOS)
endif()
# If it's an alpha, beta or dev build, then display the build date in the main menu.
# Affects the application updater and is used for displaying version info in the main menu.
if(ES_VERSION MATCHES alpha OR ES_VERSION MATCHES beta OR ES_VERSION MATCHES dev)
add_compile_definitions(MENU_BUILD_DATE)
add_compile_definitions(IS_PRERELEASE)
endif()
# GLM library options.

View file

@ -10,6 +10,7 @@
project(emulationstation-de)
set(ES_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/ApplicationUpdater.h
${CMAKE_CURRENT_SOURCE_DIR}/src/CollectionSystemsManager.h
${CMAKE_CURRENT_SOURCE_DIR}/src/EmulationStation.h
${CMAKE_CURRENT_SOURCE_DIR}/src/FileData.h
@ -58,6 +59,7 @@ set(ES_HEADERS
)
set(ES_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/ApplicationUpdater.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/CollectionSystemsManager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/FileData.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/FileFilterIndex.cpp

View file

@ -0,0 +1,403 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// ApplicationUpdater.cpp
//
// Checks for application updates.
// In the future updates will also be downloaded and possibly installed.
//
#include "ApplicationUpdater.h"
#include "EmulationStation.h"
#include "Log.h"
#include "Settings.h"
#include "utils/StringUtil.h"
#include "utils/TimeUtil.h"
#include "rapidjson/document.h"
#include "rapidjson/error/en.h"
#include <SDL2/SDL_timer.h>
#include <algorithm>
#include <deque>
#define MAX_DOWNLOAD_TIME 1
ApplicationUpdater::ApplicationUpdater()
: mTimer {0}
, mMaxTime {0}
, mAbortDownload {false}
, mCheckedForUpdate {false}
{
mUrl = "https://gitlab.com/api/v4/projects/18817634/repository/files/latest_release.json/"
"raw?ref=master";
}
ApplicationUpdater::~ApplicationUpdater()
{
// This is needed if getResults() was never called.
if (mThread)
mThread->join();
}
void ApplicationUpdater::checkForUpdates()
{
const std::string updateFrequency {
Settings::getInstance()->getString("ApplicationUpdaterFrequency")};
if (updateFrequency == "never")
return;
const std::string lastCheck {Settings::getInstance()->getString("ApplicationUpdaterLastCheck")};
unsigned int frequencyDays {0};
bool checkForUpdate {false};
if (updateFrequency == "daily")
frequencyDays = 1;
else if (updateFrequency == "weekly")
frequencyDays = 7;
else if (updateFrequency == "monthly")
frequencyDays = 30;
// Frequency set to "always" or it's the first time we check for updates.
if (frequencyDays == 0 || lastCheck == "") {
checkForUpdate = true;
}
else {
const Utils::Time::DateTime now {Utils::Time::now()};
const Utils::Time::DateTime lastTime {lastCheck};
const Utils::Time::Duration dur {now.getTime() - lastTime.getTime()};
if (dur.getDays() >= frequencyDays)
checkForUpdate = true;
}
if (checkForUpdate) {
LOG(LogInfo) << "Checking for application updates...";
mThread = std::make_unique<std::thread>(&ApplicationUpdater::updaterThread, this);
}
}
void ApplicationUpdater::updaterThread()
{
if (!downloadFile()) {
compareVersions();
}
}
bool ApplicationUpdater::downloadFile()
{
const unsigned int startTime {SDL_GetTicks()};
mTimer = startTime;
mMaxTime = mTimer + (MAX_DOWNLOAD_TIME * 1000);
mStatus = ASYNC_IN_PROGRESS;
mRequest = std::unique_ptr<HttpReq>(std::make_unique<HttpReq>(mUrl));
while (mTimer < mMaxTime || !mAbortDownload) {
SDL_Delay(10);
try {
update();
}
catch (std::runtime_error& e) {
LOG(LogWarning) << "ApplicationUpdater: Couldn't download \"latest_release.json\": "
<< e.what();
return true;
}
if (mStatus == ASYNC_DONE)
break;
mTimer = SDL_GetTicks();
};
if (mStatus == ASYNC_DONE) {
rapidjson::Document doc;
const std::string& fileContents {mRequest->getContent()};
doc.Parse(&fileContents[0], fileContents.length());
if (doc.HasMember("error") && doc["error"].IsString()) {
LOG(LogWarning)
<< "ApplicationUpdater: Couldn't download \"latest_release.json\", received "
"server error response \""
<< doc["error"].GetString() << "\"";
return true;
}
LOG(LogDebug)
<< "ApplicationUpdater::downloadFile(): Downloaded \"latest_release.json\" in "
<< mTimer - startTime << " milliseconds";
try {
parseFile();
}
catch (std::runtime_error& e) {
LOG(LogError) << "ApplicationUpdater: Couldn't parse \"latest_release.json\": "
<< e.what();
return true;
}
}
else if (mAbortDownload) {
LOG(LogWarning) << "ApplicationUpdater: Aborted download of \"latest_release.json\" after "
<< mTimer - startTime << " milliseconds as the application has started up";
return true;
}
else {
LOG(LogWarning) << "ApplicationUpdater: Couldn't download \"latest_release.json\" within "
<< MAX_DOWNLOAD_TIME << " second time limit";
return true;
}
return false;
}
void ApplicationUpdater::update()
{
HttpReq::Status reqStatus {mRequest->status()};
if (reqStatus == HttpReq::REQ_SUCCESS) {
mStatus = ASYNC_DONE;
return;
}
// Not ready yet.
if (reqStatus == HttpReq::REQ_IN_PROGRESS)
return;
// Everything else is some sort of error.
std::string errorMessage {"Network error (status: "};
errorMessage.append(std::to_string(reqStatus)).append(") - ").append(mRequest->getErrorMsg());
throw std::runtime_error(errorMessage);
}
void ApplicationUpdater::parseFile()
{
assert(mRequest->status() == HttpReq::REQ_SUCCESS);
const std::string fileContents {mRequest->getContent()};
rapidjson::Document doc;
doc.Parse(&fileContents[0], fileContents.length());
if (doc.HasParseError())
throw std::runtime_error(rapidjson::GetParseError_En(doc.GetParseError()));
const std::vector<std::string> releaseTypes {"stable", "prerelease"};
for (auto& releaseType : releaseTypes) {
Release release;
if (doc.HasMember(releaseType.c_str())) {
release.releaseType = releaseType.c_str();
const rapidjson::Value& releaseTypeEntry {doc[releaseType.c_str()]};
if (releaseTypeEntry.HasMember("version") && releaseTypeEntry["version"].IsString())
release.version = releaseTypeEntry["version"].GetString();
else
throw std::runtime_error("Invalid file structure, \"version\" key missing");
// There may not be a prerelease available.
if (releaseType == "prerelease" && release.version == "")
continue;
if (releaseTypeEntry.HasMember("release") && releaseTypeEntry["release"].IsString())
release.releaseNum = releaseTypeEntry["release"].GetString();
else
throw std::runtime_error("Invalid file structure, \"release\" key missing");
if (releaseTypeEntry.HasMember("date") && releaseTypeEntry["date"].IsString())
release.date = releaseTypeEntry["date"].GetString();
else
throw std::runtime_error("Invalid file structure, \"date\" key missing");
if (releaseTypeEntry.HasMember("packages") && releaseTypeEntry["packages"].IsArray()) {
const rapidjson::Value& packages {releaseTypeEntry["packages"]};
for (int i {0}; i < static_cast<int>(packages.Size()); ++i) {
Package package;
const rapidjson::Value& packageEntry {packages[i]};
if (packageEntry.HasMember("name") && packageEntry["name"].IsString())
package.name = packageEntry["name"].GetString();
else
throw std::runtime_error(
"Invalid file structure, package \"name\" key missing");
if (packageEntry.HasMember("filename") && packageEntry["filename"].IsString())
package.filename = packageEntry["filename"].GetString();
else
throw std::runtime_error(
"Invalid file structure, package \"filename\" key missing");
if (packageEntry.HasMember("url") && packageEntry["url"].IsString())
package.url = packageEntry["url"].GetString();
else
throw std::runtime_error(
"Invalid file structure, package \"url\" key missing");
if (packageEntry.HasMember("md5") && packageEntry["md5"].IsString())
package.md5 = packageEntry["md5"].GetString();
else
throw std::runtime_error(
"Invalid file structure, package \"md5\" key missing");
if (packageEntry.HasMember("message") && packageEntry["message"].IsString())
package.message = packageEntry["message"].GetString();
else
throw std::runtime_error(
"Invalid file structure, package \"message\" key missing");
release.packages.emplace_back(package);
}
}
else {
throw std::runtime_error("Invalid file structure");
}
if (releaseType == "stable")
mStableRelease = std::move(release);
else
mPrerelease = std::move(release);
}
else {
throw std::runtime_error("Invalid file structure, release type \"" + releaseType +
"\" missing");
}
}
if (mPrerelease.version == "") {
LOG(LogDebug) << "ApplicationUpdater::parseFile(): Latest stable release is "
<< mStableRelease.version << " (r" << mStableRelease.releaseNum
<< "), no prerelease currently available";
}
else {
LOG(LogDebug) << "ApplicationUpdater::parseFile(): Latest stable release is "
<< mStableRelease.version << " (r" << mStableRelease.releaseNum
<< ") and latest prerelease is " << mPrerelease.version << " (r"
<< mPrerelease.releaseNum << ")";
}
}
void ApplicationUpdater::compareVersions()
{
std::deque<Release*> releaseTypes {&mStableRelease};
if (mPrerelease.version != "") {
#if defined(IS_PRERELEASE)
releaseTypes.emplace_front(&mPrerelease);
#else
if (Settings::getInstance()->getBool("ApplicationUpdaterPrereleases"))
releaseTypes.emplace_front(&mPrerelease);
#endif
}
for (auto& releaseType : releaseTypes) {
bool newVersion {false};
// 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.
if (count_if(releaseType->version.cbegin(), releaseType->version.cend(),
[](char c) { return c == '.'; }) != 2) {
newVersion = true;
}
else {
std::vector<std::string> fileVersion {
Utils::String::delimitedStringToVector(releaseType->version, ".")};
const size_t dashPos {fileVersion.back().find('-')};
if (dashPos != std::string::npos)
fileVersion.back() = fileVersion.back().substr(0, dashPos);
int versionWeight {0};
if (std::stoi(fileVersion.at(0)) > PROGRAM_VERSION_MAJOR)
versionWeight += 8;
else if (std::stoi(fileVersion.at(0)) < PROGRAM_VERSION_MAJOR)
versionWeight -= 8;
if (std::stoi(fileVersion.at(1)) > PROGRAM_VERSION_MINOR)
versionWeight += 4;
else if (std::stoi(fileVersion.at(1)) < PROGRAM_VERSION_MINOR)
versionWeight -= 4;
if (std::stoi(fileVersion.at(2)) > PROGRAM_VERSION_MAINTENANCE)
versionWeight += 2;
else if (std::stoi(fileVersion.at(2)) < PROGRAM_VERSION_MAINTENANCE)
versionWeight -= 2;
// If versions match precisely then fall back to using the release number.
if (versionWeight == 0 && std::stoi(releaseType->releaseNum) > PROGRAM_RELEASE_NUMBER)
++versionWeight;
if (versionWeight > 0)
newVersion = true;
}
if (newVersion) {
std::string message;
for (auto& package : releaseType->packages) {
#if defined(_WIN64)
if (Settings::getInstance()->getBool("PortableMode")) {
if (package.name == "WindowsPortable")
message = package.message;
}
else {
if (package.name == "WindowsInstaller")
message = package.message;
}
#elif defined(MACOS_APPLE_CPU)
if (package.name == "macOSApple")
message = package.message;
#elif defined(MACOS_INTEL_CPU)
if (package.name == "macOSIntel")
message = package.message;
#elif defined(STEAM_DECK)
if (package.name == "LinuxSteamDeckAppImage")
message = package.message;
#elif defined(APPIMAGE_BUILD)
if (package.name == "LinuxAppImage")
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.
message = message.substr(0, 280);
LOG(LogInfo) << "ApplicationUpdater: A new "
<< (releaseType == &mStableRelease ? "stable release" : "prerelease")
<< " is available for download at https://es-de.org: "
<< releaseType->version << " (r" << releaseType->releaseNum
<< "), release date: " << releaseType->date;
mResults.append("New ")
.append(releaseType == &mStableRelease ? "release " : "prerelease ")
.append("available!\n")
.append(releaseType->version)
.append(" (")
.append(releaseType->date)
.append(")\n")
.append("can now be downloaded from\n")
.append("https://es-de.org/");
if (message != "")
mResults.append("\n").append(message);
mResults = Utils::String::toUpper(mResults);
break;
}
}
mCheckedForUpdate = true;
}
void ApplicationUpdater::getResults(std::string& results)
{
mAbortDownload = true;
if (mThread) {
mThread->join();
mThread.reset();
if (mCheckedForUpdate) {
if (mResults != "")
results = mResults;
Settings::getInstance()->setString(
"ApplicationUpdaterLastCheck",
Utils::Time::DateTime(Utils::Time::now()).getIsoString());
Settings::getInstance()->saveFile();
}
}
}

View file

@ -0,0 +1,67 @@
// SPDX-License-Identifier: MIT
//
// EmulationStation Desktop Edition
// ApplicationUpdater.h
//
// Checks for application updates.
// In the future updates will also be downloaded and possibly installed.
//
#ifndef ES_APP_APPLICATION_UPDATER_H
#define ES_APP_APPLICATION_UPDATER_H
#include "AsyncHandle.h"
#include "HttpReq.h"
#include <atomic>
#include <memory>
#include <thread>
#include <vector>
class ApplicationUpdater : public AsyncHandle
{
public:
ApplicationUpdater();
~ApplicationUpdater();
void checkForUpdates();
void updaterThread();
bool downloadFile();
void update() override;
void parseFile();
void compareVersions();
void getResults(std::string& results);
private:
struct Package {
std::string name;
std::string filename;
std::string url;
std::string md5;
std::string message;
};
struct Release {
std::string releaseType;
std::string version;
std::string releaseNum;
std::string date;
std::vector<Package> packages;
};
std::string mUrl;
std::string mResults;
unsigned int mTimer;
unsigned int mMaxTime;
std::atomic<bool> mAbortDownload;
bool mCheckedForUpdate;
std::unique_ptr<std::thread> mThread;
std::unique_ptr<HttpReq> mRequest;
AsyncHandleStatus mStatus;
Release mStableRelease;
Release mPrerelease;
};
#endif // ES_APP_APPLICATION_UPDATER_H

View file

@ -1408,6 +1408,48 @@ void GuiMenu::openOtherOptions()
}
});
#if defined(APPLICATION_UPDATER)
// Application updater frequency.
auto applicationUpdaterFrequency = std::make_shared<OptionListComponent<std::string>>(
getHelpStyle(), "APPLICATION UPDATES", false);
const std::string& selectedUpdaterFrequency {
Settings::getInstance()->getString("ApplicationUpdaterFrequency")};
applicationUpdaterFrequency->add("ALWAYS", "always", selectedUpdaterFrequency == "always");
applicationUpdaterFrequency->add("DAILY", "daily", selectedUpdaterFrequency == "daily");
applicationUpdaterFrequency->add("WEEKLY", "weekly", selectedUpdaterFrequency == "weekly");
applicationUpdaterFrequency->add("MONTHLY", "monthly", selectedUpdaterFrequency == "monthly");
applicationUpdaterFrequency->add("NEVER", "never", selectedUpdaterFrequency == "never");
// If there are no objects returned, then there must be a manually modified entry in the
// configuration file. Simply set updater frequency to "always" in this case.
if (applicationUpdaterFrequency->getSelectedObjects().size() == 0)
applicationUpdaterFrequency->selectEntry(0);
s->addWithLabel("CHECK FOR APPLICATION UPDATES", applicationUpdaterFrequency);
s->addSaveFunc([applicationUpdaterFrequency, s] {
if (applicationUpdaterFrequency->getSelected() !=
Settings::getInstance()->getString("ApplicationUpdaterFrequency")) {
Settings::getInstance()->setString("ApplicationUpdaterFrequency",
applicationUpdaterFrequency->getSelected());
s->setNeedsSaving();
}
});
#endif
#if defined(APPLICATION_UPDATER) && !defined(IS_PRERELEASE)
// Whether to include prereleases when checking for application updates.
auto applicationUpdaterPrereleases = std::make_shared<SwitchComponent>();
applicationUpdaterPrereleases->setState(
Settings::getInstance()->getBool("ApplicationUpdaterPrereleases"));
s->addWithLabel("INCLUDE PRERELEASES IN UPDATE CHECKS", applicationUpdaterPrereleases);
s->addSaveFunc([applicationUpdaterPrereleases, s] {
if (applicationUpdaterPrereleases->getState() !=
Settings::getInstance()->getBool("ApplicationUpdaterPrereleases")) {
Settings::getInstance()->setBool("ApplicationUpdaterPrereleases",
applicationUpdaterPrereleases->getState());
s->setNeedsSaving();
}
});
#endif
#if defined(_WIN64)
// Hide taskbar during the program session.
auto hide_taskbar = std::make_shared<SwitchComponent>();
@ -1591,6 +1633,29 @@ void GuiMenu::openOtherOptions()
});
#endif
#if defined(APPLICATION_UPDATER) && !defined(IS_PRERELEASE)
auto applicationUpdaterFrequencyFunc = [applicationUpdaterFrequency,
applicationUpdaterPrereleases](const std::string&) {
if (applicationUpdaterFrequency->getSelected() == "never") {
applicationUpdaterPrereleases->setEnabled(false);
applicationUpdaterPrereleases->setOpacity(DISABLED_OPACITY);
applicationUpdaterPrereleases->getParent()
->getChild(applicationUpdaterPrereleases->getChildIndex() - 1)
->setOpacity(DISABLED_OPACITY);
}
else {
applicationUpdaterPrereleases->setEnabled(true);
applicationUpdaterPrereleases->setOpacity(1.0f);
applicationUpdaterPrereleases->getParent()
->getChild(applicationUpdaterPrereleases->getChildIndex() - 1)
->setOpacity(1.0f);
}
};
applicationUpdaterFrequencyFunc(std::string());
applicationUpdaterFrequency->setCallback(applicationUpdaterFrequencyFunc);
#endif
s->setSize(mSize);
mWindow->pushGui(s);
}
@ -1673,7 +1738,7 @@ void GuiMenu::addVersionInfo()
mVersion.setFont(Font::get(FONT_SIZE_SMALL));
mVersion.setColor(0x5E5E5EFF);
#if defined(MENU_BUILD_DATE)
#if defined(IS_PRERELEASE)
mVersion.setText("EMULATIONSTATION-DE V" + Utils::String::toUpper(PROGRAM_VERSION_STRING) +
" (Built " + __DATE__ + ")");
#else

View file

@ -17,6 +17,7 @@
// environment and starts listening to SDL events.
//
#include "ApplicationUpdater.h"
#include "AudioManager.h"
#include "CollectionSystemsManager.h"
#include "EmulationStation.h"
@ -62,6 +63,9 @@ namespace
Window* window {nullptr};
int lastTime {0};
#if defined(APPLICATION_UPDATER)
bool noUpdateCheck {false};
#endif
bool forceInputConfig {false};
bool createSystemDirectories {false};
bool settingsNeedSaving {false};
@ -361,6 +365,11 @@ bool parseArgs(int argc, char* argv[])
else if (strcmp(argv[i], "--no-splash") == 0) {
Settings::getInstance()->setBool("SplashScreen", false);
}
#if defined(APPLICATION_UPDATER)
else if (strcmp(argv[i], "--no-update-check") == 0) {
noUpdateCheck = true;
}
#endif
else if (strcmp(argv[i], "--gamelist-only") == 0) {
Settings::getInstance()->setBool("ParseGamelistOnly", true);
settingsNeedSaving = true;
@ -418,6 +427,9 @@ bool parseArgs(int argc, char* argv[])
" --anti-aliasing [0, 2 or 4] Set MSAA anti-aliasing to disabled, 2x or 4x\n"
#endif
" --no-splash Don't show the splash screen during startup\n"
#if defined(APPLICATION_UPDATER)
" --no-update-check Don't check for application updates during startup\n"
#endif
" --gamelist-only Skip automatic game ROM search, only read from gamelist.xml\n"
" --ignore-gamelist Ignore the gamelist.xml files\n"
" --show-hidden-files Show hidden files and folders\n"
@ -587,6 +599,10 @@ int main(int argc, char* argv[])
<< PROGRAM_RELEASE_NUMBER << "), built " << PROGRAM_BUILT_STRING;
if (portableMode) {
LOG(LogInfo) << "Running in portable mode";
Settings::getInstance()->setBool("PortableMode", true);
}
else {
Settings::getInstance()->setBool("PortableMode", false);
}
// Always close the log on exit.
@ -676,6 +692,15 @@ int main(int argc, char* argv[])
renderer = Renderer::getInstance();
window = Window::getInstance();
#if defined(APPLICATION_UPDATER)
std::unique_ptr<ApplicationUpdater> applicationUpdater;
if (!noUpdateCheck) {
applicationUpdater = std::make_unique<ApplicationUpdater>();
applicationUpdater->checkForUpdates();
}
#endif
ViewController::getInstance();
CollectionSystemsManager::getInstance();
Screensaver screensaver;
@ -743,9 +768,14 @@ int main(int argc, char* argv[])
}
if (!SystemData::sStartupExitSignal) {
if (loadSystemsStatus == loadSystemsReturnCode::LOADING_OK)
std::string updaterResults;
if (loadSystemsStatus == loadSystemsReturnCode::LOADING_OK) {
ThemeData::themeLoadedLogOutput();
#if defined(APPLICATION_UPDATER)
if (!noUpdateCheck)
applicationUpdater->getResults(updaterResults);
#endif
}
// Open the input configuration GUI if the force flag was passed from the command line.
if (!loadSystemsStatus) {
if (forceInputConfig) {
@ -762,12 +792,22 @@ int main(int argc, char* argv[])
lastTime = SDL_GetTicks();
#if defined(APPLICATION_UPDATER)
if (!noUpdateCheck)
applicationUpdater.reset();
#endif
LOG(LogInfo) << "Application startup time: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now() - applicationStartTime)
.count()
<< " ms";
#if defined(APPLICATION_UPDATER)
if (updaterResults != "")
ViewController::getInstance()->updateAvailableDialog(updaterResults);
#endif
// Main application loop.
if (!SystemData::sStartupExitSignal) {

View file

@ -208,6 +208,15 @@ void ViewController::invalidAlternativeEmulatorDialog()
"INTERFACE IN THE 'OTHER SETTINGS' MENU"));
}
void ViewController::updateAvailableDialog(const std::string& message)
{
mWindow->pushGui(new GuiMsgBox(getHelpStyle(), message, "OK", nullptr, "", nullptr, "", nullptr,
true, true,
(mRenderer->getIsVerticalOrientation() ?
0.85f :
0.45f * (1.778f / mRenderer->getScreenAspectRatio()))));
}
void ViewController::goToStart(bool playTransition)
{
// Needed to avoid segfaults during emergency shutdown.

View file

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

View file

@ -6,6 +6,7 @@
// HTTP request functions.
// Used by Scraper, GamesDBJSONScraper, GamesDBJSONScraperResources and
// ScreenScraper to download game information and media files.
// Also used by ApplicationUpdater to check for application updates.
//
#include "HttpReq.h"

View file

@ -6,6 +6,7 @@
// HTTP request functions.
// Used by Scraper, GamesDBJSONScraper, GamesDBJSONScraperResources and
// ScreenScraper to download game information and media files.
// Also used by ApplicationUpdater to check for application updates.
//
#ifndef ES_CORE_HTTP_REQ_H

View file

@ -5,6 +5,7 @@
//
// Functions to read from and write to the configuration file es_settings.xml.
// The default values for the application settings are defined here as well.
// This class is not thread safe.
//
#include "Settings.h"
@ -40,6 +41,7 @@ namespace
"Debug", // --debug
// These options are only used internally during the application session:
"PortableMode",
"DebugGrid",
"DebugText",
"DebugImage",
@ -253,6 +255,8 @@ void Settings::setDefaults()
mStringMap["KeyboardQuitShortcut"] = {"AltF4", "AltF4"};
#endif
mStringMap["SaveGamelistsMode"] = {"always", "always"};
mStringMap["ApplicationUpdaterFrequency"] = {"always", "always"};
mBoolMap["ApplicationUpdaterPrereleases"] = {false, false};
#if defined(_WIN64)
mBoolMap["HideTaskbar"] = {false, false};
#endif
@ -322,6 +326,8 @@ void Settings::setDefaults()
//
mStringMap["ApplicationVersion"] = {"", ""};
mStringMap["ApplicationUpdaterLastCheck"] = {"", ""};
mBoolMap["PortableMode"] = {false, false};
mBoolMap["DebugGrid"] = {false, false};
mBoolMap["DebugText"] = {false, false};
mBoolMap["DebugImage"] = {false, false};

View file

@ -5,6 +5,7 @@
//
// Functions to read from and write to the configuration file es_settings.xml.
// The default values for the application settings are defined here as well.
// This class is not thread safe.
//
#ifndef ES_CORE_SETTINGS_H