diff --git a/CHANGELOG.md b/CHANGELOG.md index 7216ffaf..f2f2c665 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2 - Added [Stable Diffusion WebUI/UX](https://github.com/anapnoe/stable-diffusion-webui-ux) package ### Changed - If ComfyUI for Inference is chosen during the One-Click Installer, the Inference page will be opened after installation instead of the Launch page +- Changed all package installs & updates to use git commands instead of downloading zip files ### Fixed - Fixed crash when clicking Inference gallery image after the image is deleted externally in file explorer - Fixed Inference popup Install button not working on One-Click Installer diff --git a/StabilityMatrix.Avalonia/App.axaml.cs b/StabilityMatrix.Avalonia/App.axaml.cs index 25f8a52b..05556478 100644 --- a/StabilityMatrix.Avalonia/App.axaml.cs +++ b/StabilityMatrix.Avalonia/App.axaml.cs @@ -434,12 +434,12 @@ public sealed class App : Application { services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); } private static IServiceCollection ConfigureServices() diff --git a/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs b/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs index 07a38fb9..eee43c2c 100644 --- a/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs +++ b/StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs @@ -126,7 +126,11 @@ public class UnixPrerequisiteHelper : IPrerequisiteHelper } } - public async Task RunGit(string? workingDirectory = null, params string[] args) + public async Task RunGit( + string? workingDirectory = null, + Action? onProcessOutput = null, + params string[] args + ) { var command = args.Length == 0 ? "git" : "git " + string.Join(" ", args.Select(ProcessRunner.Quote)); diff --git a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs b/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs index e5ee642f..ad77d03a 100644 --- a/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs +++ b/StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs @@ -17,70 +17,82 @@ namespace StabilityMatrix.Avalonia.Helpers; public class WindowsPrerequisiteHelper : IPrerequisiteHelper { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - + private readonly IGitHubClient gitHubClient; private readonly IDownloadService downloadService; private readonly ISettingsManager settingsManager; - + private const string VcRedistDownloadUrl = "https://aka.ms/vs/16/release/vc_redist.x64.exe"; - + private string HomeDir => settingsManager.LibraryDir; - + private string VcRedistDownloadPath => Path.Combine(HomeDir, "vcredist.x64.exe"); private string AssetsDir => Path.Combine(HomeDir, "Assets"); private string SevenZipPath => Path.Combine(AssetsDir, "7za.exe"); - + private string PythonDownloadPath => Path.Combine(AssetsDir, "python-3.10.11-embed-amd64.zip"); private string PythonDir => Path.Combine(AssetsDir, "Python310"); private string PythonDllPath => Path.Combine(PythonDir, "python310.dll"); private string PythonLibraryZipPath => Path.Combine(PythonDir, "python310.zip"); private string GetPipPath => Path.Combine(PythonDir, "get-pip.pyc"); + // Temporary directory to extract venv to during python install private string VenvTempDir => Path.Combine(PythonDir, "venv"); - + private string PortableGitInstallDir => Path.Combine(HomeDir, "PortableGit"); private string PortableGitDownloadPath => Path.Combine(HomeDir, "PortableGit.7z.exe"); private string GitExePath => Path.Combine(PortableGitInstallDir, "bin", "git.exe"); public string GitBinPath => Path.Combine(PortableGitInstallDir, "bin"); - + public bool IsPythonInstalled => File.Exists(PythonDllPath); public WindowsPrerequisiteHelper( IGitHubClient gitHubClient, - IDownloadService downloadService, - ISettingsManager settingsManager) + IDownloadService downloadService, + ISettingsManager settingsManager + ) { this.gitHubClient = gitHubClient; this.downloadService = downloadService; this.settingsManager = settingsManager; } - public async Task RunGit(string? workingDirectory = null, params string[] args) + public async Task RunGit( + string? workingDirectory = null, + Action? onProcessOutput = null, + params string[] args + ) { - var process = ProcessRunner.StartAnsiProcess(GitExePath, args, + var process = ProcessRunner.StartAnsiProcess( + GitExePath, + args, workingDirectory: workingDirectory, environmentVariables: new Dictionary { - {"PATH", Compat.GetEnvPathWithExtensions(GitBinPath)} - }); - + { "PATH", Compat.GetEnvPathWithExtensions(GitBinPath) } + }, + outputDataReceived: onProcessOutput + ); + await ProcessRunner.WaitForExitConditionAsync(process); } public async Task GetGitOutput(string? workingDirectory = null, params string[] args) { var process = await ProcessRunner.GetProcessOutputAsync( - GitExePath, string.Join(" ", args), + GitExePath, + string.Join(" ", args), workingDirectory: workingDirectory, environmentVariables: new Dictionary { - {"PATH", Compat.GetEnvPathWithExtensions(GitBinPath)} - }); - + { "PATH", Compat.GetEnvPathWithExtensions(GitBinPath) } + } + ); + return process; } - + public async Task InstallAllIfNecessary(IProgress? progress = null) { await InstallVcRedistIfNecessary(progress); @@ -97,16 +109,20 @@ public class WindowsPrerequisiteHelper : IPrerequisiteHelper (Assets.SevenZipExecutable, AssetsDir), (Assets.SevenZipLicense, AssetsDir), }; - - progress?.Report(new ProgressReport(0, message: "Unpacking resources", isIndeterminate: true)); - + + progress?.Report( + new ProgressReport(0, message: "Unpacking resources", isIndeterminate: true) + ); + Directory.CreateDirectory(AssetsDir); foreach (var (asset, extractDir) in assets) { await asset.ExtractToDir(extractDir); } - - progress?.Report(new ProgressReport(1, message: "Unpacking resources", isIndeterminate: false)); + + progress?.Report( + new ProgressReport(1, message: "Unpacking resources", isIndeterminate: false) + ); } public async Task InstallPythonIfNecessary(IProgress? progress = null) @@ -120,7 +136,7 @@ public class WindowsPrerequisiteHelper : IPrerequisiteHelper Logger.Info("Python not found at {PythonDllPath}, downloading...", PythonDllPath); Directory.CreateDirectory(AssetsDir); - + // Delete existing python zip if it exists if (File.Exists(PythonLibraryZipPath)) { @@ -130,44 +146,45 @@ public class WindowsPrerequisiteHelper : IPrerequisiteHelper var remote = Assets.PythonDownloadUrl; var url = remote.Url.ToString(); Logger.Info($"Downloading Python from {url} to {PythonLibraryZipPath}"); - + // Cleanup to remove zip if download fails try { // Download python zip await downloadService.DownloadToFileAsync(url, PythonDownloadPath, progress: progress); - + // Verify python hash var downloadHash = await FileHash.GetSha256Async(PythonDownloadPath, progress); if (downloadHash != remote.HashSha256) { var fileExists = File.Exists(PythonDownloadPath); var fileSize = new FileInfo(PythonDownloadPath).Length; - var msg = $"Python download hash mismatch: {downloadHash} != {remote.HashSha256} " + - $"(file exists: {fileExists}, size: {fileSize})"; + var msg = + $"Python download hash mismatch: {downloadHash} != {remote.HashSha256} " + + $"(file exists: {fileExists}, size: {fileSize})"; throw new Exception(msg); } - + progress?.Report(new ProgressReport(progress: 1f, message: "Python download complete")); - + progress?.Report(new ProgressReport(-1, "Installing Python...", isIndeterminate: true)); - + // We also need 7z if it's not already unpacked if (!File.Exists(SevenZipPath)) { await Assets.SevenZipExecutable.ExtractToDir(AssetsDir); await Assets.SevenZipLicense.ExtractToDir(AssetsDir); } - + // Delete existing python dir if (Directory.Exists(PythonDir)) { Directory.Delete(PythonDir, true); } - + // Unzip python await ArchiveHelper.Extract7Z(PythonDownloadPath, PythonDir); - + try { // Extract embedded venv folder @@ -185,7 +202,7 @@ public class WindowsPrerequisiteHelper : IPrerequisiteHelper await resource.ExtractTo(path); } // Add venv to python's library zip - + await ArchiveHelper.AddToArchive7Z(PythonLibraryZipPath, VenvTempDir); } finally @@ -196,16 +213,16 @@ public class WindowsPrerequisiteHelper : IPrerequisiteHelper Directory.Delete(VenvTempDir, true); } } - + // Extract get-pip.pyc await Assets.PyScriptGetPip.ExtractToDir(PythonDir); - + // We need to uncomment the #import site line in python310._pth for pip to work var pythonPthPath = Path.Combine(PythonDir, "python310._pth"); var pythonPthContent = await File.ReadAllTextAsync(pythonPthPath); pythonPthContent = pythonPthContent.Replace("#import site", "import site"); await File.WriteAllTextAsync(pythonPthPath, pythonPthContent); - + progress?.Report(new ProgressReport(1f, "Python install complete")); } finally @@ -225,7 +242,7 @@ public class WindowsPrerequisiteHelper : IPrerequisiteHelper Logger.Debug("Git already installed at {GitExePath}", GitExePath); return; } - + Logger.Info("Git not found at {GitExePath}, downloading...", GitExePath); var portableGitUrl = @@ -233,7 +250,11 @@ public class WindowsPrerequisiteHelper : IPrerequisiteHelper if (!File.Exists(PortableGitDownloadPath)) { - await downloadService.DownloadToFileAsync(portableGitUrl, PortableGitDownloadPath, progress: progress); + await downloadService.DownloadToFileAsync( + portableGitUrl, + PortableGitDownloadPath, + progress: progress + ); progress?.Report(new ProgressReport(progress: 1f, message: "Git download complete")); } @@ -245,7 +266,9 @@ public class WindowsPrerequisiteHelper : IPrerequisiteHelper { var registry = Registry.LocalMachine; var key = registry.OpenSubKey( - @"SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\X64", false); + @"SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\X64", + false + ); if (key != null) { var buildId = Convert.ToUInt32(key.GetValue("Bld")); @@ -254,20 +277,44 @@ public class WindowsPrerequisiteHelper : IPrerequisiteHelper return; } } - + Logger.Info("Downloading VC Redist"); - await downloadService.DownloadToFileAsync(VcRedistDownloadUrl, VcRedistDownloadPath, progress: progress); - progress?.Report(new ProgressReport(progress: 1f, message: "Visual C++ download complete", - type: ProgressType.Download)); - + await downloadService.DownloadToFileAsync( + VcRedistDownloadUrl, + VcRedistDownloadPath, + progress: progress + ); + progress?.Report( + new ProgressReport( + progress: 1f, + message: "Visual C++ download complete", + type: ProgressType.Download + ) + ); + Logger.Info("Installing VC Redist"); - progress?.Report(new ProgressReport(progress: 0.5f, isIndeterminate: true, type: ProgressType.Generic, message: "Installing prerequisites...")); - var process = ProcessRunner.StartAnsiProcess(VcRedistDownloadPath, "/install /quiet /norestart"); + progress?.Report( + new ProgressReport( + progress: 0.5f, + isIndeterminate: true, + type: ProgressType.Generic, + message: "Installing prerequisites..." + ) + ); + var process = ProcessRunner.StartAnsiProcess( + VcRedistDownloadPath, + "/install /quiet /norestart" + ); await process.WaitForExitAsync(); - progress?.Report(new ProgressReport(progress: 1f, message: "Visual C++ install complete", - type: ProgressType.Generic)); - + progress?.Report( + new ProgressReport( + progress: 1f, + message: "Visual C++ install complete", + type: ProgressType.Generic + ) + ); + File.Delete(VcRedistDownloadPath); } @@ -286,5 +333,4 @@ public class WindowsPrerequisiteHelper : IPrerequisiteHelper File.Delete(PortableGitDownloadPath); } - } diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/InstallerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/InstallerViewModel.cs index 33f92b9d..14ddce18 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/InstallerViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/InstallerViewModel.cs @@ -238,6 +238,8 @@ public partial class InstallerViewModel : ContentDialogViewModelBase downloadOptions.VersionTag = SelectedVersion?.TagName ?? throw new NullReferenceException("Selected version is null"); + downloadOptions.IsLatest = + AvailableVersions?.First().TagName == downloadOptions.VersionTag; installedVersion.InstalledReleaseVersion = downloadOptions.VersionTag; } @@ -245,6 +247,11 @@ public partial class InstallerViewModel : ContentDialogViewModelBase { downloadOptions.CommitHash = SelectedCommit?.Sha ?? throw new NullReferenceException("Selected commit is null"); + downloadOptions.BranchName = + SelectedVersion?.TagName + ?? throw new NullReferenceException("Selected version is null"); + downloadOptions.IsLatest = AvailableCommits?.First().Sha == SelectedCommit.Sha; + installedVersion.InstalledBranch = SelectedVersion?.TagName ?? throw new NullReferenceException("Selected version is null"); @@ -259,6 +266,7 @@ public partial class InstallerViewModel : ContentDialogViewModelBase var installStep = new InstallPackageStep( SelectedPackage, SelectedTorchVersion, + downloadOptions, installLocation ); var setupModelFoldersStep = new SetupModelFoldersStep( @@ -307,6 +315,10 @@ public partial class InstallerViewModel : ContentDialogViewModelBase { SelectedVersion = null; } + else if (SelectedPackage is FooocusMre) + { + SelectedVersion = AvailableVersions.FirstOrDefault(x => x.TagName == "moonride-main"); + } else { // First try to find master diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs index 9720cc8b..c4e34c99 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs @@ -145,7 +145,7 @@ public partial class OneClickInstallViewModel : ViewModelBase // get latest version & download & install var installLocation = Path.Combine(libraryDir, "Packages", SelectedPackage.Name); - var downloadVersion = new DownloadPackageVersionOptions(); + var downloadVersion = new DownloadPackageVersionOptions { IsLatest = true }; var installedVersion = new InstalledPackageVersion(); var versionOptions = await SelectedPackage.GetAllVersionOptions(); @@ -157,13 +157,19 @@ public partial class OneClickInstallViewModel : ViewModelBase else { downloadVersion.BranchName = await SelectedPackage.GetLatestVersion(); + downloadVersion.CommitHash = + (await SelectedPackage.GetAllCommits(downloadVersion.BranchName)) + ?.FirstOrDefault() + ?.Sha ?? string.Empty; + installedVersion.InstalledBranch = downloadVersion.BranchName; + installedVersion.InstalledCommitSha = downloadVersion.CommitHash; } var torchVersion = SelectedPackage.GetRecommendedTorchVersion(); await DownloadPackage(installLocation, downloadVersion); - await InstallPackage(installLocation, torchVersion); + await InstallPackage(installLocation, torchVersion, downloadVersion); var recommendedSharedFolderMethod = SelectedPackage.RecommendedSharedFolderMethod; await SelectedPackage.SetupModelFolders(installLocation, recommendedSharedFolderMethod); @@ -222,7 +228,11 @@ public partial class OneClickInstallViewModel : ViewModelBase OneClickInstallProgress = 100; } - private async Task InstallPackage(string installLocation, TorchVersion torchVersion) + private async Task InstallPackage( + string installLocation, + TorchVersion torchVersion, + DownloadPackageVersionOptions versionOptions + ) { var progress = new Progress(progress => { @@ -235,6 +245,7 @@ public partial class OneClickInstallViewModel : ViewModelBase await SelectedPackage.InstallPackage( installLocation, torchVersion, + versionOptions, progress, (output) => SubSubHeaderText = output.Text ); diff --git a/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs index 9dac32ed..0e98284b 100644 --- a/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs @@ -24,6 +24,7 @@ using StabilityMatrix.Avalonia.Models; using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Avalonia.ViewModels.Dialogs; +using StabilityMatrix.Avalonia.ViewModels.Inference; using StabilityMatrix.Avalonia.ViewModels.OutputsPage; using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Helper; @@ -72,6 +73,9 @@ public partial class OutputsPageViewModel : PageViewModelBase [NotifyPropertyChangedFor(nameof(NumImagesSelected))] private int numItemsSelected; + [ObservableProperty] + private string searchQuery; + public bool CanShowOutputTypes => SelectedCategory.Name.Equals("Shared Output Folder"); public string NumImagesSelected => @@ -92,11 +96,35 @@ public partial class OutputsPageViewModel : PageViewModelBase this.navigationService = navigationService; this.logger = logger; + var predicate = this.WhenPropertyChanged(vm => vm.SearchQuery) + .Throttle(TimeSpan.FromMilliseconds(50))! + .Select, Func>( + propertyValue => + output => + { + if (string.IsNullOrWhiteSpace(propertyValue.Value)) + return true; + + return output.ImageFile.FileName.Contains( + propertyValue.Value, + StringComparison.OrdinalIgnoreCase + ) + || ( + output.ImageFile.GenerationParameters?.PositivePrompt != null + && output.ImageFile.GenerationParameters.PositivePrompt.Contains( + propertyValue.Value, + StringComparison.OrdinalIgnoreCase + ) + ); + } + ) + .AsObservable(); + OutputsCache .Connect() .DeferUntilLoaded() + .Filter(predicate) .SortBy(x => x.ImageFile.CreatedAt, SortDirection.Descending) - .ObserveOn(SynchronizationContext.Current) .Bind(Outputs) .WhenPropertyChanged(p => p.IsSelected) .Subscribe(_ => @@ -135,6 +163,7 @@ public partial class OutputsPageViewModel : PageViewModelBase Categories = new ObservableCollection(packageCategories); SelectedCategory = Categories.First(); SelectedOutputType = SharedOutputType.All; + SearchQuery = string.Empty; } public override void OnLoaded() @@ -280,16 +309,16 @@ public partial class OutputsPageViewModel : PageViewModelBase } } - public void SendToTextToImage(LocalImageFile image) + public void SendToTextToImage(OutputImageViewModel vm) { navigationService.NavigateTo(); - EventManager.Instance.OnInferenceTextToImageRequested(image); + EventManager.Instance.OnInferenceTextToImageRequested(vm.ImageFile); } - public void SendToUpscale(LocalImageFile image) + public void SendToUpscale(OutputImageViewModel vm) { navigationService.NavigateTo(); - EventManager.Instance.OnInferenceUpscaleRequested(image); + EventManager.Instance.OnInferenceUpscaleRequested(vm.ImageFile); } public void ClearSelection() diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs index cca0b036..03faa2d3 100644 --- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs @@ -240,7 +240,29 @@ public partial class PackageCardViewModel : ProgressViewModel { ModificationCompleteMessage = $"{packageName} Update Complete" }; - var updatePackageStep = new UpdatePackageStep(settingsManager, Package, basePackage); + + var versionOptions = new DownloadPackageVersionOptions { IsLatest = true }; + if (Package.Version.IsReleaseMode) + { + versionOptions.VersionTag = await basePackage.GetLatestVersion(); + } + else + { + var commits = await basePackage.GetAllCommits(Package.Version.InstalledBranch); + var latest = commits?.FirstOrDefault(); + if (latest == null) + throw new Exception("Could not find latest commit"); + + versionOptions.BranchName = Package.Version.InstalledBranch; + versionOptions.CommitHash = latest.Sha; + } + + var updatePackageStep = new UpdatePackageStep( + settingsManager, + Package, + versionOptions, + basePackage + ); var steps = new List { updatePackageStep }; EventManager.Instance.OnPackageInstallProgressAdded(runner); diff --git a/StabilityMatrix.Avalonia/Views/OutputsPage.axaml b/StabilityMatrix.Avalonia/Views/OutputsPage.axaml index 8fdcac4f..6a17f7b1 100644 --- a/StabilityMatrix.Avalonia/Views/OutputsPage.axaml +++ b/StabilityMatrix.Avalonia/Views/OutputsPage.axaml @@ -25,7 +25,7 @@ + ColumnDefinitions="Auto, Auto, Auto, Auto, *, Auto, Auto"> + + +