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 @@
-
-
-
+
+
+
@@ -81,7 +135,9 @@
-
+
\ No newline at end of file
diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml
index 81ddbb30..eb456c64 100644
--- a/Ink Canvas/MainWindow.xaml
+++ b/Ink Canvas/MainWindow.xaml
@@ -2594,6 +2594,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs
index b30ba004..3fedee0b 100644
--- a/Ink Canvas/MainWindow.xaml.cs
+++ b/Ink Canvas/MainWindow.xaml.cs
@@ -110,6 +110,9 @@ namespace Ink_Canvas {
CheckColorTheme(true);
CheckPenTypeUIState();
+ // 初始化墨迹平滑管理器
+ _inkSmoothingManager = new Helpers.InkSmoothingManager(Dispatcher);
+
// 注册输入事件
inkCanvas.PreviewMouseDown += inkCanvas_PreviewMouseDown;
inkCanvas.StylusDown += inkCanvas_StylusDown;
@@ -182,6 +185,7 @@ namespace Ink_Canvas {
private System.Windows.Media.Color Ink_DefaultColor = Colors.Red;
private DrawingAttributes drawingAttributes;
+ private Helpers.InkSmoothingManager _inkSmoothingManager;
private void loadPenCanvas() {
try {
@@ -854,6 +858,8 @@ namespace Ink_Canvas {
{
// 切换到关于页面
ShowSettingsSection("about");
+ // 刷新设备信息
+ RefreshDeviceInfo();
}
// 新增:个性化设置
@@ -881,6 +887,99 @@ namespace Ink_Canvas {
BorderSettingsMask.Background = null; // 确保清除蒙层背景
}
+ ///
+ /// 刷新设备信息按钮点击事件
+ ///
+ private void RefreshDeviceInfo_Click(object sender, RoutedEventArgs e)
+ {
+ RefreshDeviceInfo();
+ }
+
+ ///
+ /// 刷新设备信息显示
+ ///
+ private void RefreshDeviceInfo()
+ {
+ try
+ {
+ // 获取设备ID
+ string deviceId = DeviceIdentifier.GetDeviceId();
+ DeviceIdTextBlock.Text = deviceId;
+
+ // 获取使用频率
+ var usageFrequency = DeviceIdentifier.GetUsageFrequency();
+ string frequencyText;
+ switch (usageFrequency)
+ {
+ case DeviceIdentifier.UsageFrequency.High:
+ frequencyText = "高频用户";
+ break;
+ case DeviceIdentifier.UsageFrequency.Medium:
+ frequencyText = "中频用户";
+ break;
+ case DeviceIdentifier.UsageFrequency.Low:
+ frequencyText = "低频用户";
+ break;
+ default:
+ frequencyText = "未知";
+ break;
+ }
+ UsageFrequencyTextBlock.Text = frequencyText;
+
+ // 获取更新优先级
+ var updatePriority = DeviceIdentifier.GetUpdatePriority();
+ string priorityText;
+ switch (updatePriority)
+ {
+ case DeviceIdentifier.UpdatePriority.High:
+ priorityText = "高优先级(优先推送更新)";
+ break;
+ case DeviceIdentifier.UpdatePriority.Medium:
+ priorityText = "中优先级(正常推送更新)";
+ break;
+ case DeviceIdentifier.UpdatePriority.Low:
+ priorityText = "低优先级(延迟推送更新)";
+ break;
+ default:
+ priorityText = "未知";
+ break;
+ }
+ UpdatePriorityTextBlock.Text = priorityText;
+
+ // 获取使用统计
+ var (launchCount, totalMinutes, avgSession, _) = DeviceIdentifier.GetUsageStats();
+ LaunchCountTextBlock.Text = launchCount.ToString();
+
+ string totalUsageText;
+ if (totalMinutes < 60)
+ {
+ totalUsageText = $"{totalMinutes}分钟";
+ }
+ else if (totalMinutes < 1440)
+ {
+ totalUsageText = $"{totalMinutes / 60}小时{totalMinutes % 60}分钟";
+ }
+ else
+ {
+ totalUsageText = $"{totalMinutes / 1440}天{(totalMinutes % 1440) / 60}小时";
+ }
+ TotalUsageTextBlock.Text = totalUsageText;
+
+ LogHelper.WriteLogToFile($"MainWindow | 设备信息已刷新 - ID: {deviceId}, 频率: {frequencyText}, 优先级: {priorityText}");
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"MainWindow | 刷新设备信息失败: {ex.Message}", LogHelper.LogType.Error);
+
+ // 显示错误信息
+ DeviceIdTextBlock.Text = "获取失败";
+ UsageFrequencyTextBlock.Text = "获取失败";
+ UpdatePriorityTextBlock.Text = "获取失败";
+ LaunchCountTextBlock.Text = "获取失败";
+ TotalUsageTextBlock.Text = "获取失败";
+ }
+ }
+
// 新增:折叠侧边栏
private void CollapseNavSidebar_Click(object sender, RoutedEventArgs e)
{
diff --git a/Ink Canvas/MainWindow_cs/MW_PPT.cs b/Ink Canvas/MainWindow_cs/MW_PPT.cs
index 4bb128d2..69885c47 100644
--- a/Ink Canvas/MainWindow_cs/MW_PPT.cs
+++ b/Ink Canvas/MainWindow_cs/MW_PPT.cs
@@ -1270,10 +1270,27 @@ namespace Ink_Canvas {
LogHelper.WriteLogToFile($"结束PPT放映操作异常: {ex.ToString()}", LogHelper.LogType.Error);
}
await Application.Current.Dispatcher.InvokeAsync(() => {
+ // 隐藏侧边栏退出按钮
if (BtnExitPptFromSidebarLeft != null)
BtnExitPptFromSidebarLeft.Visibility = Visibility.Collapsed;
if (BtnExitPptFromSidebarRight != null)
BtnExitPptFromSidebarRight.Visibility = Visibility.Collapsed;
+
+ // 确保所有放映模式按钮都被隐藏,防止PptApplication_SlideShowEnd事件未触发的情况
+ try {
+ BtnPPTSlideShow.Visibility = Visibility.Visible;
+ BtnPPTSlideShowEnd.Visibility = Visibility.Collapsed;
+ StackPanelPPTControls.Visibility = Visibility.Collapsed;
+ LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
+ RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
+ LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
+ RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
+
+ LogHelper.WriteLogToFile("手动隐藏所有放映模式按钮", LogHelper.LogType.Trace);
+ }
+ catch (Exception ex) {
+ LogHelper.WriteLogToFile($"手动隐藏放映模式按钮失败: {ex.ToString()}", LogHelper.LogType.Error);
+ }
});
}
diff --git a/Ink Canvas/MainWindow_cs/MW_Settings.cs b/Ink Canvas/MainWindow_cs/MW_Settings.cs
index 51b8619a..bffec901 100644
--- a/Ink Canvas/MainWindow_cs/MW_Settings.cs
+++ b/Ink Canvas/MainWindow_cs/MW_Settings.cs
@@ -1298,7 +1298,7 @@ namespace Ink_Canvas {
private void ToggleSwitchAdvancedBezierSmoothing_Toggled(object sender, RoutedEventArgs e) {
if (!isLoaded) return;
Settings.Canvas.UseAdvancedBezierSmoothing = ToggleSwitchAdvancedBezierSmoothing.IsOn;
-
+
// 启用高级贝塞尔平滑时自动禁用原来的FitToCurve
if (ToggleSwitchAdvancedBezierSmoothing.IsOn)
{
@@ -1306,9 +1306,61 @@ namespace Ink_Canvas {
Settings.Canvas.FitToCurve = false;
drawingAttributes.FitToCurve = false;
}
-
+
+ // 更新墨迹平滑管理器配置
+ _inkSmoothingManager?.UpdateConfig();
+
SaveSettingsToFile();
}
+
+ // 注释掉这些方法,因为对应的UI控件还没有在XAML中定义
+ /*
+ private void ToggleSwitchAsyncInkSmoothing_Toggled(object sender, RoutedEventArgs e) {
+ if (!isLoaded) return;
+ Settings.Canvas.UseAsyncInkSmoothing = ToggleSwitchAsyncInkSmoothing.IsOn;
+ _inkSmoothingManager?.UpdateConfig();
+ SaveSettingsToFile();
+ }
+
+ private void ToggleSwitchHardwareAcceleration_Toggled(object sender, RoutedEventArgs e) {
+ if (!isLoaded) return;
+ Settings.Canvas.UseHardwareAcceleration = ToggleSwitchHardwareAcceleration.IsOn;
+ _inkSmoothingManager?.UpdateConfig();
+ SaveSettingsToFile();
+ }
+
+ private void ComboBoxInkSmoothingQuality_SelectionChanged(object sender, SelectionChangedEventArgs e) {
+ if (!isLoaded) return;
+ Settings.Canvas.InkSmoothingQuality = ComboBoxInkSmoothingQuality.SelectedIndex;
+ _inkSmoothingManager?.UpdateConfig();
+ SaveSettingsToFile();
+ }
+
+ private void SliderMaxConcurrentTasks_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) {
+ if (!isLoaded) return;
+ Settings.Canvas.MaxConcurrentSmoothingTasks = (int)SliderMaxConcurrentTasks.Value;
+ _inkSmoothingManager?.UpdateConfig();
+ SaveSettingsToFile();
+ }
+
+ private void ButtonApplyRecommendedSettings_Click(object sender, RoutedEventArgs e) {
+ // 应用推荐的性能设置
+ Helpers.InkSmoothingManager.ApplyRecommendedSettings();
+ LoadSettings(false);
+ _inkSmoothingManager?.UpdateConfig();
+ SaveSettingsToFile();
+
+ ShowNotification("已应用推荐的性能设置");
+ }
+
+ private void ButtonShowPerformanceStats_Click(object sender, RoutedEventArgs e) {
+ if (_inkSmoothingManager != null)
+ {
+ var stats = _inkSmoothingManager.GetPerformanceStats();
+ ShowNotification($"性能统计: {stats}");
+ }
+ }
+ */
private void ToggleSwitchAutoSaveStrokesInPowerPoint_Toggled(object sender, RoutedEventArgs e) {
if (!isLoaded) return;
diff --git a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs
index d122a8ae..09b258b8 100644
--- a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs
+++ b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs
@@ -538,6 +538,23 @@ namespace Ink_Canvas {
ToggleSwitchAdvancedBezierSmoothing.IsOn = false;
drawingAttributes.FitToCurve = false;
}
+
+ // 注释掉新的墨迹平滑性能设置,因为UI控件还没有定义
+ /*
+ // 初始化新的墨迹平滑性能设置
+ ToggleSwitchAsyncInkSmoothing.IsOn = Settings.Canvas.UseAsyncInkSmoothing;
+ ToggleSwitchHardwareAcceleration.IsOn = Settings.Canvas.UseHardwareAcceleration;
+ ComboBoxInkSmoothingQuality.SelectedIndex = Settings.Canvas.InkSmoothingQuality;
+ SliderMaxConcurrentTasks.Value = Settings.Canvas.MaxConcurrentSmoothingTasks > 0 ?
+ Settings.Canvas.MaxConcurrentSmoothingTasks : Environment.ProcessorCount;
+
+ // 检查硬件加速支持
+ if (!Helpers.InkSmoothingManager.IsHardwareAccelerationSupported())
+ {
+ ToggleSwitchHardwareAcceleration.IsEnabled = false;
+ // 可以添加提示文本说明硬件加速不可用
+ }
+ */
// 初始化直线自动拉直相关设置
ToggleSwitchAutoStraightenLine.IsOn = Settings.Canvas.AutoStraightenLine;
diff --git a/Ink Canvas/MainWindow_cs/MW_ShapeDrawing.cs b/Ink Canvas/MainWindow_cs/MW_ShapeDrawing.cs
index b971ccd9..975c007f 100644
--- a/Ink Canvas/MainWindow_cs/MW_ShapeDrawing.cs
+++ b/Ink Canvas/MainWindow_cs/MW_ShapeDrawing.cs
@@ -1579,17 +1579,16 @@ namespace Ink_Canvas {
{
try
{
- var advancedSmoothing = new Helpers.AdvancedBezierSmoothing
- {
- };
-
// 对临时笔画应用平滑
- if (lastTempStroke != null)
+ if (lastTempStroke != null && _inkSmoothingManager != null)
{
- var smoothedStroke = advancedSmoothing.SmoothStroke(lastTempStroke);
- inkCanvas.Strokes.Remove(lastTempStroke);
- lastTempStroke = smoothedStroke;
- inkCanvas.Strokes.Add(smoothedStroke);
+ var smoothedStroke = _inkSmoothingManager.SmoothStroke(lastTempStroke);
+ if (smoothedStroke != lastTempStroke)
+ {
+ inkCanvas.Strokes.Remove(lastTempStroke);
+ lastTempStroke = smoothedStroke;
+ inkCanvas.Strokes.Add(smoothedStroke);
+ }
}
}
catch (Exception ex)
diff --git a/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs b/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs
index 18499916..6d344408 100644
--- a/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs
+++ b/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Ink;
@@ -587,18 +588,27 @@ namespace Ink_Canvas {
// 检查原始笔画是否仍然存在于画布中
if (inkCanvas.Strokes.Contains(e.Stroke))
{
- var advancedSmoothing = new Helpers.AdvancedBezierSmoothing
+ // 使用新的异步墨迹平滑管理器
+ if (Settings.Canvas.UseAsyncInkSmoothing && _inkSmoothingManager != null)
{
- };
+ // 异步处理
+ _ = ProcessStrokeAsync(e.Stroke);
+ }
+ else
+ {
+ // 同步处理(向后兼容)
+ var smoothedStroke = _inkSmoothingManager?.SmoothStroke(e.Stroke) ?? e.Stroke;
- var smoothedStroke = advancedSmoothing.SmoothStroke(e.Stroke);
-
- // 替换原始笔画
- SetNewBackupOfStroke();
- _currentCommitType = CommitReason.ShapeRecognition;
- inkCanvas.Strokes.Remove(e.Stroke);
- inkCanvas.Strokes.Add(smoothedStroke);
- _currentCommitType = CommitReason.UserInput;
+ if (smoothedStroke != e.Stroke)
+ {
+ // 替换原始笔画
+ SetNewBackupOfStroke();
+ _currentCommitType = CommitReason.ShapeRecognition;
+ inkCanvas.Strokes.Remove(e.Stroke);
+ inkCanvas.Strokes.Add(smoothedStroke);
+ _currentCommitType = CommitReason.UserInput;
+ }
+ }
}
}
catch (Exception ex)
@@ -613,6 +623,32 @@ namespace Ink_Canvas {
}
}
+ ///
+ /// 异步处理笔画平滑
+ ///
+ private async Task ProcessStrokeAsync(Stroke originalStroke)
+ {
+ try
+ {
+ await _inkSmoothingManager.SmoothStrokeAsync(originalStroke, (original, smoothed) =>
+ {
+ // 在UI线程上执行笔画替换
+ if (inkCanvas.Strokes.Contains(original) && smoothed != original)
+ {
+ SetNewBackupOfStroke();
+ _currentCommitType = CommitReason.ShapeRecognition;
+ inkCanvas.Strokes.Remove(original);
+ inkCanvas.Strokes.Add(smoothed);
+ _currentCommitType = CommitReason.UserInput;
+ }
+ });
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"异步墨迹平滑失败: {ex.Message}");
+ }
+ }
+
// New method: Checks if a stroke is potentially a straight line
private bool IsPotentialStraightLine(Stroke stroke) {
// 确保有足够的点来进行线条分析
diff --git a/Ink Canvas/Properties/AssemblyInfo.cs b/Ink Canvas/Properties/AssemblyInfo.cs
index d5760b20..53f5fc1e 100644
--- a/Ink Canvas/Properties/AssemblyInfo.cs
+++ b/Ink Canvas/Properties/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/Resources/Settings.cs b/Ink Canvas/Resources/Settings.cs
index 74ba6e69..f76a5d58 100644
--- a/Ink Canvas/Resources/Settings.cs
+++ b/Ink Canvas/Resources/Settings.cs
@@ -50,6 +50,14 @@ namespace Ink_Canvas
public bool FitToCurve { get; set; } = false; // 默认关闭原来的贝塞尔平滑
[JsonProperty("useAdvancedBezierSmoothing")]
public bool UseAdvancedBezierSmoothing { get; set; } = true; // 默认启用高级贝塞尔曲线平滑
+ [JsonProperty("useAsyncInkSmoothing")]
+ public bool UseAsyncInkSmoothing { get; set; } = true; // 默认启用异步墨迹平滑
+ [JsonProperty("useHardwareAcceleration")]
+ public bool UseHardwareAcceleration { get; set; } = true; // 默认启用硬件加速
+ [JsonProperty("inkSmoothingQuality")]
+ public int InkSmoothingQuality { get; set; } = 1; // 0-低质量高性能, 1-平衡, 2-高质量低性能
+ [JsonProperty("maxConcurrentSmoothingTasks")]
+ public int MaxConcurrentSmoothingTasks { get; set; } = 0; // 0表示自动检测CPU核心数
[JsonProperty("clearCanvasAndClearTimeMachine")]
public bool ClearCanvasAndClearTimeMachine { get; set; } = false;
[JsonProperty("enablePressureTouchMode")]
diff --git a/Ink Canvas/Windows/PluginSettingsWindow.xaml b/Ink Canvas/Windows/PluginSettingsWindow.xaml
index d9ed9067..cc3ef89b 100644
--- a/Ink Canvas/Windows/PluginSettingsWindow.xaml
+++ b/Ink Canvas/Windows/PluginSettingsWindow.xaml
@@ -54,7 +54,7 @@
-
+
@@ -101,17 +101,17 @@
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+