optimize<*>: remove deprecated apis (older than .NET 6)

This commit is contained in:
leo 2022-05-20 16:00:25 +08:00
parent 890ff29ac7
commit 338f91357e
8 changed files with 677 additions and 749 deletions

View file

@ -1,186 +1,178 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Text.RegularExpressions;
namespace SourceGit.Commands {
/// <summary>
/// 用于取消命令执行的上下文对象
/// </summary>
public class Context {
public bool IsCancelRequested { get; set; } = false;
}
/// <summary>
/// 命令接口
/// </summary>
public class Command {
/// <summary>
/// 读取全部输出时的结果
/// </summary>
public class ReadToEndResult {
public bool IsSuccess { get; set; }
public string Output { get; set; }
public string Error { get; set; }
}
/// <summary>
/// 上下文
/// </summary>
public Context Ctx { get; set; } = null;
/// <summary>
/// 运行路径
/// </summary>
public string Cwd { get; set; } = "";
/// <summary>
/// 参数
/// </summary>
public string Args { get; set; } = "";
/// <summary>
/// 是否忽略错误
/// </summary>
public bool DontRaiseError { get; set; } = false;
/// <summary>
/// 使用标准错误输出
/// </summary>
public bool TraitErrorAsOutput { get; set; } = false;
/// <summary>
/// 用于设置该进程独有的环境变量
/// </summary>
public Dictionary<string, string> Envs { get; set; } = new Dictionary<string, string>();
/// <summary>
/// 运行
/// </summary>
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<string>();
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;
}
}
/// <summary>
/// 直接读取全部标准输出
/// </summary>
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;
}
/// <summary>
/// 调用Exec时的读取函数
/// </summary>
/// <param name="line"></param>
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 {
/// <summary>
/// 用于取消命令执行的上下文对象
/// </summary>
public class Context {
public bool IsCancelRequested { get; set; } = false;
}
/// <summary>
/// 命令接口
/// </summary>
public class Command {
/// <summary>
/// 读取全部输出时的结果
/// </summary>
public class ReadToEndResult {
public bool IsSuccess { get; set; }
public string Output { get; set; }
public string Error { get; set; }
}
/// <summary>
/// 上下文
/// </summary>
public Context Ctx { get; set; } = null;
/// <summary>
/// 运行路径
/// </summary>
public string Cwd { get; set; } = "";
/// <summary>
/// 参数
/// </summary>
public string Args { get; set; } = "";
/// <summary>
/// 是否忽略错误
/// </summary>
public bool DontRaiseError { get; set; } = false;
/// <summary>
/// 使用标准错误输出
/// </summary>
public bool TraitErrorAsOutput { get; set; } = false;
/// <summary>
/// 用于设置该进程独有的环境变量
/// </summary>
public Dictionary<string, string> Envs { get; set; } = new Dictionary<string, string>();
/// <summary>
/// 运行
/// </summary>
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<string>();
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;
}
}
/// <summary>
/// 直接读取全部标准输出
/// </summary>
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;
}
/// <summary>
/// 调用Exec时的读取函数
/// </summary>
/// <param name="line"></param>
public virtual void OnReadline(string line) { }
}
}

View file

@ -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 {
/// <summary>
/// Github开放API中Release信息格式
/// </summary>
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<Version> 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<Version>(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 {
/// <summary>
/// Github开放API中Release信息格式
/// </summary>
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<Version> 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<Version>(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 {
}
});
}
}
}
}

View file

