You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
230 lines
8.8 KiB
230 lines
8.8 KiB
using System; |
|
using System.Collections.Generic; |
|
using System.Diagnostics; |
|
using System.IO; |
|
using System.Linq; |
|
using System.Threading; |
|
using System.Threading.Tasks; |
|
using AsyncAwaitBestPractices; |
|
using Avalonia.Collections; |
|
using Avalonia.Controls.Notifications; |
|
using Avalonia.Threading; |
|
using CommunityToolkit.Mvvm.ComponentModel; |
|
using CommunityToolkit.Mvvm.Input; |
|
using FluentAvalonia.UI.Controls; |
|
using FluentAvalonia.UI.Media.Animation; |
|
using StabilityMatrix.Avalonia.Controls; |
|
using StabilityMatrix.Avalonia.Languages; |
|
using StabilityMatrix.Avalonia.Services; |
|
using StabilityMatrix.Avalonia.ViewModels.Base; |
|
using StabilityMatrix.Avalonia.ViewModels.Settings; |
|
using StabilityMatrix.Avalonia.Views; |
|
using StabilityMatrix.Core.Attributes; |
|
using StabilityMatrix.Core.Helper; |
|
using StabilityMatrix.Core.Models; |
|
using StabilityMatrix.Core.Models.PackageModification; |
|
using StabilityMatrix.Core.Models.Progress; |
|
using StabilityMatrix.Core.Models.Settings; |
|
using StabilityMatrix.Core.Services; |
|
using Notification = DesktopNotifications.Notification; |
|
using Symbol = FluentIcons.Common.Symbol; |
|
using SymbolIconSource = FluentIcons.Avalonia.Fluent.SymbolIconSource; |
|
|
|
namespace StabilityMatrix.Avalonia.ViewModels.Progress; |
|
|
|
[View(typeof(ProgressManagerPage))] |
|
[ManagedService] |
|
[Singleton] |
|
public partial class ProgressManagerViewModel : PageViewModelBase |
|
{ |
|
private readonly INotificationService notificationService; |
|
private readonly INavigationService<MainWindowViewModel> navigationService; |
|
private readonly INavigationService<SettingsViewModel> settingsNavService; |
|
|
|
public override string Title => "Download Manager"; |
|
public override IconSource IconSource => |
|
new SymbolIconSource { Symbol = Symbol.ArrowCircleDown, IsFilled = true }; |
|
public AvaloniaList<ProgressItemViewModelBase> ProgressItems { get; } = new(); |
|
|
|
[ObservableProperty] |
|
private bool isOpen; |
|
|
|
public ProgressManagerViewModel( |
|
ITrackedDownloadService trackedDownloadService, |
|
INotificationService notificationService, |
|
INavigationService<MainWindowViewModel> navigationService, |
|
INavigationService<SettingsViewModel> settingsNavService |
|
) |
|
{ |
|
this.notificationService = notificationService; |
|
this.navigationService = navigationService; |
|
this.settingsNavService = settingsNavService; |
|
|
|
// Attach to the event |
|
trackedDownloadService.DownloadAdded += TrackedDownloadService_OnDownloadAdded; |
|
EventManager.Instance.ToggleProgressFlyout += (_, _) => IsOpen = !IsOpen; |
|
EventManager.Instance.PackageInstallProgressAdded += InstanceOnPackageInstallProgressAdded; |
|
EventManager.Instance.RecommendedModelsDialogClosed += InstanceOnRecommendedModelsDialogClosed; |
|
} |
|
|
|
private void InstanceOnRecommendedModelsDialogClosed(object? sender, EventArgs e) |
|
{ |
|
var vm = ProgressItems.OfType<PackageInstallProgressItemViewModel>().FirstOrDefault(); |
|
vm?.ShowProgressDialog().SafeFireAndForget(); |
|
} |
|
|
|
private void InstanceOnPackageInstallProgressAdded(object? sender, IPackageModificationRunner runner) |
|
{ |
|
AddPackageInstall(runner).SafeFireAndForget(); |
|
} |
|
|
|
private void TrackedDownloadService_OnDownloadAdded(object? sender, TrackedDownload e) |
|
{ |
|
// Attach notification handlers |
|
// Use Changing because Changed might be called after the download is removed |
|
e.ProgressStateChanged += (s, state) => |
|
{ |
|
Debug.WriteLine($"Download {e.FileName} state changed to {state}"); |
|
var download = s as TrackedDownload; |
|
|
|
switch (state) |
|
{ |
|
case ProgressState.Success: |
|
var imageFile = e.DownloadDirectory.EnumerateFiles( |
|
$"{Path.GetFileNameWithoutExtension(e.FileName)}.preview.*" |
|
) |
|
.FirstOrDefault(); |
|
|
|
notificationService |
|
.ShowAsync( |
|
NotificationKey.Download_Completed, |
|
new Notification |
|
{ |
|
Title = "Download Completed", |
|
Body = $"Download of {e.FileName} completed successfully.", |
|
BodyImagePath = imageFile?.FullPath |
|
} |
|
) |
|
.SafeFireAndForget(); |
|
|
|
break; |
|
case ProgressState.Failed: |
|
var msg = ""; |
|
if (download?.Exception is { } exception) |
|
{ |
|
if ( |
|
exception is UnauthorizedAccessException |
|
|| exception.InnerException is UnauthorizedAccessException |
|
) |
|
{ |
|
Dispatcher.UIThread.InvokeAsync(async () => |
|
{ |
|
var errorDialog = new BetterContentDialog |
|
{ |
|
Title = Resources.Label_DownloadFailed, |
|
Content = Resources.Label_CivitAiLoginRequired, |
|
PrimaryButtonText = "Go to Settings", |
|
SecondaryButtonText = "Close", |
|
DefaultButton = ContentDialogButton.Primary, |
|
}; |
|
|
|
var result = await errorDialog.ShowAsync(); |
|
if (result == ContentDialogResult.Primary) |
|
{ |
|
navigationService.NavigateTo<SettingsViewModel>( |
|
new SuppressNavigationTransitionInfo() |
|
); |
|
await Task.Delay(100); |
|
settingsNavService.NavigateTo<AccountSettingsViewModel>( |
|
new SuppressNavigationTransitionInfo() |
|
); |
|
} |
|
}); |
|
|
|
return; |
|
} |
|
|
|
msg = |
|
$"({exception.GetType().Name}) {exception.InnerException?.Message ?? exception.Message}"; |
|
} |
|
|
|
notificationService |
|
.ShowAsync( |
|
NotificationKey.Download_Failed, |
|
new Notification |
|
{ |
|
Title = "Download Failed", |
|
Body = $"Download of {e.FileName} failed: {msg}" |
|
} |
|
) |
|
.SafeFireAndForget(); |
|
break; |
|
case ProgressState.Cancelled: |
|
notificationService |
|
.ShowAsync( |
|
NotificationKey.Download_Canceled, |
|
new Notification |
|
{ |
|
Title = "Download Cancelled", |
|
Body = $"Download of {e.FileName} was cancelled." |
|
} |
|
) |
|
.SafeFireAndForget(); |
|
break; |
|
} |
|
}; |
|
|
|
var vm = new DownloadProgressItemViewModel(e); |
|
|
|
ProgressItems.Add(vm); |
|
} |
|
|
|
public void AddDownloads(IEnumerable<TrackedDownload> downloads) |
|
{ |
|
foreach (var download in downloads) |
|
{ |
|
if (ProgressItems.Any(vm => vm.Id == download.Id)) |
|
{ |
|
continue; |
|
} |
|
var vm = new DownloadProgressItemViewModel(download); |
|
ProgressItems.Add(vm); |
|
} |
|
} |
|
|
|
private Task AddPackageInstall(IPackageModificationRunner packageModificationRunner) |
|
{ |
|
if (ProgressItems.Any(vm => vm.Id == packageModificationRunner.Id)) |
|
{ |
|
return Task.CompletedTask; |
|
} |
|
|
|
var vm = new PackageInstallProgressItemViewModel(packageModificationRunner); |
|
ProgressItems.Add(vm); |
|
|
|
return packageModificationRunner.ShowDialogOnStart ? vm.ShowProgressDialog() : Task.CompletedTask; |
|
} |
|
|
|
private void ShowFailedNotification(string title, string message) |
|
{ |
|
notificationService.ShowPersistent(title, message, NotificationType.Error); |
|
} |
|
|
|
public void StartEventListener() |
|
{ |
|
EventManager.Instance.ProgressChanged += OnProgressChanged; |
|
} |
|
|
|
public void ClearDownloads() |
|
{ |
|
ProgressItems.RemoveAll(ProgressItems.Where(x => x.IsCompleted)); |
|
} |
|
|
|
private void OnProgressChanged(object? sender, ProgressItem e) |
|
{ |
|
if (ProgressItems.Any(x => x.Id == e.ProgressId)) |
|
return; |
|
|
|
ProgressItems.Add(new ProgressItemViewModel(e)); |
|
} |
|
}
|
|
|