From 4215dc76f40487c7869b35044048891bdfe7de04 Mon Sep 17 00:00:00 2001 From: chylex Date: Sun, 31 Dec 2023 18:50:24 +0100 Subject: [PATCH] Add main menu button to check for updates Closes #165 --- app/Desktop/Common/SystemUtils.cs | 9 ++ app/Desktop/Main/AboutWindowModel.cs | 24 +++-- app/Desktop/Main/Pages/ViewerPageModel.cs | 6 +- app/Desktop/Main/Screens/WelcomeScreen.axaml | 21 ++--- .../Main/Screens/WelcomeScreenModel.cs | 88 +++++++++++++------ app/Desktop/Program.cs | 19 +++- 6 files changed, 109 insertions(+), 58 deletions(-) create mode 100644 app/Desktop/Common/SystemUtils.cs diff --git a/app/Desktop/Common/SystemUtils.cs b/app/Desktop/Common/SystemUtils.cs new file mode 100644 index 0000000..2782abf --- /dev/null +++ b/app/Desktop/Common/SystemUtils.cs @@ -0,0 +1,9 @@ +using System.Diagnostics; + +namespace DHT.Desktop.Common; + +static class SystemUtils { + public static void OpenUrl(string url) { + Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }); + } +} diff --git a/app/Desktop/Main/AboutWindowModel.cs b/app/Desktop/Main/AboutWindowModel.cs index 2b84bf0..260d57f 100644 --- a/app/Desktop/Main/AboutWindowModel.cs +++ b/app/Desktop/Main/AboutWindowModel.cs @@ -1,45 +1,41 @@ -using System.Diagnostics; +using DHT.Desktop.Common; namespace DHT.Desktop.Main; sealed class AboutWindowModel { public void ShowOfficialWebsite() { - OpenUrl("https://dht.chylex.com"); + SystemUtils.OpenUrl(Program.Website); } public void ShowIssueTracker() { - OpenUrl("https://github.com/chylex/Discord-History-Tracker/issues"); + SystemUtils.OpenUrl("https://github.com/chylex/Discord-History-Tracker/issues"); } public void ShowSourceCode() { - OpenUrl("https://github.com/chylex/Discord-History-Tracker"); + SystemUtils.OpenUrl("https://github.com/chylex/Discord-History-Tracker"); } public void ShowLibraryNetCore() { - OpenUrl("https://github.com/dotnet/core"); + SystemUtils.OpenUrl("https://github.com/dotnet/core"); } public void ShowLibraryAvalonia() { - OpenUrl("https://www.nuget.org/packages/Avalonia"); + SystemUtils.OpenUrl("https://www.nuget.org/packages/Avalonia"); } public void ShowLibraryCommunityToolkit() { - OpenUrl("https://github.com/CommunityToolkit/dotnet"); + SystemUtils.OpenUrl("https://github.com/CommunityToolkit/dotnet"); } public void ShowLibrarySqlite() { - OpenUrl("https://www.sqlite.org"); + SystemUtils.OpenUrl("https://www.sqlite.org"); } public void ShowLibrarySqliteAdoNet() { - OpenUrl("https://www.nuget.org/packages/Microsoft.Data.Sqlite"); + SystemUtils.OpenUrl("https://www.nuget.org/packages/Microsoft.Data.Sqlite"); } public void ShowLibraryRxNet() { - OpenUrl("https://github.com/dotnet/reactive"); - } - - private static void OpenUrl(string url) { - Process.Start(new ProcessStartInfo { FileName = url, UseShellExecute = true }); + SystemUtils.OpenUrl("https://github.com/dotnet/reactive"); } } diff --git a/app/Desktop/Main/Pages/ViewerPageModel.cs b/app/Desktop/Main/Pages/ViewerPageModel.cs index 42d506e..c264819 100644 --- a/app/Desktop/Main/Pages/ViewerPageModel.cs +++ b/app/Desktop/Main/Pages/ViewerPageModel.cs @@ -1,6 +1,5 @@ using System; using System.ComponentModel; -using System.Diagnostics; using System.Threading.Tasks; using System.Web; using Avalonia.Controls; @@ -52,10 +51,7 @@ public async void OnClickOpenViewer() { string serverUrl = "http://127.0.0.1:" + ServerConfiguration.Port; string serverToken = ServerConfiguration.Token; string sessionId = state.ViewerSessions.Register(new ViewerSession(FilterModel.CreateFilter())).ToString(); - - Process.Start(new ProcessStartInfo(serverUrl + "/viewer/?token=" + HttpUtility.UrlEncode(serverToken) + "&session=" + HttpUtility.UrlEncode(sessionId)) { - UseShellExecute = true - }); + SystemUtils.OpenUrl(serverUrl + "/viewer/?token=" + HttpUtility.UrlEncode(serverToken) + "&session=" + HttpUtility.UrlEncode(sessionId)); } catch (Exception e) { await Dialog.ShowOk(window, "Open Viewer", "Could not open viewer: " + e.Message); } diff --git a/app/Desktop/Main/Screens/WelcomeScreen.axaml b/app/Desktop/Main/Screens/WelcomeScreen.axaml index 2e47ded..562d12f 100644 --- a/app/Desktop/Main/Screens/WelcomeScreen.axaml +++ b/app/Desktop/Main/Screens/WelcomeScreen.axaml @@ -22,20 +22,21 @@ - - - - - - - - - + + + + + + + + + diff --git a/app/Desktop/Main/Screens/WelcomeScreenModel.cs b/app/Desktop/Main/Screens/WelcomeScreenModel.cs index c4a23b7..8d24cdb 100644 --- a/app/Desktop/Main/Screens/WelcomeScreenModel.cs +++ b/app/Desktop/Main/Screens/WelcomeScreenModel.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.IO; +using System.Net; +using System.Net.Http; using System.Threading.Tasks; using Avalonia.Controls; using CommunityToolkit.Mvvm.ComponentModel; @@ -10,17 +12,20 @@ using DHT.Server.Data.Settings; using DHT.Server.Database; using DHT.Server.Database.Sqlite.Schema; +using DHT.Utils.Logging; namespace DHT.Desktop.Main.Screens; sealed partial class WelcomeScreenModel : ObservableObject { + private static readonly Log Log = Log.ForType(); + public string Version => Program.Version; - + [ObservableProperty(Setter = Access.Private)] private bool isOpenOrCreateDatabaseButtonEnabled = true; - - public event EventHandler? DatabaseSelected; - + + public event EventHandler? DatabaseSelected; + private readonly Window window; private string? dbFilePath; @@ -46,28 +51,22 @@ public async Task OpenOrCreateDatabase() { public async Task OpenOrCreateDatabaseFromPath(string path) { dbFilePath = path; - + bool isNew = !File.Exists(path); - + var db = await DatabaseGui.TryOpenOrCreateDatabaseFromPath(path, window, new SchemaUpgradeCallbacks(window)); if (db == null) { return; } - + if (isNew && await Dialog.ShowYesNo(window, "Automatic Downloads", "Do you want to automatically download files hosted on Discord? You can change this later in the Downloads tab.") == DialogResult.YesNo.Yes) { await db.Settings.Set(SettingsKey.DownloadsAutoStart, true); } - + DatabaseSelected?.Invoke(this, db); } - private sealed class SchemaUpgradeCallbacks : ISchemaUpgradeCallbacks { - private readonly Window window; - - public SchemaUpgradeCallbacks(Window window) { - this.window = window; - } - + private sealed class SchemaUpgradeCallbacks(Window window) : ISchemaUpgradeCallbacks { public async Task CanUpgrade() { return DialogResult.YesNo.Yes == await DatabaseGui.ShowCanUpgradeDatabaseDialog(window); } @@ -80,20 +79,12 @@ async Task StartUpgrade(IReadOnlyList callbacks) { await doUpgrade(reporter); await Task.Delay(TimeSpan.FromMilliseconds(600)); } - + await new ProgressDialog { DataContext = new ProgressDialogModel("Upgrading Database", StartUpgrade, progressItems: 3) }.ShowProgressDialog(window); } - private sealed class ProgressReporter : ISchemaUpgradeCallbacks.IProgressReporter { - private readonly IReadOnlyList callbacks; - - private readonly int versionSteps; + private sealed class ProgressReporter(int versionSteps, IReadOnlyList callbacks) : ISchemaUpgradeCallbacks.IProgressReporter { private int versionProgress = 0; - - public ProgressReporter(int versionSteps, IReadOnlyList callbacks) { - this.callbacks = callbacks; - this.versionSteps = versionSteps; - } public async Task NextVersion() { await callbacks[0].Update("Upgrading schema version...", versionProgress++, versionSteps); @@ -118,6 +109,53 @@ private async Task HideChildren(int parentIndex) { } } + public async Task CheckUpdates() { + Version? latestVersion = await ProgressDialog.ShowIndeterminate(window, "Check Updates", "Checking for updates...", async _ => { + var client = new HttpClient(new SocketsHttpHandler { + AutomaticDecompression = DecompressionMethods.None, + AllowAutoRedirect = false, + UseCookies = false + }); + + client.Timeout = TimeSpan.FromSeconds(30); + client.MaxResponseContentBufferSize = 1024; + client.DefaultRequestHeaders.UserAgent.ParseAdd("DiscordHistoryTracker/" + Program.Version); + + string response; + try { + response = await client.GetStringAsync(Program.Website + "/version"); + } catch (TaskCanceledException e) when (e.InnerException is TimeoutException) { + await Dialog.ShowOk(window, "Check Updates", "Request timed out."); + return null; + } catch (Exception e) { + Log.Error(e); + await Dialog.ShowOk(window, "Check Updates", "Error checking for updates: " + e.Message); + return null; + } + + if (!System.Version.TryParse(response, out var latestVersion)) { + await Dialog.ShowOk(window, "Check Updates", "Server returned an invalid response."); + return null; + } + + return latestVersion; + }); + + if (latestVersion == null) { + return; + } + + if (Program.AssemblyVersion >= latestVersion) { + await Dialog.ShowOk(window, "Check Updates", "You are using the latest version."); + return; + } + + if (await Dialog.ShowYesNo(window, "Check Updates", "A newer version is available: v" + Program.VersionToString(latestVersion) + "\nVisit the official website and close the app?") == DialogResult.YesNo.Yes) { + SystemUtils.OpenUrl(Program.Website); + Exit(); + } + } + public async Task ShowAboutDialog() { await new AboutWindow { DataContext = new AboutWindowModel() }.ShowDialog(window); } diff --git a/app/Desktop/Program.cs b/app/Desktop/Program.cs index 2869caf..9f4633d 100644 --- a/app/Desktop/Program.cs +++ b/app/Desktop/Program.cs @@ -9,17 +9,18 @@ namespace DHT.Desktop; static class Program { public static string Version { get; } + public static Version AssemblyVersion { get; } public static CultureInfo Culture { get; } public static ResourceLoader Resources { get; } public static Arguments Arguments { get; } + public const string Website = "https://dht.chylex.com"; + static Program() { var assembly = Assembly.GetExecutingAssembly(); - Version = assembly.GetName().Version?.ToString() ?? ""; - while (Version.EndsWith(".0")) { - Version = Version[..^2]; - } + AssemblyVersion = assembly.GetName().Version ?? new Version(0, 0, 0, 0); + Version = VersionToString(AssemblyVersion); Culture = CultureInfo.CurrentCulture; CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; @@ -30,6 +31,16 @@ static Program() { Resources = new ResourceLoader(assembly); Arguments = new Arguments(Environment.GetCommandLineArgs()); } + + public static string VersionToString(Version version) { + string versionStr = version.ToString(); + + while (versionStr.EndsWith(".0")) { + versionStr = versionStr[..^2]; + } + + return versionStr; + } public static void Main(string[] args) { if (Arguments.Console && OperatingSystem.IsWindows()) {