兼容win7版应用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

456 lines
20 KiB

using AIK.Common.Event;
using AIK.Common.Interface;
using AIK.Common.Language;
using AIK.Common.SysCommon;
using AIK.Models.ApiModels.CloudData;
using AIK.Models.HidModels;
using AIK.Service.Extensions;
using AIK.Service.IService;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace AIK.ViewModels.CloudData
{
public partial class CloudDataViewModel : ObservableObject
{
private readonly ISettingsService _settingsService;
private readonly IConfigurationService _configurationService;
private readonly IDataCacheService _dataCacheService;
private readonly IModalDialogService _modalDialogService;
private readonly IAikDataService _aikDataService;
private readonly IFileServiceFactory _fileServiceFactory;
private readonly IFileCacheService _fileCacheService;
private readonly IWindowService _windowService;
private readonly IHidCommunicationService _hidCommunicationService;
private readonly IFileDialogService _fileDialogService;
private readonly IFileOperationService _fileOperationService;
private readonly ILogger<CloudDataViewModel> _logger;
[ObservableProperty]
private HidDeviceStatus _hidDeviceStatus = new HidDeviceStatus();
[ObservableProperty]
private string diskDrive;
[ObservableProperty]
private string _statusMessage = string.Empty;
[ObservableProperty]
private bool _isShowStatusMessage = true;
private ApiResponseOssData ossdataResult { get; set; }
public CloudDataViewModel(ISettingsService settingsService, IConfigurationService configurationService, IDataCacheService dataCacheService, IModalDialogService modalDialogService, IAikDataService aikDataService, IFileServiceFactory fileServiceFactory, IWindowService windowService, IHidCommunicationService hidCommunicationService, IFileDialogService fileDialogService, IFileOperationService fileOperationService, ILogger<CloudDataViewModel> logger)
{
_settingsService = settingsService;
_configurationService = configurationService;
_dataCacheService = dataCacheService;
_modalDialogService = modalDialogService;
_aikDataService = aikDataService;
_fileServiceFactory = fileServiceFactory;
_windowService = windowService;
_hidCommunicationService = hidCommunicationService;
_hidCommunicationService.ConnectionStateChanged += OnConnectionStateChanged;
_fileDialogService = fileDialogService;
_fileCacheService = fileServiceFactory.GetService(Common.Enum.FileServiceType.CloudData);
_fileOperationService = fileOperationService;
_logger = logger;
}
#region 页面方法
[RelayCommand]
private async Task SelectFileAsync()
{
try
{
StatusMessage = "正在选择文件...";
var filePath = await _fileDialogService.OpenFileAsync("选择要复制的文件");
if (filePath != null)
{
await _modalDialogService.ShowProgressAsync(
async (progress, ct) =>
{
return await _hidCommunicationService.CopyToUdiskAsync(filePath, isDirectory: false, progress, ct);
});
//await _hidCommunicationService.CopyToUdiskAsync(filePath, isDirectory: false, cancellationToken);
StatusMessage = "✅ 文件复制成功!";
//MessageBox.Show("文件已复制到 HID U盘!", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
}
}
catch (Exception ex)
{
StatusMessage = "❌ 操作失败";
MessageBox.Show($"错误: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
[RelayCommand]
private async Task SelectFolderAsync()
{
try
{
StatusMessage = "正在选择文件夹...";
var folderPath = await _fileDialogService.SelectFolderAsync("请选择要复制的文件夹");
if (folderPath != null)
{
await _modalDialogService.ShowProgressAsync(
async (progress, ct) =>
{
return await _hidCommunicationService.CopyToUdiskAsync(folderPath, isDirectory: true, progress, ct);
});
StatusMessage = "✅ 文件夹复制成功!";
//MessageBox.Show("文件夹已复制到 HID U盘!", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
}
}
catch (Exception ex)
{
StatusMessage = "❌ 操作失败";
MessageBox.Show($"错误: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
[RelayCommand]
private async Task SyncCloudDataAsync()
{
try
{
await GetCloudDataCacheAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "云数据同步失败");
StatusMessage = "❌ 操作失败";
MessageBox.Show($"错误: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
[RelayCommand]
private async Task OpenUDiskAsync()
{
try
{
await _hidCommunicationService.OpenUDiskAndDetectDriveAsync();
}
catch (Exception ex)
{
StatusMessage = "❌ 操作失败";
MessageBox.Show($"错误: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
[RelayCommand]
private async Task CloseUDiskAsync()
{
try
{
if (string.IsNullOrEmpty(DiskDrive))
{
return;
}
await _hidCommunicationService.CloseUDiskSafeDriveAsync(DiskDrive);
}
catch (Exception ex)
{
StatusMessage = "❌ 操作失败";
MessageBox.Show($"错误: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
[RelayCommand]
private async Task KeepDeviceAliveAsync()
{
await _hidCommunicationService.KeepDeviceAliveAsync();
}
#endregion
#region 辅助方法
private async Task GetDiskDrive()
{
IsShowStatusMessage = true;
StatusMessage = AiKLanguage.LanguageKey.GetValueOrDefault("CloudDataDeviceNotFound") ?? string.Empty;
if (!string.IsNullOrEmpty(DiskDrive))
{
IsShowStatusMessage = false;
StatusMessage = AiKLanguage.LanguageKey.GetValueOrDefault("CloudDataInitCompleted") ?? string.Empty;
return;
}
DiskDrive = await _hidCommunicationService.FindAssociatedUsbDisk();
}
#endregion
#region 连接状态事件处理
private void OnConnectionStateChanged(object? sender, ConnectionStateChangedEventArgs e)
{
HidDeviceStatus.DeviceStatus = e.IsConnected;
if (e.IsConnected)
{
HidDeviceStatus.DeviceStatusText = AiKLanguage.LanguageKey.GetValueOrDefault("Connected");
HidDeviceStatus.DeviceStatusColor = "#28a745";
HidDeviceStatus.DeviceStatusIcon = "\uE62a";
_ = GetDiskDrive();
}
else
{
HidDeviceStatus.DeviceStatusText = AiKLanguage.LanguageKey.GetValueOrDefault("Disconnected"); ;
HidDeviceStatus.DeviceStatusColor = "#FFF44336";
HidDeviceStatus.DeviceStatusIcon = "\uE6f9";
DiskDrive = "";
StatusMessage = AiKLanguage.LanguageKey.GetValueOrDefault("CloudDataDeviceNotFound") ?? string.Empty;
IsShowStatusMessage = true;
}
}
#endregion
#region 获取云数据缓存并同步数据到U盘
private async Task GetCloudDataCacheAsync()
{
try
{
var usertoken = await _dataCacheService.GetLatestUserTokenAsync();
ossdataResult = await _aikDataService.GetOssDataCache(usertoken, AIK.Common.SysCommon.WebApiAddress.OssDataCache);
var progress = new Progress<double>();
if (ossdataResult != null && ossdataResult.Data.Count > 0)
{
_logger.LogInformation("开始同步云数据,共 {Count} 个数据包", ossdataResult.Data.Count);
await _modalDialogService.ShowProgressAsync(
async (progress, ct) =>
{
return await DeployPackagesWorkflowAsync(ossdataResult.Data, progress, CancellationToken.None);
});
}
else
{
_logger.LogWarning("获取云数据缓存为空,无数据可同步");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "获取云数据缓存失败");
}
}
public async Task<(bool, List<string> downloadedPaths)> DownloadAllPackagesSimpleAsync(
IReadOnlyList<CloudPackageInfo> packages,
IProgress<double> segmentProgress,
int maxConcurrency = 2,
CancellationToken cancellationToken = default)
{
if (packages.Count == 0)
{
segmentProgress?.Report(100.0);
return (true, new List<string>());
}
var semaphore = new SemaphoreSlim(maxConcurrency);
var tasks = new List<Task<string>>();
int completedCount = 0;
object lockObj = new object();
int totalCount = packages.Count;
foreach (var item in packages)
{
tasks.Add(Task.Run(async () =>
{
await semaphore.WaitAsync(cancellationToken);
try
{
var fileProgress = new Progress<double>(_ =>
{
// 单个文件内部不细算,只按完成数算整体进度
});
var (ok, path) = await _fileCacheService.DownloadFileWithProgressAsync(
item.EsaUrl, item.Id.ToString(), fileProgress, cancellationToken);
if (!ok)
{
_logger.LogError("云数据包下载失败: {PackageName}, Id: {PackageId}, URL: {PackageUrl}",
item.Name, item.Id, item.EsaUrl);
throw new Exception($"{AiKLanguage.LanguageKey.GetValueOrDefault("CloudDataDownloadFailed") ?? string.Empty}: {item.Name}");
}
lock (lockObj)
{
completedCount++;
double progress = (double)completedCount / totalCount * 100.0;
segmentProgress.Report(progress);
}
return path;
}
catch (Exception ex)
{
_logger.LogError(ex, "云数据包下载异常: {PackageName}, Id: {PackageId}, URL: {PackageUrl}",
item.Name, item.Id, item.EsaUrl);
throw;
}
finally
{
semaphore.Release();
}
}, cancellationToken));
}
try
{
var downloadedPaths = await Task.WhenAll(tasks);
segmentProgress?.Report(100.0);
_logger.LogInformation("云数据包下载完成: {SuccessCount}/{TotalCount}", totalCount, totalCount);
return (true, downloadedPaths.ToList());
}
catch (Exception ex)
{
_logger.LogError(ex, "云数据包下载批量失败,共 {TotalCount} 个包", totalCount);
return (false, new List<string>());
}
}
#region 小文件拷贝版
// public async Task<(bool success, string message)> DeployPackagesWorkflowAsync(
//IReadOnlyList<CloudPackageInfo> packages,
//IProgress<double> progress,
//CancellationToken cancellationToken)
// {
// try
// {
// // 进度分段(流畅不卡)
// const double downloadStart = 0.0;
// const double downloadEnd = 20.0;
// const double extractStart = 20.0;
// const double extractEnd = 30.0;
// const double copyStart = 30.0;
// const double copyEnd = 100.0;
// // ========== 1. 下载:0% → 20% ==========
// var downloadProgress = new Progress<double>(p =>
// {
// double globalProgress = downloadStart + (p / 100.0) * (downloadEnd - downloadStart);
// progress.Report(globalProgress);
// });
// var (dlSuccess, downloadedPaths) = await DownloadAllPackagesSimpleAsync(
// packages, downloadProgress, 3, cancellationToken);
// if (!dlSuccess)
// return (false, "下载失败");
// // ========== 临时目录 ==========
// string shortId = Path.GetRandomFileName().Replace(".", "");
// string tempRoot = Path.Combine(Path.GetTempPath(), $"K3_{shortId}");
// string tempDir = Path.Combine(tempRoot, ComStringHelper.K3ToolDirectory);
// Directory.CreateDirectory(tempDir);
// try
// {
// // ========== 2. 解压:20% → 30% ==========
// var extractProgress = new Progress<double>(p =>
// {
// double globalProgress = extractStart + (p / 100.0) * (extractEnd - extractStart);
// progress.Report(globalProgress);
// });
// await _fileOperationService.ExtractMultipleArchivesAsync(
// downloadedPaths, tempDir, extractProgress,true, cancellationToken);
// // ========== 3. 拷贝到U盘:30% → 100% ==========
// var copyProgress = new Progress<double>(p =>
// {
// double globalProgress = copyStart + (p / 100.0) * (copyEnd - copyStart);
// progress.Report(globalProgress);
// });
// var (copySuccess, copyMsg) = await _hidCommunicationService.CopyToUdiskAsync(
// tempDir, true, copyProgress, cancellationToken);
// return (copySuccess, copyMsg);
// }
// finally
// {
// try { Directory.Delete(tempRoot, true); } catch { }
// }
// }
// catch (Exception ex)
// {
// return (false, $"操作失败:{ex.Message}");
// }
// }
#endregion
#region 单文件压缩解压版
public async Task<(bool success, string message)> DeployPackagesWorkflowAsync(
IReadOnlyList<CloudPackageInfo> packages,
IProgress<double> progress,
CancellationToken cancellationToken)
{
var sw = Stopwatch.StartNew();
try
{
// 最快最合理的进度
const double download = 25;
const double extractLocal = 15;
const double scanDiff = 10;
const double writeToUdisk = 50;
// 1. 下载多个 ZIP(并发 2:用户网络较慢(6-8 Mbps),降低并发使每个连接更稳定、减少中途断线)
progress.Report(0);
var (dlOk, zipPaths) = await DownloadAllPackagesSimpleAsync(packages,
new Progress<double>(p => progress.Report(p * download / 100)),
2, cancellationToken);
if (!dlOk)
{
_logger.LogError("云数据同步下载阶段失败,共 {Count} 个包", packages.Count);
return (false, AiKLanguage.LanguageKey.GetValueOrDefault("CloudDataDownloadFailed") ?? string.Empty);
}
// 2. 【本地快速解压】所有 ZIP → 一个完整文件夹
string tempFolder = Path.Combine(Path.GetTempPath(), $"Full_{Guid.NewGuid():N}");
string localTarget = Path.Combine(tempFolder, ComStringHelper.K3ToolDirectory);
Directory.CreateDirectory(localTarget);
try
{
// 多压缩包合并解压
await _fileOperationService.ExtractMultipleArchivesAsync(
zipPaths,
localTarget,
new Progress<double>(p => progress.Report(download + p * extractLocal / 100)),
true,
cancellationToken);
progress.Report(download + extractLocal + 5);
// ==============================
// ✅ 3. 差异对比 + 只写变化文件
// ==============================
await _hidCommunicationService.CopyDeltaDirectoryToUdiskAsync(
localTarget,
new Progress<double>(p => progress.Report(download + extractLocal + scanDiff + p * writeToUdisk / 100)),
cancellationToken);
progress.Report(100);
sw.Stop();
_logger.LogInformation("云数据同步完成,共 {Count} 个包,耗时 {Elapsed}", packages.Count, sw.Elapsed);
return (true, AiKLanguage.LanguageKey.GetValueOrDefault("CloudDataSyncCompleted") ?? string.Empty);
}
finally
{
// 清理本地临时文件
try { Directory.Delete(tempFolder, true); } catch { }
try { await _hidCommunicationService.CloseUDiskSafeDriveAsync(); } catch { }
}
}
catch (Exception ex)
{
_logger.LogError(ex, "云数据同步部署工作流失败");
return (false, $"{AiKLanguage.LanguageKey.GetValueOrDefault("CloudDataSyncFailed") ?? string.Empty}:{ex.Message}");
}
}
#endregion
#endregion
}
}