Merge pull request #7844 from spycrab/updatecommon_electric_boogaloo
UpdaterCommon: Move most of the programs here
This commit is contained in:
commit
db2542886a
|
@ -4,100 +4,12 @@
|
||||||
|
|
||||||
#import "AppDelegate.h"
|
#import "AppDelegate.h"
|
||||||
|
|
||||||
#include "Common/FileUtil.h"
|
|
||||||
|
|
||||||
#include "UpdaterCommon/UI.h"
|
|
||||||
#include "UpdaterCommon/UpdaterCommon.h"
|
#include "UpdaterCommon/UpdaterCommon.h"
|
||||||
|
|
||||||
#include <Cocoa/Cocoa.h>
|
#include <Cocoa/Cocoa.h>
|
||||||
#include <OptionParser.h>
|
#include <string>
|
||||||
#include <optional>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
struct Options
|
|
||||||
{
|
|
||||||
std::string this_manifest_url;
|
|
||||||
std::string next_manifest_url;
|
|
||||||
std::string content_store_url;
|
|
||||||
std::string install_base_path;
|
|
||||||
std::optional<std::string> binary_to_restart;
|
|
||||||
std::optional<pid_t> parent_pid;
|
|
||||||
std::optional<std::string> log_file;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::optional<Options> ParseCommandLine(std::vector<std::string>& args)
|
|
||||||
{
|
|
||||||
using optparse::OptionParser;
|
|
||||||
|
|
||||||
OptionParser parser =
|
|
||||||
OptionParser().prog("Dolphin Updater").description("Dolphin Updater binary");
|
|
||||||
|
|
||||||
parser.add_option("--this-manifest-url")
|
|
||||||
.dest("this-manifest-url")
|
|
||||||
.help("URL to the update manifest for the currently installed version.")
|
|
||||||
.metavar("URL");
|
|
||||||
parser.add_option("--next-manifest-url")
|
|
||||||
.dest("next-manifest-url")
|
|
||||||
.help("URL to the update manifest for the to-be-installed version.")
|
|
||||||
.metavar("URL");
|
|
||||||
parser.add_option("--content-store-url")
|
|
||||||
.dest("content-store-url")
|
|
||||||
.help("Base URL of the content store where files to download are stored.")
|
|
||||||
.metavar("URL");
|
|
||||||
parser.add_option("--install-base-path")
|
|
||||||
.dest("install-base-path")
|
|
||||||
.help("Base path of the Dolphin install to be updated.")
|
|
||||||
.metavar("PATH");
|
|
||||||
parser.add_option("--binary-to-restart")
|
|
||||||
.dest("binary-to-restart")
|
|
||||||
.help("Binary to restart after the update is over.")
|
|
||||||
.metavar("PATH");
|
|
||||||
parser.add_option("--log-file")
|
|
||||||
.dest("log-file")
|
|
||||||
.help("File where to log updater debug output.")
|
|
||||||
.metavar("PATH");
|
|
||||||
parser.add_option("--parent-pid")
|
|
||||||
.dest("parent-pid")
|
|
||||||
.type("int")
|
|
||||||
.help("(optional) PID of the parent process. The updater will wait for this process to "
|
|
||||||
"complete before proceeding.")
|
|
||||||
.metavar("PID");
|
|
||||||
|
|
||||||
optparse::Values options = parser.parse_args(args);
|
|
||||||
|
|
||||||
Options opts;
|
|
||||||
|
|
||||||
// Required arguments.
|
|
||||||
std::vector<std::string> required{"this-manifest-url", "next-manifest-url", "content-store-url",
|
|
||||||
"install-base-path"};
|
|
||||||
for (const auto& req : required)
|
|
||||||
{
|
|
||||||
if (!options.is_set(req))
|
|
||||||
{
|
|
||||||
parser.print_help();
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
opts.this_manifest_url = options["this-manifest-url"];
|
|
||||||
opts.next_manifest_url = options["next-manifest-url"];
|
|
||||||
opts.content_store_url = options["content-store-url"];
|
|
||||||
opts.install_base_path = options["install-base-path"];
|
|
||||||
|
|
||||||
// Optional arguments.
|
|
||||||
if (options.is_set("binary-to-restart"))
|
|
||||||
opts.binary_to_restart = options["binary-to-restart"];
|
|
||||||
if (options.is_set("parent-pid"))
|
|
||||||
opts.parent_pid = (pid_t)options.get("parent-pid");
|
|
||||||
if (options.is_set("log-file"))
|
|
||||||
opts.log_file = options["log-file"];
|
|
||||||
|
|
||||||
return opts;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
@interface AppDelegate ()
|
@interface AppDelegate ()
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -106,8 +18,6 @@ std::optional<Options> ParseCommandLine(std::vector<std::string>& args)
|
||||||
|
|
||||||
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification
|
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification
|
||||||
{
|
{
|
||||||
UI::SetVisible(false);
|
|
||||||
|
|
||||||
NSArray* arguments = [[NSProcessInfo processInfo] arguments];
|
NSArray* arguments = [[NSProcessInfo processInfo] arguments];
|
||||||
|
|
||||||
__block std::vector<std::string> args;
|
__block std::vector<std::string> args;
|
||||||
|
@ -116,118 +26,10 @@ std::optional<Options> ParseCommandLine(std::vector<std::string>& args)
|
||||||
args.push_back(std::string([obj UTF8String]));
|
args.push_back(std::string([obj UTF8String]));
|
||||||
}];
|
}];
|
||||||
|
|
||||||
std::optional<Options> maybe_opts = ParseCommandLine(args);
|
|
||||||
|
|
||||||
if (!maybe_opts)
|
|
||||||
{
|
|
||||||
[NSApp terminate:nil];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Options opts = std::move(*maybe_opts);
|
|
||||||
|
|
||||||
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
|
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
|
||||||
dispatch_async(queue, ^{
|
dispatch_async(queue, ^{
|
||||||
if (opts.log_file)
|
RunUpdater(args);
|
||||||
{
|
[NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0];
|
||||||
log_fp = fopen(opts.log_file.value().c_str(), "w");
|
|
||||||
if (!log_fp)
|
|
||||||
log_fp = stderr;
|
|
||||||
else
|
|
||||||
atexit(FlushLog);
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(log_fp, "Updating from: %s\n", opts.this_manifest_url.c_str());
|
|
||||||
fprintf(log_fp, "Updating to: %s\n", opts.next_manifest_url.c_str());
|
|
||||||
fprintf(log_fp, "Install path: %s\n", opts.install_base_path.c_str());
|
|
||||||
|
|
||||||
if (!File::IsDirectory(opts.install_base_path))
|
|
||||||
{
|
|
||||||
FatalError("Cannot find install base path, or not a directory.");
|
|
||||||
|
|
||||||
[NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.parent_pid)
|
|
||||||
{
|
|
||||||
UI::SetDescription("Waiting for Dolphin to quit...");
|
|
||||||
|
|
||||||
fprintf(log_fp, "Waiting for parent PID %d to complete...\n", *opts.parent_pid);
|
|
||||||
|
|
||||||
auto pid = opts.parent_pid.value();
|
|
||||||
|
|
||||||
for (int res = kill(pid, 0); res == 0 || (res < 0 && errno == EPERM); res = kill(pid, 0))
|
|
||||||
{
|
|
||||||
sleep(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(log_fp, "Completed! Proceeding with update.\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
UI::SetVisible(true);
|
|
||||||
|
|
||||||
UI::SetDescription("Fetching and parsing manifests...");
|
|
||||||
|
|
||||||
Manifest this_manifest, next_manifest;
|
|
||||||
{
|
|
||||||
std::optional<Manifest> maybe_manifest = FetchAndParseManifest(opts.this_manifest_url);
|
|
||||||
if (!maybe_manifest)
|
|
||||||
{
|
|
||||||
FatalError("Could not fetch current manifest. Aborting.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this_manifest = std::move(*maybe_manifest);
|
|
||||||
|
|
||||||
maybe_manifest = FetchAndParseManifest(opts.next_manifest_url);
|
|
||||||
if (!maybe_manifest)
|
|
||||||
{
|
|
||||||
FatalError("Could not fetch next manifest. Aborting.");
|
|
||||||
[NSApp performSelector:@selector(terminate:) withObject:nil afterDelay:0.0];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
next_manifest = std::move(*maybe_manifest);
|
|
||||||
}
|
|
||||||
|
|
||||||
UI::SetDescription("Computing what to do...");
|
|
||||||
|
|
||||||
TodoList todo = ComputeActionsToDo(this_manifest, next_manifest);
|
|
||||||
todo.Log();
|
|
||||||
|
|
||||||
std::optional<std::string> maybe_temp_dir = FindOrCreateTempDir(opts.install_base_path);
|
|
||||||
if (!maybe_temp_dir)
|
|
||||||
return;
|
|
||||||
std::string temp_dir = std::move(*maybe_temp_dir);
|
|
||||||
|
|
||||||
UI::SetDescription("Performing Update...");
|
|
||||||
|
|
||||||
bool ok = PerformUpdate(todo, opts.install_base_path, opts.content_store_url, temp_dir);
|
|
||||||
if (!ok)
|
|
||||||
FatalError("Failed to apply the update.");
|
|
||||||
|
|
||||||
CleanUpTempDir(temp_dir, todo);
|
|
||||||
|
|
||||||
UI::ResetCurrentProgress();
|
|
||||||
UI::ResetTotalProgress();
|
|
||||||
UI::SetCurrentMarquee(false);
|
|
||||||
UI::SetTotalMarquee(false);
|
|
||||||
UI::SetCurrentProgress(1, 1);
|
|
||||||
UI::SetTotalProgress(1, 1);
|
|
||||||
UI::SetDescription("Done!");
|
|
||||||
|
|
||||||
// Let the user process that we are done.
|
|
||||||
[NSThread sleepForTimeInterval:1.0f];
|
|
||||||
|
|
||||||
if (opts.binary_to_restart)
|
|
||||||
{
|
|
||||||
[[NSWorkspace sharedWorkspace]
|
|
||||||
launchApplication:[NSString stringWithCString:opts.binary_to_restart.value().c_str()
|
|
||||||
encoding:[NSString defaultCStringEncoding]]];
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
||||||
[NSApp terminate:nil];
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "UpdaterCommon/UI.h"
|
#include "UpdaterCommon/UI.h"
|
||||||
|
|
||||||
#include <Cocoa/Cocoa.h>
|
#include <Cocoa/Cocoa.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
|
@ -107,6 +108,32 @@ void UI::SetTotalProgress(int current, int total)
|
||||||
run_on_main([&] { [GetView() SetTotalProgress:(double)current total:(double)total]; });
|
run_on_main([&] { [GetView() SetTotalProgress:(double)current total:(double)total]; });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UI::Sleep(int seconds)
|
||||||
|
{
|
||||||
|
[NSThread sleepForTimeInterval:static_cast<float>(seconds)];
|
||||||
|
}
|
||||||
|
|
||||||
|
void UI::WaitForPID(u32 pid)
|
||||||
|
{
|
||||||
|
for (int res = kill(pid, 0); res == 0 || (res < 0 && errno == EPERM); res = kill(pid, 0))
|
||||||
|
{
|
||||||
|
UI::Sleep(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UI::LaunchApplication(std::string path)
|
||||||
|
{
|
||||||
|
[[NSWorkspace sharedWorkspace]
|
||||||
|
launchApplication:[NSString stringWithCString:path.c_str()
|
||||||
|
encoding:[NSString defaultCStringEncoding]]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stubs. These are only needed on Windows
|
||||||
|
|
||||||
|
void UI::Init()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
void UI::Stop()
|
void UI::Stop()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,36 +4,18 @@
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <ShlObj.h>
|
#include <ShlObj.h>
|
||||||
|
|
||||||
#include <OptionParser.h>
|
|
||||||
#include <array>
|
|
||||||
#include <optional>
|
|
||||||
#include <shellapi.h>
|
#include <shellapi.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "Common/CommonPaths.h"
|
#include "Common/StringUtil.h"
|
||||||
#include "Common/CommonTypes.h"
|
|
||||||
#include "Common/FileUtil.h"
|
|
||||||
|
|
||||||
#include "UpdaterCommon/UI.h"
|
#include "UpdaterCommon/UI.h"
|
||||||
#include "UpdaterCommon/UpdaterCommon.h"
|
#include "UpdaterCommon/UpdaterCommon.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
// Internal representation of options passed on the command-line.
|
|
||||||
struct Options
|
|
||||||
{
|
|
||||||
std::string this_manifest_url;
|
|
||||||
std::string next_manifest_url;
|
|
||||||
std::string content_store_url;
|
|
||||||
std::string install_base_path;
|
|
||||||
std::optional<std::string> binary_to_restart;
|
|
||||||
std::optional<DWORD> parent_pid;
|
|
||||||
std::optional<std::string> log_file;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<std::string> CommandLineToUtf8Argv(PCWSTR command_line)
|
std::vector<std::string> CommandLineToUtf8Argv(PCWSTR command_line)
|
||||||
{
|
{
|
||||||
int nargs;
|
int nargs;
|
||||||
|
@ -50,75 +32,6 @@ std::vector<std::string> CommandLineToUtf8Argv(PCWSTR command_line)
|
||||||
LocalFree(tokenized);
|
LocalFree(tokenized);
|
||||||
return argv;
|
return argv;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<Options> ParseCommandLine(PCWSTR command_line)
|
|
||||||
{
|
|
||||||
using optparse::OptionParser;
|
|
||||||
|
|
||||||
OptionParser parser = OptionParser().prog("updater.exe").description("Dolphin Updater binary");
|
|
||||||
|
|
||||||
parser.add_option("--this-manifest-url")
|
|
||||||
.dest("this-manifest-url")
|
|
||||||
.help("URL to the update manifest for the currently installed version.")
|
|
||||||
.metavar("URL");
|
|
||||||
parser.add_option("--next-manifest-url")
|
|
||||||
.dest("next-manifest-url")
|
|
||||||
.help("URL to the update manifest for the to-be-installed version.")
|
|
||||||
.metavar("URL");
|
|
||||||
parser.add_option("--content-store-url")
|
|
||||||
.dest("content-store-url")
|
|
||||||
.help("Base URL of the content store where files to download are stored.")
|
|
||||||
.metavar("URL");
|
|
||||||
parser.add_option("--install-base-path")
|
|
||||||
.dest("install-base-path")
|
|
||||||
.help("Base path of the Dolphin install to be updated.")
|
|
||||||
.metavar("PATH");
|
|
||||||
parser.add_option("--binary-to-restart")
|
|
||||||
.dest("binary-to-restart")
|
|
||||||
.help("Binary to restart after the update is over.")
|
|
||||||
.metavar("PATH");
|
|
||||||
parser.add_option("--log-file")
|
|
||||||
.dest("log-file")
|
|
||||||
.help("File where to log updater debug output.")
|
|
||||||
.metavar("PATH");
|
|
||||||
parser.add_option("--parent-pid")
|
|
||||||
.dest("parent-pid")
|
|
||||||
.type("int")
|
|
||||||
.help("(optional) PID of the parent process. The updater will wait for this process to "
|
|
||||||
"complete before proceeding.")
|
|
||||||
.metavar("PID");
|
|
||||||
|
|
||||||
std::vector<std::string> argv = CommandLineToUtf8Argv(command_line);
|
|
||||||
optparse::Values options = parser.parse_args(argv);
|
|
||||||
|
|
||||||
Options opts;
|
|
||||||
|
|
||||||
// Required arguments.
|
|
||||||
std::vector<std::string> required{"this-manifest-url", "next-manifest-url", "content-store-url",
|
|
||||||
"install-base-path"};
|
|
||||||
for (const auto& req : required)
|
|
||||||
{
|
|
||||||
if (!options.is_set(req))
|
|
||||||
{
|
|
||||||
parser.print_help();
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
opts.this_manifest_url = options["this-manifest-url"];
|
|
||||||
opts.next_manifest_url = options["next-manifest-url"];
|
|
||||||
opts.content_store_url = options["content-store-url"];
|
|
||||||
opts.install_base_path = options["install-base-path"];
|
|
||||||
|
|
||||||
// Optional arguments.
|
|
||||||
if (options.is_set("binary-to-restart"))
|
|
||||||
opts.binary_to_restart = options["binary-to-restart"];
|
|
||||||
if (options.is_set("parent-pid"))
|
|
||||||
opts.parent_pid = (DWORD)options.get("parent-pid");
|
|
||||||
if (options.is_set("log-file"))
|
|
||||||
opts.log_file = options["log-file"];
|
|
||||||
|
|
||||||
return opts;
|
|
||||||
}
|
|
||||||
}; // namespace
|
}; // namespace
|
||||||
|
|
||||||
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
|
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
|
||||||
|
@ -132,58 +45,29 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<Options> maybe_opts = ParseCommandLine(pCmdLine);
|
// Test for write permissions
|
||||||
if (!maybe_opts)
|
|
||||||
return 1;
|
|
||||||
Options opts = std::move(*maybe_opts);
|
|
||||||
|
|
||||||
bool need_admin = false;
|
bool need_admin = false;
|
||||||
|
|
||||||
if (opts.log_file)
|
FILE* test_fh = fopen("Updater.log", "w");
|
||||||
{
|
|
||||||
log_fp = _wfopen(UTF8ToUTF16(*opts.log_file).c_str(), L"w");
|
|
||||||
if (!log_fp)
|
|
||||||
{
|
|
||||||
log_fp = stderr;
|
|
||||||
// Failing to create the logfile for writing is a good indicator that we need administrator
|
|
||||||
// priviliges
|
|
||||||
need_admin = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
atexit(FlushLog);
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(log_fp, "Updating from: %s\n", opts.this_manifest_url.c_str());
|
if (test_fh == nullptr)
|
||||||
fprintf(log_fp, "Updating to: %s\n", opts.next_manifest_url.c_str());
|
need_admin = true;
|
||||||
fprintf(log_fp, "Install path: %s\n", opts.install_base_path.c_str());
|
else
|
||||||
|
fclose(test_fh);
|
||||||
if (!File::IsDirectory(opts.install_base_path))
|
|
||||||
{
|
|
||||||
FatalError("Cannot find install base path, or not a directory.");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.parent_pid)
|
|
||||||
{
|
|
||||||
fprintf(log_fp, "Waiting for parent PID %d to complete...\n", *opts.parent_pid);
|
|
||||||
HANDLE parent_handle = OpenProcess(SYNCHRONIZE, FALSE, *opts.parent_pid);
|
|
||||||
WaitForSingleObject(parent_handle, INFINITE);
|
|
||||||
CloseHandle(parent_handle);
|
|
||||||
fprintf(log_fp, "Completed! Proceeding with update.\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (need_admin)
|
if (need_admin)
|
||||||
{
|
{
|
||||||
if (IsUserAnAdmin())
|
if (IsUserAnAdmin())
|
||||||
{
|
{
|
||||||
FatalError("Failed to write to directory despite administrator priviliges.");
|
MessageBox(nullptr, L"Failed to write to directory despite administrator priviliges.",
|
||||||
|
L"Error", MB_ICONERROR);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
wchar_t path[MAX_PATH];
|
wchar_t path[MAX_PATH];
|
||||||
if (GetModuleFileName(hInstance, path, sizeof(path)) == 0)
|
if (GetModuleFileName(hInstance, path, sizeof(path)) == 0)
|
||||||
{
|
{
|
||||||
FatalError("Failed to get updater filename.");
|
MessageBox(nullptr, L"Failed to get updater filename.", L"Error", MB_ICONERROR);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,68 +76,7 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::thread thread(UI::MessageLoop);
|
std::vector<std::string> args = CommandLineToUtf8Argv(pCmdLine);
|
||||||
thread.detach();
|
|
||||||
|
|
||||||
UI::SetDescription("Fetching and parsing manifests...");
|
return RunUpdater(args) ? 0 : 1;
|
||||||
|
|
||||||
Manifest this_manifest, next_manifest;
|
|
||||||
{
|
|
||||||
std::optional<Manifest> maybe_manifest = FetchAndParseManifest(opts.this_manifest_url);
|
|
||||||
if (!maybe_manifest)
|
|
||||||
{
|
|
||||||
FatalError("Could not fetch current manifest. Aborting.");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
this_manifest = std::move(*maybe_manifest);
|
|
||||||
|
|
||||||
maybe_manifest = FetchAndParseManifest(opts.next_manifest_url);
|
|
||||||
if (!maybe_manifest)
|
|
||||||
{
|
|
||||||
FatalError("Could not fetch next manifest. Aborting.");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
next_manifest = std::move(*maybe_manifest);
|
|
||||||
}
|
|
||||||
|
|
||||||
UI::SetDescription("Computing what to do...");
|
|
||||||
|
|
||||||
TodoList todo = ComputeActionsToDo(this_manifest, next_manifest);
|
|
||||||
todo.Log();
|
|
||||||
|
|
||||||
std::optional<std::string> maybe_temp_dir = FindOrCreateTempDir(opts.install_base_path);
|
|
||||||
if (!maybe_temp_dir)
|
|
||||||
return 1;
|
|
||||||
std::string temp_dir = std::move(*maybe_temp_dir);
|
|
||||||
|
|
||||||
UI::SetDescription("Performing Update...");
|
|
||||||
|
|
||||||
bool ok = PerformUpdate(todo, opts.install_base_path, opts.content_store_url, temp_dir);
|
|
||||||
if (!ok)
|
|
||||||
FatalError("Failed to apply the update.");
|
|
||||||
|
|
||||||
CleanUpTempDir(temp_dir, todo);
|
|
||||||
|
|
||||||
UI::ResetCurrentProgress();
|
|
||||||
UI::ResetTotalProgress();
|
|
||||||
UI::SetCurrentMarquee(false);
|
|
||||||
UI::SetTotalMarquee(false);
|
|
||||||
UI::SetCurrentProgress(0, 1);
|
|
||||||
UI::SetTotalProgress(1, 1);
|
|
||||||
UI::SetDescription("Done!");
|
|
||||||
|
|
||||||
// Let the user process that we are done.
|
|
||||||
Sleep(1000);
|
|
||||||
|
|
||||||
if (opts.binary_to_restart)
|
|
||||||
{
|
|
||||||
// Hack: Launching the updater over the explorer ensures that admin priviliges are dropped. Why?
|
|
||||||
// Ask Microsoft.
|
|
||||||
ShellExecuteW(nullptr, nullptr, L"explorer.exe", UTF8ToUTF16(*opts.binary_to_restart).c_str(),
|
|
||||||
nullptr, SW_SHOW);
|
|
||||||
}
|
|
||||||
|
|
||||||
UI::Stop();
|
|
||||||
|
|
||||||
return !ok;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,9 +42,6 @@
|
||||||
</Link>
|
</Link>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\Externals\cpp-optparse\cpp-optparse.vcxproj">
|
|
||||||
<Project>{c636d9d1-82fe-42b5-9987-63b7d4836341}</Project>
|
|
||||||
</ProjectReference>
|
|
||||||
<ProjectReference Include="..\Common\Common.vcxproj">
|
<ProjectReference Include="..\Common\Common.vcxproj">
|
||||||
<Project>{2e6c348c-c75c-4d94-8d1e-9c1fcbf3efe4}</Project>
|
<Project>{2e6c348c-c75c-4d94-8d1e-9c1fcbf3efe4}</Project>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
|
|
|
@ -5,10 +5,12 @@
|
||||||
#include "UpdaterCommon/UI.h"
|
#include "UpdaterCommon/UI.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#include <CommCtrl.h>
|
#include <CommCtrl.h>
|
||||||
#include <ShObjIdl.h>
|
#include <ShObjIdl.h>
|
||||||
|
#include <shellapi.h>
|
||||||
|
|
||||||
#include "Common/Flag.h"
|
#include "Common/Flag.h"
|
||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
|
@ -39,7 +41,7 @@ constexpr int PADDING_HEIGHT = 5;
|
||||||
|
|
||||||
namespace UI
|
namespace UI
|
||||||
{
|
{
|
||||||
bool Init()
|
bool InitWindow()
|
||||||
{
|
{
|
||||||
InitCommonControls();
|
InitCommonControls();
|
||||||
|
|
||||||
|
@ -200,7 +202,7 @@ void MessageLoop()
|
||||||
request_stop.Clear();
|
request_stop.Clear();
|
||||||
running.Set();
|
running.Set();
|
||||||
|
|
||||||
if (!Init())
|
if (!InitWindow())
|
||||||
{
|
{
|
||||||
running.Clear();
|
running.Clear();
|
||||||
MessageBox(nullptr, L"Window init failed!", L"", MB_ICONERROR);
|
MessageBox(nullptr, L"Window init failed!", L"", MB_ICONERROR);
|
||||||
|
@ -224,6 +226,12 @@ void MessageLoop()
|
||||||
Destroy();
|
Destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Init()
|
||||||
|
{
|
||||||
|
std::thread thread(MessageLoop);
|
||||||
|
thread.detach();
|
||||||
|
}
|
||||||
|
|
||||||
void Stop()
|
void Stop()
|
||||||
{
|
{
|
||||||
if (!running.IsSet())
|
if (!running.IsSet())
|
||||||
|
@ -235,4 +243,29 @@ void Stop()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LaunchApplication(std::string path)
|
||||||
|
{
|
||||||
|
// Hack: Launching the updater over the explorer ensures that admin priviliges are dropped. Why?
|
||||||
|
// Ask Microsoft.
|
||||||
|
ShellExecuteW(nullptr, nullptr, L"explorer.exe", UTF8ToUTF16(path).c_str(), nullptr, SW_SHOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sleep(int sleep)
|
||||||
|
{
|
||||||
|
::Sleep(sleep * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaitForPID(u32 pid)
|
||||||
|
{
|
||||||
|
HANDLE parent_handle = OpenProcess(SYNCHRONIZE, FALSE, static_cast<DWORD>(pid));
|
||||||
|
WaitForSingleObject(parent_handle, INFINITE);
|
||||||
|
CloseHandle(parent_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetVisible(bool visible)
|
||||||
|
{
|
||||||
|
ShowWindow(window_handle, visible ? SW_SHOW : SW_HIDE);
|
||||||
|
}
|
||||||
|
|
||||||
}; // namespace UI
|
}; // namespace UI
|
||||||
|
|
|
@ -5,4 +5,5 @@ target_link_libraries(updatercommon PRIVATE
|
||||||
uicommon
|
uicommon
|
||||||
mbedtls
|
mbedtls
|
||||||
z
|
z
|
||||||
ed25519)
|
ed25519
|
||||||
|
cpp-optparse)
|
|
@ -6,11 +6,11 @@
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "Common/CommonTypes.h"
|
||||||
|
|
||||||
namespace UI
|
namespace UI
|
||||||
{
|
{
|
||||||
void MessageLoop();
|
|
||||||
void Error(const std::string& text);
|
void Error(const std::string& text);
|
||||||
void Stop();
|
|
||||||
|
|
||||||
void SetDescription(const std::string& text);
|
void SetDescription(const std::string& text);
|
||||||
|
|
||||||
|
@ -23,4 +23,11 @@ void ResetCurrentProgress();
|
||||||
void SetCurrentProgress(int current, int total);
|
void SetCurrentProgress(int current, int total);
|
||||||
|
|
||||||
void SetVisible(bool visible);
|
void SetVisible(bool visible);
|
||||||
|
|
||||||
|
void Stop();
|
||||||
|
|
||||||
|
void Init();
|
||||||
|
void Sleep(int seconds);
|
||||||
|
void WaitForPID(u32 pid);
|
||||||
|
void LaunchApplication(std::string path);
|
||||||
} // namespace UI
|
} // namespace UI
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
#include "UpdaterCommon/UpdaterCommon.h"
|
#include "UpdaterCommon/UpdaterCommon.h"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include <OptionParser.h>
|
||||||
#include <ed25519/ed25519.h>
|
#include <ed25519/ed25519.h>
|
||||||
#include <mbedtls/base64.h>
|
#include <mbedtls/base64.h>
|
||||||
#include <mbedtls/sha256.h>
|
#include <mbedtls/sha256.h>
|
||||||
|
@ -21,6 +24,8 @@
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
// Where to log updater output.
|
// Where to log updater output.
|
||||||
FILE* log_fp = stderr;
|
FILE* log_fp = stderr;
|
||||||
|
|
||||||
|
@ -31,13 +36,48 @@ const std::array<u8, 32> UPDATE_PUB_KEY = {
|
||||||
|
|
||||||
const char UPDATE_TEMP_DIR[] = "TempUpdate";
|
const char UPDATE_TEMP_DIR[] = "TempUpdate";
|
||||||
|
|
||||||
static bool ProgressCallback(double total, double now, double, double)
|
struct Manifest
|
||||||
|
{
|
||||||
|
using Filename = std::string;
|
||||||
|
using Hash = std::array<u8, 16>;
|
||||||
|
std::map<Filename, Hash> entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Represent the operations to be performed by the updater.
|
||||||
|
struct TodoList
|
||||||
|
{
|
||||||
|
struct DownloadOp
|
||||||
|
{
|
||||||
|
Manifest::Filename filename;
|
||||||
|
Manifest::Hash hash;
|
||||||
|
};
|
||||||
|
std::vector<DownloadOp> to_download;
|
||||||
|
|
||||||
|
struct UpdateOp
|
||||||
|
{
|
||||||
|
Manifest::Filename filename;
|
||||||
|
std::optional<Manifest::Hash> old_hash;
|
||||||
|
Manifest::Hash new_hash;
|
||||||
|
};
|
||||||
|
std::vector<UpdateOp> to_update;
|
||||||
|
|
||||||
|
struct DeleteOp
|
||||||
|
{
|
||||||
|
Manifest::Filename filename;
|
||||||
|
Manifest::Hash old_hash;
|
||||||
|
};
|
||||||
|
std::vector<DeleteOp> to_delete;
|
||||||
|
|
||||||
|
void Log() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool ProgressCallback(double total, double now, double, double)
|
||||||
{
|
{
|
||||||
UI::SetCurrentProgress(static_cast<int>(now), static_cast<int>(total));
|
UI::SetCurrentProgress(static_cast<int>(now), static_cast<int>(total));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string HexEncode(const u8* buffer, size_t size)
|
std::string HexEncode(const u8* buffer, size_t size)
|
||||||
{
|
{
|
||||||
std::string out(size * 2, '\0');
|
std::string out(size * 2, '\0');
|
||||||
|
|
||||||
|
@ -50,7 +90,7 @@ static std::string HexEncode(const u8* buffer, size_t size)
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool HexDecode(const std::string& hex, u8* buffer, size_t size)
|
bool HexDecode(const std::string& hex, u8* buffer, size_t size)
|
||||||
{
|
{
|
||||||
if (hex.size() != size * 2)
|
if (hex.size() != size * 2)
|
||||||
return false;
|
return false;
|
||||||
|
@ -79,7 +119,7 @@ static bool HexDecode(const std::string& hex, u8* buffer, size_t size)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<std::string> GzipInflate(const std::string& data)
|
std::optional<std::string> GzipInflate(const std::string& data)
|
||||||
{
|
{
|
||||||
z_stream zstrm;
|
z_stream zstrm;
|
||||||
zstrm.zalloc = nullptr;
|
zstrm.zalloc = nullptr;
|
||||||
|
@ -115,7 +155,7 @@ static std::optional<std::string> GzipInflate(const std::string& data)
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Manifest::Hash ComputeHash(const std::string& contents)
|
Manifest::Hash ComputeHash(const std::string& contents)
|
||||||
{
|
{
|
||||||
std::array<u8, 32> full;
|
std::array<u8, 32> full;
|
||||||
mbedtls_sha256(reinterpret_cast<const u8*>(contents.data()), contents.size(), full.data(), false);
|
mbedtls_sha256(reinterpret_cast<const u8*>(contents.data()), contents.size(), full.data(), false);
|
||||||
|
@ -125,7 +165,7 @@ static Manifest::Hash ComputeHash(const std::string& contents)
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool VerifySignature(const std::string& data, const std::string& b64_signature)
|
bool VerifySignature(const std::string& data, const std::string& b64_signature)
|
||||||
{
|
{
|
||||||
u8 signature[64]; // ed25519 sig size.
|
u8 signature[64]; // ed25519 sig size.
|
||||||
size_t sig_size;
|
size_t sig_size;
|
||||||
|
@ -173,8 +213,8 @@ void TodoList::Log() const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool DownloadContent(const std::vector<TodoList::DownloadOp>& to_download,
|
bool DownloadContent(const std::vector<TodoList::DownloadOp>& to_download,
|
||||||
const std::string& content_base_url, const std::string& temp_path)
|
const std::string& content_base_url, const std::string& temp_path)
|
||||||
{
|
{
|
||||||
Common::HttpRequest req(std::chrono::seconds(30), ProgressCallback);
|
Common::HttpRequest req(std::chrono::seconds(30), ProgressCallback);
|
||||||
|
|
||||||
|
@ -314,7 +354,7 @@ void CleanUpTempDir(const std::string& temp_dir, const TodoList& todo)
|
||||||
File::DeleteDir(temp_dir);
|
File::DeleteDir(temp_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool BackupFile(const std::string& path)
|
bool BackupFile(const std::string& path)
|
||||||
{
|
{
|
||||||
std::string backup_path = path + ".bak";
|
std::string backup_path = path + ".bak";
|
||||||
fprintf(log_fp, "Backing up unknown pre-existing %s to .bak.\n", path.c_str());
|
fprintf(log_fp, "Backing up unknown pre-existing %s to .bak.\n", path.c_str());
|
||||||
|
@ -326,8 +366,8 @@ static bool BackupFile(const std::string& path)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool DeleteObsoleteFiles(const std::vector<TodoList::DeleteOp>& to_delete,
|
bool DeleteObsoleteFiles(const std::vector<TodoList::DeleteOp>& to_delete,
|
||||||
const std::string& install_base_path)
|
const std::string& install_base_path)
|
||||||
{
|
{
|
||||||
for (const auto& op : to_delete)
|
for (const auto& op : to_delete)
|
||||||
{
|
{
|
||||||
|
@ -359,8 +399,8 @@ static bool DeleteObsoleteFiles(const std::vector<TodoList::DeleteOp>& to_delete
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool UpdateFiles(const std::vector<TodoList::UpdateOp>& to_update,
|
bool UpdateFiles(const std::vector<TodoList::UpdateOp>& to_update,
|
||||||
const std::string& install_base_path, const std::string& temp_path)
|
const std::string& install_base_path, const std::string& temp_path)
|
||||||
{
|
{
|
||||||
for (const auto& op : to_update)
|
for (const auto& op : to_update)
|
||||||
{
|
{
|
||||||
|
@ -459,7 +499,7 @@ void FatalError(const std::string& message)
|
||||||
UI::Stop();
|
UI::Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<Manifest> ParseManifest(const std::string& manifest)
|
std::optional<Manifest> ParseManifest(const std::string& manifest)
|
||||||
{
|
{
|
||||||
Manifest parsed;
|
Manifest parsed;
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
|
@ -550,3 +590,193 @@ std::optional<Manifest> FetchAndParseManifest(const std::string& url)
|
||||||
|
|
||||||
return ParseManifest(decompressed);
|
return ParseManifest(decompressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Options
|
||||||
|
{
|
||||||
|
std::string this_manifest_url;
|
||||||
|
std::string next_manifest_url;
|
||||||
|
std::string content_store_url;
|
||||||
|
std::string install_base_path;
|
||||||
|
std::optional<std::string> binary_to_restart;
|
||||||
|
std::optional<u32> parent_pid;
|
||||||
|
std::optional<std::string> log_file;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<Options> ParseCommandLine(std::vector<std::string>& args)
|
||||||
|
{
|
||||||
|
using optparse::OptionParser;
|
||||||
|
|
||||||
|
OptionParser parser =
|
||||||
|
OptionParser().prog("Dolphin Updater").description("Dolphin Updater binary");
|
||||||
|
|
||||||
|
parser.add_option("--this-manifest-url")
|
||||||
|
.dest("this-manifest-url")
|
||||||
|
.help("URL to the update manifest for the currently installed version.")
|
||||||
|
.metavar("URL");
|
||||||
|
parser.add_option("--next-manifest-url")
|
||||||
|
.dest("next-manifest-url")
|
||||||
|
.help("URL to the update manifest for the to-be-installed version.")
|
||||||
|
.metavar("URL");
|
||||||
|
parser.add_option("--content-store-url")
|
||||||
|
.dest("content-store-url")
|
||||||
|
.help("Base URL of the content store where files to download are stored.")
|
||||||
|
.metavar("URL");
|
||||||
|
parser.add_option("--install-base-path")
|
||||||
|
.dest("install-base-path")
|
||||||
|
.help("Base path of the Dolphin install to be updated.")
|
||||||
|
.metavar("PATH");
|
||||||
|
parser.add_option("--binary-to-restart")
|
||||||
|
.dest("binary-to-restart")
|
||||||
|
.help("Binary to restart after the update is over.")
|
||||||
|
.metavar("PATH");
|
||||||
|
parser.add_option("--log-file")
|
||||||
|
.dest("log-file")
|
||||||
|
.help("File where to log updater debug output.")
|
||||||
|
.metavar("PATH");
|
||||||
|
parser.add_option("--parent-pid")
|
||||||
|
.dest("parent-pid")
|
||||||
|
.type("int")
|
||||||
|
.help("(optional) PID of the parent process. The updater will wait for this process to "
|
||||||
|
"complete before proceeding.")
|
||||||
|
.metavar("PID");
|
||||||
|
|
||||||
|
optparse::Values options = parser.parse_args(args);
|
||||||
|
|
||||||
|
Options opts;
|
||||||
|
|
||||||
|
// Required arguments.
|
||||||
|
std::vector<std::string> required{"this-manifest-url", "next-manifest-url", "content-store-url",
|
||||||
|
"install-base-path"};
|
||||||
|
for (const auto& req : required)
|
||||||
|
{
|
||||||
|
if (!options.is_set(req))
|
||||||
|
{
|
||||||
|
parser.print_help();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opts.this_manifest_url = options["this-manifest-url"];
|
||||||
|
opts.next_manifest_url = options["next-manifest-url"];
|
||||||
|
opts.content_store_url = options["content-store-url"];
|
||||||
|
opts.install_base_path = options["install-base-path"];
|
||||||
|
|
||||||
|
// Optional arguments.
|
||||||
|
if (options.is_set("binary-to-restart"))
|
||||||
|
opts.binary_to_restart = options["binary-to-restart"];
|
||||||
|
if (options.is_set("parent-pid"))
|
||||||
|
opts.parent_pid = static_cast<u32>(options.get("parent-pid"));
|
||||||
|
if (options.is_set("log-file"))
|
||||||
|
opts.log_file = options["log-file"];
|
||||||
|
|
||||||
|
return opts;
|
||||||
|
}
|
||||||
|
}; // namespace
|
||||||
|
|
||||||
|
bool RunUpdater(std::vector<std::string> args)
|
||||||
|
{
|
||||||
|
std::optional<Options> maybe_opts = ParseCommandLine(args);
|
||||||
|
|
||||||
|
if (!maybe_opts)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UI::Init();
|
||||||
|
|
||||||
|
Options opts = std::move(*maybe_opts);
|
||||||
|
|
||||||
|
if (opts.log_file)
|
||||||
|
{
|
||||||
|
log_fp = fopen(opts.log_file.value().c_str(), "w");
|
||||||
|
if (!log_fp)
|
||||||
|
log_fp = stderr;
|
||||||
|
else
|
||||||
|
atexit(FlushLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(log_fp, "Updating from: %s\n", opts.this_manifest_url.c_str());
|
||||||
|
fprintf(log_fp, "Updating to: %s\n", opts.next_manifest_url.c_str());
|
||||||
|
fprintf(log_fp, "Install path: %s\n", opts.install_base_path.c_str());
|
||||||
|
|
||||||
|
if (!File::IsDirectory(opts.install_base_path))
|
||||||
|
{
|
||||||
|
FatalError("Cannot find install base path, or not a directory.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.parent_pid)
|
||||||
|
{
|
||||||
|
UI::SetDescription("Waiting for Dolphin to quit...");
|
||||||
|
|
||||||
|
fprintf(log_fp, "Waiting for parent PID %d to complete...\n", *opts.parent_pid);
|
||||||
|
|
||||||
|
auto pid = opts.parent_pid.value();
|
||||||
|
|
||||||
|
UI::WaitForPID(static_cast<u32>(pid));
|
||||||
|
|
||||||
|
fprintf(log_fp, "Completed! Proceeding with update.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
UI::SetVisible(true);
|
||||||
|
|
||||||
|
UI::SetDescription("Fetching and parsing manifests...");
|
||||||
|
|
||||||
|
Manifest this_manifest, next_manifest;
|
||||||
|
{
|
||||||
|
std::optional<Manifest> maybe_manifest = FetchAndParseManifest(opts.this_manifest_url);
|
||||||
|
if (!maybe_manifest)
|
||||||
|
{
|
||||||
|
FatalError("Could not fetch current manifest. Aborting.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this_manifest = std::move(*maybe_manifest);
|
||||||
|
|
||||||
|
maybe_manifest = FetchAndParseManifest(opts.next_manifest_url);
|
||||||
|
if (!maybe_manifest)
|
||||||
|
{
|
||||||
|
FatalError("Could not fetch next manifest. Aborting.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
next_manifest = std::move(*maybe_manifest);
|
||||||
|
}
|
||||||
|
|
||||||
|
UI::SetDescription("Computing what to do...");
|
||||||
|
|
||||||
|
TodoList todo = ComputeActionsToDo(this_manifest, next_manifest);
|
||||||
|
todo.Log();
|
||||||
|
|
||||||
|
std::optional<std::string> maybe_temp_dir = FindOrCreateTempDir(opts.install_base_path);
|
||||||
|
if (!maybe_temp_dir)
|
||||||
|
return false;
|
||||||
|
std::string temp_dir = std::move(*maybe_temp_dir);
|
||||||
|
|
||||||
|
UI::SetDescription("Performing Update...");
|
||||||
|
|
||||||
|
bool ok = PerformUpdate(todo, opts.install_base_path, opts.content_store_url, temp_dir);
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
FatalError("Failed to apply the update.");
|
||||||
|
CleanUpTempDir(temp_dir, todo);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UI::ResetCurrentProgress();
|
||||||
|
UI::ResetTotalProgress();
|
||||||
|
UI::SetCurrentMarquee(false);
|
||||||
|
UI::SetTotalMarquee(false);
|
||||||
|
UI::SetCurrentProgress(1, 1);
|
||||||
|
UI::SetTotalProgress(1, 1);
|
||||||
|
UI::SetDescription("Done!");
|
||||||
|
|
||||||
|
// Let the user process that we are done.
|
||||||
|
UI::Sleep(1);
|
||||||
|
|
||||||
|
if (opts.binary_to_restart)
|
||||||
|
{
|
||||||
|
UI::LaunchApplication(opts.binary_to_restart.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
UI::Stop();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
|
@ -13,48 +13,4 @@
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
|
||||||
extern FILE* log_fp;
|
bool RunUpdater(std::vector<std::string> args);
|
||||||
|
|
||||||
struct Manifest
|
|
||||||
{
|
|
||||||
using Filename = std::string;
|
|
||||||
using Hash = std::array<u8, 16>;
|
|
||||||
std::map<Filename, Hash> entries;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Represent the operations to be performed by the updater.
|
|
||||||
struct TodoList
|
|
||||||
{
|
|
||||||
struct DownloadOp
|
|
||||||
{
|
|
||||||
Manifest::Filename filename;
|
|
||||||
Manifest::Hash hash;
|
|
||||||
};
|
|
||||||
std::vector<DownloadOp> to_download;
|
|
||||||
|
|
||||||
struct UpdateOp
|
|
||||||
{
|
|
||||||
Manifest::Filename filename;
|
|
||||||
std::optional<Manifest::Hash> old_hash;
|
|
||||||
Manifest::Hash new_hash;
|
|
||||||
};
|
|
||||||
std::vector<UpdateOp> to_update;
|
|
||||||
|
|
||||||
struct DeleteOp
|
|
||||||
{
|
|
||||||
Manifest::Filename filename;
|
|
||||||
Manifest::Hash old_hash;
|
|
||||||
};
|
|
||||||
std::vector<DeleteOp> to_delete;
|
|
||||||
|
|
||||||
void Log() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
void FatalError(const std::string& message);
|
|
||||||
std::optional<Manifest> FetchAndParseManifest(const std::string& url);
|
|
||||||
TodoList ComputeActionsToDo(Manifest this_manifest, Manifest next_manifest);
|
|
||||||
std::optional<std::string> FindOrCreateTempDir(const std::string& base_path);
|
|
||||||
void CleanUpTempDir(const std::string& temp_dir, const TodoList& todo);
|
|
||||||
bool PerformUpdate(const TodoList& todo, const std::string& install_base_path,
|
|
||||||
const std::string& content_base_url, const std::string& temp_path);
|
|
||||||
void FlushLog();
|
|
||||||
|
|
|
@ -54,6 +54,9 @@
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
<ProjectReference Include="..\..\..\Externals\zlib\zlib.vcxproj">
|
<ProjectReference Include="..\..\..\Externals\zlib\zlib.vcxproj">
|
||||||
<Project>{ff213b23-2c26-4214-9f88-85271e557e87}</Project>
|
<Project>{ff213b23-2c26-4214-9f88-85271e557e87}</Project>
|
||||||
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\..\..\Externals\cpp-optparse\cpp-optparse.vcxproj">
|
||||||
|
<Project>{c636d9d1-82fe-42b5-9987-63b7d4836341}</Project>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
Loading…
Reference in New Issue