Browse Source

Use git operations for package updates & installs. Also adds search box to outputs page

pull/240/head
JT 1 year ago
parent
commit
ae394a1955
  1. 1
      CHANGELOG.md
  2. 6
      StabilityMatrix.Avalonia/Helpers/UnixPrerequisiteHelper.cs
  3. 152
      StabilityMatrix.Avalonia/Helpers/WindowsPrerequisiteHelper.cs
  4. 12
      StabilityMatrix.Avalonia/ViewModels/Dialogs/InstallerViewModel.cs
  5. 17
      StabilityMatrix.Avalonia/ViewModels/Dialogs/OneClickInstallViewModel.cs
  6. 38
      StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
  7. 24
      StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs
  8. 22
      StabilityMatrix.Avalonia/Views/OutputsPage.axaml
  9. 3
      StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml
  10. 13
      StabilityMatrix.Core/Helper/IPrerequisiteHelper.cs
  11. 206
      StabilityMatrix.Core/Helper/PrerequisiteHelper.cs
  12. 1
      StabilityMatrix.Core/Models/DownloadPackageVersionOptions.cs
  13. 11
      StabilityMatrix.Core/Models/PackageModification/InstallPackageStep.cs
  14. 11
      StabilityMatrix.Core/Models/PackageModification/UpdatePackageStep.cs
  15. 14
      StabilityMatrix.Core/Models/Packages/A3WebUI.cs
  16. 187
      StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs
  17. 2
      StabilityMatrix.Core/Models/Packages/BasePackage.cs
  18. 10
      StabilityMatrix.Core/Models/Packages/ComfyUI.cs
  19. 10
      StabilityMatrix.Core/Models/Packages/Fooocus.cs
  20. 13
      StabilityMatrix.Core/Models/Packages/FooocusMre.cs
  21. 134
      StabilityMatrix.Core/Models/Packages/InvokeAI.cs
  22. 14
      StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs
  23. 2
      StabilityMatrix.Core/Models/Packages/UnknownPackage.cs
  24. 20
      StabilityMatrix.Core/Models/Packages/VladAutomatic.cs
  25. 10
      StabilityMatrix.Core/Models/Packages/VoltaML.cs

1
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

6
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<ProcessOutput>? onProcessOutput = null,
params string[] args
)
{
var command =
args.Length == 0 ? "git" : "git " + string.Join(" ", args.Select(ProcessRunner.Quote));

152
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<ProcessOutput>? onProcessOutput = null,
params string[] args
)
{
var process = ProcessRunner.StartAnsiProcess(GitExePath, args,
var process = ProcessRunner.StartAnsiProcess(
GitExePath,
args,
workingDirectory: workingDirectory,
environmentVariables: new Dictionary<string, string>
{
{"PATH", Compat.GetEnvPathWithExtensions(GitBinPath)}
});
{ "PATH", Compat.GetEnvPathWithExtensions(GitBinPath) }
},
outputDataReceived: onProcessOutput
);
await ProcessRunner.WaitForExitConditionAsync(process);
}
public async Task<string> 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<string, string>
{
{"PATH", Compat.GetEnvPathWithExtensions(GitBinPath)}
});
{ "PATH", Compat.GetEnvPathWithExtensions(GitBinPath) }
}
);
return process;
}
public async Task InstallAllIfNecessary(IProgress<ProgressReport>? 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<ProgressReport>? 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);
}
}

12
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

17
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<ProgressReport>(progress =>
{
@ -235,6 +245,7 @@ public partial class OneClickInstallViewModel : ViewModelBase
await SelectedPackage.InstallPackage(
installLocation,
torchVersion,
versionOptions,
progress,
(output) => SubSubHeaderText = output.Text
);

38
StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs

@ -56,6 +56,9 @@ public partial class OutputsPageViewModel : PageViewModelBase
public IObservableCollection<OutputImageViewModel> Outputs { get; set; } =
new ObservableCollectionExtended<OutputImageViewModel>();
public IObservableCollection<OutputImageViewModel> FilteredOutputs { get; set; } =
new ObservableCollectionExtended<OutputImageViewModel>();
public IEnumerable<SharedOutputType> OutputTypes { get; } = Enum.GetValues<SharedOutputType>();
[ObservableProperty]
@ -72,6 +75,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 =>
@ -95,8 +101,24 @@ public partial class OutputsPageViewModel : PageViewModelBase
OutputsCache
.Connect()
.DeferUntilLoaded()
.Filter(output =>
{
if (string.IsNullOrWhiteSpace(SearchQuery))
return true;
return output.ImageFile.FileName.Contains(
SearchQuery,
StringComparison.OrdinalIgnoreCase
)
|| (
output.ImageFile.GenerationParameters?.PositivePrompt != null
&& output.ImageFile.GenerationParameters.PositivePrompt.Contains(
SearchQuery,
StringComparison.OrdinalIgnoreCase
)
);
})
.SortBy(x => x.ImageFile.CreatedAt, SortDirection.Descending)
.ObserveOn(SynchronizationContext.Current)
.Bind(Outputs)
.WhenPropertyChanged(p => p.IsSelected)
.Subscribe(_ =>
@ -135,6 +157,7 @@ public partial class OutputsPageViewModel : PageViewModelBase
Categories = new ObservableCollection<PackageOutputCategory>(packageCategories);
SelectedCategory = Categories.First();
SelectedOutputType = SharedOutputType.All;
SearchQuery = string.Empty;
}
public override void OnLoaded()
@ -176,6 +199,11 @@ public partial class OutputsPageViewModel : PageViewModelBase
GetOutputs(path);
}
partial void OnSearchQueryChanged(string value)
{
OutputsCache.Refresh();
}
public async Task OnImageClick(OutputImageViewModel item)
{
// Select image if we're in "select mode"
@ -280,16 +308,16 @@ public partial class OutputsPageViewModel : PageViewModelBase
}
}
public void SendToTextToImage(LocalImageFile image)
public void SendToTextToImage(OutputImageViewModel vm)
{
navigationService.NavigateTo<InferenceViewModel>();
EventManager.Instance.OnInferenceTextToImageRequested(image);
EventManager.Instance.OnInferenceTextToImageRequested(vm.ImageFile);
}
public void SendToUpscale(LocalImageFile image)
public void SendToUpscale(OutputImageViewModel vm)
{
navigationService.NavigateTo<InferenceViewModel>();
EventManager.Instance.OnInferenceUpscaleRequested(image);
EventManager.Instance.OnInferenceUpscaleRequested(vm.ImageFile);
}
public void ClearSelection()

