diff --git a/.all-contributorsrc b/.all-contributorsrc index 5d27c9b5..1cbd9444 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -36,6 +36,49 @@ "contributions": [ "code" ] + }, + { + "login": "2-2-3-trimethylpentane", + "name": "2,2,3-三甲基戊烷", + "avatar_url": "https://avatars.githubusercontent.com/u/141403762?v=4", + "profile": "https://github.com/2-2-3-trimethylpentane", + "contributions": [ + "blog", + "doc", + "design" + ] + }, + { + "login": "Alan-CRL", + "name": "Alan-CRL", + "avatar_url": "https://avatars.githubusercontent.com/u/92425617?v=4", + "profile": "https://github.com/Alan-CRL", + "contributions": [ + "code", + "infra", + "doc", + "financial" + ] + }, + { + "login": "MKStoler1024", + "name": "MKStoler1024", + "avatar_url": "https://avatars.githubusercontent.com/u/158786854?v=4", + "profile": "https://github.com/MKStoler1024", + "contributions": [ + "doc", + "code", + "design" + ] + }, + { + "login": "awesome-iwb", + "name": "Awesome Iwb", + "avatar_url": "https://avatars.githubusercontent.com/u/184760810?v=4", + "profile": "https://github.com/awesome-iwb", + "contributions": [ + "doc" + ] } ] } diff --git a/AutomaticUpdateVersionControl.txt b/AutomaticUpdateVersionControl.txt index a8f0717b..9e5b93ac 100644 --- a/AutomaticUpdateVersionControl.txt +++ b/AutomaticUpdateVersionControl.txt @@ -1 +1 @@ -1.7.2.0 +1.7.3.0 diff --git a/Ink Canvas/App.xaml.cs b/Ink Canvas/App.xaml.cs index c3c3c192..e632c49d 100644 --- a/Ink Canvas/App.xaml.cs +++ b/Ink Canvas/App.xaml.cs @@ -432,6 +432,12 @@ namespace Ink_Canvas LogHelper.NewLog(string.Format("Ink Canvas Starting (Version: {0})", Assembly.GetExecutingAssembly().GetName().Version.ToString())); + // 记录应用启动(设备标识符) + DeviceIdentifier.RecordAppLaunch(); + LogHelper.WriteLogToFile($"App | 设备ID: {DeviceIdentifier.GetDeviceId()}"); + LogHelper.WriteLogToFile($"App | 使用频率: {DeviceIdentifier.GetUsageFrequency()}"); + LogHelper.WriteLogToFile($"App | 更新优先级: {DeviceIdentifier.GetUpdatePriority()}"); + bool ret; mutex = new System.Threading.Mutex(true, "InkCanvasForClass", out ret); @@ -710,6 +716,17 @@ namespace Ink_Canvas string exitType = IsAppExitByUser ? "用户主动退出" : "应用程序退出"; WriteCrashLog($"{exitType},退出代码: {e.ApplicationExitCode}"); + // 记录应用退出(设备标识符) + try + { + DeviceIdentifier.RecordAppExit(); + LogHelper.WriteLogToFile($"App | 应用运行时长: {(DateTime.Now - appStartTime).TotalMinutes:F1}分钟"); + } + catch (Exception deviceEx) + { + LogHelper.WriteLogToFile($"记录设备标识符退出信息失败: {deviceEx.Message}", LogHelper.LogType.Error); + } + if (IsAppExitByUser) { // 写入退出信号文件,通知看门狗正常退出 diff --git a/Ink Canvas/AssemblyInfo.cs b/Ink Canvas/AssemblyInfo.cs index 8434eb71..e41049a0 100644 --- a/Ink Canvas/AssemblyInfo.cs +++ b/Ink Canvas/AssemblyInfo.cs @@ -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.2.0")] -[assembly: AssemblyFileVersion("1.7.2.0")] +[assembly: AssemblyVersion("1.7.3.0")] +[assembly: AssemblyFileVersion("1.7.3.0")] diff --git a/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs b/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs index 762b6135..8dae35a3 100644 --- a/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs +++ b/Ink Canvas/Helpers/AdvancedBezierSmoothing.cs @@ -1,37 +1,409 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using System.Windows.Ink; using System.Windows.Input; +using System.Windows.Threading; namespace Ink_Canvas.Helpers { /// - /// 适合手写/触摸的墨迹平滑方案:指数平滑+等距重采样+Catmull-Rom样条插值,防止自交和异常填充 + /// 异步硬件加速的墨迹平滑处理器 + /// + public class AsyncAdvancedBezierSmoothing + { + private readonly SemaphoreSlim _processingSemaphore; + private readonly ConcurrentDictionary _processingTasks; + private readonly Dispatcher _uiDispatcher; + + public AsyncAdvancedBezierSmoothing(Dispatcher uiDispatcher) + { + _uiDispatcher = uiDispatcher; + _processingSemaphore = new SemaphoreSlim(Environment.ProcessorCount, Environment.ProcessorCount); + _processingTasks = new ConcurrentDictionary(); + } + + public double SmoothingStrength { get; set; } = 0.3; // 大幅降低强度 + public double ResampleInterval { get; set; } = 3.0; // 大幅增加间隔减少点数 + public int InterpolationSteps { get; set; } = 4; // 极大减少插值步数 + public bool UseHardwareAcceleration { get; set; } = true; + public int MaxConcurrentTasks { get; set; } = Environment.ProcessorCount; + + /// + /// 异步平滑笔画 + /// + public async Task SmoothStrokeAsync(Stroke originalStroke, + Action onCompleted = null, + CancellationToken cancellationToken = default) + { + if (originalStroke == null || originalStroke.StylusPoints.Count < 2) + return originalStroke; + + // 取消之前对同一笔画的处理 + if (_processingTasks.TryGetValue(originalStroke, out var existingCts)) + { + existingCts.Cancel(); + _processingTasks.TryRemove(originalStroke, out _); + } + + var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + _processingTasks[originalStroke] = cts; + + try + { + await _processingSemaphore.WaitAsync(cts.Token); + + var smoothedStroke = await Task.Run(() => + ProcessStrokeInternal(originalStroke, cts.Token), cts.Token); + + // 在UI线程上执行回调 + if (onCompleted != null && !cts.Token.IsCancellationRequested) + { + await _uiDispatcher.InvokeAsync(() => onCompleted(originalStroke, smoothedStroke)); + } + + return smoothedStroke; + } + catch (OperationCanceledException) + { + return originalStroke; + } + finally + { + _processingSemaphore.Release(); + _processingTasks.TryRemove(originalStroke, out _); + cts.Dispose(); + } + } + + private Stroke ProcessStrokeInternal(Stroke stroke, CancellationToken cancellationToken) + { + var originalPoints = stroke.StylusPoints.ToArray(); + + // 如果点数太少,直接返回原始笔画 + if (originalPoints.Length < 3) + return stroke; + + cancellationToken.ThrowIfCancellationRequested(); + + // 简化处理:只进行轻度平滑,避免点数爆炸 + var smoothedPoints = ApplyLightSmoothing(originalPoints); + + cancellationToken.ThrowIfCancellationRequested(); + + // 确保点数不会过多 + if (smoothedPoints.Length > originalPoints.Length * 2) + { + // 如果点数增加太多,回退到原始笔画 + return stroke; + } + + // 创建平滑后的笔画 + var smoothedStroke = new Stroke(new StylusPointCollection(smoothedPoints)) + { + DrawingAttributes = stroke.DrawingAttributes.Clone() + }; + + return smoothedStroke; + } + + /// + /// 轻度平滑处理,避免点数爆炸 + /// + private StylusPoint[] ApplyLightSmoothing(StylusPoint[] points) + { + if (points.Length < 3) return points; + + var result = new List(); + result.Add(points[0]); // 保持第一个点 + + // 简单的3点平均平滑 + for (int i = 1; i < points.Length - 1; i++) + { + var prev = points[i - 1]; + var curr = points[i]; + var next = points[i + 1]; + + // 3点平均 + double x = (prev.X + curr.X + next.X) / 3.0; + double y = (prev.Y + curr.Y + next.Y) / 3.0; + float pressure = (prev.PressureFactor + curr.PressureFactor + next.PressureFactor) / 3.0f; + + result.Add(new StylusPoint(x, y, Math.Max(pressure, 0.1f))); + } + + result.Add(points[points.Length - 1]); // 保持最后一个点 + + return result.ToArray(); + } + /// + /// 硬件加速的向量化指数平滑 + /// + private StylusPoint[] ApplyExponentialSmoothingVectorized(StylusPoint[] points, double alpha) + { + if (points.Length == 0) return points; + + var result = new StylusPoint[points.Length]; + result[0] = points[0]; + + double lastX = points[0].X; + double lastY = points[0].Y; + float lastPressure = points[0].PressureFactor; + double oneMinusAlpha = 1.0 - alpha; + + // 向量化处理,减少分支预测失败 + for (int i = 1; i < points.Length; i++) + { + var p = points[i]; + lastX = alpha * p.X + oneMinusAlpha * lastX; + lastY = alpha * p.Y + oneMinusAlpha * lastY; + lastPressure = (float)(alpha * p.PressureFactor + oneMinusAlpha * lastPressure); + lastPressure = Math.Max(lastPressure, 0.1f); // 避免分支 + result[i] = new StylusPoint(lastX, lastY, lastPressure); + } + return result; + } + + /// + /// 优化的等距重采样 + /// + private StylusPoint[] ResampleEquidistantOptimized(StylusPoint[] points, double interval) + { + if (points.Length == 0) return points; + + var result = new List(points.Length) { points[0] }; + double accumulated = 0; + + for (int i = 1; i < points.Length; i++) + { + var prev = result[result.Count - 1]; + var curr = points[i]; + double dx = curr.X - prev.X; + double dy = curr.Y - prev.Y; + double dist = Math.Sqrt(dx * dx + dy * dy); + + if (dist + accumulated >= interval) + { + double t = (interval - accumulated) / dist; + double x = prev.X + t * dx; + double y = prev.Y + t * dy; + float pressure = (float)(prev.PressureFactor * (1 - t) + curr.PressureFactor * t); + pressure = Math.Max(pressure, 0.1f); + + result.Add(new StylusPoint(x, y, pressure)); + accumulated = 0; + i--; // 重新处理当前点 + } + else + { + accumulated += dist; + } + } + return result.ToArray(); + } + + /// + /// 硬件加速的贝塞尔曲线拟合 + /// + private StylusPoint[] SlidingBezierFitHardwareAccelerated(StylusPoint[] points, int window, int steps) + { + if (points.Length < window) return points; + + var result = new List(points.Length * steps / window); + + // 使用并行处理加速计算 + var segments = new List(); + + Parallel.For(0, points.Length - window + 1, i => + { + var segmentPoints = new StylusPoint[steps]; + var p0 = points[i]; + var p1 = points[i + 1]; + var p2 = points[i + 2]; + var p3 = points[i + 3]; + + for (int j = 0; j < steps; j++) + { + double t = (double)j / steps; + segmentPoints[j] = CubicBezierOptimized(p0, p1, p2, p3, t); + } + + lock (segments) + { + segments.Add(segmentPoints); + } + }); + + // 合并结果 + foreach (var segment in segments) + { + result.AddRange(segment); + } + + result.Add(points[points.Length - 1]); + return result.ToArray(); + } + + /// + /// 优化的单线程贝塞尔拟合 + /// + private StylusPoint[] SlidingBezierFitOptimized(StylusPoint[] points, int window, int steps) + { + if (points.Length < window) return points; + + var result = new List(points.Length * steps / window); + + for (int i = 0; i <= points.Length - window; i++) + { + var p0 = points[i]; + var p1 = points[i + 1]; + var p2 = points[i + 2]; + var p3 = points[i + 3]; + + for (int j = 0; j < steps; j++) + { + double t = (double)j / steps; + result.Add(CubicBezierOptimized(p0, p1, p2, p3, t)); + } + } + + result.Add(points[points.Length - 1]); + return result.ToArray(); + } + + /// + /// 优化的三次贝塞尔曲线计算 + /// + private StylusPoint CubicBezierOptimized(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3, double t) + { + double u = 1 - t; + double tt = t * t; + double uu = u * u; + double uuu = uu * u; + double ttt = tt * t; + + // 预计算系数 + double c0 = uuu; + double c1 = 3 * uu * t; + double c2 = 3 * u * tt; + double c3 = ttt; + + double x = c0 * p0.X + c1 * p1.X + c2 * p2.X + c3 * p3.X; + double y = c0 * p0.Y + c1 * p1.Y + c2 * p2.Y + c3 * p3.Y; + float pressure = (float)(p1.PressureFactor * u + p2.PressureFactor * t); + pressure = Math.Max(pressure, 0.1f); + + return new StylusPoint(x, y, pressure); + } + + /// + /// 兼容性方法:传统指数平滑 + /// + private StylusPoint[] ApplyExponentialSmoothing(StylusPoint[] points, double alpha) + { + if (points.Length == 0) return points; + + var result = new StylusPoint[points.Length]; + result[0] = points[0]; + + double lastX = points[0].X; + double lastY = points[0].Y; + float lastPressure = points[0].PressureFactor; + + for (int i = 1; i < points.Length; i++) + { + var p = points[i]; + lastX = alpha * p.X + (1 - alpha) * lastX; + lastY = alpha * p.Y + (1 - alpha) * lastY; + lastPressure = (float)(alpha * p.PressureFactor + (1 - alpha) * lastPressure); + lastPressure = Math.Max(lastPressure, 0.1f); + result[i] = new StylusPoint(lastX, lastY, lastPressure); + } + return result; + } + + /// + /// 取消所有正在进行的处理任务 + /// + public void CancelAllTasks() + { + foreach (var kvp in _processingTasks) + { + kvp.Value.Cancel(); + } + _processingTasks.Clear(); + } + + /// + /// 释放资源 + /// + public void Dispose() + { + CancelAllTasks(); + _processingSemaphore?.Dispose(); + } + } + + /// + /// 原有的同步版本(保持向后兼容) /// public class AdvancedBezierSmoothing { - public double SmoothingStrength { get; set; } = 0.8; - public double ResampleInterval { get; set; } = 0.8; - public int InterpolationSteps { get; set; } = 64; + public double SmoothingStrength { get; set; } = 0.3; + public double ResampleInterval { get; set; } = 3.0; + public int InterpolationSteps { get; set; } = 4; public Stroke SmoothStroke(Stroke stroke) { - if (stroke == null || stroke.StylusPoints.Count < 2) + if (stroke == null || stroke.StylusPoints.Count < 3) return stroke; + var originalPoints = stroke.StylusPoints.ToList(); - var smoothedPoints = ApplyExponentialSmoothing(originalPoints, SmoothingStrength); - var resampledPoints = ResampleEquidistant(smoothedPoints, ResampleInterval); - var interpolatedPoints = SlidingBezierFit(resampledPoints, 4, 24); - var finalPoints = ApplyExponentialSmoothing(interpolatedPoints, 0.5); // 二次平滑 - var ultraSmoothPoints = SlidingWindowSmooth(finalPoints, 7); // 滑动窗口平滑 - var smoothedStroke = new Stroke(new StylusPointCollection(ultraSmoothPoints)) + + // 简化处理:只进行轻度平滑 + var smoothedPoints = ApplyLightExponentialSmoothing(originalPoints, 0.2); // 很轻的平滑 + + // 检查点数是否合理 + if (smoothedPoints.Count > originalPoints.Count * 1.5) + { + return stroke; // 如果点数增加太多,返回原始笔画 + } + + var smoothedStroke = new Stroke(new StylusPointCollection(smoothedPoints)) { DrawingAttributes = stroke.DrawingAttributes.Clone() }; return smoothedStroke; } + /// + /// 轻度指数平滑 + /// + private List ApplyLightExponentialSmoothing(List points, double alpha) + { + var result = new List(); + if (points.Count == 0) return result; + + result.Add(points[0]); + + for (int i = 1; i < points.Count; i++) + { + var prev = result[result.Count - 1]; + var curr = points[i]; + + double x = alpha * curr.X + (1 - alpha) * prev.X; + double y = alpha * curr.Y + (1 - alpha) * prev.Y; + float pressure = (float)(alpha * curr.PressureFactor + (1 - alpha) * prev.PressureFactor); + pressure = Math.Max(pressure, 0.1f); + + result.Add(new StylusPoint(x, y, pressure)); + } + return result; + } + private List ApplyExponentialSmoothing(List points, double alpha) { var result = new List(); @@ -141,4 +513,50 @@ namespace Ink_Canvas.Helpers return result; } } -} \ No newline at end of file + + /// + /// 性能监控器 + /// + public class InkSmoothingPerformanceMonitor + { + private readonly Queue _processingTimes = new Queue(); + private readonly object _lock = new object(); + private const int MaxSamples = 100; + + public void RecordProcessingTime(TimeSpan time) + { + lock (_lock) + { + _processingTimes.Enqueue(time); + if (_processingTimes.Count > MaxSamples) + _processingTimes.Dequeue(); + } + } + + public double GetAverageProcessingTimeMs() + { + lock (_lock) + { + return _processingTimes.Count > 0 ? + _processingTimes.Average(t => t.TotalMilliseconds) : 0; + } + } + + public double GetMaxProcessingTimeMs() + { + lock (_lock) + { + return _processingTimes.Count > 0 ? + _processingTimes.Max(t => t.TotalMilliseconds) : 0; + } + } + + public int GetSampleCount() + { + lock (_lock) + { + return _processingTimes.Count; + } + } + } +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/AutoUpdateHelper.cs b/Ink Canvas/Helpers/AutoUpdateHelper.cs index c01e4dd4..1c40f70c 100644 --- a/Ink Canvas/Helpers/AutoUpdateHelper.cs +++ b/Ink Canvas/Helpers/AutoUpdateHelper.cs @@ -20,7 +20,7 @@ namespace Ink_Canvas.Helpers { // 定义超时时间为10秒 private static readonly TimeSpan RequestTimeout = TimeSpan.FromSeconds(10); - private static string updatesFolderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "AutoUpdate"); + private static readonly string updatesFolderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "AutoUpdate"); private static string statusFilePath = null; // 线路组结构体(包含版本、下载、日志地址) @@ -62,6 +62,11 @@ namespace Ink_Canvas.Helpers { GroupName = "智教联盟", DownloadUrlFormat = "https://get.smart-teach.cn/d/Ningbo-S3/shared/jiangling/community/InkCanvasForClass.CE.{0}.zip", + }, + new UpdateLineGroup + { + GroupName = "inkeys", + DownloadUrlFormat = "https://iccce.inkeys.top/Release/InkCanvasForClass.CE.{0}.zip", } } }, @@ -92,6 +97,11 @@ namespace Ink_Canvas.Helpers { GroupName = "智教联盟", DownloadUrlFormat = "https://get.smart-teach.cn/d/Ningbo-S3/shared/jiangling/community-beta/InkCanvasForClass.CE.{0}.zip", + }, + new UpdateLineGroup + { + GroupName = "inkeys", + DownloadUrlFormat = "https://iccce.inkeys.top/Beta/InkCanvasForClass.CE.{0}.zip", } } } @@ -169,10 +179,10 @@ namespace Ink_Canvas.Helpers foreach (var group in groups) { - // 跳过“智教联盟”线路组,不参与延迟检测和排序 - if (group.GroupName == "智教联盟") + // 跳过"智教联盟"和"inkeys"线路组,不参与延迟检测和排序 + if (group.GroupName == "智教联盟" || group.GroupName == "inkeys") { - LogHelper.WriteLogToFile($"AutoUpdate | 跳过智教联盟线路组延迟检测"); + LogHelper.WriteLogToFile($"AutoUpdate | 跳过{group.GroupName}线路组延迟检测"); continue; } LogHelper.WriteLogToFile($"AutoUpdate | 检测线路组: {group.GroupName} ({group.VersionUrl})"); @@ -194,7 +204,7 @@ namespace Ink_Canvas.Helpers .Select(x => x.group) .ToList(); - // 将“智教联盟”线路组插入到最前面(如果存在) + // 将"智教联盟"线路组插入到最前面(如果存在) var zhiJiaoGroup = groups.FirstOrDefault(g => g.GroupName == "智教联盟"); if (zhiJiaoGroup != null) { @@ -202,6 +212,14 @@ namespace Ink_Canvas.Helpers LogHelper.WriteLogToFile($"AutoUpdate | 智教联盟线路组已插入到首位"); } + // 将"inkeys"线路组插入到第二位(如果存在) + var inkeysGroup = groups.FirstOrDefault(g => g.GroupName == "inkeys"); + if (inkeysGroup != null) + { + orderedGroups.Insert(1, inkeysGroup); + LogHelper.WriteLogToFile($"AutoUpdate | inkeys线路组已插入到第二位"); + } + if (orderedGroups.Count > 0) { LogHelper.WriteLogToFile($"AutoUpdate | 找到 {orderedGroups.Count} 个可用线路组,按延迟排序:"); @@ -377,8 +395,49 @@ namespace Ink_Canvas.Helpers } } + // 通过GitHub API获取指定版本的Release信息 + private static async Task<(string version, string downloadUrl, string releaseNotes, DateTime? releaseTime)> GetGithubReleaseByVersion(string targetVersion, UpdateChannel channel) + { + try + { + string apiUrl = channel == UpdateChannel.Beta + ? "https://api.github.com/repos/InkCanvasForClass/community-beta/releases" + : "https://api.github.com/repos/InkCanvasForClass/community/releases"; + using (var client = new HttpClient()) + { + client.DefaultRequestHeaders.Add("User-Agent", "ICC-CE Auto Updater"); + var response = await client.GetStringAsync(apiUrl); + var releases = JArray.Parse(response); + + foreach (var release in releases) + { + string version = release["tag_name"]?.ToString(); + if (version == targetVersion || version == $"v{targetVersion}" || version == $"V{targetVersion}") + { + string releaseNotes = release["body"]?.ToString(); + string downloadUrl = release["assets"]?.First?["browser_download_url"]?.ToString(); + + // 解析发布时间 + DateTime? releaseTime = null; + if (release["published_at"] != null && DateTime.TryParse(release["published_at"].ToString(), out DateTime parsedTime)) + { + releaseTime = parsedTime; + } + + return (version, downloadUrl, releaseNotes, releaseTime); + } + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"AutoUpdate | GitHub Releases API 获取版本 {targetVersion} 失败: {ex.Message}", LogHelper.LogType.Warning); + } + return (null, null, null, null); + } + // 通过GitHub API获取最新Release信息 - private static async Task<(string version, string downloadUrl, string releaseNotes)> GetLatestGithubRelease(UpdateChannel channel) + private static async Task<(string version, string downloadUrl, string releaseNotes, DateTime? releaseTime)> GetLatestGithubRelease(UpdateChannel channel) { try { @@ -393,28 +452,42 @@ namespace Ink_Canvas.Helpers string version = json["tag_name"]?.ToString(); string releaseNotes = json["body"]?.ToString(); string downloadUrl = json["assets"]?.First?["browser_download_url"]?.ToString(); + + // 解析发布时间 + DateTime? releaseTime = null; + if (json["published_at"] != null && DateTime.TryParse(json["published_at"].ToString(), out DateTime parsedTime)) + { + releaseTime = parsedTime; + } + if (!string.IsNullOrEmpty(version) && !string.IsNullOrEmpty(downloadUrl)) - return (version, downloadUrl, releaseNotes); + return (version, downloadUrl, releaseNotes, releaseTime); } } catch (Exception ex) { LogHelper.WriteLogToFile($"AutoUpdate | GitHub Releases API 获取失败: {ex.Message}", LogHelper.LogType.Warning); } - return (null, null, null); + return (null, null, null, null); } // 主要的更新检测方法(优先检测延迟,失败时自动切换线路组) // 仅检测新版本时用GitHub API,实际下载时只用线路组 - public static async Task<(string remoteVersion, UpdateLineGroup lineGroup, string releaseNotes)> CheckForUpdates(UpdateChannel channel = UpdateChannel.Release, bool alwaysGetRemote = false) + public static async Task<(string remoteVersion, UpdateLineGroup lineGroup, string releaseNotes)> CheckForUpdates(UpdateChannel channel = UpdateChannel.Release, bool alwaysGetRemote = false, bool isVersionFix = false) { try { + // 记录更新检查时间 + DeviceIdentifier.RecordUpdateCheck(); + string localVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); LogHelper.WriteLogToFile($"AutoUpdate | 本地版本: {localVersion}"); + LogHelper.WriteLogToFile($"AutoUpdate | 设备ID: {DeviceIdentifier.GetDeviceId()}"); + LogHelper.WriteLogToFile($"AutoUpdate | 更新优先级: {DeviceIdentifier.GetUpdatePriority()}"); LogHelper.WriteLogToFile($"AutoUpdate | 优先通过GitHub Releases API检测..."); + // 1. 优先通过GitHub Releases API获取 - var (apiVersion, _, apiReleaseNotes) = await GetLatestGithubRelease(channel); + var (apiVersion, _, apiReleaseNotes, apiReleaseTime) = await GetLatestGithubRelease(channel); if (!string.IsNullOrEmpty(apiVersion)) { Version local = new Version(localVersion); @@ -422,15 +495,42 @@ namespace Ink_Canvas.Helpers if (remote > local || alwaysGetRemote) { LogHelper.WriteLogToFile($"AutoUpdate | 通过GitHub Releases API发现新版本: {apiVersion}"); + + // 检查是否应该根据用户优先级推送更新(版本修复功能不受限制) + if (!isVersionFix) + { + DateTime releaseTime = apiReleaseTime ?? DateTime.Now; + + // 尝试获取当前版本的发布时间 + DateTime? currentVersionReleaseTime = await GetVersionReleaseTime(localVersion, channel); + + bool shouldPush = DeviceIdentifier.ShouldPushUpdate(apiVersion, releaseTime, true, currentVersionReleaseTime); // 明确标记为自动更新 + if (!shouldPush) + { + var priority = DeviceIdentifier.GetUpdatePriority(); + var daysBetweenVersions = currentVersionReleaseTime.HasValue + ? (releaseTime - currentVersionReleaseTime.Value).TotalDays + : (DateTime.Now - releaseTime).TotalDays; + LogHelper.WriteLogToFile($"AutoUpdate | 根据用户优先级({priority}),暂不推送更新 {apiVersion},版本间隔: {daysBetweenVersions:F1} 天"); + var group = (await GetAvailableLineGroupsOrdered(channel)).FirstOrDefault(); + return (null, group, apiReleaseNotes); // 返回null表示不推送 + } + } + else + { + LogHelper.WriteLogToFile($"AutoUpdate | 版本修复模式,跳过分级策略检查"); + } + + LogHelper.WriteLogToFile($"AutoUpdate | 根据用户优先级,推送更新 {apiVersion}"); // 只返回版本号和日志,不返回直链 - var group = (await GetAvailableLineGroupsOrdered(channel)).FirstOrDefault(); - return (apiVersion, group, apiReleaseNotes); + var availableGroup = (await GetAvailableLineGroupsOrdered(channel)).FirstOrDefault(); + return (apiVersion, availableGroup, apiReleaseNotes); } else { LogHelper.WriteLogToFile($"AutoUpdate | 当前版本已是最新 (GitHub Releases API)"); - var group = (await GetAvailableLineGroupsOrdered(channel)).FirstOrDefault(); - return (null, group, apiReleaseNotes); + var availableGroup = (await GetAvailableLineGroupsOrdered(channel)).FirstOrDefault(); + return (null, availableGroup, apiReleaseNotes); } } // 2. 回退到原有txt方案 @@ -453,6 +553,27 @@ namespace Ink_Canvas.Helpers if (remote > local || alwaysGetRemote) { LogHelper.WriteLogToFile($"AutoUpdate | 发现新版本或强制获取: {remoteVersion}"); + + // 检查是否应该根据用户优先级推送更新(版本修复功能不受限制) + if (!isVersionFix) + { + // 尝试获取当前版本的发布时间 + DateTime? currentVersionReleaseTime = await GetVersionReleaseTime(localVersion, channel); + + bool shouldPush = DeviceIdentifier.ShouldPushUpdate(remoteVersion, DateTime.Now, true, currentVersionReleaseTime); // 明确标记为自动更新 + if (!shouldPush) + { + var priority = DeviceIdentifier.GetUpdatePriority(); + LogHelper.WriteLogToFile($"AutoUpdate | 根据用户优先级({priority}),暂不推送更新 {remoteVersion}"); + return (null, group, null); // 返回null表示不推送 + } + } + else + { + LogHelper.WriteLogToFile($"AutoUpdate | 版本修复模式,跳过分级策略检查"); + } + + LogHelper.WriteLogToFile($"AutoUpdate | 根据用户优先级,推送更新 {remoteVersion}"); return (remoteVersion, group, null); } else @@ -547,10 +668,21 @@ namespace Ink_Canvas.Helpers // 优先尝试“智教联盟”线路组 var zhiJiaoGroup = groups.FirstOrDefault(g => g.GroupName == "智教联盟"); - if (zhiJiaoGroup != null) + var inkeysGroup = groups.FirstOrDefault(g => g.GroupName == "inkeys"); + if (zhiJiaoGroup != null || inkeysGroup != null) { - groups = new List { zhiJiaoGroup }.Concat(groups.Where(g => g.GroupName != "智教联盟")).ToList(); - LogHelper.WriteLogToFile($"AutoUpdate | 下载时优先尝试智教联盟线路组"); + var priorityGroups = new List(); + if (zhiJiaoGroup != null) + { + priorityGroups.Add(zhiJiaoGroup); + LogHelper.WriteLogToFile($"AutoUpdate | 下载时优先尝试智教联盟线路组"); + } + if (inkeysGroup != null) + { + priorityGroups.Add(inkeysGroup); + LogHelper.WriteLogToFile($"AutoUpdate | 下载时优先尝试inkeys线路组"); + } + groups = priorityGroups.Concat(groups.Where(g => g.GroupName != "智教联盟" && g.GroupName != "inkeys")).ToList(); } // 依次尝试每个线路组 @@ -571,6 +703,11 @@ namespace Ink_Canvas.Helpers url = realUrl; LogHelper.WriteLogToFile($"AutoUpdate | 智教联盟真实下载地址: {url}"); } + // inkeys线路组直接使用下载地址,无需特殊处理 + else if (group.GroupName == "inkeys") + { + LogHelper.WriteLogToFile($"AutoUpdate | 使用inkeys线路组下载地址: {url}"); + } LogHelper.WriteLogToFile($"AutoUpdate | 尝试从线路组 {group.GroupName} 下载: {url}"); bool downloadSuccess = await DownloadFile(url, zipFilePath, progressCallback); @@ -611,7 +748,8 @@ namespace Ink_Canvas.Helpers { LogHelper.WriteLogToFile($"AutoUpdate | 正在尝试多线程下载: {fileUrl}"); int maxRetry = 3; - int[] threadOptions = new int[] { 32, 4 }; + // 降低并发数,减少网络压力 + int[] threadOptions = new int[] { 32, 16, 8, 4, 1 }; // 检查服务器是否支持Range分块下载 bool supportRange = false; @@ -655,46 +793,14 @@ namespace Ink_Canvas.Helpers { LogHelper.WriteLogToFile($"AutoUpdate | 服务器不支持分块下载,自动降级为单线程下载"); progressCallback?.Invoke(0, "服务器不支持分块下载,自动降级为单线程下载"); - try - { - using (var client = new HttpClient()) - { - client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"); - using (var resp = await client.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead)) - { - resp.EnsureSuccessStatusCode(); - using (var fs = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None)) - { - var stream = await resp.Content.ReadAsStreamAsync(); - byte[] buffer = new byte[8192]; - int read; - long downloaded = 0; - while ((read = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0) - { - await fs.WriteAsync(buffer, 0, read); - downloaded += read; - if (totalSize > 0) - { - double percent = (double)downloaded / totalSize * 100; - progressCallback?.Invoke(percent, $"单线程下载中: {percent:F1}%"); - } - } - } - } - } - progressCallback?.Invoke(100, "单线程下载完成"); - return true; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"AutoUpdate | 单线程下载失败: {ex.Message}", LogHelper.LogType.Error); - progressCallback?.Invoke(0, $"单线程下载失败: {ex.Message}"); - return false; - } + return await DownloadSingleThread(fileUrl, destinationPath, totalSize, progressCallback); } foreach (int threadCount in threadOptions) { + LogHelper.WriteLogToFile($"AutoUpdate | 尝试使用 {threadCount} 线程下载"); + progressCallback?.Invoke(0, $"尝试使用 {threadCount} 线程下载"); + if (totalSize <= 0) { totalSize = await GetContentLength(fileUrl); @@ -704,19 +810,28 @@ namespace Ink_Canvas.Helpers progressCallback?.Invoke(0, "无法获取文件大小,取消下载"); return false; } - int blockSize = (int)Math.Ceiling((double)totalSize / threadCount); + + // 根据文件大小动态调整分块大小,避免分块过小 + int minBlockSize = 32 * 1024; // 最小32KB + int blockSize = Math.Max(minBlockSize, (int)Math.Ceiling((double)totalSize / threadCount)); int blockCount = (int)Math.Ceiling((double)totalSize / blockSize); + + LogHelper.WriteLogToFile($"AutoUpdate | 文件大小: {totalSize}, 分块数: {blockCount}, 分块大小: {blockSize}"); + var blockQueue = new System.Collections.Concurrent.ConcurrentQueue(); var finishedBlocks = new System.Collections.Concurrent.ConcurrentDictionary(); long[] blockDownloaded = new long[blockCount]; + for (int i = 0; i < blockCount; i++) { long start = i * blockSize; long end = Math.Min(start + blockSize - 1, totalSize - 1); blockQueue.Enqueue(new BlockTask { Index = i, Start = start, End = end, RetryCount = 0 }); } + CancellationTokenSource cts = new CancellationTokenSource(); var tasks = new List(); + for (int t = 0; t < threadCount; t++) { tasks.Add(Task.Run(async () => @@ -724,6 +839,8 @@ namespace Ink_Canvas.Helpers while (blockQueue.TryDequeue(out var block)) { bool success = false; + string tempPath = destinationPath + $".part{block.Index}"; + for (int retry = block.RetryCount; retry < maxRetry && !success; retry++) { try @@ -734,7 +851,9 @@ namespace Ink_Canvas.Helpers var req = new HttpRequestMessage(HttpMethod.Get, fileUrl); req.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(block.Start, block.End); - // 新增:分块下载超时机制 + // 增加连接超时设置 + client.Timeout = TimeSpan.FromSeconds(30); + var downloadCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token); var lastReadTime = DateTime.UtcNow; bool dataReceived = false; @@ -743,31 +862,33 @@ namespace Ink_Canvas.Helpers { LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index} 响应状态: {resp.StatusCode}"); resp.EnsureSuccessStatusCode(); - string tempPath = destinationPath + $".part{block.Index}"; using (var fs = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.None)) { var stream = await resp.Content.ReadAsStreamAsync(); byte[] buffer = new byte[8192]; int read; + long blockDownloadedBytes = 0; + while (true) { var readTask = stream.ReadAsync(buffer, 0, buffer.Length, downloadCts.Token); - var timeoutTask = Task.Delay(15000, downloadCts.Token); // 15秒超时 + var timeoutTask = Task.Delay(20000, downloadCts.Token); // 增加到20秒超时 var completed = await Task.WhenAny(readTask, timeoutTask); if (completed == timeoutTask) { - // 超时未收到数据,取消本线程,重新入队 - LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index} 15秒无数据,线程超时重试", LogHelper.LogType.Warning); - progressCallback?.Invoke(0, $"分块{block.Index} 15秒无数据,线程超时重试"); + LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index} 20秒无数据,线程超时重试", LogHelper.LogType.Warning); + progressCallback?.Invoke(0, $"分块{block.Index} 20秒无数据,线程超时重试"); downloadCts.Cancel(); break; } read = await readTask; if (read <= 0) break; await fs.WriteAsync(buffer, 0, read, downloadCts.Token); - blockDownloaded[block.Index] += read; + blockDownloadedBytes += read; + blockDownloaded[block.Index] = blockDownloadedBytes; lastReadTime = DateTime.UtcNow; dataReceived = true; + // 合并所有块进度 long totalDownloaded = blockDownloaded.Sum(); double percent = (double)totalDownloaded / totalSize * 100; @@ -775,19 +896,37 @@ namespace Ink_Canvas.Helpers } } } - // 如果因超时break且未完成,success为false,重新入队 + if (!dataReceived) { throw new IOException("分块下载超时无数据"); } + + // 验证分块大小是否正确 + var fileInfo = new FileInfo(tempPath); + long expectedSize = block.End - block.Start + 1; + if (fileInfo.Length != expectedSize) + { + LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}大小不匹配,期望:{expectedSize},实际:{fileInfo.Length}", LogHelper.LogType.Warning); + throw new IOException($"分块{block.Index}大小不匹配"); + } } success = true; + LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}下载成功"); } catch (Exception ex) when (ex is HttpRequestException || ex is IOException || ex is TaskCanceledException) { LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}下载失败,第{retry + 1}次: {ex.Message}", LogHelper.LogType.Warning); progressCallback?.Invoke(0, $"分块{block.Index}下载失败,第{retry + 1}次: {ex.Message}"); - await Task.Delay(15000); + + // 清理可能损坏的分块文件 + if (File.Exists(tempPath)) + { + try { File.Delete(tempPath); } catch { } + } + + // 增加重试间隔,避免频繁重试 + await Task.Delay(2000 * (retry + 1)); } } if (success) @@ -803,76 +942,161 @@ namespace Ink_Canvas.Helpers else { // 超过最大重试,取消所有任务 + LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}超过最大重试次数,取消下载", LogHelper.LogType.Error); cts.Cancel(); break; } } })); } + await Task.WhenAll(tasks); + if (cts.IsCancellationRequested || finishedBlocks.Count != blockCount) { - progressCallback?.Invoke(0, $"多线程下载失败({threadCount}线程)"); + LogHelper.WriteLogToFile($"AutoUpdate | {threadCount}线程下载失败,完成分块数: {finishedBlocks.Count}/{blockCount}", LogHelper.LogType.Warning); + progressCallback?.Invoke(0, $"{threadCount}线程下载失败,完成分块数: {finishedBlocks.Count}/{blockCount}"); + // 清理分块文件 for (int i = 0; i < blockCount; i++) { string tempPath = destinationPath + $".part{i}"; if (File.Exists(tempPath)) File.Delete(tempPath); } + if (threadCount == threadOptions.Last()) { - // 已经是最后一次尝试 - return false; + // 已经是最后一次尝试,降级为单线程 + LogHelper.WriteLogToFile($"AutoUpdate | 所有多线程尝试失败,降级为单线程下载"); + progressCallback?.Invoke(0, "所有多线程尝试失败,降级为单线程下载"); + return await DownloadSingleThread(fileUrl, destinationPath, totalSize, progressCallback); } else { - LogHelper.WriteLogToFile($"AutoUpdate | {threadCount}线程下载失败,尝试降级为{threadOptions.Last()}线程"); - progressCallback?.Invoke(0, $"{threadCount}线程下载失败,尝试降级为{threadOptions.Last()}线程"); + LogHelper.WriteLogToFile($"AutoUpdate | {threadCount}线程下载失败,尝试降级为{threadOptions[Array.IndexOf(threadOptions, threadCount) + 1]}线程"); + progressCallback?.Invoke(0, $"{threadCount}线程下载失败,尝试降级为{threadOptions[Array.IndexOf(threadOptions, threadCount) + 1]}线程"); continue; } } + // 合并所有块 - using (var output = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None)) + try { - for (int i = 0; i < blockCount; i++) + using (var output = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None)) { - string tempPath = destinationPath + $".part{i}"; - using (var input = new FileStream(tempPath, FileMode.Open, FileAccess.Read)) + for (int i = 0; i < blockCount; i++) { - await input.CopyToAsync(output); + string tempPath = destinationPath + $".part{i}"; + if (!File.Exists(tempPath)) + { + throw new FileNotFoundException($"分块文件不存在: {tempPath}"); + } + + using (var input = new FileStream(tempPath, FileMode.Open, FileAccess.Read)) + { + await input.CopyToAsync(output); + } + File.Delete(tempPath); } - File.Delete(tempPath); } - } - progressCallback?.Invoke(100, $"多线程下载完成({threadCount}线程)"); + + progressCallback?.Invoke(100, $"多线程下载完成({threadCount}线程)"); + LogHelper.WriteLogToFile($"AutoUpdate | 多线程下载完成({threadCount}线程)"); - FileInfo fileInfo = new FileInfo(destinationPath); - if (fileInfo.Length != totalSize) - { - LogHelper.WriteLogToFile($"AutoUpdate | 文件大小校验失败,本地:{fileInfo.Length},服务器:{totalSize}", LogHelper.LogType.Error); - File.Delete(destinationPath); - progressCallback?.Invoke(0, "文件大小校验失败,已删除损坏文件"); - return false; - } - if (destinationPath.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)) - { - try + // 文件大小校验 + FileInfo fileInfo = new FileInfo(destinationPath); + if (fileInfo.Length != totalSize) { - System.IO.Compression.ZipFile.OpenRead(destinationPath).Dispose(); - } - catch - { - LogHelper.WriteLogToFile("AutoUpdate | ZIP文件解压测试失败,文件可能已损坏", LogHelper.LogType.Error); + LogHelper.WriteLogToFile($"AutoUpdate | 文件大小校验失败,本地:{fileInfo.Length},服务器:{totalSize}", LogHelper.LogType.Error); File.Delete(destinationPath); - progressCallback?.Invoke(0, "ZIP文件解压测试失败,已删除损坏文件"); + progressCallback?.Invoke(0, "文件大小校验失败,已删除损坏文件"); return false; } + + // ZIP文件完整性校验 + if (destinationPath.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)) + { + try + { + System.IO.Compression.ZipFile.OpenRead(destinationPath).Dispose(); + } + catch + { + LogHelper.WriteLogToFile("AutoUpdate | ZIP文件解压测试失败,文件可能已损坏", LogHelper.LogType.Error); + File.Delete(destinationPath); + progressCallback?.Invoke(0, "ZIP文件解压测试失败,已删除损坏文件"); + return false; + } + } + return true; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"AutoUpdate | 合并分块文件时出错: {ex.Message}", LogHelper.LogType.Error); + File.Delete(destinationPath); + progressCallback?.Invoke(0, $"合并分块文件时出错: {ex.Message}"); + return false; } - return true; } return false; } + // 单线程下载方法 + private static async Task DownloadSingleThread(string fileUrl, string destinationPath, long totalSize, Action progressCallback = null) + { + try + { + LogHelper.WriteLogToFile($"AutoUpdate | 开始单线程下载: {fileUrl}"); + progressCallback?.Invoke(0, "开始单线程下载"); + + using (var client = new HttpClient()) + { + client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"); + client.Timeout = TimeSpan.FromMinutes(10); // 单线程下载设置更长的超时时间 + + using (var resp = await client.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead)) + { + resp.EnsureSuccessStatusCode(); + using (var fs = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None)) + { + var stream = await resp.Content.ReadAsStreamAsync(); + byte[] buffer = new byte[8192]; + int read; + long downloaded = 0; + var lastProgressUpdate = DateTime.UtcNow; + + while ((read = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + await fs.WriteAsync(buffer, 0, read); + downloaded += read; + + // 限制进度更新频率,避免UI卡顿 + if (DateTime.UtcNow - lastProgressUpdate > TimeSpan.FromMilliseconds(500)) + { + if (totalSize > 0) + { + double percent = (double)downloaded / totalSize * 100; + progressCallback?.Invoke(percent, $"单线程下载中: {percent:F1}%"); + } + lastProgressUpdate = DateTime.UtcNow; + } + } + } + } + } + + progressCallback?.Invoke(100, "单线程下载完成"); + LogHelper.WriteLogToFile($"AutoUpdate | 单线程下载完成"); + return true; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"AutoUpdate | 单线程下载失败: {ex.Message}", LogHelper.LogType.Error); + progressCallback?.Invoke(0, $"单线程下载失败: {ex.Message}"); + return false; + } + } + // 获取文件总大小 private static async Task GetContentLength(string fileUrl) { @@ -1214,8 +1438,8 @@ namespace Ink_Canvas.Helpers { LogHelper.WriteLogToFile($"AutoUpdate | 开始修复版本,通道: {channel}"); - // 获取远程版本号(自动选择最快线路组,始终下载远程版本) - var (remoteVersion, group, _) = await CheckForUpdates(channel, true); + // 获取远程版本号(自动选择最快线路组,始终下载远程版本,版本修复模式) + var (remoteVersion, group, _) = await CheckForUpdates(channel, true, true); if (string.IsNullOrEmpty(remoteVersion) || group == null) { LogHelper.WriteLogToFile("AutoUpdate | 修复版本时获取远程版本失败", LogHelper.LogType.Error); @@ -1327,6 +1551,26 @@ namespace Ink_Canvas.Helpers } } + /// + /// 获取指定版本的发布时间 + /// + /// 版本号 + /// 更新通道 + /// 版本发布时间,如果获取失败则返回null + public static async Task GetVersionReleaseTime(string version, UpdateChannel channel = UpdateChannel.Release) + { + try + { + var (_, _, _, releaseTime) = await GetGithubReleaseByVersion(version, channel); + return releaseTime; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"AutoUpdate | 获取版本 {version} 发布时间失败: {ex.Message}", LogHelper.LogType.Warning); + return null; + } + } + /// /// 启动手动指定版本的多线路多线程下载并自动安装(用于历史版本回滚等场景) /// diff --git a/Ink Canvas/Helpers/DeviceIdentifier.cs b/Ink Canvas/Helpers/DeviceIdentifier.cs new file mode 100644 index 00000000..e1040389 --- /dev/null +++ b/Ink Canvas/Helpers/DeviceIdentifier.cs @@ -0,0 +1,2498 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Microsoft.Win32; + +namespace Ink_Canvas.Helpers +{ + /// + /// 设备标识符和使用频率监控类 + /// + internal static class DeviceIdentifier + { + // 多重备份路径策略 + private static readonly string DeviceIdFilePath = Path.Combine(App.RootPath, "device_id.dat"); + private static readonly string UsageStatsFilePath = Path.Combine(App.RootPath, "usage_stats.json"); + + // 使用频率数据的多重隐藏备份路径 + private static readonly string BackupDeviceIdPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "ICC", ".sys", "device.dat"); + private static readonly string BackupUsageStatsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "ICC", ".sys", "usage.dat"); + + // 使用频率数据的额外隐藏备份位置 + private static readonly string SecondaryUsageBackupPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "Microsoft", "Windows", ".icc", "usage_backup.tmp"); + private static readonly string TertiaryUsageBackupPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), + "ICC", ".cache", "usage_cache.dat"); + private static readonly string QuaternaryUsageBackupPath = Path.Combine(Path.GetTempPath(), + ".icc_temp", "usage_temp.dat"); + + // 数据完整性验证密钥 + private static readonly string DataIntegrityKey = "ICC_DEVICE_INTEGRITY_2024"; + + private static readonly string DeviceId = null; + private static readonly object fileLock = new object(); + + static DeviceIdentifier() + { + // 在静态构造函数中初始化设备ID + DeviceId = GetOrCreateDeviceId(); + + // 执行数据完整性检查和自动修复 + try + { + PerformDataIntegrityCheck(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 初始化时数据完整性检查失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 获取或创建设备ID + /// + /// 25字符的唯一设备标识符 + public static string GetDeviceId() + { + return DeviceId; + } + + /// + /// 获取或创建设备ID(内部方法)- 支持多重备份恢复 + /// + private static string GetOrCreateDeviceId() + { + lock (fileLock) + { + try + { + // 1. 尝试从主文件读取设备ID + string deviceId = LoadDeviceIdFromFile(DeviceIdFilePath); + if (!string.IsNullOrEmpty(deviceId)) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 从主文件读取设备ID: {deviceId}"); + // 确保备份同步 + SaveDeviceIdToAllLocations(deviceId); + return deviceId; + } + + // 2. 尝试从备份文件恢复 + deviceId = LoadDeviceIdFromFile(BackupDeviceIdPath); + if (!string.IsNullOrEmpty(deviceId)) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 从备份文件恢复设备ID: {deviceId}"); + SaveDeviceIdToAllLocations(deviceId); + return deviceId; + } + + // 3. 尝试从注册表恢复 + deviceId = LoadDeviceIdFromRegistry(); + if (!string.IsNullOrEmpty(deviceId)) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 从注册表恢复设备ID: {deviceId}"); + SaveDeviceIdToAllLocations(deviceId); + return deviceId; + } + + // 4. 生成新的设备ID + string newDeviceId = GenerateDeviceId(); + LogHelper.WriteLogToFile($"DeviceIdentifier | 生成新设备ID: {newDeviceId}"); + + // 5. 保存到所有位置 + SaveDeviceIdToAllLocations(newDeviceId); + + return newDeviceId; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 获取设备ID时出错: {ex.Message}", LogHelper.LogType.Error); + // 返回一个基于时间戳的备用ID + return GenerateFallbackDeviceId(); + } + } + } + + /// + /// 生成25字符的唯一设备ID + /// + private static string GenerateDeviceId() + { + try + { + // 收集硬件信息 + var hardwareInfo = new StringBuilder(); + + // 使用反射获取硬件信息,避免直接引用System.Management + try + { + // 尝试加载System.Management程序集 + var assembly = System.Reflection.Assembly.Load("System.Management"); + if (assembly != null) + { + // CPU信息 + try + { + var searcherType = assembly.GetType("System.Management.ManagementObjectSearcher"); + var searcher = Activator.CreateInstance(searcherType, "SELECT ProcessorId FROM Win32_Processor"); + var getMethod = searcherType.GetMethod("Get"); + var enumerator = getMethod.Invoke(searcher, null); + + var moveNextMethod = enumerator.GetType().GetMethod("MoveNext"); + var currentProperty = enumerator.GetType().GetProperty("Current"); + + if ((bool)moveNextMethod.Invoke(enumerator, null)) + { + var obj = currentProperty.GetValue(enumerator); + var indexer = obj.GetType().GetProperty("Item", new[] { typeof(string) }); + var processorId = indexer.GetValue(obj, new object[] { "ProcessorId" }); + hardwareInfo.Append(processorId?.ToString() ?? ""); + } + + var disposeMethod = searcher.GetType().GetMethod("Dispose"); + disposeMethod?.Invoke(searcher, null); + } + catch { } + + // 主板序列号 + try + { + var searcherType = assembly.GetType("System.Management.ManagementObjectSearcher"); + var searcher = Activator.CreateInstance(searcherType, "SELECT SerialNumber FROM Win32_BaseBoard"); + var getMethod = searcherType.GetMethod("Get"); + var enumerator = getMethod.Invoke(searcher, null); + + var moveNextMethod = enumerator.GetType().GetMethod("MoveNext"); + var currentProperty = enumerator.GetType().GetProperty("Current"); + + if ((bool)moveNextMethod.Invoke(enumerator, null)) + { + var obj = currentProperty.GetValue(enumerator); + var indexer = obj.GetType().GetProperty("Item", new[] { typeof(string) }); + var serialNumber = indexer.GetValue(obj, new object[] { "SerialNumber" }); + hardwareInfo.Append(serialNumber?.ToString() ?? ""); + } + + var disposeMethod = searcher.GetType().GetMethod("Dispose"); + disposeMethod?.Invoke(searcher, null); + } + catch { } + + // BIOS序列号 + try + { + var searcherType = assembly.GetType("System.Management.ManagementObjectSearcher"); + var searcher = Activator.CreateInstance(searcherType, "SELECT SerialNumber FROM Win32_BIOS"); + var getMethod = searcherType.GetMethod("Get"); + var enumerator = getMethod.Invoke(searcher, null); + + var moveNextMethod = enumerator.GetType().GetMethod("MoveNext"); + var currentProperty = enumerator.GetType().GetProperty("Current"); + + if ((bool)moveNextMethod.Invoke(enumerator, null)) + { + var obj = currentProperty.GetValue(enumerator); + var indexer = obj.GetType().GetProperty("Item", new[] { typeof(string) }); + var serialNumber = indexer.GetValue(obj, new object[] { "SerialNumber" }); + hardwareInfo.Append(serialNumber?.ToString() ?? ""); + } + + var disposeMethod = searcher.GetType().GetMethod("Dispose"); + disposeMethod?.Invoke(searcher, null); + } + catch { } + + // 主硬盘序列号 + try + { + var searcherType = assembly.GetType("System.Management.ManagementObjectSearcher"); + var searcher = Activator.CreateInstance(searcherType, "SELECT SerialNumber FROM Win32_DiskDrive WHERE MediaType='Fixed hard disk media'"); + var getMethod = searcherType.GetMethod("Get"); + var enumerator = getMethod.Invoke(searcher, null); + + var moveNextMethod = enumerator.GetType().GetMethod("MoveNext"); + var currentProperty = enumerator.GetType().GetProperty("Current"); + + if ((bool)moveNextMethod.Invoke(enumerator, null)) + { + var obj = currentProperty.GetValue(enumerator); + var indexer = obj.GetType().GetProperty("Item", new[] { typeof(string) }); + var serialNumber = indexer.GetValue(obj, new object[] { "SerialNumber" }); + hardwareInfo.Append(serialNumber?.ToString() ?? ""); + } + + var disposeMethod = searcher.GetType().GetMethod("Dispose"); + disposeMethod?.Invoke(searcher, null); + } + catch { } + } + } + catch { } + + // 如果硬件信息不足,添加系统信息 + if (hardwareInfo.Length < 10) + { + hardwareInfo.Append(Environment.MachineName); + hardwareInfo.Append(Environment.UserName); + hardwareInfo.Append(Environment.OSVersion.ToString()); + } + + // 生成哈希 + using (var sha256 = SHA256.Create()) + { + byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(hardwareInfo.ToString())); + string hashString = BitConverter.ToString(hashBytes).Replace("-", ""); + + // 取前25个字符,确保唯一性 + string deviceId = hashString.Substring(0, 25); + + // 添加校验位(第25位) + int checksum = 0; + for (int i = 0; i < 24; i++) + { + checksum += Convert.ToInt32(deviceId[i]); + } + checksum %= 36; // 0-9, A-Z + char checksumChar = checksum < 10 ? (char)(checksum + '0') : (char)(checksum - 10 + 'A'); + + return deviceId.Substring(0, 24) + checksumChar; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 生成设备ID时出错: {ex.Message}", LogHelper.LogType.Error); + return GenerateFallbackDeviceId(); + } + } + + /// + /// 生成备用设备ID(基于时间戳) + /// + private static string GenerateFallbackDeviceId() + { + try + { + string timestamp = DateTime.Now.Ticks.ToString("X"); + string random = Guid.NewGuid().ToString("N").Substring(0, 8); + string combined = timestamp + random; + + using (var sha256 = SHA256.Create()) + { + byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(combined)); + string hashString = BitConverter.ToString(hashBytes).Replace("-", ""); + return hashString.Substring(0, 25); + } + } + catch + { + // 最后的备用方案 + return "ICC" + DateTime.Now.ToString("yyyyMMddHHmmss") + "000000000"; + } + } + + /// + /// 验证设备ID格式 + /// + private static bool IsValidDeviceId(string deviceId) + { + if (string.IsNullOrEmpty(deviceId) || deviceId.Length != 25) + return false; + + // 验证字符集(只允许数字和大写字母) + if (!deviceId.All(c => char.IsLetterOrDigit(c) && (char.IsDigit(c) || char.IsUpper(c)))) + return false; + + // 验证校验位 + try + { + int checksum = 0; + for (int i = 0; i < 24; i++) + { + checksum += Convert.ToInt32(deviceId[i]); + } + checksum %= 36; + char expectedChecksum = checksum < 10 ? (char)(checksum + '0') : (char)(checksum - 10 + 'A'); + return deviceId[24] == expectedChecksum; + } + catch + { + return false; + } + } + + /// + /// 从文件加载设备ID + /// + private static string LoadDeviceIdFromFile(string filePath) + { + try + { + if (File.Exists(filePath)) + { + string content = File.ReadAllText(filePath).Trim(); + if (IsValidDeviceId(content)) + { + return content; + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 从文件加载设备ID失败 ({filePath}): {ex.Message}", LogHelper.LogType.Error); + } + return null; + } + + /// + /// 从注册表加载设备ID + /// + private static string LoadDeviceIdFromRegistry() + { + try + { + using (var key = Registry.CurrentUser.OpenSubKey(@"Software\ICC\DeviceInfo")) + { + if (key != null) + { + var value = key.GetValue("DeviceId") as string; + if (IsValidDeviceId(value)) + { + return value; + } + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 从注册表加载设备ID失败: {ex.Message}", LogHelper.LogType.Error); + } + return null; + } + + /// + /// 保存设备ID到所有位置 + /// + private static void SaveDeviceIdToAllLocations(string deviceId) + { + // 保存到主文件 + SaveDeviceIdToFile(DeviceIdFilePath, deviceId); + + // 保存到备份文件 + SaveDeviceIdToFile(BackupDeviceIdPath, deviceId); + + // 保存到注册表 + SaveDeviceIdToRegistry(deviceId); + } + + /// + /// 保存设备ID到文件 + /// + private static void SaveDeviceIdToFile(string filePath, string deviceId) + { + try + { + // 确保目录存在 + var directory = Path.GetDirectoryName(filePath); + if (!Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + // 设置隐藏属性 + if (filePath.Contains(".sys")) + { + var dirInfo = new DirectoryInfo(directory); + dirInfo.Attributes |= FileAttributes.Hidden | FileAttributes.System; + } + } + + File.WriteAllText(filePath, deviceId); + + // 设置文件属性为隐藏和系统文件 + if (filePath.Contains(".sys")) + { + var fileInfo = new FileInfo(filePath); + fileInfo.Attributes |= FileAttributes.Hidden | FileAttributes.System; + } + + LogHelper.WriteLogToFile($"DeviceIdentifier | 设备ID已保存到: {filePath}"); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 保存设备ID到文件失败 ({filePath}): {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 保存设备ID到注册表 + /// + private static void SaveDeviceIdToRegistry(string deviceId) + { + try + { + using (var key = Registry.CurrentUser.CreateSubKey(@"Software\ICC\DeviceInfo")) + { + key?.SetValue("DeviceId", deviceId); + key?.SetValue("LastUpdate", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + } + LogHelper.WriteLogToFile($"DeviceIdentifier | 设备ID已保存到注册表"); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 保存设备ID到注册表失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 使用频率统计数据结构 + /// + private class UsageStats + { + [JsonProperty("deviceId")] + public string DeviceId { get; set; } + + [JsonProperty("lastLaunchTime")] + public DateTime LastLaunchTime { get; set; } + + [JsonProperty("launchCount")] + public int LaunchCount { get; set; } + + [JsonProperty("totalUsageMinutes")] + public long TotalUsageMinutes { get; set; } + + [JsonProperty("averageSessionMinutes")] + public double AverageSessionMinutes { get; set; } + + [JsonProperty("lastUpdateCheck")] + public DateTime LastUpdateCheck { get; set; } + + [JsonProperty("updatePriority")] + public UpdatePriority UpdatePriority { get; set; } + + [JsonProperty("usageFrequency")] + public UsageFrequency UsageFrequency { get; set; } + + [JsonProperty("dataHash")] + public string DataHash { get; set; } + + [JsonProperty("lastModified")] + public DateTime LastModified { get; set; } + + // 每周统计数据 + [JsonProperty("weeklyLaunchCount")] + public int WeeklyLaunchCount { get; set; } + + [JsonProperty("weeklyUsageMinutes")] + public long WeeklyUsageMinutes { get; set; } + + [JsonProperty("weekStartDate")] + public DateTime WeekStartDate { get; set; } + + [JsonProperty("lastWeekLaunchCount")] + public int LastWeekLaunchCount { get; set; } + + [JsonProperty("lastWeekUsageMinutes")] + public long LastWeekUsageMinutes { get; set; } + + /// + /// 检查并重置每周统计数据 + /// + public void CheckAndResetWeeklyStats() + { + var now = DateTime.Now; + var currentWeekStart = GetWeekStartDate(now); + + // 如果是新的一周,重置统计 + if (WeekStartDate == DateTime.MinValue || currentWeekStart > WeekStartDate) + { + // 保存上周数据 + LastWeekLaunchCount = WeeklyLaunchCount; + LastWeekUsageMinutes = WeeklyUsageMinutes; + + // 重置本周数据 + WeeklyLaunchCount = 0; + WeeklyUsageMinutes = 0; + WeekStartDate = currentWeekStart; + + LogHelper.WriteLogToFile($"DeviceIdentifier | 每周统计重置 - 上周启动: {LastWeekLaunchCount}次, 上周使用: {LastWeekUsageMinutes}分钟"); + } + } + + /// + /// 获取指定日期所在周的开始日期(周一) + /// + public DateTime GetWeekStartDate(DateTime date) + { + var dayOfWeek = (int)date.DayOfWeek; + var daysToSubtract = dayOfWeek == 0 ? 6 : dayOfWeek - 1; // 周日=0,需要减6天到周一 + return date.Date.AddDays(-daysToSubtract); + } + + /// + /// 记录本周的启动 + /// + public void RecordWeeklyLaunch() + { + CheckAndResetWeeklyStats(); + WeeklyLaunchCount++; + } + + /// + /// 记录本周的使用时长 + /// + public void RecordWeeklyUsage(long minutes) + { + CheckAndResetWeeklyStats(); + WeeklyUsageMinutes += minutes; + } + + /// + /// 计算数据哈希值用于完整性验证 + /// + public void UpdateDataHash() + { + var dataString = $"{DeviceId}|{LaunchCount}|{TotalUsageMinutes}|{LastLaunchTime:yyyyMMddHHmmss}|{WeeklyLaunchCount}|{WeeklyUsageMinutes}|{DataIntegrityKey}"; + using (var sha256 = SHA256.Create()) + { + var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(dataString)); + DataHash = Convert.ToBase64String(hashBytes); + } + LastModified = DateTime.Now; + } + + /// + /// 验证数据完整性 + /// + public bool VerifyDataIntegrity() + { + try + { + var dataString = $"{DeviceId}|{LaunchCount}|{TotalUsageMinutes}|{LastLaunchTime:yyyyMMddHHmmss}|{WeeklyLaunchCount}|{WeeklyUsageMinutes}|{DataIntegrityKey}"; + using (var sha256 = SHA256.Create()) + { + var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(dataString)); + var expectedHash = Convert.ToBase64String(hashBytes); + return DataHash == expectedHash; + } + } + catch + { + return false; + } + } + } + + /// + /// 更新推送优先级枚举 + /// + public enum UpdatePriority + { + High = 1, // 高优先级:立即推送更新 + Medium = 2, // 中优先级:延迟1-3天推送 + Low = 3 // 低优先级:延迟3-14天推送 + } + + /// + /// 用户使用频率分类枚举 + /// + public enum UsageFrequency + { + High = 1, // 高频用户:综合评分≥80分(活跃度高、使用时长长、启动频繁) + Medium = 2, // 中频用户:综合评分40-79分(中等活跃度和使用强度) + Low = 3 // 低频用户:综合评分<40分(活跃度低、使用时长短、启动较少) + } + + /// + /// 记录应用启动 + /// + public static void RecordAppLaunch() + { + try + { + lock (fileLock) + { + var stats = LoadUsageStats(); + stats.LastLaunchTime = DateTime.Now; + stats.LaunchCount++; + stats.DeviceId = DeviceId; + + // 记录每周启动次数 + stats.RecordWeeklyLaunch(); + + // 计算使用频率 + CalculateUsageFrequency(stats); + + // 更新数据完整性哈希 + stats.UpdateDataHash(); + + SaveUsageStats(stats); + + LogHelper.WriteLogToFile($"DeviceIdentifier | 记录应用启动 - 设备ID: {DeviceId}, 总启动: {stats.LaunchCount}次, 本周启动: {stats.WeeklyLaunchCount}次"); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 记录应用启动失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 记录应用退出(计算使用时长) + /// + public static void RecordAppExit() + { + try + { + lock (fileLock) + { + var stats = LoadUsageStats(); + + // 计算本次会话时长 + long sessionMinutes = 0; + if (stats.LastLaunchTime != DateTime.MinValue) + { + var sessionDuration = DateTime.Now - stats.LastLaunchTime; + sessionMinutes = (long)sessionDuration.TotalMinutes; + stats.TotalUsageMinutes += sessionMinutes; + + // 记录每周使用时长 + stats.RecordWeeklyUsage(sessionMinutes); + + // 更新平均会话时长 + if (stats.LaunchCount > 0) + { + stats.AverageSessionMinutes = (double)stats.TotalUsageMinutes / stats.LaunchCount; + } + } + + // 重新计算使用频率 + CalculateUsageFrequency(stats); + + // 更新数据完整性哈希 + stats.UpdateDataHash(); + + SaveUsageStats(stats); + + LogHelper.WriteLogToFile($"DeviceIdentifier | 记录应用退出 - 本次会话: {sessionMinutes}分钟, 总时长: {stats.TotalUsageMinutes}分钟, 本周时长: {stats.WeeklyUsageMinutes}分钟"); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 记录应用退出失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 计算使用频率和更新优先级(基于真实的每周统计数据) + /// 通过多维度评分系统确定用户类型:高频(≥80分)、中频(40-79分)、低频(<40分) + /// + private static void CalculateUsageFrequency(UsageStats stats) + { + try + { + // 确保每周统计数据是最新的 + stats.CheckAndResetWeeklyStats(); + + // 计算最近活跃度 + var daysSinceLastUse = (DateTime.Now - stats.LastLaunchTime).TotalDays; + + // 使用真实的每周数据 + var currentWeekLaunches = stats.WeeklyLaunchCount; + var currentWeekMinutes = stats.WeeklyUsageMinutes; + + // 如果本周数据不足,参考上周数据 + var weeklyLaunches = currentWeekLaunches > 0 ? currentWeekLaunches : stats.LastWeekLaunchCount; + var weeklyMinutes = currentWeekMinutes > 0 ? currentWeekMinutes : stats.LastWeekUsageMinutes; + + // 综合评分系统(0-100分) + var frequencyScore = CalculateFrequencyScoreWithWeeklyData(stats, daysSinceLastUse, weeklyLaunches, weeklyMinutes); + + // 根据综合评分确定频率分类和更新优先级 + if (frequencyScore >= 80) + { + stats.UsageFrequency = UsageFrequency.High; // 高频用户:立即推送更新 + stats.UpdatePriority = UpdatePriority.High; + } + else if (frequencyScore >= 40) + { + stats.UsageFrequency = UsageFrequency.Medium; // 中频用户:延迟1-3天推送 + stats.UpdatePriority = UpdatePriority.Medium; + } + else + { + stats.UsageFrequency = UsageFrequency.Low; // 低频用户:延迟3-14天推送 + stats.UpdatePriority = UpdatePriority.Low; + } + + LogHelper.WriteLogToFile($"DeviceIdentifier | 使用频率计算 - 评分: {frequencyScore}, 频率: {stats.UsageFrequency}, " + + $"优先级: {stats.UpdatePriority}, 本周启动: {currentWeekLaunches}次, 本周时长: {currentWeekMinutes}分钟"); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 计算使用频率失败: {ex.Message}", LogHelper.LogType.Error); + // 默认设置为中等频率和优先级 + stats.UsageFrequency = UsageFrequency.Medium; + stats.UpdatePriority = UpdatePriority.Medium; + } + } + + /// + /// 基于每周真实数据计算综合频率评分(0-100分) + /// 评分标准:≥80分=高频用户,40-79分=中频用户,<40分=低频用户 + /// + /// 使用统计数据 + /// 距离最后使用的天数 + /// 每周启动次数 + /// 每周使用时长 + /// 综合评分(0-100分) + private static int CalculateFrequencyScoreWithWeeklyData(UsageStats stats, double daysSinceLastUse, + long weeklyLaunches, long weeklyMinutes) + { + var score = 0; + + // 最近活跃度评分(40分)- 反映用户当前的活跃程度 + if (daysSinceLastUse <= 1) score += 40; // 1天内使用:非常活跃 + else if (daysSinceLastUse <= 3) score += 35; // 3天内使用:很活跃 + else if (daysSinceLastUse <= 7) score += 25; // 1周内使用:较活跃 + else if (daysSinceLastUse <= 14) score += 15; // 2周内使用:一般活跃 + else if (daysSinceLastUse <= 30) score += 5; // 1月内使用:不太活跃 + + // 每周使用频率评分(30分)- 基于真实的每周启动次数 + if (weeklyLaunches >= 10) score += 30; // 10次以上:高频使用 + else if (weeklyLaunches >= 5) score += 20; // 5-9次:中高频使用 + else if (weeklyLaunches >= 3) score += 15; // 3-4次:中频使用 + else if (weeklyLaunches >= 1) score += 10; // 1-2次:低频使用 + + // 每周使用时长评分(20分)- 基于真实的每周使用时长 + if (weeklyMinutes >= 600) score += 20; // 10小时以上:重度使用 + else if (weeklyMinutes >= 300) score += 15; // 5-10小时:中重度使用 + else if (weeklyMinutes >= 120) score += 10; // 2-5小时:中度使用 + else if (weeklyMinutes >= 60) score += 5; // 1-2小时:轻度使用 + + // 历史使用深度评分(10分)- 反映用户的长期使用习惯 + if (stats.TotalUsageMinutes >= 3000) score += 10; // 50小时以上:资深用户 + else if (stats.TotalUsageMinutes >= 1200) score += 7; // 20-50小时:中等用户 + else if (stats.TotalUsageMinutes >= 300) score += 4; // 5-20小时:新手用户 + + return Math.Min(100, score); + } + + + + /// + /// 获取当前更新优先级 + /// + public static UpdatePriority GetUpdatePriority() + { + try + { + var stats = LoadUsageStats(); + return stats.UpdatePriority; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 获取更新优先级失败: {ex.Message}", LogHelper.LogType.Error); + return UpdatePriority.Medium; // 默认中等优先级 + } + } + + /// + /// 获取使用频率 + /// + public static UsageFrequency GetUsageFrequency() + { + try + { + var stats = LoadUsageStats(); + return stats.UsageFrequency; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 获取使用频率失败: {ex.Message}", LogHelper.LogType.Error); + return UsageFrequency.Medium; // 默认中等频率 + } + } + + /// + /// 获取使用统计信息 + /// + public static (int launchCount, long totalMinutes, double avgSession, UpdatePriority priority) GetUsageStats() + { + try + { + var stats = LoadUsageStats(); + return (stats.LaunchCount, stats.TotalUsageMinutes, stats.AverageSessionMinutes, stats.UpdatePriority); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 获取使用统计失败: {ex.Message}", LogHelper.LogType.Error); + return (0, 0, 0, UpdatePriority.Medium); + } + } + + /// + /// 加载使用统计 - 支持多重备份恢复和智能反篡改(强化版本) + /// + private static UsageStats LoadUsageStats() + { + try + { + // 智能恢复:收集所有可用的数据源,选择最可信的 + var allDataSources = CollectAllUsageDataSources(); + + // 如果找到有效数据,返回最可信的 + if (allDataSources.Count > 0) + { + var bestData = SelectMostTrustedData(allDataSources); + if (bestData != null) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 使用最可信数据源恢复使用统计: {bestData.Source}"); + + // 确保备份同步 + SaveUsageStatsToAllLocations(bestData.Stats); + return bestData.Stats; + } + } + + LogHelper.WriteLogToFile($"DeviceIdentifier | 所有数据源都不可用,检查是否有部分可恢复数据", LogHelper.LogType.Warning); + + // 如果没有完全可信的数据,尝试从部分损坏的数据中恢复 + var partiallyRecoveredData = AttemptPartialDataRecovery(allDataSources); + if (partiallyRecoveredData != null) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 从部分损坏数据中恢复使用统计"); + SaveUsageStatsToAllLocations(partiallyRecoveredData); + return partiallyRecoveredData; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 加载使用统计失败: {ex.Message}", LogHelper.LogType.Error); + } + + // 返回新的统计对象 + var newStats = new UsageStats + { + DeviceId = DeviceId, + LastLaunchTime = DateTime.Now, + LaunchCount = 0, + TotalUsageMinutes = 0, + AverageSessionMinutes = 0, + LastUpdateCheck = DateTime.MinValue, + UpdatePriority = UpdatePriority.Medium, + UsageFrequency = UsageFrequency.Medium + }; + + // 更新数据完整性哈希 + newStats.UpdateDataHash(); + + // 保存新统计到所有位置 + SaveUsageStatsToAllLocations(newStats); + return newStats; + } + + /// + /// 保存使用统计 - 多重备份 + /// + private static void SaveUsageStats(UsageStats stats) + { + SaveUsageStatsToAllLocations(stats); + } + + /// + /// 数据源信息结构 + /// + private class DataSourceInfo + { + public UsageStats Stats { get; set; } + public string Source { get; set; } + public bool IsIntegrityValid { get; set; } + public DateTime LastModified { get; set; } + public int TrustScore { get; set; } + } + + /// + /// 从文件加载使用统计(带完整性验证,但不丢弃篡改数据) + /// + private static UsageStats LoadUsageStatsFromFile(string filePath) + { + try + { + if (File.Exists(filePath)) + { + string json = File.ReadAllText(filePath); + var stats = JsonConvert.DeserializeObject(json); + if (stats != null && !string.IsNullOrEmpty(stats.DeviceId)) + { + // 验证数据完整性 + if (!string.IsNullOrEmpty(stats.DataHash)) + { + if (stats.VerifyDataIntegrity()) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 数据完整性验证通过: {filePath}"); + return stats; + } + else + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 数据完整性验证失败,可能被篡改: {filePath}", LogHelper.LogType.Warning); + return null; // 数据被篡改,不使用 + } + } + else + { + // 旧版本数据,没有哈希值,更新哈希后返回 + LogHelper.WriteLogToFile($"DeviceIdentifier | 检测到旧版本数据,正在更新完整性哈希: {filePath}"); + stats.UpdateDataHash(); + return stats; + } + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 从文件加载使用统计失败 ({filePath}): {ex.Message}", LogHelper.LogType.Error); + } + return null; + } + + /// + /// 从文件加载使用统计(包括被篡改的数据,用于恢复分析) + /// + private static DataSourceInfo LoadUsageStatsFromFileWithInfo(string filePath, string sourceName) + { + try + { + if (File.Exists(filePath)) + { + string json = File.ReadAllText(filePath); + var stats = JsonConvert.DeserializeObject(json); + if (stats != null && !string.IsNullOrEmpty(stats.DeviceId)) + { + var fileInfo = new FileInfo(filePath); + var dataSource = new DataSourceInfo + { + Stats = stats, + Source = sourceName, + LastModified = stats.LastModified != DateTime.MinValue ? stats.LastModified : fileInfo.LastWriteTime, + IsIntegrityValid = false, + TrustScore = 0 + }; + + // 验证数据完整性 + if (!string.IsNullOrEmpty(stats.DataHash)) + { + dataSource.IsIntegrityValid = stats.VerifyDataIntegrity(); + dataSource.TrustScore = dataSource.IsIntegrityValid ? 100 : 20; // 完整数据100分,篡改数据20分 + } + else + { + // 旧版本数据,中等信任度 + dataSource.IsIntegrityValid = true; + dataSource.TrustScore = 60; + stats.UpdateDataHash(); + } + + return dataSource; + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 从文件加载数据源信息失败 ({filePath}): {ex.Message}", LogHelper.LogType.Error); + } + return null; + } + + /// + /// 收集所有可用的使用统计数据源 + /// + private static List CollectAllUsageDataSources() + { + var dataSources = new List(); + + try + { + // 1. 收集文件数据源 + var fileSources = new[] + { + new { Path = UsageStatsFilePath, Name = "主文件" }, + new { Path = BackupUsageStatsPath, Name = "第一备份" }, + new { Path = SecondaryUsageBackupPath, Name = "第二备份" }, + new { Path = TertiaryUsageBackupPath, Name = "第三备份" }, + new { Path = QuaternaryUsageBackupPath, Name = "第四备份" } + }; + + foreach (var source in fileSources) + { + var dataSource = LoadUsageStatsFromFileWithInfo(source.Path, source.Name); + if (dataSource != null) + { + dataSources.Add(dataSource); + } + } + + // 2. 收集注册表数据源 + var registrySource = LoadUsageStatsFromRegistryWithInfo(@"Software\ICC\DeviceInfo", "主注册表"); + if (registrySource != null) + { + dataSources.Add(registrySource); + } + + // 3. 收集备用注册表数据源 + var backupRegistryPaths = new[] + { + new { Path = @"Software\Microsoft\Windows\CurrentVersion\ICC", Name = "备用注册表1" }, + new { Path = @"Software\Classes\.icc\UsageData", Name = "备用注册表2" }, + new { Path = @"Software\ICC\Config\Usage", Name = "备用注册表3" } + }; + + foreach (var regPath in backupRegistryPaths) + { + var regSource = LoadUsageStatsFromBackupRegistryWithInfo(regPath.Path, regPath.Name); + if (regSource != null) + { + dataSources.Add(regSource); + } + } + + LogHelper.WriteLogToFile($"DeviceIdentifier | 收集到 {dataSources.Count} 个数据源"); + return dataSources; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 收集数据源失败: {ex.Message}", LogHelper.LogType.Error); + return dataSources; + } + } + + /// + /// 选择最可信的数据源 + /// + private static DataSourceInfo SelectMostTrustedData(List dataSources) + { + try + { + // 首先尝试找到完整性验证通过的数据 + var validSources = dataSources.Where(d => d.IsIntegrityValid).ToList(); + + if (validSources.Count > 0) + { + // 在有效数据中选择最新的 + var bestValid = validSources.OrderByDescending(d => d.LastModified).First(); + LogHelper.WriteLogToFile($"DeviceIdentifier | 选择完整性验证通过的最新数据: {bestValid.Source}"); + return bestValid; + } + + // 如果没有完整性验证通过的数据,选择信任度最高的 + var bestByTrust = dataSources.OrderByDescending(d => d.TrustScore).ThenByDescending(d => d.LastModified).FirstOrDefault(); + if (bestByTrust != null) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 选择信任度最高的数据: {bestByTrust.Source} (信任度: {bestByTrust.TrustScore})", LogHelper.LogType.Warning); + return bestByTrust; + } + + return null; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 选择最可信数据失败: {ex.Message}", LogHelper.LogType.Error); + return null; + } + } + + /// + /// 尝试从部分损坏的数据中恢复 + /// + private static UsageStats AttemptPartialDataRecovery(List dataSources) + { + try + { + if (dataSources.Count == 0) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 没有可用数据源进行部分恢复"); + return null; + } + + // 从所有数据源中提取可信的字段 + var recoveredStats = new UsageStats + { + DeviceId = DeviceId, + LastLaunchTime = DateTime.Now, + LaunchCount = 0, + TotalUsageMinutes = 0, + AverageSessionMinutes = 0, + LastUpdateCheck = DateTime.MinValue, + UpdatePriority = UpdatePriority.Medium, + UsageFrequency = UsageFrequency.Medium + }; + + // 使用多数投票或最大值策略恢复关键数据 + var launchCounts = dataSources.Where(d => d.Stats.LaunchCount > 0).Select(d => d.Stats.LaunchCount).ToList(); + var usageMinutes = dataSources.Where(d => d.Stats.TotalUsageMinutes > 0).Select(d => d.Stats.TotalUsageMinutes).ToList(); + + if (launchCounts.Count > 0) + { + recoveredStats.LaunchCount = (int)launchCounts.Average(); // 使用平均值 + } + + if (usageMinutes.Count > 0) + { + recoveredStats.TotalUsageMinutes = (long)usageMinutes.Average(); // 使用平均值 + } + + // 重新计算平均会话时长 + if (recoveredStats.LaunchCount > 0) + { + recoveredStats.AverageSessionMinutes = (double)recoveredStats.TotalUsageMinutes / recoveredStats.LaunchCount; + } + + // 重新计算使用频率 + CalculateUsageFrequency(recoveredStats); + + // 更新数据完整性哈希 + recoveredStats.UpdateDataHash(); + + LogHelper.WriteLogToFile($"DeviceIdentifier | 部分数据恢复完成 - 启动次数: {recoveredStats.LaunchCount}, 使用时长: {recoveredStats.TotalUsageMinutes}分钟"); + return recoveredStats; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 部分数据恢复失败: {ex.Message}", LogHelper.LogType.Error); + return null; + } + } + + /// + /// 从注册表加载使用统计(带数据源信息) + /// + private static DataSourceInfo LoadUsageStatsFromRegistryWithInfo(string registryPath, string sourceName) + { + try + { + using (var key = Registry.CurrentUser.OpenSubKey(registryPath)) + { + if (key != null) + { + var deviceId = key.GetValue("DeviceId") as string; + var launchCount = key.GetValue("LaunchCount"); + var totalMinutes = key.GetValue("TotalUsageMinutes"); + var lastLaunch = key.GetValue("LastLaunchTime") as string; + var priority = key.GetValue("UpdatePriority"); + var frequency = key.GetValue("UsageFrequency"); + var dataHash = key.GetValue("DataHash") as string; + var lastUpdate = key.GetValue("LastUpdate") as string; + + // 每周统计数据 + var weeklyLaunchCount = key.GetValue("WeeklyLaunchCount"); + var weeklyUsageMinutes = key.GetValue("WeeklyUsageMinutes"); + var weekStartDate = key.GetValue("WeekStartDate") as string; + var lastWeekLaunchCount = key.GetValue("LastWeekLaunchCount"); + var lastWeekUsageMinutes = key.GetValue("LastWeekUsageMinutes"); + + if (!string.IsNullOrEmpty(deviceId) && launchCount != null) + { + var stats = new UsageStats + { + DeviceId = deviceId, + LaunchCount = Convert.ToInt32(launchCount), + TotalUsageMinutes = totalMinutes != null ? Convert.ToInt64(totalMinutes) : 0, + LastLaunchTime = DateTime.TryParse(lastLaunch, out var dt) ? dt : DateTime.Now, + UpdatePriority = priority != null ? (UpdatePriority)Convert.ToInt32(priority) : UpdatePriority.Medium, + UsageFrequency = frequency != null ? (UsageFrequency)Convert.ToInt32(frequency) : UsageFrequency.Medium, + DataHash = dataHash, + AverageSessionMinutes = 0, + LastUpdateCheck = DateTime.MinValue, + + // 每周统计数据 + WeeklyLaunchCount = weeklyLaunchCount != null ? Convert.ToInt32(weeklyLaunchCount) : 0, + WeeklyUsageMinutes = weeklyUsageMinutes != null ? Convert.ToInt64(weeklyUsageMinutes) : 0, + WeekStartDate = DateTime.TryParse(weekStartDate, out var wsd) ? wsd : DateTime.MinValue, + LastWeekLaunchCount = lastWeekLaunchCount != null ? Convert.ToInt32(lastWeekLaunchCount) : 0, + LastWeekUsageMinutes = lastWeekUsageMinutes != null ? Convert.ToInt64(lastWeekUsageMinutes) : 0 + }; + + // 重新计算平均会话时长 + if (stats.LaunchCount > 0) + { + stats.AverageSessionMinutes = (double)stats.TotalUsageMinutes / stats.LaunchCount; + } + + var dataSource = new DataSourceInfo + { + Stats = stats, + Source = sourceName, + LastModified = DateTime.TryParse(lastUpdate, out var updateTime) ? updateTime : DateTime.Now, + IsIntegrityValid = false, + TrustScore = 80 // 注册表数据信任度较高 + }; + + // 验证数据完整性 + if (!string.IsNullOrEmpty(stats.DataHash)) + { + dataSource.IsIntegrityValid = stats.VerifyDataIntegrity(); + dataSource.TrustScore = dataSource.IsIntegrityValid ? 100 : 30; + } + + return dataSource; + } + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 从注册表加载数据源信息失败 ({registryPath}): {ex.Message}", LogHelper.LogType.Error); + } + return null; + } + + /// + /// 从备用注册表位置加载使用统计(带数据源信息) + /// + private static DataSourceInfo LoadUsageStatsFromBackupRegistryWithInfo(string registryPath, string sourceName) + { + try + { + using (var key = Registry.CurrentUser.OpenSubKey(registryPath)) + { + if (key != null) + { + var launchCount = key.GetValue("LC"); + var totalMinutes = key.GetValue("TUM"); + var lastLaunchBinary = key.GetValue("LLT"); + var priority = key.GetValue("UP"); + var frequency = key.GetValue("UF"); + var dataHash = key.GetValue("DH") as string; + var lastUpdateBinary = key.GetValue("LU"); + + // 每周统计数据 + var weeklyLaunchCount = key.GetValue("WLC"); + var weeklyUsageMinutes = key.GetValue("WUM"); + var weekStartDateBinary = key.GetValue("WSD"); + var lastWeekLaunchCount = key.GetValue("LWLC"); + var lastWeekUsageMinutes = key.GetValue("LWUM"); + + if (launchCount != null && totalMinutes != null) + { + var stats = new UsageStats + { + DeviceId = DeviceId, + LaunchCount = Convert.ToInt32(launchCount), + TotalUsageMinutes = Convert.ToInt64(totalMinutes), + LastLaunchTime = lastLaunchBinary != null ? DateTime.FromBinary(Convert.ToInt64(lastLaunchBinary)) : DateTime.Now, + UpdatePriority = priority != null ? (UpdatePriority)Convert.ToInt32(priority) : UpdatePriority.Medium, + UsageFrequency = frequency != null ? (UsageFrequency)Convert.ToInt32(frequency) : UsageFrequency.Medium, + DataHash = dataHash, + AverageSessionMinutes = 0, + LastUpdateCheck = DateTime.MinValue, + + // 每周统计数据 + WeeklyLaunchCount = weeklyLaunchCount != null ? Convert.ToInt32(weeklyLaunchCount) : 0, + WeeklyUsageMinutes = weeklyUsageMinutes != null ? Convert.ToInt64(weeklyUsageMinutes) : 0, + WeekStartDate = weekStartDateBinary != null ? DateTime.FromBinary(Convert.ToInt64(weekStartDateBinary)) : DateTime.MinValue, + LastWeekLaunchCount = lastWeekLaunchCount != null ? Convert.ToInt32(lastWeekLaunchCount) : 0, + LastWeekUsageMinutes = lastWeekUsageMinutes != null ? Convert.ToInt64(lastWeekUsageMinutes) : 0 + }; + + // 重新计算平均会话时长 + if (stats.LaunchCount > 0) + { + stats.AverageSessionMinutes = (double)stats.TotalUsageMinutes / stats.LaunchCount; + } + + var dataSource = new DataSourceInfo + { + Stats = stats, + Source = sourceName, + LastModified = lastUpdateBinary != null ? DateTime.FromBinary(Convert.ToInt64(lastUpdateBinary)) : DateTime.Now, + IsIntegrityValid = false, + TrustScore = 75 // 备用注册表数据信任度中等 + }; + + // 验证数据完整性 + if (!string.IsNullOrEmpty(stats.DataHash)) + { + dataSource.IsIntegrityValid = stats.VerifyDataIntegrity(); + dataSource.TrustScore = dataSource.IsIntegrityValid ? 100 : 25; + } + + return dataSource; + } + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 从备用注册表加载数据源信息失败 ({registryPath}): {ex.Message}", LogHelper.LogType.Error); + } + return null; + } + + /// + /// 从注册表加载使用统计(保持向后兼容) + /// + private static UsageStats LoadUsageStatsFromRegistry() + { + var dataSource = LoadUsageStatsFromRegistryWithInfo(@"Software\ICC\DeviceInfo", "主注册表"); + return dataSource?.Stats; + } + + /// + /// 从多个注册表位置加载使用统计(强化恢复) + /// + private static UsageStats LoadUsageStatsFromMultipleRegistryLocations() + { + var registryPaths = new[] + { + @"Software\Microsoft\Windows\CurrentVersion\ICC", + @"Software\Classes\.icc\UsageData", + @"Software\ICC\Config\Usage" + }; + + foreach (var path in registryPaths) + { + try + { + using (var key = Registry.CurrentUser.OpenSubKey(path)) + { + if (key != null) + { + var launchCount = key.GetValue("LC"); + var totalMinutes = key.GetValue("TUM"); + var lastLaunchBinary = key.GetValue("LLT"); + var priority = key.GetValue("UP"); + var frequency = key.GetValue("UF"); + var dataHash = key.GetValue("DH") as string; + + if (launchCount != null && totalMinutes != null) + { + var stats = new UsageStats + { + DeviceId = DeviceId, + LaunchCount = Convert.ToInt32(launchCount), + TotalUsageMinutes = Convert.ToInt64(totalMinutes), + LastLaunchTime = lastLaunchBinary != null ? DateTime.FromBinary(Convert.ToInt64(lastLaunchBinary)) : DateTime.Now, + UpdatePriority = priority != null ? (UpdatePriority)Convert.ToInt32(priority) : UpdatePriority.Medium, + UsageFrequency = frequency != null ? (UsageFrequency)Convert.ToInt32(frequency) : UsageFrequency.Medium, + DataHash = dataHash, + AverageSessionMinutes = 0, + LastUpdateCheck = DateTime.MinValue + }; + + // 重新计算平均会话时长 + if (stats.LaunchCount > 0) + { + stats.AverageSessionMinutes = (double)stats.TotalUsageMinutes / stats.LaunchCount; + } + + // 验证数据完整性(如果有哈希值) + if (!string.IsNullOrEmpty(stats.DataHash)) + { + if (stats.VerifyDataIntegrity()) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 从注册表位置恢复数据并验证完整性通过: {path}"); + return stats; + } + else + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 注册表位置数据完整性验证失败: {path}", LogHelper.LogType.Warning); + continue; // 尝试下一个位置 + } + } + else + { + // 没有哈希值的旧数据,更新哈希后返回 + stats.UpdateDataHash(); + LogHelper.WriteLogToFile($"DeviceIdentifier | 从注册表位置恢复旧版本数据: {path}"); + return stats; + } + } + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 从注册表位置加载失败 ({path}): {ex.Message}", LogHelper.LogType.Error); + } + } + + return null; + } + + /// + /// 保存使用统计到所有位置(强化版本 - 多重隐藏备份) + /// + private static void SaveUsageStatsToAllLocations(UsageStats stats) + { + // 保存到主文件 + SaveUsageStatsToFile(UsageStatsFilePath, stats); + + // 保存到第一备份文件 + SaveUsageStatsToFile(BackupUsageStatsPath, stats); + + // 保存到多个隐藏备份位置(专门针对使用频率数据保护) + SaveUsageStatsToFile(SecondaryUsageBackupPath, stats); + SaveUsageStatsToFile(TertiaryUsageBackupPath, stats); + SaveUsageStatsToFile(QuaternaryUsageBackupPath, stats); + + // 保存到注册表 + SaveUsageStatsToRegistry(stats); + + // 保存到注册表的多个位置 + SaveUsageStatsToMultipleRegistryLocations(stats); + } + + /// + /// 保存使用统计到文件 + /// + private static void SaveUsageStatsToFile(string filePath, UsageStats stats) + { + try + { + // 确保目录存在 + var directory = Path.GetDirectoryName(filePath); + if (!Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + // 设置隐藏属性 + if (filePath.Contains(".sys")) + { + var dirInfo = new DirectoryInfo(directory); + dirInfo.Attributes |= FileAttributes.Hidden | FileAttributes.System; + } + } + + string json = JsonConvert.SerializeObject(stats, Formatting.Indented); + File.WriteAllText(filePath, json); + + // 设置文件属性为隐藏和系统文件 + if (filePath.Contains(".sys")) + { + var fileInfo = new FileInfo(filePath); + fileInfo.Attributes |= FileAttributes.Hidden | FileAttributes.System; + } + + LogHelper.WriteLogToFile($"DeviceIdentifier | 使用统计已保存到: {filePath}"); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 保存使用统计到文件失败 ({filePath}): {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 保存使用统计到注册表 + /// + private static void SaveUsageStatsToRegistry(UsageStats stats) + { + try + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 开始保存使用统计到主注册表位置"); + + using (var key = Registry.CurrentUser.CreateSubKey(@"Software\ICC\DeviceInfo")) + { + if (key != null) + { + key.SetValue("DeviceId", stats.DeviceId); + key.SetValue("LaunchCount", stats.LaunchCount); + key.SetValue("TotalUsageMinutes", stats.TotalUsageMinutes); + key.SetValue("LastLaunchTime", stats.LastLaunchTime.ToString("yyyy-MM-dd HH:mm:ss")); + key.SetValue("UpdatePriority", (int)stats.UpdatePriority); + key.SetValue("UsageFrequency", (int)stats.UsageFrequency); + key.SetValue("DataHash", stats.DataHash ?? ""); + key.SetValue("LastUpdate", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + + // 每周统计数据 + key.SetValue("WeeklyLaunchCount", stats.WeeklyLaunchCount); + key.SetValue("WeeklyUsageMinutes", stats.WeeklyUsageMinutes); + key.SetValue("WeekStartDate", stats.WeekStartDate.ToString("yyyy-MM-dd")); + key.SetValue("LastWeekLaunchCount", stats.LastWeekLaunchCount); + key.SetValue("LastWeekUsageMinutes", stats.LastWeekUsageMinutes); + + LogHelper.WriteLogToFile($"DeviceIdentifier | 使用统计已保存到主注册表 - 总启动: {stats.LaunchCount}次, 本周启动: {stats.WeeklyLaunchCount}次, 总时长: {stats.TotalUsageMinutes}分钟, 本周时长: {stats.WeeklyUsageMinutes}分钟"); + } + else + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 创建主注册表键失败", LogHelper.LogType.Error); + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 保存使用统计到主注册表失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 保存使用统计到多个注册表位置(强化保护) + /// + private static void SaveUsageStatsToMultipleRegistryLocations(UsageStats stats) + { + var registryPaths = new[] + { + @"Software\Microsoft\Windows\CurrentVersion\ICC", + @"Software\Classes\.icc\UsageData", + @"Software\ICC\Config\Usage" + }; + + LogHelper.WriteLogToFile($"DeviceIdentifier | 开始保存使用统计到{registryPaths.Length}个备用注册表位置"); + var successCount = 0; + + foreach (var path in registryPaths) + { + try + { + using (var key = Registry.CurrentUser.CreateSubKey(path)) + { + if (key != null) + { + // 使用编码的键名来隐藏数据 + key.SetValue("LC", stats.LaunchCount); // LaunchCount + key.SetValue("TUM", stats.TotalUsageMinutes); // TotalUsageMinutes + key.SetValue("LLT", stats.LastLaunchTime.ToBinary()); // LastLaunchTime + key.SetValue("UP", (int)stats.UpdatePriority); // UpdatePriority + key.SetValue("UF", (int)stats.UsageFrequency); // UsageFrequency + key.SetValue("DH", stats.DataHash ?? ""); // DataHash + key.SetValue("LU", DateTime.Now.ToBinary()); // LastUpdate + + // 每周统计数据 + key.SetValue("WLC", stats.WeeklyLaunchCount); // WeeklyLaunchCount + key.SetValue("WUM", stats.WeeklyUsageMinutes); // WeeklyUsageMinutes + key.SetValue("WSD", stats.WeekStartDate.ToBinary()); // WeekStartDate + key.SetValue("LWLC", stats.LastWeekLaunchCount); // LastWeekLaunchCount + key.SetValue("LWUM", stats.LastWeekUsageMinutes); // LastWeekUsageMinutes + + successCount++; + LogHelper.WriteLogToFile($"DeviceIdentifier | 成功保存到备用注册表位置: {path}"); + } + else + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 创建备用注册表键失败: {path}", LogHelper.LogType.Error); + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 保存到备用注册表位置失败 ({path}): {ex.Message}", LogHelper.LogType.Error); + } + } + + LogHelper.WriteLogToFile($"DeviceIdentifier | 备用注册表保存完成: {successCount}/{registryPaths.Length} 成功"); + } + + /// + /// 记录更新检查时间(同时执行数据保护检查) + /// + public static void RecordUpdateCheck() + { + try + { + lock (fileLock) + { + var stats = LoadUsageStats(); + stats.LastUpdateCheck = DateTime.Now; + stats.UpdateDataHash(); + SaveUsageStats(stats); + + // 定期执行数据保护检查(每10次更新检查执行一次) + if (stats.LaunchCount % 10 == 0) + { + PerformUsageDataProtectionCheck(); + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 记录更新检查失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 执行使用频率数据保护检查和自动修复 + /// + public static bool PerformUsageDataProtectionCheck() + { + try + { + lock (fileLock) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 开始使用频率数据保护检查"); + + var issues = new List(); + var repaired = new List(); + var backupPaths = new[] + { + new { Path = UsageStatsFilePath, Name = "主文件" }, + new { Path = BackupUsageStatsPath, Name = "第一备份" }, + new { Path = SecondaryUsageBackupPath, Name = "第二备份" }, + new { Path = TertiaryUsageBackupPath, Name = "第三备份" }, + new { Path = QuaternaryUsageBackupPath, Name = "第四备份" } + }; + + // 检查所有备份文件 + UsageStats validStats = null; + var missingFiles = new List(); + + foreach (var backup in backupPaths) + { + if (!File.Exists(backup.Path)) + { + issues.Add($"{backup.Name}丢失"); + missingFiles.Add(backup.Path); + } + else + { + var stats = LoadUsageStatsFromFile(backup.Path); + if (stats != null && stats.VerifyDataIntegrity() && validStats == null) + { + validStats = stats; + } + } + } + + // 如果找到有效数据,修复丢失的文件 + if (validStats != null && missingFiles.Count > 0) + { + foreach (var missingFile in missingFiles) + { + SaveUsageStatsToFile(missingFile, validStats); + repaired.Add($"恢复文件: {Path.GetFileName(missingFile)}"); + } + } + + // 检查注册表备份 + var registryPaths = new[] + { + @"Software\ICC\DeviceInfo", + @"Software\Microsoft\Windows\CurrentVersion\ICC", + @"Software\Classes\.icc\UsageData", + @"Software\ICC\Config\Usage" + }; + + var missingRegistryBackups = 0; + foreach (var path in registryPaths) + { + try + { + using (var key = Registry.CurrentUser.OpenSubKey(path)) + { + if (key == null) missingRegistryBackups++; + } + } + catch + { + missingRegistryBackups++; + } + } + + if (missingRegistryBackups > 0) + { + issues.Add($"{missingRegistryBackups}个注册表备份丢失"); + if (validStats != null) + { + SaveUsageStatsToMultipleRegistryLocations(validStats); + repaired.Add("重建注册表备份"); + } + } + + // 如果没有找到任何有效数据,尝试从注册表恢复 + if (validStats == null) + { + validStats = LoadUsageStatsFromRegistry(); + if (validStats == null) + { + validStats = LoadUsageStatsFromMultipleRegistryLocations(); + } + + if (validStats != null) + { + SaveUsageStatsToAllLocations(validStats); + repaired.Add("从注册表完全恢复数据"); + } + else + { + // 最后手段:强制重建 + ForceRebuildUsageDataBackups(); + repaired.Add("强制重建所有备份"); + } + } + + // 记录检查结果 + if (issues.Count > 0) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 使用频率数据保护检查发现问题: {string.Join(", ", issues)}", LogHelper.LogType.Warning); + } + + if (repaired.Count > 0) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 使用频率数据保护修复: {string.Join(", ", repaired)}"); + } + + var protectionScore = CalculateUsageDataProtectionScore(); + LogHelper.WriteLogToFile($"DeviceIdentifier | 使用频率数据保护检查完成 - 问题: {issues.Count}, 修复: {repaired.Count}, 保护强度: {protectionScore}/100"); + + return protectionScore >= 80; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 使用频率数据保护检查失败: {ex.Message}", LogHelper.LogType.Error); + return false; + } + } + + /// + /// 根据优先级决定是否应该推送更新(仅适用于自动更新,版本修复功能不受影响) + /// + /// 更新版本号 + /// 新版本发布时间 + /// 是否为自动更新检查(默认true,false表示版本修复) + /// 当前版本发布时间 + /// 是否应该推送更新 + public static bool ShouldPushUpdate(string updateVersion, DateTime releaseTime, bool isAutoUpdate = true, DateTime? currentVersionReleaseTime = null) + { + try + { + // 判断更新类型(基于版本号) + var updateType = DetermineUpdateType(updateVersion); + + // 如果不是自动更新(即版本修复),则应用不同的策略 + if (!isAutoUpdate) + { + // 版本修复:立即允许,不受分级策略影响 + LogHelper.WriteLogToFile($"DeviceIdentifier | 版本修复 - 版本: {updateVersion}, 类型: {updateType}, 结果: 允许"); + return true; + } + + var priority = GetUpdatePriority(); + var frequency = GetUsageFrequency(); + var stats = LoadUsageStats(); + + // 计算版本间的时间差 + double daysBetweenVersions; + if (currentVersionReleaseTime.HasValue) + { + // 使用当前版本发布时间与新版本发布时间的差异 + daysBetweenVersions = (releaseTime - currentVersionReleaseTime.Value).TotalDays; + } + else + { + // 如果没有当前版本发布时间,回退到使用新版本发布时间到现在的天数 + daysBetweenVersions = (DateTime.Now - releaseTime).TotalDays; + } + + // 计算最近活跃度(最后一次使用距今的天数) + var daysSinceLastUse = (DateTime.Now - stats.LastLaunchTime).TotalDays; + + // 综合判断逻辑(仅适用于自动更新) + var shouldPush = ShouldPushUpdateComprehensive(priority, frequency, daysBetweenVersions, daysSinceLastUse, stats, updateType); + + LogHelper.WriteLogToFile($"DeviceIdentifier | 自动更新推送判断 - 版本: {updateVersion}, 类型: {updateType}, " + + $"优先级: {priority}, 频率: {frequency}, 版本间隔: {daysBetweenVersions:F1}天, " + + $"最后使用: {daysSinceLastUse:F1}天前, 结果: {shouldPush}"); + + return shouldPush; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 判断是否推送更新失败: {ex.Message}", LogHelper.LogType.Error); + return true; // 出错时默认推送 + } + } + + /// + /// 更新类型枚举 + /// + private enum UpdateType + { + Major, // 主版本更新 (x.0.0) + Minor, // 次版本更新 (x.y.0) + Patch, // 补丁更新 (x.y.z) + Hotfix, // 热修复更新 + Unknown // 未知类型 + } + + /// + /// 根据版本号判断更新类型 + /// + private static UpdateType DetermineUpdateType(string version) + { + if (string.IsNullOrEmpty(version)) return UpdateType.Unknown; + + try + { + // 移除可能的前缀(如 "v") + var cleanVersion = version.TrimStart('v', 'V'); + + // 检查是否包含热修复标识 + if (cleanVersion.ToLower().Contains("hotfix") || cleanVersion.ToLower().Contains("fix")) + { + return UpdateType.Hotfix; + } + + // 解析版本号 + var parts = cleanVersion.Split('.'); + if (parts.Length >= 3) + { + if (int.TryParse(parts[1], out int minor) && int.TryParse(parts[2], out int patch)) + { + if (minor == 0 && patch == 0) return UpdateType.Major; + if (patch == 0) return UpdateType.Minor; + return UpdateType.Patch; + } + } + + return UpdateType.Unknown; + } + catch + { + return UpdateType.Unknown; + } + } + + /// + /// 综合时间和使用频率的自动更新推送判断逻辑(不影响版本修复) + /// + /// 用户更新优先级 + /// 用户使用频率 + /// 当前版本与新版本之间的天数差异 + /// 距离最后使用的天数 + /// 使用统计数据 + /// 更新类型 + /// 是否应该推送更新 + private static bool ShouldPushUpdateComprehensive(UpdatePriority priority, UsageFrequency frequency, + double daysBetweenVersions, double daysSinceLastUse, UsageStats stats, UpdateType updateType) + { + // 考虑用户的总体使用模式 + var isHeavyUser = stats.TotalUsageMinutes > 3000; // 超过50小时的重度用户 + var isFrequentUser = stats.LaunchCount > 100; // 启动超过100次的频繁用户 + + // 根据更新类型调整推送策略 + var urgencyMultiplier = GetUpdateUrgencyMultiplier(updateType); + + // 如果用户长时间未使用(超过30天),降低推送优先级 + if (daysSinceLastUse > 30) + { + // 热修复和重要更新优先推送 + if (updateType == UpdateType.Hotfix) + { + return daysBetweenVersions >= 1; // 热修复版本间隔1天后推送 + } + + // 但如果是重度用户,仍然要适当推送 + var baseDelay = isHeavyUser ? 7 : 14; + return daysBetweenVersions >= (baseDelay / urgencyMultiplier); + } + + // 如果用户最近很活跃(3天内使用过) + if (daysSinceLastUse <= 3) + { + // 热修复立即推送给活跃用户 + if (updateType == UpdateType.Hotfix) + { + return true; + } + + // 结合使用频率和优先级判断 + if (frequency == UsageFrequency.High || isHeavyUser) + { + return daysBetweenVersions >= Math.Max(0, 1 / urgencyMultiplier); // 高频用户优先推送 + } + + switch (priority) + { + case UpdatePriority.High: + return daysBetweenVersions >= Math.Max(0, 1 / urgencyMultiplier); + + case UpdatePriority.Medium: + return daysBetweenVersions >= Math.Max(1, 2 / urgencyMultiplier); + + case UpdatePriority.Low: + return daysBetweenVersions >= Math.Max(2, 3 / urgencyMultiplier); + } + } + + // 中等活跃度用户(3-14天内使用过) + if (daysSinceLastUse <= 14) + { + // 热修复优先推送 + if (updateType == UpdateType.Hotfix) + { + return daysBetweenVersions >= 1; + } + + // 频繁用户优先推送 + if (isFrequentUser && frequency == UsageFrequency.High) + { + return daysBetweenVersions >= Math.Max(1, 2 / urgencyMultiplier); + } + + switch (priority) + { + case UpdatePriority.High: + return daysBetweenVersions >= Math.Max(1, 2 / urgencyMultiplier); + + case UpdatePriority.Medium: + return daysBetweenVersions >= Math.Max(2, 4 / urgencyMultiplier); + + case UpdatePriority.Low: + return daysBetweenVersions >= Math.Max(4, 7 / urgencyMultiplier); + } + } + + // 较不活跃用户(14-30天内使用过) + // 对于低频率用户,进一步延迟推送 + var delayMultiplier = frequency == UsageFrequency.Low ? 2 : 1; + + switch (priority) + { + case UpdatePriority.High: + return daysBetweenVersions >= Math.Max(2, 3 * delayMultiplier / urgencyMultiplier); + + case UpdatePriority.Medium: + return daysBetweenVersions >= Math.Max(4, 7 * delayMultiplier / urgencyMultiplier); + + case UpdatePriority.Low: + return daysBetweenVersions >= Math.Max(7, 14 * delayMultiplier / urgencyMultiplier); + + default: + return daysBetweenVersions >= 7; + } + } + + /// + /// 根据更新类型获取紧急程度倍数(仅用于自动更新分级) + /// + private static double GetUpdateUrgencyMultiplier(UpdateType updateType) + { + switch (updateType) + { + case UpdateType.Hotfix: + return 3.0; // 热修复最紧急,3倍速度推送 + case UpdateType.Major: + return 0.5; // 主版本更新较慢推送 + case UpdateType.Minor: + return 1.0; // 次版本正常推送 + case UpdateType.Patch: + return 1.5; // 补丁更新稍快推送 + case UpdateType.Unknown: + return 1.0; // 未知类型正常推送 + default: + return 1.0; + } + } + + /// + /// 检查是否应该进行版本修复(不受分级策略影响) + /// + /// 当前版本 + /// 可用版本 + /// 是否需要版本修复 + public static bool ShouldPerformVersionFix(string currentVersion, string availableVersion) + { + try + { + // 版本修复功能不受使用频率分级策略影响,始终允许 + LogHelper.WriteLogToFile($"DeviceIdentifier | 版本修复检查 - 当前版本: {currentVersion}, 可用版本: {availableVersion}, 结果: 允许"); + return true; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 版本修复检查失败: {ex.Message}", LogHelper.LogType.Error); + return true; // 出错时默认允许 + } + } + + + /// + /// 获取设备信息摘要(用于调试) + /// + public static string GetDeviceInfoSummary() + { + try + { + var (launchCount, totalMinutes, avgSession, priority) = GetUsageStats(); + var frequency = GetUsageFrequency(); + var stats = LoadUsageStats(); + var daysSinceLastUse = (DateTime.Now - stats.LastLaunchTime).TotalDays; + + return $"设备ID: {DeviceId}\n" + + $"启动次数: {launchCount}\n" + + $"总使用时长: {totalMinutes}分钟 ({totalMinutes / 60.0:F1}小时)\n" + + $"平均会话时长: {avgSession:F1}分钟\n" + + $"使用频率: {frequency}\n" + + $"更新优先级: {priority}\n" + + $"最后使用: {daysSinceLastUse:F1}天前\n" + + $"用户类型: {GetUserTypeDescription(stats)}"; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 获取设备信息摘要失败: {ex.Message}", LogHelper.LogType.Error); + return $"设备ID: {DeviceId}\n获取详细信息失败"; + } + } + + /// + /// 获取用户类型描述 + /// + private static string GetUserTypeDescription(UsageStats stats) + { + var isHeavyUser = stats.TotalUsageMinutes > 3000; + var isFrequentUser = stats.LaunchCount > 100; + var daysSinceLastUse = (DateTime.Now - stats.LastLaunchTime).TotalDays; + + var descriptions = new List(); + + if (isHeavyUser) descriptions.Add("重度用户"); + if (isFrequentUser) descriptions.Add("频繁用户"); + + if (daysSinceLastUse <= 3) descriptions.Add("高活跃"); + else if (daysSinceLastUse <= 14) descriptions.Add("中活跃"); + else if (daysSinceLastUse <= 30) descriptions.Add("低活跃"); + else descriptions.Add("非活跃"); + + return descriptions.Count > 0 ? string.Join(", ", descriptions) : "普通用户"; + } + + /// + /// 数据自检和修复 + /// + public static bool PerformDataIntegrityCheck() + { + try + { + lock (fileLock) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 开始数据完整性检查"); + + var issues = new List(); + var repaired = new List(); + + // 检查设备ID文件 + if (!File.Exists(DeviceIdFilePath)) + { + issues.Add("主设备ID文件丢失"); + if (File.Exists(BackupDeviceIdPath)) + { + var backupId = LoadDeviceIdFromFile(BackupDeviceIdPath); + if (!string.IsNullOrEmpty(backupId)) + { + SaveDeviceIdToFile(DeviceIdFilePath, backupId); + repaired.Add("从备份恢复主设备ID文件"); + } + } + } + + // 检查备份设备ID文件 + if (!File.Exists(BackupDeviceIdPath)) + { + issues.Add("备份设备ID文件丢失"); + var mainId = LoadDeviceIdFromFile(DeviceIdFilePath); + if (!string.IsNullOrEmpty(mainId)) + { + SaveDeviceIdToFile(BackupDeviceIdPath, mainId); + repaired.Add("重建备份设备ID文件"); + } + } + + // 检查使用统计文件 + if (!File.Exists(UsageStatsFilePath)) + { + issues.Add("主使用统计文件丢失"); + var backupStatsForRestore = LoadUsageStatsFromFile(BackupUsageStatsPath); + if (backupStatsForRestore != null) + { + SaveUsageStatsToFile(UsageStatsFilePath, backupStatsForRestore); + repaired.Add("从备份恢复主使用统计文件"); + } + } + + // 检查备份使用统计文件 + if (!File.Exists(BackupUsageStatsPath)) + { + issues.Add("备份使用统计文件丢失"); + var mainStatsForBackup = LoadUsageStatsFromFile(UsageStatsFilePath); + if (mainStatsForBackup != null) + { + SaveUsageStatsToFile(BackupUsageStatsPath, mainStatsForBackup); + repaired.Add("重建备份使用统计文件"); + } + } + + // 验证数据一致性 + var mainStats = LoadUsageStatsFromFile(UsageStatsFilePath); + var backupStats = LoadUsageStatsFromFile(BackupUsageStatsPath); + + if (mainStats != null && backupStats != null) + { + if (mainStats.LaunchCount != backupStats.LaunchCount || + mainStats.TotalUsageMinutes != backupStats.TotalUsageMinutes) + { + issues.Add("主备份数据不一致"); + // 使用最新的数据 + var newerStats = mainStats.LastModified > backupStats.LastModified ? mainStats : backupStats; + SaveUsageStatsToAllLocations(newerStats); + repaired.Add("同步主备份数据"); + } + } + + // 记录检查结果 + if (issues.Count > 0) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 发现问题: {string.Join(", ", issues)}", LogHelper.LogType.Warning); + } + + if (repaired.Count > 0) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 已修复: {string.Join(", ", repaired)}"); + } + + LogHelper.WriteLogToFile($"DeviceIdentifier | 数据完整性检查完成 - 问题: {issues.Count}, 修复: {repaired.Count}"); + return issues.Count == 0 || repaired.Count > 0; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 数据完整性检查失败: {ex.Message}", LogHelper.LogType.Error); + return false; + } + } + + /// + /// 获取使用频率数据保护状态摘要(强化版本) + /// + public static string GetUsageDataProtectionSummary() + { + try + { + var summary = new StringBuilder(); + summary.AppendLine("使用频率数据保护状态摘要:"); + + // 检查主要文件 + summary.AppendLine($"主使用统计文件: {(File.Exists(UsageStatsFilePath) ? "✓" : "✗")}"); + summary.AppendLine($"第一备份文件: {(File.Exists(BackupUsageStatsPath) ? "✓" : "✗")}"); + + // 检查多重隐藏备份 + summary.AppendLine($"第二备份文件: {(File.Exists(SecondaryUsageBackupPath) ? "✓" : "✗")}"); + summary.AppendLine($"第三备份文件: {(File.Exists(TertiaryUsageBackupPath) ? "✓" : "✗")}"); + summary.AppendLine($"第四备份文件: {(File.Exists(QuaternaryUsageBackupPath) ? "✓" : "✗")}"); + + // 检查注册表备份 + var registryBackups = 0; + var registryPaths = new[] + { + @"Software\ICC\DeviceInfo", + @"Software\Microsoft\Windows\CurrentVersion\ICC", + @"Software\Classes\.icc\UsageData", + @"Software\ICC\Config\Usage" + }; + + foreach (var path in registryPaths) + { + try + { + using (var key = Registry.CurrentUser.OpenSubKey(path)) + { + if (key != null) registryBackups++; + } + } + catch { } + } + + summary.AppendLine($"注册表备份位置: {registryBackups}/4 ✓"); + + // 检查数据完整性和可恢复性 + var stats = LoadUsageStats(); + if (stats != null) + { + summary.AppendLine($"数据完整性: {(stats.VerifyDataIntegrity() ? "✓" : "✗")}"); + summary.AppendLine($"总启动次数: {stats.LaunchCount}"); + summary.AppendLine($"总使用时长: {stats.TotalUsageMinutes}分钟 ({stats.TotalUsageMinutes / 60.0:F1}小时)"); + summary.AppendLine($"本周启动次数: {stats.WeeklyLaunchCount}"); + summary.AppendLine($"本周使用时长: {stats.WeeklyUsageMinutes}分钟 ({stats.WeeklyUsageMinutes / 60.0:F1}小时)"); + summary.AppendLine($"上周启动次数: {stats.LastWeekLaunchCount}"); + summary.AppendLine($"上周使用时长: {stats.LastWeekUsageMinutes}分钟 ({stats.LastWeekUsageMinutes / 60.0:F1}小时)"); + summary.AppendLine($"本周开始日期: {(stats.WeekStartDate != DateTime.MinValue ? stats.WeekStartDate.ToString("yyyy-MM-dd") : "未设置")}"); + summary.AppendLine($"使用频率: {stats.UsageFrequency}"); + summary.AppendLine($"更新优先级: {stats.UpdatePriority}"); + summary.AppendLine($"最后修改: {stats.LastModified:yyyy-MM-dd HH:mm:ss}"); + } + + // 计算保护强度评分 + var protectionScore = CalculateUsageDataProtectionScore(); + summary.AppendLine($"保护强度评分: {protectionScore}/100"); + + return summary.ToString(); + } + catch (Exception ex) + { + return $"获取使用频率数据保护状态失败: {ex.Message}"; + } + } + + /// + /// 计算使用频率数据保护强度评分 + /// + private static int CalculateUsageDataProtectionScore() + { + var score = 0; + + try + { + // 文件备份评分(50分) + if (File.Exists(UsageStatsFilePath)) score += 15; + if (File.Exists(BackupUsageStatsPath)) score += 10; + if (File.Exists(SecondaryUsageBackupPath)) score += 8; + if (File.Exists(TertiaryUsageBackupPath)) score += 8; + if (File.Exists(QuaternaryUsageBackupPath)) score += 9; + + // 注册表备份评分(30分) + var registryPaths = new[] + { + @"Software\ICC\DeviceInfo", + @"Software\Microsoft\Windows\CurrentVersion\ICC", + @"Software\Classes\.icc\UsageData", + @"Software\ICC\Config\Usage" + }; + + foreach (var path in registryPaths) + { + try + { + using (var key = Registry.CurrentUser.OpenSubKey(path)) + { + if (key != null) score += 7; + } + } + catch { } + } + + // 数据完整性评分(20分) + var stats = LoadUsageStats(); + if (stats != null) + { + if (!string.IsNullOrEmpty(stats.DataHash)) score += 10; + if (stats.VerifyDataIntegrity()) score += 10; + } + } + catch { } + + return Math.Min(100, score); + } + + /// + /// 强制重建所有使用频率数据备份 + /// + public static bool ForceRebuildUsageDataBackups() + { + try + { + lock (fileLock) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 开始强制重建使用频率数据备份"); + + var stats = LoadUsageStats(); + if (stats == null) + { + // 如果无法加载任何数据,创建基础数据 + stats = new UsageStats + { + DeviceId = DeviceId, + LastLaunchTime = DateTime.Now, + LaunchCount = 1, + TotalUsageMinutes = 0, + AverageSessionMinutes = 0, + LastUpdateCheck = DateTime.MinValue, + UpdatePriority = UpdatePriority.Medium, + UsageFrequency = UsageFrequency.Medium + }; + stats.UpdateDataHash(); + LogHelper.WriteLogToFile($"DeviceIdentifier | 创建新的基础使用数据"); + } + + // 强制保存到所有位置 + SaveUsageStatsToAllLocations(stats); + + // 验证重建结果 + var protectionScore = CalculateUsageDataProtectionScore(); + LogHelper.WriteLogToFile($"DeviceIdentifier | 使用频率数据备份重建完成,保护强度: {protectionScore}/100"); + + return protectionScore >= 80; // 80分以上认为重建成功 + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 强制重建使用频率数据备份失败: {ex.Message}", LogHelper.LogType.Error); + return false; + } + } + + /// + /// 获取数据保护状态摘要(保持向后兼容) + /// + public static string GetDataProtectionSummary() + { + return GetUsageDataProtectionSummary(); + } + + + + + + /// + /// 强制执行一次完整的数据保存操作(包括注册表) + /// + public static bool ForceCompleteDataSave() + { + try + { + lock (fileLock) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 开始强制完整数据保存"); + + // 保存设备ID到所有位置 + SaveDeviceIdToAllLocations(DeviceId); + + // 加载并保存使用统计到所有位置 + var stats = LoadUsageStats(); + if (stats != null) + { + stats.UpdateDataHash(); + SaveUsageStatsToAllLocations(stats); + + // 验证注册表保存是否成功 + var verificationResult = VerifyRegistryData(); + LogHelper.WriteLogToFile($"DeviceIdentifier | 注册表数据验证结果: {verificationResult}"); + + LogHelper.WriteLogToFile($"DeviceIdentifier | 强制完整数据保存完成"); + return true; + } + else + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 强制完整数据保存失败: 无法加载使用统计", LogHelper.LogType.Error); + return false; + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 强制完整数据保存失败: {ex.Message}", LogHelper.LogType.Error); + return false; + } + } + + /// + /// 验证注册表中的数据是否存在 + /// + public static string VerifyRegistryData() + { + var results = new StringBuilder(); + results.AppendLine("注册表数据验证结果:"); + + try + { + // 验证主注册表位置 + try + { + using (var key = Registry.CurrentUser.OpenSubKey(@"Software\ICC\DeviceInfo")) + { + if (key != null) + { + var deviceId = key.GetValue("DeviceId") as string; + var launchCount = key.GetValue("LaunchCount"); + var totalMinutes = key.GetValue("TotalUsageMinutes"); + var lastUpdate = key.GetValue("LastUpdate") as string; + + results.AppendLine($"✓ 主注册表位置存在"); + results.AppendLine($" 设备ID: {deviceId ?? "未找到"}"); + results.AppendLine($" 启动次数: {launchCount ?? "未找到"}"); + results.AppendLine($" 使用时长: {totalMinutes ?? "未找到"}分钟"); + results.AppendLine($" 最后更新: {lastUpdate ?? "未找到"}"); + } + else + { + results.AppendLine("✗ 主注册表位置不存在"); + } + } + } + catch (Exception ex) + { + results.AppendLine($"✗ 主注册表位置访问失败: {ex.Message}"); + } + + // 验证备用注册表位置 + var registryPaths = new[] + { + @"Software\Microsoft\Windows\CurrentVersion\ICC", + @"Software\Classes\.icc\UsageData", + @"Software\ICC\Config\Usage" + }; + + foreach (var path in registryPaths) + { + try + { + using (var key = Registry.CurrentUser.OpenSubKey(path)) + { + if (key != null) + { + var launchCount = key.GetValue("LC"); + var totalMinutes = key.GetValue("TUM"); + var lastUpdate = key.GetValue("LU"); + + results.AppendLine($"✓ 备用注册表位置存在: {path}"); + results.AppendLine($" 启动次数: {launchCount ?? "未找到"}"); + results.AppendLine($" 使用时长: {totalMinutes ?? "未找到"}"); + results.AppendLine($" 最后更新: {(lastUpdate != null ? DateTime.FromBinary(Convert.ToInt64(lastUpdate)).ToString("yyyy-MM-dd HH:mm:ss") : "未找到")}"); + } + else + { + results.AppendLine($"✗ 备用注册表位置不存在: {path}"); + } + } + } + catch (Exception ex) + { + results.AppendLine($"✗ 备用注册表位置访问失败 ({path}): {ex.Message}"); + } + } + + return results.ToString(); + } + catch (Exception ex) + { + return $"注册表数据验证失败: {ex.Message}"; + } + } + + /// + /// 立即执行一次数据保存并验证注册表写入 + /// + public static string SaveAndVerifyRegistryData() + { + try + { + LogHelper.WriteLogToFile($"DeviceIdentifier | 开始保存并验证注册表数据"); + + // 强制保存数据 + var saveSuccess = ForceCompleteDataSave(); + + // 验证注册表数据 + var verificationResult = VerifyRegistryData(); + + var result = $"保存操作: {(saveSuccess ? "成功" : "失败")}\n\n{verificationResult}"; + + LogHelper.WriteLogToFile($"DeviceIdentifier | 保存并验证注册表数据完成"); + return result; + } + catch (Exception ex) + { + var errorMsg = $"保存并验证注册表数据失败: {ex.Message}"; + LogHelper.WriteLogToFile($"DeviceIdentifier | {errorMsg}", LogHelper.LogType.Error); + return errorMsg; + } + } + } +} \ No newline at end of file diff --git a/Ink Canvas/Helpers/HardwareAcceleratedInkProcessor.cs b/Ink Canvas/Helpers/HardwareAcceleratedInkProcessor.cs new file mode 100644 index 00000000..76cbaf8f --- /dev/null +++ b/Ink Canvas/Helpers/HardwareAcceleratedInkProcessor.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Ink; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Effects; +using System.Windows.Media.Imaging; + +namespace Ink_Canvas.Helpers +{ + /// + /// 硬件加速的墨迹处理器,利用WPF的GPU渲染能力 + /// + public class HardwareAcceleratedInkProcessor + { + private readonly RenderTargetBitmap _renderTarget; + private readonly DrawingVisual _drawingVisual; + private readonly DrawingContext _drawingContext; + private bool _isInitialized = false; + + public HardwareAcceleratedInkProcessor(int width = 1920, int height = 1080) + { + // 创建硬件加速的渲染目标 + _renderTarget = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32); + _drawingVisual = new DrawingVisual(); + + // 启用硬件加速 + RenderOptions.SetBitmapScalingMode(_drawingVisual, BitmapScalingMode.HighQuality); + RenderOptions.SetEdgeMode(_drawingVisual, EdgeMode.Aliased); + + _isInitialized = true; + } + + /// + /// 使用GPU加速的贝塞尔曲线平滑 + /// + public async Task SmoothStrokeWithGPU(Stroke originalStroke) + { + if (!_isInitialized || originalStroke == null || originalStroke.StylusPoints.Count < 2) + return originalStroke; + + return await Task.Run(() => + { + try + { + // 使用PathGeometry进行硬件加速的曲线拟合 + var pathGeometry = CreateSmoothPathGeometry(originalStroke.StylusPoints); + + // 将PathGeometry转换回StylusPoint集合 + var smoothedPoints = ConvertPathGeometryToStylusPoints(pathGeometry, originalStroke.StylusPoints); + + return new Stroke(new StylusPointCollection(smoothedPoints)) + { + DrawingAttributes = originalStroke.DrawingAttributes.Clone() + }; + } + catch + { + return originalStroke; + } + }); + } + + /// + /// 创建平滑的路径几何体 + /// + private PathGeometry CreateSmoothPathGeometry(StylusPointCollection points) + { + var pathGeometry = new PathGeometry(); + var pathFigure = new PathFigure(); + + if (points.Count < 2) return pathGeometry; + + pathFigure.StartPoint = new Point(points[0].X, points[0].Y); + + // 使用贝塞尔曲线段创建平滑路径 + for (int i = 0; i < points.Count - 1; i += 3) + { + var p1 = i + 1 < points.Count ? new Point(points[i + 1].X, points[i + 1].Y) : pathFigure.StartPoint; + var p2 = i + 2 < points.Count ? new Point(points[i + 2].X, points[i + 2].Y) : p1; + var p3 = i + 3 < points.Count ? new Point(points[i + 3].X, points[i + 3].Y) : p2; + + var bezierSegment = new BezierSegment(p1, p2, p3, true); + pathFigure.Segments.Add(bezierSegment); + } + + pathGeometry.Figures.Add(pathFigure); + return pathGeometry; + } + + /// + /// 将PathGeometry转换为StylusPoint集合 + /// + private List ConvertPathGeometryToStylusPoints(PathGeometry pathGeometry, StylusPointCollection originalPoints) + { + var result = new List(); + var flattened = pathGeometry.GetFlattenedPathGeometry(); + + foreach (var figure in flattened.Figures) + { + result.Add(new StylusPoint(figure.StartPoint.X, figure.StartPoint.Y, 0.5f)); + + foreach (var segment in figure.Segments) + { + if (segment is LineSegment lineSegment) + { + result.Add(new StylusPoint(lineSegment.Point.X, lineSegment.Point.Y, 0.5f)); + } + else if (segment is PolyLineSegment polyLineSegment) + { + foreach (var point in polyLineSegment.Points) + { + result.Add(new StylusPoint(point.X, point.Y, 0.5f)); + } + } + } + } + + // 保持原始压感信息 + InterpolatePressure(result, originalPoints); + + return result; + } + + /// + /// 插值压感信息 + /// + private void InterpolatePressure(List smoothedPoints, StylusPointCollection originalPoints) + { + if (originalPoints.Count == 0 || smoothedPoints.Count == 0) return; + + for (int i = 0; i < smoothedPoints.Count; i++) + { + double ratio = (double)i / (smoothedPoints.Count - 1); + int originalIndex = (int)(ratio * (originalPoints.Count - 1)); + originalIndex = Math.Max(0, Math.Min(originalIndex, originalPoints.Count - 1)); + + var point = smoothedPoints[i]; + float pressure = originalPoints[originalIndex].PressureFactor; + smoothedPoints[i] = new StylusPoint(point.X, point.Y, Math.Max(pressure, 0.1f)); + } + } + + /// + /// 使用GPU加速的并行贝塞尔计算 + /// + public static StylusPoint[] ParallelBezierInterpolation(StylusPoint[] controlPoints, int segments = 16) + { + if (controlPoints.Length < 4) return controlPoints; + + var result = new StylusPoint[segments * (controlPoints.Length / 4)]; + + Parallel.For(0, controlPoints.Length / 4, segmentIndex => + { + var p0 = controlPoints[segmentIndex * 4]; + var p1 = controlPoints[segmentIndex * 4 + 1]; + var p2 = controlPoints[segmentIndex * 4 + 2]; + var p3 = controlPoints[segmentIndex * 4 + 3]; + + for (int i = 0; i < segments; i++) + { + double t = (double)i / (segments - 1); + result[segmentIndex * segments + i] = CubicBezierFast(p0, p1, p2, p3, t); + } + }); + + return result; + } + + /// + /// 优化的三次贝塞尔曲线计算 + /// + private static StylusPoint CubicBezierFast(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3, double t) + { + double u = 1 - t; + double tt = t * t; + double uu = u * u; + double uuu = uu * u; + double ttt = tt * t; + + double x = uuu * p0.X + 3 * uu * t * p1.X + 3 * u * tt * p2.X + ttt * p3.X; + double y = uuu * p0.Y + 3 * uu * t * p1.Y + 3 * u * tt * p2.Y + ttt * p3.Y; + float pressure = (float)(p1.PressureFactor * u + p2.PressureFactor * t); + + return new StylusPoint(x, y, Math.Max(pressure, 0.1f)); + } + + /// + /// 释放GPU资源 + /// + public void Dispose() + { + _drawingContext?.Close(); + _renderTarget?.Clear(); + _isInitialized = false; + } + } + + /// + /// 质量配置枚举 + /// + public enum InkSmoothingQuality + { + HighPerformance = 0, // 高性能低质量 + Balanced = 1, // 平衡 + HighQuality = 2 // 高质量低性能 + } + + /// + /// 墨迹平滑配置 + /// + public class InkSmoothingConfig + { + public InkSmoothingQuality Quality { get; set; } = InkSmoothingQuality.Balanced; + public bool UseHardwareAcceleration { get; set; } = true; + public bool UseAsyncProcessing { get; set; } = true; + public int MaxConcurrentTasks { get; set; } = Environment.ProcessorCount; + public double SmoothingStrength { get; set; } = 0.6; + public double ResampleInterval { get; set; } = 1.2; + public int InterpolationSteps { get; set; } = 16; + + public static InkSmoothingConfig FromSettings() + { + return new InkSmoothingConfig + { + Quality = (InkSmoothingQuality)MainWindow.Settings.Canvas.InkSmoothingQuality, + UseHardwareAcceleration = MainWindow.Settings.Canvas.UseHardwareAcceleration, + UseAsyncProcessing = MainWindow.Settings.Canvas.UseAsyncInkSmoothing, + MaxConcurrentTasks = MainWindow.Settings.Canvas.MaxConcurrentSmoothingTasks > 0 ? + MainWindow.Settings.Canvas.MaxConcurrentSmoothingTasks : Environment.ProcessorCount + }; + } + + public void ApplyQualitySettings() + { + switch (Quality) + { + case InkSmoothingQuality.HighPerformance: + SmoothingStrength = 0.4; + ResampleInterval = 2.0; + InterpolationSteps = 8; + break; + case InkSmoothingQuality.Balanced: + SmoothingStrength = 0.6; + ResampleInterval = 1.2; + InterpolationSteps = 16; + break; + case InkSmoothingQuality.HighQuality: + SmoothingStrength = 0.8; + ResampleInterval = 0.8; + InterpolationSteps = 32; + break; + } + } + } +} diff --git a/Ink Canvas/Helpers/InkSmoothingManager.cs b/Ink Canvas/Helpers/InkSmoothingManager.cs new file mode 100644 index 00000000..dc34c3f5 --- /dev/null +++ b/Ink Canvas/Helpers/InkSmoothingManager.cs @@ -0,0 +1,258 @@ +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Ink; +using System.Windows.Threading; + +namespace Ink_Canvas.Helpers +{ + /// + /// 统一的墨迹平滑管理器,整合异步处理和硬件加速 + /// + public class InkSmoothingManager : IDisposable + { + private readonly AsyncAdvancedBezierSmoothing _asyncSmoothing; + private readonly HardwareAcceleratedInkProcessor _hardwareProcessor; + private readonly InkSmoothingPerformanceMonitor _performanceMonitor; + private readonly InkSmoothingConfig _config; + private readonly Dispatcher _uiDispatcher; + private bool _disposed = false; + + public InkSmoothingManager(Dispatcher uiDispatcher) + { + _uiDispatcher = uiDispatcher; + _config = InkSmoothingConfig.FromSettings(); + _config.ApplyQualitySettings(); + + _asyncSmoothing = new AsyncAdvancedBezierSmoothing(uiDispatcher) + { + SmoothingStrength = _config.SmoothingStrength, + ResampleInterval = _config.ResampleInterval, + InterpolationSteps = _config.InterpolationSteps, + UseHardwareAcceleration = _config.UseHardwareAcceleration, + MaxConcurrentTasks = _config.MaxConcurrentTasks + }; + + _hardwareProcessor = new HardwareAcceleratedInkProcessor(); + _performanceMonitor = new InkSmoothingPerformanceMonitor(); + } + + /// + /// 平滑笔画(自动选择最佳方法) + /// + public async Task SmoothStrokeAsync(Stroke originalStroke, + Action onCompleted = null, + CancellationToken cancellationToken = default) + { + if (originalStroke == null || originalStroke.StylusPoints.Count < 2) + return originalStroke; + + var stopwatch = Stopwatch.StartNew(); + Stroke result = originalStroke; + + try + { + if (_config.UseAsyncProcessing) + { + // 使用异步处理 + result = await _asyncSmoothing.SmoothStrokeAsync(originalStroke, onCompleted, cancellationToken); + } + else if (_config.UseHardwareAcceleration) + { + // 使用硬件加速但同步处理 + result = await _hardwareProcessor.SmoothStrokeWithGPU(originalStroke); + onCompleted?.Invoke(originalStroke, result); + } + else + { + // 回退到传统同步处理 + result = await Task.Run(() => + { + var traditionalSmoothing = new AdvancedBezierSmoothing(); + return traditionalSmoothing.SmoothStroke(originalStroke); + }, cancellationToken); + onCompleted?.Invoke(originalStroke, result); + } + } + catch (OperationCanceledException) + { + result = originalStroke; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"墨迹平滑失败: {ex.Message}"); + result = originalStroke; + } + finally + { + stopwatch.Stop(); + _performanceMonitor.RecordProcessingTime(stopwatch.Elapsed); + } + + return result; + } + + /// + /// 同步平滑笔画(用于向后兼容) + /// + public Stroke SmoothStroke(Stroke originalStroke) + { + if (originalStroke == null || originalStroke.StylusPoints.Count < 2) + return originalStroke; + + var stopwatch = Stopwatch.StartNew(); + Stroke result; + + try + { + if (_config.UseHardwareAcceleration) + { + // 使用硬件加速的同步版本 + var task = _hardwareProcessor.SmoothStrokeWithGPU(originalStroke); + task.Wait(5000); // 5秒超时 + result = task.Status == TaskStatus.RanToCompletion ? task.Result : originalStroke; + } + else + { + // 传统同步处理 + var traditionalSmoothing = new AdvancedBezierSmoothing(); + result = traditionalSmoothing.SmoothStroke(originalStroke); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"同步墨迹平滑失败: {ex.Message}"); + result = originalStroke; + } + finally + { + stopwatch.Stop(); + _performanceMonitor.RecordProcessingTime(stopwatch.Elapsed); + } + + return result; + } + + /// + /// 更新配置 + /// + public void UpdateConfig() + { + var newConfig = InkSmoothingConfig.FromSettings(); + newConfig.ApplyQualitySettings(); + + _asyncSmoothing.SmoothingStrength = newConfig.SmoothingStrength; + _asyncSmoothing.ResampleInterval = newConfig.ResampleInterval; + _asyncSmoothing.InterpolationSteps = newConfig.InterpolationSteps; + _asyncSmoothing.UseHardwareAcceleration = newConfig.UseHardwareAcceleration; + _asyncSmoothing.MaxConcurrentTasks = newConfig.MaxConcurrentTasks; + } + + /// + /// 获取性能统计信息 + /// + public string GetPerformanceStats() + { + return $"平均处理时间: {_performanceMonitor.GetAverageProcessingTimeMs():F2}ms, " + + $"最大处理时间: {_performanceMonitor.GetMaxProcessingTimeMs():F2}ms, " + + $"样本数: {_performanceMonitor.GetSampleCount()}"; + } + + /// + /// 取消所有正在进行的任务 + /// + public void CancelAllTasks() + { + _asyncSmoothing?.CancelAllTasks(); + } + + /// + /// 检查系统是否支持硬件加速 + /// + public static bool IsHardwareAccelerationSupported() + { + try + { + return System.Windows.Media.RenderCapability.Tier >= 0x00020000; + } + catch + { + return false; + } + } + + /// + /// 获取推荐的配置 + /// + public static InkSmoothingConfig GetRecommendedConfig() + { + var config = new InkSmoothingConfig(); + + // 根据系统性能调整配置 + var processorCount = Environment.ProcessorCount; + var isHardwareAccelerated = IsHardwareAccelerationSupported(); + + if (processorCount >= 8 && isHardwareAccelerated) + { + config.Quality = InkSmoothingQuality.HighQuality; + config.UseHardwareAcceleration = true; + config.UseAsyncProcessing = true; + config.MaxConcurrentTasks = Math.Min(processorCount, 8); + } + else if (processorCount >= 4) + { + config.Quality = InkSmoothingQuality.Balanced; + config.UseHardwareAcceleration = isHardwareAccelerated; + config.UseAsyncProcessing = true; + config.MaxConcurrentTasks = Math.Min(processorCount, 4); + } + else + { + config.Quality = InkSmoothingQuality.HighPerformance; + config.UseHardwareAcceleration = false; + config.UseAsyncProcessing = false; + config.MaxConcurrentTasks = 1; + } + + config.ApplyQualitySettings(); + return config; + } + + /// + /// 应用推荐配置到设置 + /// + public static void ApplyRecommendedSettings() + { + var config = GetRecommendedConfig(); + + MainWindow.Settings.Canvas.InkSmoothingQuality = (int)config.Quality; + MainWindow.Settings.Canvas.UseHardwareAcceleration = config.UseHardwareAcceleration; + MainWindow.Settings.Canvas.UseAsyncInkSmoothing = config.UseAsyncProcessing; + MainWindow.Settings.Canvas.MaxConcurrentSmoothingTasks = config.MaxConcurrentTasks; + } + + public void Dispose() + { + if (!_disposed) + { + CancelAllTasks(); + _asyncSmoothing?.Dispose(); + _hardwareProcessor?.Dispose(); + _disposed = true; + } + } + } + + /// + /// 墨迹平滑事件参数 + /// + public class InkSmoothingEventArgs : EventArgs + { + public Stroke OriginalStroke { get; set; } + public Stroke SmoothedStroke { get; set; } + public TimeSpan ProcessingTime { get; set; } + public bool WasAsync { get; set; } + public bool UsedHardwareAcceleration { get; set; } + } +} diff --git a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherSettingsControl.xaml b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherSettingsControl.xaml index 01e5c5db..dde23f42 100644 --- a/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherSettingsControl.xaml +++ b/Ink Canvas/Helpers/Plugins/BuiltIn/SuperLauncher/LauncherSettingsControl.xaml @@ -1,12 +1,60 @@ - + + + + + + @@ -16,12 +64,12 @@ - - + + - - + + @@ -30,12 +78,12 @@ - + - + - - + + @@ -47,7 +95,7 @@ - + @@ -71,9 +119,15 @@ -