impr: Better auto updater, added support for updating nightlies

This commit is contained in:
WerWolv
2025-08-08 21:25:52 +02:00
parent 9698232f69
commit 82ceb2e11f
7 changed files with 220 additions and 144 deletions

View File

@@ -7,14 +7,11 @@
using namespace std::literals::string_literals;
std::string getUpdateUrl(std::string_view versionType, std::string_view operatingSystem) {
std::string getArtifactUrl(std::string_view artifactEnding, hex::ImHexApi::System::UpdateType updateType) {
// Get the latest version info from the ImHex API
const auto response = hex::HttpRequest("GET",
ImHexApiURL + fmt::format("/update/{}/{}",
versionType,
operatingSystem
)
).execute().get();
GitHubApiURL + "/releases"s
).execute().get();
const auto &data = response.getData();
@@ -25,7 +22,30 @@ std::string getUpdateUrl(std::string_view versionType, std::string_view operatin
return { };
}
return data;
try {
const auto json = nlohmann::json::parse(data);
for (const auto &release : json) {
if (updateType == hex::ImHexApi::System::UpdateType::Stable && !release["target_commitish"].get<std::string>().starts_with("releases/v"))
continue;
if (updateType == hex::ImHexApi::System::UpdateType::Nightly && release["tag_name"].get<std::string>() != "nightly")
continue;
// Loop over all assets in the release
for (const auto &asset : release["assets"]) {
// Check if the asset name ends with the specified artifact ending
if (asset["name"].get<std::string>().ends_with(artifactEnding)) {
return asset["browser_download_url"].get<std::string>();
}
}
}
hex::log::error("No suitable artifact found for ending: {}", artifactEnding);
return { };
} catch (const std::exception &e) {
hex::log::error("Failed to parse latest version info: {}", e.what());
return { };
}
}
std::optional<std::fs::path> downloadUpdate(const std::string &url) {
@@ -40,20 +60,20 @@ std::optional<std::fs::path> downloadUpdate(const std::string &url) {
const auto &data = response.getData();
const auto updateFileName = wolv::util::splitString(url, "/").back();
// Write the update to a file
std::fs::path filePath;
{
constexpr static auto UpdateFileName = "update.hexupd";
// Loop over all available paths
wolv::io::File file;
for (const auto &path : hex::paths::Config.write()) {
// Remove any existing update files
wolv::io::fs::remove(path / UpdateFileName);
wolv::io::fs::remove(path / updateFileName);
// If a valid location hasn't been found already, try to create a new file
if (!file.isValid())
file = wolv::io::File(path / UpdateFileName, wolv::io::File::Mode::Create);
file = wolv::io::File(path / updateFileName, wolv::io::File::Mode::Create);
}
// If the update data can't be written to any of the default paths, the update cannot continue
@@ -74,67 +94,64 @@ std::optional<std::fs::path> downloadUpdate(const std::string &url) {
return filePath;
}
std::string getUpdateType() {
#if defined(__x86_64__)
#define ARCH_DEPENDENT(x86_64, arm64) x86_64
#elif defined(__arm64__)
#define ARCH_DEPENDENT(x86_64, arm64) arm64
#else
#define ARCH_DEPENDENT(x86_64, arm64) ""
#endif
std::string_view getUpdateArtifactEnding() {
#if defined (OS_WINDOWS)
if (!hex::ImHexApi::System::isPortableVersion())
return "win-msi";
if (!hex::ImHexApi::System::isPortableVersion()) {
return ARCH_DEPENDENT("Windows-x86_64.msi", "Windows-arm64.msi");
}
#elif defined (OS_MACOS)
#if defined(__x86_64__)
return "macos-dmg-x86";
#elif defined(__arm64__)
return "macos-dmg-arm";
#endif
return ARCH_DEPENDENT("macOS-x86_64.dmg", "macOS-arm64.dmg");
#elif defined (OS_LINUX)
if (hex::executeCommand("grep 'ID=ubuntu' /etc/os-release") == 0) {
if (hex::executeCommand("grep 'VERSION_ID=\"24.04\"' /etc/os-release") == 0)
return "linux-deb-24.04";
return ARCH_DEPENDENT("Ubuntu-24.04-x86_64.deb", "");
else if (hex::executeCommand("grep 'VERSION_ID=\"24.10\"' /etc/os-release") == 0)
return "linux-deb-24.10";
return ARCH_DEPENDENT("Ubuntu-24.10-x86_64.deb", "");
else if (hex::executeCommand("grep 'VERSION_ID=\"25.04\"' /etc/os-release") == 0)
return ARCH_DEPENDENT("Ubuntu-25.04-x86_64.deb", "");
} else if (hex::executeCommand("grep 'ID=fedora' /etc/os-release") == 0) {
if (hex::executeCommand("grep 'VERSION_ID=\"40\"' /etc/os-release") == 0)
return "linux-rpm-40";
else if (hex::executeCommand("grep 'VERSION_ID=\"41\"' /etc/os-release") == 0)
return "linux-rpm-41";
if (hex::executeCommand("grep 'VERSION_ID=\"41\"' /etc/os-release") == 0)
return ARCH_DEPENDENT("Fedora-41-x86_64.rpm", "");
else if (hex::executeCommand("grep 'VERSION_ID=\"42\"' /etc/os-release") == 0)
return ARCH_DEPENDENT("Fedora-42-x86_64.rpm", "");
else if (hex::executeCommand("grep 'VERSION_ID=\"rawhide\"' /etc/os-release") == 0)
return "linux-rpm-rawhide";
return ARCH_DEPENDENT("Fedora-rawhide-x86_64.rpm", "");
} else if (hex::executeCommand("grep '^NAME=\"Arch Linux\"' /etc/os-release") == 0) {
return "linux-arch";
return ARCH_DEPENDENT("ArchLinux-x86_64.pkg.tar.zst", "");
}
#endif
return "";
}
int installUpdate(const std::string &type, std::fs::path updatePath) {
int installUpdate(const std::fs::path &updatePath) {
struct UpdateHandler {
const char *type;
const char *extension;
const char *ending;
const char *command;
};
constexpr static auto UpdateHandlers = {
UpdateHandler { "win-msi", ".msi", "msiexec /i \"{}\" /qb" },
UpdateHandler { "macos-dmg-x86", ".dmg", "hdiutil attach -autoopen \"{}\"" },
UpdateHandler { "macos-dmg-arm", ".dmg", "hdiutil attach -autoopen \"{}\"" },
UpdateHandler { "linux-deb-24.04", ".deb", "sudo apt update && sudo apt install -y --fix-broken \"{}\"" },
UpdateHandler { "linux-deb-24.10", ".deb", "sudo apt update && sudo apt install -y --fix-broken \"{}\"" },
UpdateHandler { "linux-rpm-40", ".rpm", "sudo rpm -i \"{}\"" },
UpdateHandler { "linux-rpm-41", ".rpm", "sudo rpm -i \"{}\"" },
UpdateHandler { "linux-rpm-rawhide", ".rpm", "sudo rpm -i \"{}\"" },
UpdateHandler { "linux-arch", ".zst", "sudo pacman -Syy && sudo pacman -U --noconfirm \"{}\"" }
UpdateHandler { ".msi", "msiexec /i \"{}\" /qb" },
UpdateHandler { ".dmg", "hdiutil attach -autoopen \"{}\"" },
UpdateHandler { ".deb", "sudo apt update && sudo apt install -y --fix-broken \"{}\"" },
UpdateHandler { ".rpm", "sudo rpm -i \"{}\"" },
UpdateHandler { ".pkg.tar.zst", "sudo pacman -Syy && sudo pacman -U --noconfirm \"{}\"" }
};
const auto updateFileName = wolv::util::toUTF8String(updatePath.filename());
for (const auto &handler : UpdateHandlers) {
if (type == handler.type) {
// Rename the update file to the correct extension
const auto originalPath = updatePath;
updatePath.replace_extension(handler.extension);
hex::log::info("Moving update package from {} to {}", originalPath.string(), updatePath.string());
std::fs::rename(originalPath, updatePath);
if (updateFileName.ends_with(handler.ending)) {
// Install the update using the correct command
const auto command = fmt::format(fmt::runtime(handler.command), updatePath.string());
hex::log::info("Starting update process with command: '{}'", command);
hex::startProgram(command);
@@ -152,6 +169,8 @@ int installUpdate(const std::string &type, std::fs::path updatePath) {
}
int main(int argc, char **argv) {
hex::TaskManager::setCurrentThreadName("ImHex Updater");
hex::TaskManager::setMainThreadId(std::this_thread::get_id());
hex::log::impl::enableColorPrinting();
// Check we have the correct number of arguments
@@ -161,35 +180,50 @@ int main(int argc, char **argv) {
}
// Read the version type from the arguments
const auto versionType = argv[1];
hex::log::info("Updater started with version type: {}", versionType);
const std::string_view versionTypeString = argv[1];
hex::log::info("Updater started with version type: {}", versionTypeString);
// Query the update type
const auto updateType = getUpdateType();
hex::log::info("Detected OS String: {}", updateType);
// Make sure we got a valid update type
if (updateType.empty()) {
hex::log::error("Failed to detect installation type");
// Convert the version type string to the enum value
hex::ImHexApi::System::UpdateType updateType;
if (versionTypeString == "stable") {
updateType = hex::ImHexApi::System::UpdateType::Stable;
} else if (versionTypeString == "nightly") {
updateType = hex::ImHexApi::System::UpdateType::Nightly;
} else {
hex::log::error("Invalid version type: {}", versionTypeString);
return EXIT_FAILURE;
}
// Get the url to the requested update from the ImHex API
const auto updateUrl = getUpdateUrl(versionType, updateType);
if (updateUrl.empty()) {
hex::log::error("Failed to get update URL");
// Get the artifact name ending based on the current platform and architecture
const auto artifactEnding = getUpdateArtifactEnding();
if (artifactEnding.empty()) {
hex::log::error("Updater artifact ending is empty");
return EXIT_FAILURE;
}
hex::log::info("Update URL found: {}", updateUrl);
// Get the URL for the correct update artifact
const auto updateArtifactUrl = getArtifactUrl(artifactEnding, updateType);
if (updateArtifactUrl.empty()) {
// If the current artifact cannot be updated, open the latest release page in the browser
hex::log::warn("Failed to get update artifact URL for ending: {}", artifactEnding);
hex::log::info("Opening release page in browser to allow manual update");
switch (updateType) {
case hex::ImHexApi::System::UpdateType::Stable:
hex::openWebpage("https://github.com/WerWolv/ImHex/releases/latest");
break;
case hex::ImHexApi::System::UpdateType::Nightly:
hex::openWebpage("https://github.com/WerWolv/ImHex/releases/tag/nightly");
break;
}
// Download the update file
const auto updatePath = downloadUpdate(updateUrl);
if (!updatePath.has_value())
return EXIT_FAILURE;
}
hex::log::info("Downloaded update successfully");
// Download the update artifact
const auto updatePath = downloadUpdate(updateArtifactUrl);
// Install the update
return installUpdate(updateType, *updatePath);
return installUpdate(*updatePath);
}