dev2.0
洁 任 2025-04-17 10:27:31 +08:00
commit c23088b40a
7 changed files with 317 additions and 28 deletions

View File

@ -8,6 +8,67 @@
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries />
<provider:ViewModelLocator x:Key="S.ViewModelLocator.Locator" />
<Style
x:Key="{ComponentResourceKey ResourceId=S.ProgressBar.Custom, TypeInTargetAssembly={x:Type h:ProgressBarKeys}}"
BasedOn="{StaticResource {x:Static h:ProgressBarKeys.Dynamic}}" TargetType="ProgressBar">
<Setter Property="Width" Value="auto" />
<Setter Property="Height" Value="auto" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="h:Cattach.Title" Value="当前进度" />
<Setter Property="Clip">
<Setter.Value>
<RectangleGeometry RadiusX="15" RadiusY="15">
<RectangleGeometry.Rect>
<Rect Width="300" Height="30" />
</RectangleGeometry.Rect>
</RectangleGeometry>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ProgressBar">
<Grid Background="{TemplateBinding Background}">
<ProgressBar Background="Transparent"
BorderThickness="{TemplateBinding BorderThickness}"
Foreground="{TemplateBinding Foreground}"
Maximum="{TemplateBinding Maximum}"
Minimum="{TemplateBinding Minimum}"
Opacity="{TemplateBinding Value,
Converter={x:Static h:XConverter.OpacityProgressBarConverter}}"
Style="{DynamicResource {x:Static h:ProgressBarKeys.Default}}"
Value="{TemplateBinding Value}" />
<ProgressBar Background="Transparent"
BorderThickness="{TemplateBinding BorderThickness}"
Foreground="{DynamicResource {x:Static h:BrushKeys.ForegroundWhite}}"
IsIndeterminate="True"
Maximum="100"
Minimum="0"
Opacity="0.5"
Style="{DynamicResource {x:Static h:ProgressBarKeys.Default}}" />
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="{TemplateBinding FontSize}"
FontWeight="Bold"
Foreground="{DynamicResource {x:Static h:BrushKeys.ForegroundWhite}}">
<Run Text="{TemplateBinding h:Cattach.Title}" />
<Run
Text="{Binding RelativeSource={RelativeSource AncestorType=ProgressBar}, Path=Value, Converter={x:Static h:XConverter.DoubleRoundConverter}}" />
<Run Text="%" />
<TextBlock.Effect>
<DropShadowEffect BlurRadius="3" ShadowDepth="0"
Color="{DynamicResource AccentColor}" />
</TextBlock.Effect>
</TextBlock>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style
x:Key="{ComponentResourceKey ResourceId=S.Window.Link.Custom, TypeInTargetAssembly={x:Type h:LinkWindowBase}}"
BasedOn="{StaticResource {x:Static h:MessageWindowBase.DynamicKey}}"

View File

