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.
 
 
 

355 lines
11 KiB

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Notifications;
using CommunityToolkit.Mvvm.ComponentModel;
using FluentAvalonia.UI.Controls;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Animations;
using StabilityMatrix.Avalonia.Extensions;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.Dialogs;
using StabilityMatrix.Avalonia.Views.Dialogs;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Factory;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Packages;
using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Avalonia.ViewModels.PackageManager;
public partial class PackageCardViewModel : ProgressViewModel
{
private readonly ILogger<PackageCardViewModel> logger;
private readonly IPackageFactory packageFactory;
private readonly INotificationService notificationService;
private readonly ISettingsManager settingsManager;
private readonly INavigationService navigationService;
private readonly ServiceManager<ViewModelBase> vmFactory;
[ObservableProperty]
private InstalledPackage? package;
[ObservableProperty]
private string? cardImageSource;
[ObservableProperty]
private bool isUpdateAvailable;
[ObservableProperty]
private string? installedVersion;
[ObservableProperty]
private bool isUnknownPackage;
public PackageCardViewModel(
ILogger<PackageCardViewModel> logger,
IPackageFactory packageFactory,
INotificationService notificationService,
ISettingsManager settingsManager,
INavigationService navigationService,
ServiceManager<ViewModelBase> vmFactory
)
{
this.logger = logger;
this.packageFactory = packageFactory;
this.notificationService = notificationService;
this.settingsManager = settingsManager;
this.navigationService = navigationService;
this.vmFactory = vmFactory;
}
partial void OnPackageChanged(InstalledPackage? value)
{
if (string.IsNullOrWhiteSpace(value?.PackageName))
return;
if (value.PackageName == UnknownPackage.Key)
{
IsUnknownPackage = true;
CardImageSource = "";
InstalledVersion = "Unknown";
}
else
{
IsUnknownPackage = false;
var basePackage = packageFactory[value.PackageName];
CardImageSource = basePackage?.PreviewImageUri.ToString() ?? Assets.NoImage.ToString();
InstalledVersion = value.Version?.DisplayVersion ?? "Unknown";
}
}
public override async Task OnLoadedAsync()
{
IsUpdateAvailable = await HasUpdate();
}
public void Launch()
{
if (Package == null)
return;
settingsManager.Transaction(s => s.ActiveInstalledPackageId = Package.Id);
navigationService.NavigateTo<LaunchPageViewModel>(new BetterDrillInNavigationTransition());
EventManager.Instance.OnPackageLaunchRequested(Package.Id);
}
public async Task Uninstall()
{
if (Package?.LibraryPath == null)
{
return;
}
var dialog = new ContentDialog
{
Title = "Are you sure?",
Content =
"This will delete all folders in the package directory, including any generated images in that directory as well as any files you may have added.",
PrimaryButtonText = "Yes, delete it",
CloseButtonText = "No, keep it",
DefaultButton = ContentDialogButton.Primary
};
var result = await dialog.ShowAsync();
if (result == ContentDialogResult.Primary)
{
Text = "Uninstalling...";
IsIndeterminate = true;
Value = -1;
var packagePath = new DirectoryPath(settingsManager.LibraryDir, Package.LibraryPath);
var deleteTask = packagePath.DeleteVerboseAsync(logger);
var taskResult = await notificationService.TryAsync(
deleteTask,
"Some files could not be deleted. Please close any open files in the package directory and try again."
);
if (taskResult.IsSuccessful)
{
notificationService.Show(
new Notification(
"Success",
$"Package {Package.DisplayName} uninstalled",
NotificationType.Success
)
);
if (!IsUnknownPackage)
{
settingsManager.Transaction(settings =>
{
settings.RemoveInstalledPackageAndUpdateActive(Package);
});
}
EventManager.Instance.OnInstalledPackagesChanged();
}
}
}
public async Task Update()
{
if (Package is null || IsUnknownPackage)
return;
var basePackage = packageFactory[Package.PackageName!];
if (basePackage == null)
{
logger.LogWarning(
"Could not find package {SelectedPackagePackageName}",
Package.PackageName
);
notificationService.Show(
"Invalid Package type",
$"Package {Package.PackageName.ToRepr()} is not a valid package type",
NotificationType.Error
);
return;
}
var packageName = Package.DisplayName ?? Package.PackageName ?? "";
Text = $"Updating {packageName}";
IsIndeterminate = true;
var progressId = Guid.NewGuid();
EventManager.Instance.OnProgressChanged(
new ProgressItem(
progressId,
Package.DisplayName ?? Package.PackageName!,
new ProgressReport(0f, isIndeterminate: true, type: ProgressType.Update)
)
);
try
{
var progress = new Progress<ProgressReport>(progress =>
{
var percent = Convert.ToInt32(progress.Percentage);
Value = percent;
IsIndeterminate = progress.IsIndeterminate;
Text = $"Updating {Package.DisplayName}";
EventManager.Instance.OnGlobalProgressChanged(percent);
EventManager.Instance.OnProgressChanged(
new ProgressItem(progressId, packageName, progress)
);
});
var torchVersion =
Package.PreferredTorchVersion ?? basePackage.GetRecommendedTorchVersion();
var updateResult = await basePackage.Update(Package, torchVersion, progress);
settingsManager.UpdatePackageVersionNumber(Package.Id, updateResult);
notificationService.Show(
"Update complete",
$"{Package.DisplayName} has been updated to the latest version.",
NotificationType.Success
);
await using (settingsManager.BeginTransaction())
{
Package.UpdateAvailable = false;
}
IsUpdateAvailable = false;
InstalledVersion = updateResult.DisplayVersion ?? "Unknown";
EventManager.Instance.OnProgressChanged(
new ProgressItem(
progressId,
packageName,
new ProgressReport(1f, "Update complete", type: ProgressType.Update)
)
);
}
catch (Exception e)
{
logger.LogError(e, "Error Updating Package ({PackageName})", basePackage.Name);
notificationService.ShowPersistent(
$"Error Updating {Package.DisplayName}",
e.Message,
NotificationType.Error
);
EventManager.Instance.OnProgressChanged(
new ProgressItem(
progressId,
packageName,
new ProgressReport(0f, "Update failed", type: ProgressType.Update),
Failed: true
)
);
}
finally
{
IsIndeterminate = false;
Value = 0;
Text = "";
}
}
public async Task Import()
{
if (!IsUnknownPackage || Design.IsDesignMode)
return;
var viewModel = vmFactory.Get<PackageImportViewModel>(vm =>
{
vm.PackagePath = new DirectoryPath(
Package?.FullPath ?? throw new InvalidOperationException()
);
});
var dialog = new TaskDialog
{
Content = new PackageImportDialog { DataContext = viewModel },
ShowProgressBar = false,
Buttons = new List<TaskDialogButton>
{
new(Resources.Action_Import, TaskDialogStandardResult.Yes) { IsDefault = true },
new(Resources.Action_Cancel, TaskDialogStandardResult.Cancel)
}
};
dialog.Closing += async (sender, e) =>
{
// We only want to use the deferral on the 'Yes' Button
if ((TaskDialogStandardResult)e.Result == TaskDialogStandardResult.Yes)
{
var deferral = e.GetDeferral();
sender.ShowProgressBar = true;
sender.SetProgressBarState(0, TaskDialogProgressState.Indeterminate);
await using (new MinimumDelay(200, 300))
{
var result = await notificationService.TryAsync(
viewModel.AddPackageWithCurrentInputs()
);
if (result.IsSuccessful)
{
EventManager.Instance.OnInstalledPackagesChanged();
}
}
deferral.Complete();
}
};
dialog.XamlRoot = App.VisualRoot;
await dialog.ShowAsync(true);
}
public async Task OpenFolder()
{
if (string.IsNullOrWhiteSpace(Package?.FullPath))
return;
await ProcessRunner.OpenFolderBrowser(Package.FullPath);
}
private async Task<bool> HasUpdate()
{
if (Package == null || IsUnknownPackage || Design.IsDesignMode)
return false;
var basePackage = packageFactory[Package.PackageName!];
if (basePackage == null)
return false;
var canCheckUpdate =
Package.LastUpdateCheck == null
|| Package.LastUpdateCheck < DateTime.Now.AddMinutes(-15);
if (!canCheckUpdate)
{
return Package.UpdateAvailable;
}
try
{
var hasUpdate = await basePackage.CheckForUpdates(Package);
Package.UpdateAvailable = hasUpdate;
Package.LastUpdateCheck = DateTimeOffset.Now;
settingsManager.SetLastUpdateCheck(Package);
return hasUpdate;
}
catch (Exception e)
{
logger.LogError(e, "Error checking {PackageName} for updates", Package.PackageName);
return false;
}
}
}