文件上传页面
This commit is contained in:
parent
6efda290ca
commit
de0a0e3e78
54
WinUINotes.Grpc/Protos/FileTransfer.proto
Normal file
54
WinUINotes.Grpc/Protos/FileTransfer.proto
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option csharp_namespace = "WinUINotes.Grpc";
|
||||||
|
|
||||||
|
package filetransfer;
|
||||||
|
|
||||||
|
service FileTransfer {
|
||||||
|
rpc InitUpload (InitUploadRequest) returns (InitUploadResponse);
|
||||||
|
rpc UploadChunk (stream UploadChunkRequest) returns (UploadChunkResponse);
|
||||||
|
rpc CompleteUpload (CompleteUploadRequest) returns (CompleteUploadResponse);
|
||||||
|
rpc CheckUploadStatus (CheckUploadStatusRequest) returns (CheckUploadStatusResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
message InitUploadRequest {
|
||||||
|
string file_name = 1;
|
||||||
|
int64 file_size = 2;
|
||||||
|
string file_hash = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message InitUploadResponse {
|
||||||
|
string session_id = 1;
|
||||||
|
int64 uploaded_bytes = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UploadChunkRequest {
|
||||||
|
string session_id = 1;
|
||||||
|
bytes chunk_data = 2;
|
||||||
|
int64 offset = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UploadChunkResponse {
|
||||||
|
int64 uploaded_bytes = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CompleteUploadRequest {
|
||||||
|
string session_id = 1;
|
||||||
|
string file_hash = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CompleteUploadResponse {
|
||||||
|
bool success = 1;
|
||||||
|
string message = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CheckUploadStatusRequest {
|
||||||
|
string file_name = 1;
|
||||||
|
string file_hash = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CheckUploadStatusResponse {
|
||||||
|
string session_id = 1;
|
||||||
|
int64 uploaded_bytes = 2;
|
||||||
|
bool exists = 3;
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
option csharp_namespace = "WinUINotes";
|
option csharp_namespace = "WinUINotes.Grpc";
|
||||||
|
|
||||||
package greet;
|
package greet;
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
option csharp_namespace = "ZR.Admin.Grpc";
|
option csharp_namespace = "WinUINotes.Grpc";
|
||||||
|
|
||||||
package my.diary;
|
package my.diary;
|
||||||
|
|
||||||
|
|||||||
@ -20,5 +20,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
|
<Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
|
||||||
<Protobuf Include="Protos\my_diary.proto" GrpcServices="Client" />
|
<Protobuf Include="Protos\my_diary.proto" GrpcServices="Client" />
|
||||||
|
<Protobuf Include="Protos\FileTransfer.proto" GrpcServices="Client" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
14
WinUINotes/AboutPage.xaml
Normal file
14
WinUINotes/AboutPage.xaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Page
|
||||||
|
x:Class="WinUINotes.AboutPage"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:WinUINotes"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<TextBlock Text="这是关于页面。" FontSize="24" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
31
WinUINotes/AboutPage.xaml.cs
Normal file
31
WinUINotes/AboutPage.xaml.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||||
|
using Microsoft.UI.Xaml.Data;
|
||||||
|
using Microsoft.UI.Xaml.Input;
|
||||||
|
using Microsoft.UI.Xaml.Media;
|
||||||
|
using Microsoft.UI.Xaml.Navigation;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices.WindowsRuntime;
|
||||||
|
using Windows.Foundation;
|
||||||
|
using Windows.Foundation.Collections;
|
||||||
|
|
||||||
|
// To learn more about WinUI, the WinUI project structure,
|
||||||
|
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||||
|
|
||||||
|
namespace WinUINotes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class AboutPage : Page
|
||||||
|
{
|
||||||
|
public AboutPage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -26,7 +26,9 @@ namespace WinUINotes
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class App : Application
|
public partial class App : Application
|
||||||
{
|
{
|
||||||
private Window? _window;
|
private static Window? _window;
|
||||||
|
|
||||||
|
public static Window m_window => _window;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes the singleton application object. This is the first line of authored code
|
/// Initializes the singleton application object. This is the first line of authored code
|
||||||
|
|||||||
73
WinUINotes/Common/FileHelper.cs
Normal file
73
WinUINotes/Common/FileHelper.cs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WinUINotes.Common
|
||||||
|
{
|
||||||
|
public static class FileHelper
|
||||||
|
{
|
||||||
|
// 最佳缓冲区大小经过测试得出(根据硬件调整)
|
||||||
|
private const int OptimalBufferSize = 1 * 1024 * 1024; // 1MB缓冲区
|
||||||
|
public static string CalculateFileHash(string filePath)
|
||||||
|
{
|
||||||
|
using (var md5 = MD5.Create())
|
||||||
|
using (var stream = File.OpenRead(filePath))
|
||||||
|
{
|
||||||
|
var hash = md5.ComputeHash(stream);
|
||||||
|
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//public static async Task<string> CalculateFileHashAsync(string filePath)
|
||||||
|
//{
|
||||||
|
// using (var md5 = MD5.Create())
|
||||||
|
// using (var stream = File.OpenRead(filePath))
|
||||||
|
// {
|
||||||
|
// var hash = await md5.ComputeHashAsync(stream);
|
||||||
|
// return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
public static async Task<string> CalculateFileHashAsync(string filePath, int bufferSize = 81920 /* 默认缓冲区大小 */, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
using (var md5 = MD5.Create())
|
||||||
|
{
|
||||||
|
// 使用带有异步选项和优化缓冲区大小的FileStream
|
||||||
|
using (var stream = new FileStream(
|
||||||
|
filePath,
|
||||||
|
FileMode.Open,
|
||||||
|
FileAccess.Read,
|
||||||
|
FileShare.Read,
|
||||||
|
bufferSize: bufferSize,
|
||||||
|
options: FileOptions.SequentialScan | FileOptions.Asynchronous))
|
||||||
|
{
|
||||||
|
var hash = await md5.ComputeHashAsync(stream, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 快速文件标识(前1MB哈希 + 文件大小)
|
||||||
|
public static async Task<string> GetQuickFileIdentity(string filePath)
|
||||||
|
{
|
||||||
|
const int sampleSize = 1 * 1024 * 1024; // 1MB
|
||||||
|
var fileInfo = new FileInfo(filePath);
|
||||||
|
long fileSize = fileInfo.Length;
|
||||||
|
|
||||||
|
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[Math.Min(sampleSize, fileSize)];
|
||||||
|
int bytesRead = await fs.ReadAsync(buffer, 0, buffer.Length);
|
||||||
|
|
||||||
|
using (var sha = SHA256.Create())
|
||||||
|
{
|
||||||
|
byte[] hashBytes = sha.ComputeHash(buffer);
|
||||||
|
return $"{fileSize}-{BitConverter.ToString(hashBytes).Replace("-", "")}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
54
WinUINotes/FileUploadPage.xaml
Normal file
54
WinUINotes/FileUploadPage.xaml
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Page
|
||||||
|
x:Class="WinUINotes.FileUploadPage"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:WinUINotes"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<Grid Margin="12">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"></RowDefinition>
|
||||||
|
<RowDefinition Height="Auto"></RowDefinition>
|
||||||
|
<RowDefinition Height="*"></RowDefinition>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Border Grid.Row="0" BorderBrush="LightGray" BorderThickness="1" CornerRadius="4" Padding="10" Margin="0,0,0,10">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="文件信息" FontWeight="SemiBold" Margin="0,0,0,10">
|
||||||
|
</TextBlock>
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||||
|
<ColumnDefinition Width="Auto"></ColumnDefinition>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBox x:Name="txtFilePath" IsReadOnly="True" PlaceholderText="文件路径" Grid.Column="0" VerticalAlignment="Center"></TextBox>
|
||||||
|
<Button x:Name="btnBrowse" Content="浏览" Grid.Column="1" Margin="10,0,0,0" Click="BtnBrowse_OnClick"/>
|
||||||
|
</Grid>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,10,0,0">
|
||||||
|
<Button x:Name="btnUpload" Content="上传" Click="BtnUpload_OnClick" Margin="0,0,10,0"/>
|
||||||
|
<Button x:Name="btnPause" Content="暂停" IsEnabled="False" Click="BtnPause_OnClick" Margin="0,0,10,0"/>
|
||||||
|
<Button x:Name="btnCancel" Content="取消" IsEnabled="False" Click="BtnCancel_OnClick"/>
|
||||||
|
</StackPanel>
|
||||||
|
<TextBlock x:Name="lblFileSize" Text="大小: -" Margin="0,10,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Border Grid.Row="1" BorderBrush="LightGray" BorderThickness="1" CornerRadius="4" Padding="10">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="上传进度" FontWeight="SemiBold" Margin="0,0,0,10"/>
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<ProgressBar x:Name="progressBar" Grid.Column="0" Minimum="0" Maximum="100"/>
|
||||||
|
<TextBlock x:Name="lblPercentage" Text="0%" Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="10,0,0,0"/>
|
||||||
|
</Grid>
|
||||||
|
<TextBlock x:Name="lblProgress" Margin="0,5,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
347
WinUINotes/FileUploadPage.xaml.cs
Normal file
347
WinUINotes/FileUploadPage.xaml.cs
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
using Grpc.Core;
|
||||||
|
using Grpc.Net.Client;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||||
|
using Microsoft.UI.Xaml.Data;
|
||||||
|
using Microsoft.UI.Xaml.Input;
|
||||||
|
using Microsoft.UI.Xaml.Media;
|
||||||
|
using Microsoft.UI.Xaml.Navigation;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices.WindowsRuntime;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Channels;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Windows.Foundation;
|
||||||
|
using Windows.Foundation.Collections;
|
||||||
|
using Windows.Media.Protection.PlayReady;
|
||||||
|
using Windows.Storage;
|
||||||
|
using Windows.Storage.Pickers;
|
||||||
|
using WinRT.Interop;
|
||||||
|
using WinUINotes.Common;
|
||||||
|
using WinUINotes.Grpc;
|
||||||
|
|
||||||
|
// To learn more about WinUI, the WinUI project structure,
|
||||||
|
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||||
|
|
||||||
|
namespace WinUINotes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class FileUploadPage : Page
|
||||||
|
{
|
||||||
|
private GrpcChannel _channel;
|
||||||
|
private FileTransfer.FileTransferClient _client;
|
||||||
|
private CancellationTokenSource _cancellationTokenSource;
|
||||||
|
|
||||||
|
public FileUploadPage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
InitializeGrpcClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void InitializeGrpcClient()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 注意: 对于生产环境,应该使用更安全的通道配置
|
||||||
|
_channel = GrpcChannel.ForAddress("http://localhost:5065");
|
||||||
|
_client = new FileTransfer.FileTransferClient(_channel);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
//var errorDialog = new ContentDialog
|
||||||
|
//{
|
||||||
|
// Title = "Error",
|
||||||
|
// Content = $"初始化gRPC客户端失败: {ex.Message}",
|
||||||
|
// CloseButtonText = "确定", // Equivalent to MessageBoxButtons.OK
|
||||||
|
// // For XamlRoot, assume 'this' refers to your current Window or Page
|
||||||
|
// XamlRoot = this.Content.XamlRoot
|
||||||
|
//};
|
||||||
|
|
||||||
|
//await errorDialog.ShowAsync();
|
||||||
|
await ShowErrorDialog($"初始化gRPC客户端失败: {ex.Message}", "Error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void BtnBrowse_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
//disable the button to avoid double-clicking
|
||||||
|
var senderButton = sender as Button;
|
||||||
|
senderButton.IsEnabled = false;
|
||||||
|
|
||||||
|
|
||||||
|
var picker = new FileOpenPicker();
|
||||||
|
|
||||||
|
// IMPORTANT: For desktop WinUI 3 apps, you must associate the picker with the window.
|
||||||
|
// Get the current window's HWND (handle). 'this' should refer to your MainWindow or Page.
|
||||||
|
var hwnd = WindowNative.GetWindowHandle(App.m_window);
|
||||||
|
InitializeWithWindow.Initialize(picker, hwnd);
|
||||||
|
|
||||||
|
// Set properties similar to OpenFileDialog
|
||||||
|
picker.ViewMode = PickerViewMode.Thumbnail; // Or List, Details
|
||||||
|
//picker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary; // Or Desktop, PicturesLibrary, etc.
|
||||||
|
|
||||||
|
// Add file type filters. For all files, use "*"
|
||||||
|
picker.FileTypeFilter.Add("*");
|
||||||
|
// Example for specific types:
|
||||||
|
// picker.FileTypeFilter.Add(".txt");
|
||||||
|
// picker.FileTypeFilter.Add(".docx");
|
||||||
|
|
||||||
|
// Since Multiselect = false in original, use PickSingleFileAsync()
|
||||||
|
StorageFile file = await picker.PickSingleFileAsync();
|
||||||
|
|
||||||
|
if (file != null)
|
||||||
|
{
|
||||||
|
// 'file' is a StorageFile object, not just a path string.
|
||||||
|
txtFilePath.Text = file.Path;
|
||||||
|
|
||||||
|
// Get file size using StorageFile's properties (more UWP/WinUI friendly)
|
||||||
|
var properties = await file.GetBasicPropertiesAsync();
|
||||||
|
lblFileSize.Text = $"大小: {FormatFileSize(properties.Size)}"; // Assuming FormatFileSize can handle ulong
|
||||||
|
|
||||||
|
// Reset progress display
|
||||||
|
progressBar.Value = 0;
|
||||||
|
lblProgress.Text = ""; // Or "文件已选择。"
|
||||||
|
lblPercentage.Text = "0%";
|
||||||
|
|
||||||
|
// Store the selected file if you need to use it later for upload
|
||||||
|
// e.g., _selectedFile = file; (where _selectedFile is a StorageFile field)
|
||||||
|
btnUpload.IsEnabled = true; // Enable the upload button if you have one
|
||||||
|
lblProgress.Text = "文件已选择。";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// User canceled the file selection
|
||||||
|
txtFilePath.Text = string.Empty;
|
||||||
|
lblFileSize.Text = "大小: -";
|
||||||
|
btnUpload.IsEnabled = false; // Disable upload if no file is selected
|
||||||
|
lblProgress.Text = "未选择文件。";
|
||||||
|
progressBar.Value = 0;
|
||||||
|
lblPercentage.Text = "0%";
|
||||||
|
}
|
||||||
|
|
||||||
|
senderButton.IsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void BtnUpload_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(txtFilePath.Text) || !File.Exists(txtFilePath.Text))
|
||||||
|
{
|
||||||
|
await ShowErrorDialog("请选择一个有效文件.", "Error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var filePath = txtFilePath.Text;
|
||||||
|
var fileInfo = new FileInfo(filePath);
|
||||||
|
|
||||||
|
if (fileInfo.Length > 100L * 1024 * 1024 * 1024) // 100GB
|
||||||
|
{
|
||||||
|
await ShowErrorDialog("文件大小限制为100GB.", "Error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 禁用UI控件
|
||||||
|
SetUploadControls(false);
|
||||||
|
|
||||||
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await UploadFileWithResume(filePath, _cancellationTokenSource.Token);
|
||||||
|
await ShowInfoDialog("文件上传成功!", "Success");
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
await ShowInfoDialog("上传取消.", "Info");
|
||||||
|
}
|
||||||
|
catch (RpcException rpcEx) when (rpcEx.StatusCode == StatusCode.Unavailable)
|
||||||
|
{
|
||||||
|
await ShowErrorDialog("服务无效,请检查连接.", "Error");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await ShowErrorDialog($"上传失败: {ex.Message}", "Error");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// 重新启用UI控件
|
||||||
|
SetUploadControls(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetUploadControls(bool enable)
|
||||||
|
{
|
||||||
|
btnBrowse.IsEnabled = enable;
|
||||||
|
btnUpload.IsEnabled = enable;
|
||||||
|
btnPause.IsEnabled = !enable;
|
||||||
|
btnCancel.IsEnabled = !enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UploadFileWithResume(string filePath, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var fileInfo = new FileInfo(filePath);
|
||||||
|
var fileHash = await FileHelper.GetQuickFileIdentity(filePath);
|
||||||
|
const int chunkSize = 10 * 1024 * 1024; // 1MB chunk size
|
||||||
|
|
||||||
|
// 检查服务器上是否已有上传记录
|
||||||
|
var statusResponse = await _client.CheckUploadStatusAsync(new CheckUploadStatusRequest
|
||||||
|
{
|
||||||
|
FileName = fileInfo.Name,
|
||||||
|
FileHash = fileHash
|
||||||
|
});
|
||||||
|
|
||||||
|
string sessionId;
|
||||||
|
ulong uploadedBytes = 0;
|
||||||
|
|
||||||
|
if (statusResponse.Exists && statusResponse.UploadedBytes > 0)
|
||||||
|
{
|
||||||
|
// 恢复已有上传
|
||||||
|
sessionId = statusResponse.SessionId;
|
||||||
|
uploadedBytes = (ulong)statusResponse.UploadedBytes;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 开始新上传
|
||||||
|
var initResponse = await _client.InitUploadAsync(new InitUploadRequest
|
||||||
|
{
|
||||||
|
FileName = fileInfo.Name,
|
||||||
|
FileSize = fileInfo.Length,
|
||||||
|
FileHash = fileHash
|
||||||
|
});
|
||||||
|
sessionId = initResponse.SessionId;
|
||||||
|
uploadedBytes = (ulong)initResponse.UploadedBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新UI显示初始进度
|
||||||
|
UpdateProgress(uploadedBytes, (ulong)fileInfo.Length);
|
||||||
|
|
||||||
|
// 打开文件准备读取
|
||||||
|
using (var fileStream = File.OpenRead(filePath))
|
||||||
|
{
|
||||||
|
fileStream.Seek((long)uploadedBytes, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
var call = _client.UploadChunk();
|
||||||
|
var remainingBytes = fileInfo.Length - (long)uploadedBytes;
|
||||||
|
|
||||||
|
while (remainingBytes > 0 && !cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
var currentChunkSize = (int)Math.Min(chunkSize, remainingBytes);
|
||||||
|
var buffer = new byte[currentChunkSize];
|
||||||
|
var bytesRead = await fileStream.ReadAsync(buffer, 0, currentChunkSize, cancellationToken);
|
||||||
|
|
||||||
|
if (bytesRead == 0) break;
|
||||||
|
|
||||||
|
await call.RequestStream.WriteAsync(new UploadChunkRequest
|
||||||
|
{
|
||||||
|
SessionId = sessionId,
|
||||||
|
ChunkData = Google.Protobuf.ByteString.CopyFrom(buffer),
|
||||||
|
Offset = (long)uploadedBytes
|
||||||
|
});
|
||||||
|
|
||||||
|
uploadedBytes += (ulong)bytesRead;
|
||||||
|
remainingBytes -= bytesRead;
|
||||||
|
|
||||||
|
// 更新UI进度
|
||||||
|
UpdateProgress(uploadedBytes, (ulong)fileInfo.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
await call.RequestStream.CompleteAsync();
|
||||||
|
var response = await call.ResponseAsync;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
// 完成上传
|
||||||
|
var completeResponse = await _client.CompleteUploadAsync(new CompleteUploadRequest
|
||||||
|
{
|
||||||
|
SessionId = sessionId,
|
||||||
|
FileHash = fileHash
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!completeResponse.Success)
|
||||||
|
{
|
||||||
|
throw new Exception(completeResponse.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateProgress(ulong uploadedBytes, ulong totalBytes)
|
||||||
|
{
|
||||||
|
if (!this.DispatcherQueue.HasThreadAccess) // Check if the current thread has UI access
|
||||||
|
{
|
||||||
|
this.DispatcherQueue.TryEnqueue(() => UpdateProgress(uploadedBytes, totalBytes));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
progressBar.Value = (int)((double)uploadedBytes / totalBytes * 100);
|
||||||
|
lblProgress.Text = $"{FormatFileSize(uploadedBytes)} / {FormatFileSize(totalBytes)}";
|
||||||
|
lblPercentage.Text = $"{progressBar.Value}%";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatFileSize(ulong bytes)
|
||||||
|
{
|
||||||
|
string[] sizes = ["B", "KB", "MB", "GB", "TB"];
|
||||||
|
var order = 0;
|
||||||
|
double len = bytes;
|
||||||
|
|
||||||
|
while (len >= 1024 && order < sizes.Length - 1)
|
||||||
|
{
|
||||||
|
order++;
|
||||||
|
len /= 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"{len:0.##} {sizes[order]}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtnPause_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_cancellationTokenSource?.Cancel();
|
||||||
|
btnPause.IsEnabled = false;
|
||||||
|
btnUpload.IsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_cancellationTokenSource?.Cancel();
|
||||||
|
btnCancel.IsEnabled = false;
|
||||||
|
btnPause.IsEnabled = false;
|
||||||
|
btnUpload.IsEnabled = true;
|
||||||
|
|
||||||
|
// 重置进度显示
|
||||||
|
progressBar.Value = 0;
|
||||||
|
lblProgress.Text = "";
|
||||||
|
lblPercentage.Text = "0%";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method for showing info dialogs
|
||||||
|
private async Task ShowInfoDialog(string content, string title)
|
||||||
|
{
|
||||||
|
var dialog = new ContentDialog
|
||||||
|
{
|
||||||
|
Title = title,
|
||||||
|
Content = content,
|
||||||
|
CloseButtonText = "确定",
|
||||||
|
XamlRoot = this.Content.XamlRoot
|
||||||
|
};
|
||||||
|
await dialog.ShowAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method for showing error dialogs
|
||||||
|
private async Task ShowErrorDialog(string content, string title)
|
||||||
|
{
|
||||||
|
var dialog = new ContentDialog
|
||||||
|
{
|
||||||
|
Title = title,
|
||||||
|
Content = content,
|
||||||
|
CloseButtonText = "确定",
|
||||||
|
XamlRoot = this.Content.XamlRoot
|
||||||
|
};
|
||||||
|
await dialog.ShowAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
WinUINotes/HomePage.xaml
Normal file
14
WinUINotes/HomePage.xaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Page
|
||||||
|
x:Class="WinUINotes.HomePage"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:WinUINotes"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<TextBlock Text="欢迎来到主页!" FontSize="24" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
31
WinUINotes/HomePage.xaml.cs
Normal file
31
WinUINotes/HomePage.xaml.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||||
|
using Microsoft.UI.Xaml.Data;
|
||||||
|
using Microsoft.UI.Xaml.Input;
|
||||||
|
using Microsoft.UI.Xaml.Media;
|
||||||
|
using Microsoft.UI.Xaml.Navigation;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices.WindowsRuntime;
|
||||||
|
using Windows.Foundation;
|
||||||
|
using Windows.Foundation.Collections;
|
||||||
|
|
||||||
|
// To learn more about WinUI, the WinUI project structure,
|
||||||
|
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||||
|
|
||||||
|
namespace WinUINotes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class HomePage : Page
|
||||||
|
{
|
||||||
|
public HomePage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -14,7 +14,25 @@
|
|||||||
</Window.SystemBackdrop>
|
</Window.SystemBackdrop>
|
||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Frame x:Name="rootFrame" Grid.Row="1"
|
<NavigationView
|
||||||
SourcePageType="local:NotePage"/>
|
x:Name="NavigationViewControl"
|
||||||
|
IsBackButtonVisible="Auto"
|
||||||
|
IsSettingsVisible="True"
|
||||||
|
SelectionChanged="NavigationViewControl_SelectionChanged">
|
||||||
|
|
||||||
|
<NavigationView.MenuItems>
|
||||||
|
<NavigationViewItem Tag="HomePage" Icon="Home" Content="主页" />
|
||||||
|
<NavigationViewItem Tag="NotePage" Icon="Bookmarks" Content="笔记" />
|
||||||
|
<NavigationViewItem Tag="FileUploadPage" Icon="Upload" Content="文件上传"></NavigationViewItem>
|
||||||
|
<NavigationViewItemSeparator />
|
||||||
|
|
||||||
|
</NavigationView.MenuItems>
|
||||||
|
<NavigationView.FooterMenuItems>
|
||||||
|
<NavigationViewItem Tag="AboutPage" Icon="Help" Content="关于" />
|
||||||
|
</NavigationView.FooterMenuItems>
|
||||||
|
<Frame x:Name="ContentFrame" />
|
||||||
|
</NavigationView>
|
||||||
|
<!--<Frame x:Name="rootFrame" Grid.Row="1"
|
||||||
|
SourcePageType="local:NotePage"/>-->
|
||||||
</Grid>
|
</Grid>
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
@ -27,5 +27,34 @@ namespace WinUINotes
|
|||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void NavigationViewControl_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.IsSettingsSelected)
|
||||||
|
{
|
||||||
|
ContentFrame.Navigate(typeof(SettingsPage));
|
||||||
|
}
|
||||||
|
else if (args.SelectedItem is NavigationViewItem selectedItem)
|
||||||
|
{
|
||||||
|
string tag = selectedItem.Tag?.ToString();
|
||||||
|
|
||||||
|
switch (tag)
|
||||||
|
{
|
||||||
|
case "HomePage":
|
||||||
|
ContentFrame.Navigate(typeof(HomePage));
|
||||||
|
break;
|
||||||
|
case "NotePage":
|
||||||
|
ContentFrame.Navigate(typeof(NotePage));
|
||||||
|
break;
|
||||||
|
case "AboutPage":
|
||||||
|
ContentFrame.Navigate(typeof(AboutPage));
|
||||||
|
break;
|
||||||
|
case "FileUploadPage":
|
||||||
|
ContentFrame.Navigate(typeof(FileUploadPage));
|
||||||
|
break;
|
||||||
|
// 可以添加更多 case 来处理其他导航项
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
WinUINotes/Model/Note.cs
Normal file
17
WinUINotes/Model/Note.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace WinUINotes.Model
|
||||||
|
{
|
||||||
|
public class Note
|
||||||
|
{
|
||||||
|
public string Content { get; set; }
|
||||||
|
|
||||||
|
public long UserId { get; set; }
|
||||||
|
|
||||||
|
public DateTime CreateTime { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,7 +4,7 @@ using Microsoft.UI.Xaml.Controls;
|
|||||||
using System;
|
using System;
|
||||||
using Windows.Storage;
|
using Windows.Storage;
|
||||||
using Google.Protobuf.WellKnownTypes;
|
using Google.Protobuf.WellKnownTypes;
|
||||||
using ZR.Admin.Grpc;
|
using WinUINotes.Grpc;
|
||||||
|
|
||||||
// To learn more about WinUI, the WinUI project structure,
|
// To learn more about WinUI, the WinUI project structure,
|
||||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||||
|
|||||||
@ -1,10 +1,15 @@
|
|||||||
{
|
{
|
||||||
"profiles": {
|
"profiles": {
|
||||||
"WinUINotes (Package)": {
|
"WinUINotes (Package)": {
|
||||||
"commandName": "MsixPackage"
|
"commandName": "MsixPackage",
|
||||||
|
"nativeDebugging": false
|
||||||
},
|
},
|
||||||
"WinUINotes (Unpackaged)": {
|
"WinUINotes (Unpackaged)": {
|
||||||
"commandName": "Project"
|
"commandName": "Project"
|
||||||
|
},
|
||||||
|
"WSL": {
|
||||||
|
"commandName": "WSL2",
|
||||||
|
"distributionName": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
14
WinUINotes/SettingsPage.xaml
Normal file
14
WinUINotes/SettingsPage.xaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Page
|
||||||
|
x:Class="WinUINotes.SettingsPage"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:WinUINotes"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<TextBlock Text="这是设置页面。" FontSize="24" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
31
WinUINotes/SettingsPage.xaml.cs
Normal file
31
WinUINotes/SettingsPage.xaml.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||||
|
using Microsoft.UI.Xaml.Data;
|
||||||
|
using Microsoft.UI.Xaml.Input;
|
||||||
|
using Microsoft.UI.Xaml.Media;
|
||||||
|
using Microsoft.UI.Xaml.Navigation;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices.WindowsRuntime;
|
||||||
|
using Windows.Foundation;
|
||||||
|
using Windows.Foundation.Collections;
|
||||||
|
|
||||||
|
// To learn more about WinUI, the WinUI project structure,
|
||||||
|
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||||
|
|
||||||
|
namespace WinUINotes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class SettingsPage : Page
|
||||||
|
{
|
||||||
|
public SettingsPage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,7 +13,10 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<None Remove="AboutPage.xaml" />
|
||||||
|
<None Remove="HomePage.xaml" />
|
||||||
<None Remove="NotePage.xaml" />
|
<None Remove="NotePage.xaml" />
|
||||||
|
<None Remove="SettingsPage.xaml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -45,6 +48,26 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\WinUINotes.Grpc\WinUINotes.Grpc.csproj" />
|
<ProjectReference Include="..\WinUINotes.Grpc\WinUINotes.Grpc.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="FileUploadPage.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="AboutPage.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="SettingsPage.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="HomePage.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Page Update="NotePage.xaml">
|
<Page Update="NotePage.xaml">
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
|||||||
@ -1,10 +1,27 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<PropertyGroup />
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
|
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<ActiveDebugProfile>WinUINotes (Package)</ActiveDebugProfile>
|
||||||
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Page Update="AboutPage.xaml">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</Page>
|
||||||
<None Update="App.xaml">
|
<None Update="App.xaml">
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
</None>
|
</None>
|
||||||
|
<Page Update="FileUploadPage.xaml">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</Page>
|
||||||
|
<Page Update="SettingsPage.xaml">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</Page>
|
||||||
|
<Page Update="HomePage.xaml">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</Page>
|
||||||
<None Update="MainWindow.xaml">
|
<None Update="MainWindow.xaml">
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
</None>
|
</None>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user