From eb29f6da790907b3a62314830dbb18483ff4739e Mon Sep 17 00:00:00 2001 From: Ionite Date: Mon, 26 Jun 2023 02:29:12 -0400 Subject: [PATCH 01/14] Add LibrarySettings --- StabilityMatrix/Models/LibrarySettings.cs | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 StabilityMatrix/Models/LibrarySettings.cs diff --git a/StabilityMatrix/Models/LibrarySettings.cs b/StabilityMatrix/Models/LibrarySettings.cs new file mode 100644 index 00000000..60f27ed3 --- /dev/null +++ b/StabilityMatrix/Models/LibrarySettings.cs @@ -0,0 +1,6 @@ +namespace StabilityMatrix.Models; + +public class LibrarySettings +{ + public string? LibraryPath { get; set; } +} From f9a0d232e49fe145c81d9babbf78e8fa0128a97f Mon Sep 17 00:00:00 2001 From: Ionite Date: Mon, 26 Jun 2023 03:29:03 -0400 Subject: [PATCH 02/14] Add dynamic library to SettingsManager --- StabilityMatrix/Helper/ISettingsManager.cs | 10 +- StabilityMatrix/Helper/SettingsManager.cs | 130 ++++++++++++++++----- 2 files changed, 106 insertions(+), 34 deletions(-) diff --git a/StabilityMatrix/Helper/ISettingsManager.cs b/StabilityMatrix/Helper/ISettingsManager.cs index b9c72db1..018775dd 100644 --- a/StabilityMatrix/Helper/ISettingsManager.cs +++ b/StabilityMatrix/Helper/ISettingsManager.cs @@ -7,12 +7,14 @@ namespace StabilityMatrix.Helper; public interface ISettingsManager { - public string AppDataDir { get; } - public string AppHomeDir { get; } - public string DatabasePath { get; } - Settings Settings { get; } event EventHandler? ModelBrowserNsfwEnabledChanged; + + bool IsPortableMode { get; } + string LibraryDir { get; } + string DatabasePath { get; } + bool TryFindLibrary(); + IEnumerable GetOldInstalledPackages(); void SetTheme(string theme); void AddInstalledPackage(InstalledPackage p); diff --git a/StabilityMatrix/Helper/SettingsManager.cs b/StabilityMatrix/Helper/SettingsManager.cs index 80c0319f..61be937f 100644 --- a/StabilityMatrix/Helper/SettingsManager.cs +++ b/StabilityMatrix/Helper/SettingsManager.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; +using NLog; using StabilityMatrix.Models; using Wpf.Ui.Controls.Window; @@ -12,49 +13,104 @@ namespace StabilityMatrix.Helper; public class SettingsManager : ISettingsManager { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private static readonly ReaderWriterLockSlim FileLock = new(); - /// - /// Directory of %AppData% - /// - // ReSharper disable once MemberCanBePrivate.Global - public string AppDataDir => Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - - /// - /// Directory of %AppData%\StabilityMatrix - /// - public string AppHomeDir => Path.Combine(AppDataDir, "StabilityMatrix"); + private readonly string? originalEnvPath = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Process); - /// - /// Path to database file - /// - public string DatabasePath => Path.Combine(AppHomeDir, "StabilityMatrix.db"); + // Library properties + public bool IsPortableMode { get; set; } + public string LibraryDir { get; set; } = string.Empty; - private const string SettingsFileName = "settings.json"; - private string SettingsPath => Path.Combine(AppHomeDir, SettingsFileName); - private readonly string? originalEnvPath = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Process); + // Dynamic paths from library + public string DatabasePath => Path.Combine(LibraryDir, "StabilityMatrix.db"); + private string SettingsPath => Path.Combine(LibraryDir, "settings.json"); public Settings Settings { get; private set; } = new(); - +$"" public event EventHandler? ModelBrowserNsfwEnabledChanged; - - public SettingsManager() + + /// + /// Attempts to locate and set the library path + /// Return true if found, false otherwise + /// + public bool TryFindLibrary() { - if (!Directory.Exists(SettingsPath.Replace(SettingsFileName, ""))) + // 1. Check portable mode + var appDir = AppContext.BaseDirectory; + IsPortableMode = File.Exists(Path.Combine(appDir, ".sm-portable")); + if (IsPortableMode) { - Directory.CreateDirectory(SettingsPath.Replace(SettingsFileName, "")); + LibraryDir = Path.Combine(appDir, "Data"); + return true; } - - if (!File.Exists(SettingsPath)) + + // 2. Check %APPDATA%/StabilityMatrix/library.json + var appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + var libraryJsonPath = Path.Combine(appDataDir, "StabilityMatrix", "library.json"); + if (File.Exists(libraryJsonPath)) { - File.Create(SettingsPath).Close(); - Settings.Theme = "Dark"; - Settings.WindowBackdropType = WindowBackdropType.Mica; - var defaultSettingsJson = JsonSerializer.Serialize(Settings); - File.WriteAllText(SettingsPath, defaultSettingsJson); + try + { + var libraryJson = File.ReadAllText(libraryJsonPath); + var library = JsonSerializer.Deserialize(libraryJson); + if (!string.IsNullOrWhiteSpace(library?.LibraryPath)) + { + LibraryDir = library.LibraryPath; + return true; + } + } + catch (Exception e) + { + Logger.Warn("Failed to read library.json in AppData: {Message}", e.Message); + } } - LoadSettings(); + return false; + } + + /// + /// Save a new library path to %APPDATA%/StabilityMatrix/library.json + /// + public void SetLibraryPath(string path) + { + var appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + var homeDir = Path.Combine(appDataDir, "StabilityMatrix"); + Directory.CreateDirectory(homeDir); + var libraryJsonPath = Path.Combine(homeDir, "library.json"); + + var library = new LibrarySettings { LibraryPath = path }; + var libraryJson = JsonSerializer.Serialize(library, new JsonSerializerOptions { WriteIndented = true }); + File.WriteAllText(libraryJsonPath, libraryJson); + } + + /// + /// Enable and create settings files for portable mode + /// This creates the ./Data directory and the `.sm-portable` marker file + /// + public void SetupPortableMode() + { + // Get app directory + var appDir = AppContext.BaseDirectory; + // Create data directory + var dataDir = Path.Combine(appDir, "Data"); + Directory.CreateDirectory(dataDir); + // Create marker file + File.Create(Path.Combine(dataDir, ".sm-portable")).Close(); + } + + /// + /// Iterable of installed packages using the old absolute path format. + /// Can be called with Any() to check if the user needs to migrate. + /// + public IEnumerable GetOldInstalledPackages() + { + var installed = Settings.InstalledPackages; + // Absolute paths are old formats requiring migration + foreach (var package in installed.Where(package => Path.IsPathRooted(package.Path))) + { + yield return package; + } } public void SetTheme(string theme) @@ -245,11 +301,25 @@ public class SettingsManager : ISettingsManager return Settings.SharedFolderVisibleCategories?.HasFlag(type) ?? false; } + /// + /// Loads settings from the settings file + /// If the settings file does not exist, it will be created with default values + /// private void LoadSettings() { FileLock.EnterReadLock(); try { + if (!File.Exists(SettingsPath)) + { + File.Create(SettingsPath).Close(); + Settings.Theme = "Dark"; + Settings.WindowBackdropType = WindowBackdropType.Mica; + var defaultSettingsJson = JsonSerializer.Serialize(Settings); + File.WriteAllText(SettingsPath, defaultSettingsJson); + return; + } + var settingsContent = File.ReadAllText(SettingsPath); Settings = JsonSerializer.Deserialize(settingsContent, new JsonSerializerOptions { From 34e385d9e56d2ea8b068f4d4d4f3bd69f464b422 Mon Sep 17 00:00:00 2001 From: Ionite Date: Mon, 26 Jun 2023 03:29:29 -0400 Subject: [PATCH 03/14] Refactor PrerequisiteHelper for dynamic library --- StabilityMatrix/Helper/PrerequisiteHelper.cs | 33 ++++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/StabilityMatrix/Helper/PrerequisiteHelper.cs b/StabilityMatrix/Helper/PrerequisiteHelper.cs index 1a5ba7a6..b87da0a9 100644 --- a/StabilityMatrix/Helper/PrerequisiteHelper.cs +++ b/StabilityMatrix/Helper/PrerequisiteHelper.cs @@ -23,26 +23,25 @@ public class PrerequisiteHelper : IPrerequisiteHelper 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 static readonly string AppDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - private static readonly string HomeDir = Path.Combine(AppDataDir, "StabilityMatrix"); + private string HomeDir => settingsManager.LibraryDir; - private static readonly string VcRedistDownloadPath = Path.Combine(HomeDir, "vcredist.x64.exe"); + private string VcRedistDownloadPath => Path.Combine(HomeDir, "vcredist.x64.exe"); - private static readonly string AssetsDir = Path.Combine(HomeDir, "Assets"); - private static readonly string SevenZipPath = Path.Combine(AssetsDir, "7za.exe"); + private string AssetsDir => Path.Combine(HomeDir, "Assets"); + private string SevenZipPath => Path.Combine(AssetsDir, "7za.exe"); - private static readonly string PythonDownloadPath = Path.Combine(AssetsDir, "python-3.10.11-embed-amd64.zip"); - private static readonly string PythonDir = Path.Combine(AssetsDir, "Python310"); - private static readonly string PythonDllPath = Path.Combine(PythonDir, "python310.dll"); - private static readonly string PythonLibraryZipPath = Path.Combine(PythonDir, "python310.zip"); - private static readonly string GetPipPath = Path.Combine(PythonDir, "get-pip.pyc"); + 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 static readonly string VenvTempDir = Path.Combine(PythonDir, "venv"); + private string VenvTempDir => Path.Combine(PythonDir, "venv"); - private static readonly string PortableGitInstallDir = Path.Combine(HomeDir, "PortableGit"); - private static readonly string PortableGitDownloadPath = Path.Combine(HomeDir, "PortableGit.7z.exe"); - private static readonly string GitExePath = Path.Combine(PortableGitInstallDir, "bin", "git.exe"); - public static readonly string GitBinPath = Path.Combine(PortableGitInstallDir, "bin"); + 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 PrerequisiteHelper(ILogger logger, IGitHubClient gitHubClient, IDownloadService downloadService, ISettingsManager settingsManager) @@ -204,11 +203,11 @@ public class PrerequisiteHelper : IPrerequisiteHelper { if (File.Exists(GitExePath)) { - logger.LogDebug($"Git already installed at {GitExePath}"); + logger.LogDebug("Git already installed at {GitExePath}", GitExePath); return; } - logger.LogInformation($"Git not found at {GitExePath}, downloading..."); + logger.LogInformation("Git not found at {GitExePath}, downloading...", GitExePath); var latestRelease = await gitHubClient.Repository.Release.GetLatest("git-for-windows", "git"); var portableGitUrl = latestRelease.Assets From 17ad19cd408bfc4d9c546a5c5d3a4bb041756946 Mon Sep 17 00:00:00 2001 From: Ionite Date: Mon, 26 Jun 2023 03:36:11 -0400 Subject: [PATCH 04/14] Refactor SharedFolders to use dynamic library path --- StabilityMatrix/Helper/ISettingsManager.cs | 11 ++++++++--- StabilityMatrix/Helper/SettingsManager.cs | 11 +++-------- StabilityMatrix/Models/Settings.cs | 1 - StabilityMatrix/Models/SharedFolders.cs | 2 +- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/StabilityMatrix/Helper/ISettingsManager.cs b/StabilityMatrix/Helper/ISettingsManager.cs index 018775dd..8706d404 100644 --- a/StabilityMatrix/Helper/ISettingsManager.cs +++ b/StabilityMatrix/Helper/ISettingsManager.cs @@ -10,12 +10,18 @@ public interface ISettingsManager Settings Settings { get; } event EventHandler? ModelBrowserNsfwEnabledChanged; + // Library settings bool IsPortableMode { get; } string LibraryDir { get; } - string DatabasePath { get; } bool TryFindLibrary(); - IEnumerable GetOldInstalledPackages(); + // Dynamic paths from library + string DatabasePath { get; } + string ModelsDirectory { get; } + + // Migration + IEnumerable GetOldInstalledPackages(); + void SetTheme(string theme); void AddInstalledPackage(InstalledPackage p); void RemoveInstalledPackage(InstalledPackage p); @@ -39,7 +45,6 @@ public interface ISettingsManager string? GetActivePackagePort(); void SetWebApiHost(string? host); void SetWebApiPort(string? port); - void SetModelsDirectory(string? directory); void SetFirstLaunchSetupComplete(bool firstLaunchSetupCompleted); void SetModelBrowserNsfwEnabled(bool value); void SetSharedFolderCategoryVisible(SharedFolderType type, bool visible); diff --git a/StabilityMatrix/Helper/SettingsManager.cs b/StabilityMatrix/Helper/SettingsManager.cs index 61be937f..544184f2 100644 --- a/StabilityMatrix/Helper/SettingsManager.cs +++ b/StabilityMatrix/Helper/SettingsManager.cs @@ -25,9 +25,10 @@ public class SettingsManager : ISettingsManager // Dynamic paths from library public string DatabasePath => Path.Combine(LibraryDir, "StabilityMatrix.db"); private string SettingsPath => Path.Combine(LibraryDir, "settings.json"); + public string ModelsDirectory => Path.Combine(LibraryDir, "Models"); public Settings Settings { get; private set; } = new(); -$"" + public event EventHandler? ModelBrowserNsfwEnabledChanged; /// @@ -260,13 +261,7 @@ $"" Settings.WebApiPort = port; SaveSettings(); } - - public void SetModelsDirectory(string? directory) - { - Settings.ModelsDirectory = directory; - SaveSettings(); - } - + public void SetFirstLaunchSetupComplete(bool value) { Settings.FirstLaunchSetupComplete = value; diff --git a/StabilityMatrix/Models/Settings.cs b/StabilityMatrix/Models/Settings.cs index 6ddb3f71..9beb1bc7 100644 --- a/StabilityMatrix/Models/Settings.cs +++ b/StabilityMatrix/Models/Settings.cs @@ -20,7 +20,6 @@ public class Settings public List? PathExtensions { get; set; } public string? WebApiHost { get; set; } public string? WebApiPort { get; set; } - public string ModelsDirectory { get; set; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StabilityMatrix", "Models"); public bool ModelBrowserNsfwEnabled { get; set; } public SharedFolderType? SharedFolderVisibleCategories { get; set; } = diff --git a/StabilityMatrix/Models/SharedFolders.cs b/StabilityMatrix/Models/SharedFolders.cs index 4e982c9a..4c693005 100644 --- a/StabilityMatrix/Models/SharedFolders.cs +++ b/StabilityMatrix/Models/SharedFolders.cs @@ -29,7 +29,7 @@ public class SharedFolders : ISharedFolders var provider = ReparsePointFactory.Provider; foreach (var (folderType, relativePath) in sharedFolders) { - var source = Path.Combine(settingsManager.Settings.ModelsDirectory, folderType.GetStringValue()); + var source = Path.Combine(settingsManager.ModelsDirectory, folderType.GetStringValue()); var destination = Path.GetFullPath(Path.Combine(installPath, relativePath)); // Create source folder if it doesn't exist if (!Directory.Exists(source)) From 5b54d838fa5bdf63b7fc606468264620c515bff6 Mon Sep 17 00:00:00 2001 From: Ionite Date: Mon, 26 Jun 2023 04:25:03 -0400 Subject: [PATCH 05/14] Set ArchiveHelper home dir from settingsmanager --- StabilityMatrix/Helper/ArchiveHelper.cs | 6 +++--- StabilityMatrix/Helper/SettingsManager.cs | 10 +++++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/StabilityMatrix/Helper/ArchiveHelper.cs b/StabilityMatrix/Helper/ArchiveHelper.cs index ac6f58a7..62686d94 100644 --- a/StabilityMatrix/Helper/ArchiveHelper.cs +++ b/StabilityMatrix/Helper/ArchiveHelper.cs @@ -19,9 +19,9 @@ public record struct ArchiveInfo(ulong Size, ulong CompressedSize); public static class ArchiveHelper { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - - private static readonly string AppDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - private static readonly string HomeDir = Path.Combine(AppDataDir, "StabilityMatrix"); + + // HomeDir is set by ISettingsManager.TryFindLibrary() + public static string HomeDir { get; set; } = string.Empty; public static string SevenZipPath => Path.Combine(HomeDir, "Assets", "7za.exe"); private static readonly Regex Regex7ZOutput = new(@"(?<=Size:\s*)\d+|(?<=Compressed:\s*)\d+"); diff --git a/StabilityMatrix/Helper/SettingsManager.cs b/StabilityMatrix/Helper/SettingsManager.cs index 544184f2..14a14529 100644 --- a/StabilityMatrix/Helper/SettingsManager.cs +++ b/StabilityMatrix/Helper/SettingsManager.cs @@ -43,6 +43,7 @@ public class SettingsManager : ISettingsManager if (IsPortableMode) { LibraryDir = Path.Combine(appDir, "Data"); + SetStaticLibraryPaths(); return true; } @@ -58,6 +59,7 @@ public class SettingsManager : ISettingsManager if (!string.IsNullOrWhiteSpace(library?.LibraryPath)) { LibraryDir = library.LibraryPath; + SetStaticLibraryPaths(); return true; } } @@ -66,10 +68,16 @@ public class SettingsManager : ISettingsManager Logger.Warn("Failed to read library.json in AppData: {Message}", e.Message); } } - return false; } + // Set static classes requiring library path + private void SetStaticLibraryPaths() + { + // ArchiveHelper + ArchiveHelper.HomeDir = LibraryDir; + } + /// /// Save a new library path to %APPDATA%/StabilityMatrix/library.json /// From 388ad4191ecd781195b116ccbb66af2f85263c87 Mon Sep 17 00:00:00 2001 From: Ionite Date: Mon, 26 Jun 2023 04:33:03 -0400 Subject: [PATCH 06/14] Set PyRunner.HomeDir from settings manager library --- StabilityMatrix/Helper/SettingsManager.cs | 3 ++- StabilityMatrix/Python/PyRunner.cs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/StabilityMatrix/Helper/SettingsManager.cs b/StabilityMatrix/Helper/SettingsManager.cs index 14a14529..c35c4731 100644 --- a/StabilityMatrix/Helper/SettingsManager.cs +++ b/StabilityMatrix/Helper/SettingsManager.cs @@ -7,6 +7,7 @@ using System.Text.Json.Serialization; using System.Threading; using NLog; using StabilityMatrix.Models; +using StabilityMatrix.Python; using Wpf.Ui.Controls.Window; namespace StabilityMatrix.Helper; @@ -74,8 +75,8 @@ public class SettingsManager : ISettingsManager // Set static classes requiring library path private void SetStaticLibraryPaths() { - // ArchiveHelper ArchiveHelper.HomeDir = LibraryDir; + PyRunner.HomeDir = LibraryDir; } /// diff --git a/StabilityMatrix/Python/PyRunner.cs b/StabilityMatrix/Python/PyRunner.cs index daf8874c..6cb155b5 100644 --- a/StabilityMatrix/Python/PyRunner.cs +++ b/StabilityMatrix/Python/PyRunner.cs @@ -14,9 +14,9 @@ public record struct PyVersionInfo(int Major, int Minor, int Micro, string Relea public class PyRunner : IPyRunner { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - - private static readonly string AppDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - private static readonly string HomeDir = Path.Combine(AppDataDir, "StabilityMatrix"); + + // Set by ISettingsManager.TryFindLibrary() + public static string HomeDir { get; set; } = string.Empty; public static string PythonDir => Path.Combine(HomeDir, "Assets", "Python310"); public static string PythonDllPath => Path.Combine(PythonDir, "python310.dll"); From 51c375682a849e5c9683f51829fd4931a030242d Mon Sep 17 00:00:00 2001 From: Ionite Date: Mon, 26 Jun 2023 04:39:38 -0400 Subject: [PATCH 07/14] Add settings button for opening library dir --- StabilityMatrix/SettingsPage.xaml | 26 +++++++++++++------ .../ViewModels/SettingsViewModel.cs | 6 +++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/StabilityMatrix/SettingsPage.xaml b/StabilityMatrix/SettingsPage.xaml index fc75fc2f..005bcd17 100644 --- a/StabilityMatrix/SettingsPage.xaml +++ b/StabilityMatrix/SettingsPage.xaml @@ -184,14 +184,24 @@ FontWeight="Bold" Margin="0,8" Text="Directories" /> - - - - - + + + + + + + + + + + + diff --git a/StabilityMatrix/ViewModels/SettingsViewModel.cs b/StabilityMatrix/ViewModels/SettingsViewModel.cs index b1fa4c1e..6dd06d5c 100644 --- a/StabilityMatrix/ViewModels/SettingsViewModel.cs +++ b/StabilityMatrix/ViewModels/SettingsViewModel.cs @@ -301,6 +301,12 @@ public partial class SettingsViewModel : ObservableObject var appPath = Path.Combine(appDataPath, "StabilityMatrix"); Process.Start("explorer.exe", appPath); } + + [RelayCommand] + private void OpenLibraryDirectory() + { + Process.Start("explorer.exe", settingsManager.LibraryDir); + } [RelayCommand] private async Task OpenLicenseDialog() From 07ecf8e576634275a3b772ca59f865a7e99eac98 Mon Sep 17 00:00:00 2001 From: Ionite Date: Mon, 26 Jun 2023 15:52:27 -0400 Subject: [PATCH 08/14] Git runner refactors for dynamic library paths --- StabilityMatrix/Helper/IPrerequisiteHelper.cs | 7 +++++++ StabilityMatrix/Helper/PrerequisiteHelper.cs | 6 ++++++ StabilityMatrix/Models/Packages/VladAutomatic.cs | 13 +++---------- .../ViewModels/CheckpointBrowserCardViewModel.cs | 2 +- .../ViewModels/CheckpointManagerViewModel.cs | 2 +- StabilityMatrix/ViewModels/TextToImageViewModel.cs | 2 +- 6 files changed, 19 insertions(+), 13 deletions(-) diff --git a/StabilityMatrix/Helper/IPrerequisiteHelper.cs b/StabilityMatrix/Helper/IPrerequisiteHelper.cs index efc40c39..1bb577ad 100644 --- a/StabilityMatrix/Helper/IPrerequisiteHelper.cs +++ b/StabilityMatrix/Helper/IPrerequisiteHelper.cs @@ -6,11 +6,18 @@ namespace StabilityMatrix.Helper; public interface IPrerequisiteHelper { + string GitBinPath { get; } + Task InstallAllIfNecessary(IProgress? progress = null); Task UnpackResourcesIfNecessary(IProgress? progress = null); Task InstallGitIfNecessary(IProgress? progress = null); Task InstallVcRedistIfNecessary(IProgress? progress = null); + /// + /// Run embedded git with the given arguments. + /// + Task RunGit(params string[] args); + Task SetupPythonDependencies(string installLocation, string requirementsFileName, IProgress? progress = null, Action? onConsoleOutput = null); } diff --git a/StabilityMatrix/Helper/PrerequisiteHelper.cs b/StabilityMatrix/Helper/PrerequisiteHelper.cs index b87da0a9..f4e0a8a2 100644 --- a/StabilityMatrix/Helper/PrerequisiteHelper.cs +++ b/StabilityMatrix/Helper/PrerequisiteHelper.cs @@ -51,6 +51,12 @@ public class PrerequisiteHelper : IPrerequisiteHelper this.downloadService = downloadService; this.settingsManager = settingsManager; } + + public async Task RunGit(params string[] args) + { + var process = ProcessRunner.StartProcess(GitExePath, args); + await ProcessRunner.WaitForExitConditionAsync(process); + } public async Task InstallAllIfNecessary(IProgress? progress = null) { diff --git a/StabilityMatrix/Models/Packages/VladAutomatic.cs b/StabilityMatrix/Models/Packages/VladAutomatic.cs index 9f99fd39..29398912 100644 --- a/StabilityMatrix/Models/Packages/VladAutomatic.cs +++ b/StabilityMatrix/Models/Packages/VladAutomatic.cs @@ -101,16 +101,9 @@ public class VladAutomatic : BaseGitPackage progress?.Report(new ProgressReport(0.1f, message: "Downloading package...", isIndeterminate: true, type: ProgressType.Download)); Directory.CreateDirectory(InstallLocation); - - var gitCloneProcess = - ProcessRunner.StartProcess(Path.Combine(Helper.PrerequisiteHelper.GitBinPath, "git.exe"), - "clone https://github.com/vladmandic/automatic.git .", InstallLocation); - await gitCloneProcess.WaitForExitAsync(); - - var gitCheckoutProcess = - ProcessRunner.StartProcess(Path.Combine(Helper.PrerequisiteHelper.GitBinPath, "git.exe"), - $"checkout {version}", InstallLocation); - await gitCheckoutProcess.WaitForExitAsync(); + + await PrerequisiteHelper.RunGit("clone", "https://github.com/vladmandic/automatic.git", InstallLocation); + await PrerequisiteHelper.RunGit("checkout", version, InstallLocation); return version; } diff --git a/StabilityMatrix/ViewModels/CheckpointBrowserCardViewModel.cs b/StabilityMatrix/ViewModels/CheckpointBrowserCardViewModel.cs index e298088f..9d019957 100644 --- a/StabilityMatrix/ViewModels/CheckpointBrowserCardViewModel.cs +++ b/StabilityMatrix/ViewModels/CheckpointBrowserCardViewModel.cs @@ -88,7 +88,7 @@ public partial class CheckpointBrowserCardViewModel : ProgressViewModel var latestModelFile = latestVersion.Files[0]; var fileExpectedSha256 = latestModelFile.Hashes.SHA256; - var downloadFolder = Path.Combine(settingsManager.Settings.ModelsDirectory, + var downloadFolder = Path.Combine(settingsManager.ModelsDirectory, model.Type.ConvertTo().GetStringValue()); // Folders might be missing if user didn't install any packages yet Directory.CreateDirectory(downloadFolder); diff --git a/StabilityMatrix/ViewModels/CheckpointManagerViewModel.cs b/StabilityMatrix/ViewModels/CheckpointManagerViewModel.cs index 3fda6787..8d18d283 100644 --- a/StabilityMatrix/ViewModels/CheckpointManagerViewModel.cs +++ b/StabilityMatrix/ViewModels/CheckpointManagerViewModel.cs @@ -27,7 +27,7 @@ public partial class CheckpointManagerViewModel : ObservableObject public async Task OnLoaded() { - var modelsDirectory = settingsManager.Settings.ModelsDirectory; + var modelsDirectory = settingsManager.ModelsDirectory; // Get all folders within the shared folder root if (string.IsNullOrWhiteSpace(modelsDirectory)) { diff --git a/StabilityMatrix/ViewModels/TextToImageViewModel.cs b/StabilityMatrix/ViewModels/TextToImageViewModel.cs index 6c4c52e1..2d459b50 100644 --- a/StabilityMatrix/ViewModels/TextToImageViewModel.cs +++ b/StabilityMatrix/ViewModels/TextToImageViewModel.cs @@ -97,7 +97,7 @@ public partial class TextToImageViewModel : ObservableObject } // Set the diffusion checkpoint folder - var sdModelsDir = Path.Join(settingsManager.Settings.ModelsDirectory, SharedFolderType.StableDiffusion.GetStringValue()); + var sdModelsDir = Path.Join(settingsManager.ModelsDirectory, SharedFolderType.StableDiffusion.GetStringValue()); if (!Directory.Exists(sdModelsDir)) { logger.LogWarning("Skipped model folder index - {SdModelsDir} does not exist", sdModelsDir); From 7da782e1d0d005310bba0eff0f078962c61af99e Mon Sep 17 00:00:00 2001 From: Ionite Date: Mon, 26 Jun 2023 16:15:54 -0400 Subject: [PATCH 09/14] Fix unit tests for dynamic library path --- StabilityMatrix.Tests/Python/PyRunnerTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/StabilityMatrix.Tests/Python/PyRunnerTests.cs b/StabilityMatrix.Tests/Python/PyRunnerTests.cs index 571e3c0e..71cc0090 100644 --- a/StabilityMatrix.Tests/Python/PyRunnerTests.cs +++ b/StabilityMatrix.Tests/Python/PyRunnerTests.cs @@ -11,6 +11,8 @@ public class PyRunnerTests [ClassInitialize] public static async Task TestInitialize(TestContext testContext) { + var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + PyRunner.HomeDir = Path.Combine(appData, "StabilityMatrix"); await PyRunner.Initialize(); } From fa5c374d474ba3a0d990cdc3f131b5d050575859 Mon Sep 17 00:00:00 2001 From: Ionite Date: Mon, 26 Jun 2023 16:16:20 -0400 Subject: [PATCH 10/14] Create settings init function in App --- StabilityMatrix/App.xaml.cs | 29 ++++++++++++++++++++++- StabilityMatrix/Helper/SettingsManager.cs | 4 ++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/StabilityMatrix/App.xaml.cs b/StabilityMatrix/App.xaml.cs index 99c318a4..64039843 100644 --- a/StabilityMatrix/App.xaml.cs +++ b/StabilityMatrix/App.xaml.cs @@ -139,6 +139,33 @@ namespace StabilityMatrix return logConfig; } + // Find library and initialize settings + private static SettingsManager CreateSettingsManager() + { + var settings = new SettingsManager(); + var found = settings.TryFindLibrary(); + // Not found, we need to show dialog to choose library location + if (!found) + { + // See if this is an existing user for message variation + var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + var settingsPath = Path.Combine(appDataPath, "StabilityMatrix", "settings.json"); + var isExistingUser = File.Exists(settingsPath); + + // TODO: Show dialog + + // 1. For portable mode, call settings.SetPortableMode() + // 2. For custom path, call settings.SetLibraryPath(path) + + // TryFindLibrary should succeed now unless weird issue + if (!settings.TryFindLibrary()) + { + throw new Exception("Could not set library path."); + } + } + return settings; + } + private void App_OnStartup(object sender, StartupEventArgs e) { var serviceCollection = new ServiceCollection(); @@ -177,7 +204,7 @@ namespace StabilityMatrix serviceCollection.Configure(Config.GetSection(nameof(DebugOptions))); - var settingsManager = new SettingsManager(); + var settingsManager = CreateSettingsManager(); serviceCollection.AddSingleton(settingsManager); serviceCollection.AddSingleton(); diff --git a/StabilityMatrix/Helper/SettingsManager.cs b/StabilityMatrix/Helper/SettingsManager.cs index c35c4731..dc771d3b 100644 --- a/StabilityMatrix/Helper/SettingsManager.cs +++ b/StabilityMatrix/Helper/SettingsManager.cs @@ -96,9 +96,9 @@ public class SettingsManager : ISettingsManager /// /// Enable and create settings files for portable mode - /// This creates the ./Data directory and the `.sm-portable` marker file + /// Creates the ./Data directory and the `.sm-portable` marker file /// - public void SetupPortableMode() + public void SetPortableMode() { // Get app directory var appDir = AppContext.BaseDirectory; From 50425c7755b7e80e8c2858d27a15da3d3376c695 Mon Sep 17 00:00:00 2001 From: Ionite Date: Mon, 26 Jun 2023 17:27:56 -0400 Subject: [PATCH 11/14] Add Global config for library dir --- StabilityMatrix/Helper/SettingsManager.cs | 1 + StabilityMatrix/Models/GlobalConfig.cs | 26 ++++++++++++++++++++++ StabilityMatrix/Models/InstalledPackage.cs | 19 ++++++++++++---- 3 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 StabilityMatrix/Models/GlobalConfig.cs diff --git a/StabilityMatrix/Helper/SettingsManager.cs b/StabilityMatrix/Helper/SettingsManager.cs index dc771d3b..6e0e4a7e 100644 --- a/StabilityMatrix/Helper/SettingsManager.cs +++ b/StabilityMatrix/Helper/SettingsManager.cs @@ -75,6 +75,7 @@ public class SettingsManager : ISettingsManager // Set static classes requiring library path private void SetStaticLibraryPaths() { + GlobalConfig.LibraryDir = LibraryDir; ArchiveHelper.HomeDir = LibraryDir; PyRunner.HomeDir = LibraryDir; } diff --git a/StabilityMatrix/Models/GlobalConfig.cs b/StabilityMatrix/Models/GlobalConfig.cs new file mode 100644 index 00000000..afc79cf7 --- /dev/null +++ b/StabilityMatrix/Models/GlobalConfig.cs @@ -0,0 +1,26 @@ +using System; + +namespace StabilityMatrix.Models; + +public static class GlobalConfig +{ + private static string? libraryDir; + + /// + /// Absolute path to the library directory. + /// Needs to be set by SettingsManager.TryFindLibrary() before being accessed. + /// + /// + public static string LibraryDir + { + get + { + if (string.IsNullOrEmpty(libraryDir)) + { + throw new Exception("GlobalConfig.LibraryDir was not set before being accessed."); + } + return libraryDir; + } + set => libraryDir = value; + } +} diff --git a/StabilityMatrix/Models/InstalledPackage.cs b/StabilityMatrix/Models/InstalledPackage.cs index e8495e56..98a9f1b9 100644 --- a/StabilityMatrix/Models/InstalledPackage.cs +++ b/StabilityMatrix/Models/InstalledPackage.cs @@ -18,14 +18,25 @@ public class InstalledPackage // Package version public string? PackageVersion { get; set; } public string? InstalledBranch { get; set; } - public string? DisplayVersion { get; set; } - - // Install path + + // Old type absolute path + [Obsolete("Use LibraryPath instead. (Kept for migration)")] public string? Path { get; set; } + + /// + /// Relative path from the library root. + /// + public string? LibraryPath { get; set; } + + /// + /// Full path to the package, using LibraryPath and GlobalConfig.LibraryDir. + /// + public string? FullPath => LibraryPath != null ? System.IO.Path.Combine(GlobalConfig.LibraryDir, LibraryPath) : null; + public string? LaunchCommand { get; set; } public List? LaunchArgs { get; set; } public DateTimeOffset? LastUpdateCheck { get; set; } - [JsonIgnore] public bool UpdateAvailable { get; set; } = false; + [JsonIgnore] public bool UpdateAvailable { get; set; } } From 5ebb929cb8e837fc99bfab96d56df333027eb03f Mon Sep 17 00:00:00 2001 From: Ionite Date: Mon, 26 Jun 2023 17:28:07 -0400 Subject: [PATCH 12/14] Add Settings version property --- StabilityMatrix/Models/Settings.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/StabilityMatrix/Models/Settings.cs b/StabilityMatrix/Models/Settings.cs index 9beb1bc7..5d3a06a3 100644 --- a/StabilityMatrix/Models/Settings.cs +++ b/StabilityMatrix/Models/Settings.cs @@ -10,6 +10,7 @@ namespace StabilityMatrix.Models; public class Settings { + public int? Version { get; set; } = 1; public bool FirstLaunchSetupComplete { get; set; } public string? Theme { get; set; } public WindowBackdropType? WindowBackdropType { get; set; } From 62b7b3bae90e3b6ffad494b42c80b365a3eb8396 Mon Sep 17 00:00:00 2001 From: Ionite Date: Tue, 27 Jun 2023 01:32:12 -0400 Subject: [PATCH 13/14] Add GetSubPath tests --- .../Models/InstalledPackageTests.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 StabilityMatrix.Tests/Models/InstalledPackageTests.cs diff --git a/StabilityMatrix.Tests/Models/InstalledPackageTests.cs b/StabilityMatrix.Tests/Models/InstalledPackageTests.cs new file mode 100644 index 00000000..4fe4985c --- /dev/null +++ b/StabilityMatrix.Tests/Models/InstalledPackageTests.cs @@ -0,0 +1,18 @@ +using StabilityMatrix.Models; + +namespace StabilityMatrix.Tests.Models; + +[TestClass] +public class InstalledPackageTests +{ + [DataTestMethod] + [DataRow("C:\\User\\AppData\\StabilityMatrix", "C:\\User\\Other", null)] + [DataRow("C:\\Data", "D:\\Data\\abc", null)] + [DataRow("C:\\Data", "C:\\Data\\abc", "abc")] + [DataRow("C:\\User\\AppData\\StabilityMatrix", "C:\\User\\AppData\\StabilityMatrix\\Packages\\abc", "Packages\\abc")] + public void TestGetSubPath(string relativeTo, string path, string? expected) + { + var result = InstalledPackage.GetSubPath(relativeTo, path); + Assert.AreEqual(expected, result); + } +} From 6efb55020c1026d8856ee657bc948259ce5735ef Mon Sep 17 00:00:00 2001 From: Ionite Date: Tue, 27 Jun 2023 01:45:35 -0400 Subject: [PATCH 14/14] Add migration methods --- StabilityMatrix/Models/InstalledPackage.cs | 90 ++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/StabilityMatrix/Models/InstalledPackage.cs b/StabilityMatrix/Models/InstalledPackage.cs index 98a9f1b9..a5753310 100644 --- a/StabilityMatrix/Models/InstalledPackage.cs +++ b/StabilityMatrix/Models/InstalledPackage.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.IO; using System.Text.Json.Serialization; +using System.Threading.Tasks; namespace StabilityMatrix.Models; @@ -39,4 +41,92 @@ public class InstalledPackage public DateTimeOffset? LastUpdateCheck { get; set; } [JsonIgnore] public bool UpdateAvailable { get; set; } + + /// + /// Get the path as a relative sub-path of the relative path. + /// If not a sub-path, return null. + /// + internal static string? GetSubPath(string relativeTo, string path) + { + var relativePath = System.IO.Path.GetRelativePath(relativeTo, path); + // GetRelativePath returns the path if it's not relative + if (relativePath == path) return null; + // Further check if the path is a sub-path of the library + var isSubPath = relativePath != "." + && relativePath != ".." + && !relativePath.StartsWith("..\\") + && !System.IO.Path.IsPathRooted(relativePath); + return isSubPath ? relativePath : null; + } + + /// + /// Migrates the old Path to the new LibraryPath. + /// If libraryDirectory is null, GlobalConfig.LibraryDir is used. + /// + /// True if the path was migrated, false otherwise. + public bool TryPureMigratePath(string? libraryDirectory = null) + { +#pragma warning disable CS0618 + var oldPath = Path; +#pragma warning restore CS0618 + if (oldPath == null) return false; + + // Check if the path is a sub-path of the library + var library = libraryDirectory ?? GlobalConfig.LibraryDir; + var relativePath = GetSubPath(library, oldPath); + + // If so we migrate without any IO operations + if (relativePath != null) + { + LibraryPath = relativePath; +#pragma warning disable CS0618 + Path = null; +#pragma warning restore CS0618 + return true; + } + + return false; + } + + /// + /// Migrate the old Path to the new LibraryPath. + /// If libraryDirectory is null, GlobalConfig.LibraryDir is used. + /// Will move the package directory to Library/Packages if not relative. + /// + public async Task MigratePath(string? libraryDirectory = null) + { +#pragma warning disable CS0618 + var oldPath = Path; +#pragma warning restore CS0618 + if (oldPath == null) return; + + // Try using pure migration first + if (TryPureMigratePath(libraryDirectory)) return; + + // If not, we need to move the package directory + var packageFolderName = new DirectoryInfo(oldPath).Name; + + // Get the new Library/Packages path + var library = libraryDirectory ?? GlobalConfig.LibraryDir; + var newPackagesDir = System.IO.Path.Combine(library, "Packages"); + + // Get the new target path + var newPackagePath = System.IO.Path.Combine(newPackagesDir, packageFolderName); + // Ensure it is not already there, if so, add a suffix until it's not + var suffix = 2; + while (Directory.Exists(newPackagePath)) + { + newPackagePath = System.IO.Path.Combine(newPackagesDir, $"{packageFolderName}-{suffix}"); + suffix++; + } + + // Move the package directory + await Task.Run(() => Directory.Move(oldPath, newPackagePath)); + + // Update the paths +#pragma warning disable CS0618 + Path = null; +#pragma warning restore CS0618 + LibraryPath = System.IO.Path.Combine("Packages", packageFolderName); + } }