Add Windows portable auto-update from Forgejo continuous releases.
Some checks failed
Windows Release / windows-x86_64 (push) Failing after 1h12m0s
Some checks failed
Windows Release / windows-x86_64 (push) Failing after 1h12m0s
Poll windows-continuous by git SHA, download the matching zip, and apply it in place so friends stay current without manual installs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
7f34283537
commit
cdc4c5cb30
13 changed files with 371 additions and 22 deletions
3
.github/workflows/windows_release.yml
vendored
3
.github/workflows/windows_release.yml
vendored
|
|
@ -105,6 +105,7 @@ jobs:
|
|||
shell: pwsh
|
||||
env:
|
||||
ZIP_NAME: ${{ steps.ver.outputs.zip_name }}
|
||||
ASSET_VER: ${{ steps.ver.outputs.asset_ver }}
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
$exe = Get-ChildItem -Path . -Recurse -Filter infinipaint.exe |
|
||||
|
|
@ -115,6 +116,8 @@ jobs:
|
|||
Remove-Item -Recurse -Force $outDir -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType Directory -Force -Path $outDir | Out-Null
|
||||
Copy-Item $exe.FullName (Join-Path $outDir "infinipaint.exe")
|
||||
# Identity file for humans / debugging (app also embeds SHA at compile time).
|
||||
Set-Content -Path (Join-Path $outDir "version.txt") -Value $env:ASSET_VER -Encoding ascii
|
||||
$dllDir = Join-Path (Get-Location) "windowsinstall\dlls-x86_64"
|
||||
if (Test-Path $dllDir) {
|
||||
Copy-Item (Join-Path $dllDir "*.dll") $outDir -ErrorAction SilentlyContinue
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ set(sources "src/main.cpp"
|
|||
"src/WorldScreenshot.cpp"
|
||||
"src/FileHelpers.cpp"
|
||||
"src/VersionConstants.cpp"
|
||||
"src/WindowsPortableUpdater.cpp"
|
||||
"src/ScaleUpCanvas.cpp"
|
||||
"src/WorldUndoManager.cpp"
|
||||
"src/GUIHolder.cpp"
|
||||
|
|
@ -200,6 +201,30 @@ else()
|
|||
add_executable(main ${sources})
|
||||
endif()
|
||||
|
||||
# Short git SHA for continuous-release identity (Windows auto-update, About).
|
||||
set(INFINIPAINT_GIT_SHA "unknown")
|
||||
find_package(Git QUIET)
|
||||
if(GIT_FOUND)
|
||||
execute_process(
|
||||
COMMAND "${GIT_EXECUTABLE}" rev-parse --short=7 HEAD
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
OUTPUT_VARIABLE INFINIPAINT_GIT_SHA
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET
|
||||
)
|
||||
endif()
|
||||
if(NOT INFINIPAINT_GIT_SHA)
|
||||
set(INFINIPAINT_GIT_SHA "unknown")
|
||||
endif()
|
||||
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/generated")
|
||||
configure_file(
|
||||
"${CMAKE_SOURCE_DIR}/src/BuildCommit.hpp.in"
|
||||
"${CMAKE_BINARY_DIR}/generated/BuildCommit.hpp"
|
||||
@ONLY
|
||||
)
|
||||
target_include_directories(main PRIVATE "${CMAKE_BINARY_DIR}/generated")
|
||||
message(STATUS "InfiniPaint build commit: ${INFINIPAINT_GIT_SHA}")
|
||||
|
||||
# Get libraries
|
||||
|
||||
if(NO_CONAN_BUILD)
|
||||
|
|
@ -382,7 +407,8 @@ else()
|
|||
endif()
|
||||
|
||||
if((NOT EMSCRIPTEN) AND WIN32)
|
||||
target_link_libraries(main dwrite)
|
||||
target_link_libraries(main dwrite shell32)
|
||||
# shell32: ShellExecuteA for portable auto-update helper script
|
||||
# Clip library for retrieving images from windows clipboard
|
||||
set(CLIP_EXAMPLES OFF CACHE BOOL "Compile clip examples")
|
||||
set(CLIP_TESTS OFF CACHE BOOL "Compile clip tests")
|
||||
|
|
|
|||
1
FORK.md
1
FORK.md
|
|
@ -14,6 +14,7 @@ Save format: `INFPNT000009` / version `0.9.0-fork` (not network-compatible with
|
|||
| `.github/workflows/windows_release.yml` | private GitHub mirror | GitHub-hosted **`windows-latest`** | compiles zip + uploads public Forgejo `windows-continuous` |
|
||||
|
||||
**Friends (Windows):** https://forgejo.fsociety00.cc/copilot/infinipaint/releases/tag/windows-continuous — zip entpacken, `infinipaint.exe` starten.
|
||||
Portable Windows builds check that Forgejo tag on startup (if “Check for updates” is on) and can **download + install** the newer zip in place.
|
||||
Details: [docs/windows-runner.md](docs/windows-runner.md).
|
||||
## Bring Everyone Here (host)
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,14 @@ https://forgejo.fsociety00.cc/copilot/infinipaint/releases/tag/windows-continuou
|
|||
|
||||
Portable zip → extract → run `infinipaint.exe`.
|
||||
|
||||
### Auto-update (portable)
|
||||
|
||||
Windows builds embed the short git SHA at configure time. On startup they poll the
|
||||
public Forgejo API for tag `windows-continuous`. If the release commit differs,
|
||||
a dialog offers **Download and install update** (downloads the zip, extracts over
|
||||
the install folder via a helper script after quit, then restarts). Toggle via
|
||||
Settings → check for updates.
|
||||
|
||||
## Workflows
|
||||
|
||||
| File | Runner | Role |
|
||||
|
|
|
|||
3
src/BuildCommit.hpp.in
Normal file
3
src/BuildCommit.hpp.in
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#pragma once
|
||||
// Generated at configure time from git HEAD (short sha).
|
||||
#define INFINIPAINT_BUILD_COMMIT "@INFINIPAINT_GIT_SHA@"
|
||||
|
|
@ -251,6 +251,6 @@ void GlobalConfig::load_licenses() {
|
|||
std::sort(thirdPartyLicenses.begin(), thirdPartyLicenses.end(), [](const auto& a1, const auto& a2) {
|
||||
return std::lexicographical_compare(a1.first.begin(), a1.first.end(), a2.first.begin(), a2.first.end());
|
||||
});
|
||||
ownLicenseText = "InfiniPaint v" + VersionConstants::CURRENT_VERSION_STRING + "\n";
|
||||
ownLicenseText = "InfiniPaint v" + VersionConstants::full_version_string() + "\n";
|
||||
ownLicenseText += read_file_to_string("data/license");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@
|
|||
#endif
|
||||
|
||||
#include <Helpers/Logger.hpp>
|
||||
#include "VersionConstants.hpp"
|
||||
#include "WindowsPortableUpdater.hpp"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#define UPDATE_NOTIFICATION_URL "https://infinipaint.com/updateNotificationVersionAndroid.txt"
|
||||
|
|
@ -121,18 +123,95 @@ void MainProgram::update() {
|
|||
post_callback();
|
||||
}
|
||||
|
||||
void MainProgram::start_windows_portable_update_download() {
|
||||
#ifdef _WIN32
|
||||
if(updateCheckerData.downloadingZip || updateCheckerData.downloadUrl.empty())
|
||||
return;
|
||||
updateCheckerData.downloadingZip = true;
|
||||
updateCheckerData.downloadStatus = "Downloading update…";
|
||||
updateCheckerData.zipDownload = FileDownloader::download_data_from_url(updateCheckerData.downloadUrl);
|
||||
g.gui.set_to_layout();
|
||||
#else
|
||||
(void)0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void MainProgram::update_notification_check() {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef _WIN32
|
||||
// Poll in-flight zip download / apply.
|
||||
if(updateCheckerData.downloadingZip && updateCheckerData.zipDownload) {
|
||||
switch(updateCheckerData.zipDownload->status) {
|
||||
case FileDownloader::DownloadData::Status::IN_PROGRESS: {
|
||||
float p = updateCheckerData.zipDownload->progress.load();
|
||||
if(p > 0.f)
|
||||
updateCheckerData.downloadStatus = "Downloading update… " + std::to_string(int(p * 100.f)) + "%";
|
||||
break;
|
||||
}
|
||||
case FileDownloader::DownloadData::Status::SUCCESS: {
|
||||
updateCheckerData.downloadStatus = "Installing update…";
|
||||
g.gui.set_to_layout();
|
||||
if(!WindowsPortableUpdater::apply_zip_and_restart(updateCheckerData.zipDownload->str)) {
|
||||
updateCheckerData.downloadStatus = "Update install failed (see log).";
|
||||
updateCheckerData.downloadingZip = false;
|
||||
updateCheckerData.zipDownload = nullptr;
|
||||
g.gui.set_to_layout();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FileDownloader::DownloadData::Status::FAILURE:
|
||||
updateCheckerData.downloadStatus = "Download failed.";
|
||||
updateCheckerData.downloadingZip = false;
|
||||
updateCheckerData.zipDownload = nullptr;
|
||||
g.gui.set_to_layout();
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if(!updateCheckerData.updateCheckDone) {
|
||||
if(conf.checkForUpdates) {
|
||||
if(!updateCheckerData.versionFile)
|
||||
if(!updateCheckerData.versionFile) {
|
||||
#ifdef _WIN32
|
||||
// Fork continuous builds: compare git SHA against Forgejo windows-continuous.
|
||||
updateCheckerData.forgejoContinuous = true;
|
||||
updateCheckerData.versionFile = FileDownloader::download_data_from_url(FORGEJO_WINDOWS_CONTINUOUS_API);
|
||||
#else
|
||||
updateCheckerData.versionFile = FileDownloader::download_data_from_url(UPDATE_NOTIFICATION_URL);
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
switch(updateCheckerData.versionFile->status) {
|
||||
case FileDownloader::DownloadData::Status::IN_PROGRESS:
|
||||
break;
|
||||
case FileDownloader::DownloadData::Status::SUCCESS: {
|
||||
updateCheckerData.updateCheckDone = true;
|
||||
#ifdef _WIN32
|
||||
std::string label, commit, zipUrl;
|
||||
if(WindowsPortableUpdater::parse_continuous_release_json(
|
||||
updateCheckerData.versionFile->str, label, commit, zipUrl)) {
|
||||
updateCheckerData.remoteCommit = commit;
|
||||
updateCheckerData.downloadUrl = zipUrl;
|
||||
updateCheckerData.newVersionStr = label.empty()
|
||||
? (VersionConstants::CURRENT_VERSION_STRING + "+" + commit)
|
||||
: label;
|
||||
const std::string& local = VersionConstants::build_commit_short();
|
||||
Logger::get().log(Logger::LogType::INFO,
|
||||
"Forgejo continuous commit=" + commit + " local=" + local);
|
||||
if(local != "unknown" && commit != local.substr(0, commit.size())) {
|
||||
updateCheckerData.showGui = true;
|
||||
Logger::get().log(Logger::LogType::PHONE_USERINFO,
|
||||
"Windows update available: " + updateCheckerData.newVersionStr);
|
||||
g.gui.set_to_layout();
|
||||
}
|
||||
else if(local == "unknown")
|
||||
Logger::get().log(Logger::LogType::INFO, "Local build commit unknown — skip update prompt");
|
||||
else
|
||||
Logger::get().log(Logger::LogType::INFO, "Windows portable build is up to date");
|
||||
}
|
||||
else
|
||||
Logger::get().log(Logger::LogType::INFO, "Could not parse Forgejo windows-continuous release");
|
||||
#else
|
||||
std::optional<VersionNumber> newVersion = version_str_to_version_numbers(updateCheckerData.versionFile->str);
|
||||
std::optional<VersionNumber> currentVersion = VersionConstants::CURRENT_VERSION_NUMBER;
|
||||
if(newVersion.has_value() && currentVersion.has_value()) {
|
||||
|
|
@ -152,6 +231,7 @@ void MainProgram::update_notification_check() {
|
|||
}
|
||||
else
|
||||
Logger::get().log(Logger::LogType::INFO, "Update notification file couldn't be converted to version numbers");
|
||||
#endif
|
||||
updateCheckerData.versionFile = nullptr;
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,11 @@ class MainProgram {
|
|||
public:
|
||||
static constexpr size_t LOG_SIZE = 30;
|
||||
static constexpr const char* UPDATE_DOWNLOAD_URL = "https://infinipaint.com/download.html";
|
||||
/** Public Forgejo continuous Windows release (fork). */
|
||||
static constexpr const char* FORGEJO_WINDOWS_CONTINUOUS_API =
|
||||
"https://forgejo.fsociety00.cc/api/v1/repos/copilot/infinipaint/releases/tags/windows-continuous";
|
||||
static constexpr const char* FORGEJO_WINDOWS_CONTINUOUS_PAGE =
|
||||
"https://forgejo.fsociety00.cc/copilot/infinipaint/releases/tag/windows-continuous";
|
||||
|
||||
InputManager input;
|
||||
|
||||
|
|
@ -198,11 +203,18 @@ class MainProgram {
|
|||
~MainProgram();
|
||||
|
||||
void update_notification_check();
|
||||
void start_windows_portable_update_download();
|
||||
struct UpdateCheckerData {
|
||||
bool showGui = false;
|
||||
bool updateCheckDone = false;
|
||||
std::string newVersionStr;
|
||||
std::string downloadUrl;
|
||||
std::string remoteCommit;
|
||||
bool forgejoContinuous = false;
|
||||
bool downloadingZip = false;
|
||||
std::string downloadStatus;
|
||||
std::shared_ptr<FileDownloader::DownloadData> versionFile;
|
||||
std::shared_ptr<FileDownloader::DownloadData> zipDownload;
|
||||
} updateCheckerData;
|
||||
|
||||
float calculate_gui_scale();
|
||||
|
|
|
|||
|
|
@ -677,27 +677,57 @@ void Toolbar::update_notification_gui() {
|
|||
|
||||
center_obstructing_window_gui("Update notifications GUI", CLAY_SIZING_FIXED(700), CLAY_SIZING_FIT(0), [&] {
|
||||
gui.new_id("update notification gui", [&] {
|
||||
text_label_centered(gui, "Update v" + main.updateCheckerData.newVersionStr + " available!");
|
||||
text_button(gui, "download", "Open download page in web browser", {
|
||||
.wide = true,
|
||||
.onClick = [&]{
|
||||
SDL_OpenURL(MainProgram::UPDATE_DOWNLOAD_URL);
|
||||
main.updateCheckerData.showGui = false;
|
||||
text_label_centered(gui, "Update available!");
|
||||
text_label_centered(gui, main.updateCheckerData.newVersionStr);
|
||||
#ifdef _WIN32
|
||||
if(main.updateCheckerData.forgejoContinuous) {
|
||||
text_label(gui, "Local: " + VersionConstants::full_version_string());
|
||||
if(main.updateCheckerData.downloadingZip) {
|
||||
text_label_centered(gui, main.updateCheckerData.downloadStatus.empty()
|
||||
? "Downloading…"
|
||||
: main.updateCheckerData.downloadStatus);
|
||||
}
|
||||
});
|
||||
text_button(gui, "ignore forever", "Ignore and don't notify again (can be changed in settings)", {
|
||||
.wide = true,
|
||||
.onClick = [&]{
|
||||
main.conf.checkForUpdates = false;
|
||||
main.updateCheckerData.showGui = false;
|
||||
else {
|
||||
text_button(gui, "download install", "Download and install update", {
|
||||
.wide = true,
|
||||
.onClick = [&]{
|
||||
main.start_windows_portable_update_download();
|
||||
}
|
||||
});
|
||||
text_button(gui, "open page", "Open Forgejo release page", {
|
||||
.wide = true,
|
||||
.onClick = [&]{
|
||||
SDL_OpenURL(MainProgram::FORGEJO_WINDOWS_CONTINUOUS_PAGE);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
text_button(gui, "ignore for now", "Ignore for now", {
|
||||
.wide = true,
|
||||
.onClick = [&]{
|
||||
main.updateCheckerData.showGui = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
text_button(gui, "download", "Open download page in web browser", {
|
||||
.wide = true,
|
||||
.onClick = [&]{
|
||||
SDL_OpenURL(MainProgram::UPDATE_DOWNLOAD_URL);
|
||||
main.updateCheckerData.showGui = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
if(!main.updateCheckerData.downloadingZip) {
|
||||
text_button(gui, "ignore forever", "Ignore and don't notify again (can be changed in settings)", {
|
||||
.wide = true,
|
||||
.onClick = [&]{
|
||||
main.conf.checkForUpdates = false;
|
||||
main.updateCheckerData.showGui = false;
|
||||
}
|
||||
});
|
||||
text_button(gui, "ignore for now", "Ignore for now", {
|
||||
.wide = true,
|
||||
.onClick = [&]{
|
||||
main.updateCheckerData.showGui = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,10 +17,20 @@
|
|||
*/
|
||||
|
||||
#include "VersionConstants.hpp"
|
||||
#include "BuildCommit.hpp"
|
||||
#include <stdexcept>
|
||||
|
||||
namespace VersionConstants {
|
||||
|
||||
const std::string& build_commit_short() {
|
||||
static const std::string sha = INFINIPAINT_BUILD_COMMIT;
|
||||
return sha;
|
||||
}
|
||||
|
||||
std::string full_version_string() {
|
||||
return CURRENT_VERSION_STRING + "+" + build_commit_short();
|
||||
}
|
||||
|
||||
VersionNumber header_to_version_number(const std::string& header) {
|
||||
static std::unordered_map<std::string, VersionNumber> m;
|
||||
if(m.empty()) {
|
||||
|
|
|
|||
|
|
@ -31,4 +31,8 @@ namespace VersionConstants {
|
|||
const std::string CURRENT_SAVEFILE_HEADER = "INFPNT000009"; // Change whenever the save file is incompatible with the previous version
|
||||
const std::string CURRENT_VERSION_STRING = "0.9.0-fork";
|
||||
constexpr VersionNumber CURRENT_VERSION_NUMBER(0, 9, 0);
|
||||
/** Short git SHA baked at configure time (`BuildCommit.hpp`). */
|
||||
const std::string& build_commit_short();
|
||||
/** e.g. `0.9.0-fork+7f34283` */
|
||||
std::string full_version_string();
|
||||
}
|
||||
|
|
|
|||
156
src/WindowsPortableUpdater.cpp
Normal file
156
src/WindowsPortableUpdater.cpp
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
#include "WindowsPortableUpdater.hpp"
|
||||
|
||||
#include <Helpers/Logger.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <regex>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <SDL3/SDL.h>
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace WindowsPortableUpdater {
|
||||
|
||||
bool parse_continuous_release_json(
|
||||
const std::string& jsonText,
|
||||
std::string& outLabel,
|
||||
std::string& outCommit,
|
||||
std::string& outZipUrl)
|
||||
{
|
||||
outLabel.clear();
|
||||
outCommit.clear();
|
||||
outZipUrl.clear();
|
||||
try {
|
||||
auto j = nlohmann::json::parse(jsonText);
|
||||
outLabel = j.value("name", "");
|
||||
std::regex shaRe(R"(\+([0-9a-fA-F]{7,40})\b)");
|
||||
std::smatch m;
|
||||
if(std::regex_search(outLabel, m, shaRe))
|
||||
outCommit = m[1].str().substr(0, 7);
|
||||
if(outCommit.empty()) {
|
||||
std::string body = j.value("body", "");
|
||||
if(std::regex_search(body, m, shaRe))
|
||||
outCommit = m[1].str().substr(0, 7);
|
||||
}
|
||||
if(!j.contains("assets") || !j["assets"].is_array())
|
||||
return false;
|
||||
// Prefer the zip that matches the release commit (rolling tag keeps older assets).
|
||||
auto pick_url = [](const nlohmann::json& a) -> std::string {
|
||||
std::string u = a.value("browser_download_url", "");
|
||||
if(u.empty())
|
||||
u = a.value("url", "");
|
||||
return u;
|
||||
};
|
||||
for(auto& a : j["assets"]) {
|
||||
std::string name = a.value("name", "");
|
||||
if(name.find("win-x86_64-portable.zip") == std::string::npos)
|
||||
continue;
|
||||
if(!outCommit.empty() && name.find(outCommit) == std::string::npos)
|
||||
continue;
|
||||
if(outCommit.empty() && std::regex_search(name, m, shaRe))
|
||||
outCommit = m[1].str().substr(0, 7);
|
||||
outZipUrl = pick_url(a);
|
||||
if(!outZipUrl.empty())
|
||||
break;
|
||||
}
|
||||
// Fallback: newest matching zip by created_at if commit filter found nothing.
|
||||
if(outZipUrl.empty()) {
|
||||
std::string bestCreated;
|
||||
for(auto& a : j["assets"]) {
|
||||
std::string name = a.value("name", "");
|
||||
if(name.find("win-x86_64-portable.zip") == std::string::npos)
|
||||
continue;
|
||||
std::string created = a.value("created_at", "");
|
||||
if(created < bestCreated && !bestCreated.empty())
|
||||
continue;
|
||||
std::string url = pick_url(a);
|
||||
if(url.empty())
|
||||
continue;
|
||||
bestCreated = created;
|
||||
outZipUrl = url;
|
||||
if(outCommit.empty() && std::regex_search(name, m, shaRe))
|
||||
outCommit = m[1].str().substr(0, 7);
|
||||
}
|
||||
}
|
||||
return !outCommit.empty() && !outZipUrl.empty();
|
||||
}
|
||||
catch(const std::exception& e) {
|
||||
Logger::get().log(Logger::LogType::INFO, std::string("Windows update JSON parse failed: ") + e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool apply_zip_and_restart(const std::string& zipBytes) {
|
||||
#ifdef _WIN32
|
||||
namespace fs = std::filesystem;
|
||||
try {
|
||||
char* basePath = SDL_GetBasePath();
|
||||
if(!basePath)
|
||||
return false;
|
||||
fs::path appDir = fs::path(basePath);
|
||||
SDL_free(basePath);
|
||||
|
||||
fs::path tempRoot = fs::temp_directory_path() / "infinipaint-update";
|
||||
fs::create_directories(tempRoot);
|
||||
fs::path zipPath = tempRoot / "update.zip";
|
||||
fs::path extractDir = tempRoot / "extracted";
|
||||
fs::path batPath = tempRoot / "apply_update.bat";
|
||||
|
||||
{
|
||||
std::ofstream out(zipPath, std::ios::binary);
|
||||
if(!out)
|
||||
return false;
|
||||
out.write(zipBytes.data(), static_cast<std::streamsize>(zipBytes.size()));
|
||||
}
|
||||
|
||||
DWORD pid = GetCurrentProcessId();
|
||||
std::ofstream bat(batPath);
|
||||
if(!bat)
|
||||
return false;
|
||||
bat << "@echo off\r\n"
|
||||
<< "setlocal\r\n"
|
||||
<< "set \"APPDIR=" << appDir.string() << "\"\r\n"
|
||||
<< "set \"ZIP=" << zipPath.string() << "\"\r\n"
|
||||
<< "set \"OUT=" << extractDir.string() << "\"\r\n"
|
||||
<< "set \"PID=" << pid << "\"\r\n"
|
||||
<< ":wait\r\n"
|
||||
<< "tasklist /FI \"PID eq %PID%\" 2>nul | find \"%PID%\" >nul\r\n"
|
||||
<< "if not errorlevel 1 (\r\n"
|
||||
<< " timeout /t 1 /nobreak >nul\r\n"
|
||||
<< " goto wait\r\n"
|
||||
<< ")\r\n"
|
||||
<< "if exist \"%OUT%\" rmdir /s /q \"%OUT%\"\r\n"
|
||||
<< "mkdir \"%OUT%\" >nul 2>nul\r\n"
|
||||
<< "powershell -NoProfile -ExecutionPolicy Bypass -Command "
|
||||
<< "\"Expand-Archive -Force -Path '%ZIP%' -DestinationPath '%OUT%'\"\r\n"
|
||||
<< "if errorlevel 1 exit /b 1\r\n"
|
||||
<< "xcopy /E /Y /Q \"%OUT%\\*\" \"%APPDIR%\\\" >nul\r\n"
|
||||
<< "start \"\" \"%APPDIR%\\infinipaint.exe\"\r\n"
|
||||
<< "endlocal\r\n";
|
||||
bat.close();
|
||||
|
||||
std::string batStr = batPath.string();
|
||||
HINSTANCE hi = ShellExecuteA(nullptr, "open", batStr.c_str(), nullptr, nullptr, SW_HIDE);
|
||||
if(reinterpret_cast<intptr_t>(hi) <= 32) {
|
||||
Logger::get().log(Logger::LogType::INFO, "ShellExecute apply_update.bat failed");
|
||||
return false;
|
||||
}
|
||||
Logger::get().log(Logger::LogType::INFO, "Windows update script launched; quitting to apply");
|
||||
SDL_Event e{};
|
||||
e.type = SDL_EVENT_QUIT;
|
||||
SDL_PushEvent(&e);
|
||||
return true;
|
||||
}
|
||||
catch(const std::exception& e) {
|
||||
Logger::get().log(Logger::LogType::INFO, std::string("Windows update apply failed: ") + e.what());
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
(void)zipBytes;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
16
src/WindowsPortableUpdater.hpp
Normal file
16
src/WindowsPortableUpdater.hpp
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
/** Windows portable self-update helpers (Forgejo windows-continuous). */
|
||||
namespace WindowsPortableUpdater {
|
||||
/** Parse Forgejo release JSON; fill remote label, commit, zip URL. Returns true if usable. */
|
||||
bool parse_continuous_release_json(
|
||||
const std::string& jsonText,
|
||||
std::string& outLabel,
|
||||
std::string& outCommit,
|
||||
std::string& outZipUrl);
|
||||
|
||||
/** Write zip + apply script, then request process exit. Returns false on I/O failure. */
|
||||
bool apply_zip_and_restart(const std::string& zipBytes);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue