2025-04-10 13:17:39 +08:00
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
|
using System.Collections.ObjectModel;
|
|
|
|
|
|
using System.ComponentModel;
|
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
using System.Windows.Data;
|
|
|
|
|
|
using System.Windows.Input;
|
2025-04-08 16:22:25 +08:00
|
|
|
|
using HeBianGu.Base.WpfBase;
|
|
|
|
|
|
using HeBianGu.Service.Mvc;
|
|
|
|
|
|
using Hopetry.Models;
|
|
|
|
|
|
using Hopetry.Provider;
|
|
|
|
|
|
using Hopetry.Services;
|
|
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
|
using SqlSugar;
|
2025-04-10 14:31:05 +08:00
|
|
|
|
using System.Windows.Data;
|
2025-04-08 16:22:25 +08:00
|
|
|
|
|
|
|
|
|
|
namespace Hopetry.ViewModel.Send;
|
|
|
|
|
|
|
|
|
|
|
|
[ViewModel("Down")]
|
|
|
|
|
|
public class DownViewModel : MvcViewModelBase
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly MinioService _minioService;
|
2025-04-10 13:17:39 +08:00
|
|
|
|
|
|
|
|
|
|
// todo 完成取消下载及暂停下载逻辑
|
|
|
|
|
|
public RelayCommand<DownItem> OpenDownItemFolder { get; set; }
|
|
|
|
|
|
public RelayCommand<string> LoadData { get; set; }
|
|
|
|
|
|
private readonly ConcurrentQueue<MinioDownloadTask> _taskQueue = new();
|
|
|
|
|
|
private SemaphoreSlim _concurrencySemaphore;
|
|
|
|
|
|
|
|
|
|
|
|
private CancellationTokenSource _processingCts = new();
|
|
|
|
|
|
|
2025-04-10 14:31:05 +08:00
|
|
|
|
private string _allTaskHeader;
|
2025-04-10 13:17:39 +08:00
|
|
|
|
|
|
|
|
|
|
public string AllTaskHeader
|
|
|
|
|
|
{
|
2025-04-10 14:31:05 +08:00
|
|
|
|
get => _allTaskHeader;
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
_allTaskHeader = value;
|
|
|
|
|
|
RaisePropertyChanged();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private string _runningTaskHeader;
|
|
|
|
|
|
|
|
|
|
|
|
public string RunningTaskHeader
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _runningTaskHeader;
|
2025-04-10 13:17:39 +08:00
|
|
|
|
set
|
|
|
|
|
|
{
|
2025-04-10 14:31:05 +08:00
|
|
|
|
_runningTaskHeader = value;
|
|
|
|
|
|
RaisePropertyChanged();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private string _finishedTaskHeader;
|
|
|
|
|
|
|
|
|
|
|
|
public string FinishedTaskHeader
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _finishedTaskHeader;
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
_finishedTaskHeader = value;
|
2025-04-10 13:17:39 +08:00
|
|
|
|
RaisePropertyChanged();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 绑定属性
|
|
|
|
|
|
public ObservableCollection<MinioDownloadTask> Tasks { get; } = new();
|
|
|
|
|
|
|
|
|
|
|
|
// 所有任务
|
|
|
|
|
|
public ObservableCollection<MinioDownloadTask> AllTasks { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
public ICollectionView RunningTasksView { get; set; }
|
2025-04-10 14:31:05 +08:00
|
|
|
|
public ICollectionView FinishedTasksView { get; set; }
|
2025-04-10 13:17:39 +08:00
|
|
|
|
private int _maxConcurrent = 3;
|
|
|
|
|
|
|
|
|
|
|
|
public int MaxConcurrent
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _maxConcurrent;
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
if (value < 1) value = 1;
|
|
|
|
|
|
if (_maxConcurrent == value) return;
|
|
|
|
|
|
|
|
|
|
|
|
_maxConcurrent = value;
|
|
|
|
|
|
AdjustConcurrency();
|
|
|
|
|
|
RaisePropertyChanged();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private int _runningTasks;
|
|
|
|
|
|
|
|
|
|
|
|
public int RunningTasks
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _runningTasks;
|
|
|
|
|
|
private set
|
|
|
|
|
|
{
|
|
|
|
|
|
_runningTasks = value;
|
|
|
|
|
|
RaisePropertyChanged();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 命令
|
|
|
|
|
|
public ICommand AddTaskCommand { get; }
|
|
|
|
|
|
public ICommand ClearFinishedCommand { get; }
|
|
|
|
|
|
|
2025-04-08 16:22:25 +08:00
|
|
|
|
|
|
|
|
|
|
private ObservableCollection<DownItem> _downItems;
|
|
|
|
|
|
|
2025-04-10 13:17:39 +08:00
|
|
|
|
|
2025-04-08 16:22:25 +08:00
|
|
|
|
public ObservableCollection<DownItem> DownItems
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _downItems;
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
_downItems = value;
|
|
|
|
|
|
RaisePropertyChanged();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-10 14:31:05 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 构造函数
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="minioService"></param>
|
2025-04-08 16:22:25 +08:00
|
|
|
|
public DownViewModel(MinioService minioService)
|
|
|
|
|
|
{
|
|
|
|
|
|
_minioService = minioService;
|
|
|
|
|
|
Console.WriteLine("初始化DownViewModel");
|
|
|
|
|
|
var client = SqlSugarConfig.GetSqlSugarScope();
|
2025-04-10 13:17:39 +08:00
|
|
|
|
var data = client.Ado.SqlQuery<MinioDownloadTask>($"select * from download_task");
|
|
|
|
|
|
//DownItems = new ObservableCollection<DownItem>(data);
|
|
|
|
|
|
AllTasks = new ObservableCollection<MinioDownloadTask>(data);
|
|
|
|
|
|
AllTaskHeader = $"全部({AllTasks.Count})";
|
2025-04-08 16:22:25 +08:00
|
|
|
|
Console.WriteLine(JsonConvert.SerializeObject(data));
|
2025-04-10 13:17:39 +08:00
|
|
|
|
OpenDownItemFolder = new RelayCommand<DownItem>(DoOpenDownItemFolder);
|
|
|
|
|
|
// 命令初始化
|
|
|
|
|
|
// AddTaskCommand = new RelayCommand(AddTask, CanAddTask);
|
|
|
|
|
|
// 修正:使用 CollectionViewSource 确保通知
|
|
|
|
|
|
var cvsRunning = new CollectionViewSource { Source = AllTasks };
|
|
|
|
|
|
cvsRunning.Filter += (s, e) =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var b = ((MinioDownloadTask)e.Item).Status == "下载中";
|
|
|
|
|
|
e.Accepted = b;
|
|
|
|
|
|
};
|
|
|
|
|
|
RunningTasksView = cvsRunning.View;
|
|
|
|
|
|
|
|
|
|
|
|
var cvsPaused = new CollectionViewSource { Source = AllTasks };
|
|
|
|
|
|
cvsPaused.Filter += (s, e) =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var b = ((MinioDownloadTask)e.Item).Status == "已完成";
|
|
|
|
|
|
e.Accepted = b;
|
|
|
|
|
|
};
|
2025-04-10 14:31:05 +08:00
|
|
|
|
FinishedTasksView = cvsPaused.View;
|
|
|
|
|
|
if (FinishedTasksView is ListCollectionView finishListView)
|
|
|
|
|
|
{
|
|
|
|
|
|
FinishedTaskHeader = $"下载中({finishListView.Count})";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (RunningTasksView is ListCollectionView runningListView)
|
|
|
|
|
|
{
|
|
|
|
|
|
RunningTaskHeader = $"下载中({runningListView.Count})";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-10 13:17:39 +08:00
|
|
|
|
// 关键:订阅数据源变更事件,强制视图刷新
|
|
|
|
|
|
AllTasks.CollectionChanged += (s, e) =>
|
|
|
|
|
|
{
|
|
|
|
|
|
RunningTasksView.Refresh();
|
2025-04-10 14:31:05 +08:00
|
|
|
|
FinishedTasksView.Refresh();
|
2025-04-10 13:17:39 +08:00
|
|
|
|
};
|
2025-04-10 14:31:05 +08:00
|
|
|
|
|
2025-04-10 13:17:39 +08:00
|
|
|
|
ClearFinishedCommand = new CustomCommand(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
// todo 删除已完成的下载记录
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 启动任务处理线程
|
|
|
|
|
|
_concurrencySemaphore = new SemaphoreSlim(_maxConcurrent);
|
|
|
|
|
|
new Thread(ProcessTasksLoop) { IsBackground = true }.Start();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void ProcessTasksLoop()
|
|
|
|
|
|
{
|
|
|
|
|
|
while (!_processingCts.IsCancellationRequested)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 按当前并发数启动任务
|
|
|
|
|
|
for (int i = 0; i < _maxConcurrent; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_taskQueue.TryDequeue(out var task))
|
|
|
|
|
|
{
|
|
|
|
|
|
_ = ExecuteTaskAsync(task);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Thread.Sleep(100);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void AddTask()
|
|
|
|
|
|
{
|
|
|
|
|
|
var task = new MinioDownloadTask(_minioService, NewBucket, NewObjectName);
|
|
|
|
|
|
Tasks.Add(task);
|
|
|
|
|
|
_taskQueue.Enqueue(task);
|
|
|
|
|
|
RaisePropertyChanged(nameof(NewBucket));
|
|
|
|
|
|
RaisePropertyChanged(nameof(NewObjectName));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool CanAddTask() => !string.IsNullOrEmpty(NewBucket) && !string.IsNullOrEmpty(NewObjectName);
|
|
|
|
|
|
|
|
|
|
|
|
private async Task ExecuteTaskAsync(MinioDownloadTask task)
|
|
|
|
|
|
{
|
|
|
|
|
|
RunningTasks++;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// todo 配置下载路径
|
|
|
|
|
|
var savePath = Path.Combine(task.FileName);
|
|
|
|
|
|
await task.StartDownload(savePath);
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
RunningTasks--;
|
|
|
|
|
|
// 任务完成后自动处理下一个
|
|
|
|
|
|
if (!_processingCts.IsCancellationRequested)
|
|
|
|
|
|
ProcessTasksLoop();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void AdjustConcurrency()
|
|
|
|
|
|
{
|
|
|
|
|
|
lock (_concurrencySemaphore)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 调整信号量容量
|
|
|
|
|
|
var delta = _maxConcurrent - _concurrencySemaphore.CurrentCount;
|
|
|
|
|
|
if (delta > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int i = 0; i < delta; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
_concurrencySemaphore.Release();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 绑定属性
|
|
|
|
|
|
private string _newBucket;
|
|
|
|
|
|
|
|
|
|
|
|
public string NewBucket
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _newBucket;
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
_newBucket = value;
|
|
|
|
|
|
RaisePropertyChanged();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private string _newObjectName;
|
|
|
|
|
|
|
|
|
|
|
|
public string NewObjectName
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _newObjectName;
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
_newObjectName = value;
|
|
|
|
|
|
RaisePropertyChanged();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void DoOpenDownItemFolder(DownItem para)
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.WriteLine($"点击了什么值:{JsonConvert.SerializeObject(para)}");
|
|
|
|
|
|
Process.Start("explorer.exe", para.FilePath);
|
2025-04-08 16:22:25 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected override void Init()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected override void Loaded(string args)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
2025-04-10 13:17:39 +08:00
|
|
|
|
|
2025-04-08 16:22:25 +08:00
|
|
|
|
[SugarTable("f_down_item")]
|
|
|
|
|
|
public class DownItem
|
|
|
|
|
|
{
|
|
|
|
|
|
public DownItem()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-10 13:17:39 +08:00
|
|
|
|
[SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnName = "id")]
|
2025-04-08 16:22:25 +08:00
|
|
|
|
public long Id { get; set; }
|
2025-04-10 13:17:39 +08:00
|
|
|
|
|
2025-04-08 16:22:25 +08:00
|
|
|
|
// 进度 已下载 多久下载完成 下载速度
|
2025-04-10 13:17:39 +08:00
|
|
|
|
[SugarColumn(IsIgnore = true)] public int ProgressInt { get; set; }
|
|
|
|
|
|
|
2025-04-08 16:22:25 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// object key
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[SugarColumn(ColumnName = "object_key")]
|
|
|
|
|
|
public string ObjectKey { get; set; }
|
2025-04-10 13:17:39 +08:00
|
|
|
|
|
2025-04-08 16:22:25 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 文件名称
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[SugarColumn(ColumnName = "file_name")]
|
|
|
|
|
|
public string FileName { get; set; }
|
2025-04-10 13:17:39 +08:00
|
|
|
|
|
2025-04-08 16:22:25 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 文件类型
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[SugarColumn(ColumnName = "file_type")]
|
|
|
|
|
|
public string FileType { get; set; }
|
2025-04-10 13:17:39 +08:00
|
|
|
|
|
2025-04-08 16:22:25 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 文件大小
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[SugarColumn(ColumnName = "file_size")]
|
|
|
|
|
|
public long FileSize { get; set; }
|
2025-04-10 13:17:39 +08:00
|
|
|
|
|
2025-04-08 16:22:25 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 速度 100kb/s
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[SugarColumn(IsIgnore = true)]
|
|
|
|
|
|
public long Speed { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 本地保存路径
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[SugarColumn(ColumnName = "file_path")]
|
|
|
|
|
|
public string FilePath { get; set; }
|
2025-04-10 13:17:39 +08:00
|
|
|
|
|
2025-04-08 16:22:25 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 文件的ETag
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[SugarColumn(ColumnName = "file_etag")]
|
|
|
|
|
|
public string FileETag { get; set; }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|