24
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<IPackageStep> { updatePackageStep };
EventManager.Instance.OnPackageInstallProgressAdded(runner);

22
StabilityMatrix.Avalonia/Views/OutputsPage.axaml

@ -25,7 +25,7 @@
<Grid Grid.Row="0" Margin="0,0,0,16"
HorizontalAlignment="Stretch"
RowDefinitions="Auto, Auto"
ColumnDefinitions="Auto, Auto, Auto, *, Auto, Auto">
ColumnDefinitions="Auto, Auto, Auto, Auto, *, Auto, Auto">
<TextBlock Grid.Row="0"
Grid.Column="0"
Text="{x:Static lang:Resources.Label_OutputFolder}"
@ -68,9 +68,21 @@
Margin="4,0"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center" />
<TextBlock Grid.Row="0"
Grid.Column="2"
Margin="4"
Text="Search"/>
<TextBox Grid.Row="1"
Grid.Column="2"
Text="{Binding SearchQuery, Mode=TwoWay}"
Watermark="Search"
Margin="4, 0"
VerticalContentAlignment="Center"
MinWidth="150"/>
<TextBlock Grid.Row="1"
Grid.Column="3"
Grid.Column="4"
IsVisible="{Binding !!NumItemsSelected}"
FontSize="16"
Margin="8, 0"
@ -80,7 +92,7 @@
Text="{Binding NumImagesSelected, FallbackValue=1234 images selected}" />
<Button Grid.Row="1"
Grid.Column="4"
Grid.Column="5"
VerticalAlignment="Bottom"
CornerRadius="8"
Padding="12, 0"
@ -92,7 +104,7 @@
</Button>
<Button Grid.Row="1"
Grid.Column="5"
Grid.Column="6"
Content="{x:Static lang:Resources.Action_ClearSelection}"
VerticalAlignment="Bottom"
CornerRadius="8"
@ -101,7 +113,7 @@
Command="{Binding ClearSelection}"
IsVisible="{Binding !!NumItemsSelected}" />
<Button Grid.Row="1"
Grid.Column="5"
Grid.Column="6"
Content="{x:Static lang:Resources.Action_SelectAll}"
VerticalAlignment="Bottom"
Classes="accent"

3
StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml

@ -120,7 +120,8 @@
</MenuItem>
<MenuItem
Header="{x:Static lang:Resources.Label_UseSharedOutputFolder}"
Command="{Binding ToggleSharedOutput}">
Command="{Binding ToggleSharedOutput}"
IsVisible="{Binding !IsUnknownPackage}">
<MenuItem.Icon>
<CheckBox Margin="8,0,0,0"
Padding="0"

13
StabilityMatrix.Core/Helper/IPrerequisiteHelper.cs

@ -1,25 +1,30 @@
using System.Runtime.Versioning;
using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Processes;
namespace StabilityMatrix.Core.Helper;
public interface IPrerequisiteHelper
{
string GitBinPath { get; }
bool IsPythonInstalled { get; }
Task InstallAllIfNecessary(IProgress<ProgressReport>? progress = null);
Task UnpackResourcesIfNecessary(IProgress<ProgressReport>? progress = null);
Task InstallGitIfNecessary(IProgress<ProgressReport>? progress = null);
Task InstallPythonIfNecessary(IProgress<ProgressReport>? progress = null);
[SupportedOSPlatform("Windows")]
Task InstallVcRedistIfNecessary(IProgress<ProgressReport>? progress = null);
/// <summary>
/// Run embedded git with the given arguments.
/// </summary>
Task RunGit(string? workingDirectory = null, params string[] args);
Task RunGit(
string? workingDirectory = null,
Action<ProcessOutput>? onProcessOutput = null,
params string[] args
);
Task<string> GetGitOutput(string? workingDirectory = null, params string[] args);
}

206
StabilityMatrix.Core/Helper/PrerequisiteHelper.cs