@ -1,124 +1,124 @@
<controls:Window
x:Class="SourceGit.Views.About"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
mc:Ignorable="d"
WindowStartupLocation="CenterOwner"
Title="{DynamicResource Text.About}"
Width="420" SizeToContent="Height"
ResizeMode="NoResize">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="28"/>
<RowDefinition Height="1"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Title bar -->
<Grid Grid.Row="0" Background="{DynamicResource Brush.TitleBar}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Icon -->
<Path Grid.Column="0" Margin="6,0" Width="16" Height="16" Data="{StaticResource Icon.Help}"/>
<!-- Title -->
<TextBlock Grid.Column="1" Text="{DynamicResource Text.About}"/>
<!-- Close -->
<controls:IconButton
Grid.Column="3"
Click="Quit"
Width="28"
Padding="8"
Icon="{StaticResource Icon.Close}"
HoverBackground="Red"
WindowChrome.IsHitTestVisibleInChrome="True"/>
</Grid>
<Rectangle
Grid.Row="1"
Height="1"
HorizontalAlignment="Stretch"
Fill="{DynamicResource Brush.Border0}"/>
<!-- Content -->
<StackPanel Grid.Row="2" Orientation="Vertical">
<!-- LOGO -->
<Path
Margin="0,16,0,0"
Width="64" Height="64"
Data="{StaticResource Icon.Git}"
Fill="{DynamicResource Brush.Logo}"/>
<!-- Title -->
<TextBlock
Margin="0,24,0,8"
Text="{DynamicResource Text.About.Title}"
HorizontalAlignment="Center"
FontSize="18"/>
<!-- Version -->
<TextBlock
x:Name="version"
Margin="0,0,0,8"
HorizontalAlignment="Center"
FontSize="11"
Text="VERSION: v1.0 .NET: v5.0"/>
<DataGrid
x:Name="hotkeys"
Grid.Row="2"
Margin="16,8"
Background="{DynamicResource Brush.Contents}"
GridLinesVisibility="All"
HorizontalGridLinesBrush="{DynamicResource Brush.Border0}"
VerticalGridLinesBrush="{DynamicResource Brush.Border0}"
HeadersVisibility="Column"
RowHeight="24"
ColumnHeaderHeight="24"
IsHitTestVisible="False"
BorderThickness="1,1,0,0"
BorderBrush="{DynamicResource Brush.Border0}">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Border BorderThickness="0,0,1,1" BorderBrush="{DynamicResource Brush.Border0}" Background="{DynamicResource Brush.Window}">
<TextBlock
Text="{TemplateBinding Content}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="DemiBold"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTextColumn Width="100" Header="{DynamicResource Text.Hotkeys.Col.Key}" Binding="{Binding .Key}" ElementStyle="{StaticResource Style.TextBlock.LineContent}"/>
<DataGridTextColumn Width="*" Header="{DynamicResource Text.Hotkeys.Col.Desc}" Binding="{Binding .Desc}" ElementStyle="{StaticResource Style.TextBlock.LineContent}"/>
</DataGrid.Columns>
</DataGrid>
<!-- Official site -->
<TextBlock HorizontalAlignment="Center" Margin="0,4,0,16">
<TextBlock Text="© 2021" Margin="0" Padding="0"/>
<Hyperlink NavigateUri="https://github.com/sourcegit-scm/sourcegit.git" RequestNavigate="OnRequestNavigate" ToolTip="https://github.com/sourcegit-scm/sourcegit.git">
<Run Text="SourceGit."/>
</Hyperlink>
<TextBlock Text="All rights reserved." Margin="0" Padding="0"/>
</TextBlock>
</StackPanel>
</Grid>
</controls:Window>
<controls:Window
x:Class="SourceGit.Views.About"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
mc:Ignorable="d"
WindowStartupLocation="CenterOwner"
Title="{DynamicResource Text.About}"
Width="420" SizeToContent="Height"
ResizeMode="NoResize">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="28"/>
<RowDefinition Height="1"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Title bar -->
<Grid Grid.Row="0" Background="{DynamicResource Brush.TitleBar}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Icon -->
<Path Grid.Column="0" Margin="6,0" Width="16" Height="16" Data="{StaticResource Icon.Help}"/>
<!-- Title -->
<TextBlock Grid.Column="1" Text="{DynamicResource Text.About}"/>
<!-- Close -->
<controls:IconButton
Grid.Column="3"
Click="Quit"
Width="28"
Padding="8"
Icon="{StaticResource Icon.Close}"
HoverBackground="Red"
WindowChrome.IsHitTestVisibleInChrome="True"/>
</Grid>
<Rectangle
Grid.Row="1"
Height="1"
HorizontalAlignment="Stretch"
Fill="{DynamicResource Brush.Border0}"/>
<!-- Content -->
<StackPanel Grid.Row="2" Orientation="Vertical">
<!-- LOGO -->
<Path
Margin="0,16,0,0"
Width="64" Height="64"
Data="{StaticResource Icon.Git}"
Fill="{DynamicResource Brush.Logo}"/>
<!-- Title -->
<TextBlock
Margin="0,24,0,8"
Text="{DynamicResource Text.About.Title}"
HorizontalAlignment="Center"
FontSize="18"/>
<!-- Version -->
<TextBlock
x:Name="version"
Margin="0,0,0,8"
HorizontalAlignment="Center"
FontSize="11"
Text="VERSION: v1.0"/>
<DataGrid
x:Name="hotkeys"
Grid.Row="2"
Margin="16,8"
Background="{DynamicResource Brush.Contents}"
GridLinesVisibility="All"
HorizontalGridLinesBrush="{DynamicResource Brush.Border0}"
VerticalGridLinesBrush="{DynamicResource Brush.Border0}"
HeadersVisibility="Column"
RowHeight="24"
ColumnHeaderHeight="24"
IsHitTestVisible="False"
BorderThickness="1,1,0,0"
BorderBrush="{DynamicResource Brush.Border0}">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Border BorderThickness="0,0,1,1" BorderBrush="{DynamicResource Brush.Border0}" Background="{DynamicResource Brush.Window}">
<TextBlock
Text="{TemplateBinding Content}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="DemiBold"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTextColumn Width="100" Header="{DynamicResource Text.Hotkeys.Col.Key}" Binding="{Binding .Key}" ElementStyle="{StaticResource Style.TextBlock.LineContent}"/>
<DataGridTextColumn Width="*" Header="{DynamicResource Text.Hotkeys.Col.Desc}" Binding="{Binding .Desc}" ElementStyle="{StaticResource Style.TextBlock.LineContent}"/>
</DataGrid.Columns>
</DataGrid>
<!-- Official site -->
<TextBlock HorizontalAlignment="Center" Margin="0,4,0,16">
<TextBlock Text="© 2021" Margin="0" Padding="0"/>
<Hyperlink NavigateUri="https://github.com/sourcegit-scm/sourcegit.git" RequestNavigate="OnRequestNavigate" ToolTip="https://github.com/sourcegit-scm/sourcegit.git">
<Run Text="SourceGit."/>
</Hyperlink>
<TextBlock Text="All rights reserved." Margin="0" Padding="0"/>
</TextBlock>
</StackPanel>
</Grid>
</controls:Window>

