Skip to content

Commit

Permalink
Add upload task management
Browse files Browse the repository at this point in the history
  • Loading branch information
aiguoli committed Aug 19, 2023
1 parent 778c5ab commit 42eea10
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 47 deletions.
30 changes: 19 additions & 11 deletions Pages/CloudPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@
using Microsoft.UI.Xaml.Controls;
using SimpleList.ViewModels;
using SimpleList.Views;
using SimpleList.Services;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Windows.ApplicationModel.DataTransfer;
using Windows.Storage;
using CommunityToolkit.Mvvm.DependencyInjection;
using System.Linq;
using System.Threading.Tasks;

// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
Expand Down Expand Up @@ -51,17 +55,21 @@ private async void DropToUpload(object sender, DragEventArgs e)
{
IReadOnlyList<IStorageItem> items = await e.DataView.GetStorageItemsAsync();
CloudViewModel cloudViewModel = (CloudViewModel)DataContext;
foreach (IStorageItem item in items)
{
if (item is StorageFile file)
{
await cloudViewModel.Drive.UploadFileAsync(file, cloudViewModel.ParentItemId);
}
if (item is StorageFolder folder)
{
await cloudViewModel.Drive.UploadFolderAsync(folder, cloudViewModel.ParentItemId);
}
}
//foreach (IStorageItem item in items)
//{
// if (item is StorageFile file)
// {
// await cloudViewModel.Drive.UploadFileAsync(file, cloudViewModel.ParentItemId);
// }
// if (item is StorageFolder folder)
// {
// await cloudViewModel.Drive.UploadFolderAsync(folder, cloudViewModel.ParentItemId);
// }
//}

TaskManagerViewModel manager = Ioc.Default.GetService<TaskManagerViewModel>();
var tasks = items.Select(item => manager.AddUploadTask(cloudViewModel.ParentItemId, item));
await Task.WhenAll(tasks);
cloudViewModel.Refresh();
}
}
Expand Down
29 changes: 28 additions & 1 deletion Pages/TaskManagerPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,41 @@
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate x:Key="UploadTaskTemplate" x:DataType="vm:UploadTaskViewModel">
<Grid Margin="0 10 0 10">
<Grid.ContextFlyout>
<MenuFlyout>
<MenuFlyoutItem Text="Resume" />
</MenuFlyout>
</Grid.ContextFlyout>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Name}" FontSize="20" Margin="0 5 0 5" />
<ProgressBar Value="{Binding Progress}" Visibility="{Binding Completed, Converter={StaticResource BoolToObjectConverter}}" />
<TextBlock Text="Download completed" Visibility="{Binding Completed, Converter={StaticResource BoolToVisibilityConverter}}" />
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" Margin="10 0 10 0">
<Button Margin="0 0 10 0" Visibility="{Binding IsUploading, Converter={StaticResource BoolToVisibilityConverter}}" Command="{Binding PauseCommand}">
<!--Pasue icon-->
<FontIcon Glyph="&#xE769;" FontSize="10" />
</Button>
<Button Command="{Binding CancelCommand}">
<FontIcon Glyph="&#xE711;" FontSize="10" />
</Button>
</StackPanel>
</Grid>
</DataTemplate>
</Grid.Resources>

<Pivot>
<PivotItem Header="Download">
<ListView ItemsSource="{Binding DownloadTasks}" ItemTemplate="{StaticResource DownloadTaskTemplate}" />
</PivotItem>
<PivotItem Header="Upload">
<TextBlock Text="Upload tasks" />
<ListView ItemsSource="{Binding UploadTasks}" ItemTemplate="{StaticResource UploadTaskTemplate}" />
</PivotItem>
</Pivot>
</Grid>
Expand Down
38 changes: 25 additions & 13 deletions Services/OneDrive.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Windows.Storage;

Expand Down Expand Up @@ -50,32 +52,42 @@ public async Task<DriveItem> RenameFile(string itemId, string newName)
return await graphClient.Me.Drive.Items[itemId].Request().UpdateAsync(requestBody);
}

