using KGySoft.CoreLibraries; using NLog; using StabilityMatrix.Core.Extensions; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Models.FileInterfaces; using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Processes; namespace StabilityMatrix.Core.Models.Packages.Extensions; public abstract class GitPackageExtensionManager(IPrerequisiteHelper prerequisiteHelper) : IPackageExtensionManager { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); public abstract string RelativeInstallDirectory { get; } public virtual IEnumerable DefaultManifests { get; } = Enumerable.Empty(); protected virtual IEnumerable IndexRelativeDirectories => [RelativeInstallDirectory]; public abstract Task> GetManifestExtensionsAsync( ExtensionManifest manifest, CancellationToken cancellationToken = default ); /// Task> IPackageExtensionManager.GetManifestExtensionsAsync( ExtensionManifest manifest, CancellationToken cancellationToken ) { return GetManifestExtensionsAsync(manifest, cancellationToken); } protected virtual IEnumerable GetManifests(InstalledPackage installedPackage) { if (installedPackage.ExtraExtensionManifestUrls is not { } customUrls) { return DefaultManifests; } var manifests = DefaultManifests.ToList(); foreach (var url in customUrls) { if (!string.IsNullOrEmpty(url) && Uri.TryCreate(url, UriKind.Absolute, out var uri)) { manifests.Add(new ExtensionManifest(uri)); } } return manifests; } /// IEnumerable IPackageExtensionManager.GetManifests(InstalledPackage installedPackage) { return GetManifests(installedPackage); } /// public async Task> GetInstalledExtensionsAsync( InstalledPackage installedPackage, CancellationToken cancellationToken = default ) { if (installedPackage.FullPath is not { } packagePath) { return Enumerable.Empty(); } var extensions = new List(); // Search for installed extensions in the package's index directories. foreach ( var indexDirectory in IndexRelativeDirectories.Select( path => new DirectoryPath(packagePath, path) ) ) { cancellationToken.ThrowIfCancellationRequested(); // Check subdirectories of the index directory foreach (var subDirectory in indexDirectory.EnumerateDirectories()) { cancellationToken.ThrowIfCancellationRequested(); // Skip if not valid git repository if (await prerequisiteHelper.CheckIsGitRepository(subDirectory).ConfigureAwait(false) != true) continue; // Get git version var version = await prerequisiteHelper .GetGitRepositoryVersion(subDirectory) .ConfigureAwait(false); // Get git remote var remoteUrlResult = await prerequisiteHelper .GetGitRepositoryRemoteOriginUrl(subDirectory) .ConfigureAwait(false); extensions.Add( new InstalledPackageExtension { Paths = [subDirectory], Version = new PackageExtensionVersion { Tag = version.Tag, Branch = version.Branch, CommitSha = version.CommitSha }, GitRepositoryUrl = remoteUrlResult.IsSuccessExitCode ? remoteUrlResult.StandardOutput?.Trim() : null } ); } } return extensions; } /// public async Task InstallExtensionAsync( PackageExtension extension, InstalledPackage installedPackage, PackageExtensionVersion? version = null, IProgress? progress = null, CancellationToken cancellationToken = default ) { if (installedPackage.FullPath is not { } packagePath) throw new ArgumentException("Package must have a valid path.", nameof(installedPackage)); // Ensure type if (extension.InstallType?.ToLowerInvariant() != "git-clone") { throw new ArgumentException( $"Extension must have install type 'git-clone' but has '{extension.InstallType}'.", nameof(extension) ); } // Git clone all files var cloneRoot = new DirectoryPath(packagePath, RelativeInstallDirectory); foreach (var repositoryUri in extension.Files) { cancellationToken.ThrowIfCancellationRequested(); progress?.Report(new ProgressReport(0f, $"Cloning {repositoryUri}", isIndeterminate: true)); await prerequisiteHelper .CloneGitRepository(cloneRoot, repositoryUri.ToString(), version) .ConfigureAwait(false); progress?.Report(new ProgressReport(1f, $"Cloned {repositoryUri}")); } } /// public async Task UpdateExtensionAsync( InstalledPackageExtension installedExtension, InstalledPackage installedPackage, PackageExtensionVersion? version = null, IProgress? progress = null, CancellationToken cancellationToken = default ) { ArgumentNullException.ThrowIfNull(installedPackage.FullPath); foreach (var repoPath in installedExtension.Paths.OfType()) { cancellationToken.ThrowIfCancellationRequested(); // Check git if (!await prerequisiteHelper.CheckIsGitRepository(repoPath.FullPath).ConfigureAwait(false)) continue; // Get remote url var remoteUrlResult = await prerequisiteHelper .GetGitRepositoryRemoteOriginUrl(repoPath.FullPath) .EnsureSuccessExitCode() .ConfigureAwait(false); progress?.Report( new ProgressReport(0f, $"Updating git repository {repoPath.Name}", isIndeterminate: true) ); // If version not provided, use current branch if (version is null) { ArgumentNullException.ThrowIfNull(installedExtension.Version?.Branch); version = new PackageExtensionVersion { Branch = installedExtension.Version?.Branch }; } await prerequisiteHelper .UpdateGitRepository(repoPath, remoteUrlResult.StandardOutput!.Trim(), version) .ConfigureAwait(false); progress?.Report(new ProgressReport(1f, $"Updated git repository {repoPath.Name}")); } } /// public async Task UninstallExtensionAsync( InstalledPackageExtension installedExtension, InstalledPackage installedPackage, IProgress? progress = null, CancellationToken cancellationToken = default ) { foreach (var path in installedExtension.Paths.Where(p => p.Exists)) { cancellationToken.ThrowIfCancellationRequested(); if (path is DirectoryPath directoryPath) { await directoryPath .DeleteVerboseAsync(cancellationToken: cancellationToken) .ConfigureAwait(false); } else { await path.DeleteAsync().ConfigureAwait(false); } } } }