From 0870ab4d3ccd906e7817f24daa79ff27fea7bcf4 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Sat, 9 Aug 2025 23:46:15 +0200 Subject: [PATCH] feat: Add option to check for updates to the Extras menu --- lib/libimhex/include/hex/api/imhex_api.hpp | 6 ++ lib/libimhex/source/api/imhex_api.cpp | 70 +++++++++++++++++++ plugins/builtin/romfs/lang/en_US.json | 3 + plugins/builtin/source/content/init_tasks.cpp | 64 +---------------- .../source/content/main_menu_items.cpp | 17 +++++ 5 files changed, 99 insertions(+), 61 deletions(-) diff --git a/lib/libimhex/include/hex/api/imhex_api.hpp b/lib/libimhex/include/hex/api/imhex_api.hpp index 17f80f815..632fc7122 100644 --- a/lib/libimhex/include/hex/api/imhex_api.hpp +++ b/lib/libimhex/include/hex/api/imhex_api.hpp @@ -680,6 +680,12 @@ EXPORT_MODULE namespace hex { */ bool isNightlyBuild(); + /** + * @brief Checks if there's an update available for the current version of ImHex + * @return Optional string returning the version string of the new version, or std::nullopt if no update is available + */ + std::optional checkForUpdate(); + enum class UpdateType { Stable, Nightly diff --git a/lib/libimhex/source/api/imhex_api.cpp b/lib/libimhex/source/api/imhex_api.cpp index e1b86f91b..ecca3a9b6 100644 --- a/lib/libimhex/source/api/imhex_api.cpp +++ b/lib/libimhex/source/api/imhex_api.cpp @@ -26,8 +26,11 @@ #include #include #include +#include +#include #include +#include #if defined(OS_WINDOWS) #include @@ -941,6 +944,73 @@ namespace hex { return getImHexVersion().nightly(); } + std::optional checkForUpdate() { + #if defined(OS_WEB) + return std::nullopt; + #else + if (ImHexApi::System::isNightlyBuild()) { + HttpRequest request("GET", GitHubApiURL + std::string("/releases/tags/nightly")); + request.setTimeout(10000); + + // Query the GitHub API for the latest release version + auto response = request.execute().get(); + if (response.getStatusCode() != 200) + return std::nullopt; + + nlohmann::json releases; + try { + releases = nlohmann::json::parse(response.getData()); + } catch (const std::exception &) { + return std::nullopt; + } + + // Check if the response is valid + if (!releases.contains("assets") || !releases["assets"].is_array()) + return std::nullopt; + + const auto firstAsset = releases["assets"].front(); + if (!firstAsset.is_object() || !firstAsset.contains("updated_at")) + return std::nullopt; + + const auto nightlyUpdateTime = hex::parseTime("%Y-%m-%dT%H:%M:%SZ", firstAsset["updated_at"].get()); + const auto imhexBuildTime = ImHexApi::System::getBuildTime(); + if (nightlyUpdateTime.has_value() && imhexBuildTime.has_value() && *nightlyUpdateTime > *imhexBuildTime) { + return "Nightly"; + } + } else { + HttpRequest request("GET", GitHubApiURL + std::string("/releases/latest")); + + // Query the GitHub API for the latest release version + auto response = request.execute().get(); + if (response.getStatusCode() != 200) + return std::nullopt; + + nlohmann::json releases; + try { + releases = nlohmann::json::parse(response.getData()); + } catch (const std::exception &) { + return std::nullopt; + } + + // Check if the response is valid + if (!releases.contains("tag_name") || !releases["tag_name"].is_string()) + return std::nullopt; + + // Convert the current version string to a format that can be compared to the latest release + auto currVersion = "v" + ImHexApi::System::getImHexVersion().get(false); + + // Get the latest release version string + auto latestVersion = releases["tag_name"].get(); + + // Check if the latest release is different from the current version + if (latestVersion != currVersion) + return latestVersion; + } + + return std::nullopt; + #endif + } + bool updateImHex(UpdateType updateType) { #if defined(OS_WEB) switch (updateType) { diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index 5bbd0fe2e..04af01b09 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -128,6 +128,7 @@ "hex.builtin.view.hex_editor.menu.edit.redo": "Redo", "hex.builtin.view.hex_editor.menu.edit.undo": "Undo", "hex.builtin.menu.extras": "Extras", + "hex.builtin.menu.extras.check_for_update": "Check for Updates", "hex.builtin.menu.extras.switch_to_stable": "Downgrade to Release", "hex.builtin.menu.extras.switch_to_nightly": "Update to Nightly", "hex.builtin.menu.file": "File", @@ -411,6 +412,8 @@ "hex.builtin.popup.blocking_task.desc": "A task is currently executing.", "hex.builtin.popup.save_layout.title": "Save Layout", "hex.builtin.popup.save_layout.desc": "Enter a name under which to save the current layout.", + "hex.builtin.popup.no_update_available": "No new update available", + "hex.builtin.popup.update_available": "A new version of ImHex is available!\n\nWould you like to update to '{0}'?", "hex.builtin.popup.waiting_for_tasks.desc": "There are still tasks running in the background.\nImHex will close after they are finished.", "hex.builtin.provider.rename": "Rename", "hex.builtin.provider.rename.desc": "Enter a name for this data source.", diff --git a/plugins/builtin/source/content/init_tasks.cpp b/plugins/builtin/source/content/init_tasks.cpp index a792ce1db..cbbb5d3aa 100644 --- a/plugins/builtin/source/content/init_tasks.cpp +++ b/plugins/builtin/source/content/init_tasks.cpp @@ -33,67 +33,9 @@ namespace hex::plugin::builtin { // Check if we should check for updates TaskManager::createBackgroundTask("Update Check", [] { - std::string updateString; - if (ImHexApi::System::isNightlyBuild()) { - HttpRequest request("GET", GitHubApiURL + "/releases/tags/nightly"s); - request.setTimeout(10000); + const auto updateString = ImHexApi::System::checkForUpdate(); - // Query the GitHub API for the latest release version - auto response = request.execute().get(); - if (response.getStatusCode() != 200) - return; - - nlohmann::json releases; - try { - releases = nlohmann::json::parse(response.getData()); - } catch (const std::exception &) { - return; - } - - // Check if the response is valid - if (!releases.contains("assets") || !releases["assets"].is_array()) - return; - - const auto firstAsset = releases["assets"].front(); - if (!firstAsset.is_object() || !firstAsset.contains("updated_at")) - return; - - const auto nightlyUpdateTime = hex::parseTime("%Y-%m-%dT%H:%M:%SZ", firstAsset["updated_at"].get()); - const auto imhexBuildTime = ImHexApi::System::getBuildTime(); - if (nightlyUpdateTime.has_value() && imhexBuildTime.has_value() && *nightlyUpdateTime > *imhexBuildTime) { - updateString = "Nightly"; - } - } else { - HttpRequest request("GET", GitHubApiURL + "/releases/latest"s); - - // Query the GitHub API for the latest release version - auto response = request.execute().get(); - if (response.getStatusCode() != 200) - return; - - nlohmann::json releases; - try { - releases = nlohmann::json::parse(response.getData()); - } catch (const std::exception &) { - return; - } - - // Check if the response is valid - if (!releases.contains("tag_name") || !releases["tag_name"].is_string()) - return; - - // Convert the current version string to a format that can be compared to the latest release - auto currVersion = "v" + ImHexApi::System::getImHexVersion().get(false); - - // Get the latest release version string - auto latestVersion = releases["tag_name"].get(); - - // Check if the latest release is different from the current version - if (latestVersion != currVersion) - updateString = latestVersion; - } - - if (updateString.empty()) + if (!updateString.has_value()) return; TaskManager::doLater([updateString] { @@ -101,7 +43,7 @@ namespace hex::plugin::builtin { ImHexApi::System::updateImHex(ImHexApi::System::isNightlyBuild() ? ImHexApi::System::UpdateType::Nightly : ImHexApi::System::UpdateType::Stable); }); - ui::ToastInfo::open(fmt::format("hex.builtin.welcome.update.desc"_lang, updateString)); + ui::ToastInfo::open(fmt::format("hex.builtin.welcome.update.desc"_lang, *updateString)); }); }); diff --git a/plugins/builtin/source/content/main_menu_items.cpp b/plugins/builtin/source/content/main_menu_items.cpp index 2a4408cae..78c3e6ac4 100644 --- a/plugins/builtin/source/content/main_menu_items.cpp +++ b/plugins/builtin/source/content/main_menu_items.cpp @@ -28,6 +28,7 @@ #include #include +#include using namespace std::literals::string_literals; using namespace wolv::literals; @@ -634,6 +635,22 @@ namespace hex::plugin::builtin { static void createExtrasMenu() { ContentRegistry::Interface::registerMainMenuItem("hex.builtin.menu.extras", 5000); + ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.extras" }, 2600); + + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.extras", "hex.builtin.menu.extras.check_for_update" }, ICON_VS_SYNC, 2700, Shortcut::None, [] { + TaskManager::createBackgroundTask("Checking for updates", [] { + auto versionString = ImHexApi::System::checkForUpdate(); + if (!versionString.has_value()) { + ui::ToastInfo::open("hex.builtin.popup.no_update_available"_lang); + return; + } + + ui::PopupQuestion::open(fmt::format(fmt::runtime("hex.builtin.popup.update_available"_lang.get()), versionString.value()), [] { + ImHexApi::System::updateImHex(ImHexApi::System::isNightlyBuild() ? ImHexApi::System::UpdateType::Nightly : ImHexApi::System::UpdateType::Stable); + }, [] { }); + }); + }); + if (ImHexApi::System::isNightlyBuild()) { ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.extras", "hex.builtin.menu.extras.switch_to_stable" }, ICON_VS_ROCKET, 2750, Shortcut::None, [] { ImHexApi::System::updateImHex(ImHexApi::System::UpdateType::Stable);