diff --git a/Ink Canvas/App.xaml.cs b/Ink Canvas/App.xaml.cs index 23328060..eca96c36 100644 --- a/Ink Canvas/App.xaml.cs +++ b/Ink Canvas/App.xaml.cs @@ -37,23 +37,65 @@ namespace Ink_Canvas public App() { + // 如果是看门狗子进程,直接进入看门狗主循环并终止主流程 + var args = Environment.GetCommandLineArgs(); + if (args.Length >= 2 && args[1] == "--watchdog") + { + RunWatchdogIfNeeded(); + Environment.Exit(0); + return; + } + + // 启动时优先同步设置,确保CrashAction为最新 + SyncCrashActionFromSettings(); + this.Startup += new StartupEventHandler(App_Startup); this.DispatcherUnhandledException += App_DispatcherUnhandledException; StartHeartbeatMonitor(); - StartWatchdogIfNeeded(); + + // 仅在崩溃后操作为静默重启时才启动看门狗 + if (CrashAction == CrashActionType.SilentRestart) + { + StartWatchdogIfNeeded(); + } this.Exit += App_Exit; // 注册退出事件 } // 增加字段保存崩溃后操作设置 public static CrashActionType CrashAction = CrashActionType.SilentRestart; + // 修正:允许静态调用 + public static void SyncCrashActionFromSettings() + { + try + { + // 优先从 Settings.json 直接读取 + var settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Settings.json"); + if (File.Exists(settingsPath)) + { + var json = File.ReadAllText(settingsPath); + dynamic obj = Newtonsoft.Json.JsonConvert.DeserializeObject(json); + int crashAction = 0; + try { crashAction = (int)(obj["startup"]["crashAction"] ?? 0); } catch { } + CrashAction = (CrashActionType)crashAction; + } + // 兜底:从主窗口同步 + else if (Ink_Canvas.MainWindow.Settings != null && Ink_Canvas.MainWindow.Settings.Startup != null) + { + CrashAction = (CrashActionType)Ink_Canvas.MainWindow.Settings.Startup.CrashAction; + } + } + catch { } + } + private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) { Ink_Canvas.MainWindow.ShowNewMessage("抱歉,出现未预期的异常,可能导致 InkCanvasForClass 运行不稳定。\n建议保存墨迹后重启应用。", true); LogHelper.NewLog(e.Exception.ToString()); e.Handled = true; - // 修改:仅当非用户主动退出时才触发自动重启 + SyncCrashActionFromSettings(); // 新增:崩溃时同步最新设置 + if (CrashAction == CrashActionType.SilentRestart && !IsAppExitByUser) { StartupCount.Increment(); @@ -78,7 +120,6 @@ namespace Ink_Canvas void App_Startup(object sender, StartupEventArgs e) { - RunWatchdogIfNeeded(); /*if (!StoreHelper.IsStoreApp) */RootPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase; LogHelper.NewLog(string.Format("Ink Canvas Starting (Version: {0})", Assembly.GetExecutingAssembly().GetName().Version.ToString())); @@ -91,6 +132,16 @@ namespace Ink_Canvas LogHelper.NewLog("Detected existing instance"); MessageBox.Show("已有一个程序实例正在运行"); LogHelper.NewLog("Ink Canvas automatically closed"); + IsAppExitByUser = true; // 多开时标记为用户主动退出 + // 写入退出信号,确保看门狗不会重启 + try { + StartupCount.Reset(); + File.WriteAllText(watchdogExitSignalFile, "exit"); + if (watchdogProcess != null && !watchdogProcess.HasExited) + { + watchdogProcess.Kill(); + } + } catch { } Environment.Exit(0); } @@ -260,6 +311,7 @@ namespace Ink_Canvas if ((DateTime.Now - lastHeartbeat).TotalSeconds > 10) { LogHelper.NewLog("检测到主线程无响应,自动重启。"); + SyncCrashActionFromSettings(); // 新增:心跳检测时同步最新设置 if (CrashAction == CrashActionType.SilentRestart) { StartupCount.Increment(); @@ -320,16 +372,21 @@ namespace Ink_Canvas } Thread.Sleep(2000); } - // 主进程异常退出,自动重启 - StartupCount.Increment(); - if (StartupCount.GetCount() >= 5) + // 主进程异常退出,自动重启前判断崩溃后操作 + SyncCrashActionFromSettings(); // 新增:同步设置 + if (CrashAction == CrashActionType.SilentRestart) { - MessageBox.Show("检测到程序已连续重启5次,已停止自动重启。请联系开发者或检查系统环境。", "重启次数过多", MessageBoxButton.OK, MessageBoxImage.Error); - StartupCount.Reset(); - Environment.Exit(1); + StartupCount.Increment(); + if (StartupCount.GetCount() >= 5) + { + MessageBox.Show("检测到程序已连续重启5次,已停止自动重启。请联系开发者或检查系统环境。", "重启次数过多", MessageBoxButton.OK, MessageBoxImage.Error); + StartupCount.Reset(); + Environment.Exit(1); + } + string exePath = Process.GetCurrentProcess().MainModule.FileName; + Process.Start(exePath); } - string exePath = Process.GetCurrentProcess().MainModule.FileName; - Process.Start(exePath); + // CrashActionType.NoAction 时不重启,直接退出 } catch { } Environment.Exit(0); diff --git a/Ink Canvas/Helpers/AutoUpdateHelper.cs b/Ink Canvas/Helpers/AutoUpdateHelper.cs index ad4e9ae5..6df97809 100644 --- a/Ink Canvas/Helpers/AutoUpdateHelper.cs +++ b/Ink Canvas/Helpers/AutoUpdateHelper.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Windows.Controls; using System.IO.Compression; using System.Text; +using System.Collections.Generic; namespace Ink_Canvas.Helpers { @@ -576,358 +577,162 @@ namespace Ink_Canvas.Helpers } } - private static bool CopyDirectory(string sourceDir, string destinationDir) + private static async Task GetUrlDelay(string url) { - bool allCopiesSuccessful = true; - try { - // 创建目标目录(如果不存在) - Directory.CreateDirectory(destinationDir); - LogHelper.WriteLogToFile($"AutoUpdate | Created/verified destination directory: {destinationDir}"); - - // 复制所有文件 - foreach (string filePath in Directory.GetFiles(sourceDir)) + using (var client = new System.Net.Http.HttpClient()) { - string fileName = Path.GetFileName(filePath); - string destPath = Path.Combine(destinationDir, fileName); - try - { - LogHelper.WriteLogToFile($"AutoUpdate | Copying file: {fileName}"); - - // 如果目标文件存在,先删除 - if (File.Exists(destPath)) - { - File.Delete(destPath); - } - - File.Copy(filePath, destPath); - } - catch (Exception ex) - { - allCopiesSuccessful = false; - LogHelper.WriteLogToFile($"AutoUpdate | Error copying file {fileName}: {ex.Message}", LogHelper.LogType.Error); - } + client.Timeout = TimeSpan.FromSeconds(5); + var sw = System.Diagnostics.Stopwatch.StartNew(); + var resp = await client.SendAsync(new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Head, url)); + sw.Stop(); + if (resp.IsSuccessStatusCode) + return sw.ElapsedMilliseconds; } - - // 递归复制所有子目录 - foreach (string subDirPath in Directory.GetDirectories(sourceDir)) - { - string subDirName = Path.GetFileName(subDirPath); - string destSubDir = Path.Combine(destinationDir, subDirName); - - bool subDirCopyResult = CopyDirectory(subDirPath, destSubDir); - if (!subDirCopyResult) - { - allCopiesSuccessful = false; - } - } - - return allCopiesSuccessful; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | Error copying directory {sourceDir}: {ex.Message}", LogHelper.LogType.Error); - return false; } + catch { } + return -1; } - private static void RestartApplication() + // 线路组结构体(包含版本、下载、日志地址) + public class UpdateLineGroup + { + public string GroupName { get; set; } // 组名 + public string VersionUrl { get; set; } // 版本检测地址 + public string DownloadUrlFormat { get; set; } // 下载地址格式(带{0}占位符) + public string LogUrl { get; set; } // 更新日志地址 + } + + // 通道-线路组映射 + private static readonly Dictionary> ChannelLineGroups = new Dictionary> + { + { UpdateChannel.Release, new List + { + new UpdateLineGroup + { + GroupName = "GitHub主线", + VersionUrl = "https://github.com/InkCanvasForClass/community/raw/refs/heads/beta/AutomaticUpdateVersionControl.txt", + DownloadUrlFormat = "https://github.com/InkCanvasForClass/community/releases/download/{0}/InkCanvasForClass.CE.{0}.zip", + LogUrl = "https://github.com/InkCanvasForClass/community/raw/refs/heads/beta/UpdateLog.md" + }, + new UpdateLineGroup + { + GroupName = "bgithub备用", + VersionUrl = "https://bgithub.xyz/InkCanvasForClass/community/raw/refs/heads/main/AutomaticUpdateVersionControl.txt", + DownloadUrlFormat = "https://bgithub.xyz/InkCanvasForClass/community/releases/download/{0}/InkCanvasForClass.CE.{0}.zip", + LogUrl = "https://bgithub.xyz/InkCanvasForClass/community/raw/refs/heads/main/UpdateLog.md" + } + } + }, + { UpdateChannel.Beta, new List + { + new UpdateLineGroup + { + GroupName = "GitHub主线", + VersionUrl = "https://github.com/InkCanvasForClass/community-beta/raw/refs/heads/main/AutomaticUpdateVersionControl.txt", + DownloadUrlFormat = "https://github.com/InkCanvasForClass/community-beta/releases/download/{0}/InkCanvasForClass.CE.{0}.zip", + LogUrl = "https://github.com/InkCanvasForClass/community-beta/raw/refs/heads/main/UpdateLog.md" + }, + new UpdateLineGroup + { + GroupName = "bgithub备用", + VersionUrl = "https://bgithub.xyz/InkCanvasForClass/community-beta/raw/refs/heads/main/AutomaticUpdateVersionControl.txt", + DownloadUrlFormat = "https://bgithub.xyz/InkCanvasForClass/community-beta/releases/download/{0}/InkCanvasForClass.CE.{0}.zip", + LogUrl = "https://bgithub.xyz/InkCanvasForClass/community-beta/raw/refs/heads/main/UpdateLog.md" + } + } + } + }; + + // 检测线路组延迟,只检测当前通道下的所有线路组,返回最快组 + private static async Task GetFastestLineGroup(UpdateChannel channel) + { + var groups = ChannelLineGroups[channel]; + long minDelay = long.MaxValue; + UpdateLineGroup bestGroup = null; + foreach (var group in groups) + { + var delay = await GetUrlDelay(group.VersionUrl); + if (delay >= 0 && delay < minDelay) + { + minDelay = delay; + bestGroup = group; + } + } + return bestGroup; + } + + // 新的自动选择线路组的更新检测方法,返回远程版本号和所用线路组 + public static async Task<(string remoteVersion, UpdateLineGroup lineGroup)> CheckForUpdatesWithAutoLine(UpdateChannel channel = UpdateChannel.Release, bool alwaysGetRemote = false) { try { - string appPath = Assembly.GetExecutingAssembly().Location; - LogHelper.WriteLogToFile($"AutoUpdate | Restarting application: {appPath}"); - - // Create a batch file to wait briefly and then start the application - // This allows the current process to fully exit before starting the new instance - string batchFilePath = Path.Combine(Path.GetTempPath(), "RestartICC_" + Guid.NewGuid().ToString().Substring(0, 8) + ".bat"); - - string batchContent = - "@echo off\r\n" + - "timeout /t 2 /nobreak >nul\r\n" + - ":: 检查应用程序是否已经在运行\r\n" + - "tasklist /FI \"IMAGENAME eq Ink Canvas.exe\" | find /i \"Ink Canvas.exe\" > nul\r\n" + - "if %ERRORLEVEL% neq 0 (\r\n" + - " echo 启动应用程序...\r\n" + - $" start \"\" \"{appPath}\"\r\n" + - ") else (\r\n" + - " echo 应用程序已经在运行,不再重复启动\r\n" + - ")\r\n" + - "timeout /t 1 /nobreak >nul\r\n" + - "del \"%~f0\"\r\n" + - "exit\r\n"; // 确保批处理进程结束 - - File.WriteAllText(batchFilePath, batchContent); - - // Start the batch file - Process.Start(new ProcessStartInfo + string localVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); + LogHelper.WriteLogToFile($"AutoUpdate | Local version: {localVersion}"); + LogHelper.WriteLogToFile($"AutoUpdate | 检测通道 {channel} 下最快线路组..."); + var bestGroup = await GetFastestLineGroup(channel); + if (bestGroup == null) { - FileName = "cmd.exe", - Arguments = $"/c start \"\" \"{batchFilePath}\"", - CreateNoWindow = true, - UseShellExecute = false - }); - - LogHelper.WriteLogToFile($"AutoUpdate | Created restart script at {batchFilePath}"); - - // Shutdown the application - LogHelper.WriteLogToFile($"AutoUpdate | Shutting down application for restart"); - Application.Current.Dispatcher.Invoke(() => + LogHelper.WriteLogToFile("AutoUpdate | 所有线路组均不可用", LogHelper.LogType.Error); + return (null, null); + } + LogHelper.WriteLogToFile($"AutoUpdate | 选择最快线路组: {bestGroup.GroupName} {bestGroup.VersionUrl}"); + string remoteVersion = await GetRemoteVersion(bestGroup.VersionUrl); + if (remoteVersion != null) { - Application.Current.Shutdown(); - }); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | Error restarting application: {ex.Message}", LogHelper.LogType.Error); - - // Fallback direct restart approach - try - { - string appPath = Assembly.GetExecutingAssembly().Location; - LogHelper.WriteLogToFile($"AutoUpdate | Attempting direct restart: {appPath}"); - - // 检查是否已有实例运行 - Process[] processes = Process.GetProcessesByName("Ink Canvas"); - if (processes.Length <= 1) // 只有当前进程 + LogHelper.WriteLogToFile($"AutoUpdate | Remote version: {remoteVersion}"); + Version local = new Version(localVersion); + Version remote = new Version(remoteVersion); + if (remote > local || alwaysGetRemote) { - Process.Start(appPath); + LogHelper.WriteLogToFile($"AutoUpdate | New version available or alwaysGetRemote: {remoteVersion}"); + return (remoteVersion, bestGroup); } else { - LogHelper.WriteLogToFile($"AutoUpdate | Application already running, not starting a new instance"); - } - - Application.Current.Dispatcher.Invoke(() => - { - Application.Current.Shutdown(); - }); - } - catch (Exception fallbackEx) - { - LogHelper.WriteLogToFile($"AutoUpdate | Fallback restart also failed: {fallbackEx.Message}", LogHelper.LogType.Error); - } - } - } - - private static void ExecuteCommandLine(string command) - { - try - { - ProcessStartInfo processStartInfo = new ProcessStartInfo - { - FileName = "cmd.exe", - Arguments = $"/c {command} & exit", // 添加exit确保cmd进程退出 - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - using (Process process = new Process { StartInfo = processStartInfo }) - { - process.Start(); - // 设置一个超时时间 - bool exited = process.WaitForExit(500); // 等待500毫秒 - if (!exited) - { - // 不等待进程完成,让它在后台运行 - LogHelper.WriteLogToFile($"AutoUpdate | Command is running in background"); - } - Application.Current.Shutdown(); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | Error executing command: {ex.Message}", LogHelper.LogType.Error); - } - } - - public static void DeleteUpdatesFolder() - { - try - { - if (Directory.Exists(updatesFolderPath)) - { - // Try to delete all files first in case of locking issues - foreach (string file in Directory.GetFiles(updatesFolderPath, "*", SearchOption.AllDirectories)) - { - try - { - File.Delete(file); - LogHelper.WriteLogToFile($"AutoUpdate | Deleted file: {file}"); - } - catch (Exception fileEx) - { - LogHelper.WriteLogToFile($"AutoUpdate | Could not delete file {file}: {fileEx.Message}", LogHelper.LogType.Warning); - } - } - - // Then try to delete subdirectories - foreach (string dir in Directory.GetDirectories(updatesFolderPath)) - { - try - { - Directory.Delete(dir, true); - LogHelper.WriteLogToFile($"AutoUpdate | Deleted directory: {dir}"); - } - catch (Exception dirEx) - { - LogHelper.WriteLogToFile($"AutoUpdate | Could not delete directory {dir}: {dirEx.Message}", LogHelper.LogType.Warning); - } - } - - // Finally try to delete the main directory - try - { - Directory.Delete(updatesFolderPath, true); - LogHelper.WriteLogToFile($"AutoUpdate | Deleted updates folder: {updatesFolderPath}"); - } - catch (Exception mainDirEx) - { - LogHelper.WriteLogToFile($"AutoUpdate | Could not completely delete updates folder: {mainDirEx.Message}", LogHelper.LogType.Warning); + LogHelper.WriteLogToFile($"AutoUpdate | Current version is up to date"); + return (null, bestGroup); } } else { - LogHelper.WriteLogToFile($"AutoUpdate | Updates folder does not exist: {updatesFolderPath}"); + LogHelper.WriteLogToFile("AutoUpdate | 获取远程版本失败", LogHelper.LogType.Error); + return (null, bestGroup); } } catch (Exception ex) { - LogHelper.WriteLogToFile($"AutoUpdate | Error deleting updates folder: {ex.Message}", LogHelper.LogType.Error); + LogHelper.WriteLogToFile($"AutoUpdate | Error in CheckForUpdatesWithAutoLine: {ex.Message}", LogHelper.LogType.Error); + return (null, null); } } - // 新增:版本修复方法,强制下载并安装指定通道的最新版本 - public static async Task FixVersion(UpdateChannel channel = UpdateChannel.Release) + // 使用指定线路组下载新版 + public static async Task DownloadSetupFileWithLineGroup(string version, UpdateLineGroup group) { try { - LogHelper.WriteLogToFile($"AutoUpdate | Starting version fix for {channel} channel"); - - // 获取远程版本号,而不是检查更新 - string remoteVersion = null; - string proxy = null; - - // 根据通道选择URL - string primaryUrl, fallbackUrl; - - if (channel == UpdateChannel.Release) - { - // Release通道版本信息地址 - primaryUrl = "https://github.com/InkCanvasForClass/community/raw/refs/heads/beta/AutomaticUpdateVersionControl.txt"; - fallbackUrl = "https://bgithub.xyz/InkCanvasForClass/community/raw/refs/heads/main/AutomaticUpdateVersionControl.txt"; - } - else - { - // Beta通道版本信息地址 - primaryUrl = "https://github.com/InkCanvasForClass/community-beta/raw/refs/heads/main/AutomaticUpdateVersionControl.txt"; - fallbackUrl = "https://bgithub.xyz/InkCanvasForClass/community-beta/raw/refs/heads/main/AutomaticUpdateVersionControl.txt"; - } - - LogHelper.WriteLogToFile($"AutoUpdate | Retrieving remote version from {channel} channel"); - - // 先尝试主地址 - remoteVersion = await GetRemoteVersion(primaryUrl); - - // 如果主地址失败,尝试备用地址 - if (remoteVersion == null) - { - LogHelper.WriteLogToFile($"AutoUpdate | Primary URL failed, trying fallback URL"); - remoteVersion = await GetRemoteVersion(fallbackUrl); - } - - if (string.IsNullOrEmpty(remoteVersion)) - { - LogHelper.WriteLogToFile("AutoUpdate | Failed to retrieve remote version for fixing", LogHelper.LogType.Error); - return false; - } - - LogHelper.WriteLogToFile($"AutoUpdate | Remote version for fixing: {remoteVersion}"); - - // 无论版本是否为最新,都下载远程版本 - bool downloadResult = await DownloadSetupFileAndSaveStatus(remoteVersion, "", channel); - - if (!downloadResult) - { - LogHelper.WriteLogToFile("AutoUpdate | Failed to download update for fixing", LogHelper.LogType.Error); - return false; - } - - // 执行安装,非静默模式 - InstallNewVersionApp(remoteVersion, false); - - // 设置为用户主动退出,避免被看门狗判定为崩溃 - App.IsAppExitByUser = true; - - // 关闭应用程序 - Application.Current.Dispatcher.Invoke(() => { - Application.Current.Shutdown(); - }); - - return true; + string url = string.Format(group.DownloadUrlFormat, version); + string zipFilePath = Path.Combine(updatesFolderPath, $"InkCanvasForClass.CE.{version}.zip"); + LogHelper.WriteLogToFile($"AutoUpdate | Downloading from: {url}"); + return await DownloadFile(url, zipFilePath); } catch (Exception ex) { - LogHelper.WriteLogToFile($"AutoUpdate | Error in FixVersion: {ex.Message}", LogHelper.LogType.Error); + LogHelper.WriteLogToFile($"AutoUpdate | Error in DownloadSetupFileWithLineGroup: {ex.Message}", LogHelper.LogType.Error); return false; } } - // 获取更新日志 - public static async Task GetUpdateLog(UpdateChannel channel = UpdateChannel.Release) + // 使用指定线路组获取更新日志 + public static async Task GetUpdateLogWithLineGroup(UpdateLineGroup group) { - try - { - string primaryUrl, fallbackUrl; - - if (channel == UpdateChannel.Release) - { - // Release通道更新日志地址 - primaryUrl = "https://github.com/InkCanvasForClass/community/raw/refs/heads/beta/UpdateLog.md"; - fallbackUrl = "https://bgithub.xyz/InkCanvasForClass/community/raw/refs/heads/main/UpdateLog.md"; - } - else - { - // Beta通道更新日志地址 - primaryUrl = "https://github.com/InkCanvasForClass/community-beta/raw/refs/heads/main/UpdateLog.md"; - fallbackUrl = "https://bgithub.xyz/InkCanvasForClass/community-beta/raw/refs/heads/main/UpdateLog.md"; - } - - LogHelper.WriteLogToFile($"AutoUpdate | Getting update log from {channel} channel"); - - // 先尝试主地址 - string updateLog = await GetRemoteContent(primaryUrl); - - // 如果主地址失败,尝试备用地址 - if (string.IsNullOrEmpty(updateLog)) - { - LogHelper.WriteLogToFile($"AutoUpdate | Primary URL failed for update log, trying fallback URL"); - updateLog = await GetRemoteContent(fallbackUrl); - } - - if (!string.IsNullOrEmpty(updateLog)) - { - LogHelper.WriteLogToFile($"AutoUpdate | Successfully retrieved update log"); - return updateLog; - } - else - { - LogHelper.WriteLogToFile("AutoUpdate | Failed to retrieve update log from both URLs.", LogHelper.LogType.Error); - return $"# 无法获取更新日志\n\n无法从服务器获取更新日志信息,请检查网络连接后重试。"; - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | Error in GetUpdateLog: {ex.Message}", LogHelper.LogType.Error); - return $"# 获取更新日志时发生错误\n\n错误信息: {ex.Message}"; - } + return await AutoUpdateHelper.GetRemoteContent(group.LogUrl); } - // 获取远程内容的通用方法 - private static async Task GetRemoteContent(string fileUrl) + // 获取远程内容的通用方法(public 以便线路组方法调用) + public static async Task GetRemoteContent(string fileUrl) { using (HttpClient client = new HttpClient()) { @@ -935,26 +740,18 @@ namespace Ink_Canvas.Helpers { // 设置超时时间为10秒 client.Timeout = RequestTimeout; - LogHelper.WriteLogToFile($"AutoUpdate | Sending HTTP request to: {fileUrl}"); - - // 使用带超时的Task.WhenAny来确保请求不会无限期等待 var downloadTask = client.GetAsync(fileUrl); var timeoutTask = Task.Delay(RequestTimeout); - var completedTask = await Task.WhenAny(downloadTask, timeoutTask); if (completedTask == timeoutTask) { LogHelper.WriteLogToFile($"AutoUpdate | Request timed out after {RequestTimeout.TotalSeconds} seconds", LogHelper.LogType.Error); return null; } - - // 请求完成,检查结果 HttpResponseMessage response = await downloadTask; - LogHelper.WriteLogToFile($"AutoUpdate | HTTP response status: {response.StatusCode}"); response.EnsureSuccessStatusCode(); - string content = await response.Content.ReadAsStringAsync(); return content; } @@ -970,10 +767,75 @@ namespace Ink_Canvas.Helpers { LogHelper.WriteLogToFile($"AutoUpdate | Error: {ex.Message}", LogHelper.LogType.Error); } - return null; } } + + // 兼容旧接口:获取更新日志(自动选择最快线路组) + public static async Task GetUpdateLog(UpdateChannel channel = UpdateChannel.Release) + { + var group = await GetFastestLineGroup(channel); + if (group == null) return "# 无法获取更新日志\n\n所有线路均不可用。"; + return await GetUpdateLogWithLineGroup(group); + } + + // 兼容旧接口:删除更新文件夹 + public static void DeleteUpdatesFolder() + { + try + { + if (Directory.Exists(updatesFolderPath)) + { + // Try to delete all files first in case of locking issues + foreach (string file in Directory.GetFiles(updatesFolderPath, "*", SearchOption.AllDirectories)) + { + try { File.Delete(file); } catch { } + } + foreach (string dir in Directory.GetDirectories(updatesFolderPath)) + { + try { Directory.Delete(dir, true); } catch { } + } + try { Directory.Delete(updatesFolderPath, true); } catch { } + } + } + catch { } + } + + // 兼容旧接口:版本修复方法,强制下载并安装指定通道的最新版本 + public static async Task FixVersion(UpdateChannel channel = UpdateChannel.Release) + { + try + { + LogHelper.WriteLogToFile($"AutoUpdate | Starting version fix for {channel} channel"); + // 获取远程版本号(自动选择最快线路组,始终下载远程版本) + var (remoteVersion, group) = await CheckForUpdatesWithAutoLine(channel, true); + if (string.IsNullOrEmpty(remoteVersion) || group == null) + { + LogHelper.WriteLogToFile("AutoUpdate | Failed to retrieve remote version for fixing", LogHelper.LogType.Error); + return false; + } + LogHelper.WriteLogToFile($"AutoUpdate | Remote version for fixing: {remoteVersion}"); + // 无论版本是否为最新,都下载远程版本 + bool downloadResult = await DownloadSetupFileWithLineGroup(remoteVersion, group); + if (!downloadResult) + { + LogHelper.WriteLogToFile("AutoUpdate | Failed to download update for fixing", LogHelper.LogType.Error); + return false; + } + // 执行安装,非静默模式 + InstallNewVersionApp(remoteVersion, false); + App.IsAppExitByUser = true; + Application.Current.Dispatcher.Invoke(() => { + Application.Current.Shutdown(); + }); + return true; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"AutoUpdate | Error in FixVersion: {ex.Message}", LogHelper.LogType.Error); + return false; + } + } } internal class AutoUpdateWithSilenceTimeComboBox diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs index ba242a9d..e81dcb55 100644 --- a/Ink Canvas/MainWindow.xaml.cs +++ b/Ink Canvas/MainWindow.xaml.cs @@ -499,12 +499,16 @@ namespace Ink_Canvas { if (RadioCrashSilentRestart != null && RadioCrashSilentRestart.IsChecked == true) { App.CrashAction = App.CrashActionType.SilentRestart; + Settings.Startup.CrashAction = 0; } else if (RadioCrashNoAction != null && RadioCrashNoAction.IsChecked == true) { App.CrashAction = App.CrashActionType.NoAction; + Settings.Startup.CrashAction = 1; } SaveSettingsToFile(); + // 强制同步全局变量,防止后台逻辑未及时感知 + App.SyncCrashActionFromSettings(); } // 添加一个辅助方法,根据当前编辑模式设置光标 diff --git a/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs b/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs index cab4b519..9590fda9 100644 --- a/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs +++ b/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs @@ -20,40 +20,45 @@ namespace Ink_Canvas { SaveInkCanvasStrokes(true, true); } - private void SaveInkCanvasStrokes(Boolean newNotice, Boolean saveByUser) { + private void SaveInkCanvasStrokes(Boolean newNotice, Boolean saveByUser, string userSavePath = null) { try { - // 修改保存路径为软件根目录下的Saves文件夹 - string appDirectory = AppDomain.CurrentDomain.BaseDirectory; - if (string.IsNullOrEmpty(appDirectory)) - { - appDirectory = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); - } - - string savePath = Path.Combine(appDirectory, "Saves", - (saveByUser ? @"User Saved - " : @"Auto Saved - ") + - (currentMode == 0 ? "Annotation Strokes" : "BlackBoard Strokes")); - - if (!Directory.Exists(savePath)) Directory.CreateDirectory(savePath); + // 优先使用用户指定的保存路径,否则使用默认路径 string savePathWithName; - if (currentMode != 0) // 黑板模式下 - savePathWithName = savePath + @"\" + DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss-fff") + " Page-" + - CurrentWhiteboardIndex + " StrokesCount-" + inkCanvas.Strokes.Count + ".icstk"; - else - savePathWithName = savePath + @"\" + DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss-fff") + ".icstk"; + if (!string.IsNullOrEmpty(userSavePath)) { + // 用户指定了完整保存路径(含文件名) + savePathWithName = userSavePath; + string dir = Path.GetDirectoryName(savePathWithName); + if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); + } else { + // 默认保存到软件根目录下的Saves文件夹 + string appDirectory = AppDomain.CurrentDomain.BaseDirectory; + if (string.IsNullOrEmpty(appDirectory)) + appDirectory = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); + string savePath = Path.Combine(appDirectory, "Saves", + (saveByUser ? @"User Saved - " : @"Auto Saved - ") + + (currentMode == 0 ? "Annotation Strokes" : "BlackBoard Strokes")); + if (!Directory.Exists(savePath)) Directory.CreateDirectory(savePath); + if (currentMode != 0) // 黑板模式下 + savePathWithName = Path.Combine(savePath, DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss-fff") + + " Page-" + CurrentWhiteboardIndex + " StrokesCount-" + inkCanvas.Strokes.Count + ".icstk"); + else + savePathWithName = Path.Combine(savePath, DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss-fff") + ".icstk"); + } try { - using (FileStream fs = new FileStream(savePathWithName, FileMode.Create)) { + using (FileStream fs = new FileStream(savePathWithName, FileMode.Create)) { inkCanvas.Strokes.Save(fs); } } catch (Exception ex) when (ex is UnauthorizedAccessException || ex is DirectoryNotFoundException) { - // 修改异常处理中的备用路径为软件根目录下的Saves文件夹 + // 异常时备用路径仍为默认Saves文件夹 + string appDirectory = AppDomain.CurrentDomain.BaseDirectory; + if (string.IsNullOrEmpty(appDirectory)) + appDirectory = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); string fallbackPath = Path.Combine(appDirectory, "Saves"); Directory.CreateDirectory(fallbackPath); - string fileName = Path.GetFileNameWithoutExtension(savePathWithName) + "_retry.icstk"; string newPath = Path.Combine(fallbackPath, fileName); - try { using (FileStream fs = new FileStream(newPath, FileMode.Create)) { inkCanvas.Strokes.Save(fs); diff --git a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs index 9f6adb19..d8a9c871 100644 --- a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs +++ b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs @@ -115,6 +115,22 @@ namespace Ink_Canvas { Settings.Startup = new Startup(); } + // 恢复崩溃后操作设置 + if (Settings.Startup != null) + { + // 恢复崩溃后操作选项 + if (Settings.Startup.CrashAction == 0) + { + App.CrashAction = App.CrashActionType.SilentRestart; + if (RadioCrashSilentRestart != null) RadioCrashSilentRestart.IsChecked = true; + } + else + { + App.CrashAction = App.CrashActionType.NoAction; + if (RadioCrashNoAction != null) RadioCrashNoAction.IsChecked = true; + } + } + // Appearance if (Settings.Appearance != null) { if (!Settings.Appearance.IsEnableDisPlayNibModeToggler) { diff --git a/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs b/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs index d962f890..2c7eeb87 100644 --- a/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs +++ b/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs @@ -40,9 +40,10 @@ namespace Ink_Canvas { } private void MainWindow_TouchDown(object sender, TouchEventArgs e) { - if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint - || inkCanvas.EditingMode == InkCanvasEditingMode.EraseByStroke - || inkCanvas.EditingMode == InkCanvasEditingMode.Select) return; + // 允许触摸在擦除、套索等模式下也能操作,不再直接 return + // if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint + // || inkCanvas.EditingMode == InkCanvasEditingMode.EraseByStroke + // || inkCanvas.EditingMode == InkCanvasEditingMode.Select) return; if (!isHidingSubPanelsWhenInking) { isHidingSubPanelsWhenInking = true; @@ -71,7 +72,6 @@ namespace Ink_Canvas { } private void MainWindow_StylusDown(object sender, StylusDownEventArgs e) { - inkCanvas.CaptureStylus(); ViewboxFloatingBar.IsHitTestVisible = false; BlackboardUIGridForInkReplay.IsHitTestVisible = false; @@ -94,9 +94,9 @@ namespace Ink_Canvas { System.Windows.Forms.Cursor.Show(); } + // 只在橡皮和线擦时 return,套索选区不 return if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint - || inkCanvas.EditingMode == InkCanvasEditingMode.EraseByStroke - || inkCanvas.EditingMode == InkCanvasEditingMode.Select) return; + || inkCanvas.EditingMode == InkCanvasEditingMode.EraseByStroke) TouchDownPointsList[e.StylusDevice.Id] = InkCanvasEditingMode.None; } @@ -305,6 +305,9 @@ namespace Ink_Canvas { private InkCanvasEditingMode lastInkCanvasEditingMode = InkCanvasEditingMode.Ink; private bool isSingleFingerDragMode = false; + // 记录手掌擦前的编辑模式 + private InkCanvasEditingMode prevEditingModeBeforePalmEraser = InkCanvasEditingMode.Ink; + private void inkCanvas_PreviewTouchDown(object sender, TouchEventArgs e) { inkCanvas.CaptureTouch(e.TouchDevice); @@ -312,7 +315,7 @@ namespace Ink_Canvas { BlackboardUIGridForInkReplay.IsHitTestVisible = false; dec.Add(e.TouchDevice.Id); - //设备1个的时候,记录中心点 + // 设备1个的时候,记录中心点 if (dec.Count == 1) { var touchPoint = e.GetTouchPoint(inkCanvas); centerPoint = touchPoint.Position; @@ -320,13 +323,31 @@ namespace Ink_Canvas { //记录第一根手指点击时的 StrokeCollection lastTouchDownStrokeCollection = inkCanvas.Strokes.Clone(); } - //设备两个及两个以上,将画笔功能关闭 - if (dec.Count > 1 || isSingleFingerDragMode || !Settings.Gesture.IsEnableTwoFingerGesture) { - if (isInMultiTouchMode || !Settings.Gesture.IsEnableTwoFingerGesture) return; - if (inkCanvas.EditingMode == InkCanvasEditingMode.None || - inkCanvas.EditingMode == InkCanvasEditingMode.Select) return; - lastInkCanvasEditingMode = inkCanvas.EditingMode; - inkCanvas.EditingMode = InkCanvasEditingMode.None; + + // 多指书写功能开启时禁用手掌擦 + if (Settings.Gesture.IsEnableTwoFingerGesture) { + // 关闭手掌擦逻辑 + if (dec.Count > 1) { + if (inkCanvas.EditingMode != InkCanvasEditingMode.None && inkCanvas.EditingMode != InkCanvasEditingMode.Select) { + lastInkCanvasEditingMode = inkCanvas.EditingMode; + inkCanvas.EditingMode = InkCanvasEditingMode.None; + } + } + return; + } + + // 3指及以上触控时触发手掌擦 + if (dec.Count >= 3) { + // 记录触发前的模式 + if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint) { + prevEditingModeBeforePalmEraser = inkCanvas.EditingMode; + } + // 切换为橡皮 + inkCanvas.EditingMode = InkCanvasEditingMode.EraseByPoint; + isLastTouchEraser = true; + // 可自定义橡皮形状 + currentPalmEraserShape = GetPalmRectangleEraserShape(); + inkCanvas.EraserShape = currentPalmEraserShape; } } @@ -335,30 +356,23 @@ namespace Ink_Canvas { ViewboxFloatingBar.IsHitTestVisible = true; BlackboardUIGridForInkReplay.IsHitTestVisible = true; - //手势完成后切回之前的状态 - if (dec.Count > 1) - if (inkCanvas.EditingMode == InkCanvasEditingMode.None) - inkCanvas.EditingMode = lastInkCanvasEditingMode; dec.Remove(e.TouchDevice.Id); inkCanvas.Opacity = 1; - - // 如果是手掌触发的面积擦抬起,需要确保橡皮擦形状被正确重置 - if (isLastTouchEraser && dec.Count == 0) { - isLastTouchEraser = false; - currentPalmEraserShape = null; // 清除保存的手掌擦形状 - if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint && forcePointEraser) { - // 重新应用当前设置的橡皮擦形状 - ApplyCurrentEraserShape(); + + // 没有触控输入后,自动恢复手掌擦前的功能 + if (dec.Count == 0) { + if (isLastTouchEraser) { + isLastTouchEraser = false; + currentPalmEraserShape = null; + inkCanvas.EditingMode = prevEditingModeBeforePalmEraser; } - } - - if (dec.Count == 0) if (lastTouchDownStrokeCollection.Count() != inkCanvas.Strokes.Count() && !(drawingShapeMode == 9 && !isFirstTouchCuboid)) { var whiteboardIndex = CurrentWhiteboardIndex; if (currentMode == 0) whiteboardIndex = 0; strokeCollections[whiteboardIndex] = lastTouchDownStrokeCollection; } + } } private void inkCanvas_ManipulationStarting(object sender, ManipulationStartingEventArgs e) { diff --git a/Ink Canvas/Resources/Settings.cs b/Ink Canvas/Resources/Settings.cs index 9513fe39..0ceed0c3 100644 --- a/Ink Canvas/Resources/Settings.cs +++ b/Ink Canvas/Resources/Settings.cs @@ -116,19 +116,14 @@ namespace Ink_Canvas public string AutoUpdateWithSilenceEndTime { get; set; } = "22:00"; [JsonProperty("updateChannel")] public UpdateChannel UpdateChannel { get; set; } = UpdateChannel.Release; - [JsonProperty("skippedVersion")] public string SkippedVersion { get; set; } = ""; - [JsonProperty("isEnableNibMode")] public bool IsEnableNibMode { get; set; } = false; - /* - [JsonProperty("isAutoHideCanvas")] - public bool IsAutoHideCanvas { get; set; } = true; - [JsonProperty("isAutoEnterModeFinger")] - public bool IsAutoEnterModeFinger { get; set; } = false;*/ [JsonProperty("isFoldAtStartup")] public bool IsFoldAtStartup { get; set; } = false; + [JsonProperty("crashAction")] + public int CrashAction { get; set; } = 0; } public class Appearance