feat(Upload/WebDav):迁移Dlass并添加WebDav管理 (#381)
* feat(Upload/Common): 重构上传功能以添加通用设置管理 - 新增UploadSettings类用于管理上传通用设置 - 重构上传逻辑,将延迟上传功能移至UploadHelper - 在Dlass设置窗口添加通用设置标签页 - 支持多上传提供者管理及取消操作 - 增强文件上传前的验证和错误处理 Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com> * feat(upload): 添加WebDav文件上传支持 - 新增WebDavUploader工具类实现文件上传功能 - 添加WebDavUploadProvider作为上传提供者 - 在设置界面增加WebDav配置选项 - 添加WebDav.Client NuGet包依赖 Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com> * feat(WebDAV): 实现WebDAV上传队列管理 Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com> * feat(Upload): 重命名Dlass设置项为云存储以支持WebDav保存 Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com> * feat(Dlass):迁移Dlass注册位置 Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com> * refactor(Upload): 优化上传逻辑和界面交互 - 修改Dlass标签页检测逻辑,使用Tag属性替代Header - 限制WebDav上传队列的批量处理大小 - 移除多处上传延迟逻辑,统一在通用设置中配置 - 更新Dlass设置界面提示文本 Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com> * chore:修改窗口命名 Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com> * refactor(upload): 重构上传队列为统一管理架构 重构上传队列系统,引入BaseUploadQueue基类实现通用队列管理逻辑,创建UploadQueueHelper统一管理所有上传队列。将DlassUploadQueue和WebDavUploadQueue重构为继承自BaseUploadQueue的具体实现,简化代码并提高可维护性。修改MainWindow初始化代码以使用新的统一初始化方法。 Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com> * refactor(Upload): 重构上传队列系统,改进错误处理和资源管理 - 将上传队列改为可释放资源,实现IDisposable接口 - 移除硬编码的文件验证逻辑,改为可重写方法 - 改进API客户端,支持取消操作和更好的资源管理 - 优化队列初始化流程,增加错误处理 - 统一上传提供者的队列注册方式 - 改进日志记录和错误信息 Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com> * Update Settings.cs * refactor(UpLoad/Queue): 移除冗余的上传成功/失败日志记录 优化WebDav上传逻辑,增加目录创建重试机制 Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com> * refactor(MW_Settings): 重构全选复选框状态更新逻辑 将直接设置全选复选框状态的逻辑拆分为两步,先计算所有分类复选框状态,再更新全选复选框,提高代码可读性 Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com> --------- Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com> Co-authored-by: CJK_mkp <113243675+CJKmkp@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,604 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 上传队列项数据(用于序列化)
|
||||
/// </summary>
|
||||
public class UploadQueueItemData
|
||||
{
|
||||
[JsonProperty("file_path")]
|
||||
public string FilePath { get; set; }
|
||||
|
||||
[JsonProperty("retry_count")]
|
||||
public int RetryCount { get; set; }
|
||||
|
||||
[JsonProperty("added_time")]
|
||||
public DateTime AddedTime { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传队列项
|
||||
/// </summary>
|
||||
public class UploadQueueItem
|
||||
{
|
||||
public string FilePath { get; set; }
|
||||
public int RetryCount { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通用上传队列基类
|
||||
/// </summary>
|
||||
public abstract class BaseUploadQueue : IDisposable
|
||||
{
|
||||
protected const int BATCH_SIZE = 10; // 批量上传大小
|
||||
protected const int MAX_RETRY_COUNT = 3; // 最大重试次数
|
||||
|
||||
/// <summary>
|
||||
/// 上传队列
|
||||
/// </summary>
|
||||
protected readonly ConcurrentQueue<UploadQueueItem> _uploadQueue = new ConcurrentQueue<UploadQueueItem>();
|
||||
|
||||
/// <summary>
|
||||
/// 队列处理锁,防止并发处理
|
||||
/// </summary>
|
||||
protected readonly SemaphoreSlim _queueProcessingLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// 队列保存锁,防止并发保存
|
||||
/// </summary>
|
||||
protected readonly SemaphoreSlim _queueSaveLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// 是否已初始化队列
|
||||
/// </summary>
|
||||
protected bool _isQueueInitialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// 是否已释放资源
|
||||
/// </summary>
|
||||
private bool _disposed = false;
|
||||
|
||||
/// <summary>
|
||||
/// 队列文件名
|
||||
/// </summary>
|
||||
protected abstract string QueueFileName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 允许的文件扩展名
|
||||
/// </summary>
|
||||
protected virtual HashSet<string> AllowedExtensions => new HashSet<string> { ".png", ".icstk", ".xml", ".zip" };
|
||||
|
||||
/// <summary>
|
||||
/// 获取队列文件路径
|
||||
/// </summary>
|
||||
protected string GetQueueFilePath()
|
||||
{
|
||||
var configsDir = Path.Combine(App.RootPath, "Configs");
|
||||
if (!Directory.Exists(configsDir))
|
||||
{
|
||||
Directory.CreateDirectory(configsDir);
|
||||
}
|
||||
return Path.Combine(configsDir, QueueFileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取最大文件大小
|
||||
/// </summary>
|
||||
/// <param name="extension">文件扩展名</param>
|
||||
/// <returns>最大文件大小(字节)</returns>
|
||||
protected virtual long GetMaxFileSize(string extension)
|
||||
{
|
||||
return extension == ".zip" ? 50 * 1024 * 1024 : 10 * 1024 * 1024;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化上传队列
|
||||
/// </summary>
|
||||
public void InitializeQueue()
|
||||
{
|
||||
if (_isQueueInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var queueFilePath = GetQueueFilePath();
|
||||
if (!File.Exists(queueFilePath))
|
||||
{
|
||||
_isQueueInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var jsonContent = File.ReadAllText(queueFilePath);
|
||||
if (string.IsNullOrWhiteSpace(jsonContent))
|
||||
{
|
||||
_isQueueInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var queueData = JsonConvert.DeserializeObject<List<UploadQueueItemData>>(jsonContent);
|
||||
if (queueData == null || queueData.Count == 0)
|
||||
{
|
||||
_isQueueInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
int restoredCount = 0;
|
||||
int skippedCount = 0;
|
||||
|
||||
foreach (var item in queueData)
|
||||
{
|
||||
// 验证文件是否存在
|
||||
if (!File.Exists(item.FilePath))
|
||||
{
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 验证文件格式和大小
|
||||
if (!IsValidFile(item.FilePath))
|
||||
{
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 恢复队列项
|
||||
_uploadQueue.Enqueue(new UploadQueueItem
|
||||
{
|
||||
FilePath = item.FilePath,
|
||||
RetryCount = item.RetryCount
|
||||
});
|
||||
restoredCount++;
|
||||
}
|
||||
|
||||
_isQueueInitialized = true;
|
||||
|
||||
if (restoredCount > 0)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 已恢复上传队列:{restoredCount}个文件,跳过{skippedCount}个无效文件", LogHelper.LogType.Event);
|
||||
// 如果恢复了队列,触发处理
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await ProcessUploadQueueAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 恢复上传队列后处理时出错: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (skippedCount > 0)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 队列恢复完成:跳过{skippedCount}个无效文件", LogHelper.LogType.Event);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 恢复上传队列时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
_isQueueInitialized = true; // 即使出错也标记为已初始化,避免重复尝试
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存队列到文件
|
||||
/// </summary>
|
||||
protected async Task SaveQueueToFileAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!await _queueSaveLock.WaitAsync(1000, cancellationToken)) // 最多等待1秒
|
||||
{
|
||||
return; // 如果无法获取锁,跳过保存(避免阻塞)
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var queueData = new List<UploadQueueItemData>();
|
||||
|
||||
// 将队列转换为可序列化的格式
|
||||
foreach (var item in _uploadQueue)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
queueData.Add(new UploadQueueItemData
|
||||
{
|
||||
FilePath = item.FilePath,
|
||||
RetryCount = item.RetryCount,
|
||||
AddedTime = DateTime.Now
|
||||
});
|
||||
}
|
||||
|
||||
var queueFilePath = GetQueueFilePath();
|
||||
|
||||
// 如果队列为空,清空文件
|
||||
if (queueData.Count == 0)
|
||||
{
|
||||
ClearQueueFile();
|
||||
return;
|
||||
}
|
||||
|
||||
var jsonContent = JsonConvert.SerializeObject(queueData, Formatting.Indented);
|
||||
|
||||
// 使用进程保护的写入门控,避免安全面板中"进程文件保护"占用导致无法写入
|
||||
var tempFilePath = queueFilePath + ".tmp";
|
||||
ProcessProtectionManager.WithWriteAccess(queueFilePath, () =>
|
||||
{
|
||||
File.WriteAllText(tempFilePath, jsonContent);
|
||||
if (File.Exists(queueFilePath))
|
||||
File.Delete(queueFilePath);
|
||||
File.Move(tempFilePath, queueFilePath);
|
||||
});
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 取消操作,静默处理
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 保存上传队列时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_queueSaveLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空队列文件
|
||||
/// </summary>
|
||||
protected void ClearQueueFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
var queueFilePath = GetQueueFilePath();
|
||||
ProcessProtectionManager.WithWriteAccess(queueFilePath, () =>
|
||||
{
|
||||
if (File.Exists(queueFilePath))
|
||||
File.WriteAllText(queueFilePath, "[]");
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 清空队列文件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将文件加入上传队列
|
||||
/// </summary>
|
||||
protected void EnqueueFile(string filePath, int retryCount = 0, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_uploadQueue.Enqueue(new UploadQueueItem
|
||||
{
|
||||
FilePath = filePath,
|
||||
RetryCount = retryCount
|
||||
});
|
||||
|
||||
// 异步保存队列到文件
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
await SaveQueueToFileAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 取消操作,静默处理
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 保存上传队列时出错(后台任务): {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}, cancellationToken);
|
||||
|
||||
// 触发队列处理
|
||||
_ = ProcessUploadQueueAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理上传队列,批量上传文件
|
||||
/// </summary>
|
||||
protected async Task ProcessUploadQueueAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// 使用信号量防止并发处理
|
||||
if (!await _queueProcessingLock.WaitAsync(0, cancellationToken))
|
||||
{
|
||||
return; // 已有处理任务在运行
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var filesToUpload = new List<UploadQueueItem>();
|
||||
|
||||
// 从队列中取出最多BATCH_SIZE个文件
|
||||
int count = 0;
|
||||
while (count < BATCH_SIZE && _uploadQueue.TryDequeue(out UploadQueueItem item))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// 再次检查文件是否存在
|
||||
if (File.Exists(item.FilePath) && IsValidFile(item.FilePath))
|
||||
{
|
||||
filesToUpload.Add(item);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (filesToUpload.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否启用
|
||||
if (!IsUploadEnabled())
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败:上传未启用", LogHelper.LogType.Error);
|
||||
// 将文件重新加入队列
|
||||
foreach (var item in filesToUpload)
|
||||
{
|
||||
EnqueueFile(item.FilePath, item.RetryCount, cancellationToken);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 并发上传所有文件,并处理失败重试
|
||||
var uploadTasks = filesToUpload.Select(async item =>
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var success = await UploadFileInternalAsync(item.FilePath, cancellationToken);
|
||||
if (!success)
|
||||
{
|
||||
// 检查是否是可重试的错误
|
||||
if (IsRetryableError(item.FilePath))
|
||||
{
|
||||
// 检查重试次数
|
||||
if (item.RetryCount < MAX_RETRY_COUNT)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败,将重试 ({item.RetryCount + 1}/{MAX_RETRY_COUNT}): {Path.GetFileName(item.FilePath)}", LogHelper.LogType.Event);
|
||||
EnqueueFile(item.FilePath, item.RetryCount + 1, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败,已达到最大重试次数: {Path.GetFileName(item.FilePath)}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 取消操作,将文件重新加入队列
|
||||
EnqueueFile(item.FilePath, item.RetryCount, cancellationToken);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 检查是否是可重试的错误(超时、网络错误等)
|
||||
var errorMessage = ex.Message.ToLower();
|
||||
bool isRetryable = errorMessage.Contains("超时") ||
|
||||
errorMessage.Contains("timeout") ||
|
||||
errorMessage.Contains("网络错误") ||
|
||||
errorMessage.Contains("network") ||
|
||||
errorMessage.Contains("408") || // 请求超时
|
||||
errorMessage.Contains("423") || // 资源锁定
|
||||
errorMessage.Contains("429") || // 请求过多
|
||||
errorMessage.Contains("500") || // 服务器错误
|
||||
errorMessage.Contains("502") || // 网关错误
|
||||
errorMessage.Contains("503") || // 服务不可用
|
||||
errorMessage.Contains("504"); // 网关超时
|
||||
|
||||
if (isRetryable && IsRetryableError(item.FilePath))
|
||||
{
|
||||
// 检查重试次数
|
||||
if (item.RetryCount < MAX_RETRY_COUNT)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败({ex.Message}),将重试 ({item.RetryCount + 1}/{MAX_RETRY_COUNT}): {Path.GetFileName(item.FilePath)}", LogHelper.LogType.Error);
|
||||
EnqueueFile(item.FilePath, item.RetryCount + 1, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败,已达到最大重试次数: {Path.GetFileName(item.FilePath)}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败(不可重试): {Path.GetFileName(item.FilePath)} - {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
await Task.WhenAll(uploadTasks).ConfigureAwait(false);
|
||||
|
||||
// 上传完成后保存队列状态
|
||||
await SaveQueueToFileAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// 检查队列中是否还有文件,如果有就继续处理
|
||||
if (_uploadQueue.Count > 0)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await ProcessUploadQueueAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 继续处理上传队列时出错: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}, cancellationToken);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_queueProcessingLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证文件是否有效
|
||||
/// </summary>
|
||||
protected virtual bool IsValidFile(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fileExtension = Path.GetExtension(filePath).ToLower();
|
||||
if (!AllowedExtensions.Contains(fileExtension))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
long maxSize = GetMaxFileSize(fileExtension);
|
||||
if (fileInfo.Length > maxSize)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败:文件过大({fileInfo.Length / 1024 / 1024:F2}MB),超过{maxSize / 1024 / 1024}MB限制", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断错误是否可重试
|
||||
/// </summary>
|
||||
protected bool IsRetryableError(string filePath)
|
||||
{
|
||||
// 检查文件是否存在
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
return false; // 文件不存在,不可重试
|
||||
}
|
||||
|
||||
// 检查文件是否有效
|
||||
if (!IsValidFile(filePath))
|
||||
{
|
||||
return false; // 文件无效,不可重试
|
||||
}
|
||||
|
||||
// 检查是否启用
|
||||
if (!IsUploadEnabled())
|
||||
{
|
||||
return false; // 上传未启用,不可重试
|
||||
}
|
||||
|
||||
// 其他错误(超时、网络错误等)可以重试
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查上传是否启用
|
||||
/// </summary>
|
||||
protected abstract bool IsUploadEnabled();
|
||||
|
||||
/// <summary>
|
||||
/// 内部上传方法,执行实际上传操作
|
||||
/// </summary>
|
||||
protected abstract Task<bool> UploadFileInternalAsync(string filePath, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// 异步上传文件
|
||||
/// </summary>
|
||||
public async Task<bool> UploadFileAsync(string filePath, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// 检查是否启用
|
||||
if (!IsUploadEnabled())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 基本验证
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败:文件不存在 - {filePath}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsValidFile(filePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确保队列已初始化
|
||||
if (!_isQueueInitialized)
|
||||
{
|
||||
InitializeQueue();
|
||||
}
|
||||
|
||||
// 加入队列
|
||||
EnqueueFile(filePath, 0, cancellationToken);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 上传被取消: {Path.GetFileName(filePath)}", LogHelper.LogType.Event);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 加入上传队列时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源
|
||||
/// </summary>
|
||||
/// <param name="disposing">是否手动释放</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_queueProcessingLock?.Dispose();
|
||||
_queueSaveLock?.Dispose();
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 析构函数
|
||||
/// </summary>
|
||||
~BaseUploadQueue()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
@@ -54,7 +55,8 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 获取访问令牌(Access Token)
|
||||
/// </summary>
|
||||
public async Task<string> GetAccessTokenAsync()
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
public async Task<string> GetAccessTokenAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_userToken))
|
||||
{
|
||||
@@ -68,6 +70,8 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var requestData = new
|
||||
{
|
||||
app_id = _appId,
|
||||
@@ -78,7 +82,7 @@ namespace Ink_Canvas.Helpers
|
||||
var json = JsonConvert.SerializeObject(requestData);
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await _httpClient.PostAsync("/oauth/token", content);
|
||||
var response = await _httpClient.PostAsync("/oauth/token", content, cancellationToken);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
@@ -93,14 +97,14 @@ namespace Ink_Canvas.Helpers
|
||||
throw new Exception($"获取Access Token失败: {response.StatusCode}");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (HttpRequestException httpEx)
|
||||
{
|
||||
throw new Exception($"获取Access Token时网络错误: {httpEx.Message}", httpEx);
|
||||
}
|
||||
catch (TaskCanceledException timeoutEx)
|
||||
{
|
||||
throw new Exception("获取Access Token时请求超时", timeoutEx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"获取Access Token时出错: {ex.Message}", ex);
|
||||
@@ -110,14 +114,19 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 发送GET请求
|
||||
/// </summary>
|
||||
public async Task<T> GetAsync<T>(string endpoint, bool requireAuth = true)
|
||||
/// <param name="endpoint">API端点</param>
|
||||
/// <param name="requireAuth">是否需要认证</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
public async Task<T> GetAsync<T>(string endpoint, bool requireAuth = true, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
string token = null;
|
||||
if (requireAuth)
|
||||
{
|
||||
token = await GetAccessTokenAsync();
|
||||
token = await GetAccessTokenAsync(cancellationToken);
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, endpoint);
|
||||
@@ -134,7 +143,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
@@ -150,14 +159,14 @@ namespace Ink_Canvas.Helpers
|
||||
throw new Exception($"API请求失败: {response.StatusCode} - {content}");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (HttpRequestException httpEx)
|
||||
{
|
||||
throw new Exception($"发送请求时出错: {httpEx.Message}", httpEx);
|
||||
}
|
||||
catch (TaskCanceledException timeoutEx)
|
||||
{
|
||||
throw new Exception($"请求超时: {endpoint}", timeoutEx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"发送请求时出错: {ex.Message}", ex);
|
||||
@@ -167,14 +176,20 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 发送POST请求
|
||||
/// </summary>
|
||||
public async Task<T> PostAsync<T>(string endpoint, object data = null, bool requireAuth = true)
|
||||
/// <param name="endpoint">API端点</param>
|
||||
/// <param name="data">请求数据</param>
|
||||
/// <param name="requireAuth">是否需要认证</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
public async Task<T> PostAsync<T>(string endpoint, object data = null, bool requireAuth = true, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
string token = null;
|
||||
if (requireAuth)
|
||||
{
|
||||
token = await GetAccessTokenAsync();
|
||||
token = await GetAccessTokenAsync(cancellationToken);
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||
@@ -197,7 +212,7 @@ namespace Ink_Canvas.Helpers
|
||||
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
}
|
||||
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
@@ -213,14 +228,14 @@ namespace Ink_Canvas.Helpers
|
||||
throw new Exception($"API请求失败: {response.StatusCode} - {content}");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (HttpRequestException httpEx)
|
||||
{
|
||||
throw new Exception($"发送请求时出错: {httpEx.Message}", httpEx);
|
||||
}
|
||||
catch (TaskCanceledException timeoutEx)
|
||||
{
|
||||
throw new Exception($"请求超时: {endpoint}", timeoutEx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"发送请求时出错: {ex.Message}", ex);
|
||||
@@ -230,14 +245,20 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 发送PUT请求
|
||||
/// </summary>
|
||||
public async Task<T> PutAsync<T>(string endpoint, object data = null, bool requireAuth = true)
|
||||
/// <param name="endpoint">API端点</param>
|
||||
/// <param name="data">请求数据</param>
|
||||
/// <param name="requireAuth">是否需要认证</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
public async Task<T> PutAsync<T>(string endpoint, object data = null, bool requireAuth = true, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
string token = null;
|
||||
if (requireAuth)
|
||||
{
|
||||
token = await GetAccessTokenAsync();
|
||||
token = await GetAccessTokenAsync(cancellationToken);
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Put, endpoint);
|
||||
@@ -261,7 +282,7 @@ namespace Ink_Canvas.Helpers
|
||||
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
}
|
||||
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
@@ -277,14 +298,14 @@ namespace Ink_Canvas.Helpers
|
||||
throw new Exception($"API请求失败: {response.StatusCode} - {content}");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (HttpRequestException httpEx)
|
||||
{
|
||||
throw new Exception($"发送请求时出错: {httpEx.Message}", httpEx);
|
||||
}
|
||||
catch (TaskCanceledException timeoutEx)
|
||||
{
|
||||
throw new Exception($"请求超时: {endpoint}", timeoutEx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"发送请求时出错: {ex.Message}", ex);
|
||||
@@ -294,14 +315,19 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 发送DELETE请求
|
||||
/// </summary>
|
||||
public async Task<bool> DeleteAsync(string endpoint, bool requireAuth = true)
|
||||
/// <param name="endpoint">API端点</param>
|
||||
/// <param name="requireAuth">是否需要认证</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
public async Task<bool> DeleteAsync(string endpoint, bool requireAuth = true, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
string token = null;
|
||||
if (requireAuth)
|
||||
{
|
||||
token = await GetAccessTokenAsync();
|
||||
token = await GetAccessTokenAsync(cancellationToken);
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Delete, endpoint);
|
||||
@@ -319,7 +345,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -330,11 +356,11 @@ namespace Ink_Canvas.Helpers
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException)
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return false;
|
||||
throw;
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -354,10 +380,13 @@ namespace Ink_Canvas.Helpers
|
||||
/// <param name="title">笔记标题(可选)</param>
|
||||
/// <param name="description">笔记描述(可选)</param>
|
||||
/// <param name="tags">笔记标签(可选)</param>
|
||||
public async Task<T> UploadNoteAsync<T>(string endpoint, string filePath, string boardId, string secretKey, string title = null, string description = null, string tags = null)
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
public async Task<T> UploadNoteAsync<T>(string endpoint, string filePath, string boardId, string secretKey, string title = null, string description = null, string tags = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
throw new FileNotFoundException($"文件不存在: {filePath}");
|
||||
@@ -394,7 +423,7 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
request.Content = content;
|
||||
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
var response = await _httpClient.SendAsync(request, cancellationToken);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
@@ -410,14 +439,14 @@ namespace Ink_Canvas.Helpers
|
||||
throw new Exception($"上传文件失败: {response.StatusCode} - {responseContent}");
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (HttpRequestException httpEx)
|
||||
{
|
||||
throw new Exception($"上传文件时网络错误: {httpEx.Message}", httpEx);
|
||||
}
|
||||
catch (TaskCanceledException timeoutEx)
|
||||
{
|
||||
throw new Exception($"上传文件超时: {endpoint}", timeoutEx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"上传文件时出错: {ex.Message}", ex);
|
||||
|
||||
@@ -1,807 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Dlass笔记自动上传辅助类
|
||||
/// </summary>
|
||||
public class DlassNoteUploader
|
||||
{
|
||||
private const string APP_ID = "app_WkjocWqsrVY7T6zQV2CfiA";
|
||||
private const string APP_SECRET = "o7dx5b5ASGUMcM72PCpmRQYAhSijqaOVHoGyBK0IxbA";
|
||||
private const int BATCH_SIZE = 10; // 批量上传大小
|
||||
private const int MAX_RETRY_COUNT = 3; // 最大重试次数
|
||||
private const string QUEUE_FILE_NAME = "DlassUploadQueue.json";
|
||||
|
||||
/// <summary>
|
||||
/// 上传队列项
|
||||
/// </summary>
|
||||
private class UploadQueueItemData
|
||||
{
|
||||
[JsonProperty("file_path")]
|
||||
public string FilePath { get; set; }
|
||||
|
||||
[JsonProperty("retry_count")]
|
||||
public int RetryCount { get; set; }
|
||||
|
||||
[JsonProperty("added_time")]
|
||||
public DateTime AddedTime { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传队列项
|
||||
/// </summary>
|
||||
private class UploadQueueItem
|
||||
{
|
||||
public string FilePath { get; set; }
|
||||
public int RetryCount { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传队列
|
||||
/// </summary>
|
||||
private static readonly ConcurrentQueue<UploadQueueItem> _uploadQueue = new ConcurrentQueue<UploadQueueItem>();
|
||||
|
||||
/// <summary>
|
||||
/// 队列处理锁,防止并发处理
|
||||
/// </summary>
|
||||
private static readonly SemaphoreSlim _queueProcessingLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// 队列保存锁,防止并发保存
|
||||
/// </summary>
|
||||
private static readonly SemaphoreSlim _queueSaveLock = new SemaphoreSlim(1, 1);
|
||||
|
||||
/// <summary>
|
||||
/// 是否已初始化队列
|
||||
/// </summary>
|
||||
private static bool _isQueueInitialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// 获取队列文件路径
|
||||
/// </summary>
|
||||
private static string GetQueueFilePath()
|
||||
{
|
||||
var configsDir = Path.Combine(App.RootPath, "Configs");
|
||||
if (!Directory.Exists(configsDir))
|
||||
{
|
||||
Directory.CreateDirectory(configsDir);
|
||||
}
|
||||
return Path.Combine(configsDir, QUEUE_FILE_NAME);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化上传队列
|
||||
/// </summary>
|
||||
public static void InitializeQueue()
|
||||
{
|
||||
if (_isQueueInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var queueFilePath = GetQueueFilePath();
|
||||
if (!File.Exists(queueFilePath))
|
||||
{
|
||||
_isQueueInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var jsonContent = File.ReadAllText(queueFilePath);
|
||||
if (string.IsNullOrWhiteSpace(jsonContent))
|
||||
{
|
||||
_isQueueInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var queueData = JsonConvert.DeserializeObject<List<UploadQueueItemData>>(jsonContent);
|
||||
if (queueData == null || queueData.Count == 0)
|
||||
{
|
||||
_isQueueInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
int restoredCount = 0;
|
||||
int skippedCount = 0;
|
||||
|
||||
foreach (var item in queueData)
|
||||
{
|
||||
// 验证文件是否存在
|
||||
if (!File.Exists(item.FilePath))
|
||||
{
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 验证文件格式和大小
|
||||
var fileExtension = Path.GetExtension(item.FilePath).ToLower();
|
||||
if (fileExtension != ".png" && fileExtension != ".icstk" && fileExtension != ".xml" && fileExtension != ".zip")
|
||||
{
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(item.FilePath);
|
||||
long maxSize = fileExtension == ".zip" ? 50 * 1024 * 1024 : 10 * 1024 * 1024;
|
||||
if (fileInfo.Length > maxSize)
|
||||
{
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 恢复队列项
|
||||
_uploadQueue.Enqueue(new UploadQueueItem
|
||||
{
|
||||
FilePath = item.FilePath,
|
||||
RetryCount = item.RetryCount
|
||||
});
|
||||
restoredCount++;
|
||||
}
|
||||
|
||||
_isQueueInitialized = true;
|
||||
|
||||
if (restoredCount > 0)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"已恢复上传队列:{restoredCount}个文件,跳过{skippedCount}个无效文件", LogHelper.LogType.Event);
|
||||
// 如果恢复了队列,触发处理
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await ProcessUploadQueueAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"恢复上传队列后处理时出错: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (skippedCount > 0)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"队列恢复完成:跳过{skippedCount}个无效文件", LogHelper.LogType.Event);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"恢复上传队列时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
_isQueueInitialized = true; // 即使出错也标记为已初始化,避免重复尝试
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存队列到文件
|
||||
/// </summary>
|
||||
private static async Task SaveQueueToFileAsync()
|
||||
{
|
||||
if (!await _queueSaveLock.WaitAsync(1000)) // 最多等待1秒
|
||||
{
|
||||
return; // 如果无法获取锁,跳过保存(避免阻塞)
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var queueData = new List<UploadQueueItemData>();
|
||||
|
||||
// 将队列转换为可序列化的格式
|
||||
foreach (var item in _uploadQueue)
|
||||
{
|
||||
queueData.Add(new UploadQueueItemData
|
||||
{
|
||||
FilePath = item.FilePath,
|
||||
RetryCount = item.RetryCount,
|
||||
AddedTime = DateTime.Now
|
||||
});
|
||||
}
|
||||
|
||||
var queueFilePath = GetQueueFilePath();
|
||||
|
||||
// 如果队列为空,清空文件
|
||||
if (queueData.Count == 0)
|
||||
{
|
||||
ClearQueueFile();
|
||||
return;
|
||||
}
|
||||
|
||||
var jsonContent = JsonConvert.SerializeObject(queueData, Formatting.Indented);
|
||||
|
||||
// 使用进程保护的写入门控,避免安全面板中“进程文件保护”占用导致无法写入
|
||||
var tempFilePath = queueFilePath + ".tmp";
|
||||
ProcessProtectionManager.WithWriteAccess(queueFilePath, () =>
|
||||
{
|
||||
File.WriteAllText(tempFilePath, jsonContent);
|
||||
if (File.Exists(queueFilePath))
|
||||
File.Delete(queueFilePath);
|
||||
File.Move(tempFilePath, queueFilePath);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存上传队列时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_queueSaveLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空队列文件
|
||||
/// </summary>
|
||||
private static void ClearQueueFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
var queueFilePath = GetQueueFilePath();
|
||||
ProcessProtectionManager.WithWriteAccess(queueFilePath, () =>
|
||||
{
|
||||
if (File.Exists(queueFilePath))
|
||||
File.WriteAllText(queueFilePath, "[]");
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清空队列文件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传笔记响应模型
|
||||
/// </summary>
|
||||
public class UploadNoteResponse
|
||||
{
|
||||
[JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
|
||||
[JsonProperty("message")]
|
||||
public string Message { get; set; }
|
||||
|
||||
[JsonProperty("note_id")]
|
||||
public int? NoteId { get; set; }
|
||||
|
||||
[JsonProperty("filename")]
|
||||
public string Filename { get; set; }
|
||||
|
||||
[JsonProperty("file_path")]
|
||||
public string FilePath { get; set; }
|
||||
|
||||
[JsonProperty("file_url")]
|
||||
public string FileUrl { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 白板信息模型(用于查找白板)
|
||||
/// </summary>
|
||||
private class WhiteboardInfo
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("board_id")]
|
||||
public string BoardId { get; set; }
|
||||
|
||||
[JsonProperty("secret_key")]
|
||||
public string SecretKey { get; set; }
|
||||
|
||||
[JsonProperty("class_name")]
|
||||
public string ClassName { get; set; }
|
||||
|
||||
[JsonProperty("class_id")]
|
||||
public int ClassId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 认证响应模型
|
||||
/// </summary>
|
||||
private class AuthWithTokenResponse
|
||||
{
|
||||
[JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
|
||||
[JsonProperty("whiteboards")]
|
||||
public List<WhiteboardInfo> Whiteboards { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步上传笔记文件到Dlass(支持PNG、ICSTK、XML和ZIP格式)
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径(支持PNG、ICSTK、XML和ZIP)</param>
|
||||
/// <returns>是否成功加入队列(不等待实际上传完成)</returns>
|
||||
public static async Task<bool> UploadNoteFileAsync(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查是否启用自动上传
|
||||
if (MainWindow.Settings?.Dlass?.IsAutoUploadNotes != true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 基本验证
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传失败:文件不存在 - {filePath}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
var fileExtension = Path.GetExtension(filePath).ToLower();
|
||||
if (fileExtension != ".png" && fileExtension != ".icstk" && fileExtension != ".xml" && fileExtension != ".zip")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
long maxSize = fileExtension == ".zip" ? 50 * 1024 * 1024 : 10 * 1024 * 1024;
|
||||
if (fileInfo.Length > maxSize)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传失败:文件过大({fileInfo.Length / 1024 / 1024}MB),超过{maxSize / 1024 / 1024}MB限制", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取上传延迟时间(分钟)
|
||||
var delayMinutes = MainWindow.Settings?.Dlass?.AutoUploadDelayMinutes ?? 0;
|
||||
|
||||
// 如果设置了延迟时间,在后台任务中等待后再加入队列
|
||||
if (delayMinutes > 0)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(delayMinutes)).ConfigureAwait(false);
|
||||
if (MainWindow.Settings?.Dlass?.IsAutoUploadNotes != true)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"延迟结束后自动上传已关闭,跳过入队: {filePath}", LogHelper.LogType.Event);
|
||||
return;
|
||||
}
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"延迟结束后文件已不存在,跳过入队: {filePath}", LogHelper.LogType.Event);
|
||||
return;
|
||||
}
|
||||
EnqueueFile(filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"延迟加入上传队列时出错: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
EnqueueFile(filePath);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加入上传队列时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将文件加入上传队列
|
||||
/// </summary>
|
||||
private static void EnqueueFile(string filePath, int retryCount = 0)
|
||||
{
|
||||
_uploadQueue.Enqueue(new UploadQueueItem
|
||||
{
|
||||
FilePath = filePath,
|
||||
RetryCount = retryCount
|
||||
});
|
||||
|
||||
// 异步保存队列到文件
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await SaveQueueToFileAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存上传队列时出错(后台任务): {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
|
||||
// 如果队列达到批量大小,触发批量上传
|
||||
if (_uploadQueue.Count >= BATCH_SIZE)
|
||||
{
|
||||
_ = ProcessUploadQueueAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理上传队列,批量上传文件
|
||||
/// </summary>
|
||||
private static async Task ProcessUploadQueueAsync()
|
||||
{
|
||||
// 使用信号量防止并发处理
|
||||
if (!await _queueProcessingLock.WaitAsync(0))
|
||||
{
|
||||
return; // 已有处理任务在运行
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var filesToUpload = new List<UploadQueueItem>();
|
||||
|
||||
// 从队列中取出最多BATCH_SIZE个文件
|
||||
while (filesToUpload.Count < BATCH_SIZE && _uploadQueue.TryDequeue(out UploadQueueItem item))
|
||||
{
|
||||
// 再次检查文件是否存在
|
||||
if (File.Exists(item.FilePath))
|
||||
{
|
||||
filesToUpload.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (filesToUpload.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取共享的白板信息(同一批次的所有文件共享认证信息)
|
||||
WhiteboardInfo sharedWhiteboard = null;
|
||||
string apiBaseUrl = null;
|
||||
string userToken = null;
|
||||
|
||||
try
|
||||
{
|
||||
var selectedClassName = MainWindow.Settings?.Dlass?.SelectedClassName;
|
||||
if (string.IsNullOrEmpty(selectedClassName))
|
||||
{
|
||||
LogHelper.WriteLogToFile("上传失败:未选择班级", LogHelper.LogType.Error);
|
||||
// 将文件重新加入队列
|
||||
foreach (var item in filesToUpload)
|
||||
{
|
||||
EnqueueFile(item.FilePath, item.RetryCount);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
userToken = MainWindow.Settings?.Dlass?.UserToken;
|
||||
if (string.IsNullOrEmpty(userToken))
|
||||
{
|
||||
LogHelper.WriteLogToFile("上传失败:未设置用户Token", LogHelper.LogType.Error);
|
||||
// 将文件重新加入队列
|
||||
foreach (var item in filesToUpload)
|
||||
{
|
||||
EnqueueFile(item.FilePath, item.RetryCount);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
apiBaseUrl = MainWindow.Settings?.Dlass?.ApiBaseUrl ?? "https://dlass.tech";
|
||||
|
||||
// 获取白板信息(只获取一次,所有文件共享)
|
||||
using (var apiClient = new DlassApiClient(APP_ID, APP_SECRET, apiBaseUrl, userToken))
|
||||
{
|
||||
var authData = new
|
||||
{
|
||||
app_id = APP_ID,
|
||||
app_secret = APP_SECRET,
|
||||
user_token = userToken
|
||||
};
|
||||
|
||||
var authResult = await apiClient.PostAsync<AuthWithTokenResponse>("/api/whiteboard/framework/auth-with-token", authData, requireAuth: false);
|
||||
|
||||
if (authResult == null || !authResult.Success || authResult.Whiteboards == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("上传失败:无法获取白板信息", LogHelper.LogType.Error);
|
||||
// 将文件重新加入队列
|
||||
foreach (var item in filesToUpload)
|
||||
{
|
||||
EnqueueFile(item.FilePath, item.RetryCount);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sharedWhiteboard = authResult.Whiteboards
|
||||
.FirstOrDefault(w => !string.IsNullOrEmpty(w.ClassName) && w.ClassName == selectedClassName);
|
||||
|
||||
if (sharedWhiteboard == null || string.IsNullOrEmpty(sharedWhiteboard.BoardId) || string.IsNullOrEmpty(sharedWhiteboard.SecretKey))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传失败:未找到班级'{selectedClassName}'对应的白板", LogHelper.LogType.Error);
|
||||
// 将文件重新加入队列
|
||||
foreach (var item in filesToUpload)
|
||||
{
|
||||
EnqueueFile(item.FilePath, item.RetryCount);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"批量上传获取白板信息时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
// 将文件重新加入队列
|
||||
foreach (var item in filesToUpload)
|
||||
{
|
||||
EnqueueFile(item.FilePath, item.RetryCount);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 并发上传所有文件(共享白板信息),并处理失败重试
|
||||
var uploadTasks = filesToUpload.Select(async item =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var success = await UploadFileInternalAsync(item.FilePath, sharedWhiteboard, apiBaseUrl, userToken);
|
||||
if (!success)
|
||||
{
|
||||
// 检查是否是可重试的错误
|
||||
if (IsRetryableError(item.FilePath))
|
||||
{
|
||||
// 检查重试次数
|
||||
if (item.RetryCount < MAX_RETRY_COUNT)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传失败,将重试 ({item.RetryCount + 1}/{MAX_RETRY_COUNT}): {Path.GetFileName(item.FilePath)}", LogHelper.LogType.Event);
|
||||
EnqueueFile(item.FilePath, item.RetryCount + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传失败,已达到最大重试次数: {Path.GetFileName(item.FilePath)}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 检查是否是可重试的错误(超时、网络错误等)
|
||||
var errorMessage = ex.Message.ToLower();
|
||||
bool isRetryable = errorMessage.Contains("超时") ||
|
||||
errorMessage.Contains("timeout") ||
|
||||
errorMessage.Contains("网络错误") ||
|
||||
errorMessage.Contains("network");
|
||||
|
||||
if (isRetryable && IsRetryableError(item.FilePath))
|
||||
{
|
||||
// 检查重试次数
|
||||
if (item.RetryCount < MAX_RETRY_COUNT)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传失败({ex.Message}),将重试 ({item.RetryCount + 1}/{MAX_RETRY_COUNT}): {Path.GetFileName(item.FilePath)}", LogHelper.LogType.Event);
|
||||
EnqueueFile(item.FilePath, item.RetryCount + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传失败,已达到最大重试次数: {Path.GetFileName(item.FilePath)}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
await Task.WhenAll(uploadTasks);
|
||||
|
||||
// 上传完成后保存队列状态
|
||||
await SaveQueueToFileAsync();
|
||||
|
||||
// 如果队列达到批量大小,继续处理
|
||||
if (_uploadQueue.Count >= BATCH_SIZE)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await ProcessUploadQueueAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"继续批量处理上传队列时出错: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_queueProcessingLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内部上传方法,执行实际上传操作
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
/// <param name="whiteboard">白板信息(如果为null则重新获取)</param>
|
||||
/// <param name="apiBaseUrl">API基础URL(如果为null则从设置获取)</param>
|
||||
/// <param name="userToken">用户Token(如果为null则从设置获取)</param>
|
||||
private static async Task<bool> UploadFileInternalAsync(string filePath, WhiteboardInfo whiteboard = null, string apiBaseUrl = null, string userToken = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 再次检查文件是否存在(可能在队列等待时被删除)
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查文件扩展名
|
||||
var fileExtension = Path.GetExtension(filePath).ToLower();
|
||||
if (fileExtension != ".png" && fileExtension != ".icstk" && fileExtension != ".xml" && fileExtension != ".zip")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查文件大小(最大10MB,ZIP文件可能更大,允许50MB)
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
long maxSize = fileExtension == ".zip" ? 50 * 1024 * 1024 : 10 * 1024 * 1024;
|
||||
if (fileInfo.Length > maxSize)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传失败:文件过大({fileInfo.Length / 1024 / 1024}MB),超过{maxSize / 1024 / 1024}MB限制", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果白板信息未提供,则重新获取
|
||||
if (whiteboard == null)
|
||||
{
|
||||
var selectedClassName = MainWindow.Settings?.Dlass?.SelectedClassName;
|
||||
if (string.IsNullOrEmpty(selectedClassName))
|
||||
{
|
||||
LogHelper.WriteLogToFile("上传失败:未选择班级", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
userToken = userToken ?? MainWindow.Settings?.Dlass?.UserToken;
|
||||
if (string.IsNullOrEmpty(userToken))
|
||||
{
|
||||
LogHelper.WriteLogToFile("上传失败:未设置用户Token", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
apiBaseUrl = apiBaseUrl ?? MainWindow.Settings?.Dlass?.ApiBaseUrl ?? "https://dlass.tech";
|
||||
|
||||
// 创建API客户端并获取白板信息
|
||||
using (var apiClient = new DlassApiClient(APP_ID, APP_SECRET, apiBaseUrl, userToken))
|
||||
{
|
||||
var authData = new
|
||||
{
|
||||
app_id = APP_ID,
|
||||
app_secret = APP_SECRET,
|
||||
user_token = userToken
|
||||
};
|
||||
|
||||
var authResult = await apiClient.PostAsync<AuthWithTokenResponse>("/api/whiteboard/framework/auth-with-token", authData, requireAuth: false);
|
||||
|
||||
if (authResult == null || !authResult.Success || authResult.Whiteboards == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("上传失败:无法获取白板信息", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 查找匹配班级的白板
|
||||
whiteboard = authResult.Whiteboards
|
||||
.FirstOrDefault(w => !string.IsNullOrEmpty(w.ClassName) && w.ClassName == selectedClassName);
|
||||
|
||||
if (whiteboard == null || string.IsNullOrEmpty(whiteboard.BoardId) || string.IsNullOrEmpty(whiteboard.SecretKey))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传失败:未找到班级'{selectedClassName}'对应的白板", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取API基础URL和用户Token(如果未提供)
|
||||
apiBaseUrl = apiBaseUrl ?? MainWindow.Settings?.Dlass?.ApiBaseUrl ?? "https://dlass.tech";
|
||||
userToken = userToken ?? MainWindow.Settings?.Dlass?.UserToken;
|
||||
|
||||
// 准备上传参数
|
||||
var fileName = Path.GetFileNameWithoutExtension(filePath);
|
||||
var title = fileName;
|
||||
string fileType;
|
||||
string tags;
|
||||
if (fileExtension == ".zip")
|
||||
{
|
||||
fileType = "多页面墨迹压缩包";
|
||||
tags = "自动上传,多页面,zip,压缩包";
|
||||
}
|
||||
else if (fileExtension == ".icstk")
|
||||
{
|
||||
fileType = "墨迹文件";
|
||||
tags = "自动上传,墨迹,icstk";
|
||||
}
|
||||
else if (fileExtension == ".xml")
|
||||
{
|
||||
fileType = "XML文件";
|
||||
tags = "自动上传,xml";
|
||||
}
|
||||
else
|
||||
{
|
||||
fileType = "笔记";
|
||||
tags = "自动上传,笔记,png";
|
||||
}
|
||||
var description = $"自动上传的{fileType} - {DateTime.Now:yyyy-MM-dd HH:mm:ss}";
|
||||
|
||||
// 创建API客户端并上传文件
|
||||
using (var apiClient = new DlassApiClient(APP_ID, APP_SECRET, apiBaseUrl, userToken))
|
||||
{
|
||||
var uploadResult = await apiClient.UploadNoteAsync<UploadNoteResponse>(
|
||||
"/api/whiteboard/upload_note",
|
||||
filePath,
|
||||
whiteboard.BoardId,
|
||||
whiteboard.SecretKey,
|
||||
title,
|
||||
description,
|
||||
tags);
|
||||
|
||||
if (uploadResult != null && uploadResult.Success)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"笔记上传成功:{fileName} -> {uploadResult.FileUrl}", LogHelper.LogType.Event);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传失败:服务器响应失败 - {uploadResult?.Message ?? "未知错误"}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 记录错误信息,抛出异常以便调用方判断是否可重试
|
||||
LogHelper.WriteLogToFile($"上传笔记时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断错误是否可重试(超时、网络错误等)
|
||||
/// </summary>
|
||||
private static bool IsRetryableError(string filePath)
|
||||
{
|
||||
// 检查文件是否存在
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
return false; // 文件不存在,不可重试
|
||||
}
|
||||
|
||||
// 检查文件扩展名
|
||||
var fileExtension = Path.GetExtension(filePath).ToLower();
|
||||
if (fileExtension != ".png" && fileExtension != ".icstk" && fileExtension != ".xml" && fileExtension != ".zip")
|
||||
{
|
||||
return false; // 文件格式错误,不可重试
|
||||
}
|
||||
|
||||
// 检查文件大小
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
long maxSize = fileExtension == ".zip" ? 50 * 1024 * 1024 : 10 * 1024 * 1024;
|
||||
if (fileInfo.Length > maxSize)
|
||||
{
|
||||
return false; // 文件过大,不可重试
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false; // 无法读取文件信息,不可重试
|
||||
}
|
||||
|
||||
// 其他错误(超时、网络错误等)可以重试
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Dlass上传队列
|
||||
/// </summary>
|
||||
public class DlassUploadQueue : BaseUploadQueue
|
||||
{
|
||||
private const string APP_ID = "app_WkjocWqsrVY7T6zQV2CfiA";
|
||||
private const string APP_SECRET = "o7dx5b5ASGUMcM72PCpmRQYAhSijqaOVHoGyBK0IxbA";
|
||||
|
||||
/// <summary>
|
||||
/// 队列文件名
|
||||
/// </summary>
|
||||
protected override string QueueFileName => "DlassUploadQueue.json";
|
||||
|
||||
/// <summary>
|
||||
/// 上传笔记响应模型
|
||||
/// </summary>
|
||||
public class UploadNoteResponse
|
||||
{
|
||||
[JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
|
||||
[JsonProperty("message")]
|
||||
public string Message { get; set; }
|
||||
|
||||
[JsonProperty("note_id")]
|
||||
public int? NoteId { get; set; }
|
||||
|
||||
[JsonProperty("filename")]
|
||||
public string Filename { get; set; }
|
||||
|
||||
[JsonProperty("file_path")]
|
||||
public string FilePath { get; set; }
|
||||
|
||||
[JsonProperty("file_url")]
|
||||
public string FileUrl { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 白板信息模型(用于查找白板)
|
||||
/// </summary>
|
||||
private class WhiteboardInfo
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("board_id")]
|
||||
public string BoardId { get; set; }
|
||||
|
||||
[JsonProperty("secret_key")]
|
||||
public string SecretKey { get; set; }
|
||||
|
||||
[JsonProperty("class_name")]
|
||||
public string ClassName { get; set; }
|
||||
|
||||
[JsonProperty("class_id")]
|
||||
public int ClassId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 认证响应模型
|
||||
/// </summary>
|
||||
private class AuthWithTokenResponse
|
||||
{
|
||||
[JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
|
||||
[JsonProperty("whiteboards")]
|
||||
public List<WhiteboardInfo> Whiteboards { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查上传是否启用
|
||||
/// </summary>
|
||||
protected override bool IsUploadEnabled()
|
||||
{
|
||||
return MainWindow.Settings?.Dlass?.IsAutoUploadNotes == true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内部上传方法,执行实际上传操作
|
||||
/// </summary>
|
||||
protected override async Task<bool> UploadFileInternalAsync(string filePath, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// 再次检查文件是否存在(可能在队列等待时被删除)
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取白板信息
|
||||
var whiteboard = await GetWhiteboardInfo(cancellationToken);
|
||||
if (whiteboard == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取API基础URL和用户Token
|
||||
var apiBaseUrl = MainWindow.Settings?.Dlass?.ApiBaseUrl ?? "https://dlass.tech";
|
||||
var userToken = MainWindow.Settings?.Dlass?.UserToken;
|
||||
|
||||
// 准备上传参数
|
||||
var fileName = Path.GetFileNameWithoutExtension(filePath);
|
||||
var fileExtension = Path.GetExtension(filePath).ToLower();
|
||||
var title = fileName;
|
||||
string fileType;
|
||||
string tags;
|
||||
if (fileExtension == ".zip")
|
||||
{
|
||||
fileType = "多页面墨迹压缩包";
|
||||
tags = "自动上传,多页面,zip,压缩包";
|
||||
}
|
||||
else if (fileExtension == ".icstk")
|
||||
{
|
||||
fileType = "墨迹文件";
|
||||
tags = "自动上传,墨迹,icstk";
|
||||
}
|
||||
else if (fileExtension == ".xml")
|
||||
{
|
||||
fileType = "XML文件";
|
||||
tags = "自动上传,xml";
|
||||
}
|
||||
else
|
||||
{
|
||||
fileType = "笔记";
|
||||
tags = "自动上传,笔记,png";
|
||||
}
|
||||
var description = $"自动上传的{fileType} - {DateTime.Now:yyyy-MM-dd HH:mm:ss}";
|
||||
|
||||
// 创建API客户端并上传文件
|
||||
var apiClient = new DlassApiClient(APP_ID, APP_SECRET, apiBaseUrl, userToken);
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var uploadResult = await apiClient.UploadNoteAsync<UploadNoteResponse>(
|
||||
"/api/whiteboard/upload_note",
|
||||
filePath,
|
||||
whiteboard.BoardId,
|
||||
whiteboard.SecretKey,
|
||||
title,
|
||||
description,
|
||||
tags,
|
||||
cancellationToken);
|
||||
|
||||
if (uploadResult != null && uploadResult.Success)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
apiClient.Dispose();
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取白板信息
|
||||
/// </summary>
|
||||
private async Task<WhiteboardInfo> GetWhiteboardInfo(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var selectedClassName = MainWindow.Settings?.Dlass?.SelectedClassName;
|
||||
if (string.IsNullOrEmpty(selectedClassName))
|
||||
{
|
||||
LogHelper.WriteLogToFile("[DlassUploadQueue] 上传失败:未选择班级", LogHelper.LogType.Error);
|
||||
return null;
|
||||
}
|
||||
|
||||
var userToken = MainWindow.Settings?.Dlass?.UserToken;
|
||||
if (string.IsNullOrEmpty(userToken))
|
||||
{
|
||||
LogHelper.WriteLogToFile("[DlassUploadQueue] 上传失败:未设置用户Token", LogHelper.LogType.Error);
|
||||
return null;
|
||||
}
|
||||
|
||||
var apiBaseUrl = MainWindow.Settings?.Dlass?.ApiBaseUrl ?? "https://dlass.tech";
|
||||
|
||||
// 创建API客户端并获取白板信息
|
||||
var apiClient = new DlassApiClient(APP_ID, APP_SECRET, apiBaseUrl, userToken);
|
||||
try
|
||||
{
|
||||
var authData = new
|
||||
{
|
||||
app_id = APP_ID,
|
||||
app_secret = APP_SECRET,
|
||||
user_token = userToken
|
||||
};
|
||||
|
||||
var authResult = await apiClient.PostAsync<AuthWithTokenResponse>("/api/whiteboard/framework/auth-with-token", authData, requireAuth: false, cancellationToken: cancellationToken);
|
||||
|
||||
if (authResult == null || !authResult.Success || authResult.Whiteboards == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("[DlassUploadQueue] 上传失败:无法获取白板信息", LogHelper.LogType.Error);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 查找匹配班级的白板
|
||||
var whiteboard = authResult.Whiteboards
|
||||
.FirstOrDefault(w => !string.IsNullOrEmpty(w.ClassName) && w.ClassName == selectedClassName);
|
||||
|
||||
if (whiteboard == null || string.IsNullOrEmpty(whiteboard.BoardId) || string.IsNullOrEmpty(whiteboard.SecretKey))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[DlassUploadQueue] 上传失败:未找到班级'{selectedClassName}'对应的白板", LogHelper.LogType.Error);
|
||||
return null;
|
||||
}
|
||||
|
||||
return whiteboard;
|
||||
}
|
||||
finally
|
||||
{
|
||||
apiClient.Dispose();
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
@@ -24,8 +26,9 @@ namespace Ink_Canvas.Helpers
|
||||
/// 上传文件
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>是否上传成功</returns>
|
||||
Task<bool> UploadAsync(string filePath);
|
||||
Task<bool> UploadAsync(string filePath, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -33,6 +36,8 @@ namespace Ink_Canvas.Helpers
|
||||
/// </summary>
|
||||
public class DlassUploadProvider : IUploadProvider
|
||||
{
|
||||
public static readonly DlassUploadQueue Queue = new DlassUploadQueue();
|
||||
|
||||
/// <summary>
|
||||
/// 提供者名称
|
||||
/// </summary>
|
||||
@@ -41,16 +46,46 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 是否启用
|
||||
/// </summary>
|
||||
public bool IsEnabled => MainWindow.Settings?.Dlass?.IsAutoUploadNotes ?? false;
|
||||
public bool IsEnabled => MainWindow.Settings?.Upload?.EnabledProviders?.Contains(Name) ?? false;
|
||||
|
||||
/// <summary>
|
||||
/// 上传文件
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>是否上传成功</returns>
|
||||
public async Task<bool> UploadAsync(string filePath)
|
||||
public async Task<bool> UploadAsync(string filePath, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await DlassNoteUploader.UploadNoteFileAsync(filePath);
|
||||
return await Queue.UploadFileAsync(filePath, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WebDav上传提供者
|
||||
/// </summary>
|
||||
public class WebDavUploadProvider : IUploadProvider
|
||||
{
|
||||
public static readonly WebDavUploadQueue Queue = new WebDavUploadQueue();
|
||||
|
||||
/// <summary>
|
||||
/// 提供者名称
|
||||
/// </summary>
|
||||
public string Name => "WebDav";
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用
|
||||
/// </summary>
|
||||
public bool IsEnabled => MainWindow.Settings?.Upload?.EnabledProviders?.Contains(Name) ?? false;
|
||||
|
||||
/// <summary>
|
||||
/// 上传文件
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>是否上传成功</returns>
|
||||
public async Task<bool> UploadAsync(string filePath, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await Queue.UploadFileAsync(filePath, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +112,14 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
// 注册默认上传提供者
|
||||
RegisterProviderInternal(new DlassUploadProvider());
|
||||
RegisterProviderInternal(new WebDavUploadProvider());
|
||||
|
||||
// 注册上传队列
|
||||
UploadQueueHelper.RegisterQueue(DlassUploadProvider.Queue);
|
||||
UploadQueueHelper.RegisterQueue(WebDavUploadProvider.Queue);
|
||||
|
||||
// 初始化所有上传队列
|
||||
UploadQueueHelper.InitializeAllQueues();
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
@@ -113,8 +156,9 @@ namespace Ink_Canvas.Helpers
|
||||
/// 上传文件到所有启用的提供者
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>是否至少有一个提供者上传成功</returns>
|
||||
public static async Task<bool> UploadFileAsync(string filePath)
|
||||
public static async Task<bool> UploadFileAsync(string filePath, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
@@ -129,19 +173,56 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
bool anySuccess = false;
|
||||
|
||||
// 获取上传延迟时间
|
||||
int delayMinutes = MainWindow.Settings?.Upload?.UploadDelayMinutes ?? 0;
|
||||
|
||||
// 应用上传延迟
|
||||
if (delayMinutes > 0)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传延迟 {delayMinutes} 分钟", LogHelper.LogType.Event);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
await Task.Delay(TimeSpan.FromMinutes(delayMinutes), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// 上传前验证文件是否存在且可访问
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传失败:文件不存在 - {filePath}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 检查文件是否可访问
|
||||
using (var fileStream = File.OpenRead(filePath))
|
||||
{
|
||||
// 文件可访问
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传失败:文件不可访问 - {filePath}, 原因: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var provider in providersSnapshot)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (provider.IsEnabled)
|
||||
{
|
||||
bool success = await provider.UploadAsync(filePath);
|
||||
bool success = await provider.UploadAsync(filePath, cancellationToken).ConfigureAwait(false);
|
||||
if (success)
|
||||
{
|
||||
anySuccess = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"上传被取消: {provider.Name}", LogHelper.LogType.Event);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"使用 {provider.Name} 上传失败: {ex}", LogHelper.LogType.Error);
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 上传队列帮助类,提供统一的队列管理功能
|
||||
/// </summary>
|
||||
public static class UploadQueueHelper
|
||||
{
|
||||
private static readonly List<BaseUploadQueue> _queues = new List<BaseUploadQueue>();
|
||||
private static readonly object _syncLock = new object();
|
||||
private static volatile bool _initialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化所有上传队列
|
||||
/// </summary>
|
||||
public static void InitializeAllQueues()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (_initialized)
|
||||
return;
|
||||
|
||||
// 初始化所有注册的队列
|
||||
foreach (var queue in _queues)
|
||||
{
|
||||
try
|
||||
{
|
||||
queue.InitializeQueue();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[UploadQueueHelper] 初始化队列时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册上传队列
|
||||
/// </summary>
|
||||
/// <param name="queue">上传队列实例</param>
|
||||
public static void RegisterQueue(BaseUploadQueue queue)
|
||||
{
|
||||
if (queue == null)
|
||||
return;
|
||||
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (!_queues.Contains(queue))
|
||||
{
|
||||
try
|
||||
{
|
||||
// 先初始化队列,再添加到列表
|
||||
queue.InitializeQueue();
|
||||
_queues.Add(queue);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[UploadQueueHelper] 注册队列时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有注册的上传队列
|
||||
/// </summary>
|
||||
/// <returns>上传队列列表</returns>
|
||||
public static IReadOnlyList<BaseUploadQueue> GetAllQueues()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
return new List<BaseUploadQueue>(_queues).AsReadOnly();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保所有队列都已初始化
|
||||
/// </summary>
|
||||
public static void EnsureQueuesInitialized()
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
InitializeAllQueues();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// WebDAV上传队列
|
||||
/// </summary>
|
||||
public class WebDavUploadQueue : BaseUploadQueue
|
||||
{
|
||||
/// <summary>
|
||||
/// 队列文件名
|
||||
/// </summary>
|
||||
protected override string QueueFileName => "WebDavUploadQueue.json";
|
||||
|
||||
/// <summary>
|
||||
/// 检查上传是否启用
|
||||
/// </summary>
|
||||
protected override bool IsUploadEnabled()
|
||||
{
|
||||
return WebDavUploader.IsWebDavEnabled();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内部上传方法,执行实际上传操作
|
||||
/// </summary>
|
||||
protected override async Task<bool> UploadFileInternalAsync(string filePath, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// 再次检查文件是否存在(可能在队列等待时被删除)
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查WebDAV是否仍然启用
|
||||
if (!WebDavUploader.IsWebDavEnabled())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 调用WebDavUploader进行实际上传
|
||||
var success = await WebDavUploader.UploadFileAsync(filePath, cancellationToken);
|
||||
return success;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using WebDav;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// WebDav上传工具类
|
||||
/// </summary>
|
||||
public static class WebDavUploader
|
||||
{
|
||||
/// <summary>
|
||||
/// 上传文件到WebDav服务器
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>是否上传成功</returns>
|
||||
public static async Task<bool> UploadFileAsync(string filePath, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// 检查文件是否存在
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取WebDav设置
|
||||
var webDavUrl = MainWindow.Settings?.Dlass?.WebDavUrl;
|
||||
var username = MainWindow.Settings?.Dlass?.WebDavUsername;
|
||||
var password = MainWindow.Settings?.Dlass?.WebDavPassword;
|
||||
var rootDirectory = MainWindow.Settings?.Dlass?.WebDavRootDirectory;
|
||||
|
||||
// 验证设置
|
||||
if (string.IsNullOrEmpty(webDavUrl))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 构建完整的目标路径
|
||||
var fileName = Path.GetFileName(filePath);
|
||||
var targetPath = Path.Combine(rootDirectory ?? string.Empty, fileName).Replace("\\", "/");
|
||||
if (targetPath.StartsWith("/"))
|
||||
{
|
||||
targetPath = targetPath.Substring(1);
|
||||
}
|
||||
|
||||
// 创建WebDav客户端
|
||||
var clientParams = new WebDavClientParams
|
||||
{
|
||||
BaseAddress = new Uri(webDavUrl),
|
||||
Credentials = new NetworkCredential(username ?? string.Empty, password ?? string.Empty)
|
||||
};
|
||||
|
||||
using (var client = new WebDavClient(clientParams))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// 先直接尝试上传文件
|
||||
using (var fileStream = File.OpenRead(filePath))
|
||||
{
|
||||
// 检查取消令牌
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var result = await client.PutFile(targetPath, fileStream);
|
||||
if (result.IsSuccessful)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 上传失败,尝试创建目录
|
||||
var directoryPath = Path.GetDirectoryName(targetPath);
|
||||
if (!string.IsNullOrEmpty(directoryPath))
|
||||
{
|
||||
await EnsureDirectoryExistsAsync(client, directoryPath, cancellationToken);
|
||||
|
||||
// 再次尝试上传文件
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
using (var retryStream = File.OpenRead(filePath))
|
||||
{
|
||||
var retryResult = await client.PutFile(targetPath, retryStream);
|
||||
return retryResult.IsSuccessful;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 没有目录路径,直接返回失败
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保WebDav目录存在
|
||||
/// </summary>
|
||||
/// <param name="client">WebDav客户端</param>
|
||||
/// <param name="directoryPath">目录路径</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
private static async Task EnsureDirectoryExistsAsync(IWebDavClient client, string directoryPath, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 分割路径并逐级创建目录
|
||||
var pathParts = directoryPath.Split('/');
|
||||
var currentPath = string.Empty;
|
||||
|
||||
foreach (var part in pathParts)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (string.IsNullOrEmpty(part))
|
||||
continue;
|
||||
|
||||
currentPath = Path.Combine(currentPath, part).Replace("\\", "/");
|
||||
|
||||
// 检查取消令牌
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// 尝试创建目录
|
||||
await client.Mkcol(currentPath);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 静默处理目录创建错误
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查WebDAV是否已启用
|
||||
/// </summary>
|
||||
/// <returns>是否启用</returns>
|
||||
public static bool IsWebDavEnabled()
|
||||
{
|
||||
// 检查WebDav设置是否有效
|
||||
var webDavUrl = MainWindow.Settings?.Dlass?.WebDavUrl;
|
||||
if (string.IsNullOrEmpty(webDavUrl))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 尝试解析URL
|
||||
try
|
||||
{
|
||||
new Uri(webDavUrl);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,6 +166,7 @@
|
||||
<PackageReference Include="AForge.Video.DirectShow" Version="2.2.5" />
|
||||
<PackageReference Include="AForge.Imaging" Version="2.2.5" />
|
||||
<PackageReference Include="AForge.Math" Version="2.2.5" />
|
||||
<PackageReference Include="WebDav.Client" Version="2.9.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<COMReference Include="IWshRuntimeLibrary">
|
||||
|
||||
@@ -3655,7 +3655,7 @@
|
||||
StrokeThickness="1" Margin="0,8,0,8" />
|
||||
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,8,0,0">
|
||||
<Button Name="BtnDlassSettingsManage" Content="Dlass设置管理"
|
||||
<Button Name="BtnDlassSettingsManage" Content="云存储管理"
|
||||
HorizontalAlignment="Left"
|
||||
Click="BtnDlassSettingsManage_Click"
|
||||
Padding="15,5"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using Ink_Canvas.Helpers.Plugins;
|
||||
using Ink_Canvas.Properties;
|
||||
using Ink_Canvas.Windows;
|
||||
using iNKORE.UI.WPF.Modern;
|
||||
using iNKORE.UI.WPF.Modern.Controls;
|
||||
@@ -1159,8 +1158,16 @@ namespace Ink_Canvas
|
||||
AutoBackupManager.Initialize(Settings);
|
||||
CheckUpdateChannelAndTelemetryConsistency();
|
||||
|
||||
// 初始化Dlass上传队列(恢复上次的上传队列)
|
||||
DlassNoteUploader.InitializeQueue();
|
||||
// 初始化上传队列(恢复上次的上传队列)
|
||||
try
|
||||
{
|
||||
UploadQueueHelper.InitializeAllQueues();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[MainWindow] 初始化上传队列时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
// 继续执行其他初始化操作,不中断整个加载过程
|
||||
}
|
||||
|
||||
_ = TelemetryUploader.UploadTelemetryIfNeededAsync();
|
||||
|
||||
@@ -1995,7 +2002,7 @@ namespace Ink_Canvas
|
||||
else
|
||||
{
|
||||
// 下载失败
|
||||
MessageBox.Show(Strings.GetString("Msg_UpdateDownloadFailed"), Strings.GetString("Msg_DownloadFailedTitle"), MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
MessageBox.Show("更新下载失败,请检查网络连接后重试。", "下载失败", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -2018,12 +2025,12 @@ namespace Ink_Canvas
|
||||
timerCheckAutoUpdateWithSilence.Start();
|
||||
|
||||
// 通知用户
|
||||
MessageBox.Show(Strings.GetString("Msg_UpdateReady"), Strings.GetString("Msg_UpdateReadyTitle"), MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
MessageBox.Show("更新已下载完成,将在软件关闭时自动安装。", "更新已准备就绪", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile("AutoUpdate | Update download failed", LogHelper.LogType.Error);
|
||||
MessageBox.Show(Strings.GetString("Msg_UpdateDownloadFailed"), Strings.GetString("Msg_DownloadFailedTitle"), MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
MessageBox.Show("更新下载失败,请检查网络连接后重试。", "下载失败", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -2038,8 +2045,8 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
|
||||
// 通知用户
|
||||
MessageBox.Show(string.Format(Strings.GetString("Msg_SkipVersion"), AvailableLatestVersion),
|
||||
Strings.GetString("Msg_SkipVersionTitle"),
|
||||
MessageBox.Show($"已设置跳过版本 {AvailableLatestVersion},在下次发布新版本之前不会再提示更新。",
|
||||
"已跳过此版本",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Information);
|
||||
break;
|
||||
|
||||
@@ -95,7 +95,125 @@ namespace Ink_Canvas
|
||||
//savePathWithName = savePath + @"\" + DateTime.Now.ToString("u").Replace(':', '-') + ".icstk";
|
||||
savePathWithName = savePath + @"\" + DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss-fff") + ".icstk";
|
||||
|
||||
if (Settings.Automation.IsSaveFullPageStrokes)
|
||||
if (Settings.Automation.IsSaveStrokesAsXML)
|
||||
{
|
||||
// XML保存模式 - 检查是否存在多页面墨迹
|
||||
bool hasMultiplePages = false;
|
||||
List<StrokeCollection> allPageStrokes = new List<StrokeCollection>();
|
||||
|
||||
// 检查PPT放映模式下的多页面墨迹
|
||||
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible && _pptManager?.IsConnected == true)
|
||||
{
|
||||
hasMultiplePages = true;
|
||||
var totalSlides = _pptManager.SlidesCount;
|
||||
var currentSlide = _pptManager.GetCurrentSlideNumber();
|
||||
|
||||
for (int i = 1; i <= totalSlides; i++)
|
||||
{
|
||||
var slideStrokes = _singlePPTInkManager?.LoadSlideStrokes(i);
|
||||
if (slideStrokes != null && slideStrokes.Count > 0)
|
||||
{
|
||||
allPageStrokes.Add(slideStrokes);
|
||||
}
|
||||
else if (i == currentSlide && inkCanvas.Strokes.Count > 0)
|
||||
{
|
||||
allPageStrokes.Add(inkCanvas.Strokes.Clone());
|
||||
}
|
||||
else
|
||||
{
|
||||
allPageStrokes.Add(new StrokeCollection());
|
||||
}
|
||||
}
|
||||
}
|
||||
// 检查白板模式下的多页面墨迹
|
||||
else if (currentMode != 0 && WhiteboardTotalCount > 1)
|
||||
{
|
||||
hasMultiplePages = true;
|
||||
for (int i = 1; i <= WhiteboardTotalCount; i++)
|
||||
{
|
||||
if (TimeMachineHistories[i] != null)
|
||||
{
|
||||
var strokes = ApplyHistoriesToNewStrokeCollection(TimeMachineHistories[i]);
|
||||
allPageStrokes.Add(strokes);
|
||||
}
|
||||
else
|
||||
{
|
||||
allPageStrokes.Add(new StrokeCollection());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasMultiplePages && allPageStrokes.Count > 0)
|
||||
{
|
||||
// 检查是否是PPT模式
|
||||
bool isPPTMode = BtnPPTSlideShowEnd.Visibility == Visibility.Visible && _pptManager?.IsConnected == true;
|
||||
|
||||
if (isPPTMode)
|
||||
{
|
||||
// PPT模式:保存为多个XML文件
|
||||
string basePath = Path.GetDirectoryName(savePathWithName);
|
||||
string baseFileName = Path.GetFileNameWithoutExtension(savePathWithName);
|
||||
|
||||
int savedCount = 0;
|
||||
for (int i = 0; i < allPageStrokes.Count; i++)
|
||||
{
|
||||
var strokes = allPageStrokes[i];
|
||||
if (strokes.Count > 0)
|
||||
{
|
||||
string pageFileName = Path.Combine(basePath, $"{baseFileName}_Page-{i + 1}.xml");
|
||||
SaveStrokesAsXML(strokes, pageFileName, false);
|
||||
savedCount++;
|
||||
|
||||
// 异步上传每个XML文件
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await Helpers.UploadHelper.UploadFileAsync(pageFileName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (newNotice)
|
||||
{
|
||||
Task.Delay(100).ContinueWith(t =>
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
ShowNotification($"多页面XML墨迹成功保存为 {savedCount} 个XML文件");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 非PPT模式:保存为XML压缩包
|
||||
string zipFileName = Path.ChangeExtension(savePathWithName, "zip");
|
||||
SaveMultiPageStrokesAsXMLZip(allPageStrokes, zipFileName, newNotice);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 单页面XML保存
|
||||
string xmlPath = Path.ChangeExtension(savePathWithName, ".xml");
|
||||
SaveStrokesAsXML(inkCanvas.Strokes, xmlPath);
|
||||
if (newNotice)
|
||||
{
|
||||
Task.Delay(100).ContinueWith(t =>
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
ShowNotification("墨迹成功保存为XML格式至 " + xmlPath);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Settings.Automation.IsSaveFullPageStrokes)
|
||||
{
|
||||
// 全页面保存模式 - 检查是否存在多页面墨迹
|
||||
bool hasMultiplePages = false;
|
||||
@@ -159,9 +277,9 @@ namespace Ink_Canvas
|
||||
SaveSinglePageStrokesAsImage(savePathWithName, newNotice);
|
||||
}
|
||||
}
|
||||
else if (Settings.Automation.IsSaveStrokesAsXML)
|
||||
else
|
||||
{
|
||||
// XML保存模式 - 检查是否存在多页面墨迹
|
||||
// 常规保存模式 - 检查是否存在多页面墨迹
|
||||
bool hasMultiplePages = false;
|
||||
List<StrokeCollection> allPageStrokes = new List<StrokeCollection>();
|
||||
|
||||
@@ -209,99 +327,117 @@ namespace Ink_Canvas
|
||||
|
||||
if (hasMultiplePages && allPageStrokes.Count > 0)
|
||||
{
|
||||
// 多页面XML保存为压缩包
|
||||
string zipFileName = Path.ChangeExtension(savePathWithName, "zip");
|
||||
SaveMultiPageStrokesAsXMLZip(allPageStrokes, zipFileName, newNotice);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 单页面XML保存
|
||||
string xmlPath = Path.ChangeExtension(savePathWithName, ".xml");
|
||||
SaveStrokesAsXML(inkCanvas.Strokes, xmlPath);
|
||||
if (newNotice)
|
||||
// 多页面保存为多个icstk文件
|
||||
string basePath = Path.GetDirectoryName(savePathWithName);
|
||||
string baseFileName = Path.GetFileNameWithoutExtension(savePathWithName);
|
||||
|
||||
for (int i = 0; i < allPageStrokes.Count; i++)
|
||||
{
|
||||
Task.Delay(100).ContinueWith(t =>
|
||||
var strokes = allPageStrokes[i];
|
||||
if (strokes.Count > 0)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
string pageFileName = Path.Combine(basePath, $"{baseFileName}_Page-{i + 1}.icstk");
|
||||
using (var fs = new FileStream(pageFileName, FileMode.Create))
|
||||
{
|
||||
ShowNotification("墨迹成功保存为XML格式至 " + xmlPath);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 常规保存模式 - 仅保存墨迹对象
|
||||
if (Settings.Automation.IsSaveStrokesAsXML)
|
||||
{
|
||||
// 保存为XML格式
|
||||
string xmlPath = Path.ChangeExtension(savePathWithName, ".xml");
|
||||
SaveStrokesAsXML(inkCanvas.Strokes, xmlPath);
|
||||
if (newNotice)
|
||||
{
|
||||
Task.Delay(100).ContinueWith(t =>
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
strokes.Save(fs);
|
||||
}
|
||||
|
||||
// 异步上传每个icstk文件
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
ShowNotification("墨迹成功保存为XML格式至 " + xmlPath);
|
||||
try
|
||||
{
|
||||
await Helpers.UploadHelper.UploadFileAsync(pageFileName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 保存为二进制格式
|
||||
var fs = new FileStream(savePathWithName, FileMode.Create);
|
||||
inkCanvas.Strokes.Save(fs);
|
||||
fs.Close();
|
||||
if (newNotice)
|
||||
{
|
||||
Task.Delay(100).ContinueWith(t =>
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
ShowNotification("墨迹成功保存至 " + savePathWithName);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var delayMinutes = Settings?.Dlass?.AutoUploadDelayMinutes ?? 0;
|
||||
if (delayMinutes > 0)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(delayMinutes));
|
||||
}
|
||||
|
||||
await Helpers.DlassNoteUploader.UploadNoteFileAsync(savePathWithName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
});
|
||||
|
||||
// 保存元素信息
|
||||
var elementInfos = new List<CanvasElementInfo>();
|
||||
foreach (var child in inkCanvas.Children)
|
||||
{
|
||||
if (child is Image img && img.Source is BitmapImage bmp)
|
||||
if (newNotice)
|
||||
{
|
||||
elementInfos.Add(new CanvasElementInfo
|
||||
Task.Delay(100).ContinueWith(t =>
|
||||
{
|
||||
Type = "Image",
|
||||
SourcePath = bmp.UriSource?.LocalPath ?? "",
|
||||
Left = InkCanvas.GetLeft(img),
|
||||
Top = InkCanvas.GetTop(img),
|
||||
Width = img.Width,
|
||||
Height = img.Height,
|
||||
Stretch = img.Stretch.ToString()
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
ShowNotification($"多页面墨迹成功保存为 {allPageStrokes.Count} 个icstk文件");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
File.WriteAllText(Path.ChangeExtension(savePathWithName, ".elements.json"), JsonConvert.SerializeObject(elementInfos, Newtonsoft.Json.Formatting.Indented));
|
||||
else
|
||||
{
|
||||
// 单页面保存
|
||||
if (Settings.Automation.IsSaveStrokesAsXML)
|
||||
{
|
||||
// 保存为XML格式
|
||||
string xmlPath = Path.ChangeExtension(savePathWithName, ".xml");
|
||||
SaveStrokesAsXML(inkCanvas.Strokes, xmlPath);
|
||||
if (newNotice)
|
||||
{
|
||||
Task.Delay(100).ContinueWith(t =>
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
ShowNotification("墨迹成功保存为XML格式至 " + xmlPath);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 保存为二进制格式
|
||||
var fs = new FileStream(savePathWithName, FileMode.Create);
|
||||
inkCanvas.Strokes.Save(fs);
|
||||
fs.Close();
|
||||
if (newNotice)
|
||||
{
|
||||
Task.Delay(100).ContinueWith(t =>
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
ShowNotification("墨迹成功保存至 " + savePathWithName);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 异步上传文件
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
string uploadPath = Settings.Automation.IsSaveStrokesAsXML ? Path.ChangeExtension(savePathWithName, ".xml") : savePathWithName;
|
||||
await Helpers.UploadHelper.UploadFileAsync(uploadPath);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
});
|
||||
|
||||
// 保存元素信息
|
||||
var elementInfos = new List<CanvasElementInfo>();
|
||||
foreach (var child in inkCanvas.Children)
|
||||
{
|
||||
if (child is Image img && img.Source is BitmapImage bmp)
|
||||
{
|
||||
elementInfos.Add(new CanvasElementInfo
|
||||
{
|
||||
Type = "Image",
|
||||
SourcePath = bmp.UriSource?.LocalPath ?? "",
|
||||
Left = InkCanvas.GetLeft(img),
|
||||
Top = InkCanvas.GetTop(img),
|
||||
Width = img.Width,
|
||||
Height = img.Height,
|
||||
Stretch = img.Stretch.ToString()
|
||||
});
|
||||
}
|
||||
}
|
||||
string elementsPath = Settings.Automation.IsSaveStrokesAsXML ? Path.ChangeExtension(savePathWithName, ".elements.json") : Path.ChangeExtension(savePathWithName, ".elements.json");
|
||||
File.WriteAllText(elementsPath, JsonConvert.SerializeObject(elementInfos, Newtonsoft.Json.Formatting.Indented));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -314,7 +450,7 @@ namespace Ink_Canvas
|
||||
/// <summary>
|
||||
/// 将StrokeCollection保存为XML格式
|
||||
/// </summary>
|
||||
private void SaveStrokesAsXML(StrokeCollection strokes, string xmlPath)
|
||||
private void SaveStrokesAsXML(StrokeCollection strokes, string xmlPath, bool triggerUpload = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -368,22 +504,19 @@ namespace Ink_Canvas
|
||||
File.WriteAllText(Path.ChangeExtension(xmlPath, ".elements.json"), JsonConvert.SerializeObject(elementInfos, Newtonsoft.Json.Formatting.Indented));
|
||||
|
||||
// 异步上传到Dlass
|
||||
_ = Task.Run(async () =>
|
||||
if (triggerUpload)
|
||||
{
|
||||
try
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
var delayMinutes = Settings?.Dlass?.AutoUploadDelayMinutes ?? 0;
|
||||
if (delayMinutes > 0)
|
||||
try
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(delayMinutes));
|
||||
await Helpers.UploadHelper.UploadFileAsync(xmlPath);
|
||||
}
|
||||
|
||||
await Helpers.DlassNoteUploader.UploadNoteFileAsync(xmlPath);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
});
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -427,9 +560,9 @@ namespace Ink_Canvas
|
||||
var strokes = allPageStrokes[i];
|
||||
if (strokes.Count > 0)
|
||||
{
|
||||
// 保存XML文件
|
||||
// 保存XML文件(临时文件,不触发上传)
|
||||
string xmlFileName = Path.Combine(tempDir, $"page_{i + 1:D4}.xml");
|
||||
SaveStrokesAsXML(strokes, xmlFileName);
|
||||
SaveStrokesAsXML(strokes, xmlFileName, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,28 +593,22 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
// 创建ZIP文件
|
||||
if (File.Exists(zipFileName))
|
||||
File.Delete(zipFileName);
|
||||
if (File.Exists(zipFileName))
|
||||
File.Delete(zipFileName);
|
||||
|
||||
ZipFile.CreateFromDirectory(tempDir, zipFileName);
|
||||
ZipFile.CreateFromDirectory(tempDir, zipFileName);
|
||||
|
||||
// 异步上传ZIP文件到Dlass
|
||||
_ = Task.Run(async () =>
|
||||
// 异步上传ZIP文件到Dlass
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
var delayMinutes = Settings?.Dlass?.AutoUploadDelayMinutes ?? 0;
|
||||
if (delayMinutes > 0)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(delayMinutes));
|
||||
}
|
||||
|
||||
await Helpers.DlassNoteUploader.UploadNoteFileAsync(zipFileName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
});
|
||||
await Helpers.UploadHelper.UploadFileAsync(zipFileName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
});
|
||||
|
||||
if (newNotice)
|
||||
{
|
||||
@@ -587,13 +714,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
var delayMinutes = Settings?.Dlass?.AutoUploadDelayMinutes ?? 0;
|
||||
if (delayMinutes > 0)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(delayMinutes));
|
||||
}
|
||||
|
||||
await Helpers.DlassNoteUploader.UploadNoteFileAsync(zipFileName);
|
||||
await Helpers.UploadHelper.UploadFileAsync(zipFileName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@@ -696,13 +817,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
var delayMinutes = Settings?.Dlass?.AutoUploadDelayMinutes ?? 0;
|
||||
if (delayMinutes > 0)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(delayMinutes));
|
||||
}
|
||||
|
||||
await Helpers.DlassNoteUploader.UploadNoteFileAsync(imagePathWithName);
|
||||
await Helpers.UploadHelper.UploadFileAsync(imagePathWithName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
@@ -1364,7 +1364,9 @@ namespace Ink_Canvas
|
||||
{
|
||||
if (isUpdatingSelectAll) return;
|
||||
isUpdatingSelectAll = true;
|
||||
selectAllCheckBox.IsChecked = categoryCheckBoxes.Values.All(cb => cb.IsChecked == true);
|
||||
// 检查所有分类复选框是否都被勾选
|
||||
bool allChecked = categoryCheckBoxes.Values.All(cb => cb.IsChecked == true);
|
||||
selectAllCheckBox.IsChecked = allChecked;
|
||||
isUpdatingSelectAll = false;
|
||||
};
|
||||
checkBox.Unchecked += (s, args) =>
|
||||
@@ -3388,44 +3390,6 @@ namespace Ink_Canvas
|
||||
HideSubPanels();
|
||||
try
|
||||
{
|
||||
// 检查是否是第一次打开(检查用户是否已设置Token)
|
||||
bool hasToken = !string.IsNullOrEmpty(Settings?.Dlass?.UserToken?.Trim());
|
||||
bool isFirstTime = !hasToken;
|
||||
|
||||
if (isFirstTime)
|
||||
{
|
||||
// 第一次打开,询问用户是否已注册
|
||||
var result = MessageBox.Show(
|
||||
"您是否已经注册了Dlass账号?\n\n" +
|
||||
"• 如果已注册:将直接打开设置管理页面\n" +
|
||||
"• 如果未注册:将打开浏览器跳转到注册页面",
|
||||
"Dlass账号注册",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question);
|
||||
|
||||
if (result == MessageBoxResult.No)
|
||||
{
|
||||
// 用户未注册,打开浏览器
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "https://dlass.tech/dashboard",
|
||||
UseShellExecute = true
|
||||
});
|
||||
LogHelper.WriteLogToFile("已打开浏览器跳转到Dlass注册页面", LogHelper.LogType.Event);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"打开浏览器时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"无法打开浏览器。请手动访问: https://dlass.tech/dashboard",
|
||||
"提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
return; // 不打开设置窗口
|
||||
}
|
||||
// 如果用户选择"是",继续打开设置窗口
|
||||
}
|
||||
|
||||
// 打开设置管理窗口
|
||||
var dlassSettingsWindow = new Windows.DlassSettingsWindow();
|
||||
dlassSettingsWindow.Owner = this;
|
||||
@@ -3433,8 +3397,8 @@ namespace Ink_Canvas
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"打开Dlass设置管理窗口时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"打开Dlass设置管理窗口时发生错误: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
LogHelper.WriteLogToFile($"打开云存储管理窗口时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"打开云存储管理窗口时发生错误: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,9 @@ namespace Ink_Canvas
|
||||
[JsonProperty("dlass")]
|
||||
public DlassSettings Dlass { get; set; } = new DlassSettings();
|
||||
|
||||
[JsonProperty("upload")]
|
||||
public UploadSettings Upload { get; set; } = new UploadSettings();
|
||||
|
||||
[JsonProperty("security")]
|
||||
public Security Security { get; set; } = new Security();
|
||||
}
|
||||
@@ -102,13 +105,10 @@ namespace Ink_Canvas
|
||||
public bool LineEndpointSnapping { get; set; } = true; // 是否启用直线端点吸附
|
||||
[JsonProperty("lineEndpointSnappingThreshold")]
|
||||
public int LineEndpointSnappingThreshold { get; set; } = 15; // 直线端点吸附的距离阈值(像素)
|
||||
|
||||
[JsonProperty("usingWhiteboard")]
|
||||
public bool UsingWhiteboard { get; set; }
|
||||
|
||||
[JsonProperty("customBackgroundColor")]
|
||||
public string CustomBackgroundColor { get; set; } = "#162924";
|
||||
|
||||
[JsonProperty("hyperbolaAsymptoteOption")]
|
||||
public OptionalOperation HyperbolaAsymptoteOption { get; set; } = OptionalOperation.Ask;
|
||||
[JsonProperty("isCompressPicturesUploaded")]
|
||||
@@ -121,8 +121,6 @@ namespace Ink_Canvas
|
||||
public bool ClearCanvasAlsoClearImages { get; set; } = true;
|
||||
[JsonProperty("showCircleCenter")]
|
||||
public bool ShowCircleCenter { get; set; }
|
||||
|
||||
// 墨迹渐隐功能设置
|
||||
[JsonProperty("enableInkFade")]
|
||||
public bool EnableInkFade { get; set; } = false;
|
||||
[JsonProperty("inkFadeTime")]
|
||||
@@ -448,6 +446,7 @@ namespace Ink_Canvas
|
||||
|
||||
[JsonProperty("isAutoFoldInEasiNote3")]
|
||||
public bool IsAutoFoldInEasiNote3 { get; set; }
|
||||
|
||||
[JsonProperty("isAutoFoldInEasiNote3C")]
|
||||
public bool IsAutoFoldInEasiNote3C { get; set; }
|
||||
|
||||
@@ -859,6 +858,37 @@ namespace Ink_Canvas
|
||||
get { return _autoUploadDelayMinutes; }
|
||||
set { _autoUploadDelayMinutes = Math.Max(0, value); }
|
||||
}
|
||||
|
||||
[JsonProperty("webDavUrl")]
|
||||
public string WebDavUrl { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty("webDavUsername")]
|
||||
public string WebDavUsername { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty("webDavPassword")]
|
||||
public string WebDavPassword { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty("webDavRootDirectory")]
|
||||
public string WebDavRootDirectory { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class UploadSettings
|
||||
{
|
||||
[JsonProperty("uploadDelayMinutes")]
|
||||
public int UploadDelayMinutes
|
||||
{
|
||||
get { return _uploadDelayMinutes; }
|
||||
set { _uploadDelayMinutes = Math.Max(0, Math.Min(60, value)); }
|
||||
}
|
||||
private int _uploadDelayMinutes = 0;
|
||||
|
||||
[JsonProperty("enabledProviders")]
|
||||
public List<string> EnabledProviders
|
||||
{
|
||||
get { return _enabledProviders; }
|
||||
set { _enabledProviders = value ?? new List<string>(); }
|
||||
}
|
||||
private List<string> _enabledProviders = new List<string>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,20 +7,31 @@ using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
|
||||
using ui = iNKORE.UI.WPF.Modern.Controls;
|
||||
|
||||
namespace Ink_Canvas.Windows
|
||||
{
|
||||
/// <summary>
|
||||
/// DlassSettingsWindow.xaml 的交互逻辑
|
||||
/// 云储存管理窗口
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该窗口包含三个标签页:
|
||||
/// 1. 通用设置 - 管理所有上传提供者的通用设置,包括上传延迟时间和提供者启用/禁用
|
||||
/// 2. Dlass - 管理Dlass服务端连接和设置,包括用户Token、班级选择和自动上传设置
|
||||
/// 3. WebDav - 预留的WebDav连接设置页面
|
||||
/// </remarks>
|
||||
public partial class DlassSettingsWindow : Window
|
||||
{
|
||||
private const string APP_ID = "app_WkjocWqsrVY7T6zQV2CfiA";
|
||||
private const string APP_SECRET = "o7dx5b5ASGUMcM72PCpmRQYAhSijqaOVHoGyBK0IxbA";
|
||||
|
||||
// 静态 Regex 实例,用于验证数字输入
|
||||
private static readonly Regex _nonDigitRegex = new Regex("[^0-9]+", RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||||
|
||||
private DlassApiClient _apiClient;
|
||||
private List<WhiteboardInfo> _currentWhiteboards = new List<WhiteboardInfo>();
|
||||
private UserInfo _currentUser;
|
||||
private bool _isFirstTimeDlassTab = true;
|
||||
|
||||
public DlassSettingsWindow(MainWindow mainWindow = null)
|
||||
{
|
||||
@@ -38,6 +49,12 @@ namespace Ink_Canvas.Windows
|
||||
// 加载自动上传设置
|
||||
LoadAutoUploadSettings();
|
||||
|
||||
// 加载通用设置
|
||||
LoadUniversalUploadSettings();
|
||||
|
||||
// 加载WebDav设置
|
||||
LoadWebDavSettings();
|
||||
|
||||
// 初始化API客户端(优先使用用户token)
|
||||
InitializeApiClient();
|
||||
|
||||
@@ -291,7 +308,7 @@ namespace Ink_Canvas.Windows
|
||||
{
|
||||
delayMinutes = 0;
|
||||
}
|
||||
TxtUploadDelayMinutes.Text = delayMinutes.ToString();
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -310,6 +327,31 @@ namespace Ink_Canvas.Windows
|
||||
if (MainWindow.Settings?.Dlass != null)
|
||||
{
|
||||
MainWindow.Settings.Dlass.IsAutoUploadNotes = ToggleSwitchAutoUploadNotes.IsOn;
|
||||
|
||||
// 同步更新到EnabledProviders列表
|
||||
if (MainWindow.Settings.Upload != null)
|
||||
{
|
||||
if (MainWindow.Settings.Upload.EnabledProviders == null)
|
||||
{
|
||||
MainWindow.Settings.Upload.EnabledProviders = new List<string>();
|
||||
}
|
||||
|
||||
if (ToggleSwitchAutoUploadNotes.IsOn)
|
||||
{
|
||||
if (!MainWindow.Settings.Upload.EnabledProviders.Contains("Dlass"))
|
||||
{
|
||||
MainWindow.Settings.Upload.EnabledProviders.Add("Dlass");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MainWindow.Settings.Upload.EnabledProviders.Remove("Dlass");
|
||||
}
|
||||
|
||||
// 重新加载通用设置,更新UI
|
||||
LoadUniversalUploadSettings();
|
||||
}
|
||||
|
||||
MainWindow.SaveSettingsToFile();
|
||||
}
|
||||
}
|
||||
@@ -319,53 +361,145 @@ namespace Ink_Canvas.Windows
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 上传延迟时间输入框文本改变事件
|
||||
/// 加载通用设置
|
||||
/// </summary>
|
||||
private void TxtUploadDelayMinutes_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
|
||||
private void LoadUniversalUploadSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (MainWindow.Settings?.Dlass != null && int.TryParse(TxtUploadDelayMinutes.Text, out int delayMinutes))
|
||||
// 加载上传延迟时间
|
||||
if (MainWindow.Settings?.Upload != null)
|
||||
{
|
||||
var delayMinutes = MainWindow.Settings.Upload.UploadDelayMinutes;
|
||||
if (delayMinutes < 0 || delayMinutes > 60)
|
||||
{
|
||||
delayMinutes = 0;
|
||||
}
|
||||
TxtUniversalUploadDelayMinutes.Text = delayMinutes.ToString();
|
||||
}
|
||||
|
||||
// 加载上传提供者列表
|
||||
LoadUploadProvidersList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载通用设置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载上传提供者列表
|
||||
/// </summary>
|
||||
private void LoadUploadProvidersList()
|
||||
{
|
||||
try
|
||||
{
|
||||
var providers = UploadHelper.GetProviders();
|
||||
LstUploadProviders.ItemsSource = providers;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载上传提供者列表时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通用设置延迟时间输入框文本改变事件
|
||||
/// </summary>
|
||||
private void TxtUniversalUploadDelayMinutes_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (MainWindow.Settings?.Upload != null && int.TryParse(TxtUniversalUploadDelayMinutes.Text, out int delayMinutes))
|
||||
{
|
||||
// 限制范围在0-60分钟
|
||||
if (delayMinutes < 0)
|
||||
{
|
||||
delayMinutes = 0;
|
||||
TxtUploadDelayMinutes.Text = "0";
|
||||
TxtUniversalUploadDelayMinutes.Text = "0";
|
||||
}
|
||||
else if (delayMinutes > 60)
|
||||
{
|
||||
delayMinutes = 60;
|
||||
TxtUploadDelayMinutes.Text = "60";
|
||||
TxtUniversalUploadDelayMinutes.Text = "60";
|
||||
}
|
||||
|
||||
MainWindow.Settings.Dlass.AutoUploadDelayMinutes = delayMinutes;
|
||||
MainWindow.Settings.Upload.UploadDelayMinutes = delayMinutes;
|
||||
MainWindow.SaveSettingsToFile();
|
||||
}
|
||||
else if (string.IsNullOrWhiteSpace(TxtUploadDelayMinutes.Text))
|
||||
else if (string.IsNullOrWhiteSpace(TxtUniversalUploadDelayMinutes.Text))
|
||||
{
|
||||
// 空文本时设置为0
|
||||
if (MainWindow.Settings?.Dlass != null)
|
||||
if (MainWindow.Settings?.Upload != null)
|
||||
{
|
||||
MainWindow.Settings.Dlass.AutoUploadDelayMinutes = 0;
|
||||
MainWindow.Settings.Upload.UploadDelayMinutes = 0;
|
||||
MainWindow.SaveSettingsToFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存上传延迟时间时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.WriteLogToFile($"保存通用设置延迟时间时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传延迟时间输入框预览文本输入事件(只允许数字)
|
||||
/// 通用设置延迟时间输入框预览文本输入事件(只允许数字)
|
||||
/// </summary>
|
||||
private void TxtUploadDelayMinutes_PreviewTextInput(object sender, TextCompositionEventArgs e)
|
||||
private void TxtUniversalUploadDelayMinutes_PreviewTextInput(object sender, TextCompositionEventArgs e)
|
||||
{
|
||||
Regex regex = new Regex("[^0-9]+");
|
||||
e.Handled = regex.IsMatch(e.Text);
|
||||
e.Handled = _nonDigitRegex.IsMatch(e.Text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传提供者启用/禁用开关切换事件
|
||||
/// </summary>
|
||||
private void ToggleProviderEnabled_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (sender is iNKORE.UI.WPF.Modern.Controls.ToggleSwitch toggleSwitch && toggleSwitch.DataContext is IUploadProvider provider)
|
||||
{
|
||||
if (MainWindow.Settings?.Upload != null)
|
||||
{
|
||||
if (MainWindow.Settings.Upload.EnabledProviders == null)
|
||||
{
|
||||
MainWindow.Settings.Upload.EnabledProviders = new List<string>();
|
||||
}
|
||||
|
||||
if (toggleSwitch.IsOn)
|
||||
{
|
||||
if (!MainWindow.Settings.Upload.EnabledProviders.Contains(provider.Name))
|
||||
{
|
||||
MainWindow.Settings.Upload.EnabledProviders.Add(provider.Name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MainWindow.Settings.Upload.EnabledProviders.Remove(provider.Name);
|
||||
}
|
||||
|
||||
// 同步更新Dlass的IsAutoUploadNotes设置(如果是Dlass提供者)
|
||||
if (provider.Name == "Dlass" && MainWindow.Settings.Dlass != null)
|
||||
{
|
||||
MainWindow.Settings.Dlass.IsAutoUploadNotes = toggleSwitch.IsOn;
|
||||
// 同步更新Dlass标签页中的开关状态
|
||||
ToggleSwitchAutoUploadNotes.IsOn = toggleSwitch.IsOn;
|
||||
}
|
||||
|
||||
MainWindow.SaveSettingsToFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存上传提供者启用状态时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -532,6 +666,121 @@ namespace Ink_Canvas.Windows
|
||||
Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TabControl选择改变事件
|
||||
/// </summary>
|
||||
private void TabControl_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (sender is System.Windows.Controls.TabControl tabControl && tabControl.SelectedItem is System.Windows.Controls.TabItem selectedTab)
|
||||
{
|
||||
// 检查是否切换到Dlass标签页
|
||||
if (selectedTab.Tag?.ToString() == "DlassTab" && _isFirstTimeDlassTab)
|
||||
{
|
||||
// 检查是否是第一次打开(检查用户是否已设置Token)
|
||||
bool hasToken = !string.IsNullOrEmpty(GetUserToken()?.Trim());
|
||||
bool isFirstTime = !hasToken;
|
||||
|
||||
if (isFirstTime)
|
||||
{
|
||||
// 第一次打开,询问用户是否已注册
|
||||
var result = MessageBox.Show(
|
||||
"您是否已经注册了Dlass账号?\n\n" +
|
||||
"• 如果已注册:将打开Dlass管理标签页\n" +
|
||||
"• 如果未注册:将打开浏览器跳转到注册页面",
|
||||
"Dlass账号注册",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question);
|
||||
|
||||
if (result == MessageBoxResult.No)
|
||||
{
|
||||
// 用户未注册,打开浏览器
|
||||
try
|
||||
{
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = "https://dlass.tech/dashboard",
|
||||
UseShellExecute = true
|
||||
});
|
||||
LogHelper.WriteLogToFile("已打开浏览器跳转到Dlass注册页面", LogHelper.LogType.Event);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"打开浏览器时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"无法打开浏览器。请手动访问: https://dlass.tech/dashboard",
|
||||
"提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
}
|
||||
// 如果用户选择"是",继续打开设置窗口
|
||||
}
|
||||
|
||||
// 标记为已打开过Dlass标签页
|
||||
_isFirstTimeDlassTab = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"TabControl选择改变事件处理出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载WebDav设置
|
||||
/// </summary>
|
||||
private void LoadWebDavSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (MainWindow.Settings?.Dlass != null)
|
||||
{
|
||||
TxtWebDavUrl.Text = MainWindow.Settings.Dlass.WebDavUrl;
|
||||
TxtWebDavUsername.Text = MainWindow.Settings.Dlass.WebDavUsername;
|
||||
TxtWebDavPassword.Password = MainWindow.Settings.Dlass.WebDavPassword;
|
||||
TxtWebDavRootDirectory.Text = MainWindow.Settings.Dlass.WebDavRootDirectory;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载WebDav设置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存WebDav设置按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnSaveWebDav_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (MainWindow.Settings?.Dlass != null)
|
||||
{
|
||||
MainWindow.Settings.Dlass.WebDavUrl = TxtWebDavUrl.Text;
|
||||
MainWindow.Settings.Dlass.WebDavUsername = TxtWebDavUsername.Text;
|
||||
MainWindow.Settings.Dlass.WebDavPassword = TxtWebDavPassword.Password;
|
||||
MainWindow.Settings.Dlass.WebDavRootDirectory = TxtWebDavRootDirectory.Text;
|
||||
MainWindow.SaveSettingsToFile();
|
||||
|
||||
MessageBox.Show("WebDav设置已保存", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存WebDav设置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"保存WebDav设置时发生错误: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消WebDav设置按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnCancelWebDav_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 重新加载设置,恢复原值
|
||||
LoadWebDavSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试API连接
|
||||
/// </summary>
|
||||
|
||||
@@ -657,11 +657,11 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Dlass设置管理 -->
|
||||
<!-- 云存储管理 -->
|
||||
<Border Margin="0,25,0,0" BorderBrush="#e6e6e6" BorderThickness="1.25,1.25,1.25,4" CornerRadius="8">
|
||||
<StackPanel Orientation="Vertical" Margin="18,18,18,18">
|
||||
<TextBlock Text="Dlass设置管理" FontWeight="Bold" Foreground="#2e3436" FontSize="18" Margin="0,0,0,12"/>
|
||||
<Button x:Name="BtnDlassSettingsManage" Tag="dlass_settings" Content="Dlass设置管理" Padding="15,5" HorizontalAlignment="Left" Background="#2563eb" Foreground="White" Click="Button_Click"/>
|
||||
<TextBlock Text="云存储管理" FontWeight="Bold" Foreground="#2e3436" FontSize="18" Margin="0,0,0,12"/>
|
||||
<Button x:Name="BtnDlassSettingsManage" Tag="dlass_settings" Content="云存储管理" Padding="15,5" HorizontalAlignment="Left" Background="#2563eb" Foreground="White" Click="Button_Click"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
@@ -137,6 +137,12 @@
|
||||
"System.Text.Json": "8.0.5"
|
||||
}
|
||||
},
|
||||
"WebDav.Client": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.9.0, )",
|
||||
"resolved": "2.9.0",
|
||||
"contentHash": "GLhd1tQAJeuVO1sj3Wm/dkg0GEVWxk+XGl6rdegMSMHenZuOaWQw4PifWDsjNEC1dtV1/C8JJfK0qfdkM+VIgA=="
|
||||
},
|
||||
"AForge": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2.2.5",
|
||||
|
||||
Reference in New Issue
Block a user