From 92e065feba42f865c4915c7016400c4c145d623d Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 26 Mar 2024 22:11:06 +0800 Subject: [PATCH] feature: simple self-update implementation (#29) --- src/SourceGit/App.JsonCodeGen.cs | 9 ++ src/SourceGit/App.axaml.cs | 62 ++++++++ src/SourceGit/Models/Version.cs | 37 +++++ src/SourceGit/Resources/Icons.axaml | 1 + src/SourceGit/Resources/Locales.Designer.cs | 72 +++++++++ src/SourceGit/Resources/Locales.en.resx | 24 +++ src/SourceGit/Resources/Locales.resx | 24 +++ src/SourceGit/Resources/Locales.zh.resx | 24 +++ src/SourceGit/ViewModels/Preference.cs | 21 ++- src/SourceGit/ViewModels/SelfUpdate.cs | 15 ++ src/SourceGit/Views/Launcher.axaml | 7 + src/SourceGit/Views/Launcher.axaml.cs | 6 + src/SourceGit/Views/Preference.axaml | 7 +- src/SourceGit/Views/SelfUpdate.axaml | 153 ++++++++++++++++++++ src/SourceGit/Views/SelfUpdate.axaml.cs | 39 +++++ 15 files changed, 494 insertions(+), 7 deletions(-) create mode 100644 src/SourceGit/App.JsonCodeGen.cs create mode 100644 src/SourceGit/Models/Version.cs create mode 100644 src/SourceGit/ViewModels/SelfUpdate.cs create mode 100644 src/SourceGit/Views/SelfUpdate.axaml create mode 100644 src/SourceGit/Views/SelfUpdate.axaml.cs diff --git a/src/SourceGit/App.JsonCodeGen.cs b/src/SourceGit/App.JsonCodeGen.cs new file mode 100644 index 00000000..af73a68e --- /dev/null +++ b/src/SourceGit/App.JsonCodeGen.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace SourceGit +{ + [JsonSourceGenerationOptions(WriteIndented = true, IgnoreReadOnlyFields = true, IgnoreReadOnlyProperties = true)] + [JsonSerializable(typeof(Models.Version))] + [JsonSerializable(typeof(ViewModels.Preference))] + internal partial class JsonCodeGen : JsonSerializerContext { } +} diff --git a/src/SourceGit/App.axaml.cs b/src/SourceGit/App.axaml.cs index f0e2dac7..6780fd2e 100644 --- a/src/SourceGit/App.axaml.cs +++ b/src/SourceGit/App.axaml.cs @@ -2,8 +2,11 @@ using System; using System.Collections; using System.Globalization; using System.IO; +using System.Net.Http; using System.Reflection; using System.Text; +using System.Text.Json; +using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; @@ -13,6 +16,7 @@ using Avalonia.Markup.Xaml; using Avalonia.Media; using Avalonia.Media.Fonts; using Avalonia.Styling; +using Avalonia.Threading; namespace SourceGit { @@ -162,6 +166,43 @@ namespace SourceGit return null; } + public static void Check4Update(bool manually = false) + { + Task.Run(async () => + { + try + { + // Fetch lastest release information. + var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(2) }; + var data = await client.GetStringAsync("https://api.github.com/repos/sourcegit-scm/sourcegit/releases/latest"); + + // Parse json into Models.Version. + var ver = JsonSerializer.Deserialize(data, JsonCodeGen.Default.Version); + if (ver == null) return; + + // Check if already up-to-date. + if (!ver.IsNewVersion) + { + if (manually) ShowSelfUpdateResult(new Models.AlreadyUpToDate()); + return; + } + + // Should not check ignored tag if this is called manually. + if (!manually) + { + var pref = ViewModels.Preference.Instance; + if (ver.TagName == pref.IgnoreUpdateTag) return; + } + + ShowSelfUpdateResult(ver); + } + catch (Exception e) + { + if (manually) ShowSelfUpdateResult(e); + } + }); + } + public static void Quit() { if (Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) @@ -190,11 +231,32 @@ namespace SourceGit var launcher = new Views.Launcher(); _notificationReceiver = launcher; desktop.MainWindow = launcher; + + if (ViewModels.Preference.Instance.Check4UpdatesOnStartup) Check4Update(); } base.OnFrameworkInitializationCompleted(); } + private static void ShowSelfUpdateResult(object data) + { + Dispatcher.UIThread.Post(() => + { + if (Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + var dialog = new Views.SelfUpdate() + { + DataContext = new ViewModels.SelfUpdate + { + Data = data + } + }; + + dialog.Show(desktop.MainWindow); + } + }); + } + private ResourceDictionary _activeLocale = null; private Models.INotificationReceiver _notificationReceiver = null; } diff --git a/src/SourceGit/Models/Version.cs b/src/SourceGit/Models/Version.cs new file mode 100644 index 00000000..301de02a --- /dev/null +++ b/src/SourceGit/Models/Version.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using System.Text.Json.Serialization; +using System.Text.RegularExpressions; + +namespace SourceGit.Models +{ + public partial class Version + { + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("tag_name")] + public string TagName { get; set; } + + [JsonPropertyName("body")] + public string Body { get; set; } + + [GeneratedRegex(@"^v(\d+)\.(\d+)$")] + private static partial Regex REG_VERSION_TAG(); + + public bool IsNewVersion + { + get + { + var match = REG_VERSION_TAG().Match(TagName); + if (!match.Success) return false; + + var major = int.Parse(match.Groups[1].Value); + var minor = int.Parse(match.Groups[2].Value); + var ver = Assembly.GetExecutingAssembly().GetName().Version; + return ver.Major < major || (ver.Major == major && ver.Minor < minor); + } + } + } + + public class AlreadyUpToDate { } +} diff --git a/src/SourceGit/Resources/Icons.axaml b/src/SourceGit/Resources/Icons.axaml index b84385d5..6b2c3217 100644 --- a/src/SourceGit/Resources/Icons.axaml +++ b/src/SourceGit/Resources/Icons.axaml @@ -86,4 +86,5 @@ M875 117H149C109 117 75 151 75 192v640c0 41 34 75 75 75h725c41 0 75-34 75-75V192c0-41-34-75-75-75zM139 832V192c0-6 4-11 11-11h331v661H149c-6 0-11-4-11-11zm747 0c0 6-4 11-11 11H544v-661H875c6 0 11 4 11 11v640z M875 117H149C109 117 75 151 75 192v640c0 41 34 75 75 75h725c41 0 75-34 75-75V192c0-41-34-75-75-75zm-725 64h725c6 0 11 4 11 11v288h-747V192c0-6 4-11 11-11zm725 661H149c-6 0-11-4-11-11V544h747V832c0 6-4 11-11 11z M875 128h-725A107 107 0 0043 235v555A107 107 0 00149 896h725a107 107 0 00107-107v-555A107 107 0 00875 128zm-115 640h-183v-58l25-3c15 0 19-8 14-24l-22-61H419l-28 82 39 2V768h-166v-58l18-3c18-2 22-11 26-24l125-363-40-4V256h168l160 448 39 3zM506 340l-72 218h145l-71-218h-2z + M900 287c40 69 60 144 60 225s-20 156-60 225c-40 69-94 123-163 163-69 40-144 60-225 60s-156-20-225-60c-69-40-123-94-163-163C84 668 64 593 64 512s20-156 60-225 94-123 163-163c69-40 144-60 225-60s156 20 225 60 123 94 163 163zM762 512c0-9-3-16-9-22L578 315l-44-44c-6-6-13-9-22-9s-16 3-22 9l-44 44-176 176c-6 6-9 13-9 22s3 16 9 22l44 44c6 6 13 9 22 9s16-3 22-9l92-92v269c0 9 3 16 9 22 6 6 13 9 22 9h62c8 0 16-3 22-9 6-6 9-13 9-22V486l92 92c6 6 13 9 22 9 8 0 16-3 22-9l44-44c6-6 9-13 9-22z diff --git a/src/SourceGit/Resources/Locales.Designer.cs b/src/SourceGit/Resources/Locales.Designer.cs index 09bcd24a..ba492504 100644 --- a/src/SourceGit/Resources/Locales.Designer.cs +++ b/src/SourceGit/Resources/Locales.Designer.cs @@ -2301,6 +2301,15 @@ namespace SourceGit.Resources { } } + /// + /// Looks up a localized string similar to Check for updates on startup. + /// + public static string Text_Preference_General_Check4UpdatesOnStartup { + get { + return ResourceManager.GetString("Text.Preference.General.Check4UpdatesOnStartup", resourceCulture); + } + } + /// /// Looks up a localized string similar to Language. /// @@ -3255,6 +3264,69 @@ namespace SourceGit.Resources { } } + /// + /// Looks up a localized string similar to Check for Updates .... + /// + public static string Text_SelfUpdate { + get { + return ResourceManager.GetString("Text.SelfUpdate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to New version of this software is available: . + /// + public static string Text_SelfUpdate_Available { + get { + return ResourceManager.GetString("Text.SelfUpdate.Available", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Check for updates failed!. + /// + public static string Text_SelfUpdate_Error { + get { + return ResourceManager.GetString("Text.SelfUpdate.Error", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Download. + /// + public static string Text_SelfUpdate_GotoDownload { + get { + return ResourceManager.GetString("Text.SelfUpdate.GotoDownload", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Skip This Version. + /// + public static string Text_SelfUpdate_IgnoreThisVersion { + get { + return ResourceManager.GetString("Text.SelfUpdate.IgnoreThisVersion", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Software Update. + /// + public static string Text_SelfUpdate_Title { + get { + return ResourceManager.GetString("Text.SelfUpdate.Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to There are currently no updates available.. + /// + public static string Text_SelfUpdate_UpToDate { + get { + return ResourceManager.GetString("Text.SelfUpdate.UpToDate", resourceCulture); + } + } + /// /// Looks up a localized string similar to Squash HEAD Into Parent. /// diff --git a/src/SourceGit/Resources/Locales.en.resx b/src/SourceGit/Resources/Locales.en.resx index a41f7d8e..2f9e7bdd 100644 --- a/src/SourceGit/Resources/Locales.en.resx +++ b/src/SourceGit/Resources/Locales.en.resx @@ -1302,4 +1302,28 @@ APPEARANCE + + Software Update + + + Check for updates failed! + + + New version of this software is available: + + + Download + + + Skip This Version + + + Check for Updates ... + + + There are currently no updates available. + + + Check for updates on startup + \ No newline at end of file diff --git a/src/SourceGit/Resources/Locales.resx b/src/SourceGit/Resources/Locales.resx index 0d73ab60..8e06cd4d 100644 --- a/src/SourceGit/Resources/Locales.resx +++ b/src/SourceGit/Resources/Locales.resx @@ -1302,4 +1302,28 @@ Appearance + + Software Update + + + Check for updates failed! + + + New version of this software is available: + + + Download + + + Skip This Version + + + Check for Updates ... + + + There are currently no updates available. + + + Check for updates on startup + \ No newline at end of file diff --git a/src/SourceGit/Resources/Locales.zh.resx b/src/SourceGit/Resources/Locales.zh.resx index 58441466..5371c768 100644 --- a/src/SourceGit/Resources/Locales.zh.resx +++ b/src/SourceGit/Resources/Locales.zh.resx @@ -1302,4 +1302,28 @@ 外观配置 + + 软件更新 + + + 获取最新版本信息失败! + + + 检测到软件有版本更新: + + + 下 载 + + + 忽略此版本 + + + 检测更新... + + + 当前已是最新版本。 + + + 启动时检测软件更新 + \ No newline at end of file diff --git a/src/SourceGit/ViewModels/Preference.cs b/src/SourceGit/ViewModels/Preference.cs index 9e769dd8..c1ed4ef5 100644 --- a/src/SourceGit/ViewModels/Preference.cs +++ b/src/SourceGit/ViewModels/Preference.cs @@ -28,7 +28,7 @@ namespace SourceGit.ViewModels { try { - _instance = JsonSerializer.Deserialize(File.ReadAllText(_savePath), JsonSerializationCodeGen.Default.Preference); + _instance = JsonSerializer.Deserialize(File.ReadAllText(_savePath), JsonCodeGen.Default.Preference); } catch { @@ -133,6 +133,18 @@ namespace SourceGit.ViewModels set => SetProperty(ref _useFixedTabWidth, value); } + public bool Check4UpdatesOnStartup + { + get => _check4UpdatesOnStartup; + set => SetProperty(ref _check4UpdatesOnStartup, value); + } + + public string IgnoreUpdateTag + { + get; + set; + } = string.Empty; + public bool UseTwoColumnsLayoutInHistories { get => _useTwoColumnsLayoutInHistories; @@ -342,7 +354,7 @@ namespace SourceGit.ViewModels var dir = Path.GetDirectoryName(_savePath); if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); - var data = JsonSerializer.Serialize(_instance, JsonSerializationCodeGen.Default.Preference); + var data = JsonSerializer.Serialize(_instance, JsonCodeGen.Default.Preference); File.WriteAllText(_savePath, data); } @@ -390,6 +402,7 @@ namespace SourceGit.ViewModels private int _maxHistoryCommits = 20000; private bool _restoreTabs = false; private bool _useFixedTabWidth = true; + private bool _check4UpdatesOnStartup = true; private bool _useTwoColumnsLayoutInHistories = false; private bool _useSideBySideDiff = false; private bool _useSyntaxHighlighting = false; @@ -421,8 +434,4 @@ namespace SourceGit.ViewModels writer.WriteStringValue(value.ToString()); } } - - [JsonSourceGenerationOptions(WriteIndented = true, IgnoreReadOnlyFields = true, IgnoreReadOnlyProperties = true)] - [JsonSerializable(typeof(Preference))] - internal partial class JsonSerializationCodeGen : JsonSerializerContext { } } \ No newline at end of file diff --git a/src/SourceGit/ViewModels/SelfUpdate.cs b/src/SourceGit/ViewModels/SelfUpdate.cs new file mode 100644 index 00000000..3b471576 --- /dev/null +++ b/src/SourceGit/ViewModels/SelfUpdate.cs @@ -0,0 +1,15 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace SourceGit.ViewModels +{ + public class SelfUpdate : ObservableObject + { + public object Data + { + get => _data; + set => SetProperty(ref _data, value); + } + + private object _data = null; + } +} diff --git a/src/SourceGit/Views/Launcher.axaml b/src/SourceGit/Views/Launcher.axaml index 1e83a651..6a583c7d 100644 --- a/src/SourceGit/Views/Launcher.axaml +++ b/src/SourceGit/Views/Launcher.axaml @@ -65,6 +65,13 @@ + + + + + + + diff --git a/src/SourceGit/Views/Launcher.axaml.cs b/src/SourceGit/Views/Launcher.axaml.cs index 959f0061..57c5e282 100644 --- a/src/SourceGit/Views/Launcher.axaml.cs +++ b/src/SourceGit/Views/Launcher.axaml.cs @@ -317,6 +317,12 @@ namespace SourceGit.Views e.Handled = true; } + private void Check4Update(object sender, RoutedEventArgs e) + { + App.Check4Update(true); + e.Handled = true; + } + private async void OpenAboutDialog(object sender, RoutedEventArgs e) { var dialog = new About(); diff --git a/src/SourceGit/Views/Preference.axaml b/src/SourceGit/Views/Preference.axaml index 9625d027..24d10e8d 100644 --- a/src/SourceGit/Views/Preference.axaml +++ b/src/SourceGit/Views/Preference.axaml @@ -67,7 +67,7 @@ - + + + diff --git a/src/SourceGit/Views/SelfUpdate.axaml b/src/SourceGit/Views/SelfUpdate.axaml new file mode 100644 index 00000000..f29da5b5 --- /dev/null +++ b/src/SourceGit/Views/SelfUpdate.axaml @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +