feat: Add option to check for updates to the Extras menu

This commit is contained in:
WerWolv
2025-08-09 23:46:15 +02:00
parent 6be0eeff72
commit 0870ab4d3c
5 changed files with 99 additions and 61 deletions

View File

@@ -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<std::string> checkForUpdate();
enum class UpdateType {
Stable,
Nightly

View File

@@ -26,8 +26,11 @@
#include <set>
#include <algorithm>
#include <GLFW/glfw3.h>
#include <hex/api_urls.hpp>
#include <hex/helpers/http_requests.hpp>
#include <hex/helpers/utils_macos.hpp>
#include <nlohmann/json.hpp>
#if defined(OS_WINDOWS)
#include <windows.h>
@@ -941,6 +944,73 @@ namespace hex {
return getImHexVersion().nightly();
}
std::optional<std::string> 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<std::string>());
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<std::string>();
// 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) {

View File

@@ -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.",

View File

@@ -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<std::string>());
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<std::string_view>();
// 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));
});
});

View File

@@ -28,6 +28,7 @@
#include <hex/helpers/menu_items.hpp>
#include <GLFW/glfw3.h>
#include <popups/popup_question.hpp>
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);