using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using isn.Abstract; using isnd.Data; using isnd.Data.Catalog; using isnd.Data.Packages; using isnd.Data.Packages.Catalog; using isnd.Entities; using isnd.Helpers; using isnd.Interfaces; using isnd.ViewModels; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using NuGet.Versioning; using Unleash; namespace isnd.Services { public class PackageManager : IPackageManager { public const string BASE_API_LEVEL = "3.5.0"; ApplicationDbContext dbContext; public PackageManager(ApplicationDbContext dbContext, IOptions siteConfigOptionsOptions) { this.dbContext = dbContext; isndSettings = siteConfigOptionsOptions.Value; extUrl = isndSettings.ExternalUrl + "/"; CurrentCatalogIndex = GetCatalogIndex(); } public IEnumerable GetResources(IUnleash unleashClient) { var res = new List(); // stable if (unleashClient.IsEnabled("pkg-push", true)) res.Add( new Resource { Id = extUrl + ApiConfig.Publish, Type = "PackagePublish/2.0.0", // TODO BASE_API_LEVEL Comment = "Package Publish service" }); // under dev, only leash in release mode if (unleashClient.IsEnabled("pkg-get", true)) res.Add( new Resource { Id = extUrl + ApiConfig.GetPackage, Type = "PackageBaseAddress/3.0.0", Comment = "Package Base Address service" }); if (unleashClient.IsEnabled("pkg-autocomplete", true)) res.Add( new Resource { Id = extUrl + ApiConfig.AutoComplete, Type = "SearchAutocompleteService/" + BASE_API_LEVEL, Comment = "Auto complete service" }); if (unleashClient.IsEnabled("pkg-search", true)) res.Add( new Resource { Id = extUrl + ApiConfig.Search, Type = "SearchQueryService/" + BASE_API_LEVEL, Comment = "Search Query service" }); if (unleashClient.IsEnabled("pkg-catalog", true)) res.Add( new Resource { Id = extUrl + ApiConfig.Catalog, Type = "Catalog/" + BASE_API_LEVEL, Comment = "Package Catalog Index" }); /* FIXME */ res.Add( new Resource { Id = extUrl + "v" + BASE_API_LEVEL + "/" + ApiConfig.Registration, Type = "RegistrationsBaseUrl", Comment = "Base URL of storage where isn package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages." }); res.Add( new Resource { Id = extUrl + "v3.0.0-beta/" + ApiConfig.Registration, Type = "RegistrationsBaseUrl/3.0.0-beta", Comment = "Base URL of storage where isn package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages." }); res.Add( new Resource { Id = extUrl + "v3.0.0-rc/" + ApiConfig.Registration, Type = "RegistrationsBaseUrl/3.0.0-rc", Comment = "Base URL of storage where isn package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages." }); res.Add(new Resource { Id = extUrl + "v3.4.0/" + ApiConfig.Registration, Type = "RegistrationsBaseUrl/3.4.0", Comment = "Base URL of storage where isn package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages." }); res.Add(new Resource { Id = extUrl + "v3.6.0/" + ApiConfig.Registration, Type = "RegistrationsBaseUrl/3.6.0", Comment = "Base URL of storage where isn package registration info is stored in GZIP format. This base URL includes SemVer 2.0.0 packages." }); return res; } public AutoCompleteResult AutoComplete(string id, int skip, int take, bool prerelease = false, string packageType = null) { var scope = dbContext.PackageVersions.Where( v => v.PackageId == id && (prerelease || !v.IsPrerelease) && (packageType == null || v.Type == packageType) ) .OrderBy(v => v.FullString); return new AutoCompleteResult { totalHits = scope.Count(), data = scope.Select(v => v.FullString) .Skip(skip).Take(take).ToArray() }; } // TODO stocker MetaData plutôt que FullString en base, // et en profiter pour corriger ce listing public string[] GetVersions( string id, NuGetVersion parsedVersion, bool prerelease = false, string packageType = null, int skip = 0, int take = 25) { return dbContext.PackageVersions.Where( v => v.PackageId == id && (prerelease || !v.IsPrerelease) && (packageType == null || v.Type == packageType) && (parsedVersion.CompareTo(new SemanticVersion(v.Major, v.Minor, v.Patch)) < 0) ) .OrderBy(v => v.NugetVersion) .Select(v => v.FullString) .Skip(skip).Take(take).ToArray(); } public static RegistrationPageIndex CurrentCatalogIndex { get; protected set; } public static List CurrentCatalogPages { get; protected set; } public string CatalogBaseUrl => extUrl; private IsndSettings isndSettings; private string extUrl; public virtual RegistrationPageIndex GetCatalogIndex() { if (CurrentCatalogIndex == null) { ÛpdateCatalogFor(); } return CurrentCatalogIndex; } public void ÛpdateCatalogFor(Commit reason = null) { int i = 0; int p = 0; var oldIndex = CurrentCatalogIndex; var oldPages = CurrentCatalogPages; string baseid = extUrl + ApiConfig.Catalog; string bidreg = $"{extUrl}v3.4.0/{ApiConfig.Registration}/"; string basepageid = extUrl + ApiConfig.CatalogPage; CurrentCatalogIndex = new RegistrationPageIndex(baseid); CurrentCatalogPages = new List(); var scope = dbContext.Commits.OrderBy(c => c.TimeStamp); RegistrationPage page = null; i = isndSettings.CatalogPageLen; foreach (var commit in scope) { if (i >= this.isndSettings.CatalogPageLen) { page = new RegistrationPage(basepageid + "-" + p++) { Parent = baseid, CommitId = commit.CommitId, CommitTimeStamp = commit.CommitTimeStamp }; CurrentCatalogPages.Add(page); var pageRef = new RegistrationPage(page.Id) { CommitId = commit.CommitId, CommitTimeStamp = commit.CommitTimeStamp }; CurrentCatalogIndex.Items.Add(pageRef); i = 0; } var validPkgs = dbContext.Packages .Include(po => po.Owner) .Include(pkg => pkg.Versions) .Include(pkg => pkg.LatestVersion) .Where( pkg => pkg.Versions.Count(v => v.CommitId == commit.CommitId) > 0 ).GroupBy((q)=> q.Id); // pkg.Versions.OrderByDescending(vi => vi.CommitNId).First().FullString foreach (var pkgid in validPkgs) { StringBuilder refid = new StringBuilder(bidreg); refid.AppendFormat("{0}/", pkgid.Key); /* var pkgref = new PackageRef { Version = v.FullString, LastCommit = v.LatestCommit, CommitId = v.LatestCommit.CommitId, CommitTimeStamp = v.LatestCommit.CommitTimeStamp, RefId = refid.ToString(), Id = v.PackageId, RefType = v.LatestCommit.Action == PackageAction.PublishPackage ? "nuget:PackageDetails" : "nuget:PackageDelete" }; */ foreach (var pkgv in pkgid) page.Items.Add(pkgv.ToLeave(bidreg)); } reason = commit; i++; } if (reason != null) { CurrentCatalogIndex.CommitId = reason.CommitId; } else { // From a fresh db CurrentCatalogIndex.CommitId = "none"; } } public async Task DeletePackageAsync(string pkgid, string version, string type) { // TODO deletion on disk var commit = new Commit { Action = PackageAction.DeletePackage, TimeStamp = DateTime.Now }; dbContext.Commits.Add(commit); var pkg = await dbContext.PackageVersions.SingleOrDefaultAsync( v => v.PackageId == pkgid && v.FullString == version && v.Type == type ); if (pkg == null) { return new PackageDeletionReport { Deleted = false }; } dbContext.PackageVersions.Remove(pkg); await dbContext.SaveChangesAsync(); ÛpdateCatalogFor(commit); return new PackageDeletionReport { Deleted = true, DeletedVersion = pkg }; } public async Task GetPackageAsync(string pkgid, string version, string type) { return await dbContext.PackageVersions.SingleOrDefaultAsync( v => v.PackageId == pkgid && v.FullString == version && v.Type == type ); } public IEnumerable GetCatalogLeaf(string pkgId, string semver, string pkgType) { return dbContext.PackageVersions .Include(v => v.Package) .Include(v => v.LatestCommit) .Where(v => v.PackageId == pkgId && (semver == null || semver.StartsWith(v.SementicVersionString)) && (pkgType == null || pkgType == v.Type)); } public async Task UserAskForPackageDeletionAsync(string uid, string id, string lower, string type) { PackageVersion packageVersion = await dbContext.PackageVersions .Include(pv => pv.Package) .FirstOrDefaultAsync(m => m.PackageId == id && m.FullString == lower && m.Type == type); if (packageVersion == null) return null; if (packageVersion.Package.OwnerId != uid) return null; return new PackageDeletionReport { Deleted = true, DeletedVersion = packageVersion }; } public IEnumerable SearchById(string pkgId, string semver, string pkgType) { string bid = $"{extUrl}v3.4.0/{ApiConfig.Registration}/"; return dbContext.PackageVersions .Include(v => v.Package) .Include(v => v.Package.Owner) .Include(v => v.LatestCommit) .Where(v => v.PackageId == pkgId && semver.StartsWith(v.SementicVersionString) && (pkgType == null || pkgType == v.Type)).Select(p => p.Package.ToLeave(bid)); } public PackageVersion GetPackage(string pkgId, string semver, string pkgType) { return dbContext.PackageVersions .Include(v => v.Package) .Include(v => v.LatestCommit) .Single(v => v.PackageId == pkgId && semver == v.FullString && (pkgType == null || pkgType == v.Type)); } public RegistrationPageIndexQuery GetPackageRegistrationIndex(RegistrationPageIndexQuery query) { // RegistrationPageIndexAndQuery var scope = dbContext.Packages .Include(p => p.Versions) .Include(p => p.Owner) .Where( p => (PackageIdHelpers.CamelCaseMatch(p.Id, query.Query) || PackageIdHelpers.SeparatedByMinusMatch(p.Id, query.Query)) && (query.Prerelease || p.Versions.Any(v => !v.IsPrerelease)) ); var total = scope.Count(); var pkgs = scope.Skip(query.Skip).Take(query.Take).ToArray(); string bid = $"{extUrl}v3.4.0/{ApiConfig.Registration}/"; var leaves = pkgs.Select(p => p.ToLeave(bid)); return new RegistrationPageIndexQuery(bid, leaves); } } }