using System.Collections.Concurrent; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Windows; using System.Windows.Data; using System.Windows.Input; using HeBianGu.App.Disk; using HeBianGu.Base.WpfBase; using HeBianGu.Service.Mvc; using Hopetry.Models; using Hopetry.Provider; using Hopetry.Services; using Path = System.IO.Path; namespace Hopetry.ViewModel.Send; [ViewModel("Down")] public class DownViewModel : MvcViewModelBase { private readonly MinioService _minioService; public RelayCommand OpenDownItemFolder { get; set; } public RelayCommand CancelDownload { get; set; } private readonly ConcurrentQueue _taskQueue = new(); private SemaphoreSlim _semaphore; private CancellationTokenSource _processingCts = new(); public int count { get; set; } private readonly ReaderWriterLockSlim _lock = new(); // 下载中 功能按钮显示 private Visibility _tab0Visibility; public Visibility Tab0Visibility { get => _tab0Visibility; set { _tab0Visibility = value; RaisePropertyChanged(); } } private Visibility _tab1Visibility; public Visibility Tab1Visibility { get => _tab1Visibility; set { _tab1Visibility = value; RaisePropertyChanged(); } } private int _tabIndex; // tab 索引 public int TabIndex { get => _tabIndex; set { _tabIndex = value; if (_tabIndex == 0) { Tab0Visibility = Visibility.Visible; Tab1Visibility = Visibility.Hidden; } else if (_tabIndex == 1) { Tab0Visibility = Visibility.Hidden; Tab1Visibility = Visibility.Visible; } RaisePropertyChanged(); } } /// /// 全部任务动态标题 /// private string _allTaskHeader; public string AllTaskHeader { get => _allTaskHeader; set { _allTaskHeader = value; RaisePropertyChanged(); } } private string _runningTaskHeader; public string RunningTaskHeader { get => _runningTaskHeader; set { _runningTaskHeader = value; RaisePropertyChanged(); } } private string _finishedTaskHeader; public string FinishedTaskHeader { get => _finishedTaskHeader; set { _finishedTaskHeader = value; RaisePropertyChanged(); } } // 绑定属性 //public ObservableCollection AllTasks { get; set; } private ObservableCollection _runningTasks; public ObservableCollection RunningTasks { get => _runningTasks; set { _runningTasks = value; RaisePropertyChanged(); // 触发INotifyPropertyChanged通知 } } private ObservableCollection _finishedTasks; public ObservableCollection FinishedTasks { get => _finishedTasks; set { _finishedTasks = value; RaisePropertyChanged(); // 触发INotifyPropertyChanged通知 } } private CollectionViewSource _finishedTasksViewSource; public CollectionViewSource FinishedTasksViewSource { get => _finishedTasksViewSource; set { _finishedTasksViewSource = value; RaisePropertyChanged(); // 触发INotifyProperty } } private int _maxConcurrent; public int MaxConcurrent { get => _maxConcurrent; set { if (value < 1) value = 1; if (_maxConcurrent == value) return; var delta = value - _maxConcurrent; _maxConcurrent = value; AdjustConcurrency(delta); RaisePropertyChanged(); } } public ICommand ClearFinishedCommand { get; } public RelayCommand StartOrPauseDown { get; } public ICommand PauseAllTask { get; } public ICommand StopAllTask { get; } /// /// 构造函数 /// /// public DownViewModel(MinioService minioService) { // 下载服务 _minioService = minioService; // 暂时未使用 似乎不太有用 FinishedTasksViewSource = new CollectionViewSource { Source = FinishedTasks }; FinishedTasksViewSource.SortDescriptions.Add(new SortDescription("FinishedTime", ListSortDirection.Descending)); Console.WriteLine("初始化DownViewModel"); using var client = SqlSugarConfig.GetSqlSugarScope(); OpenDownItemFolder = new RelayCommand(DoOpenDownItemFolder); // 取消下载 CancelDownload = new RelayCommand(DoCancelDownload); // 暂停下载或者开启下载 StartOrPauseDown = new RelayCommand(DoStartOrPauseDown); // 全部暂停 PauseAllTask = new CustomCommand(DoPauseAllTask); // 全部取消 StopAllTask = new CustomCommand(DoStopAllTask); // 加载已完成任务记录 LoadFinishedTasks(); // 加载未完成任务 LoadRunningTasks(); ClearFinishedCommand = new CustomCommand(DoClearFinishedCommand); Console.WriteLine($"初始下载任务并发数:{DownloadSetting.Instance.MaxConcurrent}"); // 启动任务处理线程 _maxConcurrent = DownloadSetting.Instance.MaxConcurrent; _semaphore = new SemaphoreSlim(DownloadSetting.Instance.MaxConcurrent); Console.WriteLine($"初始化信号量,信号源数量{_semaphore.CurrentCount}"); DownloadSetting.Instance.PropertyChanged += (sender, args) => { Console.WriteLine($"当前最大下载任务并发数:{MaxConcurrent}"); // todo 检查值是否保存了 Console.WriteLine($"任务并发数新值:{DownloadSetting.Instance.MaxConcurrent}"); MaxConcurrent = DownloadSetting.Instance.MaxConcurrent; }; new Thread(ProcessTasksLoop) { IsBackground = true }.Start(); } // 停止所有任务 private void DoStopAllTask() { _taskQueue.Clear(); foreach (var minioDownloadTask in RunningTasks) { //Console.WriteLine("取消下载"); // 取消操作 if (minioDownloadTask.StopDownTs != null) { minioDownloadTask.StopDownTs.Cancel(); } minioDownloadTask.Status = "删除中"; using var client = SqlSugarConfig.GetSqlSugarScope(); client.Deleteable().Where(x => x.TaskId == minioDownloadTask.TaskId).ExecuteCommand(); } RunningTasks.Clear(); RefreshHeader(); MessageProxy.Snacker.Show("取消下载成功"); } // 暂停所有任务 private void DoPauseAllTask() { _taskQueue.Clear(); Thread.Sleep(100); foreach (var minioDownloadTask in RunningTasks) { //暂停下载 if (minioDownloadTask.StopDownTs != null) { minioDownloadTask.StopDownTs.Cancel(); } minioDownloadTask.Status = "已暂停"; minioDownloadTask.StartOrPauseIcon = "\xe748"; // 速度及剩余时间(视图显示信息) minioDownloadTask.Speed = "已暂停"; } } private void DoStartOrPauseDown(MinioDownloadTask item) { if (item.Status is "下载中" or "等待中") { if (item.StopDownTs != null) { //暂停下载 item.StopDownTs.Cancel(); } item.Status = "已暂停"; item.StartOrPauseIcon = "\xe748"; // 速度及剩余时间(视图显示信息) item.Speed = "已暂停"; // 避免结构函数加载的任务报错 } else if (item.Status == "已暂停") { item.Status = "等待中"; // 更新排队时,下载信息 item.Speed = "等待中"; item.StartOrPauseIcon = "\xe76e"; // 加入下载等待队列 _taskQueue.Enqueue(item); } } private void DoClearFinishedCommand() { using var client = SqlSugarConfig.GetSqlSugarScope(); client.Deleteable().Where(x => x.Status == "已完成").ExecuteCommand(); FinishedTasks.Clear(); FinishedTaskHeader = $"已完成(0)"; } public void RefreshHeader() { RunningTaskHeader = $"下载中({RunningTasks.Count})"; ViewModelLocator.SendViewModel.DownLinkAction.DisplayName = $"下载 {RunningTasks.Count}"; FinishedTaskHeader = $"已完成({FinishedTasks.Count})"; } public void LoadRunningTasks() { // todo 队列中有几种任务状态 数据库中应该有几种任务状态 // using var client = SqlSugarConfig.GetSqlSugarScope(); var data = client.Ado .SqlQuery( $"select * from download_task where status='下载中' or status='等待中' or status='已暂停' order by task_id desc") .Select(x => new MinioDownloadTask { Status = "已暂停", TaskId = x.TaskId, FileName = x.FileName, BucketName = x.BucketName, ObjectKey = x.ObjectKey, TotalSize = x.TotalSize, Downloaded = x.Downloaded, FilePath = x.FilePath, CreateTime = x.CreateTime, FinishedTime = x.FinishedTime, FileSize = x.FileSize, FileETag = x.FileETag, FileNumber = x.FileNumber, DownloadNumber = x.DownloadNumber, FileIcon = x.FileIcon, Type = x.Type, // 初始化显示 DownloadInfo = x.Type == 0 ? (x.Downloaded > 1048576 ? $"{x.Downloaded / 1048576:f2}MB" : $"{x.Downloaded / 1024:f2}KB") + $"/{x.FileSize}" : $"{x.DownloadNumber}/{x.FileNumber}", Speed = "已暂停", StartOrPauseIcon = "\xe748" }).ToList(); RunningTasks = new ObservableCollection(data); RunningTaskHeader = $"下载中({RunningTasks.Count})"; } public void LoadFinishedTasks() { using var client = SqlSugarConfig.GetSqlSugarScope(); var data = client.Ado .SqlQuery( $"select * from download_task where status='已完成' order by datetime(finished_time) desc"); FinishedTasks = new ObservableCollection(data); FinishedTaskHeader = $"已完成({FinishedTasks.Count})"; } public void AddTask(string bucketName, string objectKey, string size, string downDir) { var task = new MinioDownloadTask(_minioService, bucketName, objectKey, downDir, size); using var client = SqlSugarConfig.GetSqlSugarScope(); client.Insertable(task).ExecuteCommandIdentityIntoEntity(); Application.Current.Dispatcher.Invoke(() => { RunningTasks.Add(task); RefreshHeader(); }); Console.WriteLine($"文件大小:{size} "); _taskQueue.Enqueue(task); } private async void ProcessTasksLoop() { while (!_processingCts.IsCancellationRequested) { //Console.WriteLine($"申请前信号源数量:{_semaphore.CurrentCount}"); await _semaphore.WaitAsync(); // 应该是在这里堵塞了 //Console.WriteLine($"当前信号源数量:{_semaphore.CurrentCount}"); // 不应该卡到这里吗??? if (_taskQueue.TryDequeue(out var task)) { Console.WriteLine("申请下载许可成功!马上开启下载"); // 启动新线程执行下载任务 // _ = Task.Run(() => ExecuteTaskAsync(task)); _ = ExecuteTaskAsync(task).ContinueWith(_ => _semaphore.Release()); } else { // 队列无内容,马上释放信号源 _semaphore.Release(); } // 无任务时,进入低功耗轮询 Thread.Sleep(500); } } private async Task ExecuteTaskAsync(MinioDownloadTask task) { try { // minio中好像自带重试机制 // 补充 if (task.Minio == null) { task.Minio = _minioService; } if (task.Type == 0) { await task.StartDownload(); } else { await task.StartDownloadDir(); } Application.Current.Dispatcher.Invoke(() => { RunningTasks.Remove(task); FinishedTasks.Add(task); RefreshHeader(); }); } catch (Exception ex) { // 异常释放信号源 //_semaphore.Release(); Console.WriteLine($"下载失败或取消:{ex.Message}"); Console.WriteLine(ex.StackTrace); } finally { if (task.Status.Equals("删除中")) { Console.WriteLine($"删除文件:{task.FileName}"); File.Delete(Path.Combine(task.FilePath, task.FileName)); Console.WriteLine($"{task.FileName}删除成功"); } } } private void AdjustConcurrency(int delta) { Console.WriteLine("AdjustConcurrency 被调用了"); lock (_semaphore) { // 调整信号量容量 if (delta > 0) { _semaphore.Release(delta); } else { var runTask = _runningTasks.Where(task => task is { Status: "下载中", StopDownTs: not null }).ToList(); Console.WriteLine($"正在下载任务数:{runTask.Count}"); var num = -delta; var temp = new List(); while (_taskQueue.TryDequeue(out var task)) { temp.Add(task); } _taskQueue.Clear(); // 清空任务队列 foreach (var item in runTask) { //Console.WriteLine($"当前可用许可:{_semaphore.CurrentCount}"); item.StopDownTs.Cancel(); // 暂停任务 释放一个许可 item.Status = "等待中"; item.Speed = "等待中"; // 重新加入下载队列 temp.Add(item); } while (num-- > 0) { _semaphore.Wait(); } foreach (var item in temp) { _taskQueue.Enqueue(item); } } } } /// /// 打开文件夹 /// /// public void DoOpenDownItemFolder(MinioDownloadTask para) { switch (para.Type) { //Console.WriteLine($"点击item值:{JsonConvert.SerializeObject(para)}"); //Process.Start("explorer.exe", para.FilePath); case 0: { var file = Path.Combine(para.FilePath, para.FileName); Process.Start("explorer.exe", $"/select,\"{file}\""); break; } case 1: { var combinePath = Path.Combine(para.FilePath, para.ObjectKey); combinePath = combinePath.Replace("/", "\\"); Process.Start("explorer.exe", $"/select,\"{combinePath}\""); break; } default: { var path = Path.Combine(para.FilePath, para.BucketName); // 存储桶 Process.Start("explorer.exe", $"/select,\"{path}\""); break; } } } /// /// 任务取消 /// /// public void DoCancelDownload(MinioDownloadTask item) { Console.WriteLine("取消下载"); // 取消操作 if (item.StopDownTs != null) { item.StopDownTs.Cancel(); } item.Status = "删除中"; item.Speed = "删除中"; using var client = SqlSugarConfig.GetSqlSugarScope(); client.Deleteable().Where(x => x.TaskId == item.TaskId).ExecuteCommand(); RunningTasks.Remove(item); RefreshHeader(); // 删除下载文件 放到下载里面了 //File.Delete(Path.Combine(item.FilePath, item.FileName)); MessageProxy.Snacker.Show($"<{item.FileName}>取消下载"); } protected override void Init() { } protected override void Loaded(string args) { } public async void AddDirTask(string bucketName, string objectKey, string name,string downDir) { // 情景1 只选了一个文件夹 // 情景2 只选了一个存储桶 // 嵌套情景: 文件夹或者存储桶下,存在文件夹 var task = new MinioDownloadTask { FilePath = downDir, ObjectKey = objectKey, BucketName = bucketName, FileName = name, // todo 确定修改这里是否有影响 Status = "等待中", FileIcon = "\xe87a", //DownloadInfo = "0/na", Speed = "等待中", CreateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), // 1 文件夹 2 存储桶 Type = objectKey.Equals(bucketName) ? 2 : 1 }; // 存储桶 文件(路径)兼容 var prefix = objectKey.Equals(bucketName) ? "" : objectKey; var x = await _minioService.ListAllObject(bucketName, prefix, true); var number = 0; await foreach (var item in x) { // todo 可以记录信息 number++; /*Console.WriteLine("=============="); Console.WriteLine(item.Key); Console.WriteLine(item.Size); Console.WriteLine(item.ETag); Console.WriteLine(item.LastModified); Console.WriteLine(item.IsDir);*/ } task.FileNumber = number; task.FileSize = number + "(个)"; task.DownloadInfo = $"0/{number}"; using (var client = SqlSugarConfig.GetSqlSugarScope()) { await client.Insertable(task).ExecuteCommandIdentityIntoEntityAsync(); } Application.Current.Dispatcher.Invoke(() => { RunningTasks.Add(task); RefreshHeader(); }); _taskQueue.Enqueue(task); } }