Skip to content

Commit

Permalink
Add sprite download progress bar and cancellation
Browse files Browse the repository at this point in the history
  • Loading branch information
MattEqualsCoder committed Jul 17, 2024
1 parent 185cc8f commit 7ce052c
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using TrackerCouncil.Smz3.Data.Options;
Expand All @@ -19,6 +20,7 @@ public class GitHubSpriteDownloaderService : IGitHubSpriteDownloaderService
private ILogger<GitHubSpriteDownloaderService> _logger;
private string _spriteFolder;
private OptionsFactory _optionsFactory;
private CancellationTokenSource _cts = new();

public GitHubSpriteDownloaderService(ILogger<GitHubSpriteDownloaderService> logger, OptionsFactory optionsFactory)

Check warning on line 25 in src/TrackerCouncil.Smz3.Data/Services/GitHubSpriteDownloaderService.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable event 'SpriteDownloadUpdate' must contain a non-null value when exiting constructor. Consider declaring the event as nullable.

Check warning on line 25 in src/TrackerCouncil.Smz3.Data/Services/GitHubSpriteDownloaderService.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable event 'SpriteDownloadUpdate' must contain a non-null value when exiting constructor. Consider declaring the event as nullable.
{
Expand All @@ -28,6 +30,10 @@ public GitHubSpriteDownloaderService(ILogger<GitHubSpriteDownloaderService> logg
_logger.LogInformation("Sprite path: {Path} | {OtherPath}", Sprite.SpritePath, AppContext.BaseDirectory);
}

public void CancelDownload() => _cts.Cancel();

public event EventHandler<SpriteDownloadUpdateEventArgs> SpriteDownloadUpdate;

public async Task<IDictionary<string, string>?> GetSpritesToDownloadAsync(string owner, string repo, TimeSpan? timeout = null)
{
var sprites = await GetGitHubSpritesAsync(owner, repo, timeout);
Expand Down Expand Up @@ -59,11 +65,14 @@ public GitHubSpriteDownloaderService(ILogger<GitHubSpriteDownloaderService> logg

public async Task DownloadSpritesAsync(string owner, string repo, IDictionary<string, string>? spritesToDownload = null, TimeSpan? timeout = null)
{
spritesToDownload ??= await GetSpritesToDownloadAsync(owner, repo, timeout);
if (spritesToDownload == null)
{
spritesToDownload = await GetSpritesToDownloadAsync(owner, repo, timeout);
return;
}

_cts.TryReset();

if (!Directory.Exists(_spriteFolder))
{
Directory.CreateDirectory(_spriteFolder);
Expand All @@ -72,9 +81,12 @@ public async Task DownloadSpritesAsync(string owner, string repo, IDictionary<st
var previousHashes = GetPreviousSpriteHashes();
var added = new ConcurrentDictionary<string, string>();

var total = spritesToDownload.Count;
var completed = 0;

if (spritesToDownload?.Any() == true)
{
await Parallel.ForEachAsync(spritesToDownload, parallelOptions: new ParallelOptions() { MaxDegreeOfParallelism = 4 },
await Parallel.ForEachAsync(spritesToDownload, parallelOptions: new ParallelOptions() { MaxDegreeOfParallelism = 4, CancellationToken = _cts.Token},
async (spriteData, _) =>
{
var localPath = ConvertGitHubPath(spriteData.Key);
Expand All @@ -86,6 +98,9 @@ public async Task DownloadSpritesAsync(string owner, string repo, IDictionary<st
{
added[localPath] = currentHash;
}
completed++;
SpriteDownloadUpdate.Invoke(this, new SpriteDownloadUpdateEventArgs(completed, total));
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,14 @@ public interface IGitHubSpriteDownloaderService
/// <param name="timeout">How long to timeout from the api call</param>
/// <returns></returns>
public Task DownloadSpritesAsync(string owner, string repo, IDictionary<string, string>? spritesToDownload = null, TimeSpan? timeout = null);

/// <summary>
/// Cancels the current sprite download
/// </summary>
public void CancelDownload();

/// <summary>
/// Event that fires off after each completed sprite download
/// </summary>
public event EventHandler<SpriteDownloadUpdateEventArgs> SpriteDownloadUpdate;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

namespace TrackerCouncil.Smz3.Data.Services;

public class SpriteDownloadUpdateEventArgs(int completed, int total) : EventArgs
{
public int Completed => completed;
public int Total => total;
}
2 changes: 1 addition & 1 deletion src/TrackerCouncil.Smz3.UI/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public static IServiceCollection ConfigureServices(this IServiceCollection servi
services.AddSingleton<IGameDbService, GameDbService>();
services.AddTransient<SourceRomValidationService>();
services.AddTransient<IGitHubConfigDownloaderService, GitHubConfigDownloaderService>();
services.AddTransient<IGitHubSpriteDownloaderService, GitHubSpriteDownloaderService>();
services.AddSingleton<IGitHubSpriteDownloaderService, GitHubSpriteDownloaderService>();
services.AddSingleton<OptionsFactory>();
services.AddSingleton<IMicrophoneService, NullMicrophoneService>();

Expand Down
26 changes: 26 additions & 0 deletions src/TrackerCouncil.Smz3.UI/Services/SpriteDownloadWindowService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using AvaloniaControls.ControlServices;
using TrackerCouncil.Smz3.Data.Services;
using TrackerCouncil.Smz3.UI.ViewModels;

namespace TrackerCouncil.Smz3.UI.Services;

public class SpriteDownloadWindowService(IGitHubSpriteDownloaderService gitHubSpriteDownloaderService) : ControlService
{
private SpriteDownloadWindowViewModel _model = new();

public SpriteDownloadWindowViewModel InitializeModel()
{
gitHubSpriteDownloaderService.SpriteDownloadUpdate += (sender, args) =>
{
_model.NumTotal = args.Total;
_model.NumCompleted = args.Completed;
};

return _model;
}

public void CancelDownload()
{
gitHubSpriteDownloaderService.CancelDownload();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using ReactiveUI.Fody.Helpers;

namespace TrackerCouncil.Smz3.UI.ViewModels;

public class SpriteDownloadWindowViewModel : ViewModelBase
{
[Reactive] public int NumCompleted { get; set; }
[Reactive] public int NumTotal { get; set; } = 1;
}
18 changes: 15 additions & 3 deletions src/TrackerCouncil.Smz3.UI/Views/SpriteDownloadWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,28 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:TrackerCouncil.Smz3.UI.ViewModels"
xmlns:controls="clr-namespace:AvaloniaControls.Controls;assembly=AvaloniaControls"
mc:Ignorable="d"
Width="300"
Height="100"
CanResize="False"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center"
Padding="10"
SizeToContent="Height"
WindowStartupLocation="CenterOwner"
Icon="/Assets/smz3.ico"
x:Class="TrackerCouncil.Smz3.UI.Views.SpriteDownloadWindow"
x:DataType="viewModels:SpriteDownloadWindowViewModel"
Closing="Window_OnClosing"
Title="SMZ3 Cas' Randomizer">
Downloading Sprites
<DockPanel Margin="0">

<StackPanel Orientation="Vertical">
<TextBlock Text="Downloading Sprites"></TextBlock>
<ProgressBar Maximum="{Binding NumTotal}" Value="{Binding NumCompleted}" Margin="0 5 0 0"></ProgressBar>
<Button HorizontalAlignment="Center" Margin="0 10 0 0" Click="Button_OnClick">Cancel Download</Button>
</StackPanel>
</DockPanel>

</Window>

29 changes: 29 additions & 0 deletions src/TrackerCouncil.Smz3.UI/Views/SpriteDownloadWindow.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,41 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using AvaloniaControls.Extensions;
using TrackerCouncil.Smz3.UI.Services;
using TrackerCouncil.Smz3.UI.ViewModels;

namespace TrackerCouncil.Smz3.UI.Views;

public partial class SpriteDownloadWindow : Window
{
private SpriteDownloadWindowService? _service;

public SpriteDownloadWindow()
{
InitializeComponent();

if (Design.IsDesignMode)
{
DataContext = new SpriteDownloadWindowViewModel
{
NumCompleted = 5, NumTotal = 10
};
}
else
{
_service = this.GetControlService<SpriteDownloadWindowService>();
DataContext = _service?.InitializeModel() ?? new SpriteDownloadWindowViewModel();
}
}

private void Window_OnClosing(object? sender, WindowClosingEventArgs e)
{
_service?.CancelDownload();
}

private void Button_OnClick(object? sender, RoutedEventArgs e)
{
Close();
}
}

0 comments on commit 7ce052c

Please sign in to comment.