feat(docstring):添加部分docstring (#376)
* feat(docstring):添加docstring Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com> * fix(docstring):修复部分docstring格式错误 Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com> * fix(docstring):修复部分docstring Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com> * chore(Docstring):MW_* 前14 * chore(Docstring):MW_* part 2 * chore(Docstring):MW_* part 3 * chore:优化缩进 * fix: 修复数学计算中的潜在除零错误和数值稳定性问题 Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com> * chore:删除Rebase时多余的OOBE函数 * chore: 更新代码注释和文档格式 修复XML文档注释中的格式问题,统一使用<c>和<see>标签 更新ConfigHelper类的预留说明,明确未来扩展用途 优化TimerDisplayDate_Elapsed方法的注释,说明UI异步更新机制 合并重复的注释摘要行,提高文档可读性 添加形状识别功能的64位进程限制说明 修正视频呈现器设备选择逻辑的文档说明 * chore(IPPTLinkManager): 更新TryEndSlideShow方法的XML注释格式 * chore: 修正代码注释中的术语和格式问题 更新多个文件中的XML注释,统一使用<see langword="..."/>标记代替<c>...</c>标记 规范术语使用(如"延迟初始化"代替"懒惰初始化") 修正注释中的格式错误和补充说明 调整代码区域的注释对齐格式 --------- Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com>
This commit is contained in:
@@ -19,7 +19,7 @@ namespace Ink_Canvas.Helpers
|
||||
/// 检查是否需要执行自动备份
|
||||
/// </summary>
|
||||
/// <param name="settings">设置对象</param>
|
||||
/// <returns>如果需要备份返回true,否则返回false</returns>
|
||||
/// <returns>如果需要备份返回<see langword="true"/>,否则返回<see langword="false"/></returns>
|
||||
public static bool ShouldPerformAutoBackup(Settings settings)
|
||||
{
|
||||
try
|
||||
@@ -50,8 +50,11 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 执行自动备份
|
||||
/// </summary>
|
||||
/// <param name="settings">设置对象</param>
|
||||
/// <returns>备份是否成功</returns>
|
||||
/// <remarks>
|
||||
/// 为主配置文件创建一次自动备份并在成功后更新并保存设置中的最后备份时间。
|
||||
/// </remarks>
|
||||
/// <param name="settings">应用的设置对象;在成功备份后会更新 settings.Advanced.LastAutoBackupTime 并调用保存操作。</param>
|
||||
/// <returns><see langword="true"/> 表示备份成功,<see langword="false"/> 表示备份失败或被跳过。</returns>
|
||||
public static bool PerformAutoBackup(Settings settings)
|
||||
{
|
||||
try
|
||||
@@ -91,7 +94,10 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 尝试从备份恢复配置文件
|
||||
/// </summary>
|
||||
/// <returns>恢复是否成功</returns>
|
||||
/// <remarks>
|
||||
/// 从最新可用的自动备份恢复主设置文件(Settings.json)。如果当前设置文件存在,会先将其复制到备份目录并加上时间戳作为“损坏”的备份副本,然后用最新备份覆盖原文件。
|
||||
/// </remarks>
|
||||
/// <returns><see langword="true"/> 如果恢复成功,<see langword="false"/> 否则。</returns>
|
||||
public static bool TryRestoreFromBackup()
|
||||
{
|
||||
try
|
||||
@@ -156,6 +162,10 @@ namespace Ink_Canvas.Helpers
|
||||
/// 清理过期的备份文件
|
||||
/// 保留最近30天的备份文件
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 删除备份目录中按“备份前缀”匹配且创建时间早于 30 天的自动备份文件(即自动备份文件的命名前缀),不会删除诸如 Settings_Corrupted_*.json 之类的其他备份或错误状态文件。
|
||||
/// 如果备份目录不存在则不执行任何操作;删除操作在受写入保护的上下文中执行,任何错误会被记录但不会抛出异常。
|
||||
/// </remarks>
|
||||
public static void CleanupOldBackups()
|
||||
{
|
||||
try
|
||||
|
||||
@@ -1320,7 +1320,13 @@ namespace Ink_Canvas.Helpers
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 保存下载状态
|
||||
/// <summary>
|
||||
/// 将下载完成状态写入预定义的状态文件以供后续检查。
|
||||
/// </summary>
|
||||
/// <param name="isSuccess">指示下载是否成功;将以字符串形式写入状态文件("True" 或 "False")。</param>
|
||||
/// <remarks>
|
||||
/// 如果状态文件路径为空则不执行任何操作;方法内部捕获异常并记录日志,不会向调用方抛出异常。
|
||||
/// </remarks>
|
||||
private static void SaveDownloadStatus(bool isSuccess)
|
||||
{
|
||||
try
|
||||
@@ -1341,7 +1347,13 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
// 安装新版本应用
|
||||
/// <summary>
|
||||
/// 安装指定版本的更新包并启动新版本进程以完成替换,然后退出当前应用程序。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该方法会临时将 App.IsUpdateInstalling 置为 true、尝试关闭进程保护(并在结束时还原)、在必要时备份当前设置、解压更新 ZIP、启动解压后的新可执行文件(以更新模式传递旧进程 ID、解压路径和目标路径等参数),并在新进程启动后关闭当前进程。方法会记录日志并在遇到错误时安全退出相应步骤,但不会抛出异常给调用方以外的上下文。</remarks>
|
||||
/// <param name="version">要安装的版本号,用于定位更新包文件名(例如 InkCanvasForClass.CE.{version}.zip)。</param>
|
||||
/// <param name="isInSilence">指示是否以静默模式启动新版本(影响传递给新进程的参数和可能的用户提示)。</param>
|
||||
public static void InstallNewVersionApp(string version, bool isInSilence)
|
||||
{
|
||||
bool wasProcessProtectionEnabled = false;
|
||||
@@ -2304,4 +2316,4 @@ namespace Ink_Canvas.Helpers
|
||||
return currentTime >= StartTime || currentTime <= EndTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,10 +47,22 @@ namespace Ink_Canvas.Helpers
|
||||
#endregion
|
||||
|
||||
#region 生命周期管理
|
||||
/// <summary>
|
||||
/// 开始监控本地 PowerPoint 的连接与运行状态,并在状态变化时触发相应事件。
|
||||
/// </summary>
|
||||
public void StartMonitoring() => _inner.StartMonitoring();
|
||||
|
||||
/// <summary>
|
||||
/// 停止对 PowerPoint 的监控,断开当前连接并停止触发相关事件。
|
||||
/// </summary>
|
||||
public void StopMonitoring() => _inner.StopMonitoring();
|
||||
|
||||
/// <summary>
|
||||
/// 强制断开当前 COM PPT 连接并停止对其监控,同时写入事件日志。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 会向日志记录一条事件信息并调用内部管理器停止监控;该方法不会重新启动监控或重新初始化内部管理器实例。
|
||||
/// </remarks>
|
||||
public void ReloadConnection()
|
||||
{
|
||||
LogHelper.WriteLogToFile("COM PPT 执行热重载:强制断开并重新连接", LogHelper.LogType.Event);
|
||||
@@ -90,4 +102,3 @@ namespace Ink_Canvas.Helpers
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -360,6 +360,11 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 保存设备ID到文件
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 将设备标识信息以格式化的 JSON 写入指定文件,并确保目标目录存在;在失败时记录错误但不抛出异常。
|
||||
/// </remarks>
|
||||
/// <param name="filePath">目标文件的完整路径,用于保存设备标识信息。</param>
|
||||
/// <param name="info">要保存的设备标识信息对象(包含 DeviceId 和 可选的硬件指纹)。</param>
|
||||
private static void SaveDeviceIdToFile(string filePath, DeviceIdInfo info)
|
||||
{
|
||||
try
|
||||
@@ -1619,4 +1624,3 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,14 +20,32 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
object PPTApplication { get; }
|
||||
|
||||
// 生命周期管理
|
||||
/// <summary>
|
||||
/// 开始监视与 PowerPoint 的连接以及幻灯片放映相关状态,并在状态变化时触发对应事件。
|
||||
/// </summary>
|
||||
void StartMonitoring();
|
||||
/// <summary>
|
||||
/// 停止监控 PowerPoint 的连接与事件,停止接收并处理与演示文稿和幻灯片放映相关的通知。
|
||||
/// </summary>
|
||||
void StopMonitoring();
|
||||
|
||||
/// <summary>
|
||||
/// 重新加载或重建与 PowerPoint 的连接。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 调用后实现应刷新内部连接与状态,必要时重建与 PowerPoint 的会话;此操作可能导致 IsConnected 变化并触发 PPTConnectionChanged 或其他相关事件(例如 SlideShowStateChanged)。
|
||||
/// </remarks>
|
||||
void ReloadConnection();
|
||||
|
||||
// 放映控制
|
||||
/// <summary>
|
||||
/// 尝试启动当前演示文稿的放映模式。
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> 如果放映已成功启动,<c>false</c> 否则。</returns>
|
||||
bool TryStartSlideShow();
|
||||
/// <summary>
|
||||
/// 尝试结束当前正在进行的幻灯片放映。
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> 如果放映已成功结束,<c>false</c> 否则。</returns>
|
||||
bool TryEndSlideShow();
|
||||
|
||||
// 导航控制
|
||||
@@ -43,4 +61,3 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -29,6 +29,11 @@ namespace Ink_Canvas.Helpers
|
||||
WriteLogToFile(msg, LogType.Error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一条日志消息记录到应用的日志文件中(可能是单一日志文件或按启动时间存档的文件),同时在日志条目中包含时间戳、线程 ID 和调用者信息,并遵循应用的日志设置。
|
||||
/// </summary>
|
||||
/// <param name="str">要记录的日志文本消息。</param>
|
||||
/// <param name="logType">日志的类型/等级,用于在日志条目中标识(例如 Info、Error、Warning 等)。</param>
|
||||
public static void WriteLogToFile(string str, LogType logType = LogType.Info)
|
||||
{
|
||||
// 检查日志是否启用
|
||||
@@ -92,6 +97,16 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查指定日志文件夹的总大小,并在超过 MaxLogsFolderSizeBytes 时删除该文件夹下的所有文件并记录清理日志。
|
||||
/// </summary>
|
||||
/// <param name="logsPath">要检查和清理的日志文件夹路径。</param>
|
||||
/// <remarks>
|
||||
/// - 如果目录不存在则直接返回。
|
||||
/// - 当总大小超过 MaxLogsFolderSizeBytes 时,会尝试删除目录下的每个文件(单个删除失败将被忽略)。
|
||||
/// - 清理完成后会向该目录下的 Log_{AppStartTime}.txt 写入一条带有时间戳和 [Cleanup] 标签的记录。
|
||||
/// - 方法内部捕获并忽略所有异常以避免影响调用者流程。
|
||||
/// </remarks>
|
||||
private static void CheckAndCleanLogsFolder(string logsPath)
|
||||
{
|
||||
try
|
||||
@@ -155,4 +170,4 @@ namespace Ink_Canvas.Helpers
|
||||
Warning
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,11 +46,18 @@ namespace Ink_Canvas.Helpers
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
/// <summary>
|
||||
/// 初始化 PPTInkManager 实例并为内部内存流分配初始容量以跟踪默认最大幻灯片数加上备用槽位。
|
||||
/// </summary>
|
||||
public PPTInkManager()
|
||||
{
|
||||
InitializeMemoryStreams(DefaultMaxSlides + 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据指定容量初始化用于存储每页墨迹的内存流数组。
|
||||
/// </summary>
|
||||
/// <param name="capacity">期望的数组容量;如果小于 2,则会使用 2 作为最小容量。</param>
|
||||
private void InitializeMemoryStreams(int capacity)
|
||||
{
|
||||
if (_memoryStreams != null)
|
||||
@@ -69,6 +76,12 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 初始化新的演示文稿
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 为新的或当前的演示文稿初始化墨迹管理器的内部状态。
|
||||
/// 方法会清除所有内存中的笔迹数据,重置墨迹写入锁与快速切换追踪,并根据演示文稿的幻灯片数量分配内部内存缓冲区。
|
||||
/// 如果已启用自动保存且设置了 <see cref="AutoSaveLocation"/>,则会尝试加载磁盘上的已保存墨迹文件。
|
||||
/// </remarks>
|
||||
/// <param name="presentation">要初始化的 PowerPoint Presentation 实例;为 null 时方法不执行任何操作并直接返回。</param>
|
||||
public void InitializePresentation(Presentation presentation)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -115,6 +128,11 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 保存当前页面的墨迹
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 将指定幻灯片的墨迹保存到内部内存缓存,并在必要时触发内存清理。
|
||||
/// </remarks>
|
||||
/// <param name="slideIndex">要保存的幻灯片索引(从 1 开始)。方法在索引小于或等于 0 时不执行任何操作。</param>
|
||||
/// <param name="strokes">要保存的墨迹集合;为 null 时方法不执行任何操作。</param>
|
||||
public void SaveCurrentSlideStrokes(int slideIndex, StrokeCollection strokes)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -140,6 +158,11 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 强制保存指定页墨迹到内存(不受锁定限制)。用于放映结束前保存当前画布到当前页。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 强制将指定幻灯片的墨迹保存到内部内存缓存,覆盖该幻灯片已有的墨迹数据。
|
||||
/// </remarks>
|
||||
/// <param name="slideIndex">要保存的幻灯片索引(从 1 开始)。</param>
|
||||
/// <param name="strokes">要保存的墨迹集合,不能为空。</param>
|
||||
public void ForceSaveSlideStrokes(int slideIndex, StrokeCollection strokes)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -163,6 +186,11 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 加载指定页面的墨迹
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 加载并返回指定幻灯片的墨迹集合。
|
||||
/// </remarks>
|
||||
/// <param name="slideIndex">要加载的幻灯片索引(从1开始)。</param>
|
||||
/// <returns>包含指定幻灯片的墨迹的 StrokeCollection;如果该幻灯片没有已保存的墨迹或加载失败,则返回空的 StrokeCollection。</returns>
|
||||
public StrokeCollection LoadSlideStrokes(int slideIndex)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -190,6 +218,12 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 切换到指定页面并加载墨迹
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 切换到指定幻灯片并返回该幻灯片的已加载笔迹集合。
|
||||
/// </remarks>
|
||||
/// <param name="slideIndex">要切换到的幻灯片索引(从 1 开始)。</param>
|
||||
/// <param name="currentStrokes">可选的当前笔迹集合,用于在切换时提供当前画面状态。</param>
|
||||
/// <returns>`StrokeCollection`:指定幻灯片已加载的笔迹集合;若加载失败则返回一个空的 `StrokeCollection`。</returns>
|
||||
public StrokeCollection SwitchToSlide(int slideIndex, StrokeCollection currentStrokes = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -221,8 +255,12 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 保存所有墨迹到文件
|
||||
/// </summary>
|
||||
/// <param name="presentation">演示文稿对象</param>
|
||||
/// <param name="currentSlideIndex">当前播放的页码,如果提供则使用此值保存位置,否则使用_lockedSlideIndex</param>
|
||||
/// <remarks>
|
||||
/// 将内存中当前演示文稿的每页墨迹保存到磁盘,并根据情况写入当前播放位置文件。
|
||||
/// 仅在 IsAutoSaveEnabled 为真且 AutoSaveLocation 已设置时执行。会在演示文稿专属文件夹中写入按页编号的墨迹文件(带 `.icstk` 扩展名)和可选的 Position 文件。遇到特定 COM 错误(HRESULT 0x80048010)时会中止保存当前幻灯片计数读取而不抛出异常;单页保存失败会记录错误并继续处理其他页。
|
||||
/// </remarks>
|
||||
/// <param name="presentation">要保存墨迹的 PowerPoint 演示文稿对象。</param>
|
||||
/// <param name="currentSlideIndex">当前播放的页码;如果大于 0 则以此值写入 Position 文件,否则使用当前被锁定的页码或最后切换的页码作为保存位置。</param>
|
||||
public void SaveAllStrokesToFile(Presentation presentation, int currentSlideIndex = -1)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -288,6 +326,12 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 从文件加载已保存的墨迹
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 从自动保存目录加载已保存的幻灯片墨迹数据到内存流中,供后续显示和编辑使用。
|
||||
/// 仅在启用自动保存且已设置 AutoSaveLocation 时执行。函数获取当前演示文稿的自动保存文件夹,遍历以 <c>.icstk</c> 为扩展名的文件,
|
||||
/// 将文件名(去除扩展名)解析为幻灯片索引并在合法且文件大小大于 8 字节时加载到对应的内存流槽位。对单个文件的读取失败会记录错误并继续处理其他文件;
|
||||
/// 若成功加载则会记录已加载页数。方法在内部使用锁以保证线程安全。
|
||||
/// </remarks>
|
||||
public void LoadSavedStrokes()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -337,6 +381,10 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 清除所有墨迹
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 清除并释放当前演示文稿所有幻灯片的墨迹数据和相关内存资源。
|
||||
/// 该方法在内部加锁以保证线程安全;会处置并清空所有内部存储的墨迹流、重建内部流数组并清空 CurrentStrokes。
|
||||
/// </remarks>
|
||||
public void ClearAllStrokes()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -346,6 +394,10 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为指定幻灯片设置短时墨迹写入锁,防止在该时间窗口内对其他幻灯片进行写入操作。
|
||||
/// </summary>
|
||||
/// <param name="slideIndex">要上锁的幻灯片索引(大于 0)。锁从调用时刻开始,持续 InkLockMilliseconds 毫秒。</param>
|
||||
public void LockInkForSlide(int slideIndex)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -353,6 +405,11 @@ namespace Ink_Canvas.Helpers
|
||||
_lockedSlideIndex = slideIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确定在当前滑页上下文中是否允许写入墨迹(基于短期的墨迹写入锁与容差窗口)。
|
||||
/// </summary>
|
||||
/// <param name="currentSlideIndex">当前尝试写入墨迹的幻灯片索引(从 1 开始)。</param>
|
||||
/// <returns>`true` 如果允许写入墨迹(锁已过期、目标为被锁定的幻灯片,或处于短暂的容差窗口内),`false` 否则。</returns>
|
||||
public bool CanWriteInk(int currentSlideIndex)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -362,6 +419,12 @@ namespace Ink_Canvas.Helpers
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置与墨迹书写和幻灯片切换相关的锁与跟踪状态为初始(未锁定)值。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 将内部的墨迹写入到期时间、当前被锁定的幻灯片索引、上次切换时间和上次切换的幻灯片索引均恢复为默认未设置状态。
|
||||
/// </remarks>
|
||||
public void ResetLockState()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -378,6 +441,14 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
#region Private Helpers
|
||||
|
||||
/// <summary>
|
||||
/// 释放并清除类内用于存储各页墨迹的所有内存流,清空当前画笔集合,并重置内部内存流数组容量为 _maxSlides + 2。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// - 会逐个释放已存在的 MemoryStream(忽略释放过程中的异常),并将对应槽位设为 null。
|
||||
/// - 会清空 CurrentStrokes 集合。
|
||||
/// - 会记录一条跟踪日志,指示已完成清除操作。
|
||||
/// </remarks>
|
||||
private void ClearAllStrokesInternal()
|
||||
{
|
||||
if (_memoryStreams != null)
|
||||
@@ -393,6 +464,11 @@ namespace Ink_Canvas.Helpers
|
||||
LogHelper.WriteLogToFile("已清除所有墨迹", LogHelper.LogType.Trace);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用指定的笔迹集合替换内部存储中对应幻灯片索引的内存流:释放(并忽略释放错误)旧流,将 <paramref name="strokes"/> 序列化到新的 <see cref="MemoryStream"/> 并保存回内部数组。
|
||||
/// </summary>
|
||||
/// <param name="slideIndex">要替换的幻灯片索引(内部内存流数组的索引)。</param>
|
||||
/// <param name="strokes">要序列化并保存到内存流的笔迹集合。</param>
|
||||
private void ReplaceSlideStream(int slideIndex, StrokeCollection strokes)
|
||||
{
|
||||
try { _memoryStreams[slideIndex]?.Dispose(); } catch (Exception ex) { LogHelper.WriteLogToFile($"释放旧内存流失败: {ex}", LogHelper.LogType.Warning); }
|
||||
@@ -402,6 +478,11 @@ namespace Ink_Canvas.Helpers
|
||||
_memoryStreams[slideIndex] = ms;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从指定文件夹删除对应幻灯片的笔迹文件(按四位索引命名);如果文件不存在或删除失败则静默忽略错误。
|
||||
/// </summary>
|
||||
/// <param name="folderPath">存放笔迹文件的文件夹路径。</param>
|
||||
/// <param name="slideIndex">用于生成文件名的幻灯片索引(格式化为四位,例如 1 -> "0001")。</param>
|
||||
private void TryDeleteStrokeFile(string folderPath, int slideIndex)
|
||||
{
|
||||
try
|
||||
@@ -412,6 +493,12 @@ namespace Ink_Canvas.Helpers
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查当前墨迹内存使用状况并在超过阈值时触发清理操作。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 会更新内部的内存使用统计并刷新上次清理时间;当总占用超过 MaxMemoryUsageBytes 时,会记录警告并调用 CleanupInactiveSlideStrokes 清理不活跃幻灯页的墨迹流。若检查或清理过程中发生异常,会记录错误日志。
|
||||
/// </remarks>
|
||||
private void CheckAndPerformMemoryCleanup()
|
||||
{
|
||||
try
|
||||
@@ -440,6 +527,12 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理不活跃幻灯片的内存化墨迹数据以回收内存空间。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 将释放除当前锁定幻灯片与最近切换幻灯片之外的每页内存流(若存在),并将对应数组项设为 null;完成后若有释放,会记录已清理页数与释放的总大小(KB)。
|
||||
/// </remarks>
|
||||
private void CleanupInactiveSlideStrokes()
|
||||
{
|
||||
if (_memoryStreams == null) return;
|
||||
@@ -459,6 +552,10 @@ namespace Ink_Canvas.Helpers
|
||||
LogHelper.WriteLogToFile($"已清理 {cleaned} 页墨迹,释放 {freed / 1024}KB", LogHelper.LogType.Trace);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成基于演示文稿名称、幻灯片数量和路径哈希的标识符字符串。
|
||||
/// </summary>
|
||||
/// <returns>由 `名称_幻灯片数_路径哈希` 组成的标识符;若生成失败则返回形如 `unknown_{ticks}` 的回退标识符。</returns>
|
||||
private string GeneratePresentationId(Presentation presentation)
|
||||
{
|
||||
try
|
||||
@@ -483,6 +580,12 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
#region Dispose
|
||||
|
||||
/// <summary>
|
||||
/// 释放 PPTInkManager 持有的资源并清除所有内存中的笔迹数据。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 调用后该实例将进入已释放状态,不应再被使用。方法为幂等且线程安全:如果已释放则立即返回,否则在同步区内清理资源并标记为已释放。
|
||||
/// </remarks>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
@@ -498,4 +601,4 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,6 +119,13 @@ namespace Ink_Canvas.Helpers
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// 在系统的运行对象表(ROT)中查找并返回最合适的正在运行的 PowerPoint 应用实例。
|
||||
/// </summary>
|
||||
/// <param name="targetApp">可选的目标 PowerPoint COM 对象,用于优先比较;传入 null 表示不指定目标。</param>
|
||||
/// <param name="bestPriority">输出参数:返回找到的最佳实例的优先级(0 表示未找到或无活动演示)。</param>
|
||||
/// <param name="targetPriority">输出参数:返回与 <paramref name="targetApp"/> 对应实例的优先级(如果未提供或未命中则为 0)。</param>
|
||||
/// <returns>最合适的 PowerPoint 应用对象(通常为 COM Application 实例),若未找到则返回 null。</returns>
|
||||
public static object GetAnyActivePowerPoint(object targetApp, out int bestPriority, out int targetPriority)
|
||||
{
|
||||
IRunningObjectTable rot = null;
|
||||
@@ -480,4 +487,3 @@ namespace Ink_Canvas.Helpers
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@ namespace Ink_Canvas.Helpers
|
||||
get { lock (_lock) return _enabled; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从应用设置读取 EnableProcessProtection 并相应地启用或禁用进程保护。
|
||||
/// </summary>
|
||||
public static void ApplyFromSettings()
|
||||
{
|
||||
try
|
||||
@@ -43,6 +46,10 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换进程保护的启用状态;在状态发生变化时触发相应的启用或禁用操作并保证线程安全。
|
||||
/// </summary>
|
||||
/// <param name="enabled">为 `true` 时启用进程保护,为 `false` 时禁用进程保护。</param>
|
||||
public static void SetEnabled(bool enabled)
|
||||
{
|
||||
lock (_lock)
|
||||
@@ -69,6 +76,15 @@ namespace Ink_Canvas.Helpers
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在受进程保护的上下文中执行对指定目标的写入操作;在执行时会在必要情况下临时释放针对目标路径及其父目录的锁,执行完成后恢复这些锁。
|
||||
/// </summary>
|
||||
/// <param name="targetPath">目标文件或目录的路径,用于确定需要临时释放和随后恢复的锁。</param>
|
||||
/// <param name="action">执行写入的操作委托,不能为空。</param>
|
||||
/// <remarks>
|
||||
/// 如果 ProcessProtectionManager.Enabled 为 false,会直接执行 <paramref name="action"/>;
|
||||
/// 若在有限时间内无法获取写入门闩,会记录警告并降级为直接执行 <paramref name="action"/>。方法在内部处理异常,不会抛出异常给调用者。
|
||||
/// </remarks>
|
||||
public static void WithWriteAccess(string targetPath, Action action)
|
||||
{
|
||||
if (action == null) return;
|
||||
@@ -214,6 +230,11 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试在指定的毫秒数内获取写入门控(write gate)。
|
||||
/// </summary>
|
||||
/// <param name="timeoutMs">等待超时时间(毫秒)。小于或等于 0 时视为 1 毫秒。</param>
|
||||
/// <returns>`true` 如果在指定时间内成功获取到写入门控,`false` 否则。</returns>
|
||||
private static bool TryEnterWriteGate(int timeoutMs)
|
||||
{
|
||||
if (timeoutMs <= 0) timeoutMs = 1;
|
||||
@@ -230,11 +251,19 @@ namespace Ink_Canvas.Helpers
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用进程保护并对应用根路径进行完整重扫描以锁定需要保护的目录和文件。
|
||||
/// </summary>
|
||||
private static void Enable()
|
||||
{
|
||||
Enable(rescanRoot: true, rescanDirs: null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在应用根目录或提供的路径集合上建立目录句柄和文件读取锁以启用进程保护。
|
||||
/// </summary>
|
||||
/// <param name="rescanRoot">为 true 时对 App.RootPath 进行递归扫描并锁定其下的目录与文件;为 false 时仅处理 <paramref name="rescanDirs"/> 指定的路径(若为 null 则不处理)。</param>
|
||||
/// <param name="rescanDirs">当 <paramref name="rescanRoot"/> 为 false 时,按项对存在的目录建立目录锁,对存在的文件建立文件锁;可为 null。</param>
|
||||
private static void Enable(bool rescanRoot, IEnumerable<string> rescanDirs)
|
||||
{
|
||||
try
|
||||
@@ -282,6 +311,13 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放并清除当前进程持有的所有文件和目录锁定句柄与流资源。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 在内部同步锁定下逐一 Dispose 已记录的 FileStream 和 SafeFileHandle,并清空对应的缓存字典;
|
||||
/// 释放过程中发生的异常会被忽略(吞掉)。
|
||||
/// </remarks>
|
||||
private static void Disable()
|
||||
{
|
||||
lock (_lock)
|
||||
@@ -300,6 +336,11 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 递归地尝试为指定目录及其所有子目录建立并保持目录句柄锁定,跳过配置的排除目录。
|
||||
/// </summary>
|
||||
/// <param name="root">起始目录的路径;从此路径开始遍历并对符合条件的子目录尝试建立锁定。</param>
|
||||
/// <remarks>遇到的异常会被捕获并忽略,不会向调用方抛出。</remarks>
|
||||
private static void LockDirectoryRecursive(string root)
|
||||
{
|
||||
try
|
||||
@@ -321,6 +362,12 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 递归扫描指定根目录下的所有文件,并为具有特定扩展名的文件建立读取锁定以防止被进程修改或替换。
|
||||
/// </summary>
|
||||
/// <param name="root">要开始扫描的根目录路径。</param>
|
||||
/// <remarks>
|
||||
/// 仅处理扩展名为 `.exe`, `.dll`, `.config`, `.manifest`, `.dat`, `.enc` 的文件;会跳过被 IsExcludedPath 判定为排除的路径。遇到任何 I/O 或访问错误时会静默忽略,不会抛出异常。</remarks>
|
||||
private static void LockFilesRecursive(string root)
|
||||
{
|
||||
try
|
||||
@@ -347,6 +394,10 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 以只读方式打开并保留指定文件的句柄,将其加入内部锁定缓存以减少该文件被外部修改或删除的可能性。
|
||||
/// </summary>
|
||||
/// <param name="filePath">要锁定的文件的路径(会被规范化为完整路径)。</param>
|
||||
private static void LockFile(string filePath)
|
||||
{
|
||||
filePath = NormalizePath(filePath);
|
||||
@@ -364,6 +415,10 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试为指定目录获取一个用于保持目录打开的句柄并将其保存为内部锁定记录;若目录已被记录则不作任何操作,发生错误时静默忽略。
|
||||
/// </summary>
|
||||
/// <param name="dirPath">要锁定的目录路径;调用时会对路径进行规范化(转换为完整路径并移除多余分隔符)。</param>
|
||||
private static void LockDirectory(string dirPath)
|
||||
{
|
||||
dirPath = NormalizePath(dirPath);
|
||||
@@ -384,6 +439,11 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将路径标准化为不含末尾路径分隔符的绝对路径。
|
||||
/// </summary>
|
||||
/// <param name="p">要规范化的路径;如果为 null、空或仅空白,则返回原值。</param>
|
||||
/// <returns>规范化后的路径:在解析成功时返回去除末尾分隔符的绝对路径;在解析失败时返回原始输入。</returns>
|
||||
private static string NormalizePath(string p)
|
||||
{
|
||||
try
|
||||
@@ -397,6 +457,11 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建从指定路径向上直到应用根目录(App.RootPath)的目录链,并按从下到上的顺序返回已规范化的目录路径。
|
||||
/// </summary>
|
||||
/// <param name="path">起始路径,既可为文件路径也可为目录路径;若为文件则使用其所在目录作为起点。</param>
|
||||
/// <returns>包含起始目录及其各级父目录直到并包含应用根目录的列表;当根路径无效或未能匹配到根目录时返回空列表。</returns>
|
||||
private static List<string> GetDirChainToRoot(string path)
|
||||
{
|
||||
var list = new List<string>();
|
||||
@@ -420,6 +485,11 @@ namespace Ink_Canvas.Helpers
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查给定路径是否位于应用根目录下的受排除子目录之一。
|
||||
/// </summary>
|
||||
/// <param name="path">要检查的文件或目录路径。</param>
|
||||
/// <returns>`true` 如果路径位于任何配置为排除的子目录下,`false` 否则。</returns>
|
||||
private static bool IsExcludedPath(string path)
|
||||
{
|
||||
try
|
||||
@@ -442,6 +512,11 @@ namespace Ink_Canvas.Helpers
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为指定目录打开一个文件句柄,便于对该目录进行锁定或访问其元数据。
|
||||
/// </summary>
|
||||
/// <param name="dirPath">目标目录的完整路径。</param>
|
||||
/// <returns>表示已打开目录的 <see cref="SafeFileHandle"/>;若无法打开则返回无效的句柄,调用方应检查句柄有效性。</returns>
|
||||
private static SafeFileHandle CreateDirectoryHandle(string dirPath)
|
||||
{
|
||||
const uint GENERIC_READ = 0x80000000;
|
||||
@@ -459,6 +534,17 @@ namespace Ink_Canvas.Helpers
|
||||
IntPtr.Zero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用原生 CreateFile API 打开或创建一个文件/目录并返回底层句柄的包装函数声明。
|
||||
/// </summary>
|
||||
/// <param name="lpFileName">要打开或创建的文件或目录的完整路径(UTF-16 编码)。</param>
|
||||
/// <param name="dwDesiredAccess">请求的访问权限位掩码(例如读取或写入访问)。</param>
|
||||
/// <param name="dwShareMode">共享模式位掩码,指定其他进程可以如何共享此文件句柄。</param>
|
||||
/// <param name="lpSecurityAttributes">指向安全属性结构的指针,或为 <see cref="IntPtr.Zero"/> 表示默认安全性。</param>
|
||||
/// <param name="dwCreationDisposition">指定如何处理已存在或不存在的文件(例如打开、创建或截断)。</param>
|
||||
/// <param name="dwFlagsAndAttributes">文件属性和标志位,用于控制文件或目录的特殊行为(例如备份语义)。</param>
|
||||
/// <param name="hTemplateFile">用于创建新文件时的模板句柄,通常为 <see cref="IntPtr.Zero"/>。</param>
|
||||
/// <returns>表示文件或目录句柄的 <see cref="Microsoft.Win32.SafeHandles.SafeFileHandle"/>;调用失败时返回无效的句柄(可通过检查句柄或调用 <see cref="System.Runtime.InteropServices.Marshal.GetLastWin32Error"/> 获取错误码)。</returns>
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern SafeFileHandle CreateFile(
|
||||
string lpFileName,
|
||||
@@ -470,4 +556,3 @@ namespace Ink_Canvas.Helpers
|
||||
IntPtr hTemplateFile);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -195,6 +195,12 @@ namespace Ink_Canvas.Helpers
|
||||
OnSlideShowStateCheckTimerElapsed(sender, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动内部轮询计时器以开始监测 ROT/PPT 连接状态和幻灯片放映状态。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 如果实例已被释放(Dispose 已调用)或计时器未初始化,则此方法不会执行任何操作。
|
||||
/// </remarks>
|
||||
public void StartMonitoring()
|
||||
{
|
||||
if (_disposed) return;
|
||||
@@ -202,12 +208,18 @@ namespace Ink_Canvas.Helpers
|
||||
_unifiedRotTimer?.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止内部的 ROT 监视计时器并断开与当前 PowerPoint 实例的连接。
|
||||
/// </summary>
|
||||
public void StopMonitoring()
|
||||
{
|
||||
_unifiedRotTimer?.Stop();
|
||||
DisconnectFromPPT(restartMonitoring: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 强制断开当前与 PowerPoint 的 ROT 连接并触发后续的重连流程。
|
||||
/// </summary>
|
||||
public void ReloadConnection()
|
||||
{
|
||||
if (_disposed) return;
|
||||
@@ -217,6 +229,12 @@ namespace Ink_Canvas.Helpers
|
||||
#endregion
|
||||
|
||||
#region Connection Management
|
||||
/// <summary>
|
||||
/// 在计时器触发时检查与 PowerPoint 的 ROT 连接,并在尚未处于已释放或模块卸载期间时尝试通过 ROT 建立连接。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 如果在检查或连接过程中发生异常,异常信息会记录到日志并被吞掉,不会向上抛出。
|
||||
/// </remarks>
|
||||
private void OnConnectionCheckTimerElapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -231,6 +249,12 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在定时器触发时检查并在必要时更新 PPT 的放映状态。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 当实例未被释放、模块未在卸载过程中且与 PowerPoint 仍保持连接时会调用内部的放映状态检查逻辑。若检查过程中发生异常,会将错误写入日志并吞并异常以保证定时器循环继续运行。
|
||||
/// </remarks>
|
||||
private void OnSlideShowStateCheckTimerElapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -245,6 +269,16 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 ROT 检查并在必要时建立、切换或断开与 PowerPoint 的 COM 连接。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 当管理器已释放或模块正在卸载时不执行任何操作。此方法在内部加锁以防止并发切换;会通过 ROT 获取当前可用的 PowerPoint 实例并:
|
||||
/// - 若尚未连接则建立连接;
|
||||
/// - 若当前无可用实例则断开现有连接;
|
||||
/// - 若检测到已连接的实例发生变化则先断开再重新连接。
|
||||
/// 遇到异常时会记录错误日志,并在存在活动连接时尝试断开以保证状态一致性。
|
||||
/// </remarks>
|
||||
private void CheckAndConnectToPPTViaRot()
|
||||
{
|
||||
if (_disposed || _isModuleUnloading) return;
|
||||
@@ -318,6 +352,13 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定的 PowerPoint COM 应用对象作为当前连接并在 UI 线程上注册必要的演示文稿与幻灯片放映事件,触发连接通知并在已处于放映时尝试触发一次放映开始回调。
|
||||
/// </summary>
|
||||
/// <param name="appObj">要绑定的 PowerPoint COM 应用对象(通常来自 ROT)。</param>
|
||||
/// <remarks>
|
||||
/// 如果事件注册或后续处理失败,会记录错误并在异常路径上清除内部引用(将内部应用对象置空)。方法会触发 <c>PPTConnectionChanged(true)</c> 并记录连接成功的事件日志;若检测到当前正处于幻灯片放映,则尝试调用一次放映开始的内部处理以同步状态。异常信息会写入日志,但方法不会向外抛出未捕获的异常(内部会捕获并记录错误后清理状态)。
|
||||
/// </remarks>
|
||||
private void ConnectToPPT(object appObj)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -16,23 +16,54 @@ namespace Ink_Canvas.Helpers
|
||||
private const int SaltSizeBytes = 16;
|
||||
private const int HashSizeBytes = 32;
|
||||
|
||||
/// <summary>
|
||||
/// 检查设置中是否启用了密码安全功能。
|
||||
/// </summary>
|
||||
/// <param name="settings">应用程序设置对象(可能为 null)。</param>
|
||||
/// <returns>`true` 当 settings 非 null 且其 Security 部分存在且已启用密码功能;`false` 否则。</returns>
|
||||
public static bool IsPasswordFeatureEnabled(Settings settings)
|
||||
=> settings?.Security != null && settings.Security.PasswordEnabled;
|
||||
=> settings?.Security != null && settings.Security.PasswordEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// 确定给定设置中是否已配置密码(存在非空的密码盐和密码哈希)。
|
||||
/// </summary>
|
||||
/// <param name="settings">应用的设置;为 null 或未包含 Security 部分时视为未配置密码。</param>
|
||||
/// <returns>`true` 如果设置包含非空的 PasswordSalt 和 PasswordHash,否则 `false`。</returns>
|
||||
public static bool HasPasswordConfigured(Settings settings)
|
||||
=> settings?.Security != null
|
||||
&& !string.IsNullOrWhiteSpace(settings.Security.PasswordSalt)
|
||||
&& !string.IsNullOrWhiteSpace(settings.Security.PasswordHash);
|
||||
&& !string.IsNullOrWhiteSpace(settings.Security.PasswordSalt)
|
||||
&& !string.IsNullOrWhiteSpace(settings.Security.PasswordHash);
|
||||
|
||||
/// <summary>
|
||||
/// 确定在退出应用时是否需要输入密码。
|
||||
/// </summary>
|
||||
/// <param name="settings">应用配置;如果为 null,则视为未启用或未配置密码。</param>
|
||||
/// <returns>`true` 当密码功能已启用、已配置密码且设置要求在退出时需要密码,`false` 否则。</returns>
|
||||
public static bool IsPasswordRequiredForExit(Settings settings)
|
||||
=> IsPasswordFeatureEnabled(settings) && HasPasswordConfigured(settings) && settings.Security.RequirePasswordOnExit;
|
||||
|
||||
/// <summary>
|
||||
/// 确定在进入设置界面时是否需要输入密码。
|
||||
/// </summary>
|
||||
/// <param name="settings">应用配置;为 null 或未启用密码功能时视为未配置密码。</param>
|
||||
/// <returns>`true` 如果已启用密码功能、已配置密码且已设置为在进入设置时要求密码,`false` 否则。</returns>
|
||||
public static bool IsPasswordRequiredForEnterSettings(Settings settings)
|
||||
=> IsPasswordFeatureEnabled(settings) && HasPasswordConfigured(settings) && settings.Security.RequirePasswordOnEnterSettings;
|
||||
|
||||
/// <summary>
|
||||
/// 指示在重置配置时是否需要输入密码。
|
||||
/// </summary>
|
||||
/// <param name="settings">应用设置对象;如果为 null 或未启用密码功能,则视为不需要密码。</param>
|
||||
/// <returns>`true` 如果已启用密码功能、已有配置的密码且设置要求在重置配置时进行密码验证;`false` 否则。</returns>
|
||||
public static bool IsPasswordRequiredForResetConfig(Settings settings)
|
||||
=> IsPasswordFeatureEnabled(settings) && HasPasswordConfigured(settings) && settings.Security.RequirePasswordOnResetConfig;
|
||||
|
||||
/// <summary>
|
||||
/// 将提供的明文密码与 Settings 中存储的密码散列进行比对以验证密码是否正确。
|
||||
/// </summary>
|
||||
/// <param name="settings">包含存储的密码盐和哈希的设置对象(使用 Base64 编码的 PasswordSalt 和 PasswordHash)。</param>
|
||||
/// <param name="password">要验证的明文密码。</param>
|
||||
/// <returns>`true` 如果密码与存储的哈希匹配,`false` 否则(包括未配置密码、password 为 null 或在解析/派生过程中发生错误)。</returns>
|
||||
public static bool VerifyPassword(Settings settings, string password)
|
||||
{
|
||||
if (!HasPasswordConfigured(settings)) return false;
|
||||
@@ -52,6 +83,10 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 如果已配置密码,显示一个对话框提示用户输入密码并验证;如果未配置密码则直接允许通过。
|
||||
/// </summary>
|
||||
/// <returns>`true` 如果未配置密码或用户确认并输入了正确的密码,`false` 如果用户取消或验证失败。</returns>
|
||||
public static async Task<bool> PromptAndVerifyAsync(Settings settings, Window owner, string title, string message)
|
||||
{
|
||||
if (!HasPasswordConfigured(settings)) return true;
|
||||
@@ -90,6 +125,11 @@ namespace Ink_Canvas.Helpers
|
||||
return VerifyPassword(settings, passwordBox.Password);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示一个对话框让用户输入并确认新密码,成功时返回该密码。
|
||||
/// </summary>
|
||||
/// <param name="owner">对话框的所属窗口(用于指定父窗口)。</param>
|
||||
/// <returns>用户输入的新密码;如果用户取消或输入无效(长度不足或两次不匹配),则返回 <c>null</c>。</returns>
|
||||
public static async Task<string> PromptSetNewPasswordAsync(Window owner)
|
||||
{
|
||||
var dialog = new ContentDialog
|
||||
@@ -141,6 +181,12 @@ namespace Ink_Canvas.Helpers
|
||||
return pwd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 弹出对话框以更改已配置的安全密码;如果尚未配置密码则转而提示设置新密码。
|
||||
/// </summary>
|
||||
/// <param name="settings">应用配置对象,包含当前存储的密码信息。</param>
|
||||
/// <param name="owner">对话框的父窗口(用于定位/所有权)。</param>
|
||||
/// <returns>用户成功更改后返回新的密码字符串;当用户取消、验证失败或校验不通过时返回 <c>null</c>。</returns>
|
||||
public static async Task<string> PromptChangePasswordAsync(Settings settings, Window owner)
|
||||
{
|
||||
if (!HasPasswordConfigured(settings))
|
||||
@@ -207,6 +253,11 @@ namespace Ink_Canvas.Helpers
|
||||
return newPwd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为指定 Settings 生成并存储新的密码盐与哈希到 settings.Security 中。
|
||||
/// </summary>
|
||||
/// <param name="settings">要更新的设置对象;如果为 null 或其 Security 为 null 则不执行任何操作。</param>
|
||||
/// <param name="password">用于派生哈希的原始密码字符串。</param>
|
||||
public static void SetPassword(Settings settings, string password)
|
||||
{
|
||||
if (settings?.Security == null) return;
|
||||
@@ -222,6 +273,10 @@ namespace Ink_Canvas.Helpers
|
||||
settings.Security.PasswordHash = Convert.ToBase64String(hash);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除设置中存储的密码信息。
|
||||
/// </summary>
|
||||
/// <param name="settings">要更新的设置对象;将把其 Security.PasswordSalt 和 Security.PasswordHash 设为空字符串。若 <paramref name="settings"/> 为 null 或其 Security 为 null 则不执行任何操作。</param>
|
||||
public static void ClearPassword(Settings settings)
|
||||
{
|
||||
if (settings?.Security == null) return;
|
||||
@@ -229,6 +284,13 @@ namespace Ink_Canvas.Helpers
|
||||
settings.Security.PasswordHash = "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 PBKDF2(Rfc2898)从给定的密码和盐派生指定长度的密钥字节。
|
||||
/// </summary>
|
||||
/// <param name="password">用于派生的密码字符串。</param>
|
||||
/// <param name="salt">用于派生的盐字节数组(不可为 null)。</param>
|
||||
/// <param name="keyBytes">要返回的密钥字节长度(以字节为单位)。</param>
|
||||
/// <returns>派生出的密钥字节数组,长度等于 <paramref name="keyBytes"/>。</returns>
|
||||
private static byte[] DeriveKey(string password, byte[] salt, int keyBytes)
|
||||
{
|
||||
// 注意:Rfc2898DeriveBytes 在 net472 默认 HMACSHA1
|
||||
@@ -238,6 +300,12 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 以固定时间方式比较两个字节数组的内容是否完全相同,防止基于时序的比对攻击。
|
||||
/// </summary>
|
||||
/// <param name="a">要比较的第一个字节数组。</param>
|
||||
/// <param name="b">要比较的第二个字节数组。</param>
|
||||
/// <returns>`true` 如果两个数组长度相同且所有字节相等,`false` 否则。</returns>
|
||||
private static bool FixedTimeEquals(byte[] a, byte[] b)
|
||||
{
|
||||
if (a == null || b == null) return false;
|
||||
@@ -251,4 +319,3 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user