using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Threading.Tasks; namespace OpenTap.Package { class PackageDependencyCache { readonly string os; readonly CpuArchitecture deploymentInstallationArchitecture; readonly PackageDependencyGraph graph = new PackageDependencyGraph(); public PackageDependencyGraph Graph => graph; public List Repositories { get; } static readonly TraceSource log = Log.CreateSource("Package Query"); public PackageDependencyCache(string os, CpuArchitecture deploymentInstallationArchitecture, IEnumerable repositories = null) { graph.UpdatePrerelease = UpdatePrerelease; this.os = os; this.deploymentInstallationArchitecture = deploymentInstallationArchitecture; var urls = PackageManagerSettings.Current.GetEnabledRepositories(repositories).Select(x => x.Url).ToList(); Repositories = urls; } private void UpdatePrerelease(string name, string version) { var newGraphs = graphs .Where(g => repos[g] is IQueryPrereleases) .AsParallel() .Select(g => { var newGraph = (repos[g] as IQueryPrereleases).QueryPrereleases(os, deploymentInstallationArchitecture, version, name); g.Absorb(newGraph); return newGraph; }); foreach (var g in newGraphs) { Graph.Absorb(g); } } readonly List graphs = new List(); readonly Dictionary repos = new Dictionary(); public void LoadFromRepositories() { Exception GetInnermostException(Exception ex) { return ex switch { AggregateException aex when aex.InnerExceptions.Count == 1 => GetInnermostException( aex.InnerException), TargetInvocationException tex => GetInnermostException(tex.InnerException), _ => ex }; } var repositories = Repositories.Select(PackageRepositoryHelpers.DetermineRepositoryType).ToArray(); graphs.Clear(); var graphList = repositories.AsParallel().TrySelect(repo => (graph: GetGraph(repo), repo: repo), (ex, repo) => { var innermost = GetInnermostException(ex); if (innermost is TaskCanceledException or OperationCanceledException) { log.Warning($"Timeout while querying '{repo.Url}': {innermost.Message}", innermost); } else if (innermost is PackageQueryException) { log.Warning($"Error querying '{repo.Url}': {innermost.Message}", innermost); } else { log.Warning($"Repository is unreachable: {innermost.Message}", innermost); } }); foreach (var r in graphList) { if (r.graph == null) continue; // error while querying repo. graphs.Add(r.graph); repos[r.graph] = r.repo; graph.Absorb(r.graph); } } List addedPackages = new List(); public void AddPackages(IEnumerable packages) { var graph = new PackageDependencyGraph(); graph.LoadFromPackageDefs(packages); this.graph.Absorb(graph); addedPackages.AddRange(packages); } PackageDependencyGraph GetGraph(IPackageRepository repo) { if (repo is IQueryPrereleases q) return q.QueryPrereleases(os, deploymentInstallationArchitecture, "", null); if (repo is FilePackageRepository fpkg) { var sw = Stopwatch.StartNew(); var graph = new PackageDependencyGraph(); var packages = fpkg.GetAllPackages(TapThread.Current.AbortToken); graph.LoadFromPackageDefs(packages.Where(x => x.IsPlatformCompatible(deploymentInstallationArchitecture, os))); log.Debug(sw, "Read {1} packages from {0}", repo, packages.Length); return graph; } { // This occurs during unit testing when mock repositories are used. // UPDATE: No it doesn't. Mock Repository implements IQueryPrereleases. // Figure out if this is safe to delete. // IPackageRepository is public, so this code is probably not unreachable. var sw = Stopwatch.StartNew(); var graph = new PackageDependencyGraph(); var names = repo.GetPackageNames(); List packages = new List(); foreach (var name in names) { foreach (var version in repo.GetPackageVersions(name)) { var pkgs = repo.GetPackages( new PackageSpecifier(version.Name, version.Version.AsExactSpecifier(), version.Architecture, version.OS), TapThread.Current.AbortToken); packages.AddRange(pkgs); } } var compatiblePackages = packages .Where(x => x.IsPlatformCompatible(deploymentInstallationArchitecture, os)).ToArray(); graph.LoadFromPackageDefs(compatiblePackages); log.Debug(sw, "Read {1} packages from {0}", repo, packages.Count); return graph; } } public PackageDef GetPackageDef(PackageSpecifier packageSpecifier) { if (packageSpecifier.Version.TryAsExactSemanticVersion(out var v) == false) return null; var ps = new PackageSpecifier(packageSpecifier.Name, packageSpecifier.Version, deploymentInstallationArchitecture, os); foreach (var graph in graphs) { if (graph.HasPackage(packageSpecifier.Name, v)) { if (repos.TryGetValue(graph, out var repo)) { var pkgs = repo.GetPackages(ps); if (pkgs.FirstOrDefault() is PackageDef r) return r; } } } return addedPackages.FirstOrDefault(x => x.Name == packageSpecifier.Name && packageSpecifier.Version.IsSatisfiedBy(x.Version.AsExactSpecifier())); } } }