@ -17,35 +17,42 @@ public class PrerequisiteHelper : IPrerequisiteHelper
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 const string PythonDownloadUrl = "https://www.python.org/ftp/python/3.10.11/python-3.10.11-embed-amd64.zip";
private const string PythonDownloadHashBlake3 = "24923775f2e07392063aaa0c78fbd4ae0a320e1fc9c6cfbab63803402279fe5a";
private const string PythonDownloadUrl =
"https://www.python.org/ftp/python/3.10.11/python-3.10.11-embed-amd64.zip";
private const string PythonDownloadHashBlake3 =
"24923775f2e07392063aaa0c78fbd4ae0a320e1fc9c6cfbab63803402279fe5a";
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 PrerequisiteHelper(ILogger<PrerequisiteHelper> logger, IGitHubClient gitHubClient,
IDownloadService downloadService, ISettingsManager settingsManager)
public PrerequisiteHelper(
ILogger<PrerequisiteHelper> logger,
IGitHubClient gitHubClient,
IDownloadService downloadService,
ISettingsManager settingsManager
)
{
this.logger = logger;
this.gitHubClient = gitHubClient;
@ -53,19 +60,33 @@ public class PrerequisiteHelper : IPrerequisiteHelper
this.settingsManager = settingsManager;
}
public async Task RunGit(string? workingDirectory = null, params string[] args)
public async Task RunGit(
string? workingDirectory = null,
Action<ProcessOutput>? onProcessOutput = null,
params string[] args
)
{
var process = ProcessRunner.StartAnsiProcess(GitExePath, args, workingDirectory: workingDirectory);
var process = ProcessRunner.StartAnsiProcess(
GitExePath,
args,
workingDirectory,
onProcessOutput
);
await ProcessRunner.WaitForExitConditionAsync(process).ConfigureAwait(false);
}
public async Task<string> GetGitOutput(string? workingDirectory = null, params string[] args)
{
var output = await ProcessRunner.GetProcessOutputAsync(GitExePath, string.Join(" ", args),
workingDirectory: workingDirectory).ConfigureAwait(false);
var output = await ProcessRunner
.GetProcessOutputAsync(
GitExePath,
string.Join(" ", args),
workingDirectory: workingDirectory
)
.ConfigureAwait(false);
return output;
}
public async Task InstallAllIfNecessary(IProgress<ProgressReport>? progress = null)
{
await InstallVcRedistIfNecessary(progress);
@ -85,11 +106,14 @@ public class PrerequisiteHelper : IPrerequisiteHelper
// from "StabilityMatrix.Assets.Python310.libssl-1_1.dll"
// to "Python310\libssl-1_1.dll"
var fileExt = Path.GetExtension(resourceName);
var fileName = resourceName
.Replace(fileExt, "")
.Replace("StabilityMatrix.Assets.", "")
.Replace(".", Path.DirectorySeparatorChar.ToString()) + fileExt;
await using var resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)!;
var fileName =
resourceName
.Replace(fileExt, "")
.Replace("StabilityMatrix.Assets.", "")
.Replace(".", Path.DirectorySeparatorChar.ToString()) + fileExt;
await using var resourceStream = Assembly
.GetExecutingAssembly()
.GetManifestResourceStream(resourceName)!;
if (resourceStream == null)
{
throw new Exception($"Resource {resourceName} not found");
@ -101,7 +125,11 @@ public class PrerequisiteHelper : IPrerequisiteHelper
/// <summary>
/// Extracts all embedded resources starting with resourceDir to outputDirectory
/// </summary>
private async Task ExtractAllEmbeddedResources(string resourceDir, string outputDirectory, string resourceRoot = "StabilityMatrix.Assets.")
private async Task ExtractAllEmbeddedResources(
string resourceDir,
string outputDirectory,
string resourceRoot = "StabilityMatrix.Assets."
)
{
Directory.CreateDirectory(outputDirectory);
// Unpack from embedded resources
@ -117,12 +145,15 @@ public class PrerequisiteHelper : IPrerequisiteHelper
// from "StabilityMatrix.Assets.Python310.libssl-1_1.dll"
// to "Python310\libssl-1_1.dll"
var fileExt = Path.GetExtension(resourceName);
var fileName = resourceName
.Replace(fileExt, "")
.Replace(resourceRoot, "")
.Replace(".", Path.DirectorySeparatorChar.ToString()) + fileExt;
var fileName =
resourceName
.Replace(fileExt, "")
.Replace(resourceRoot, "")
.Replace(".", Path.DirectorySeparatorChar.ToString()) + fileExt;
// Unpack resource
await using var resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)!;
await using var resourceStream = Assembly
.GetExecutingAssembly()
.GetManifestResourceStream(resourceName)!;
var outputFilePath = Path.Combine(outputDirectory, fileName);
// Create missing directories
var outputDir = Path.GetDirectoryName(outputFilePath);
@ -134,13 +165,21 @@ public class PrerequisiteHelper : IPrerequisiteHelper
await resourceStream.CopyToAsync(fileStream);
copied.Add(outputFilePath);
}
logger.LogInformation("Successfully unpacked {Num} embedded resources: [{Resources}]", total, string.Join(",", copied));
logger.LogInformation(
"Successfully unpacked {Num} embedded resources: [{Resources}]",
total,
string.Join(",", copied)
);
}
public async Task UnpackResourcesIfNecessary(IProgress<ProgressReport>? progress = null)
{
// Skip if all files exist
if (File.Exists(SevenZipPath) && File.Exists(PythonDllPath) && File.Exists(PythonLibraryZipPath))
if (
File.Exists(SevenZipPath)
&& File.Exists(PythonDllPath)
&& File.Exists(PythonLibraryZipPath)
)
{
return;
}
@ -149,14 +188,14 @@ public class PrerequisiteHelper : IPrerequisiteHelper
// Create directories
Directory.CreateDirectory(AssetsDir);
Directory.CreateDirectory(PythonDir);
// Run if 7za missing
if (!File.Exists(SevenZipPath))
{
await ExtractEmbeddedResource("StabilityMatrix.Assets.7za.exe", AssetsDir);
await ExtractEmbeddedResource("StabilityMatrix.Assets.7za - LICENSE.txt", AssetsDir);
}
progress?.Report(new ProgressReport(1f, "Unpacking complete"));
}
@ -171,60 +210,70 @@ public class PrerequisiteHelper : IPrerequisiteHelper
logger.LogInformation("Python not found at {PythonDllPath}, downloading...", PythonDllPath);
Directory.CreateDirectory(AssetsDir);
// Delete existing python zip if it exists
if (File.Exists(PythonLibraryZipPath))
{
File.Delete(PythonLibraryZipPath);
}
logger.LogInformation(
"Downloading Python from {PythonLibraryZipUrl} to {PythonLibraryZipPath}",
PythonDownloadUrl, PythonLibraryZipPath);
"Downloading Python from {PythonLibraryZipUrl} to {PythonLibraryZipPath}",
PythonDownloadUrl,
PythonLibraryZipPath
);
// Cleanup to remove zip if download fails
try
{
// Download python zip
await downloadService.DownloadToFileAsync(PythonDownloadUrl, PythonDownloadPath, progress: progress);
await downloadService.DownloadToFileAsync(
PythonDownloadUrl,
PythonDownloadPath,
progress: progress
);
// Verify python hash
var downloadHash = await FileHash.GetBlake3Async(PythonDownloadPath, progress);
if (downloadHash != PythonDownloadHashBlake3)
{
var fileExists = File.Exists(PythonDownloadPath);
var fileSize = new FileInfo(PythonDownloadPath).Length;
var msg = $"Python download hash mismatch: {downloadHash} != {PythonDownloadHashBlake3} " +
$"(file exists: {fileExists}, size: {fileSize})";
var msg =
$"Python download hash mismatch: {downloadHash} != {PythonDownloadHashBlake3} "
+ $"(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 ExtractEmbeddedResource("StabilityMatrix.Assets.7za.exe", AssetsDir);
await ExtractEmbeddedResource("StabilityMatrix.Assets.7za - LICENSE.txt", AssetsDir);
await ExtractEmbeddedResource(
"StabilityMatrix.Assets.7za - LICENSE.txt",
AssetsDir
);
}
// Delete existing python dir
if (Directory.Exists(PythonDir))
{
Directory.Delete(PythonDir, true);
}
// Unzip python
await ArchiveHelper.Extract7Z(PythonDownloadPath, PythonDir);
try
{
// Extract embedded venv
await ExtractAllEmbeddedResources("StabilityMatrix.Assets.venv", PythonDir);
// Add venv to python's library zip
await ArchiveHelper.AddToArchive7Z(PythonLibraryZipPath, VenvTempDir);
}
finally
@ -235,16 +284,16 @@ public class PrerequisiteHelper : IPrerequisiteHelper
Directory.Delete(VenvTempDir, true);
}
}
// Extract get-pip.pyc
await ExtractEmbeddedResource("StabilityMatrix.Assets.get-pip.pyc", 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
@ -264,7 +313,7 @@ public class PrerequisiteHelper : IPrerequisiteHelper
logger.LogDebug("Git already installed at {GitExePath}", GitExePath);
return;
}
logger.LogInformation("Git not found at {GitExePath}, downloading...", GitExePath);
var portableGitUrl =
@ -272,7 +321,11 @@ public class PrerequisiteHelper : 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"));
}
@ -284,7 +337,9 @@ public class PrerequisiteHelper : 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"));
@ -293,20 +348,44 @@ public class PrerequisiteHelper : IPrerequisiteHelper
return;
}
}
logger.LogInformation("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.LogInformation("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);
}
@ -335,5 +414,4 @@ public class PrerequisiteHelper : IPrerequisiteHelper
settingsManager.AddPathExtension(GitBinPath);
settingsManager.InsertPathExtensions();
}
}

