Multi-Platform Package Manager for Stable Diffusion
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.

231 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);
}
}
10 months ago
private Task AddPackageInstall(IPackageModificationRunner packageModificationRunner)
{
if (ProgressItems.Any(vm => vm.Id == packageModificationRunner.Id))
{
return Task.CompletedTask;
}
10 months ago
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));
}
}