@ -26,6 +26,8 @@ namespace HeBianGu.App.Disk
{
get => new ComponentResourceKey(typeof (LinkWindowBase), (object) "S.Window.Link.Custom");
}
public static ComponentResourceKey WaitingPercent => new ComponentResourceKey(typeof(ProgressBarKeys), "S.ProgressBar.Custom");
protected override MainWindowBase CreateMainWindow(StartupEventArgs e)
{
return new ShellWindow();

View File

@ -1,6 +1,8 @@
using System.Configuration;
using System.Windows;
using System.Windows.Forms.VisualStyles;
using System.Windows.Input;
using System.Windows.Threading;
using HeBianGu.Base.WpfBase;
using Hopetry.Models;
using Hopetry.Provider;
@ -24,7 +26,7 @@ public class MinioDownloadTask : INotifyPropertyChanged
/// <summary>
///
/// </summary>
private CancellationTokenSource _cts;
public CancellationTokenSource _cts;
/// <summary>
///
@ -60,13 +62,58 @@ public class MinioDownloadTask : INotifyPropertyChanged
[SugarColumn(ColumnName = "finished_time")]
public string FinishedTime { get; set; }
[SugarColumn(ColumnName = "file_size")]
public string FileSize { get; set; }
[SugarColumn(ColumnName = "file_etag")]
public string FileETag { get; set; }
private String _downloadInfo;
[SugarColumn(IsIgnore = true)]
public string Progress
public String DownloadInfo
{
get { return TotalSize == 0 ? "0%" : $"{(Downloaded * 100 / TotalSize):0.0}%"; }
get => _downloadInfo;
set
{
_downloadInfo = value;
OnPropertyChanged(nameof(DownloadInfo));
}
}
private String _speed;
[SugarColumn(IsIgnore = true)]
public String Speed
{
get => _speed;
set
{
_speed = value;
OnPropertyChanged(nameof(Speed));
}
}
private Double _progress;
[SugarColumn(IsIgnore = true)]
public Double Progress
{
get
{
if (_progress == 0)
{
_progress = Downloaded == 0 ? 0 : (Downloaded * 100 / TotalSize);
}
return _progress;
}
set
{
_progress = value;
OnPropertyChanged(nameof(Progress));
}
}
[SugarColumn(IsIgnore = true)] public ICommand PauseCommand { get; }
@ -78,7 +125,7 @@ public class MinioDownloadTask : INotifyPropertyChanged
CancelCommand = new CustomCommand(OnCancel);
}
public MinioDownloadTask(MinioService minio, string bucket, string objectKey, string downDir,string fileSize)
public MinioDownloadTask(MinioService minio, string bucket, string objectKey, string downDir, string fileSize)
{
Status = "等待中";
_minio = minio;
@ -94,6 +141,12 @@ public class MinioDownloadTask : INotifyPropertyChanged
CancelCommand = new CustomCommand(OnCancel);
}
public void RefreshProgress()
{
OnPropertyChanged(nameof(Progress));
OnPropertyChanged("Progress");
}
/// <summary>
/// 下载
/// </summary>
@ -104,11 +157,17 @@ public class MinioDownloadTask : INotifyPropertyChanged
try
{
var stat = await _minio.GetObjectMetadata(Bucket, ObjectKey);
// 获取对象信息
TotalSize = stat.Size;
FileETag = stat.ETag;
Status = "下载中";
var updateTask = new MinioDownloadTask
{
TaskId = TaskId,
Status = Status
Status = Status,
TotalSize = TotalSize,
FileETag = FileETag
};
using (var client = SqlSugarConfig.GetSqlSugarScope())
@ -118,7 +177,36 @@ public class MinioDownloadTask : INotifyPropertyChanged
Console.WriteLine($"id {TaskId} path: {FilePath} key: {ObjectKey}文件下载中...");
updateTask.Status = "下载中";
await _minio.DownLoadObject(Bucket, ObjectKey, FilePath, "");
var humanFileSize = (TotalSize > 1048576
? $"{TotalSize / 1048576}MB"
: $"{TotalSize / 1024}KB");
await _minio.DownLoadObjectWithCallBack(Bucket, ObjectKey, FilePath, FileETag,
(downloaded, total, speed) =>
{
var progress = (double)downloaded / total * 100;
Downloaded = downloaded;
/*using (var client = SqlSugarConfig.GetSqlSugarScope())
{
updateTask.Downloaded = downloaded;
client.Updateable(updateTask).IgnoreNullColumns().ExecuteCommandAsync();
}*/
Application.Current.Dispatcher.Invoke(() =>
{
var remaining = total - downloaded;
var seconds = speed > 0 ? remaining / speed : 0;
Speed = (speed > 1048576
? $"{speed / 1048576:f2}MB/s"
: $"{speed:f2}KB/s") + $" 剩余时间 {TimeSpan.FromSeconds(seconds):mm\\:ss}";
Progress = progress;
DownloadInfo = (downloaded > 1048576
? $"{downloaded / 1048576:f2}MB"
: $"{downloaded:f2}KB") + "/" + humanFileSize;
//Console.WriteLine($"{DownloadInfo}");
/*var x =
$"{(double)downloaded / total:P1} | {speed:0.0} KB/s | 剩余 {TimeSpan.FromSeconds(seconds):mm\\:ss}";
Console.WriteLine(x);*/
});
});
Status = "已完成";
updateTask.Status = Status;
updateTask.FinishedTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
@ -170,5 +258,7 @@ public class MinioDownloadTask : INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

View File

@ -1,7 +1,9 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;
using System.Threading.Channels;
using System.Windows.Threading;
using Microsoft.Extensions.Configuration;
using Minio;
using Minio.DataModel.Args;
@ -273,7 +275,7 @@ namespace Hopetry.Services
.WithBucket(string.IsNullOrEmpty(bucketName) ? _bucketName : bucketName)
.WithObject(objectKey)
.WithFile(localPath);
await _minioClient.GetObjectAsync(getArgs);
var stat = await _minioClient.GetObjectAsync(getArgs);
if (VerifyETag(localPath, objectETag))
{
// todo 先忽略处理
@ -379,7 +381,7 @@ namespace Hopetry.Services
() => Console.WriteLine($"Stopped listening for bucket notifications\n"));
// 等待取消请求
await Task.Delay(Timeout.Infinite,cancellationToken);
await Task.Delay(Timeout.Infinite, cancellationToken);
}
catch (OperationCanceledException e)
{
@ -398,5 +400,80 @@ namespace Hopetry.Services
#endregion
}
public async Task DownLoadObjectWithCallBack(string bucketName, string objectKey, string filePath,
string fileETag, Action<long, long, double> action)
{
long totalBytes = 0;
var index = objectKey.LastIndexOf("/", StringComparison.Ordinal);
if (index > 0)
{
var dir = Path.Combine(filePath, objectKey.Substring(0, index));
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
}
var args = new StatObjectArgs()
.WithBucket(string.IsNullOrEmpty(bucketName) ? _bucketName : bucketName)
.WithObject(objectKey);
var stat = await _minioClient.StatObjectAsync(args);
totalBytes = stat.Size;
var localPath = Path.Combine(filePath, objectKey.Replace('/', Path.DirectorySeparatorChar));
GetObjectArgs getObjectArgs = new GetObjectArgs()
.WithBucket(string.IsNullOrEmpty(bucketName) ? _bucketName : bucketName)
.WithObject(objectKey)
.WithCallbackStream((stream) =>
{
long bytesRead = 0;
byte[] buffer = new byte[64 * 1024]; // 64KB 缓冲区
int read;
// 速度计算变量
var speedWindow = TimeSpan.FromSeconds(5); // 5秒时间窗口
var speedRecords = new Queue<(DateTime Time, long Bytes)>();
var lastUpdate = DateTime.MinValue;
using (var fileStream = File.Create(localPath))
{
while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
{
fileStream.Write(buffer, 0, read);
bytesRead += read;
// 记录当前数据块
var now = DateTime.Now;
speedRecords.Enqueue((now, read));
// 清理过期记录
while (speedRecords.Count > 0 &&
(now - speedRecords.Peek().Time) > speedWindow)
{
speedRecords.Dequeue();
}
// 计算窗口内总字节
long windowBytes = speedRecords.Sum(r => r.Bytes);
// 计算速度KB/s
double speedBps = windowBytes / speedWindow.TotalSeconds;
// 每秒触发一次回调
if ((now - lastUpdate).TotalMilliseconds >= 500)
{
action?.Invoke(bytesRead, totalBytes, speedBps);
lastUpdate = now;
}
}
// 触发最终速度回调
action?.Invoke(bytesRead, totalBytes, 0);
}
});
await _minioClient.GetObjectAsync(getObjectArgs);
/*if (VerifyETag(localPath, objectETag))
{
// todo 先忽略处理
}*/
Console.WriteLine($"{objectKey} Download complete");
}
}
}