1
StabilityMatrix.Core/Models/DownloadPackageVersionOptions.cs

@ -5,4 +5,5 @@ public class DownloadPackageVersionOptions
public string BranchName { get; set; }
public string CommitHash { get; set; }
public string VersionTag { get; set; }
public bool IsLatest { get; set; }
}

11
StabilityMatrix.Core/Models/PackageModification/InstallPackageStep.cs

@ -8,12 +8,19 @@ public class InstallPackageStep : IPackageStep
{
private readonly BasePackage package;
private readonly TorchVersion torchVersion;
private readonly DownloadPackageVersionOptions versionOptions;
private readonly string installPath;
public InstallPackageStep(BasePackage package, TorchVersion torchVersion, string installPath)
public InstallPackageStep(
BasePackage package,
TorchVersion torchVersion,
DownloadPackageVersionOptions versionOptions,
string installPath
)
{
this.package = package;
this.torchVersion = torchVersion;
this.versionOptions = versionOptions;
this.installPath = installPath;
}
@ -25,7 +32,7 @@ public class InstallPackageStep : IPackageStep
}
await package
.InstallPackage(installPath, torchVersion, progress, OnConsoleOutput)
.InstallPackage(installPath, torchVersion, versionOptions, progress, OnConsoleOutput)
.ConfigureAwait(false);
}

11
StabilityMatrix.Core/Models/PackageModification/UpdatePackageStep.cs

@ -9,16 +9,19 @@ public class UpdatePackageStep : IPackageStep
{
private readonly ISettingsManager settingsManager;
private readonly InstalledPackage installedPackage;
private readonly DownloadPackageVersionOptions versionOptions;
private readonly BasePackage basePackage;
public UpdatePackageStep(
ISettingsManager settingsManager,
InstalledPackage installedPackage,
DownloadPackageVersionOptions versionOptions,
BasePackage basePackage
)
{
this.settingsManager = settingsManager;
this.installedPackage = installedPackage;
this.versionOptions = versionOptions;
this.basePackage = basePackage;
}
@ -33,7 +36,13 @@ public class UpdatePackageStep : IPackageStep
}
var updateResult = await basePackage
.Update(installedPackage, torchVersion, progress, onConsoleOutput: OnConsoleOutput)
.Update(
installedPackage,
torchVersion,
versionOptions,
progress,
onConsoleOutput: OnConsoleOutput
)
.ConfigureAwait(false);
settingsManager.UpdatePackageVersionNumber(installedPackage.Id, updateResult);

14
StabilityMatrix.Core/Models/Packages/A3WebUI.cs

@ -67,9 +67,9 @@ public class A3WebUI : BaseGitPackage
[SharedOutputType.Extras] = new[] { "outputs/extras-images" },
[SharedOutputType.Saved] = new[] { "log/images" },
[SharedOutputType.Img2Img] = new[] { "outputs/img2img-images" },
[SharedOutputType.Text2Img] = new[] { "outputs/text2img-images" },
[SharedOutputType.Text2Img] = new[] { "outputs/txt2img-images" },
[SharedOutputType.Img2ImgGrids] = new[] { "outputs/img2img-grids" },
[SharedOutputType.Text2ImgGrids] = new[] { "outputs/text2img-grids" }
[SharedOutputType.Text2ImgGrids] = new[] { "outputs/txt2img-grids" }
};
[SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeNotEvident")]
@ -181,11 +181,19 @@ public class A3WebUI : BaseGitPackage
public override async Task InstallPackage(
string installLocation,
TorchVersion torchVersion,
DownloadPackageVersionOptions versionOptions,
IProgress<ProgressReport>? progress = null,
Action<ProcessOutput>? onConsoleOutput = null
)
{
await base.InstallPackage(installLocation, torchVersion, progress).ConfigureAwait(false);
await base.InstallPackage(
installLocation,
torchVersion,
versionOptions,
progress,
onConsoleOutput
)
.ConfigureAwait(false);
progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true));
// Setup venv

187
StabilityMatrix.Core/Models/Packages/BaseGitPackage.cs

@ -170,29 +170,54 @@ public abstract class BaseGitPackage : BasePackage
IProgress<ProgressReport>? progress = null
)
{
var downloadUrl = GetDownloadUrl(versionOptions);
if (!Directory.Exists(DownloadLocation.Replace($"{Name}.zip", "")))
if (!string.IsNullOrWhiteSpace(versionOptions.VersionTag))
{
Directory.CreateDirectory(DownloadLocation.Replace($"{Name}.zip", ""));
await PrerequisiteHelper
.RunGit(
null,
null,
"clone",
"--branch",
versionOptions.VersionTag,
GithubUrl,
$"\"{installLocation}\""
)
.ConfigureAwait(false);
}
else if (!string.IsNullOrWhiteSpace(versionOptions.BranchName))
{
await PrerequisiteHelper
.RunGit(
null,
null,
"clone",
"--branch",
versionOptions.BranchName,
GithubUrl,
$"\"{installLocation}\""
)
.ConfigureAwait(false);
}
await DownloadService
.DownloadToFileAsync(downloadUrl, DownloadLocation, progress: progress)
.ConfigureAwait(false);
if (!versionOptions.IsLatest && !string.IsNullOrWhiteSpace(versionOptions.CommitHash))
{
await PrerequisiteHelper
.RunGit(installLocation, null, "checkout", versionOptions.CommitHash)
.ConfigureAwait(false);
}
progress?.Report(new ProgressReport(100, message: "Download Complete"));
}
public override async Task InstallPackage(
public override Task InstallPackage(
string installLocation,
TorchVersion torchVersion,
DownloadPackageVersionOptions versionOptions,
IProgress<ProgressReport>? progress = null,
Action<ProcessOutput>? onConsoleOutput = null
)
{
await UnzipPackage(installLocation, progress).ConfigureAwait(false);
File.Delete(DownloadLocation);
return Task.CompletedTask;
}
protected Task UnzipPackage(string installLocation, IProgress<ProgressReport>? progress = null)
@ -279,6 +304,7 @@ public abstract class BaseGitPackage : BasePackage
public override async Task<InstalledPackageVersion> Update(
InstalledPackage installedPackage,
TorchVersion torchVersion,
DownloadPackageVersionOptions versionOptions,
IProgress<ProgressReport>? progress = null,
bool includePrerelease = false,
Action<ProcessOutput>? onConsoleOutput = null
@ -287,47 +313,146 @@ public abstract class BaseGitPackage : BasePackage
if (installedPackage.Version == null)
throw new NullReferenceException("Version is null");
if (installedPackage.Version.IsReleaseMode)
if (!Directory.Exists(Path.Combine(installedPackage.FullPath!, ".git")))
{
Logger.Info("not a git repo, initializing...");
progress?.Report(
new ProgressReport(-1f, "Initializing git repo", isIndeterminate: true)
);
await PrerequisiteHelper
.RunGit(installedPackage.FullPath!, onConsoleOutput, "init")
.ConfigureAwait(false);
await PrerequisiteHelper
.RunGit(
installedPackage.FullPath!,
onConsoleOutput,
"remote",
"add",
"origin",
GithubUrl
)
.ConfigureAwait(false);
}
if (!string.IsNullOrWhiteSpace(versionOptions.VersionTag))
{
var releases = await GetAllReleases().ConfigureAwait(false);
var latestRelease = releases.First(x => includePrerelease || !x.Prerelease);
progress?.Report(new ProgressReport(-1f, "Fetching tags...", isIndeterminate: true));
await PrerequisiteHelper
.RunGit(installedPackage.FullPath!, onConsoleOutput, "fetch", "--tags")
.ConfigureAwait(false);
await DownloadPackage(
installedPackage.FullPath,
new DownloadPackageVersionOptions { VersionTag = latestRelease.TagName },
progress
progress?.Report(
new ProgressReport(
-1f,
$"Checking out {versionOptions.VersionTag}",
isIndeterminate: true
)
);
await PrerequisiteHelper
.RunGit(
installedPackage.FullPath!,
onConsoleOutput,
"checkout",
versionOptions.VersionTag,
"--force"
)
.ConfigureAwait(false);
await InstallPackage(installedPackage.FullPath, torchVersion, progress, onConsoleOutput)
await InstallPackage(
installedPackage.FullPath!,
torchVersion,
new DownloadPackageVersionOptions
{
VersionTag = versionOptions.VersionTag,
IsLatest = versionOptions.IsLatest
},
progress,
onConsoleOutput
)
.ConfigureAwait(false);
return new InstalledPackageVersion { InstalledReleaseVersion = latestRelease.TagName };
return new InstalledPackageVersion
{
InstalledReleaseVersion = versionOptions.VersionTag
};
}
// Commit mode
var allCommits = await GetAllCommits(installedPackage.Version.InstalledBranch)
// fetch
progress?.Report(new ProgressReport(-1f, "Fetching data...", isIndeterminate: true));
await PrerequisiteHelper
.RunGit(installedPackage.FullPath!, onConsoleOutput, "fetch")
.ConfigureAwait(false);
var latestCommit = allCommits?.First();
if (latestCommit is null || string.IsNullOrEmpty(latestCommit.Sha))
if (versionOptions.IsLatest)
{
throw new Exception("No commits found for branch");
// checkout
progress?.Report(
new ProgressReport(
-1f,
$"Checking out {installedPackage.Version.InstalledBranch}...",
isIndeterminate: true
)
);
await PrerequisiteHelper
.RunGit(
installedPackage.FullPath!,
onConsoleOutput,
"checkout",
versionOptions.BranchName,
"--force"
)
.ConfigureAwait(false);
// pull
progress?.Report(new ProgressReport(-1f, "Pulling changes...", isIndeterminate: true));
await PrerequisiteHelper
.RunGit(
installedPackage.FullPath!,
onConsoleOutput,
"pull",
"origin",
installedPackage.Version.InstalledBranch
)
.ConfigureAwait(false);
}
else
{
// checkout
progress?.Report(
new ProgressReport(
-1f,
$"Checking out {installedPackage.Version.InstalledBranch}...",
isIndeterminate: true
)
);
await PrerequisiteHelper
.RunGit(
installedPackage.FullPath!,
onConsoleOutput,
"checkout",
versionOptions.CommitHash,
"--force"
)
.ConfigureAwait(false);
}
await DownloadPackage(
await InstallPackage(
installedPackage.FullPath,
new DownloadPackageVersionOptions { CommitHash = latestCommit.Sha },
progress
torchVersion,
new DownloadPackageVersionOptions
{
CommitHash = versionOptions.CommitHash,
IsLatest = versionOptions.IsLatest
},
progress,
onConsoleOutput
)
.ConfigureAwait(false);
await InstallPackage(installedPackage.FullPath, torchVersion, progress, onConsoleOutput)
.ConfigureAwait(false);
return new InstalledPackageVersion
{
InstalledBranch = installedPackage.Version.InstalledBranch,
InstalledCommitSha = latestCommit.Sha
InstalledBranch = versionOptions.BranchName,
InstalledCommitSha = versionOptions.CommitHash
};
}

2
StabilityMatrix.Core/Models/Packages/BasePackage.cs

@ -49,6 +49,7 @@ public abstract class BasePackage
public abstract Task InstallPackage(
string installLocation,
TorchVersion torchVersion,
DownloadPackageVersionOptions versionOptions,
IProgress<ProgressReport>? progress = null,
Action<ProcessOutput>? onConsoleOutput = null
);
@ -65,6 +66,7 @@ public abstract class BasePackage
public abstract Task<InstalledPackageVersion> Update(
InstalledPackage installedPackage,
TorchVersion torchVersion,
DownloadPackageVersionOptions versionOptions,
IProgress<ProgressReport>? progress = null,
bool includePrerelease = false,
Action<ProcessOutput>? onConsoleOutput = null

10
StabilityMatrix.Core/Models/Packages/ComfyUI.cs

@ -151,11 +151,19 @@ public class ComfyUI : BaseGitPackage
public override async Task InstallPackage(
string installLocation,
TorchVersion torchVersion,
DownloadPackageVersionOptions versionOptions,
IProgress<ProgressReport>? progress = null,
Action<ProcessOutput>? onConsoleOutput = null
)
{
await base.InstallPackage(installLocation, torchVersion, progress).ConfigureAwait(false);
await base.InstallPackage(
installLocation,
torchVersion,
versionOptions,
progress,
onConsoleOutput
)
.ConfigureAwait(false);
progress?.Report(new ProgressReport(-1, "Setting up venv", isIndeterminate: true));
// Setup venv

10
StabilityMatrix.Core/Models/Packages/Fooocus.cs

@ -98,11 +98,19 @@ public class Fooocus : BaseGitPackage
public override async Task InstallPackage(
string installLocation,
TorchVersion torchVersion,
DownloadPackageVersionOptions versionOptions,
IProgress<ProgressReport>? progress = null,
Action<ProcessOutput>? onConsoleOutput = null
)
{
await base.InstallPackage(installLocation, torchVersion, progress).ConfigureAwait(false);
await base.InstallPackage(
installLocation,
torchVersion,
versionOptions,
progress,
onConsoleOutput
)
.ConfigureAwait(false);
var venvRunner = await SetupVenv(installLocation, forceRecreate: true)
.ConfigureAwait(false);

13
StabilityMatrix.Core/Models/Packages/FooocusMre.cs

@ -37,6 +37,9 @@ public class FooocusMre : BaseGitPackage
"https://user-images.githubusercontent.com/130458190/265366059-ce430ea0-0995-4067-98dd-cef1d7dc1ab6.png"
);
public override string Disclaimer =>
"This package may no longer receive updates from its author. It may be removed from Stability Matrix in the future.";
public override List<LaunchOptionDefinition> LaunchOptions =>
new()
{
@ -102,11 +105,19 @@ public class FooocusMre : BaseGitPackage
public override async Task InstallPackage(
string installLocation,
TorchVersion torchVersion,
DownloadPackageVersionOptions versionOptions,
IProgress<ProgressReport>? progress = null,
Action<ProcessOutput>? onConsoleOutput = null
)
{
await base.InstallPackage(installLocation, torchVersion, progress).ConfigureAwait(false);
await base.InstallPackage(
installLocation,
torchVersion,
versionOptions,
progress,
onConsoleOutput
)
.ConfigureAwait(false);
var venvRunner = await SetupVenv(installLocation, forceRecreate: true)
.ConfigureAwait(false);

134
StabilityMatrix.Core/Models/Packages/InvokeAI.cs

@ -1,4 +1,5 @@
using System.Text.RegularExpressions;
using System.Globalization;
using System.Text.RegularExpressions;
using NLog;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
@ -143,18 +144,10 @@ public class InvokeAI : BaseGitPackage
return base.GetRecommendedTorchVersion();
}
public override Task DownloadPackage(
string installLocation,
DownloadPackageVersionOptions downloadOptions,
IProgress<ProgressReport>? progress = null
)
{
return Task.CompletedTask;
}
public override async Task InstallPackage(
string installLocation,
TorchVersion torchVersion,
DownloadPackageVersionOptions versionOptions,
IProgress<ProgressReport>? progress = null,
Action<ProcessOutput>? onConsoleOutput = null
)
@ -170,25 +163,30 @@ public class InvokeAI : BaseGitPackage
progress?.Report(new ProgressReport(-1f, "Installing Package", isIndeterminate: true));
var pipCommandArgs =
"InvokeAI --use-pep517 --extra-index-url https://download.pytorch.org/whl/cpu";
"-e . --use-pep517 --extra-index-url https://download.pytorch.org/whl/cpu";
switch (torchVersion)
{
// If has Nvidia Gpu, install CUDA version
case TorchVersion.Cuda:
await InstallCudaTorch(venvRunner, progress, onConsoleOutput).ConfigureAwait(false);
Logger.Info("Starting InvokeAI install (CUDA)...");
pipCommandArgs =
"InvokeAI[xformers] --use-pep517 --extra-index-url https://download.pytorch.org/whl/cu117";
"-e .[xformers] --use-pep517 --extra-index-url https://download.pytorch.org/whl/cu118";
break;
// For AMD, Install ROCm version
case TorchVersion.Rocm:
await venvRunner
.PipInstall(PyVenvRunner.TorchPipInstallArgsRocm542, onConsoleOutput)
.ConfigureAwait(false);
Logger.Info("Starting InvokeAI install (ROCm)...");
pipCommandArgs =
"InvokeAI --use-pep517 --extra-index-url https://download.pytorch.org/whl/rocm5.4.2";
"-e . --use-pep517 --extra-index-url https://download.pytorch.org/whl/rocm5.4.2";
break;
case TorchVersion.Mps:
// For Apple silicon, use MPS
Logger.Info("Starting InvokeAI install (MPS)...");
pipCommandArgs = "InvokeAI --use-pep517";
pipCommandArgs = "-e . --use-pep517";
break;
}
@ -212,73 +210,6 @@ public class InvokeAI : BaseGitPackage
progress?.Report(new ProgressReport(1f, "Done!", isIndeterminate: false));
}
public override async Task<InstalledPackageVersion> Update(
InstalledPackage installedPackage,
TorchVersion torchVersion,
IProgress<ProgressReport>? progress = null,
bool includePrerelease = false,
Action<ProcessOutput>? onConsoleOutput = null
)
{
progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true));
if (installedPackage.FullPath is null || installedPackage.Version is null)
{
throw new NullReferenceException("Installed package is missing Path and/or Version");
}
await using var venvRunner = new PyVenvRunner(
Path.Combine(installedPackage.FullPath, "venv")
);
venvRunner.WorkingDirectory = installedPackage.FullPath;
venvRunner.EnvironmentVariables = GetEnvVars(installedPackage.FullPath);
var latestVersion = await GetUpdateVersion(installedPackage).ConfigureAwait(false);
var isReleaseMode = installedPackage.Version.IsReleaseMode;
var downloadUrl = isReleaseMode
? $"https://github.com/invoke-ai/InvokeAI/archive/{latestVersion}.zip"
: $"https://github.com/invoke-ai/InvokeAI/archive/refs/heads/{installedPackage.Version.InstalledBranch}.zip";
progress?.Report(new ProgressReport(-1f, "Installing Package", isIndeterminate: true));
var pipCommandArgs =
$"\"InvokeAI @ {downloadUrl}\" --use-pep517 --extra-index-url https://download.pytorch.org/whl/cpu --upgrade";
switch (torchVersion)
{
// If has Nvidia Gpu, install CUDA version
case TorchVersion.Cuda:
Logger.Info("Starting InvokeAI install (CUDA)...");
pipCommandArgs =
$"\"InvokeAI[xformers] @ {downloadUrl}\" --use-pep517 --extra-index-url https://download.pytorch.org/whl/cu117 --upgrade";
break;
// For AMD, Install ROCm version
case TorchVersion.Rocm:
Logger.Info("Starting InvokeAI install (ROCm)...");
pipCommandArgs =
$"\"InvokeAI @ {downloadUrl}\" --use-pep517 --extra-index-url https://download.pytorch.org/whl/rocm5.4.2 --upgrade";
break;
case TorchVersion.Mps:
// For Apple silicon, use MPS
Logger.Info("Starting InvokeAI install (MPS)...");
pipCommandArgs = $"\"InvokeAI @ {downloadUrl}\" --use-pep517 --upgrade";
break;
}
await venvRunner.PipInstall(pipCommandArgs, onConsoleOutput).ConfigureAwait(false);
progress?.Report(new ProgressReport(1f, "Done!", isIndeterminate: false));
return isReleaseMode
? new InstalledPackageVersion { InstalledReleaseVersion = latestVersion }
: new InstalledPackageVersion
{
InstalledBranch = installedPackage.Version.InstalledBranch,
InstalledCommitSha = latestVersion
};
}
public override Task RunPackage(
string installedPackagePath,
string command,
@ -286,27 +217,6 @@ public class InvokeAI : BaseGitPackage
Action<ProcessOutput>? onConsoleOutput
) => RunInvokeCommand(installedPackagePath, command, arguments, true, onConsoleOutput);
private async Task<string> GetUpdateVersion(
InstalledPackage installedPackage,
bool includePrerelease = false
)
{
if (installedPackage.Version == null)
throw new NullReferenceException("Installed package version is null");
if (installedPackage.Version.IsReleaseMode)
{
var releases = await GetAllReleases().ConfigureAwait(false);
var latestRelease = releases.First(x => includePrerelease || !x.Prerelease);
return latestRelease.TagName;
}
var allCommits = await GetAllCommits(installedPackage.Version.InstalledBranch)
.ConfigureAwait(false);
var latestCommit = allCommits.First();
return latestCommit.Sha;
}
private async Task RunInvokeCommand(
string installedPackagePath,
string command,
@ -320,7 +230,6 @@ public class InvokeAI : BaseGitPackage
arguments = command switch
{
"invokeai-configure" => "--yes --skip-sd-weights",
"invokeai-model-install" => "--yes",
_ => arguments
};
@ -343,6 +252,21 @@ public class InvokeAI : BaseGitPackage
// above the minimum in invokeai.frontend.install.widgets
var code = $"""
try:
import os
import shutil
from invokeai.frontend.install import widgets
_min_cols = widgets.MIN_COLS
_min_lines = widgets.MIN_LINES
static_size_fn = lambda: os.terminal_size((_min_cols, _min_lines))
shutil.get_terminal_size = static_size_fn
widgets.get_terminal_size = static_size_fn
except Exception as e:
import warnings
warnings.warn('Could not patch terminal size for InvokeAI' + str(e))
import sys
from {split[0]} import {split[1]}
sys.exit({split[1]}())

14
StabilityMatrix.Core/Models/Packages/StableDiffusionUx.cs

@ -68,9 +68,9 @@ public class StableDiffusionUx : BaseGitPackage
[SharedOutputType.Extras] = new[] { "outputs/extras-images" },
[SharedOutputType.Saved] = new[] { "log/images" },
[SharedOutputType.Img2Img] = new[] { "outputs/img2img-images" },
[SharedOutputType.Text2Img] = new[] { "outputs/text2img-images" },
[SharedOutputType.Text2Img] = new[] { "outputs/txt2img-images" },
[SharedOutputType.Img2ImgGrids] = new[] { "outputs/img2img-grids" },
[SharedOutputType.Text2ImgGrids] = new[] { "outputs/text2img-grids" }
[SharedOutputType.Text2ImgGrids] = new[] { "outputs/txt2img-grids" }
};
[SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeNotEvident")]
@ -180,11 +180,19 @@ public class StableDiffusionUx : BaseGitPackage
public override async Task InstallPackage(
string installLocation,
TorchVersion torchVersion,
DownloadPackageVersionOptions versionOptions,
IProgress<ProgressReport>? progress = null,
Action<ProcessOutput>? onConsoleOutput = null
)
{
await base.InstallPackage(installLocation, torchVersion, progress).ConfigureAwait(false);
await base.InstallPackage(
installLocation,
torchVersion,
versionOptions,
progress,
onConsoleOutput
)
.ConfigureAwait(false);
progress?.Report(new ProgressReport(-1f, "Setting up venv", isIndeterminate: true));
// Setup venv

2
StabilityMatrix.Core/Models/Packages/UnknownPackage.cs

@ -41,6 +41,7 @@ public class UnknownPackage : BasePackage
public override Task InstallPackage(
string installLocation,
TorchVersion torchVersion,
DownloadPackageVersionOptions versionOptions,
IProgress<ProgressReport>? progress = null,
Action<ProcessOutput>? onConsoleOutput = null
)
@ -120,6 +121,7 @@ public class UnknownPackage : BasePackage
public override Task<InstalledPackageVersion> Update(
InstalledPackage installedPackage,
TorchVersion torchVersion,
DownloadPackageVersionOptions versionOptions,
IProgress<ProgressReport>? progress = null,
bool includePrerelease = false,
Action<ProcessOutput>? onConsoleOutput = null

20
StabilityMatrix.Core/Models/Packages/VladAutomatic.cs

@ -174,6 +174,7 @@ public class VladAutomatic : BaseGitPackage
public override async Task InstallPackage(
string installLocation,
TorchVersion torchVersion,
DownloadPackageVersionOptions versionOptions,
IProgress<ProgressReport>? progress = null,
Action<ProcessOutput>? onConsoleOutput = null
)
@ -238,6 +239,7 @@ public class VladAutomatic : BaseGitPackage
await PrerequisiteHelper
.RunGit(
installDir.Parent ?? "",
null,
"clone",
"https://github.com/vladmandic/automatic",
installDir.Name
@ -245,7 +247,7 @@ public class VladAutomatic : BaseGitPackage
.ConfigureAwait(false);
await PrerequisiteHelper
.RunGit(installLocation, "checkout", downloadOptions.CommitHash)
.RunGit(installLocation, null, "checkout", downloadOptions.CommitHash)
.ConfigureAwait(false);
}
else if (!string.IsNullOrWhiteSpace(downloadOptions.BranchName))
@ -253,6 +255,7 @@ public class VladAutomatic : BaseGitPackage
await PrerequisiteHelper
.RunGit(
installDir.Parent ?? "",
null,
"clone",
"-b",
downloadOptions.BranchName,
@ -301,16 +304,12 @@ public class VladAutomatic : BaseGitPackage
public override async Task<InstalledPackageVersion> Update(
InstalledPackage installedPackage,
TorchVersion torchVersion,
DownloadPackageVersionOptions versionOptions,
IProgress<ProgressReport>? progress = null,
bool includePrerelease = false,
Action<ProcessOutput>? onConsoleOutput = null
)
{
if (installedPackage.Version is null)
{
throw new Exception("Version is null");
}
progress?.Report(
new ProgressReport(
-1f,
@ -321,7 +320,12 @@ public class VladAutomatic : BaseGitPackage
);
await PrerequisiteHelper
.RunGit(installedPackage.FullPath, "checkout", installedPackage.Version.InstalledBranch)
.RunGit(
installedPackage.FullPath,
onConsoleOutput,
"checkout",
versionOptions.BranchName
)
.ConfigureAwait(false);
var venvRunner = new PyVenvRunner(Path.Combine(installedPackage.FullPath!, "venv"));
@ -340,7 +344,7 @@ public class VladAutomatic : BaseGitPackage
return new InstalledPackageVersion
{
InstalledBranch = installedPackage.Version.InstalledBranch,
InstalledBranch = versionOptions.BranchName,
InstalledCommitSha = output.Replace(Environment.NewLine, "").Replace("\n", "")
};
}

10
StabilityMatrix.Core/Models/Packages/VoltaML.cs

@ -146,11 +146,19 @@ public class VoltaML : BaseGitPackage
public override async Task InstallPackage(
string installLocation,
TorchVersion torchVersion,
DownloadPackageVersionOptions versionOptions,
IProgress<ProgressReport>? progress = null,
Action<ProcessOutput>? onConsoleOutput = null
)
{
await base.InstallPackage(installLocation, torchVersion, progress).ConfigureAwait(false);
await base.InstallPackage(
installLocation,
torchVersion,
versionOptions,
progress,
onConsoleOutput
)
.ConfigureAwait(false);
// Setup venv
progress?.Report(new ProgressReport(-1, "Setting up venv", isIndeterminate: true));

Loading…
Cancel
Save