View file

@ -1,55 +1,48 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Windows;
namespace SourceGit.Views {
/// <summary>
/// 关于对话框
/// </summary>
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<Keymap>() {
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 {
/// <summary>
/// 关于对话框
/// </summary>
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<Keymap>() {
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();
}
}
}

View file

@ -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 {
/// <summary>
/// 头像控件
/// </summary>
public class Avatar : Image {
/// <summary>
/// 显示FallbackLabel时的背景色
/// </summary>
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),
};
/// <summary>
/// 头像资源本地缓存路径
/// </summary>
public static readonly string CACHE_PATH = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"SourceGit",
"avatars");
/// <summary>
/// 邮件属性定义
/// </summary>
public static readonly DependencyProperty EmailProperty = DependencyProperty.Register(
"Email",
typeof(string),
typeof(Avatar),
new PropertyMetadata(null, OnEmailChanged));
/// <summary>
/// 邮件属性
/// </summary>
public string Email {
get { return (string)GetValue(EmailProperty); }
set { SetValue(EmailProperty, value); }
}
/// <summary>
/// 下载头像失败时显示的Label属性定义
/// </summary>
public static readonly DependencyProperty FallbackLabelProperty = DependencyProperty.Register(
"FallbackLabel",
typeof(string),
typeof(Avatar),
new PropertyMetadata("?", OnFallbackLabelChanged));
/// <summary>
/// 下载头像失败时显示的Label属性
/// </summary>
public string FallbackLabel {
get { return (string)GetValue(FallbackLabelProperty); }
set { SetValue(FallbackLabelProperty, value); }
}
private static Dictionary<string, List<Avatar>> requesting = new Dictionary<string, List<Avatar>>();
private static Dictionary<string, BitmapImage> loaded = new Dictionary<string, BitmapImage>();
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);
}
/// <summary>
/// 取消一个下载任务
/// </summary>
/// <param name="email"></param>
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);
}
}
}
/// <summary>
/// 渲染实现
/// </summary>
/// <param name="dc"></param>
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));
}
}
/// <summary>
/// 显示文本变化时触发
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
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;
}
/// <summary>
/// 邮件变化时触发
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
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<Avatar>());
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 {
/// <summary>
/// 头像控件
/// </summary>
public class Avatar : Image {
/// <summary>
/// 显示FallbackLabel时的背景色
/// </summary>
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),
};
/// <summary>
/// 头像资源本地缓存路径
/// </summary>
public static readonly string CACHE_PATH = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"SourceGit",
"avatars");
/// <summary>
/// 邮件属性定义
/// </summary>
public static readonly DependencyProperty EmailProperty = DependencyProperty.Register(
"Email",
typeof(string),
typeof(Avatar),
new PropertyMetadata(null, OnEmailChanged));
/// <summary>
/// 邮件属性
/// </summary>
public string Email {
get { return (string)GetValue(EmailProperty); }
set { SetValue(EmailProperty, value); }
}
/// <summary>
/// 下载头像失败时显示的Label属性定义
/// </summary>
public static readonly DependencyProperty FallbackLabelProperty = DependencyProperty.Register(
"FallbackLabel",
typeof(string),
typeof(Avatar),
new PropertyMetadata("?", OnFallbackLabelChanged));
/// <summary>
/// 下载头像失败时显示的Label属性
/// </summary>
public string FallbackLabel {
get { return (string)GetValue(FallbackLabelProperty); }
set { SetValue(FallbackLabelProperty, value); }
}
private static Dictionary<string, List<Avatar>> requesting = new Dictionary<string, List<Avatar>>();
private static Dictionary<string, BitmapImage> loaded = new Dictionary<string, BitmapImage>();
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);
}
/// <summary>
/// 取消一个下载任务
/// </summary>
/// <param name="email"></param>
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);
}
}
}
/// <summary>
/// 渲染实现
/// </summary>
/// <param name="dc"></param>
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));
}
}
/// <summary>
/// 显示文本变化时触发
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
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;
}
/// <summary>
/// 邮件变化时触发
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
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<Avatar>());
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);
}
}
}
}

View file

@ -1,32 +1,28 @@
using System.Diagnostics;
using System.Windows;
namespace SourceGit.Views {
/// <summary>
/// 新版本提示窗口
/// </summary>
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 {
/// <summary>
/// 新版本提示窗口
/// </summary>
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();
}
}
}

View file

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

View file

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