Merge pull request #76 from InkCanvasForClass/beta

ICC CE Beta 1.7.0.4
This commit is contained in:
CJK_mkp
2025-07-06 16:24:32 +08:00
committed by GitHub
10 changed files with 348 additions and 393 deletions
+68 -11
View File
@@ -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);
+2 -2
View File
@@ -49,5 +49,5 @@ using System.Windows;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.7.0.3")]
[assembly: AssemblyFileVersion("1.7.0.3")]
[assembly: AssemblyVersion("1.7.0.4")]
[assembly: AssemblyFileVersion("1.7.0.4")]
+182 -320
View File
@@ -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<long> 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<UpdateChannel, List<UpdateLineGroup>> ChannelLineGroups = new Dictionary<UpdateChannel, List<UpdateLineGroup>>
{
{ UpdateChannel.Release, new List<UpdateLineGroup>
{
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<UpdateLineGroup>
{
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<UpdateLineGroup> 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<bool> FixVersion(UpdateChannel channel = UpdateChannel.Release)
// 使用指定线路组下载新版
public static async Task<bool> 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<string> GetUpdateLog(UpdateChannel channel = UpdateChannel.Release)
// 使用指定线路组获取更新日志
public static async Task<string> 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<string> GetRemoteContent(string fileUrl)
// 获取远程内容的通用方法public 以便线路组方法调用)
public static async Task<string> 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<string> 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<bool> 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
+4
View File
@@ -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();
}
// 添加一个辅助方法,根据当前编辑模式设置光标
+27 -22
View File
@@ -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);
@@ -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) {
+43 -29
View File
@@ -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) {
+2 -2
View File
@@ -49,5 +49,5 @@ using System.Windows;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.7.0.3")]
[assembly: AssemblyFileVersion("1.7.0.3")]
[assembly: AssemblyVersion("1.7.0.4")]
[assembly: AssemblyFileVersion("1.7.0.4")]
+2 -7
View File
@@ -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
+2
View File
@@ -69,6 +69,8 @@
>
> 请注意,本贡献指南由 Hydrogen @Hydro11451 )撰写,尚未受到官方认可,并且尚未完成。
请前往 InkCanvasForClass/dubious-notes
**请注意,在贡献代码时,_务必_ 将所有代码提交到 _beta_ 分支,以保证beta版本总是新于main版本。**
## TODO LIST