View File

@ -5,6 +5,7 @@
xmlns:h="https://github.com/HeBianGu"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Hopetry.ViewModel.Send"
xmlns:disk="clr-namespace:HeBianGu.App.Disk"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
@ -73,33 +74,39 @@
Grid.Column="2"
HorizontalAlignment="Right"
Style="{DynamicResource {x:Static h:TextBlockKeys.Default}}"
Text="时间未知" />
Text="{Binding DownloadInfo}" />
<TextBlock Grid.Row="0"
Grid.Column="3"
Style="{DynamicResource {x:Static h:TextBlockKeys.Default}}"
Text="" />
<h:FProgressBar Grid.Column="3"
Height="15"
CornerRadius="2"
Maximum="100"
Value="11" />
Text="100" />
<!--<ProgressBar Grid.Row="0"
Grid.Column="3"
Maximum="100"
Value="{Binding Progress,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"
Style="{DynamicResource {x:Static disk:App.WaitingPercent}}">
</ProgressBar>-->
<h:FProgressBar Grid.Row="0" Grid.Column="3"
Height="15"
Maximum="100"
Value="{Binding Progress,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="1"
Grid.Column="3"
Margin="-3,0"
HorizontalAlignment="Left"
Style="{DynamicResource {x:Static h:TextBlockKeys.Default}}"
Text="N/A kb/s" />
Text="{Binding Speed,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}" />
<!--操作-->
<StackPanel Grid.RowSpan="2"
<StackPanel Grid.Row="0" Grid.RowSpan="2"
Grid.Column="4"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<Button h:Cattach.Icon="&#xe76e;" /> <!--暂停-->
<Button h:Cattach.Icon="&#xe76e;"
Command="{Binding RelativeSource={RelativeSource AncestorType=ListBox},
Path= DataContext.DownViewModel.StartOrPauseDown }"
CommandParameter="{Binding}" /> <!--暂停-->
<Button h:Cattach.Icon="&#xe8a0;" h:Cattach.IconSize="13" /> <!--取消-->
<Button h:Cattach.Icon="&#xe87a;" h:Cattach.IconSize="15"
Command="{Binding RelativeSource={RelativeSource AncestorType=ListBox},
@ -174,6 +181,17 @@
Style="{DynamicResource {x:Static h:TextBlockKeys.Default}}"
Text="{Binding FinishedTime}" />
<!--操作-->
<StackPanel Grid.RowSpan="2"
Grid.Column="4"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<Button h:Cattach.Icon="&#xe87a;" h:Cattach.IconSize="15"
Command="{Binding RelativeSource={RelativeSource AncestorType=ListBox},
Path= DataContext.DownViewModel.OpenDownItemFolder }"
CommandParameter="{Binding}" /> <!--打开所在文件夹-->
</StackPanel>
<Border Grid.RowSpan="11"
Grid.ColumnSpan="11"
Margin="0,0,0,-8"