public async Task UploadFileAsync(StorageFile file, string itemId)
public async Task UploadFileAsync(StorageFile file, string itemId, IProgress<long> progress = null)
{
GraphServiceClient graphClient = _provider.GetClient();
using Stream stream = await file.OpenStreamForReadAsync();
Stream stream = await file.OpenStreamForReadAsync();
if ((await file.GetBasicPropertiesAsync()).Size == 0)
{
// Upload an empty file
await graphClient.Me.Drive.Items[itemId].ItemWithPath(file.Name).Content.Request().PutAsync<DriveItem>(new MemoryStream());
return;
}
UploadSession uploadSession = await graphClient.Me.Drive.Items[itemId].ItemWithPath(file.Name).CreateUploadSession().Request().PostAsync();
int maxChunckSize = 320 * 1024;
LargeFileUploadTask<DriveItem> fileUploadTask = new(uploadSession, stream, maxChunckSize, graphClient);

long fileSize = stream.Length;
await fileUploadTask.UploadAsync();
await fileUploadTask.UploadAsync(progress);
}

public async Task UploadFolderAsync(StorageFolder folder, string itemId)
public async Task UploadFolderAsync(StorageFolder folder, string itemId, IProgress<long> progress = null)
{
ulong totalSize = await Utils.GetFolderSize(folder);
ulong uploadedSize = 0;
var files = await folder.GetFilesAsync();
DriveItem cloudFolder = await CreateFolder(itemId, folder.Name);
foreach (var file in files)
{
await UploadFileAsync(file, cloudFolder.Id);
}

var subfolders = await folder.GetFoldersAsync();
foreach(var subfolder in subfolders)
IEnumerable<Task> uploadTasks = files.Select(async file =>
{
await UploadFolderAsync(subfolder, cloudFolder.Id);
}
await UploadFileAsync(file, cloudFolder.Id, progress);
ulong fileSize = (await file.GetBasicPropertiesAsync()).Size;
Interlocked.Add(ref uploadedSize, fileSize);
progress?.Report((long)(uploadedSize / totalSize));
});
await Task.WhenAll(uploadTasks);

IReadOnlyList<StorageFolder> subfolders = await folder.GetFoldersAsync();
IEnumerable<Task> subfolderTasks = subfolders.Select(subfolder => UploadFolderAsync(subfolder, cloudFolder.Id, progress));
await Task.WhenAll(subfolderTasks);
}

public async Task<string> CreateLink(string itemId, DateTimeOffset? expirationDateTime=null, string password=null, string type = "view")
Expand Down
25 changes: 25 additions & 0 deletions Services/Utils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.Threading.Tasks;
using Windows.Storage;

namespace SimpleList.Services
{
public class Utils
{
public static async Task<ulong> GetFolderSize(StorageFolder folder)
{
ulong res = 0;
foreach (StorageFile file in await folder.GetFilesAsync())
{
Windows.Storage.FileProperties.BasicProperties properties = await file.GetBasicPropertiesAsync();
res += properties.Size;
}

foreach (StorageFolder subFolder in await folder.GetFoldersAsync())
{
res += await GetFolderSize(subFolder);
}
return res;
}
}
}
14 changes: 7 additions & 7 deletions SimpleList.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
<AssemblyName>SimpleList</AssemblyName>
<Authors>Thawne</Authors>
<PackageProjectUrl>https://github.com/aiguoli/SimpleList</PackageProjectUrl>
<AssemblyVersion>1.0.0</AssemblyVersion>
<AssemblyVersion>1.1.0</AssemblyVersion>
<ApplicationIcon>Assets\favicon.ico</ApplicationIcon>
<PackageIcon></PackageIcon>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand Down Expand Up @@ -163,21 +163,21 @@
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
<DebugType>none</DebugType>
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<DebugType>none</DebugType>
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<DebugType>none</DebugType>
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
<DebugType>none</DebugType>
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DebugType>none</DebugType>
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
<DebugType>none</DebugType>
<DebugType>portable</DebugType>
</PropertyGroup>
</Project>
6 changes: 2 additions & 4 deletions ViewModels/DownloadTaskViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.WinUI;
using Downloader;
using Microsoft.Graph;
using Microsoft.UI.Dispatching;
using SimpleList.Services;
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using Windows.Networking.BackgroundTransfer;
using Windows.Storage;

namespace SimpleList.ViewModels
Expand Down Expand Up @@ -96,14 +94,14 @@ public void CancelTask()
{
_downloader.CancelAsync();
}
_manager.RemoveSelectedCompletedTask(this);
_manager.RemoveSelectedDownloadTasks(this);
}

