diff --git a/Models/CustomCommand.cs b/Models/CustomCommand.cs
new file mode 100644
index 0000000..0bce3bd
--- /dev/null
+++ b/Models/CustomCommand.cs
@@ -0,0 +1,25 @@
+using System.Windows.Input;
+
+namespace Hopetry.Models;
+
+public class CustomCommand : ICommand
+{
+ private readonly Action _execute;
+
+ public CustomCommand(Action execute)
+ {
+ _execute = execute;
+ }
+
+ public bool CanExecute(object parameter)
+ {
+ return true;
+ }
+
+ public event EventHandler CanExecuteChanged;
+
+ public void Execute(object parameter)
+ {
+ _execute();
+ }
+}
\ No newline at end of file
diff --git a/Provider/SqlSugarConfig.cs b/Provider/SqlSugarConfig.cs
index 11a0390..3982c46 100644
--- a/Provider/SqlSugarConfig.cs
+++ b/Provider/SqlSugarConfig.cs
@@ -14,7 +14,7 @@ namespace Hopetry.Provider
{
return new SqlSugarClient(new ConnectionConfig()
{
- ConnectionString = @"DataSource=E:\数据上传转存\sqlite\minio.db", // 数据库路径
+ ConnectionString = @"DataSource=minio.db", // 数据库路径
DbType = DbType.Sqlite, // 数据库类型
IsAutoCloseConnection = true, // 自动释放
InitKeyType = InitKeyType.Attribute // 从实体特性中读取主键信息
diff --git a/Services/MinioDownloadTask.cs b/Services/MinioDownloadTask.cs
new file mode 100644
index 0000000..5a2f398
--- /dev/null
+++ b/Services/MinioDownloadTask.cs
@@ -0,0 +1,155 @@
+using System.Windows;
+using System.Windows.Input;
+using HeBianGu.Base.WpfBase;
+using Hopetry.Models;
+using SqlSugar;
+
+namespace Hopetry.Services;
+
+using System;
+using System.IO;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using System.ComponentModel;
+using Minio;
+
+[SugarTable(TableName = "download_task")]
+public class MinioDownloadTask : INotifyPropertyChanged
+{
+ private readonly MinioService _minio;
+
+ ///
+ ///
+ ///
+ private CancellationTokenSource _cts;
+
+ ///
+ ///
+ ///
+ private ManualResetEventSlim _pauseEvent = new(true);
+
+ // 任务属性(绑定到UI)
+ [SugarColumn(ColumnName = "task_id")] public int TaskId { get; set; }
+
+ [SugarColumn(ColumnName = "file_name")]
+ public string FileName { get; set; }
+
+ [SugarColumn(ColumnName = "bucket_name")]
+ public string Bucket { get; set; }
+
+ [SugarColumn(ColumnName = "object_key")]
+ public string ObjectKey { get; set; }
+
+ [SugarColumn(ColumnName = "total_size")]
+ public long TotalSize { get; private set; }
+
+ [SugarColumn(ColumnName = "downloaded")]
+ public long Downloaded { get; private set; }
+
+ [SugarColumn(ColumnName = "file_path")]
+ public string FilePath { get; set; }
+
+ [SugarColumn(ColumnName = "status")]
+ public string Status { get; private set; } = "等待中";
+
+ public string Progress
+ {
+ get { return TotalSize == 0 ? "0%" : $"{(Downloaded * 100 / TotalSize):0.0}%"; }
+ }
+
+ // 命令
+ public ICommand PauseCommand { get; }
+ public ICommand CancelCommand { get; }
+
+ public MinioDownloadTask()
+ {
+ PauseCommand = new CustomCommand(OnPause);
+ CancelCommand = new CustomCommand(OnCancel);
+ }
+ public MinioDownloadTask(MinioService minio, string bucket, string objectKey)
+ {
+ _minio = minio;
+ Bucket = bucket;
+ ObjectKey = objectKey;
+ TaskId = Interlocked.Increment(ref _globalId);
+ FileName = Path.GetFileName(objectKey);
+
+ PauseCommand = new CustomCommand(OnPause);
+ CancelCommand = new CustomCommand(OnCancel);
+ }
+
+ ///
+ /// 下载
+ ///
+ ///
+ public async Task StartDownload(string savePath)
+ {
+ Status = "初始化";
+ _cts = new CancellationTokenSource();
+ var tmpPath = $"{savePath}.download";
+
+ try
+ {
+ // 断点续传初始化
+ if (File.Exists(tmpPath))
+ Downloaded = new FileInfo(tmpPath).Length;
+
+ // 获取文件信息
+ var stat = await _minio.GetObjectMetadata(Bucket, ObjectKey);
+ TotalSize = stat.Size;
+
+ if (Downloaded >= TotalSize)
+ {
+ Status = "已完成";
+ return;
+ }
+
+ // todo 下载逻辑
+ Status = "已完成";
+ }
+ catch (OperationCanceledException)
+ {
+ Status = "已取消";
+ }
+ catch (Exception ex)
+ {
+ Status = $"错误:{ex.Message}";
+ }
+ finally
+ {
+ _pauseEvent.Dispose();
+ OnPropertyChanged(nameof(Status));
+ }
+ }
+
+ private void OnPause()
+ {
+ if (Status == "下载中")
+ {
+ _pauseEvent.Reset();
+ Status = "已暂停";
+ }
+ else if (Status == "已暂停")
+ {
+ _pauseEvent.Set();
+ Status = "下载中";
+ }
+
+ OnPropertyChanged(nameof(Status));
+ }
+
+ private void OnCancel()
+ {
+ _cts?.Cancel();
+ Status = "已取消";
+ OnPropertyChanged(nameof(Status));
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ protected void OnPropertyChanged(string propertyName)
+ => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+
+ private static int _globalId;
+}
\ No newline at end of file
diff --git a/View/Send/DownControl.xaml b/View/Send/DownControl.xaml
index 6c076f8..4b96d1e 100644
--- a/View/Send/DownControl.xaml
+++ b/View/Send/DownControl.xaml
@@ -18,12 +18,17 @@
-
-
-
-
-
+
+
+
@@ -51,7 +56,7 @@
HorizontalAlignment="Left"
Style="{DynamicResource {x:Static h:TextBlockKeys.Default}}"
Text="{Binding FileName}" />
-
+
-
-
-
-
-
-
-
-
-
+ Value="11">
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
"暂停",
+ "已暂停" => "恢复",
+ _ => "开始"
+ };
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+}
\ No newline at end of file
diff --git a/ViewModel/Loyout/LoyoutViewModel.cs b/ViewModel/Loyout/LoyoutViewModel.cs
index d338cfc..126af25 100644
--- a/ViewModel/Loyout/LoyoutViewModel.cs
+++ b/ViewModel/Loyout/LoyoutViewModel.cs
@@ -1,23 +1,22 @@
-using HeBianGu.Base.WpfBase;
-using HeBianGu.Service.Mvc;
-using Hopetry.Services;
-using Microsoft.Win32;
-using Minio.DataModel.Args;
-using Minio.DataModel;
-using System;
-using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
+using System.Timers;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
-using Minio;
-using Microsoft.Extensions.Configuration;
-using Hopetry.ViewModel.Send;
-using System.IO;
-using System.Security.AccessControl;
-using System.Diagnostics;
-using Microsoft.WindowsAPICodePack.Dialogs;
+using HeBianGu.Base.WpfBase;
+using HeBianGu.Service.Mvc;
using Hopetry.Models;
-using Yitter.IdGenerator;
+using Hopetry.Services;
+using Hopetry.ViewModel.Send;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Win32;
+using Microsoft.WindowsAPICodePack.Dialogs;
+using Minio;
+using Minio.DataModel;
+using Minio.DataModel.Args;
+using Timer = System.Timers.Timer;
namespace HeBianGu.App.Disk
{
@@ -103,7 +102,7 @@ namespace HeBianGu.App.Disk
private SendViewModel _sendViewModel;
private IConfiguration config;
private SemaphoreSlim _semaphore = new SemaphoreSlim(5);
- private System.Timers.Timer _progressTimer;
+ private Timer _progressTimer;
private bool _isTimerRunning = false;
private object _timerLock = new object();
private bool _isUploading = false; // 新增:标记是否有上传任务正在运行
@@ -123,7 +122,7 @@ namespace HeBianGu.App.Disk
UploadCommand = new AsyncRelayCommand(async () => await UploadFile());
UploadCommand1 = new AsyncRelayCommand(async () => await UploadFile1());
// 初始化Timer
- _progressTimer = new System.Timers.Timer(1000);
+ _progressTimer = new Timer(1000);
_progressTimer.Elapsed += UpdateProgress;
_sendViewModel.UpLoadItems.CollectionChanged += (s, e) =>
{
@@ -131,7 +130,7 @@ namespace HeBianGu.App.Disk
};
}
- private void UpdateProgress(object sender, System.Timers.ElapsedEventArgs e)
+ private void UpdateProgress(object sender, ElapsedEventArgs e)
{
lock (_timerLock)
{
@@ -174,7 +173,7 @@ namespace HeBianGu.App.Disk
private async Task UploadFile()
{
- var openFileDialog = new Microsoft.Win32.OpenFileDialog
+ var openFileDialog = new OpenFileDialog
{
Multiselect = true,
Filter = "All Files (*.*)|*.*"
@@ -352,7 +351,7 @@ namespace HeBianGu.App.Disk
// 构建配置
var config = builder.Build();
// 从配置获取MinIO设置更安全
- IMinioClient client = new Minio.MinioClient()
+ IMinioClient client = new MinioClient()
.WithEndpoint(config["Minio:Endpoint"])
.WithCredentials(config["Minio:AccessKey"], config["Minio:SecretKey"])
.Build();
@@ -426,7 +425,7 @@ namespace HeBianGu.App.Disk
{
Process.Start("shutdown", "/s /t 0");
}
- catch (System.ComponentModel.Win32Exception ex)
+ catch (Win32Exception ex)
{
MessageBox.Show($"关机失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
diff --git a/ViewModel/Send/DownViewModel.cs b/ViewModel/Send/DownViewModel.cs
index 94b2b2b..ff0d247 100644
--- a/ViewModel/Send/DownViewModel.cs
+++ b/ViewModel/Send/DownViewModel.cs
@@ -1,4 +1,10 @@
-using System.Collections.ObjectModel;
+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;
using HeBianGu.Base.WpfBase;
using HeBianGu.Service.Mvc;
using Hopetry.Models;
@@ -13,10 +19,71 @@ namespace Hopetry.ViewModel.Send;
public class DownViewModel : MvcViewModelBase
{
private readonly MinioService _minioService;
-
+
+ // todo 完成取消下载及暂停下载逻辑
+ public RelayCommand OpenDownItemFolder { get; set; }
+ public RelayCommand LoadData { get; set; }
+ private readonly ConcurrentQueue _taskQueue = new();
+ private SemaphoreSlim _concurrencySemaphore;
+
+ private CancellationTokenSource _processingCts = new();
+
+ public string _AllTaskHeader;
+
+ public string AllTaskHeader
+ {
+ get => _AllTaskHeader;
+ set
+ {
+ _AllTaskHeader = value;
+ RaisePropertyChanged();
+ }
+ }
+
+ // 绑定属性
+ public ObservableCollection Tasks { get; } = new();
+
+ // 所有任务
+ public ObservableCollection AllTasks { get; set; }
+
+ public ICollectionView RunningTasksView { get; set; }
+ public ICollectionView PausedTasksView { get; set; }
+ 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; }
+
private ObservableCollection _downItems;
+
public ObservableCollection DownItems
{
get => _downItems;
@@ -32,9 +99,139 @@ public class DownViewModel : MvcViewModelBase
_minioService = minioService;
Console.WriteLine("初始化DownViewModel");
var client = SqlSugarConfig.GetSqlSugarScope();
- var data = client.Ado.SqlQuery($"select * from f_down_item");
- DownItems = new ObservableCollection(data);
+ var data = client.Ado.SqlQuery($"select * from download_task");
+ //DownItems = new ObservableCollection(data);
+ AllTasks = new ObservableCollection(data);
+ AllTaskHeader = $"全部({AllTasks.Count})";
Console.WriteLine(JsonConvert.SerializeObject(data));
+ OpenDownItemFolder = new RelayCommand(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;
+ };
+ PausedTasksView = cvsPaused.View;
+ // 关键:订阅数据源变更事件,强制视图刷新
+ AllTasks.CollectionChanged += (s, e) =>
+ {
+ RunningTasksView.Refresh();
+ PausedTasksView.Refresh();
+ };
+ Console.WriteLine($"运行中任务:{JsonConvert.SerializeObject(RunningTasksView)}");
+ Console.WriteLine($"已完成任务:{JsonConvert.SerializeObject(PausedTasksView)}");
+ 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);
}
protected override void Init()
@@ -44,7 +241,7 @@ public class DownViewModel : MvcViewModelBase
protected override void Loaded(string args)
{
}
-
+
[SugarTable("f_down_item")]
public class DownItem
{
@@ -52,32 +249,36 @@ public class DownViewModel : MvcViewModelBase
{
}
- [SugarColumn(IsPrimaryKey = true, IsIdentity = true,ColumnName = "id")]
+ [SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnName = "id")]
public long Id { get; set; }
+
// 进度 已下载 多久下载完成 下载速度
- [SugarColumn(IsIgnore = true)]
- public int ProgressInt { get; set; }
+ [SugarColumn(IsIgnore = true)] public int ProgressInt { get; set; }
+
///
/// object key
///
[SugarColumn(ColumnName = "object_key")]
public string ObjectKey { get; set; }
+
///
/// 文件名称
///
[SugarColumn(ColumnName = "file_name")]
public string FileName { get; set; }
+
///
/// 文件类型
///
[SugarColumn(ColumnName = "file_type")]
public string FileType { get; set; }
+
///
/// 文件大小
///
[SugarColumn(ColumnName = "file_size")]
public long FileSize { get; set; }
-
+
///
/// 速度 100kb/s
///
@@ -89,12 +290,11 @@ public class DownViewModel : MvcViewModelBase
///
[SugarColumn(ColumnName = "file_path")]
public string FilePath { get; set; }
+
///
/// 文件的ETag
///
[SugarColumn(ColumnName = "file_etag")]
public string FileETag { get; set; }
-
-
}
}
\ No newline at end of file
diff --git a/ViewModel/Sync/SyncViewModel.cs b/ViewModel/Sync/SyncViewModel.cs
index 4a5a909..2dc4232 100644
--- a/ViewModel/Sync/SyncViewModel.cs
+++ b/ViewModel/Sync/SyncViewModel.cs
@@ -4,6 +4,7 @@ using System.Windows;
using System.Windows.Input;
using HeBianGu.Base.WpfBase;
using HeBianGu.Service.Mvc;
+using Hopetry.Models;
using Hopetry.Services;
using Microsoft.WindowsAPICodePack.Dialogs;
using SystemSetting = FileUploader.Models.SystemSetting;
@@ -17,6 +18,9 @@ public class SyncViewModel : MvcViewModelBase
private readonly ISerializerService _serializerService;
private readonly MinioService _minioService;
private readonly SystemSetting _setting;
+ public CustomCommand SyncData { get; set; }
+ public CustomCommand ComboBox_SelectionChanged { get; set; }
+ public CustomCommand OpenTargetDirCommand { get; set; }
protected override void Init()
{
@@ -39,10 +43,10 @@ public class SyncViewModel : MvcViewModelBase
_setting = new SystemSetting();
}
- ComboBox_SelectionChanged = new RelayCommand(async () => await ComboBox_SelectionUpdate());
- OpenDirCommand = new RelayCommand(async () => await ButtonBase_OnClick());
- OpenTargetDirCommand = new RelayCommand(async () => await Button_OpenTargetDir());
- SyncData = new RelayCommand(async () => await SyncDataQuick());
+ ComboBox_SelectionChanged = new CustomCommand(async () => await ComboBox_SelectionUpdate());
+ OpenDirCommand = new CustomCommand(async () => await ButtonBase_OnClick());
+ OpenTargetDirCommand = new CustomCommand(async () => await Button_OpenTargetDir());
+ SyncData = new CustomCommand(async () => await SyncDataQuick());
}
private async Task ComboBox_SelectionUpdate()
@@ -85,32 +89,9 @@ public class SyncViewModel : MvcViewModelBase
}
}
- public RelayCommand SyncData { get; set; }
- public RelayCommand ComboBox_SelectionChanged { get; set; }
- public RelayCommand OpenTargetDirCommand { get; set; }
- public class RelayCommand : ICommand
- {
- private readonly Action _execute;
-
- public RelayCommand(Action execute)
- {
- _execute = execute;
- }
-
- public bool CanExecute(object parameter)
- {
- return true;
- }
-
- public event EventHandler CanExecuteChanged;
-
- public void Execute(object parameter)
- {
- _execute();
- }
- }
+
private async Task Button_OpenTargetDir()
{
diff --git a/minio.db b/minio.db
index 687844b..f7af0ad 100644
Binary files a/minio.db and b/minio.db differ