using NLog; using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Helper.Factory; using StabilityMatrix.Core.Models; using StabilityMatrix.Core.Models.FileInterfaces; using StabilityMatrix.Core.Models.Packages; using StabilityMatrix.Core.ReparsePoints; using StabilityMatrix.Core.Services; namespace StabilityMatrix.Core.Helper; public class SharedFolders : ISharedFolders { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private readonly ISettingsManager settingsManager; private readonly IPackageFactory packageFactory; public SharedFolders(ISettingsManager settingsManager, IPackageFactory packageFactory) { this.settingsManager = settingsManager; this.packageFactory = packageFactory; } /// /// Platform redirect for junctions / symlinks /// private static void CreateLinkOrJunction(string junctionDir, string targetDir, bool overwrite) { if (Compat.IsWindows) { Junction.Create(junctionDir, targetDir, overwrite); } else { // Create parent directory if it doesn't exist, since CreateSymbolicLink doesn't seem to new DirectoryPath(junctionDir).Parent?.Create(); Directory.CreateSymbolicLink(junctionDir, targetDir); } } /// /// Creates or updates junction link from the source to the destination. /// Moves destination files to source if they exist. /// /// Shared source (i.e. "Models/") /// Destination (i.e. "webui/models/lora") /// Whether to overwrite the destination if it exists public static async Task CreateOrUpdateLink( DirectoryPath sourceDir, DirectoryPath destinationDir, bool overwrite = false ) { // Create source folder if it doesn't exist if (!sourceDir.Exists) { Logger.Info($"Creating junction source {sourceDir}"); sourceDir.Create(); } if (destinationDir.Exists) { // Existing dest is a link if (destinationDir.IsSymbolicLink) { // If link is already the same, just skip if (destinationDir.Info.LinkTarget == sourceDir) { Logger.Info( $"Skipped updating matching folder link ({destinationDir} -> ({sourceDir})" ); return; } // Otherwise delete the link Logger.Info($"Deleting existing junction at target {destinationDir}"); await destinationDir.DeleteAsync(false).ConfigureAwait(false); } else { // Move all files if not empty if (destinationDir.Info.EnumerateFileSystemInfos().Any()) { Logger.Info($"Moving files from {destinationDir} to {sourceDir}"); await FileTransfers .MoveAllFilesAndDirectories( destinationDir, sourceDir, overwriteIfHashMatches: true, overwrite: overwrite ) .ConfigureAwait(false); } Logger.Info($"Deleting existing empty folder at target {destinationDir}"); await destinationDir.DeleteAsync(false).ConfigureAwait(false); } } Logger.Info($"Updating junction link from {sourceDir} to {destinationDir}"); CreateLinkOrJunction(destinationDir, sourceDir, true); } [Obsolete("Use static methods instead")] public void SetupLinksForPackage(BasePackage basePackage, DirectoryPath installDirectory) { var modelsDirectory = new DirectoryPath(settingsManager.ModelsDirectory); var sharedFolders = basePackage.SharedFolders; if (sharedFolders == null) return; UpdateLinksForPackage(sharedFolders, modelsDirectory, installDirectory) .GetAwaiter() .GetResult(); } /// /// Updates or creates shared links for a package. /// Will attempt to move files from the destination to the source if the destination is not empty. /// public static async Task UpdateLinksForPackage( Dictionary> sharedFolders, DirectoryPath modelsDirectory, DirectoryPath installDirectory ) { foreach (var (folderType, relativePaths) in sharedFolders) { foreach (var relativePath in relativePaths) { var sourceDir = new DirectoryPath(modelsDirectory, folderType.GetStringValue()); var destinationDir = installDirectory.JoinDir(relativePath); await CreateOrUpdateLink(sourceDir, destinationDir).ConfigureAwait(false); } } } public static void RemoveLinksForPackage(BasePackage package, DirectoryPath installPath) { var sharedFolders = package.SharedFolders; if (sharedFolders == null) { return; } foreach (var (_, relativePaths) in sharedFolders) { foreach (var relativePath in relativePaths) { var destination = Path.GetFullPath(Path.Combine(installPath, relativePath)); // Delete the destination folder if it exists if (!Directory.Exists(destination)) continue; Logger.Info($"Deleting junction target {destination}"); Directory.Delete(destination, false); } } } public void RemoveLinksForAllPackages() { var packages = settingsManager.Settings.InstalledPackages; foreach (var package in packages) { try { if ( packageFactory.FindPackageByName(package.PackageName) is not { } basePackage || package.FullPath is null ) { continue; } var sharedFolderMethod = package.PreferredSharedFolderMethod ?? basePackage.RecommendedSharedFolderMethod; basePackage .RemoveModelFolderLinks(package.FullPath, sharedFolderMethod) .GetAwaiter() .GetResult(); } catch (Exception e) { Logger.Warn( "Failed to remove links for package {Package} " + "({DisplayName}): {Message}", package.PackageName, package.DisplayName, e.Message ); } } } public void SetupSharedModelFolders() { var modelsDir = settingsManager.ModelsDirectory; if (string.IsNullOrWhiteSpace(modelsDir)) return; Directory.CreateDirectory(modelsDir); var allSharedFolderTypes = Enum.GetValues(); foreach (var sharedFolder in allSharedFolderTypes) { var dir = new DirectoryPath(modelsDir, sharedFolder.GetStringValue()); dir.Create(); } } }