feat: refactor project, installation, licensing, a bunch of features
This commit is contained in:
parent
f9f7d653e3
commit
2eea14d000
|
|
@ -7,6 +7,7 @@ namespace Kiseki.Launcher.Windows
|
|||
public static string Base { get; private set; } = "";
|
||||
public static string Logs { get; private set; } = "";
|
||||
public static string Versions { get; private set; } = "";
|
||||
public static string License { get; private set; } = "";
|
||||
public static string Application { get; private set; } = "";
|
||||
|
||||
public static void Initialize(string baseDirectory)
|
||||
|
|
@ -15,8 +16,9 @@ namespace Kiseki.Launcher.Windows
|
|||
|
||||
Logs = Path.Combine(Base, "Logs");
|
||||
Versions = Path.Combine(Base, "Versions");
|
||||
|
||||
Application = Path.Combine(Base, $"{Launcher.ProjectName}.exe");
|
||||
|
||||
License = Path.Combine(Base, "license.bin");
|
||||
Application = Path.Combine(Base, $"{Constants.ProjectName}.Launcher.exe");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,28 +6,62 @@ namespace Kiseki.Launcher.Windows
|
|||
{
|
||||
public class Launcher : ILauncher
|
||||
{
|
||||
public const string ProjectName = "Kiseki";
|
||||
public const string ProjectRepository = "kiseki-lol/launcher";
|
||||
|
||||
public readonly static string BaseUrl = "test.kiseki.lol"; // TODO: This should be set dynamically somehow
|
||||
public readonly static string Version = Assembly.GetExecutingAssembly().GetName().Version!.ToString()[..^2];
|
||||
|
||||
public static void Install()
|
||||
public async static void Install()
|
||||
{
|
||||
|
||||
Directory.CreateDirectory(Directories.Base);
|
||||
int response = await Web.CheckHealth();
|
||||
|
||||
if (response != Web.RESPONSE_SUCCESS || response != Web.RESPONSE_MAINTENANCE)
|
||||
{
|
||||
if (response != Web.RESPONSE_MAINTENANCE)
|
||||
{
|
||||
// The Kiseki website is either down or we can't connect to the internet.
|
||||
// TODO: This is a strange scenario where we need to display an error outside of the controller. Can we do this within the page instead of a message box?
|
||||
MessageBox.Show($"Failed to connect to the {Constants.ProjectName} website. Please check your internet connection.", Constants.ProjectName, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We are in maintenance mode, so let's ask for a license.
|
||||
AskForLicense(Directories.License);
|
||||
Web.LoadLicense(File.ReadAllText(Directories.License));
|
||||
|
||||
// ... try this again;
|
||||
Install();
|
||||
}
|
||||
}
|
||||
|
||||
// okay, now download the launcher from the Kiseki website...
|
||||
}
|
||||
|
||||
private static void AskForLicense(string licensePath)
|
||||
{
|
||||
using OpenFileDialog dialog = new()
|
||||
{
|
||||
Title = "Select your license file",
|
||||
Filter = "License files (*.bin)|*.bin",
|
||||
InitialDirectory = Directories.Base
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
File.Copy(dialog.FileName, licensePath, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Register()
|
||||
{
|
||||
using (RegistryKey applicationKey = Registry.CurrentUser.CreateSubKey($@"Software\{ProjectName}"))
|
||||
using (RegistryKey applicationKey = Registry.CurrentUser.CreateSubKey($@"Software\{Constants.ProjectName}"))
|
||||
{
|
||||
applicationKey.SetValue("InstallLocation", Directories.Base);
|
||||
}
|
||||
|
||||
using RegistryKey uninstallKey = Registry.CurrentUser.CreateSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{ProjectName}");
|
||||
using RegistryKey uninstallKey = Registry.CurrentUser.CreateSubKey($@"Software\Microsoft\Windows\CurrentVersion\Uninstall\{Constants.ProjectName}");
|
||||
|
||||
uninstallKey.SetValue("DisplayIcon", $"{Directories.Application},0");
|
||||
uninstallKey.SetValue("DisplayName", ProjectName);
|
||||
uninstallKey.SetValue("DisplayName", Constants.ProjectName);
|
||||
uninstallKey.SetValue("DisplayVersion", Version);
|
||||
|
||||
if (uninstallKey.GetValue("InstallDate") is null)
|
||||
|
|
@ -35,17 +69,17 @@ namespace Kiseki.Launcher.Windows
|
|||
|
||||
uninstallKey.SetValue("InstallLocation", Directories.Base);
|
||||
uninstallKey.SetValue("NoRepair", 1);
|
||||
uninstallKey.SetValue("Publisher", ProjectName);
|
||||
uninstallKey.SetValue("Publisher", Constants.ProjectName);
|
||||
uninstallKey.SetValue("ModifyPath", $"\"{Directories.Application}\" -menu");
|
||||
uninstallKey.SetValue("QuietUninstallString", $"\"{Directories.Application}\" -uninstall -quiet");
|
||||
uninstallKey.SetValue("UninstallString", $"\"{Directories.Application}\" -uninstall");
|
||||
uninstallKey.SetValue("URLInfoAbout", $"https://github.com/{ProjectRepository}");
|
||||
uninstallKey.SetValue("URLUpdateInfo", $"https://github.com/{ProjectRepository}/releases/latest");
|
||||
uninstallKey.SetValue("URLInfoAbout", $"https://github.com/{Constants.ProjectRepository}");
|
||||
uninstallKey.SetValue("URLUpdateInfo", $"https://github.com/{Constants.ProjectRepository}/releases/latest");
|
||||
}
|
||||
|
||||
public static void Unregister()
|
||||
{
|
||||
Registry.CurrentUser.DeleteSubKey($@"Software\{ProjectName}");
|
||||
Registry.CurrentUser.DeleteSubKey($@"Software\{Constants.ProjectName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,13 +9,19 @@ namespace Kiseki.Launcher.Windows
|
|||
private readonly TaskDialogPage Page;
|
||||
private readonly Controller Controller;
|
||||
|
||||
public MainWindow(string[] args)
|
||||
public MainWindow(string payload)
|
||||
{
|
||||
CloseButton = TaskDialogButton.Close;
|
||||
Controller = new Controller(payload);
|
||||
Controller.OnPageHeadingChange += Controller_PageHeadingChanged;
|
||||
Controller.OnProgressBarAdd += Controller_ProgressBarAdded;
|
||||
Controller.OnProgressBarStateChange += Controller_ProgressBarStateChanged;
|
||||
Controller.OnErrorShow += Controller_ErrorShown;
|
||||
Controller.OnLaunch += (s, e) => Environment.Exit(0);
|
||||
|
||||
CloseButton = TaskDialogButton.Close;
|
||||
Page = new TaskDialogPage()
|
||||
{
|
||||
Caption = Launcher.ProjectName,
|
||||
Caption = Constants.ProjectName,
|
||||
AllowMinimize = true,
|
||||
|
||||
ProgressBar = new TaskDialogProgressBar()
|
||||
|
|
@ -26,13 +32,10 @@ namespace Kiseki.Launcher.Windows
|
|||
Buttons = { CloseButton }
|
||||
};
|
||||
|
||||
Controller = new Controller(Launcher.BaseUrl, args);
|
||||
Controller.OnPageHeadingChange += Controller_PageHeadingChanged;
|
||||
Controller.OnProgressBarChange += Controller_ProgressBarChanged;
|
||||
Controller.OnProgressBarStateChange += Controller_ProgressBarStateChanged;
|
||||
|
||||
Controller.OnInstall += (s, e) => Launcher.Install();
|
||||
Controller.OnLaunch += (s, e) => Environment.Exit(0);
|
||||
Page.Created += (s, e) =>
|
||||
{
|
||||
Controller.Start();
|
||||
};
|
||||
|
||||
Page.Destroyed += (s, e) =>
|
||||
{
|
||||
|
|
@ -45,22 +48,23 @@ namespace Kiseki.Launcher.Windows
|
|||
|
||||
private void CloseButton_Click(object? sender, EventArgs e)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
Controller.Dispose();
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
private void Controller_PageHeadingChanged(object? sender, string Heading)
|
||||
private void Controller_PageHeadingChanged(object? sender, string heading)
|
||||
{
|
||||
Page.Heading = Heading;
|
||||
Page.Heading = heading;
|
||||
}
|
||||
|
||||
private void Controller_ProgressBarChanged(object? sender, int Value)
|
||||
private void Controller_ProgressBarAdded(object? sender, int value)
|
||||
{
|
||||
Page.ProgressBar!.Value = Value;
|
||||
Page.ProgressBar!.Value += value;
|
||||
}
|
||||
|
||||
private void Controller_ProgressBarStateChanged(object? sender, ProgressBarState State)
|
||||
private void Controller_ProgressBarStateChanged(object? sender, ProgressBarState state)
|
||||
{
|
||||
Page.ProgressBar!.State = State switch
|
||||
Page.ProgressBar!.State = state switch
|
||||
{
|
||||
ProgressBarState.Normal => TaskDialogProgressBarState.Normal,
|
||||
ProgressBarState.Marquee => TaskDialogProgressBarState.Marquee,
|
||||
|
|
@ -68,16 +72,20 @@ namespace Kiseki.Launcher.Windows
|
|||
};
|
||||
}
|
||||
|
||||
private void Controller_ErrorShown(object? sender, string[] texts)
|
||||
{
|
||||
Page.Icon = TaskDialogIcon.Error;
|
||||
Page.Heading = texts[0];
|
||||
Page.Text = texts[1];
|
||||
|
||||
Controller.Dispose();
|
||||
}
|
||||
|
||||
private void ShowProgressDialog()
|
||||
{
|
||||
TaskDialogIcon logo = new(Resources.IconKiseki);
|
||||
Page.Icon = logo;
|
||||
|
||||
Page.Created += (s, e) =>
|
||||
{
|
||||
Controller.Start();
|
||||
};
|
||||
|
||||
TaskDialog.ShowDialog(Page);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Diagnostics;
|
||||
|
||||
namespace Kiseki.Launcher.Windows
|
||||
{
|
||||
internal static class Program
|
||||
|
|
@ -6,10 +8,34 @@ namespace Kiseki.Launcher.Windows
|
|||
static void Main(string[] args)
|
||||
{
|
||||
string parentFolder = Path.GetDirectoryName(AppContext.BaseDirectory)!;
|
||||
Directories.Initialize(parentFolder.ToLower().Contains(Launcher.ProjectName) ? AppContext.BaseDirectory : Path.Combine(Directories.LocalAppData, Launcher.ProjectName));
|
||||
if (parentFolder.ToLower().Contains(Constants.ProjectName))
|
||||
{
|
||||
// Set to the current directory (either user-installed or default; it has "Kiseki" in the path, so that's good enough for us)
|
||||
Directories.Initialize(AppContext.BaseDirectory);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set to the default directory (user likely hasn't installed the launcher yet)
|
||||
Directories.Initialize(Path.Combine(Directories.LocalAppData, Constants.ProjectName));
|
||||
}
|
||||
|
||||
Web.Initialize();
|
||||
|
||||
if (!File.Exists(Directories.Application))
|
||||
{
|
||||
// The launcher is not installed, so let's install it.
|
||||
Launcher.Install();
|
||||
}
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
// Nothing for us to do :P
|
||||
Process.Start(Web.Url("/games"));
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
ApplicationConfiguration.Initialize();
|
||||
Application.Run(new MainWindow(args));
|
||||
Application.Run(new MainWindow(args[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
namespace Kiseki.Launcher
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
public const string ProjectName = "Kiseki";
|
||||
public const string ProjectRepository = "kiseki-lol/launcher";
|
||||
}
|
||||
}
|
||||
|
|
@ -10,54 +10,43 @@ namespace Kiseki.Launcher
|
|||
|
||||
public class Controller
|
||||
{
|
||||
private readonly string BaseURL;
|
||||
private readonly Dictionary<string, string> Arguments = new();
|
||||
|
||||
public event EventHandler<string>? OnPageHeadingChange;
|
||||
public event EventHandler<int>? OnProgressBarChange;
|
||||
public event EventHandler<int>? OnProgressBarAdd;
|
||||
public event EventHandler<ProgressBarState>? OnProgressBarStateChange;
|
||||
public event EventHandler? OnInstall;
|
||||
public event EventHandler<string[]>? OnErrorShow;
|
||||
public event EventHandler? OnLaunch;
|
||||
|
||||
public static readonly HttpClient HttpClient = new();
|
||||
|
||||
public Controller(string baseURL, string[] args)
|
||||
public Controller(string payload)
|
||||
{
|
||||
BaseURL = baseURL;
|
||||
|
||||
if (args.Length == 0)
|
||||
if (!Base64.IsBase64String(payload))
|
||||
{
|
||||
// We are launching for the first time. This means that we should trigger the launcher install process.
|
||||
Install();
|
||||
ErrorShow($"Failed to launch {Constants.ProjectName}", $"Try launching {Constants.ProjectName} from the website again.");
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
// TODO: The payload will soon include more members; update this accordingly
|
||||
payload = Base64.ConvertBase64ToString(payload);
|
||||
if (payload.Split("|").Length != 2)
|
||||
{
|
||||
// TODO: handle these more gracefully
|
||||
if (!Base64.IsBase64String(args[0]))
|
||||
{
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
// TODO: the payload will soon include more members, such as whether to open the IDE or not (as well as values required for our weird loopback authentication thing)
|
||||
string payload = Base64.ConvertBase64ToString(args[0]);
|
||||
if (payload.Split("|").Length != 2)
|
||||
{
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
Arguments["JoinScriptURL"] = payload.Split("|")[0];
|
||||
Arguments["Ticket"] = payload.Split("|")[1];
|
||||
ErrorShow($"Failed to launch {Constants.ProjectName}", $"Try launching {Constants.ProjectName} from the website again.");
|
||||
return;
|
||||
}
|
||||
|
||||
Arguments["JoinScriptURL"] = payload.Split("|")[0];
|
||||
Arguments["Ticket"] = payload.Split("|")[1];
|
||||
}
|
||||
|
||||
public async void Start()
|
||||
{
|
||||
PageHeadingChange("Connecting to Kiseki...");
|
||||
|
||||
var response = await Http.GetJson<Models.Health>($"https://{BaseURL}/api/health");
|
||||
if (response is null)
|
||||
// This is kind of useless, but whatever
|
||||
int response = await Web.CheckHealth();
|
||||
if (response != Web.RESPONSE_SUCCESS)
|
||||
{
|
||||
PageHeadingChange("Failed to connect");
|
||||
ErrorShow($"Failed to connect to {Constants.ProjectName}.", "Please check your internet connection.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -71,18 +60,15 @@ namespace Kiseki.Launcher
|
|||
marquee = false;
|
||||
}
|
||||
|
||||
ProgressBarChange(progressValue);
|
||||
ProgressBarAdd(progressValue);
|
||||
}
|
||||
|
||||
static async IAsyncEnumerable<int> StreamBackgroundOperationProgressAsync()
|
||||
{
|
||||
await Task.Delay(2800);
|
||||
|
||||
for (int i = 0; i <= 100; i += 4)
|
||||
{
|
||||
yield return i;
|
||||
await Task.Delay(200);
|
||||
}
|
||||
yield return 4;
|
||||
await Task.Delay(200);
|
||||
}
|
||||
|
||||
PageHeadingChange("Installing Kiseki...");
|
||||
|
|
@ -103,27 +89,27 @@ namespace Kiseki.Launcher
|
|||
// TODO: This will only be called when the user closes the window OR we're done (i.e. the Launched event is called.)
|
||||
}
|
||||
|
||||
protected virtual void PageHeadingChange(string Heading)
|
||||
protected virtual void PageHeadingChange(string heading)
|
||||
{
|
||||
OnPageHeadingChange!.Invoke(this, Heading);
|
||||
OnPageHeadingChange!.Invoke(this, heading);
|
||||
}
|
||||
|
||||
protected virtual void ProgressBarChange(int Value)
|
||||
protected virtual void ProgressBarAdd(int value)
|
||||
{
|
||||
OnProgressBarChange!.Invoke(this, Value);
|
||||
OnProgressBarAdd!.Invoke(this, value);
|
||||
}
|
||||
|
||||
protected virtual void ProgressBarStateChange(ProgressBarState State)
|
||||
protected virtual void ProgressBarStateChange(ProgressBarState state)
|
||||
{
|
||||
OnProgressBarStateChange!.Invoke(this, State);
|
||||
OnProgressBarStateChange!.Invoke(this, state);
|
||||
}
|
||||
|
||||
protected virtual void Install()
|
||||
protected virtual void ErrorShow(string heading, string text)
|
||||
{
|
||||
OnInstall!.Invoke(this, EventArgs.Empty);
|
||||
// ugly hack for now (I don't want to derive EventHandler just for this)
|
||||
OnErrorShow!.Invoke(this, new string[] { heading, text });
|
||||
}
|
||||
|
||||
|
||||
protected virtual void Launch()
|
||||
{
|
||||
OnLaunch!.Invoke(this, EventArgs.Empty);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace Kiseki.Launcher.Helpers
|
|||
{
|
||||
public static async Task<T?> GetJson<T>(string url)
|
||||
{
|
||||
string json = await Controller.HttpClient.GetStringAsync(url);
|
||||
string json = await Web.HttpClient.GetStringAsync(url);
|
||||
|
||||
try
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@ namespace Kiseki.Launcher.Models
|
|||
{
|
||||
public class Health
|
||||
{
|
||||
public string Response { get; set; } = "";
|
||||
public int Response { get; set; } = -1;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
namespace Kiseki.Launcher
|
||||
{
|
||||
public static class Web
|
||||
{
|
||||
public const int RESPONSE_SUCCESS = 0;
|
||||
public const int RESPONSE_FAILURE = -1;
|
||||
|
||||
public const int RESPONSE_MAINTENANCE = 1;
|
||||
|
||||
public const string BaseUrl = "kiseki.lol";
|
||||
public const string MaintenanceDomain = "test";
|
||||
|
||||
public static readonly HttpClient HttpClient = new();
|
||||
public static string CurrentUrl { get; private set; } = "";
|
||||
|
||||
public static void Initialize() => CurrentUrl = BaseUrl;
|
||||
public static string Url(string path) => $"https://{CurrentUrl}/{path}";
|
||||
|
||||
public static async Task<int> CheckHealth()
|
||||
{
|
||||
var response = await Helpers.Http.GetJson<Models.Health>(Url("/api/health"));
|
||||
|
||||
return response?.Response ?? RESPONSE_FAILURE;
|
||||
}
|
||||
|
||||
public static void LoadLicense(string license)
|
||||
{
|
||||
CurrentUrl = $"{MaintenanceDomain}.{CurrentUrl}";
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue