From 338f91357ef996247e7c2e5f95c0ac80e2873014 Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 20 May 2022 16:00:25 +0800 Subject: [PATCH] optimize<*>: remove deprecated apis (older than .NET 6) --- src/Commands/Command.cs | 364 +++++++------- src/Models/Version.cs | 149 +++--- src/Views/About.xaml | 248 +++++----- src/Views/About.xaml.cs | 103 ++-- src/Views/Controls/Avatar.cs | 488 +++++++++---------- src/Views/Upgrade.xaml.cs | 60 ++- src/Views/Widgets/Histories.xaml.cs | 10 - src/Views/Widgets/WorkingCopyChanges.xaml.cs | 4 - 8 files changed, 677 insertions(+), 749 deletions(-) diff --git a/src/Commands/Command.cs b/src/Commands/Command.cs index 2228727f..90eff69a 100644 --- a/src/Commands/Command.cs +++ b/src/Commands/Command.cs @@ -1,186 +1,178 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; -using System.Text.RegularExpressions; - -namespace SourceGit.Commands { - - /// - /// 用于取消命令执行的上下文对象 - /// - public class Context { - public bool IsCancelRequested { get; set; } = false; - } - - /// - /// 命令接口 - /// - public class Command { - - /// - /// 读取全部输出时的结果 - /// - public class ReadToEndResult { - public bool IsSuccess { get; set; } - public string Output { get; set; } - public string Error { get; set; } - } - - /// - /// 上下文 - /// - public Context Ctx { get; set; } = null; - - /// - /// 运行路径 - /// - public string Cwd { get; set; } = ""; - - /// - /// 参数 - /// - public string Args { get; set; } = ""; - - /// - /// 是否忽略错误 - /// - public bool DontRaiseError { get; set; } = false; - - /// - /// 使用标准错误输出 - /// - public bool TraitErrorAsOutput { get; set; } = false; - - /// - /// 用于设置该进程独有的环境变量 - /// - public Dictionary Envs { get; set; } = new Dictionary(); - - /// - /// 运行 - /// - public bool Exec() { - var start = new ProcessStartInfo(); - start.FileName = Models.Preference.Instance.Git.Path; - start.Arguments = "--no-pager -c core.quotepath=off " + Args; - start.UseShellExecute = false; - start.CreateNoWindow = true; - start.RedirectStandardOutput = true; - start.RedirectStandardError = true; - start.StandardOutputEncoding = Encoding.UTF8; - start.StandardErrorEncoding = Encoding.UTF8; - - if (!string.IsNullOrEmpty(Cwd)) start.WorkingDirectory = Cwd; - - foreach (var kv in Envs) start.EnvironmentVariables[kv.Key] = kv.Value; - - var progressFilter = new Regex(@"\s\d+%\s"); - var errs = new List(); - var proc = new Process() { StartInfo = start }; - var isCancelled = false; - - proc.OutputDataReceived += (o, e) => { - if (Ctx != null && Ctx.IsCancelRequested) { - isCancelled = true; - proc.CancelErrorRead(); - proc.CancelOutputRead(); -#if NET48 - if (!proc.HasExited) proc.Kill(); -#else - if (!proc.HasExited) proc.Kill(true); -#endif - return; - } - - if (e.Data == null) return; - OnReadline(e.Data); - }; - proc.ErrorDataReceived += (o, e) => { - if (Ctx != null && Ctx.IsCancelRequested) { - isCancelled = true; - proc.CancelErrorRead(); - proc.CancelOutputRead(); -#if NET48 - if (!proc.HasExited) proc.Kill(); -#else - if (!proc.HasExited) proc.Kill(true); -#endif - return; - } - - if (string.IsNullOrEmpty(e.Data)) return; - if (TraitErrorAsOutput) OnReadline(e.Data); - - if (progressFilter.IsMatch(e.Data)) return; - if (e.Data.StartsWith("remote: Counting objects:", StringComparison.Ordinal)) return; - errs.Add(e.Data); - }; - - try { - proc.Start(); - } catch (Exception e) { - if (!DontRaiseError) Models.Exception.Raise(e.Message); - return false; - } - - proc.BeginOutputReadLine(); - proc.BeginErrorReadLine(); - proc.WaitForExit(); - - int exitCode = proc.ExitCode; - proc.Close(); - - if (!isCancelled && exitCode != 0 && errs.Count > 0) { - if (!DontRaiseError) Models.Exception.Raise(string.Join("\n", errs)); - return false; - } else { - return true; - } - } - - /// - /// 直接读取全部标准输出 - /// - public ReadToEndResult ReadToEnd() { - var start = new ProcessStartInfo(); - start.FileName = Models.Preference.Instance.Git.Path; - start.Arguments = "--no-pager -c core.quotepath=off " + Args; - start.UseShellExecute = false; - start.CreateNoWindow = true; - start.RedirectStandardOutput = true; - start.RedirectStandardError = true; - start.StandardOutputEncoding = Encoding.UTF8; - start.StandardErrorEncoding = Encoding.UTF8; - - if (!string.IsNullOrEmpty(Cwd)) start.WorkingDirectory = Cwd; - - var proc = new Process() { StartInfo = start }; - try { - proc.Start(); - } catch (Exception e) { - return new ReadToEndResult() { - Output = "", - Error = e.Message, - IsSuccess = false, - }; - } - - var rs = new ReadToEndResult(); - rs.Output = proc.StandardOutput.ReadToEnd(); - rs.Error = proc.StandardError.ReadToEnd(); - - proc.WaitForExit(); - rs.IsSuccess = proc.ExitCode == 0; - proc.Close(); - - return rs; - } - - /// - /// 调用Exec时的读取函数 - /// - /// - public virtual void OnReadline(string line) { } - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Text.RegularExpressions; + +namespace SourceGit.Commands { + + /// + /// 用于取消命令执行的上下文对象 + /// + public class Context { + public bool IsCancelRequested { get; set; } = false; + } + + /// + /// 命令接口 + /// + public class Command { + + /// + /// 读取全部输出时的结果 + /// + public class ReadToEndResult { + public bool IsSuccess { get; set; } + public string Output { get; set; } + public string Error { get; set; } + } + + /// + /// 上下文 + /// + public Context Ctx { get; set; } = null; + + /// + /// 运行路径 + /// + public string Cwd { get; set; } = ""; + + /// + /// 参数 + /// + public string Args { get; set; } = ""; + + /// + /// 是否忽略错误 + /// + public bool DontRaiseError { get; set; } = false; + + /// + /// 使用标准错误输出 + /// + public bool TraitErrorAsOutput { get; set; } = false; + + /// + /// 用于设置该进程独有的环境变量 + /// + public Dictionary Envs { get; set; } = new Dictionary(); + + /// + /// 运行 + /// + public bool Exec() { + var start = new ProcessStartInfo(); + start.FileName = Models.Preference.Instance.Git.Path; + start.Arguments = "--no-pager -c core.quotepath=off " + Args; + start.UseShellExecute = false; + start.CreateNoWindow = true; + start.RedirectStandardOutput = true; + start.RedirectStandardError = true; + start.StandardOutputEncoding = Encoding.UTF8; + start.StandardErrorEncoding = Encoding.UTF8; + + if (!string.IsNullOrEmpty(Cwd)) start.WorkingDirectory = Cwd; + + foreach (var kv in Envs) start.EnvironmentVariables[kv.Key] = kv.Value; + + var progressFilter = new Regex(@"\s\d+%\s"); + var errs = new List(); + var proc = new Process() { StartInfo = start }; + var isCancelled = false; + + proc.OutputDataReceived += (o, e) => { + if (Ctx != null && Ctx.IsCancelRequested) { + isCancelled = true; + proc.CancelErrorRead(); + proc.CancelOutputRead(); + if (!proc.HasExited) proc.Kill(true); + return; + } + + if (e.Data == null) return; + OnReadline(e.Data); + }; + proc.ErrorDataReceived += (o, e) => { + if (Ctx != null && Ctx.IsCancelRequested) { + isCancelled = true; + proc.CancelErrorRead(); + proc.CancelOutputRead(); + if (!proc.HasExited) proc.Kill(true); + return; + } + + if (string.IsNullOrEmpty(e.Data)) return; + if (TraitErrorAsOutput) OnReadline(e.Data); + + if (progressFilter.IsMatch(e.Data)) return; + if (e.Data.StartsWith("remote: Counting objects:", StringComparison.Ordinal)) return; + errs.Add(e.Data); + }; + + try { + proc.Start(); + } catch (Exception e) { + if (!DontRaiseError) Models.Exception.Raise(e.Message); + return false; + } + + proc.BeginOutputReadLine(); + proc.BeginErrorReadLine(); + proc.WaitForExit(); + + int exitCode = proc.ExitCode; + proc.Close(); + + if (!isCancelled && exitCode != 0 && errs.Count > 0) { + if (!DontRaiseError) Models.Exception.Raise(string.Join("\n", errs)); + return false; + } else { + return true; + } + } + + /// + /// 直接读取全部标准输出 + /// + public ReadToEndResult ReadToEnd() { + var start = new ProcessStartInfo(); + start.FileName = Models.Preference.Instance.Git.Path; + start.Arguments = "--no-pager -c core.quotepath=off " + Args; + start.UseShellExecute = false; + start.CreateNoWindow = true; + start.RedirectStandardOutput = true; + start.RedirectStandardError = true; + start.StandardOutputEncoding = Encoding.UTF8; + start.StandardErrorEncoding = Encoding.UTF8; + + if (!string.IsNullOrEmpty(Cwd)) start.WorkingDirectory = Cwd; + + var proc = new Process() { StartInfo = start }; + try { + proc.Start(); + } catch (Exception e) { + return new ReadToEndResult() { + Output = "", + Error = e.Message, + IsSuccess = false, + }; + } + + var rs = new ReadToEndResult(); + rs.Output = proc.StandardOutput.ReadToEnd(); + rs.Error = proc.StandardError.ReadToEnd(); + + proc.WaitForExit(); + rs.IsSuccess = proc.ExitCode == 0; + proc.Close(); + + return rs; + } + + /// + /// 调用Exec时的读取函数 + /// + /// + public virtual void OnReadline(string line) { } + } +} diff --git a/src/Models/Version.cs b/src/Models/Version.cs index 07ff3041..a6cf963a 100644 --- a/src/Models/Version.cs +++ b/src/Models/Version.cs @@ -1,80 +1,69 @@ -using System; -using System.Reflection; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -#if NET6_0 -using System.Net.Http; -#else -using System.Net; -using System.Text; -#endif - -namespace SourceGit.Models { - - /// - /// Github开放API中Release信息格式 - /// - public class Version { - [JsonPropertyName("id")] - public ulong Id { get; set; } - [JsonPropertyName("tag_name")] - public string TagName { get; set; } - [JsonPropertyName("target_commitish")] - public string CommitSHA { get; set; } - [JsonPropertyName("prerelease")] - public bool PreRelease { get; set; } - [JsonPropertyName("name")] - public string Name { get; set; } - [JsonPropertyName("body")] - public string Body { get; set; } - [JsonPropertyName("created_at")] - public DateTime CreatedAt { get; set; } - - public string PublishTime { - get { return CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"); } - } - - public string IsPrerelease { - get { return PreRelease ? "YES" : "NO"; } - } - - public static void Check(Action onUpgradable) { - if (!Preference.Instance.General.CheckForUpdate) return; - - var curDayOfYear = DateTime.Now.DayOfYear; - var lastDayOfYear = Preference.Instance.General.LastCheckDay; - if (lastDayOfYear != curDayOfYear) { - Preference.Instance.General.LastCheckDay = curDayOfYear; - Task.Run(async () => { - try { -#if NET6_0 - var req = new HttpClient(); - var rsp = await req.GetAsync("https://api.github.com/repos/sourcegit-scm/sourcegit/releases/latest"); - rsp.EnsureSuccessStatusCode(); - - var raw = await rsp.Content.ReadAsStringAsync(); -#else - var web = new WebClient() { Encoding = Encoding.UTF8 }; - var raw = await web.DownloadStringTaskAsync("https://api.github.com/repos/sourcegit-scm/sourcegit/releases/latest"); -#endif - var ver = JsonSerializer.Deserialize(raw); - var cur = Assembly.GetExecutingAssembly().GetName().Version; - - var matches = Regex.Match(ver.TagName, @"^v(\d+)\.(\d+).*"); - if (!matches.Success) return; - - var major = int.Parse(matches.Groups[1].Value); - var minor = int.Parse(matches.Groups[2].Value); - if (major > cur.Major || (major == cur.Major && minor > cur.Minor)) { - onUpgradable?.Invoke(ver); - } - } catch { - } - }); - } - } - } -} +using System; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Net.Http; + +namespace SourceGit.Models { + + /// + /// Github开放API中Release信息格式 + /// + public class Version { + [JsonPropertyName("id")] + public ulong Id { get; set; } + [JsonPropertyName("tag_name")] + public string TagName { get; set; } + [JsonPropertyName("target_commitish")] + public string CommitSHA { get; set; } + [JsonPropertyName("prerelease")] + public bool PreRelease { get; set; } + [JsonPropertyName("name")] + public string Name { get; set; } + [JsonPropertyName("body")] + public string Body { get; set; } + [JsonPropertyName("created_at")] + public DateTime CreatedAt { get; set; } + + public string PublishTime { + get { return CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"); } + } + + public string IsPrerelease { + get { return PreRelease ? "YES" : "NO"; } + } + + public static void Check(Action onUpgradable) { + if (!Preference.Instance.General.CheckForUpdate) return; + + var curDayOfYear = DateTime.Now.DayOfYear; + var lastDayOfYear = Preference.Instance.General.LastCheckDay; + if (lastDayOfYear != curDayOfYear) { + Preference.Instance.General.LastCheckDay = curDayOfYear; + Task.Run(async () => { + try { + var req = new HttpClient(); + var rsp = await req.GetAsync("https://api.github.com/repos/sourcegit-scm/sourcegit/releases/latest"); + rsp.EnsureSuccessStatusCode(); + + var raw = await rsp.Content.ReadAsStringAsync(); + var ver = JsonSerializer.Deserialize(raw); + var cur = Assembly.GetExecutingAssembly().GetName().Version; + + var matches = Regex.Match(ver.TagName, @"^v(\d+)\.(\d+).*"); + if (!matches.Success) return; + + var major = int.Parse(matches.Groups[1].Value); + var minor = int.Parse(matches.Groups[2].Value); + if (major > cur.Major || (major == cur.Major && minor > cur.Minor)) { + onUpgradable?.Invoke(ver); + } + } catch { + } + }); + } + } + } +} diff --git a/src/Views/About.xaml b/src/Views/About.xaml index 361bab18..e084e0b0 100644 --- a/src/Views/About.xaml +++ b/src/Views/About.xaml @@ -1,124 +1,124 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Views/About.xaml.cs b/src/Views/About.xaml.cs index c533ae2b..cce5e618 100644 --- a/src/Views/About.xaml.cs +++ b/src/Views/About.xaml.cs @@ -1,55 +1,48 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Reflection; -using System.Windows; - -namespace SourceGit.Views { - - /// - /// 关于对话框 - /// - public partial class About : Controls.Window { - - public class Keymap { - public string Key { get; set; } - public string Desc { get; set; } - public Keymap(string k, string d) { Key = k; Desc = App.Text($"Hotkeys.{d}"); } - } - - public About() { - InitializeComponent(); - - var asm = Assembly.GetExecutingAssembly().GetName(); - var framework = AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName; - var dotnetVer = framework.Substring(framework.IndexOf("=") + 1); - - version.Text = $"VERSION : v{asm.Version.Major}.{asm.Version.Minor} .NET : {dotnetVer}"; - - hotkeys.ItemsSource = new List() { - new Keymap("CTRL + T", "NewTab"), - new Keymap("CTRL + W", "CloseTab"), - new Keymap("CTRL + TAB", "NextTab"), - new Keymap("CTRL + [1-9]", "SwitchTo"), - new Keymap("CTRL + F", "Search"), - new Keymap("F5", "Refresh"), - new Keymap("SPACE", "ToggleStage"), - new Keymap("ESC", "CancelPopup"), - }; - } - - private void OnRequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) { -#if NET48 - Process.Start(e.Uri.AbsoluteUri); -#else - var info = new ProcessStartInfo("cmd", $"/c start {e.Uri.AbsoluteUri}"); - info.CreateNoWindow = true; - Process.Start(info); -#endif - } - - private void Quit(object sender, RoutedEventArgs e) { - Close(); - } - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Windows; + +namespace SourceGit.Views { + + /// + /// 关于对话框 + /// + public partial class About : Controls.Window { + + public class Keymap { + public string Key { get; set; } + public string Desc { get; set; } + public Keymap(string k, string d) { Key = k; Desc = App.Text($"Hotkeys.{d}"); } + } + + public About() { + InitializeComponent(); + + var asm = Assembly.GetExecutingAssembly().GetName(); + version.Text = $"VERSION : v{asm.Version.Major}.{asm.Version.Minor}"; + + hotkeys.ItemsSource = new List() { + new Keymap("CTRL + T", "NewTab"), + new Keymap("CTRL + W", "CloseTab"), + new Keymap("CTRL + TAB", "NextTab"), + new Keymap("CTRL + [1-9]", "SwitchTo"), + new Keymap("CTRL + F", "Search"), + new Keymap("F5", "Refresh"), + new Keymap("SPACE", "ToggleStage"), + new Keymap("ESC", "CancelPopup"), + }; + } + + private void OnRequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) { + var info = new ProcessStartInfo("cmd", $"/c start {e.Uri.AbsoluteUri}"); + info.CreateNoWindow = true; + Process.Start(info); + } + + private void Quit(object sender, RoutedEventArgs e) { + Close(); + } + } +} diff --git a/src/Views/Controls/Avatar.cs b/src/Views/Controls/Avatar.cs index 45fbb539..128033fe 100644 --- a/src/Views/Controls/Avatar.cs +++ b/src/Views/Controls/Avatar.cs @@ -1,258 +1,230 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Net; -using System.Security.Cryptography; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; -using System.Windows.Media.Imaging; - -#if NET6_0 -using System.Net.Http; -#endif - -namespace SourceGit.Views.Controls { - - /// - /// 头像控件 - /// - public class Avatar : Image { - - /// - /// 显示FallbackLabel时的背景色 - /// - private static readonly Brush[] BACKGROUND_BRUSHES = new Brush[] { - new LinearGradientBrush(Colors.Orange, Color.FromRgb(255, 213, 134), 90), - new LinearGradientBrush(Colors.DodgerBlue, Colors.LightSkyBlue, 90), - new LinearGradientBrush(Colors.LimeGreen, Color.FromRgb(124, 241, 124), 90), - new LinearGradientBrush(Colors.Orchid, Color.FromRgb(248, 161, 245), 90), - new LinearGradientBrush(Colors.Tomato, Color.FromRgb(252, 165, 150), 90), - }; - - /// - /// 头像资源本地缓存路径 - /// - public static readonly string CACHE_PATH = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), - "SourceGit", - "avatars"); - - /// - /// 邮件属性定义 - /// - public static readonly DependencyProperty EmailProperty = DependencyProperty.Register( - "Email", - typeof(string), - typeof(Avatar), - new PropertyMetadata(null, OnEmailChanged)); - - /// - /// 邮件属性 - /// - public string Email { - get { return (string)GetValue(EmailProperty); } - set { SetValue(EmailProperty, value); } - } - - /// - /// 下载头像失败时显示的Label属性定义 - /// - public static readonly DependencyProperty FallbackLabelProperty = DependencyProperty.Register( - "FallbackLabel", - typeof(string), - typeof(Avatar), - new PropertyMetadata("?", OnFallbackLabelChanged)); - - /// - /// 下载头像失败时显示的Label属性 - /// - public string FallbackLabel { - get { return (string)GetValue(FallbackLabelProperty); } - set { SetValue(FallbackLabelProperty, value); } - } - - private static Dictionary> requesting = new Dictionary>(); - private static Dictionary loaded = new Dictionary(); - private static Task loader = null; - - private int colorIdx = 0; - private FormattedText label = null; - - public Avatar() { - SetValue(RenderOptions.BitmapScalingModeProperty, BitmapScalingMode.HighQuality); - SetValue(RenderOptions.ClearTypeHintProperty, ClearTypeHint.Auto); - Unloaded += (o, e) => Cancel(Email); - } - - /// - /// 取消一个下载任务 - /// - /// - private void Cancel(string email) { - if (!string.IsNullOrEmpty(email) && requesting.ContainsKey(email)) { - if (requesting[email].Count <= 1) { - requesting.Remove(email); - } else { - requesting[email].Remove(this); - } - } - } - - /// - /// 渲染实现 - /// - /// - protected override void OnRender(DrawingContext dc) { - base.OnRender(dc); - - if (Source == null && label != null) { - var corner = Math.Max(2, Width / 16); - var offsetX = (double)0; - if (HorizontalAlignment == HorizontalAlignment.Right) { - offsetX = -Width * 0.5; - } else if (HorizontalAlignment == HorizontalAlignment.Left) { - offsetX = Width * 0.5; - } - - Brush brush = BACKGROUND_BRUSHES[colorIdx]; - dc.DrawRoundedRectangle(brush, null, new Rect(-Width * 0.5 + offsetX, -Height * 0.5, Width, Height), corner, corner); - dc.DrawText(label, new Point(label.Width * -0.5 + offsetX, label.Height * -0.5)); - } - } - - /// - /// 显示文本变化时触发 - /// - /// - /// - private static void OnFallbackLabelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - Avatar a = d as Avatar; - if (a == null) return; - - var placeholder = a.FallbackLabel.Length > 0 ? a.FallbackLabel.Substring(0, 1) : "?"; - - a.colorIdx = 0; - a.label = new FormattedText( - placeholder, - CultureInfo.CurrentCulture, - FlowDirection.LeftToRight, - new Typeface(new FontFamily(Models.Preference.Instance.General.FontFamilyWindow), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal), - a.Width * 0.65, - Brushes.White, - VisualTreeHelper.GetDpi(a).PixelsPerDip); - - var chars = placeholder.ToCharArray(); - foreach (var ch in chars) a.colorIdx += Math.Abs(ch); - a.colorIdx = a.colorIdx % BACKGROUND_BRUSHES.Length; - } - - /// - /// 邮件变化时触发 - /// - /// - /// - private static void OnEmailChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - Avatar a = d as Avatar; - if (a == null) return; - - a.Cancel(e.OldValue as string); - a.Source = null; - a.InvalidateVisual(); - - var email = e.NewValue as string; - if (string.IsNullOrEmpty(email)) return; - - if (loaded.ContainsKey(email)) { - a.Source = loaded[email]; - return; - } - - if (requesting.ContainsKey(email)) { - requesting[email].Add(a); - return; - } - - byte[] hash = MD5.Create().ComputeHash(Encoding.Default.GetBytes(email.ToLower().Trim())); - string md5 = ""; - for (int i = 0; i < hash.Length; i++) md5 += hash[i].ToString("x2"); - md5 = md5.ToLower(); - - string filePath = Path.Combine(CACHE_PATH, md5); - if (File.Exists(filePath)) { - var img = new BitmapImage(new Uri(filePath)); - loaded.Add(email, img); - a.Source = img; - return; - } - - requesting.Add(email, new List()); - requesting[email].Add(a); - - Action job = () => { - if (!requesting.ContainsKey(email)) return; - - try { -#if NET6_0 - var req = new HttpClient().GetAsync(Models.Preference.Instance.General.AvatarServer + md5 + "?d=404"); - req.Wait(); - - var rsp = req.Result; - if (rsp != null && rsp.StatusCode == HttpStatusCode.OK) { - var writer = File.OpenWrite(filePath); - rsp.Content.CopyToAsync(writer).Wait(); - writer.Close(); - - a.Dispatcher.Invoke(() => { - var img = new BitmapImage(new Uri(filePath)); - loaded.Add(email, img); - - if (requesting.ContainsKey(email)) { - foreach (var one in requesting[email]) one.Source = img; - } - }); - } else { - if (!loaded.ContainsKey(email)) loaded.Add(email, null); - } -#else - HttpWebRequest req = WebRequest.CreateHttp(Models.Preference.Instance.General.AvatarServer + md5 + "?d=404"); - req.Timeout = 2000; - req.Method = "GET"; - - HttpWebResponse rsp = req.GetResponse() as HttpWebResponse; - if (rsp.StatusCode == HttpStatusCode.OK) { - using (Stream reader = rsp.GetResponseStream()) - using (FileStream writer = File.OpenWrite(filePath)) { - reader.CopyTo(writer); - } - - a.Dispatcher.Invoke(() => { - var img = new BitmapImage(new Uri(filePath)); - loaded.Add(email, img); - - if (requesting.ContainsKey(email)) { - foreach (var one in requesting[email]) one.Source = img; - } - }); - } else { - if (!loaded.ContainsKey(email)) loaded.Add(email, null); - } -#endif - } catch { - if (!loaded.ContainsKey(email)) loaded.Add(email, null); - } - - requesting.Remove(email); - }; - - if (loader != null && !loader.IsCompleted) { - loader = loader.ContinueWith(t => { job(); }); - } else { - loader = Task.Run(job); - } - } - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Net; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Net.Http; + +namespace SourceGit.Views.Controls { + + /// + /// 头像控件 + /// + public class Avatar : Image { + + /// + /// 显示FallbackLabel时的背景色 + /// + private static readonly Brush[] BACKGROUND_BRUSHES = new Brush[] { + new LinearGradientBrush(Colors.Orange, Color.FromRgb(255, 213, 134), 90), + new LinearGradientBrush(Colors.DodgerBlue, Colors.LightSkyBlue, 90), + new LinearGradientBrush(Colors.LimeGreen, Color.FromRgb(124, 241, 124), 90), + new LinearGradientBrush(Colors.Orchid, Color.FromRgb(248, 161, 245), 90), + new LinearGradientBrush(Colors.Tomato, Color.FromRgb(252, 165, 150), 90), + }; + + /// + /// 头像资源本地缓存路径 + /// + public static readonly string CACHE_PATH = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "SourceGit", + "avatars"); + + /// + /// 邮件属性定义 + /// + public static readonly DependencyProperty EmailProperty = DependencyProperty.Register( + "Email", + typeof(string), + typeof(Avatar), + new PropertyMetadata(null, OnEmailChanged)); + + /// + /// 邮件属性 + /// + public string Email { + get { return (string)GetValue(EmailProperty); } + set { SetValue(EmailProperty, value); } + } + + /// + /// 下载头像失败时显示的Label属性定义 + /// + public static readonly DependencyProperty FallbackLabelProperty = DependencyProperty.Register( + "FallbackLabel", + typeof(string), + typeof(Avatar), + new PropertyMetadata("?", OnFallbackLabelChanged)); + + /// + /// 下载头像失败时显示的Label属性 + /// + public string FallbackLabel { + get { return (string)GetValue(FallbackLabelProperty); } + set { SetValue(FallbackLabelProperty, value); } + } + + private static Dictionary> requesting = new Dictionary>(); + private static Dictionary loaded = new Dictionary(); + private static Task loader = null; + + private int colorIdx = 0; + private FormattedText label = null; + + public Avatar() { + SetValue(RenderOptions.BitmapScalingModeProperty, BitmapScalingMode.HighQuality); + SetValue(RenderOptions.ClearTypeHintProperty, ClearTypeHint.Auto); + Unloaded += (o, e) => Cancel(Email); + } + + /// + /// 取消一个下载任务 + /// + /// + private void Cancel(string email) { + if (!string.IsNullOrEmpty(email) && requesting.ContainsKey(email)) { + if (requesting[email].Count <= 1) { + requesting.Remove(email); + } else { + requesting[email].Remove(this); + } + } + } + + /// + /// 渲染实现 + /// + /// + protected override void OnRender(DrawingContext dc) { + base.OnRender(dc); + + if (Source == null && label != null) { + var corner = Math.Max(2, Width / 16); + var offsetX = (double)0; + if (HorizontalAlignment == HorizontalAlignment.Right) { + offsetX = -Width * 0.5; + } else if (HorizontalAlignment == HorizontalAlignment.Left) { + offsetX = Width * 0.5; + } + + Brush brush = BACKGROUND_BRUSHES[colorIdx]; + dc.DrawRoundedRectangle(brush, null, new Rect(-Width * 0.5 + offsetX, -Height * 0.5, Width, Height), corner, corner); + dc.DrawText(label, new Point(label.Width * -0.5 + offsetX, label.Height * -0.5)); + } + } + + /// + /// 显示文本变化时触发 + /// + /// + /// + private static void OnFallbackLabelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { + Avatar a = d as Avatar; + if (a == null) return; + + var placeholder = a.FallbackLabel.Length > 0 ? a.FallbackLabel.Substring(0, 1) : "?"; + + a.colorIdx = 0; + a.label = new FormattedText( + placeholder, + CultureInfo.CurrentCulture, + FlowDirection.LeftToRight, + new Typeface(new FontFamily(Models.Preference.Instance.General.FontFamilyWindow), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal), + a.Width * 0.65, + Brushes.White, + VisualTreeHelper.GetDpi(a).PixelsPerDip); + + var chars = placeholder.ToCharArray(); + foreach (var ch in chars) a.colorIdx += Math.Abs(ch); + a.colorIdx = a.colorIdx % BACKGROUND_BRUSHES.Length; + } + + /// + /// 邮件变化时触发 + /// + /// + /// + private static void OnEmailChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { + Avatar a = d as Avatar; + if (a == null) return; + + a.Cancel(e.OldValue as string); + a.Source = null; + a.InvalidateVisual(); + + var email = e.NewValue as string; + if (string.IsNullOrEmpty(email)) return; + + if (loaded.ContainsKey(email)) { + a.Source = loaded[email]; + return; + } + + if (requesting.ContainsKey(email)) { + requesting[email].Add(a); + return; + } + + byte[] hash = MD5.Create().ComputeHash(Encoding.Default.GetBytes(email.ToLower().Trim())); + string md5 = ""; + for (int i = 0; i < hash.Length; i++) md5 += hash[i].ToString("x2"); + md5 = md5.ToLower(); + + string filePath = Path.Combine(CACHE_PATH, md5); + if (File.Exists(filePath)) { + var img = new BitmapImage(new Uri(filePath)); + loaded.Add(email, img); + a.Source = img; + return; + } + + requesting.Add(email, new List()); + requesting[email].Add(a); + + Action job = () => { + if (!requesting.ContainsKey(email)) return; + + try { + var req = new HttpClient().GetAsync(Models.Preference.Instance.General.AvatarServer + md5 + "?d=404"); + req.Wait(); + + var rsp = req.Result; + if (rsp != null && rsp.StatusCode == HttpStatusCode.OK) { + var writer = File.OpenWrite(filePath); + rsp.Content.CopyToAsync(writer).Wait(); + writer.Close(); + + a.Dispatcher.Invoke(() => { + var img = new BitmapImage(new Uri(filePath)); + loaded.Add(email, img); + + if (requesting.ContainsKey(email)) { + foreach (var one in requesting[email]) one.Source = img; + } + }); + } else { + if (!loaded.ContainsKey(email)) loaded.Add(email, null); + } + } catch { + if (!loaded.ContainsKey(email)) loaded.Add(email, null); + } + + requesting.Remove(email); + }; + + if (loader != null && !loader.IsCompleted) { + loader = loader.ContinueWith(t => { job(); }); + } else { + loader = Task.Run(job); + } + } + } +} diff --git a/src/Views/Upgrade.xaml.cs b/src/Views/Upgrade.xaml.cs index eefd3a50..6a8e4027 100644 --- a/src/Views/Upgrade.xaml.cs +++ b/src/Views/Upgrade.xaml.cs @@ -1,32 +1,28 @@ -using System.Diagnostics; -using System.Windows; - -namespace SourceGit.Views { - /// - /// 新版本提示窗口 - /// - public partial class Upgrade : Controls.Window { - public Models.Version Version { get; set; } = new Models.Version(); - - public Upgrade(Models.Version ver) { - Version = ver; - InitializeComponent(); - txtRelease.Text = App.Text("UpdateAvailable.Title", ver.Name); - } - - private void Download(object sender, RoutedEventArgs e) { - var url = $"https://github.com/sourcegit-scm/sourcegit/releases/{Version.TagName}"; -#if NET48 - Process.Start(url); -#else - var info = new ProcessStartInfo("cmd", $"/c start {url}"); - info.CreateNoWindow = true; - Process.Start(info); -#endif - } - - private void Quit(object sender, RoutedEventArgs e) { - Close(); - } - } -} +using System.Diagnostics; +using System.Windows; + +namespace SourceGit.Views { + /// + /// 新版本提示窗口 + /// + public partial class Upgrade : Controls.Window { + public Models.Version Version { get; set; } = new Models.Version(); + + public Upgrade(Models.Version ver) { + Version = ver; + InitializeComponent(); + txtRelease.Text = App.Text("UpdateAvailable.Title", ver.Name); + } + + private void Download(object sender, RoutedEventArgs e) { + var url = $"https://github.com/sourcegit-scm/sourcegit/releases/{Version.TagName}"; + var info = new ProcessStartInfo("cmd", $"/c start {url}"); + info.CreateNoWindow = true; + Process.Start(info); + } + + private void Quit(object sender, RoutedEventArgs e) { + Close(); + } + } +} diff --git a/src/Views/Widgets/Histories.xaml.cs b/src/Views/Widgets/Histories.xaml.cs index 585bdb2d..07cb7227 100644 --- a/src/Views/Widgets/Histories.xaml.cs +++ b/src/Views/Widgets/Histories.xaml.cs @@ -72,15 +72,6 @@ namespace SourceGit.Views.Widgets { } else { searching = true; foreach (var c in cachedCommits) { -#if NET48 - if (c.SHA.Contains(filter) - || c.Subject.Contains(filter) - || c.Message.Contains(filter) - || c.Author.Name.Contains(filter) - || c.Committer.Name.Contains(filter)) { - visible.Add(c); - } -#else if (c.SHA.Contains(filter, StringComparison.Ordinal) || c.Subject.Contains(filter, StringComparison.Ordinal) || c.Message.Contains(filter, StringComparison.Ordinal) @@ -88,7 +79,6 @@ namespace SourceGit.Views.Widgets { || c.Committer.Name.Contains(filter, StringComparison.Ordinal)) { visible.Add(c); } -#endif } } diff --git a/src/Views/Widgets/WorkingCopyChanges.xaml.cs b/src/Views/Widgets/WorkingCopyChanges.xaml.cs index 94796161..97c97caf 100644 --- a/src/Views/Widgets/WorkingCopyChanges.xaml.cs +++ b/src/Views/Widgets/WorkingCopyChanges.xaml.cs @@ -215,11 +215,7 @@ namespace SourceGit.Views.Widgets { if (!added) Changes.Add(c); -#if NET48 - int sepIdx = c.Path.IndexOf("/", StringComparison.Ordinal); -#else int sepIdx = c.Path.IndexOf('/', StringComparison.Ordinal); -#endif if (sepIdx < 0) { GetOrAddTreeNode(Nodes, c.Path, c, false); } else {