JT
1 year ago
20 changed files with 953 additions and 160 deletions
@ -0,0 +1,91 @@
|
||||
using System; |
||||
using System.Diagnostics.CodeAnalysis; |
||||
using System.IO; |
||||
using System.Threading.Tasks; |
||||
using Microsoft.Extensions.Logging; |
||||
using Polly; |
||||
using StabilityMatrix.Core.Models.FileInterfaces; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Extensions; |
||||
|
||||
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] |
||||
public static class DirectoryPathExtensions |
||||
{ |
||||
/// <summary> |
||||
/// Deletes a directory and all of its contents recursively. |
||||
/// Uses Polly to retry the deletion if it fails, up to 5 times with an exponential backoff. |
||||
/// </summary> |
||||
public static Task DeleteVerboseAsync(this DirectoryPath directory, ILogger? logger = default) |
||||
{ |
||||
var policy = Policy.Handle<IOException>() |
||||
.WaitAndRetryAsync(3, attempt => TimeSpan.FromMilliseconds(50 * Math.Pow(2, attempt)), |
||||
onRetry: (exception, calculatedWaitDuration) => |
||||
{ |
||||
logger?.LogWarning( |
||||
exception, |
||||
"Deletion of {TargetDirectory} failed. Retrying in {CalculatedWaitDuration}", |
||||
directory, calculatedWaitDuration); |
||||
}); |
||||
|
||||
return policy.ExecuteAsync(async () => |
||||
{ |
||||
await Task.Run(() => { DeleteVerbose(directory, logger); }); |
||||
}); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Deletes a directory and all of its contents recursively. |
||||
/// Removes link targets without deleting the source. |
||||
/// </summary> |
||||
public static void DeleteVerbose(this DirectoryPath directory, ILogger? logger = default) |
||||
{ |
||||
// Skip if directory does not exist |
||||
if (!directory.Exists) |
||||
{ |
||||
return; |
||||
} |
||||
// For junction points, delete with recursive false |
||||
if (directory.IsSymbolicLink) |
||||
{ |
||||
logger?.LogInformation("Removing junction point {TargetDirectory}", directory); |
||||
try |
||||
{ |
||||
directory.Delete(false); |
||||
return; |
||||
} |
||||
catch (IOException ex) |
||||
{ |
||||
throw new IOException($"Failed to delete junction point {directory}", ex); |
||||
} |
||||
} |
||||
// Recursively delete all subdirectories |
||||
foreach (var subDir in directory.Info.EnumerateDirectories()) |
||||
{ |
||||
DeleteVerbose(subDir, logger); |
||||
} |
||||
|
||||
// Delete all files in the directory |
||||
foreach (var filePath in directory.Info.EnumerateFiles()) |
||||
{ |
||||
try |
||||
{ |
||||
filePath.Attributes = FileAttributes.Normal; |
||||
filePath.Delete(); |
||||
} |
||||
catch (IOException ex) |
||||
{ |
||||
throw new IOException($"Failed to delete file {filePath.FullName}", ex); |
||||
} |
||||
} |
||||
|
||||
// Delete this directory |
||||
try |
||||
{ |
||||
directory.Delete(false); |
||||
} |
||||
catch (IOException ex) |
||||
{ |
||||
throw new IOException($"Failed to delete directory {directory}", ex); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,221 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Collections.Immutable; |
||||
using System.Collections.ObjectModel; |
||||
using System.IO; |
||||
using System.Linq; |
||||
using System.Threading.Tasks; |
||||
using AsyncAwaitBestPractices; |
||||
using Avalonia.Controls; |
||||
using Avalonia.Threading; |
||||
using CommunityToolkit.Mvvm.ComponentModel; |
||||
using NLog; |
||||
using StabilityMatrix.Avalonia.Views.Dialogs; |
||||
using StabilityMatrix.Core.Attributes; |
||||
using StabilityMatrix.Core.Helper.Factory; |
||||
using StabilityMatrix.Core.Models; |
||||
using StabilityMatrix.Core.Models.Database; |
||||
using StabilityMatrix.Core.Models.FileInterfaces; |
||||
using StabilityMatrix.Core.Models.Packages; |
||||
using StabilityMatrix.Core.Services; |
||||
|
||||
namespace StabilityMatrix.Avalonia.ViewModels.Dialogs; |
||||
|
||||
[View(typeof(PackageImportDialog))] |
||||
public partial class PackageImportViewModel : ContentDialogViewModelBase |
||||
{ |
||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); |
||||
|
||||
private readonly IPackageFactory packageFactory; |
||||
private readonly ISettingsManager settingsManager; |
||||
|
||||
[ObservableProperty] private DirectoryPath? packagePath; |
||||
[ObservableProperty] private BasePackage? selectedBasePackage; |
||||
|
||||
public IReadOnlyList<BasePackage> AvailablePackages |
||||
=> packageFactory.GetAllAvailablePackages().ToImmutableArray(); |
||||
|
||||
[ObservableProperty] private PackageVersion? selectedVersion; |
||||
|
||||
[ObservableProperty] private ObservableCollection<GitCommit>? availableCommits; |
||||
[ObservableProperty] private ObservableCollection<PackageVersion>? availableVersions; |
||||
|
||||
[ObservableProperty] private GitCommit? selectedCommit; |
||||
|
||||
// Version types (release or commit) |
||||
[ObservableProperty] |
||||
[NotifyPropertyChangedFor(nameof(ReleaseLabelText), |
||||
nameof(IsReleaseMode), nameof(SelectedVersion))] |
||||
private PackageVersionType selectedVersionType = PackageVersionType.Commit; |
||||
|
||||
[ObservableProperty] |
||||
[NotifyPropertyChangedFor(nameof(IsReleaseModeAvailable))] |
||||
private PackageVersionType availableVersionTypes = |
||||
PackageVersionType.GithubRelease | PackageVersionType.Commit; |
||||
public string ReleaseLabelText => IsReleaseMode ? "Version" : "Branch"; |
||||
public bool IsReleaseMode |
||||
{ |
||||
get => SelectedVersionType == PackageVersionType.GithubRelease; |
||||
set => SelectedVersionType = value ? PackageVersionType.GithubRelease : PackageVersionType.Commit; |
||||
} |
||||
|
||||
public bool IsReleaseModeAvailable => AvailableVersionTypes.HasFlag(PackageVersionType.GithubRelease); |
||||
|
||||
public PackageImportViewModel( |
||||
IPackageFactory packageFactory, |
||||
ISettingsManager settingsManager) |
||||
{ |
||||
this.packageFactory = packageFactory; |
||||
this.settingsManager = settingsManager; |
||||
} |
||||
|
||||
public override async Task OnLoadedAsync() |
||||
{ |
||||
SelectedBasePackage ??= AvailablePackages[0]; |
||||
|
||||
if (Design.IsDesignMode) return; |
||||
// Populate available versions |
||||
try |
||||
{ |
||||
if (IsReleaseMode) |
||||
{ |
||||
var versions = (await SelectedBasePackage.GetAllVersions()).ToList(); |
||||
AvailableVersions = new ObservableCollection<PackageVersion>(versions); |
||||
if (!AvailableVersions.Any()) return; |
||||
|
||||
SelectedVersion = AvailableVersions[0]; |
||||
} |
||||
else |
||||
{ |
||||
var branches = (await SelectedBasePackage.GetAllBranches()).ToList(); |
||||
AvailableVersions = new ObservableCollection<PackageVersion>(branches.Select(b => |
||||
new PackageVersion |
||||
{ |
||||
TagName = b.Name, |
||||
ReleaseNotesMarkdown = b.Commit.Label |
||||
})); |
||||
UpdateSelectedVersionToLatestMain(); |
||||
} |
||||
} |
||||
catch (Exception e) |
||||
{ |
||||
Logger.Warn("Error getting versions: {Exception}", e.ToString()); |
||||
} |
||||
} |
||||
|
||||
private static string GetDisplayVersion(string version, string? branch) |
||||
{ |
||||
return branch == null ? version : $"{branch}@{version[..7]}"; |
||||
} |
||||
|
||||
// When available version types change, reset selected version type if not compatible |
||||
partial void OnAvailableVersionTypesChanged(PackageVersionType value) |
||||
{ |
||||
if (!value.HasFlag(SelectedVersionType)) |
||||
{ |
||||
SelectedVersionType = value; |
||||
} |
||||
} |
||||
|
||||
// When changing branch / release modes, refresh |
||||
// ReSharper disable once UnusedParameterInPartialMethod |
||||
partial void OnSelectedVersionTypeChanged(PackageVersionType value) |
||||
=> OnSelectedBasePackageChanged(SelectedBasePackage); |
||||
|
||||
partial void OnSelectedBasePackageChanged(BasePackage? value) |
||||
{ |
||||
if (value is null || SelectedBasePackage is null) |
||||
{ |
||||
AvailableVersions?.Clear(); |
||||
AvailableCommits?.Clear(); |
||||
return; |
||||
} |
||||
|
||||
AvailableVersions?.Clear(); |
||||
AvailableCommits?.Clear(); |
||||
|
||||
AvailableVersionTypes = SelectedBasePackage.ShouldIgnoreReleases |
||||
? PackageVersionType.Commit |
||||
: PackageVersionType.GithubRelease | PackageVersionType.Commit; |
||||
|
||||
if (Design.IsDesignMode) return; |
||||
|
||||
Dispatcher.UIThread.InvokeAsync(async () => |
||||
{ |
||||
Logger.Debug($"Release mode: {IsReleaseMode}"); |
||||
var versions = (await value.GetAllVersions(IsReleaseMode)).ToList(); |
||||
|
||||
if (!versions.Any()) return; |
||||
|
||||
AvailableVersions = new ObservableCollection<PackageVersion>(versions); |
||||
Logger.Debug($"Available versions: {string.Join(", ", AvailableVersions)}"); |
||||
SelectedVersion = AvailableVersions[0]; |
||||
|
||||
if (!IsReleaseMode) |
||||
{ |
||||
var commits = (await value.GetAllCommits(SelectedVersion.TagName))?.ToList(); |
||||
if (commits is null || commits.Count == 0) return; |
||||
|
||||
AvailableCommits = new ObservableCollection<GitCommit>(commits); |
||||
SelectedCommit = AvailableCommits[0]; |
||||
UpdateSelectedVersionToLatestMain(); |
||||
} |
||||
}).SafeFireAndForget(); |
||||
} |
||||
|
||||
private void UpdateSelectedVersionToLatestMain() |
||||
{ |
||||
if (AvailableVersions is null) |
||||
{ |
||||
SelectedVersion = null; |
||||
} |
||||
else |
||||
{ |
||||
// First try to find master |
||||
var version = AvailableVersions.FirstOrDefault(x => x.TagName == "master"); |
||||
// If not found, try main |
||||
version ??= AvailableVersions.FirstOrDefault(x => x.TagName == "main"); |
||||
|
||||
// If still not found, just use the first one |
||||
version ??= AvailableVersions[0]; |
||||
|
||||
SelectedVersion = version; |
||||
} |
||||
} |
||||
|
||||
public void AddPackageWithCurrentInputs() |
||||
{ |
||||
if (SelectedBasePackage is null || PackagePath is null) |
||||
return; |
||||
|
||||
string version; |
||||
if (IsReleaseMode) |
||||
{ |
||||
version = SelectedVersion?.TagName ?? |
||||
throw new NullReferenceException("Selected version is null"); |
||||
} |
||||
else |
||||
{ |
||||
version = SelectedCommit?.Sha ?? |
||||
throw new NullReferenceException("Selected commit is null"); |
||||
} |
||||
|
||||
var branch = SelectedVersionType == PackageVersionType.GithubRelease ? |
||||
null : SelectedVersion!.TagName; |
||||
|
||||
var package = new InstalledPackage |
||||
{ |
||||
Id = Guid.NewGuid(), |
||||
DisplayName = PackagePath.Name, |
||||
PackageName = SelectedBasePackage.Name, |
||||
LibraryPath = $"Packages{Path.DirectorySeparatorChar}{PackagePath.Name}", |
||||
PackageVersion = version, |
||||
DisplayVersion = GetDisplayVersion(version, branch), |
||||
InstalledBranch = branch, |
||||
LaunchCommand = SelectedBasePackage.LaunchCommand, |
||||
LastUpdateCheck = DateTimeOffset.Now, |
||||
}; |
||||
|
||||
settingsManager.Transaction(s => s.InstalledPackages.Add(package)); |
||||
} |
||||
} |
@ -0,0 +1,51 @@
|
||||
<controls:UserControlBase xmlns="https://github.com/avaloniaui" |
||||
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:ui="using:FluentAvalonia.UI.Controls" |
||||
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData" |
||||
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls" |
||||
xmlns:vmDialogs="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs" |
||||
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" |
||||
d:DataContext="{x:Static mocks:DesignData.PackageImportViewModel}" |
||||
x:DataType="vmDialogs:PackageImportViewModel" |
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
||||
x:Class="StabilityMatrix.Avalonia.Views.Dialogs.PackageImportDialog"> |
||||
<StackPanel Margin="8" Spacing="8"> |
||||
<ui:SettingsExpander Header="{x:Static lang:Resources.Label_PackageType}"> |
||||
<ui:SettingsExpander.Footer> |
||||
<ui:FAComboBox |
||||
HorizontalAlignment="Stretch" |
||||
ItemsSource="{Binding AvailablePackages}" |
||||
DisplayMemberBinding="{Binding DisplayName}" |
||||
SelectedItem="{Binding SelectedBasePackage}"/> |
||||
</ui:SettingsExpander.Footer> |
||||
</ui:SettingsExpander> |
||||
|
||||
<ui:SettingsExpander Header="{x:Static lang:Resources.Label_VersionType}"> |
||||
<ui:SettingsExpander.Footer> |
||||
<ToggleSwitch |
||||
IsEnabled="{Binding IsReleaseModeAvailable}" |
||||
OnContent="{x:Static lang:Resources.Label_Releases}" |
||||
OffContent="{x:Static lang:Resources.Label_Branches}" |
||||
IsChecked="{Binding IsReleaseMode}"/> |
||||
</ui:SettingsExpander.Footer> |
||||
</ui:SettingsExpander> |
||||
|
||||
<ui:SettingsExpander Header="{x:Static lang:Resources.Label_Version}"> |
||||
<ui:SettingsExpander.Footer> |
||||
<StackPanel Orientation="Horizontal" Spacing="8"> |
||||
<ui:FAComboBox |
||||
ItemsSource="{Binding AvailableVersions}" |
||||
DisplayMemberBinding="{Binding TagName}" |
||||
SelectedItem="{Binding SelectedVersion}"/> |
||||
<ui:FAComboBox |
||||
IsVisible="{Binding !IsReleaseMode}" |
||||
ItemsSource="{Binding AvailableCommits}" |
||||
DisplayMemberBinding="{Binding Sha}" |
||||
SelectedItem="{Binding SelectedCommit}"/> |
||||
</StackPanel> |
||||
</ui:SettingsExpander.Footer> |
||||
</ui:SettingsExpander> |
||||
</StackPanel> |
||||
</controls:UserControlBase> |
@ -0,0 +1,17 @@
|
||||
using Avalonia.Markup.Xaml; |
||||
using StabilityMatrix.Avalonia.Controls; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Views.Dialogs; |
||||
|
||||
public partial class PackageImportDialog : UserControlBase |
||||
{ |
||||
public PackageImportDialog() |
||||
{ |
||||
InitializeComponent(); |
||||
} |
||||
|
||||
private void InitializeComponent() |
||||
{ |
||||
AvaloniaXamlLoader.Load(this); |
||||
} |
||||
} |
@ -0,0 +1,24 @@
|
||||
namespace StabilityMatrix.Core.Helper; |
||||
|
||||
public class PropertyComparer<T> : IEqualityComparer<T> where T : class
|
||||
{ |
||||
private Func<T, object> Expr { get; set; } |
||||
|
||||
public PropertyComparer(Func<T, object> expr) |
||||
{ |
||||
Expr = expr; |
||||
} |
||||
public bool Equals(T? x, T? y) |
||||
{ |
||||
if (x == null || y == null) return false; |
||||
|
||||
var first = Expr.Invoke(x); |
||||
var second = Expr.Invoke(y); |
||||
|
||||
return first.Equals(second); |
||||
} |
||||
public int GetHashCode(T obj) |
||||
{ |
||||
return obj.GetHashCode(); |
||||
} |
||||
} |
@ -0,0 +1,105 @@
|
||||
using Octokit; |
||||
using StabilityMatrix.Core.Models.Database; |
||||
using StabilityMatrix.Core.Models.FileInterfaces; |
||||
using StabilityMatrix.Core.Models.Progress; |
||||
|
||||
namespace StabilityMatrix.Core.Models.Packages; |
||||
|
||||
public class UnknownPackage : BasePackage |
||||
{ |
||||
public static string Key => "unknown-package"; |
||||
public override string Name => Key; |
||||
public override string DisplayName { get; set; } = "Unknown Package"; |
||||
public override string Author => ""; |
||||
|
||||
public override string GithubUrl => ""; |
||||
public override string LicenseType => "AGPL-3.0"; |
||||
public override string LicenseUrl => |
||||
"https://github.com/LykosAI/StabilityMatrix/blob/main/LICENSE"; |
||||
public override string Blurb => "A dank interface for diffusion"; |
||||
public override string LaunchCommand => "test"; |
||||
|
||||
public override Uri PreviewImageUri => new(""); |
||||
|
||||
public override IReadOnlyList<string> ExtraLaunchCommands => new[] |
||||
{ |
||||
"test-config", |
||||
}; |
||||
|
||||
/// <inheritdoc /> |
||||
public override Task<string> DownloadPackage(string version, bool isCommitHash, string? branch, IProgress<ProgressReport>? progress = null) |
||||
{ |
||||
throw new NotImplementedException(); |
||||
} |
||||
|
||||
/// <inheritdoc /> |
||||
public override Task InstallPackage(IProgress<ProgressReport>? progress = null) |
||||
{ |
||||
throw new NotImplementedException(); |
||||
} |
||||
|
||||
public override Task RunPackage(string installedPackagePath, string command, string arguments) |
||||
{ |
||||
throw new NotImplementedException(); |
||||
} |
||||
|
||||
/// <inheritdoc /> |
||||
public override Task SetupModelFolders(DirectoryPath installDirectory) |
||||
{ |
||||
throw new NotImplementedException(); |
||||
} |
||||
|
||||
/// <inheritdoc /> |
||||
public override Task UpdateModelFolders(DirectoryPath installDirectory) |
||||
{ |
||||
throw new NotImplementedException(); |
||||
} |
||||
|
||||
/// <inheritdoc /> |
||||
public override Task RemoveModelFolderLinks(DirectoryPath installDirectory) |
||||
{ |
||||
throw new NotImplementedException(); |
||||
} |
||||
|
||||
/// <inheritdoc /> |
||||
public override void Shutdown() |
||||
{ |
||||
throw new NotImplementedException(); |
||||
} |
||||
|
||||
/// <inheritdoc /> |
||||
public override Task WaitForShutdown() |
||||
{ |
||||
throw new NotImplementedException(); |
||||
} |
||||
|
||||
/// <inheritdoc /> |
||||
public override Task<bool> CheckForUpdates(InstalledPackage package) |
||||
{ |
||||
throw new NotImplementedException(); |
||||
} |
||||
|
||||
/// <inheritdoc /> |
||||
public override Task<string> Update(InstalledPackage installedPackage, IProgress<ProgressReport>? progress = null, |
||||
bool includePrerelease = false) |
||||
{ |
||||
throw new NotImplementedException(); |
||||
} |
||||
|
||||
/// <inheritdoc /> |
||||
public override Task<IEnumerable<Release>> GetReleaseTags() => Task.FromResult(Enumerable.Empty<Release>()); |
||||
|
||||
public override List<LaunchOptionDefinition> LaunchOptions => new(); |
||||
public override Task<string> GetLatestVersion() => Task.FromResult(string.Empty); |
||||
|
||||
public override Task<IEnumerable<PackageVersion>> GetAllVersions(bool isReleaseMode = true) => Task.FromResult(Enumerable.Empty<PackageVersion>()); |
||||
|
||||
/// <inheritdoc /> |
||||
public override Task<IEnumerable<GitCommit>?> GetAllCommits(string branch, int page = 1, int perPage = 10) => Task.FromResult<IEnumerable<GitCommit>?>(null); |
||||
|
||||
/// <inheritdoc /> |
||||
public override Task<IEnumerable<Branch>> GetAllBranches() => Task.FromResult(Enumerable.Empty<Branch>()); |
||||
|
||||
/// <inheritdoc /> |
||||
public override Task<IEnumerable<Release>> GetAllReleases() => Task.FromResult(Enumerable.Empty<Release>()); |
||||
} |
@ -0,0 +1,17 @@
|
||||
using StabilityMatrix.Core.Models.Packages; |
||||
|
||||
namespace StabilityMatrix.Core.Models; |
||||
|
||||
public class UnknownInstalledPackage : InstalledPackage |
||||
{ |
||||
public static UnknownInstalledPackage FromDirectoryName(string name) |
||||
{ |
||||
return new UnknownInstalledPackage |
||||
{ |
||||
Id = Guid.NewGuid(), |
||||
PackageName = UnknownPackage.Key, |
||||
DisplayName = name, |
||||
LibraryPath = $"Packages{System.IO.Path.DirectorySeparatorChar}{name}", |
||||
}; |
||||
} |
||||
} |
Loading…
Reference in new issue