using Launcher.Next.Core; namespace Launcher.Next.Infrastructure; /// /// Reads a catalog descriptor only. The returned source is later copied into /// local immutable staging by ; it is never /// an active launch root. /// public sealed class FileSystemCatalogPackageAcquirer(string catalogRoot) : IPackageCatalogPackageAcquirer { public async Task AcquireAsync(PackageRequirement requirement, CancellationToken cancellationToken) { var root = Path.GetFullPath(catalogRoot); var candidate = Path.GetFullPath(Path.Combine(root, requirement.PackageId, requirement.RequiredVersion)); if (!IsWithin(root, candidate) || !Directory.Exists(candidate) || IsReparsePoint(candidate)) return null; var manifestPath = Path.Combine(candidate, "package.manifest.json"); if (!File.Exists(manifestPath) || IsReparsePoint(manifestPath)) return null; var manifest = PackageManifestJson.Parse(await File.ReadAllTextAsync(manifestPath, cancellationToken)); return new CatalogPackage(manifest, candidate); } private static bool IsWithin(string root, string candidate) => candidate.StartsWith(Path.TrimEndingDirectorySeparator(root) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase); private static bool IsReparsePoint(string path) => (File.GetAttributes(path) & FileAttributes.ReparsePoint) != 0; }