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 @@
+
+
+