diff --git a/src/Common.ClientLib/Application/Essentials/Browser2.utils.cs b/src/Common.ClientLib/Application/Essentials/Browser2.utils.cs index c50b28ef1..13366d8ff 100644 --- a/src/Common.ClientLib/Application/Essentials/Browser2.utils.cs +++ b/src/Common.ClientLib/Application/Essentials/Browser2.utils.cs @@ -30,13 +30,15 @@ namespace System.Application Unknown = -2, } + const string TAG = nameof(Browser2); + static void HandlerException(Exception e) { if (OnError == null) { try { - Toast.Show(e, nameof(Browser2)); + e.LogAndShowT(TAG); } catch { diff --git a/src/Common.ClientLib/Application/Essentials/FilePicker2.cs b/src/Common.ClientLib/Application/Essentials/FilePicker2.cs index c225e6229..76eb75844 100644 --- a/src/Common.ClientLib/Application/Essentials/FilePicker2.cs +++ b/src/Common.ClientLib/Application/Essentials/FilePicker2.cs @@ -57,6 +57,8 @@ namespace System.Application } } + const string TAG = nameof(FilePicker2); + /// /// 启动文件选择器以选择单个文件。 /// @@ -86,7 +88,7 @@ namespace System.Application } catch (Exception e) { - Toast.Show(e, nameof(FilePicker2)); + e.LogAndShowT(TAG); } } } @@ -129,7 +131,7 @@ namespace System.Application } catch (Exception e) { - Toast.Show(e, nameof(FilePicker2)); + e.LogAndShowT(TAG); } } } @@ -202,7 +204,7 @@ namespace System.Application } catch (Exception e) { - Toast.Show(e, nameof(FilePicker2)); + e.LogAndShowT(TAG); } } } @@ -245,7 +247,7 @@ namespace System.Application } catch (Exception e) { - Toast.Show(e, nameof(FilePicker2)); + e.LogAndShowT(TAG); } } } diff --git a/src/Common.ClientLib/Toast.cs b/src/Common.ClientLib/Toast.cs index 8f231e234..e81715bbf 100644 --- a/src/Common.ClientLib/Toast.cs +++ b/src/Common.ClientLib/Toast.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Logging; using System.Application.Services; +using System.Runtime.CompilerServices; using System.Text; namespace System @@ -21,41 +22,42 @@ namespace System toast.Show(text, duration); } + static void ShowLong(string text) => Show(text, ToastLength.Long); + +#if DEBUG + [Obsolete("use e.LogAndShowT(..", true)] + public static void Show(Exception e, + string? tag = null, LogLevel level = LogLevel.Error, + string? msg = null, params object?[] args) => e.LogAndShowT(tag, level, "", msg, args); +#endif + /// - /// 通过 显示 Toast 并纪录日志,传入 可不写日志 + /// 通过 纪录日志并在 Toast 上显示,传入 可不写日志 /// /// + /// /// /// /// /// - public static void Show(Exception e, string? tag = null, LogLevel level = LogLevel.Error, string? msg = null, params string[] args) - { - if (string.IsNullOrWhiteSpace(tag)) tag = nameof(Toast); - var has_msg = !string.IsNullOrWhiteSpace(msg); - var has_args = args.Any_Nullable(); - if (level < LogLevel.None) - { - var logger = Log.CreateLogger(tag); - if (has_msg) - { - if (has_args) - { - logger.Log(level, e, msg!, args); - } - else - { - logger.Log(level, e, msg!); - } - } - else - { - logger.Log(level, e, null); - } - } + /// + public static void LogAndShowT(this Exception? e, + string? tag = null, LogLevel level = LogLevel.Error, + [CallerMemberName] string memberName = "", + string? msg = null, params object?[] args) + => ExceptionExtensions.LogAndShow(e, + ShowLong, + string.IsNullOrWhiteSpace(tag) ? nameof(Toast) : tag, level, + memberName, + msg, args); - var text = ExceptionExtensions.GetAllMessageCore(e, has_msg, has_args, msg, args); - Show(text, ToastLength.Long); - } + /// + public static void LogAndShowT(this Exception? e, + ILogger logger, LogLevel level = LogLevel.Error, + [CallerMemberName] string memberName = "", + string? msg = null, params object?[] args) => ExceptionExtensions.LogAndShow(e, + ShowLong, + logger, level, + memberName, msg, args); } } \ No newline at end of file diff --git a/src/Common.CoreLib/Extensions/ExceptionExtensions.cs b/src/Common.CoreLib/Extensions/ExceptionExtensions.cs index f60dd32b2..af25bced0 100644 --- a/src/Common.CoreLib/Extensions/ExceptionExtensions.cs +++ b/src/Common.CoreLib/Extensions/ExceptionExtensions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using System.Linq; using System.Text; +using Microsoft.Extensions.Logging; // ReSharper disable once CheckNamespace namespace System @@ -10,11 +11,20 @@ namespace System { static bool JavaInterop => OperatingSystem2.IsAndroid; + /// + /// 获取异常是否为已知类型,通常不为 的异常不需要纪录日志 + /// + /// + /// public static ExceptionKnownType GetKnownType(this Exception e) { - if (e is OperationCanceledException) + if (e is TaskCanceledException) { - return ExceptionKnownType.Canceled; + return ExceptionKnownType.TaskCanceled; + } + else if (e is OperationCanceledException) + { + return ExceptionKnownType.OperationCanceled; } if (JavaInterop) { @@ -42,34 +52,145 @@ namespace System /// /// 是否为取消操作异常 /// - /// + /// /// - [Obsolete("use GetKnownType")] - public static bool IsCanceledException(this Exception e) => GetKnownType(e) == ExceptionKnownType.Canceled; + public static bool IsCanceledException(this ExceptionKnownType value) + => value == ExceptionKnownType.Canceled || + value == ExceptionKnownType.OperationCanceled || + value == ExceptionKnownType.TaskCanceled; + + ///// + ///// 是否为取消操作异常 + ///// + ///// + ///// + //[Obsolete("use GetKnownType", true)] + //public static bool IsCanceledException(this Exception e) => GetKnownType(e).IsCanceledException(); + + /// + /// 通过 纪录日志并在 UI 上显示,传入 可不写日志 + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static void LogAndShow(Exception? e, + Action? show, + string tag, LogLevel level = LogLevel.Error, + string memberName = "", + string? msg = null, params object?[] args) => LogAndShowCore(e, show, tag, logger: null, level, memberName, msg, args); + + /// + public static void LogAndShow(Exception? e, + Action? show, + ILogger? logger, LogLevel level = LogLevel.Error, + string memberName = "", + string? msg = null, params object?[] args) => LogAndShowCore(e, show, tag: null, logger, level, memberName, msg, args); + + static void LogAndShowCore(Exception? e, + Action? show, + string? tag = null, ILogger? logger = null, LogLevel level = LogLevel.Error, + string memberName = "", + string? msg = null, params object?[] args) + { + bool has_msg = !string.IsNullOrWhiteSpace(msg); + if (!has_msg) + { + if (!string.IsNullOrWhiteSpace(memberName)) + { + msg = $"{memberName} Error"; + has_msg = true; + } + } + var has_args = args.Any_Nullable(); + var has_e = e != null; + if (has_e) + { + var knownType = e!.GetKnownType(); + if (knownType != ExceptionKnownType.Unknown) level = LogLevel.None; + } + var has_log_level = level < LogLevel.None; + if (has_log_level) + { + if (logger == null && tag != null) + { + logger = Log.CreateLogger(tag); + } + if (logger != null) + { + if (has_args) + { + logger.Log(level, e, msg!, args); + } + else + { + logger.Log(level, e, msg!); + } + } + } + show?.Invoke(GetShowMsg()); + string GetShowMsg() + { + if (has_e) return GetAllMessageCore(e!, has_msg, has_args, msg, args); + if (has_msg) + { + if (has_args) + { + return msg!.Format(args); + } + else + { + return msg!; + } + } + return ""; + } + } /// /// 获取异常中所有错误信息 /// /// 当前捕获的异常 + /// + /// /// 可选的消息,将写在第一行 /// 可选的消息参数 /// - public static string GetAllMessage(this Exception e, string? msg = null, params string[] args) + public static string GetAllMessage(this Exception e, string? msg = null, params object?[] args) { var has_msg = !string.IsNullOrWhiteSpace(msg); var has_args = args.Any_Nullable(); return GetAllMessageCore(e, has_msg, has_args, msg, args); } - /// - public static string GetAllMessageCore(Exception e, bool has_msg, bool has_args, string? msg = null, params string[] args) + /// + static string GetAllMessageCore(Exception e, + bool has_msg, bool has_args, + string? msg = null, params object?[] args) { StringBuilder sb = new(); + if (has_msg) { if (has_args) { - sb.AppendFormat(msg, args); + try + { + sb.AppendFormat(msg, args); + } + catch + { + sb.Append(msg); + foreach (var item in args) + { + sb.Append(' '); + sb.Append(item); + } + } sb.AppendLine(); } else @@ -77,27 +198,17 @@ namespace System sb.AppendLine(msg!); } } - var e_str = e.ToString(); - sb.AppendLine(e_str); - var stackTrace = e.StackTrace; - if (!string.IsNullOrWhiteSpace(stackTrace) && - !e_str.Contains(stackTrace)) - { - sb.AppendLine(stackTrace); - } var exception = e; ushort i = 0; while (exception != null && i++ < byte.MaxValue) { var exception_message = exception.Message; - if (!string.IsNullOrWhiteSpace(exception_message) && - !e_str.Contains(exception_message)) + if (!string.IsNullOrWhiteSpace(exception_message)) { sb.AppendLine(exception_message); } exception = exception.InnerException; - } var text = sb.ToString().Trim(); @@ -105,6 +216,9 @@ namespace System } } + /// + /// 异常已知类型,通常不为 的异常不需要纪录日志 + /// public enum ExceptionKnownType : byte { Unknown, @@ -118,5 +232,11 @@ namespace System /// 证书时间验证错误异常,通常为本地时间不正确导致 SSL 握手失败或服务端证书失效 /// CertificateNotYetValid, + + /// + OperationCanceled, + + /// + TaskCanceled, } } \ No newline at end of file diff --git a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/CommunityProxyPage.axaml b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/CommunityProxyPage.axaml index 83990b542..7b564b87a 100644 --- a/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/CommunityProxyPage.axaml +++ b/src/ST.Client.Desktop.Avalonia/Application/UI/Views/Pages/CommunityProxyPage.axaml @@ -131,6 +131,7 @@ + @@ -138,6 +139,21 @@ + + + + + + + + + + + diff --git a/src/ST.Client.Desktop.Linux/Properties/SR.Designer.cs b/src/ST.Client.Desktop.Linux/Properties/SR.Designer.cs new file mode 100644 index 000000000..bd851ee26 --- /dev/null +++ b/src/ST.Client.Desktop.Linux/Properties/SR.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace System.Application.Properties { + using System; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class SR { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal SR() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("System.Application.Properties.SR", typeof(SR).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 重写当前线程的 CurrentUICulture 属性,对 + /// 使用此强类型资源类的所有资源查找执行重写。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// 查找类似 127.0.0.1 localhost + ///127.0.1.1 ${USERNAME} + /// + ///# The following lines are desirable for IPv6 capable hosts + ///::1 ip6-localhost ip6-loopback + ///fe00::0 ip6-localnet + ///ff00::0 ip6-mcastprefix + ///ff02::1 ip6-allnodes + ///ff02::2 ip6-allrouters + /// 的本地化字符串。 + /// + internal static string hosts { + get { + return ResourceManager.GetString("hosts", resourceCulture); + } + } + } +} diff --git a/src/ST.Client.Desktop.Linux/Properties/SR.resx b/src/ST.Client.Desktop.Linux/Properties/SR.resx new file mode 100644 index 000000000..dc27701e1 --- /dev/null +++ b/src/ST.Client.Desktop.Linux/Properties/SR.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\hosts.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;gb2312 + + \ No newline at end of file diff --git a/src/ST.Client.Desktop.Linux/Resources/hosts.txt b/src/ST.Client.Desktop.Linux/Resources/hosts.txt new file mode 100644 index 000000000..9b6cbe4b9 --- /dev/null +++ b/src/ST.Client.Desktop.Linux/Resources/hosts.txt @@ -0,0 +1,9 @@ +127.0.0.1 localhost +127.0.1.1 ${USERNAME} + +# The following lines are desirable for IPv6 capable hosts +::1 ip6-localhost ip6-loopback +fe00::0 ip6-localnet +ff00::0 ip6-mcastprefix +ff02::1 ip6-allnodes +ff02::2 ip6-allrouters diff --git a/src/ST.Client.Desktop.Linux/ST.Client.Linux.csproj b/src/ST.Client.Desktop.Linux/ST.Client.Linux.csproj index 04e8489b9..8d255291a 100644 --- a/src/ST.Client.Desktop.Linux/ST.Client.Linux.csproj +++ b/src/ST.Client.Desktop.Linux/ST.Client.Linux.csproj @@ -51,4 +51,19 @@ + + + + True + True + SR.resx + + + + + + ResXFileCodeGenerator + SR.Designer.cs + + \ No newline at end of file diff --git a/src/ST.Client.Desktop.Linux/Services/Implementation/LinuxPlatformServiceImpl.cs b/src/ST.Client.Desktop.Linux/Services/Implementation/LinuxPlatformServiceImpl.cs index 5e525f66d..d8da0cf85 100644 --- a/src/ST.Client.Desktop.Linux/Services/Implementation/LinuxPlatformServiceImpl.cs +++ b/src/ST.Client.Desktop.Linux/Services/Implementation/LinuxPlatformServiceImpl.cs @@ -1,4 +1,5 @@ using System.Application.Models; +using System.Application.Properties; using System.Application.UI.Resx; using System.Application.UI.ViewModels; using System.Diagnostics; @@ -208,8 +209,7 @@ namespace System.Application.Services.Implementation } catch (Exception e) { - Log.Error("Shell Error", e, "Run Shell Error"); - Toast.Show(e); + e.LogAndShowT(TAG); } return string.Empty; } @@ -225,7 +225,7 @@ namespace System.Application.Services.Implementation }; var pwd = await TextBoxWindowViewModel.ShowDialogAsync(vm); if (!string.IsNullOrWhiteSpace(pwd)) - { + { if (!string.IsNullOrWhiteSpace(RunShell($"echo \"{pwd}\" | sudo -S sh -c \"sudo -n true\""))) { SystemUserPassword = pwd; @@ -246,5 +246,7 @@ namespace System.Application.Services.Implementation } } } + + public string DefaultHostsContent => SR.hosts.Replace("${USERNAME}", Environment.UserName); } } \ No newline at end of file diff --git a/src/ST.Client.Desktop.Mac/Internals/UnixHelper.RunShellAsync.cs b/src/ST.Client.Desktop.Mac/Internals/UnixHelper.RunShellAsync.cs index 2235d2bf2..4ecf28728 100644 --- a/src/ST.Client.Desktop.Mac/Internals/UnixHelper.RunShellAsync.cs +++ b/src/ST.Client.Desktop.Mac/Internals/UnixHelper.RunShellAsync.cs @@ -9,6 +9,8 @@ using System.Threading.Tasks; static partial class UnixHelper { + const string TAG = nameof(UnixHelper); + static readonly Lazy _bin_bash = new(() => string.Format("{0}bin{0}bash", Path.DirectorySeparatorChar)); /// /// /bin/bash @@ -98,7 +100,7 @@ static partial class UnixHelper try { using var p = new Process(); - p.StartInfo.FileName = UnixHelper.BinBash; + p.StartInfo.FileName = BinBash; p.StartInfo.Arguments = ""; p.StartInfo.RedirectStandardOutput = true; p.StartInfo.RedirectStandardInput = true; @@ -115,8 +117,7 @@ static partial class UnixHelper } catch (Exception e) { - Log.Error("Shell Error", e, "Run Shell Error"); - Toast.Show(e); + e.LogAndShowT(TAG); } return string.Empty; } diff --git a/src/ST.Client.Desktop.Mac/Services/Implementation/MacPlatformServiceImpl.cs b/src/ST.Client.Desktop.Mac/Services/Implementation/MacPlatformServiceImpl.cs index ef08935ad..2bd745012 100644 --- a/src/ST.Client.Desktop.Mac/Services/Implementation/MacPlatformServiceImpl.cs +++ b/src/ST.Client.Desktop.Mac/Services/Implementation/MacPlatformServiceImpl.cs @@ -292,8 +292,7 @@ namespace System.Application.Services.Implementation } catch (Exception e) { - Log.Error("OSAScript Error", e, "Run OSAScript Error"); - Toast.Show(e); + e.LogAndShowT(TAG); } return string.Empty; } diff --git a/src/ST.Client.Desktop.Windows/Properties/SR.Designer.cs b/src/ST.Client.Desktop.Windows/Properties/SR.Designer.cs index ea0fc2982..d869de8ca 100644 --- a/src/ST.Client.Desktop.Windows/Properties/SR.Designer.cs +++ b/src/ST.Client.Desktop.Windows/Properties/SR.Designer.cs @@ -60,6 +60,26 @@ namespace System.Application.Properties { } } + /// + /// 查找类似 # Copyright (c) 1993-2009 Microsoft Corp. + ///# + ///# This is a sample HOSTS file used by Microsoft TCP/IP for Windows. + ///# + ///# This file contains the mappings of IP addresses to host names. Each + ///# entry should be kept on an individual line. The IP address should + ///# be placed in the first column followed by the corresponding host name. + ///# The IP address and the host name should be separated by at least one + ///# space. + ///# + ///# Additionally, comments (such as these) may be inserted on individual + ///# lines or following th [字符串的其余部分被截断]"; 的本地化字符串。 + /// + internal static string hosts { + get { + return ResourceManager.GetString("hosts", resourceCulture); + } + } + /// /// 查找类似 @echo off ///color a diff --git a/src/ST.Client.Desktop.Windows/Properties/SR.resx b/src/ST.Client.Desktop.Windows/Properties/SR.resx index 159a485f9..aee9373e9 100644 --- a/src/ST.Client.Desktop.Windows/Properties/SR.resx +++ b/src/ST.Client.Desktop.Windows/Properties/SR.resx @@ -112,11 +112,15 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\hosts.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;gb2312 + @echo off color a diff --git a/src/ST.Client.Desktop.Windows/Resources/hosts.txt b/src/ST.Client.Desktop.Windows/Resources/hosts.txt new file mode 100644 index 000000000..63847a2c9 --- /dev/null +++ b/src/ST.Client.Desktop.Windows/Resources/hosts.txt @@ -0,0 +1,21 @@ +# Copyright (c) 1993-2009 Microsoft Corp. +# +# This is a sample HOSTS file used by Microsoft TCP/IP for Windows. +# +# This file contains the mappings of IP addresses to host names. Each +# entry should be kept on an individual line. The IP address should +# be placed in the first column followed by the corresponding host name. +# The IP address and the host name should be separated by at least one +# space. +# +# Additionally, comments (such as these) may be inserted on individual +# lines or following the machine name denoted by a '#' symbol. +# +# For example: +# +# 102.54.94.97 rhino.acme.com # source server +# 38.25.63.10 x.acme.com # x client host + +# localhost name resolution is handled within DNS itself. +# 127.0.0.1 localhost +# ::1 localhost diff --git a/src/ST.Client.Desktop.Windows/Services/Implementation/NativeWindowApiServiceImpl.cs b/src/ST.Client.Desktop.Windows/Services/Implementation/NativeWindowApiServiceImpl.cs index 6a0f4a2b1..768fce19c 100644 --- a/src/ST.Client.Desktop.Windows/Services/Implementation/NativeWindowApiServiceImpl.cs +++ b/src/ST.Client.Desktop.Windows/Services/Implementation/NativeWindowApiServiceImpl.cs @@ -11,6 +11,8 @@ namespace System.Application.Services.Implementation [SupportedOSPlatform("Windows7.0")] internal sealed class NativeWindowApiServiceImpl : INativeWindowApiService { + const string TAG = "NativeWindowApiS"; + /// /// 拖拽指针获取目标窗口 /// @@ -62,7 +64,7 @@ namespace System.Application.Services.Implementation } catch (Exception e) { - Toast.Show(e, nameof(NativeWindowApiServiceImpl)); + e.LogAndShowT(TAG); } } MouseHook.SetSystemCursor(MouseHook.LoadCursor(IntPtr.Zero, MouseHook.OCR_CROSS), MouseHook.OCR_NORMAL); diff --git a/src/ST.Client.Desktop.Windows/Services/Implementation/WindowsPlatformServiceImpl.cs b/src/ST.Client.Desktop.Windows/Services/Implementation/WindowsPlatformServiceImpl.cs index e5e696b16..99cc199bb 100644 --- a/src/ST.Client.Desktop.Windows/Services/Implementation/WindowsPlatformServiceImpl.cs +++ b/src/ST.Client.Desktop.Windows/Services/Implementation/WindowsPlatformServiceImpl.cs @@ -18,6 +18,7 @@ using System.Runtime.Versioning; using System.Runtime.InteropServices; using System.Security.Principal; using Windows.ApplicationModel; +using System.Application.Properties; namespace System.Application.Services.Implementation { @@ -54,6 +55,8 @@ namespace System.Application.Services.Implementation public string HostsFilePath => mHostsFilePath.Value; + public string DefaultHostsContent => SR.hosts; + static string GetFolderPath(Environment.SpecialFolder folder) { switch (folder) diff --git a/src/ST.Client/MessageBox.cs b/src/ST.Client/MessageBox.cs index a4c211cb8..52390b1bb 100644 --- a/src/ST.Client/MessageBox.cs +++ b/src/ST.Client/MessageBox.cs @@ -88,6 +88,8 @@ namespace System.Application /// 捐助提示 /// Donate = 2, + + ResetHostsFile, } /// @@ -155,12 +157,12 @@ namespace System.Application await ShowAsync(messageBoxText, caption, button, icon); } - /// - public static Task ShowAsync(Exception exception, string caption = default_caption, Button button = default_button, Image icon = default) - { - var messageBoxText = exception.GetAllMessage(); - return ShowAsync(messageBoxText, caption, button, icon); - } + ///// + //public static Task ShowAsync(Exception exception, string caption = default_caption, Button button = default_button, Image icon = default) + //{ + // var messageBoxText = exception.GetAllMessage(); + // return ShowAsync(messageBoxText, caption, button, icon); + //} /// public static void Show(Exception exception, string caption = default_caption, Button button = default_button, Image icon = default) diff --git a/src/ST.Client/Services/IHostsFileService.cs b/src/ST.Client/Services/IHostsFileService.cs index f9ac5474c..dd0e77f41 100644 --- a/src/ST.Client/Services/IHostsFileService.cs +++ b/src/ST.Client/Services/IHostsFileService.cs @@ -16,6 +16,16 @@ namespace System.Application.Services /// void OpenFile(); + /// + /// 打开 hosts 所在文件夹 + /// + void OpenFileDir(); + + /// + /// 重置 hosts 文件 + /// + void ResetFile(); + /// /// 读取 hosts 文件 /// diff --git a/src/ST.Client/Services/IPlatformService.cs b/src/ST.Client/Services/IPlatformService.cs index 95bc92625..88d869c74 100644 --- a/src/ST.Client/Services/IPlatformService.cs +++ b/src/ST.Client/Services/IPlatformService.cs @@ -136,6 +136,11 @@ namespace System.Application.Services /// string HostsFilePath => string.Format("{0}etc{0}hosts", Path.DirectorySeparatorChar); + /// + /// 默认 hosts 文件内容 + /// + string DefaultHostsContent => string.Empty; + /// /// 使用资源管理器打开某个路径 /// diff --git a/src/ST.Client/Services/Implementation/ArchiSteamFarmServiceImpl.cs b/src/ST.Client/Services/Implementation/ArchiSteamFarmServiceImpl.cs index fed024e3f..961d5bcd4 100644 --- a/src/ST.Client/Services/Implementation/ArchiSteamFarmServiceImpl.cs +++ b/src/ST.Client/Services/Implementation/ArchiSteamFarmServiceImpl.cs @@ -20,6 +20,8 @@ namespace System.Application.Services.Implementation { public partial class ArchiSteamFarmServiceImpl : ReactiveObject, IArchiSteamFarmService { + const string TAG = "ArchiSteamFarmS"; + public event Action? OnConsoleWirteLine; public TaskCompletionSource? ReadLineTask { get; set; } @@ -80,9 +82,9 @@ namespace System.Application.Services.Implementation } } } - catch (Exception ex) + catch (Exception e) { - Toast.Show(ex, nameof(ArchiSteamFarmServiceImpl)); + e.LogAndShowT(TAG); await Stop().ConfigureAwait(false); } } diff --git a/src/ST.Client/Services/Implementation/HostsFileServiceImpl.cs b/src/ST.Client/Services/Implementation/HostsFileServiceImpl.cs index 7f47337f5..c0c2e887e 100644 --- a/src/ST.Client/Services/Implementation/HostsFileServiceImpl.cs +++ b/src/ST.Client/Services/Implementation/HostsFileServiceImpl.cs @@ -74,6 +74,15 @@ namespace System.Application.Services.Implementation /// const long MaxFileLength = 52428800; + void WriteDefaultHostsContent(FileStream stream) + { + var encoding = GetEncoding(); + using StreamWriter sw = new(stream, encoding); + sw.Write(s.DefaultHostsContent); + sw.Flush(); + stream.SetLength(stream.Position); + } + /// /// 尝试开始操作前 /// @@ -98,7 +107,7 @@ namespace System.Application.Services.Implementation { try { - fileInfo.Create().Dispose(); + WriteDefaultHostsContent(fileInfo.Create()); return true; } catch (Exception ex) @@ -317,7 +326,7 @@ namespace System.Application.Services.Implementation var removeLen = last_line_value.Length + Environment.NewLine.Length; stringBuilder.Remove(stringBuilder.Length - removeLen, removeLen); } - if (!markLength.Add(mark)) throw new Exception($"hosts file mark duplicate, value: {mark}"); + if (!markLength.Add(mark)) throw new HandleHostsFileException(AppResources.CommunityFix_Hosts_MarkDuplicate_.Format(mark)); return null; }); if (is_effective_value_v2 != HandleLineResult.Duplicate) @@ -461,7 +470,11 @@ namespace System.Application.Services.Implementation Log.Error(TAG, ex, "UpdateHosts catch."); result.ResultType = OperationResultType.Error; result.AppendData = ex; - if (ex is UnauthorizedAccessException || ex is SecurityException) + if (ex is HandleHostsFileException) + { + result.Message = ex.Message; + } + else if (ex is UnauthorizedAccessException || ex is SecurityException) { result.Message = AppResources.FileUnauthorized; } @@ -481,6 +494,30 @@ namespace System.Application.Services.Implementation public void OpenFile() => s.OpenFileByTextReader(s.HostsFilePath); + public void OpenFileDir() => s.OpenFolder(s.HostsFilePath); + + public async void ResetFile() + { + var r = await MessageBox.ShowAsync( + AppResources.CommunityFix_ResetHostsFileTip, + AppResources.Warning, + button: MessageBox.Button.OKCancel, + rememberChooseKey: MessageBox.DontPromptType.ResetHostsFile); + if (r.IsOK()) + { + try + { + WriteDefaultHostsContent(new FileStream(s.HostsFilePath, FileMode.OpenOrCreate)); + Toast.Show(AppResources.CommunityFix_ResetHostsFileOk); + } + catch (Exception e) + { + Log.Error(TAG, e, nameof(ResetFile)); + Toast.Show(AppResources.CommunityFix_ResetHostsFileCatchTip_.Format(e.Message)); + } + } + } + /// /// 获取 hosts 文件编码 /// @@ -695,5 +732,13 @@ namespace System.Application.Services.Implementation mOccupyHostsFileStream = new FileStream(s.HostsFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None); } #endif + + sealed class HandleHostsFileException : Exception + { + public HandleHostsFileException(string message) : base(message) + { + + } + } } } \ No newline at end of file diff --git a/src/ST.Client/Services/Implementation/HttpProxyServiceImpl.cs b/src/ST.Client/Services/Implementation/HttpProxyServiceImpl.cs index f8244ddf1..9e5ddc57e 100644 --- a/src/ST.Client/Services/Implementation/HttpProxyServiceImpl.cs +++ b/src/ST.Client/Services/Implementation/HttpProxyServiceImpl.cs @@ -428,8 +428,7 @@ namespace System.Application.Services.Implementation #if DEBUG catch (Exception e) { - Toast.Show("TrustRootCertificate Error" + Environment.NewLine + e.GetAllMessage()); - Log.Error(TAG, e, "CertificateManager.TrustRootCertificate catch."); + e.LogAndShowT(TAG, msg: "TrustRootCertificate Error"); } #else catch { } @@ -442,8 +441,7 @@ namespace System.Application.Services.Implementation #if DEBUG catch (Exception e) { - Toast.Show("EnsureRootCertificate Error" + Environment.NewLine + e.GetAllMessage()); - Log.Error(TAG, e, "CertificateManager.EnsureRootCertificate catch."); + e.LogAndShowT(TAG, msg: "EnsureRootCertificate Error"); } #else catch { } @@ -743,7 +741,7 @@ namespace System.Application.Services.Implementation } catch (Exception ex) { - Toast.Show(ex, nameof(HttpProxyServiceImpl), msg: nameof(StopProxy)); + ex.LogAndShowT(TAG); } } diff --git a/src/ST.Client/Services/Mvvm/AuthService.cs b/src/ST.Client/Services/Mvvm/AuthService.cs index 34ec22fee..bcb7b52c4 100644 --- a/src/ST.Client/Services/Mvvm/AuthService.cs +++ b/src/ST.Client/Services/Mvvm/AuthService.cs @@ -24,33 +24,40 @@ namespace System.Application.Services { public sealed class AuthService : ReactiveObject { + const string TAG = "AuthS"; + static AuthService? mCurrent; public static AuthService Current => mCurrent ?? new(); + static Stream? GetQrCodeStream(IEnumerable datas) + { + var dtos = datas.Select(x => x.ToLightweightExportDTO()).ToArray(); + var bytes = Serializable.SMP(dtos); +#if DEBUG + var bytes_compress_gzip = bytes.CompressByteArray(); +#endif + var bytes_compress_br = bytes.CompressByteArrayByBrotli(); +#if DEBUG + Toast.Show($"bytesLength, source: {bytes.Length}, gzip: {bytes_compress_gzip.Length}, br: {bytes_compress_br.Length}"); +#endif + (var result, var stream, var e) = QRCodeHelper.Create(bytes_compress_br); + switch (result) + { + case QRCodeHelper.QRCodeCreateResult.DataTooLong: + Toast.Show(AppResources.AuthLocal_ExportToQRCodeTooLongErrorTip); + break; + case QRCodeHelper.QRCodeCreateResult.Exception: + e?.LogAndShowT(TAG); + break; + } + return stream; + } + public static async Task GetQrCodeStreamAsync(IEnumerable datas) { var qrCode = await Task.Run(() => { - var dtos = datas.Select(x => x.ToLightweightExportDTO()).ToArray(); - var bytes = Serializable.SMP(dtos); -#if DEBUG - var bytes_compress_gzip = bytes.CompressByteArray(); -#endif - var bytes_compress_br = bytes.CompressByteArrayByBrotli(); -#if DEBUG - Toast.Show($"bytesLength, source: {bytes.Length}, gzip: {bytes_compress_gzip.Length}, br: {bytes_compress_br.Length}"); -#endif - (var result, var stream, var e) = QRCodeHelper.Create(bytes_compress_br); - switch (result) - { - case QRCodeHelper.QRCodeCreateResult.DataTooLong: - Toast.Show(AppResources.AuthLocal_ExportToQRCodeTooLongErrorTip); - break; - case QRCodeHelper.QRCodeCreateResult.Exception: - Toast.Show(e!, nameof(AuthService), msg: nameof(GetQrCodeStreamAsync)); - break; - } - return stream; + return GetQrCodeStream(datas); }); return qrCode; } @@ -765,9 +772,9 @@ namespace System.Application.Services await fileWriteStream.FlushAsync(); await fileWriteStream.DisposeAsync(); } - catch (Exception ex) + catch (Exception e) { - Toast.Show(ex, nameof(AuthService), msg: nameof(ExportAuthenticators)); + e.LogAndShowT(TAG); } } diff --git a/src/ST.Client/UI/Resx/AppResources.Designer.cs b/src/ST.Client/UI/Resx/AppResources.Designer.cs index 54038573a..25ab3dee2 100644 --- a/src/ST.Client/UI/Resx/AppResources.Designer.cs +++ b/src/ST.Client/UI/Resx/AppResources.Designer.cs @@ -1202,6 +1202,15 @@ namespace System.Application.UI.Resx { } } + /// + /// 查找类似 Mark 出现多个 [{0}],请尝试重置 Hosts 文件或编辑 Hosts 文件并删除多余的 Mark 后再试一次 的本地化字符串。 + /// + public static string CommunityFix_Hosts_MarkDuplicate_ { + get { + return ResourceManager.GetString("CommunityFix_Hosts_MarkDuplicate_", resourceCulture); + } + } + /// /// 查找类似 启用 VPN 模式 的本地化字符串。 /// @@ -1256,6 +1265,15 @@ namespace System.Application.UI.Resx { } } + /// + /// 查找类似 打开 Hosts 所在文件夹 的本地化字符串。 + /// + public static string CommunityFix_OpenHostsDir { + get { + return ResourceManager.GetString("CommunityFix_OpenHostsDir", resourceCulture); + } + } + /// /// 查找类似 加速模式 的本地化字符串。 /// @@ -1337,6 +1355,42 @@ namespace System.Application.UI.Resx { } } + /// + /// 查找类似 重置 Hosts 文件 的本地化字符串。 + /// + public static string CommunityFix_ResetHostsFile { + get { + return ResourceManager.GetString("CommunityFix_ResetHostsFile", resourceCulture); + } + } + + /// + /// 查找类似 重置 Hosts 文件失败,错误:{0} 的本地化字符串。 + /// + public static string CommunityFix_ResetHostsFileCatchTip_ { + get { + return ResourceManager.GetString("CommunityFix_ResetHostsFileCatchTip_", resourceCulture); + } + } + + /// + /// 查找类似 重置 Hosts 文件成功 的本地化字符串。 + /// + public static string CommunityFix_ResetHostsFileOk { + get { + return ResourceManager.GetString("CommunityFix_ResetHostsFileOk", resourceCulture); + } + } + + /// + /// 查找类似 重置 Hosts 文件将删除所有数据并还原当前操作系统默认值,建议先编辑 Hosts 文件查看是否有需要的数据先进行备份后再执行此操作,是否确认重置? 的本地化字符串。 + /// + public static string CommunityFix_ResetHostsFileTip { + get { + return ResourceManager.GetString("CommunityFix_ResetHostsFileTip", resourceCulture); + } + } + /// /// 查找类似 管理脚本 的本地化字符串。 /// diff --git a/src/ST.Client/UI/Resx/AppResources.resx b/src/ST.Client/UI/Resx/AppResources.resx index 2a868f57f..0b237c496 100644 --- a/src/ST.Client/UI/Resx/AppResources.resx +++ b/src/ST.Client/UI/Resx/AppResources.resx @@ -2033,4 +2033,22 @@ AAAAA-BBBBB-CCCC3 启用 VPN 模式 + + 打开 Hosts 所在文件夹 + + + 重置 Hosts 文件 + + + 重置 Hosts 文件失败,错误:{0} + + + 重置 Hosts 文件将删除所有数据并还原当前操作系统默认值,建议先编辑 Hosts 文件查看是否有需要的数据先进行备份后再执行此操作,是否确认重置? + + + Mark 出现多个 [{0}],请尝试重置 Hosts 文件或编辑 Hosts 文件并删除多余的 Mark 后再试一次 + + + 重置 Hosts 文件成功 + \ No newline at end of file diff --git a/src/ST.Client/UI/ViewModels/Pages/CommunityProxyPageViewModel.cs b/src/ST.Client/UI/ViewModels/Pages/CommunityProxyPageViewModel.cs index a30ead16f..9054999ce 100644 --- a/src/ST.Client/UI/ViewModels/Pages/CommunityProxyPageViewModel.cs +++ b/src/ST.Client/UI/ViewModels/Pages/CommunityProxyPageViewModel.cs @@ -30,6 +30,10 @@ namespace System.Application.UI.ViewModels public ReactiveCommand? EditHostsFileCommand { get; } + public ReactiveCommand? OpenHostsDirCommand { get; } + + public ReactiveCommand? ResetHostsFileCommand { get; } + public ReactiveCommand? NetworkFixCommand { get; } //public ReactiveCommand AutoRunProxyCommand { get; } @@ -57,6 +61,8 @@ namespace System.Application.UI.ViewModels SetupCertificateCommand = ReactiveCommand.Create(SetupCertificate_OnClick); DeleteCertificateCommand = ReactiveCommand.Create(DeleteCertificate_OnClick); EditHostsFileCommand = ReactiveCommand.Create(hostsFileService.OpenFile); + OpenHostsDirCommand = ReactiveCommand.Create(hostsFileService.OpenFileDir); + ResetHostsFileCommand = ReactiveCommand.Create(hostsFileService.ResetFile); NetworkFixCommand = ReactiveCommand.Create(ProxyService.Current.FixNetwork); TrustCerCommand = ReactiveCommand.Create(TrustCer_OnClick); OpenCertificateDirCommand = ReactiveCommand.Create(() => diff --git a/src/ST.Client/UI/ViewModels/Pages/GameListPageViewModel.cs b/src/ST.Client/UI/ViewModels/Pages/GameListPageViewModel.cs index 472b21d67..9f19c15a1 100644 --- a/src/ST.Client/UI/ViewModels/Pages/GameListPageViewModel.cs +++ b/src/ST.Client/UI/ViewModels/Pages/GameListPageViewModel.cs @@ -16,6 +16,8 @@ namespace System.Application.UI.ViewModels { public partial class GameListPageViewModel { + const string TAG = "GameListPageVM"; + readonly Dictionary dictPinYinArray = new(); Func PredicateName(string? text) { @@ -301,9 +303,10 @@ namespace System.Application.UI.ViewModels } catch (Exception e) { - Toast.Show(e, nameof(GameListPageViewModel)); + e.LogAndShowT(TAG); } } + public void AddAFKAppListFunc(SteamApp app) { try @@ -321,9 +324,10 @@ namespace System.Application.UI.ViewModels } catch (Exception e) { - Toast.Show(e, nameof(GameListPageViewModel)); + e.LogAndShowT(TAG); } } + public void AddHideAppList(SteamApp app) { try @@ -336,9 +340,10 @@ namespace System.Application.UI.ViewModels } catch (Exception e) { - Toast.Show(e, nameof(GameListPageViewModel)); + e.LogAndShowT(TAG); } } + public async void UnlockAchievement_Click(SteamApp app) { if (!ISteamService.Instance.IsRunningSteamProcess) diff --git a/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/AuthTradeWindowViewModel.cs b/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/AuthTradeWindowViewModel.cs index ffd3bf822..939d9317c 100644 --- a/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/AuthTradeWindowViewModel.cs +++ b/src/ST.Client/UI/ViewModels/Windows/LocalAuthPage/AuthTradeWindowViewModel.cs @@ -22,6 +22,8 @@ namespace System.Application.UI.ViewModels { public partial class AuthTradeWindowViewModel : MyAuthenticatorWindowViewModel { + const string TAG = "AuthTradeWindowVM"; + public AuthTradeWindowViewModel() : base() { } @@ -547,12 +549,12 @@ namespace System.Application.UI.ViewModels } catch (WinAuthInvalidTradesResponseException ex) { - Toast.Show(ex, nameof(AuthTradeWindowViewModel), msg: nameof(AcceptTrade)); + ex.LogAndShowT(TAG); return false; } catch (ApplicationException ex) { - Toast.Show(ex, nameof(AuthTradeWindowViewModel), msg: nameof(AcceptTrade)); + ex.LogAndShowT(TAG); return false; } } @@ -586,12 +588,12 @@ namespace System.Application.UI.ViewModels } catch (WinAuthInvalidTradesResponseException ex) { - Toast.Show(ex, nameof(AuthTradeWindowViewModel), msg: nameof(RejectTrade)); + ex.LogAndShowT(TAG); return false; } catch (ApplicationException ex) { - Toast.Show(ex, nameof(AuthTradeWindowViewModel), msg: nameof(RejectTrade)); + ex.LogAndShowT(TAG); return false; } } diff --git a/src/ST.Services.CloudService/Services/CloudService/ApiConnection.cs b/src/ST.Services.CloudService/Services/CloudService/ApiConnection.cs index d291f0bbe..ae312835c 100644 --- a/src/ST.Services.CloudService/Services/CloudService/ApiConnection.cs +++ b/src/ST.Services.CloudService/Services/CloudService/ApiConnection.cs @@ -66,6 +66,10 @@ namespace System.Application.Services.CloudService { case ExceptionKnownType.Canceled: return (ApiResponseCode.Canceled, null); + case ExceptionKnownType.OperationCanceled: + return (ApiResponseCode.OperationCanceled, null); + case ExceptionKnownType.TaskCanceled: + return (ApiResponseCode.TaskCanceled, null); case ExceptionKnownType.CertificateNotYetValid: return (ApiResponseCode.CertificateNotYetValid, null); }