View File

@ -5,12 +5,15 @@ using System.Diagnostics;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Shapes;
using System.Windows.Threading;
using HeBianGu.Base.WpfBase;
using HeBianGu.Service.Mvc;
using Hopetry.Models;
using Hopetry.Provider;
using Hopetry.Services;
using Newtonsoft.Json;
using Path = System.IO.Path;
namespace Hopetry.ViewModel.Send;
@ -128,6 +131,7 @@ public class DownViewModel : MvcViewModelBase
}
private ObservableCollection<MinioDownloadTask> _finishedTasks;
public ObservableCollection<MinioDownloadTask> FinishedTasks
{
get => _finishedTasks;
@ -137,6 +141,7 @@ public class DownViewModel : MvcViewModelBase
RaisePropertyChanged(); // 触发INotifyPropertyChanged通知
}
}
private int _maxConcurrent = 3;
public int MaxConcurrent
@ -154,6 +159,7 @@ public class DownViewModel : MvcViewModelBase
}
public ICommand ClearFinishedCommand { get; }
public RelayCommand<MinioDownloadTask> StartOrPauseDown { get; }
/// <summary>
@ -166,6 +172,7 @@ public class DownViewModel : MvcViewModelBase
Console.WriteLine("初始化DownViewModel");
using var client = SqlSugarConfig.GetSqlSugarScope();
OpenDownItemFolder = new RelayCommand<MinioDownloadTask>(DoOpenDownItemFolder);
StartOrPauseDown = new RelayCommand<MinioDownloadTask>(DoStartOrPauseDown);
LoadFinishedTasks();
LoadRunningTasks();
ClearFinishedCommand = new CustomCommand(DoClearFinishedCommand);
@ -174,6 +181,20 @@ public class DownViewModel : MvcViewModelBase
new Thread(ProcessTasksLoop) { IsBackground = true }.Start();
}
private void DoStartOrPauseDown(MinioDownloadTask item)
{
if (item.Status == "下载中")
{
item.Status = "已暂停";
// TODO 暂停下载
}
else if (item.Status == "已暂停")
{
item.Status = "下载中";
_taskQueue.Enqueue(item);
}
}
private void DoClearFinishedCommand()
{
using var client = SqlSugarConfig.GetSqlSugarScope();
@ -182,8 +203,16 @@ public class DownViewModel : MvcViewModelBase
FinishedTaskHeader = $"已完成0";
}
public void RefreshHeader()
{
RunningTaskHeader = $"下载中({RunningTasks.Count}";
FinishedTaskHeader = $"已完成({FinishedTasks.Count}";
}
public void LoadRunningTasks()
{
// todo 队列中有几种任务状态 数据库中应该有几种任务状态
//
using var client = SqlSugarConfig.GetSqlSugarScope();
var data = client.Ado
.SqlQuery<MinioDownloadTask>(
@ -196,7 +225,8 @@ public class DownViewModel : MvcViewModelBase
{
using var client = SqlSugarConfig.GetSqlSugarScope();
var data = client.Ado
.SqlQuery<MinioDownloadTask>($"select * from download_task where status='已完成' order by datetime(finished_time) desc");
.SqlQuery<MinioDownloadTask>(
$"select * from download_task where status='已完成' order by datetime(finished_time) desc");
FinishedTasks = new ObservableCollection<MinioDownloadTask>(data);
FinishedTaskHeader = $"已完成({FinishedTasks.Count}";
}
@ -215,7 +245,6 @@ public class DownViewModel : MvcViewModelBase
// _ = Task.Run(() => ExecuteTaskAsync(task));
_ = ExecuteTaskAsync(task).ContinueWith(_ => _semaphore.Release());
}
// 无任务时,进入低功耗轮询
Thread.Sleep(500);
}
@ -227,8 +256,11 @@ public class DownViewModel : MvcViewModelBase
var task = new MinioDownloadTask(_minioService, bucketName, objectKey, downDir, size);
using var client = SqlSugarConfig.GetSqlSugarScope();
client.Insertable(task).ExecuteCommandIdentityIntoEntity();
LoadFinishedTasks();
LoadRunningTasks();
Application.Current.Dispatcher.Invoke(() =>
{
RunningTasks.Add(task);
RefreshHeader();
});
Console.WriteLine($"文件大小:{size} ");
_taskQueue.Enqueue(task);
}
@ -238,9 +270,16 @@ public class DownViewModel : MvcViewModelBase
{
try
{
// todo 下载失败3次停止下载
await task.StartDownload();
LoadRunningTasks();
LoadFinishedTasks();
Application.Current.Dispatcher.Invoke(() =>
{
RunningTasks.Remove(task);
FinishedTasks.Add(task);
RefreshHeader();
});
//LoadRunningTasks();
//LoadFinishedTasks();
Console.WriteLine($"异步下载完成:{task.FileName}");
}
finally
@ -267,7 +306,9 @@ public class DownViewModel : MvcViewModelBase
public void DoOpenDownItemFolder(MinioDownloadTask para)
{
Console.WriteLine($"点击item值{JsonConvert.SerializeObject(para)}");
Process.Start("explorer.exe", para.FilePath);
//Process.Start("explorer.exe", para.FilePath);
var file = Path.Combine(para.FilePath, para.FileName);
Process.Start("explorer.exe", $"/select,\"{file}\"");
}
public void DoCancelDownItem(MinioDownloadTask item)

BIN
minio.db

Binary file not shown.