public static readonly int chunkSize = 1024 * 1024; // 1MB chunks
private readonly string _itemId;
private readonly StorageFile _file;
private OneDrive Drive { get; }
private TaskManagerViewModel _manager = Ioc.Default.GetService<TaskManagerViewModel>();
private readonly TaskManagerViewModel _manager = Ioc.Default.GetService<TaskManagerViewModel>();
private DownloadService _downloader;
private DownloadPackage _pack;
private readonly DispatcherQueue _dispatcher = DispatcherQueue.GetForCurrentThread();
Expand Down
23 changes: 19 additions & 4 deletions ViewModels/TaskManagerViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
Expand All @@ -9,15 +10,16 @@ namespace SimpleList.ViewModels
public class TaskManagerViewModel : ObservableObject
{
public ObservableCollection<DownloadTaskViewModel> DownloadTasks { get; } = new();
public ObservableCollection<UploadTaskViewModel> UploadTasks { get; } = new();

public async Task AddDownloadTask(string itemId, StorageFile file)
{
DownloadTaskViewModel task = new(itemId, file);
DownloadTasks.Add(task);
await task.StartDownload();
}
}

public void StartAll()
public void StartAllDownloadTasks()
{
foreach (DownloadTaskViewModel task in DownloadTasks)
{
Expand All @@ -28,17 +30,30 @@ public void StartAll()
}
}

public void RemoveSelectedCompletedTask(DownloadTaskViewModel task)
public void RemoveSelectedDownloadTasks(DownloadTaskViewModel task)
{
DownloadTasks.Remove(task);
}

public void RemoveCompletedTasks()
public void RemoveCompletedDonwloadTasks()
{
foreach (DownloadTaskViewModel completedTask in DownloadTasks.Where(t => t.Completed).ToList())
{
DownloadTasks.Remove(completedTask);
}
}


public async Task AddUploadTask(string itemId, IStorageItem item)
{
UploadTaskViewModel task = new(itemId, item);
UploadTasks.Add(task);
await task.StartUpload();
}

public void RemoveSelectedUploadTasks(UploadTaskViewModel task)
{
UploadTasks.Remove(task);
}
}
}
67 changes: 60 additions & 7 deletions ViewModels/UploadTaskViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,71 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Dispatching;
using SimpleList.Services;
using System;
using System.Threading;
using System.Threading.Tasks;
using Windows.Storage;

namespace SimpleList.ViewModels
{
public class UploadTaskViewModel : ObservableObject
{
private string _name;
private string _path;
private string _status;
private double _progress;
private bool _isCompleted;
public UploadTaskViewModel(string itemId, IStorageItem item)
{
_itemId = itemId;
_item = item;
Drive = Ioc.Default.GetService<OneDrive>();
}

public void StartUpload()
public async Task StartUpload()
{
_isUploading = true;
IProgress<long> progress = new Progress<long>(value =>
{
_dispatcher.TryEnqueue(async () =>
{
Progress = _item is StorageFile ? (int)((ulong)value * 100 / (await _item.GetBasicPropertiesAsync()).Size) : (int)value;
});
});
if (_item is StorageFile file)
{

await Drive.UploadFileAsync(file, _itemId, progress);
} else if (_item is StorageFolder folder)
{
await Drive.UploadFolderAsync(folder, _itemId, progress);
}
Completed = true;
_isUploading = false;
await Task.CompletedTask;
}

public static void PauseUpload()
{
// TODO: Implement upload logic
// Seems that Microsoft Graph API doesn't support pause upload.
}

public void CancelTask()
{
// https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/1678
// Unfortunately, Microsoft Graph v4 doesn't support cancel upload while CommunityToolkit.Graph is still using this version.
// So before it updates to v5, we can only remove the task from the list.
_manager.RemoveSelectedUploadTasks(this);
}

private readonly string _itemId;
private readonly IStorageItem _item;
private readonly TaskManagerViewModel _manager = Ioc.Default.GetService<TaskManagerViewModel>();
private readonly DispatcherQueue _dispatcher = DispatcherQueue.GetForCurrentThread();
private int _progress;
private bool _completed = false;
private bool _isUploading = true;

public OneDrive Drive;
public string Name => _item.Name;
public bool Completed { get => _completed; private set => SetProperty(ref _completed, value); }
public int Progress { get => _progress; private set => SetProperty(ref _progress, value); }
public bool IsUploading { get => _isUploading; private set => SetProperty(ref _isUploading, value); }
}
}

0 comments on commit 42eea10

Please sign in to comment.