object 下载及部门传输页面逻辑
parent
ed9561f654
commit
a69e71b86b
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,7 @@ namespace Hopetry.Provider
|
||||||
{
|
{
|
||||||
return new SqlSugarClient(new ConnectionConfig()
|
return new SqlSugarClient(new ConnectionConfig()
|
||||||
{
|
{
|
||||||
ConnectionString = @"DataSource=E:\数据上传转存\sqlite\minio.db", // 数据库路径
|
ConnectionString = @"DataSource=minio.db", // 数据库路径
|
||||||
DbType = DbType.Sqlite, // 数据库类型
|
DbType = DbType.Sqlite, // 数据库类型
|
||||||
IsAutoCloseConnection = true, // 自动释放
|
IsAutoCloseConnection = true, // 自动释放
|
||||||
InitKeyType = InitKeyType.Attribute // 从实体特性中读取主键信息
|
InitKeyType = InitKeyType.Attribute // 从实体特性中读取主键信息
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
private CancellationTokenSource _cts;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 下载
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="savePath"></param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
@ -18,12 +18,17 @@
|
||||||
<Button Grid.Column="1" Content="全部取消"
|
<Button Grid.Column="1" Content="全部取消"
|
||||||
Style="{DynamicResource {x:Static h:ButtonKeys.BorderBrushTransparent}}" />
|
Style="{DynamicResource {x:Static h:ButtonKeys.BorderBrushTransparent}}" />
|
||||||
</h:Row>
|
</h:Row>
|
||||||
<TabControl Padding="1 " HorizontalContentAlignment="Left">
|
<TabControl h:Cattach.UseSearch="False">
|
||||||
<TabItem Header="全部" Style="{DynamicResource {x:Static h:TabItemKeys.Accent}}" />
|
<TabItem Header="{Binding DownViewModel.AllTaskHeader}">
|
||||||
<TabItem Header="下载中" />
|
<ListBox h:Cattach.ItemHeight="Auto"
|
||||||
<TabItem Header="已完成">
|
ItemsSource="{Binding DownViewModel.AllTasks}"
|
||||||
<ListBox h:Cattach.ItemHeight="Auto" ItemsSource="{Binding DownViewModel.DownItems}"
|
Style="{DynamicResource {x:Static h:ListBoxKeys.Single}}"
|
||||||
Style="{DynamicResource {x:Static h:ListBoxKeys.Single}}">
|
VirtualizingPanel.CacheLength="15"
|
||||||
|
VirtualizingPanel.CacheLengthUnit="Item"
|
||||||
|
VirtualizingPanel.IsVirtualizing="True"
|
||||||
|
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
|
||||||
|
VirtualizingPanel.ScrollUnit="Pixel"
|
||||||
|
VirtualizingPanel.VirtualizationMode="Recycling">
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Grid Height="50" Margin="10">
|
<Grid Height="50" Margin="10">
|
||||||
|
|
@ -67,20 +72,7 @@
|
||||||
Height="15"
|
Height="15"
|
||||||
CornerRadius="2"
|
CornerRadius="2"
|
||||||
Maximum="100"
|
Maximum="100"
|
||||||
Value="{Binding ProgressInt}">
|
Value="11">
|
||||||
<h:FProgressBar.Triggers>
|
|
||||||
<EventTrigger RoutedEvent="Loaded">
|
|
||||||
<BeginStoryboard>
|
|
||||||
<Storyboard
|
|
||||||
Timeline.DesiredFrameRate="{x:Static h:StoryboardSetting.DesiredFrameRate}">
|
|
||||||
<DoubleAnimation Storyboard.TargetProperty="Value"
|
|
||||||
From="0"
|
|
||||||
To="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Value}"
|
|
||||||
Duration="00:00:30" />
|
|
||||||
</Storyboard>
|
|
||||||
</BeginStoryboard>
|
|
||||||
</EventTrigger>
|
|
||||||
</h:FProgressBar.Triggers>
|
|
||||||
</h:FProgressBar>
|
</h:FProgressBar>
|
||||||
|
|
||||||
<TextBlock Grid.Row="1"
|
<TextBlock Grid.Row="1"
|
||||||
|
|
@ -96,9 +88,186 @@
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Orientation="Horizontal">
|
Orientation="Horizontal">
|
||||||
<Button h:Cattach.Icon="" />
|
<Button h:Cattach.Icon="" /> <!--暂停-->
|
||||||
<Button h:Cattach.Icon="" h:Cattach.IconSize="13" />
|
<Button h:Cattach.Icon="" h:Cattach.IconSize="13" /> <!--取消-->
|
||||||
<Button h:Cattach.Icon="" h:Cattach.IconSize="15" />
|
<Button h:Cattach.Icon="" 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"
|
||||||
|
BorderBrush="{DynamicResource {x:Static h:BrushKeys.BorderBrushDefault}}"
|
||||||
|
BorderThickness="0,0,0,1" />
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</TabItem>
|
||||||
|
<TabItem Header="下载中">
|
||||||
|
<ListBox h:Cattach.ItemHeight="Auto"
|
||||||
|
ItemsSource="{Binding DownViewModel.RunningTasksView}"
|
||||||
|
Style="{DynamicResource {x:Static h:ListBoxKeys.Single}}"
|
||||||
|
VirtualizingPanel.CacheLength="15"
|
||||||
|
VirtualizingPanel.CacheLengthUnit="Item"
|
||||||
|
VirtualizingPanel.IsVirtualizing="True"
|
||||||
|
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
|
||||||
|
VirtualizingPanel.ScrollUnit="Pixel"
|
||||||
|
VirtualizingPanel.VirtualizationMode="Recycling">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Grid Height="50" Margin="10">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition />
|
||||||
|
<RowDefinition />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="30" />
|
||||||
|
<ColumnDefinition />
|
||||||
|
<ColumnDefinition />
|
||||||
|
<ColumnDefinition />
|
||||||
|
<ColumnDefinition />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Grid.RowSpan="2"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontSize="30"
|
||||||
|
Style="{DynamicResource {x:Static h:TextBlockKeys.Icon}}"
|
||||||
|
Text="" />
|
||||||
|
|
||||||
|
<TextBlock Grid.RowSpan="2"
|
||||||
|
Grid.Column="1"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Style="{DynamicResource {x:Static h:TextBlockKeys.Default}}"
|
||||||
|
Text="{Binding FileName}" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="0"
|
||||||
|
Grid.Column="2"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Style="{DynamicResource {x:Static h:TextBlockKeys.Default}}"
|
||||||
|
Text="10:10:10" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="0"
|
||||||
|
Grid.Column="3"
|
||||||
|
Style="{DynamicResource {x:Static h:TextBlockKeys.Default}}"
|
||||||
|
Text="{Binding FileName}" />
|
||||||
|
|
||||||
|
<h:FProgressBar Grid.Column="3"
|
||||||
|
Height="15"
|
||||||
|
CornerRadius="2"
|
||||||
|
Maximum="100"
|
||||||
|
Value="11">
|
||||||
|
</h:FProgressBar>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="1"
|
||||||
|
Grid.Column="3"
|
||||||
|
Margin="-3,0"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Style="{DynamicResource {x:Static h:TextBlockKeys.Default}}"
|
||||||
|
Text="{Binding FileName}" />
|
||||||
|
|
||||||
|
<!--操作-->
|
||||||
|
<StackPanel Grid.RowSpan="2"
|
||||||
|
Grid.Column="4"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<Button h:Cattach.Icon="" /> <!--暂停-->
|
||||||
|
<Button h:Cattach.Icon="" h:Cattach.IconSize="13" /> <!--取消-->
|
||||||
|
<Button h:Cattach.Icon="" 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"
|
||||||
|
BorderBrush="{DynamicResource {x:Static h:BrushKeys.BorderBrushDefault}}"
|
||||||
|
BorderThickness="0,0,0,1" />
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</TabItem>
|
||||||
|
<TabItem Header="已完成">
|
||||||
|
<ListBox h:Cattach.ItemHeight="Auto"
|
||||||
|
ItemsSource="{Binding DownViewModel.PausedTasksView}"
|
||||||
|
Style="{DynamicResource {x:Static h:ListBoxKeys.Single}}"
|
||||||
|
VirtualizingPanel.CacheLength="15"
|
||||||
|
VirtualizingPanel.CacheLengthUnit="Item"
|
||||||
|
VirtualizingPanel.IsVirtualizing="True"
|
||||||
|
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
|
||||||
|
VirtualizingPanel.ScrollUnit="Pixel"
|
||||||
|
VirtualizingPanel.VirtualizationMode="Recycling">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Grid Height="50" Margin="10">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition />
|
||||||
|
<RowDefinition />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="30" />
|
||||||
|
<ColumnDefinition />
|
||||||
|
<ColumnDefinition />
|
||||||
|
<ColumnDefinition />
|
||||||
|
<ColumnDefinition />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Grid.RowSpan="2"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontSize="30"
|
||||||
|
Style="{DynamicResource {x:Static h:TextBlockKeys.Icon}}"
|
||||||
|
Text="" />
|
||||||
|
|
||||||
|
<TextBlock Grid.RowSpan="2"
|
||||||
|
Grid.Column="1"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Style="{DynamicResource {x:Static h:TextBlockKeys.Default}}"
|
||||||
|
Text="{Binding FileName}" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="0"
|
||||||
|
Grid.Column="2"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Style="{DynamicResource {x:Static h:TextBlockKeys.Default}}"
|
||||||
|
Text="10:10:10" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="0"
|
||||||
|
Grid.Column="3"
|
||||||
|
Style="{DynamicResource {x:Static h:TextBlockKeys.Default}}"
|
||||||
|
Text="{Binding FileName}" />
|
||||||
|
|
||||||
|
<h:FProgressBar Grid.Column="3"
|
||||||
|
Height="15"
|
||||||
|
CornerRadius="2"
|
||||||
|
Maximum="100"
|
||||||
|
Value="11">
|
||||||
|
</h:FProgressBar>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="1"
|
||||||
|
Grid.Column="3"
|
||||||
|
Margin="-3,0"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Style="{DynamicResource {x:Static h:TextBlockKeys.Default}}"
|
||||||
|
Text="{Binding FileName}" />
|
||||||
|
|
||||||
|
<!--操作-->
|
||||||
|
<StackPanel Grid.RowSpan="2"
|
||||||
|
Grid.Column="4"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<Button h:Cattach.Icon="" /> <!--暂停-->
|
||||||
|
<Button h:Cattach.Icon="" h:Cattach.IconSize="13" /> <!--取消-->
|
||||||
|
<Button h:Cattach.Icon="" h:Cattach.IconSize="15"
|
||||||
|
Command="{Binding RelativeSource={RelativeSource AncestorType=ListBox},
|
||||||
|
Path= DataContext.DownViewModel.OpenDownItemFolder }"
|
||||||
|
CommandParameter="{Binding}" /> <!--打开所在文件夹-->
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<Border Grid.RowSpan="11"
|
<Border Grid.RowSpan="11"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
namespace HeBianGu.App.Disk;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
public class StatusToButtonConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
return (string)value switch {
|
||||||
|
"下载中" => "暂停",
|
||||||
|
"已暂停" => "恢复",
|
||||||
|
_ => "开始"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,23 +1,22 @@
|
||||||
using HeBianGu.Base.WpfBase;
|
using System.ComponentModel;
|
||||||
using HeBianGu.Service.Mvc;
|
using System.Diagnostics;
|
||||||
using Hopetry.Services;
|
using System.IO;
|
||||||
using Microsoft.Win32;
|
using System.Timers;
|
||||||
using Minio.DataModel.Args;
|
|
||||||
using Minio.DataModel;
|
|
||||||
using System;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
using Minio;
|
using HeBianGu.Base.WpfBase;
|
||||||
using Microsoft.Extensions.Configuration;
|
using HeBianGu.Service.Mvc;
|
||||||
using Hopetry.ViewModel.Send;
|
|
||||||
using System.IO;
|
|
||||||
using System.Security.AccessControl;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using Microsoft.WindowsAPICodePack.Dialogs;
|
|
||||||
using Hopetry.Models;
|
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
|
namespace HeBianGu.App.Disk
|
||||||
{
|
{
|
||||||
|
|
@ -103,7 +102,7 @@ namespace HeBianGu.App.Disk
|
||||||
private SendViewModel _sendViewModel;
|
private SendViewModel _sendViewModel;
|
||||||
private IConfiguration config;
|
private IConfiguration config;
|
||||||
private SemaphoreSlim _semaphore = new SemaphoreSlim(5);
|
private SemaphoreSlim _semaphore = new SemaphoreSlim(5);
|
||||||
private System.Timers.Timer _progressTimer;
|
private Timer _progressTimer;
|
||||||
private bool _isTimerRunning = false;
|
private bool _isTimerRunning = false;
|
||||||
private object _timerLock = new object();
|
private object _timerLock = new object();
|
||||||
private bool _isUploading = false; // 新增:标记是否有上传任务正在运行
|
private bool _isUploading = false; // 新增:标记是否有上传任务正在运行
|
||||||
|
|
@ -123,7 +122,7 @@ namespace HeBianGu.App.Disk
|
||||||
UploadCommand = new AsyncRelayCommand(async () => await UploadFile());
|
UploadCommand = new AsyncRelayCommand(async () => await UploadFile());
|
||||||
UploadCommand1 = new AsyncRelayCommand(async () => await UploadFile1());
|
UploadCommand1 = new AsyncRelayCommand(async () => await UploadFile1());
|
||||||
// 初始化Timer
|
// 初始化Timer
|
||||||
_progressTimer = new System.Timers.Timer(1000);
|
_progressTimer = new Timer(1000);
|
||||||
_progressTimer.Elapsed += UpdateProgress;
|
_progressTimer.Elapsed += UpdateProgress;
|
||||||
_sendViewModel.UpLoadItems.CollectionChanged += (s, e) =>
|
_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)
|
lock (_timerLock)
|
||||||
{
|
{
|
||||||
|
|
@ -174,7 +173,7 @@ namespace HeBianGu.App.Disk
|
||||||
|
|
||||||
private async Task UploadFile()
|
private async Task UploadFile()
|
||||||
{
|
{
|
||||||
var openFileDialog = new Microsoft.Win32.OpenFileDialog
|
var openFileDialog = new OpenFileDialog
|
||||||
{
|
{
|
||||||
Multiselect = true,
|
Multiselect = true,
|
||||||
Filter = "All Files (*.*)|*.*"
|
Filter = "All Files (*.*)|*.*"
|
||||||
|
|
@ -352,7 +351,7 @@ namespace HeBianGu.App.Disk
|
||||||
// 构建配置
|
// 构建配置
|
||||||
var config = builder.Build();
|
var config = builder.Build();
|
||||||
// 从配置获取MinIO设置更安全
|
// 从配置获取MinIO设置更安全
|
||||||
IMinioClient client = new Minio.MinioClient()
|
IMinioClient client = new MinioClient()
|
||||||
.WithEndpoint(config["Minio:Endpoint"])
|
.WithEndpoint(config["Minio:Endpoint"])
|
||||||
.WithCredentials(config["Minio:AccessKey"], config["Minio:SecretKey"])
|
.WithCredentials(config["Minio:AccessKey"], config["Minio:SecretKey"])
|
||||||
.Build();
|
.Build();
|
||||||
|
|
@ -426,7 +425,7 @@ namespace HeBianGu.App.Disk
|
||||||
{
|
{
|
||||||
Process.Start("shutdown", "/s /t 0");
|
Process.Start("shutdown", "/s /t 0");
|
||||||
}
|
}
|
||||||
catch (System.ComponentModel.Win32Exception ex)
|
catch (Win32Exception ex)
|
||||||
{
|
{
|
||||||
MessageBox.Show($"关机失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
MessageBox.Show($"关机失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.Base.WpfBase;
|
||||||
using HeBianGu.Service.Mvc;
|
using HeBianGu.Service.Mvc;
|
||||||
using Hopetry.Models;
|
using Hopetry.Models;
|
||||||
|
|
@ -14,9 +20,70 @@ public class DownViewModel : MvcViewModelBase
|
||||||
{
|
{
|
||||||
private readonly MinioService _minioService;
|
private readonly MinioService _minioService;
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
public string _AllTaskHeader;
|
||||||
|
|
||||||
|
public string AllTaskHeader
|
||||||
|
{
|
||||||
|
get => _AllTaskHeader;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_AllTaskHeader = value;
|
||||||
|
RaisePropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定属性
|
||||||
|
public ObservableCollection<MinioDownloadTask> Tasks { get; } = new();
|
||||||
|
|
||||||
|
// 所有任务
|
||||||
|
public ObservableCollection<MinioDownloadTask> 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<DownItem> _downItems;
|
private ObservableCollection<DownItem> _downItems;
|
||||||
|
|
||||||
|
|
||||||
public ObservableCollection<DownItem> DownItems
|
public ObservableCollection<DownItem> DownItems
|
||||||
{
|
{
|
||||||
get => _downItems;
|
get => _downItems;
|
||||||
|
|
@ -32,9 +99,139 @@ public class DownViewModel : MvcViewModelBase
|
||||||
_minioService = minioService;
|
_minioService = minioService;
|
||||||
Console.WriteLine("初始化DownViewModel");
|
Console.WriteLine("初始化DownViewModel");
|
||||||
var client = SqlSugarConfig.GetSqlSugarScope();
|
var client = SqlSugarConfig.GetSqlSugarScope();
|
||||||
var data = client.Ado.SqlQuery<DownItem>($"select * from f_down_item");
|
var data = client.Ado.SqlQuery<MinioDownloadTask>($"select * from download_task");
|
||||||
DownItems = new ObservableCollection<DownItem>(data);
|
//DownItems = new ObservableCollection<DownItem>(data);
|
||||||
|
AllTasks = new ObservableCollection<MinioDownloadTask>(data);
|
||||||
|
AllTaskHeader = $"全部({AllTasks.Count})";
|
||||||
Console.WriteLine(JsonConvert.SerializeObject(data));
|
Console.WriteLine(JsonConvert.SerializeObject(data));
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
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()
|
protected override void Init()
|
||||||
|
|
@ -54,24 +251,28 @@ public class DownViewModel : MvcViewModelBase
|
||||||
|
|
||||||
[SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnName = "id")]
|
[SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnName = "id")]
|
||||||
public long Id { get; set; }
|
public long Id { get; set; }
|
||||||
|
|
||||||
// 进度 已下载 多久下载完成 下载速度
|
// 进度 已下载 多久下载完成 下载速度
|
||||||
[SugarColumn(IsIgnore = true)]
|
[SugarColumn(IsIgnore = true)] public int ProgressInt { get; set; }
|
||||||
public int ProgressInt { get; set; }
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// object key
|
/// object key
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SugarColumn(ColumnName = "object_key")]
|
[SugarColumn(ColumnName = "object_key")]
|
||||||
public string ObjectKey { get; set; }
|
public string ObjectKey { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 文件名称
|
/// 文件名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SugarColumn(ColumnName = "file_name")]
|
[SugarColumn(ColumnName = "file_name")]
|
||||||
public string FileName { get; set; }
|
public string FileName { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 文件类型
|
/// 文件类型
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SugarColumn(ColumnName = "file_type")]
|
[SugarColumn(ColumnName = "file_type")]
|
||||||
public string FileType { get; set; }
|
public string FileType { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 文件大小
|
/// 文件大小
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -89,12 +290,11 @@ public class DownViewModel : MvcViewModelBase
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SugarColumn(ColumnName = "file_path")]
|
[SugarColumn(ColumnName = "file_path")]
|
||||||
public string FilePath { get; set; }
|
public string FilePath { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 文件的ETag
|
/// 文件的ETag
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SugarColumn(ColumnName = "file_etag")]
|
[SugarColumn(ColumnName = "file_etag")]
|
||||||
public string FileETag { get; set; }
|
public string FileETag { get; set; }
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using HeBianGu.Base.WpfBase;
|
using HeBianGu.Base.WpfBase;
|
||||||
using HeBianGu.Service.Mvc;
|
using HeBianGu.Service.Mvc;
|
||||||
|
using Hopetry.Models;
|
||||||
using Hopetry.Services;
|
using Hopetry.Services;
|
||||||
using Microsoft.WindowsAPICodePack.Dialogs;
|
using Microsoft.WindowsAPICodePack.Dialogs;
|
||||||
using SystemSetting = FileUploader.Models.SystemSetting;
|
using SystemSetting = FileUploader.Models.SystemSetting;
|
||||||
|
|
@ -17,6 +18,9 @@ public class SyncViewModel : MvcViewModelBase
|
||||||
private readonly ISerializerService _serializerService;
|
private readonly ISerializerService _serializerService;
|
||||||
private readonly MinioService _minioService;
|
private readonly MinioService _minioService;
|
||||||
private readonly SystemSetting _setting;
|
private readonly SystemSetting _setting;
|
||||||
|
public CustomCommand SyncData { get; set; }
|
||||||
|
public CustomCommand ComboBox_SelectionChanged { get; set; }
|
||||||
|
public CustomCommand OpenTargetDirCommand { get; set; }
|
||||||
|
|
||||||
protected override void Init()
|
protected override void Init()
|
||||||
{
|
{
|
||||||
|
|
@ -39,10 +43,10 @@ public class SyncViewModel : MvcViewModelBase
|
||||||
_setting = new SystemSetting();
|
_setting = new SystemSetting();
|
||||||
}
|
}
|
||||||
|
|
||||||
ComboBox_SelectionChanged = new RelayCommand(async () => await ComboBox_SelectionUpdate());
|
ComboBox_SelectionChanged = new CustomCommand(async () => await ComboBox_SelectionUpdate());
|
||||||
OpenDirCommand = new RelayCommand(async () => await ButtonBase_OnClick());
|
OpenDirCommand = new CustomCommand(async () => await ButtonBase_OnClick());
|
||||||
OpenTargetDirCommand = new RelayCommand(async () => await Button_OpenTargetDir());
|
OpenTargetDirCommand = new CustomCommand(async () => await Button_OpenTargetDir());
|
||||||
SyncData = new RelayCommand(async () => await SyncDataQuick());
|
SyncData = new CustomCommand(async () => await SyncDataQuick());
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ComboBox_SelectionUpdate()
|
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()
|
private async Task Button_OpenTargetDir()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue