From 676b1aabfcd5f755ba82da963763410d7842ddc6 Mon Sep 17 00:00:00 2001 From: crueter Date: Tue, 28 Apr 2026 20:42:23 +0200 Subject: [frontend] Built-in auto updater (#3845) Checks latest release and opens a dialog containing the changelog, and allow the user to select a specific build to download. After downloading, it prompts the user to open it. On Windows, this just opens up the zip in File Explorer. In the future setup files will be available. On macOS this opens up the DMG in Finder so the user can drag it to the Applications folder. Android retains the auto-update functionality from before, but updated to the new scheme. Body/View on Forgejo are not implemented, that should be in a future PR. Additionally, moved some common httplib incantations to `Common::Net`. This will serve as the common network accessor and JSON parser from here on out. TODO: - [x] android :( - [x] Search for builds based on keywords, with weights towards certain builds (e.g. macOS will search for dmg then tar.gz, windows msvc then mingw/exe then zip, etc.) - [x] remove linux leftovers - [x] don't allow asset selection on platforms w/o assets - [x] nightly changelog should be in the real FUTURE IMPLEMENTATION: - [ ] Body/View on Forgejo for Android - [ ] Setup files for Windows (Eden/nightly are separate) -- maybe portable/setup selector? - [ ] Something else I'm forgetting Signed-off-by: crueter Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3845 --- src/frontend_common/update_checker.cpp | 143 +++++---------------------------- 1 file changed, 21 insertions(+), 122 deletions(-) (limited to 'src/frontend_common/update_checker.cpp') diff --git a/src/frontend_common/update_checker.cpp b/src/frontend_common/update_checker.cpp index 71a94f38d4..cccced5f60 100644 --- a/src/frontend_common/update_checker.cpp +++ b/src/frontend_common/update_checker.cpp @@ -5,146 +5,45 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#ifdef NIGHTLY_BUILD #include #include +#endif #include -#include "common/logging.h" +#include "common/net/net.h" #include "common/scm_rev.h" #include "update_checker.h" -#include "common/httplib.h" - -#ifdef YUZU_BUNDLED_OPENSSL -#include -#endif - -#include -#include -#include - -std::optional UpdateChecker::GetResponse(std::string url, std::string path) -{ - try { - constexpr std::size_t timeout_seconds = 15; - - std::unique_ptr client = std::make_unique(url); - client->set_connection_timeout(timeout_seconds); - client->set_read_timeout(timeout_seconds); - client->set_write_timeout(timeout_seconds); - -#ifdef YUZU_BUNDLED_OPENSSL - client->load_ca_cert_store(kCert, sizeof(kCert)); -#endif +#include "common/logging.h" - if (client == nullptr) { - LOG_ERROR(Frontend, "Invalid URL {}{}", url, path); - return {}; - } +std::optional UpdateChecker::GetUpdate() { + const auto latest = Common::Net::GetLatestRelease(); + if (!latest) return std::nullopt; - httplib::Request request{ - .method = "GET", - .path = path, - }; + LOG_INFO(Frontend, "Received update {}", latest->title); - client->set_follow_location(true); - httplib::Result result = client->send(request); +#ifdef NIGHTLY_BUILD + std::vector result; - if (!result) { - LOG_ERROR(Frontend, "GET to {}{} returned null", url, path); - return {}; - } + boost::split(result, latest->tag, boost::is_any_of(".")); + if (result.size() != 2) + return std::nullopt; - const auto &response = result.value(); - if (response.status >= 400) { - LOG_ERROR(Frontend, - "GET to {}{} returned error status code: {}", - url, - path, - response.status); - return {}; - } - if (!response.headers.contains("content-type")) { - LOG_ERROR(Frontend, "GET to {}{} returned no content", url, path); - return {}; - } + const std::string tag = result[1]; - return response.body; - } catch (std::exception &e) { - LOG_ERROR(Frontend, - "GET to {}{} failed during update check: {}", - url, - path, - e.what()); + boost::split(result, std::string{Common::g_build_version}, boost::is_any_of("-")); + if (result.empty()) return std::nullopt; - } -} -std::optional UpdateChecker::GetLatestRelease() { -#ifdef YUZU_BUNDLED_OPENSSL - const auto update_check_url = fmt::format("https://{}", Common::g_build_auto_update_api); + const std::string build = result[0]; #else - const auto update_check_url = std::string{Common::g_build_auto_update_api}; + const std::string tag = latest->tag; + const std::string build = Common::g_build_version; #endif - auto update_check_path = std::string{Common::g_build_auto_update_api_path}; - try { - const auto response = UpdateChecker::GetResponse(update_check_url, update_check_path); - - if (!response) - return {}; - - const std::string latest_tag = nlohmann::json::parse(response.value()).at("tag_name"); - const std::string latest_name = nlohmann::json::parse(response.value()).at("name"); - - return Update{latest_tag, latest_name}; - } catch (nlohmann::detail::out_of_range&) { - LOG_ERROR(Frontend, - "Parsing JSON response from {}{} failed during update check: " - "nlohmann::detail::out_of_range", - update_check_url, - update_check_path); - return {}; - } catch (nlohmann::detail::type_error&) { - LOG_ERROR(Frontend, - "Parsing JSON response from {}{} failed during update check: " - "nlohmann::detail::type_error", - update_check_url, - update_check_path); - return {}; - } -} - -std::optional UpdateChecker::GetUpdate() { - const std::optional latest_release_tag = - UpdateChecker::GetLatestRelease(); - - if (!latest_release_tag) - goto empty; - - { - std::string tag, build; - if (Common::g_is_nightly_build) { - std::vector result; - - boost::split(result, latest_release_tag->tag, boost::is_any_of(".")); - if (result.size() != 2) - goto empty; - tag = result[1]; - - boost::split(result, std::string{Common::g_build_version}, boost::is_any_of("-")); - if (result.empty()) - goto empty; - build = result[0]; - } else { - tag = latest_release_tag->tag; - build = Common::g_build_version; - } - - if (tag != build) - return latest_release_tag.value(); - } + if (tag != build) + return latest; -empty: return std::nullopt; } -- cgit v1.2.3