using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using isn.abst; using isn.Abstract; using isnd.Data; using isnd.Data.Catalog; using isnd.Data.Packages; using isnd.Entities; using isnd.Interfaces; using isnd.ViewModels; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using NuGet.Versioning; namespace isnd.Services { public class PackageManager : IPackageManager { public const string BASE_API_LEVEL = "3.0.0"; readonly ApplicationDbContext dbContext; public PackageManager(ApplicationDbContext dbContext, IOptions siteConfigOptionsOptions) { this.dbContext = dbContext; isndSettings = siteConfigOptionsOptions.Value; apiBase = isndSettings.ExternalUrl + Constants.ApiVersionPrefix; } public IEnumerable GetResources() { var res = new List { new Resource(apiBase + ApiConfig.Package, "PackagePublish/2.0.0") { Comment = "Package Publish service" }, // under dev, only leash in release mode new Resource(apiBase + ApiConfig.Package + "/{id}/{version}", "PackageDetailsUriTemplate/5.1.0") { Comment = "URI template used by NuGet Client to construct details URL for packages" }, new Resource(apiBase + ApiConfig.Content, "PackageBaseAddress/3.0.0") { Comment = "Package Base Address service - " + "Base URL of where NuGet packages are stored, in the format " // "https:///nupkg/{id-lower}/{version-lower}/{id-lower}.{version-lower}.nupkg" }, new Resource(apiBase + ApiConfig.AutoComplete, "SearchAutocompleteService/" + BASE_API_LEVEL) { Comment = "Auto complete service" }, new Resource(apiBase + ApiConfig.Search, "SearchQueryService/3.0.0-rc") { Comment = "Query endpoint of NuGet Search service (primary) used by RC clients" }, new Resource(apiBase + ApiConfig.Registration, "RegistrationsBaseUrl/Versioned") { Comment = "Base URL of storage where package registration info is stored. " + "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.StartsWith(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() }; } public string[] GetVersions( string id, NuGetVersion parsedVersion = null, 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==null || 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 string CatalogBaseUrl => apiBase; private IsndSettings isndSettings; private readonly string apiBase; public virtual async Task GetCatalogIndexAsync() { return await ÛpdateCatalogForAsync(null); } public async Task ÛpdateCatalogForAsync (Commit reason = null) { int i = 0; string baseid = apiBase + ApiConfig.Catalog; string bidreg = $"{apiBase}{ApiConfig.Registration}"; PackageRegistration index = new PackageRegistration(baseid); var scope = await dbContext.Commits.OrderBy(c => c.TimeStamp).ToArrayAsync(); i = isndSettings.CatalogPageLen; foreach (var commit in scope) { var validPkgs = (await dbContext.Packages .Include(po => po.Owner) .Include(pkg => pkg.Versions) .Include(pkg => pkg.LatestCommit) .ToArrayAsync()) .GroupBy((q) => q.Id); foreach (var pkgid in validPkgs) { CatalogPage page = index.Items.FirstOrDefault (p => p.GetPackageId() == pkgid.Key); if (page == null) { page = new CatalogPage(pkgid.Key, apiBase); index.Items.Add(page); } foreach (var pkgv in pkgid) { page.AddVersionRange(pkgv.Versions); } } reason = commit; i++; } return index; } public async Task DeletePackageAsync (string pkgid, string version, string type) { // TODO deletion on disk var commit = new Commit { Action = PackageAction.DeletePackage, TimeStamp = DateTimeOffset.Now.ToUniversalTime() }; 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(); await ÛpdateCatalogForAsync(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 async Task GetCatalogEntryAsync (string pkgId, string semver = null, string pkgType = null) { return (await dbContext.PackageVersions .Include(v => v.Package).Include(v => v.Package.Owner) .Where(v => v.PackageId == pkgId && v.FullString == semver).SingleOrDefaultAsync()).ToPackage( apiBase); } 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 SearchCatalogEntriesById (string pkgId, string semver, string pkgType, bool preRelease) { return dbContext.PackageVersions .Include(v => v.Package) .Include(v => v.Package.Owner) .Include(v => v.LatestCommit) .Where(v => v.PackageId == pkgId && semver == v.FullString && (pkgType == null || pkgType == v.Type) && (preRelease || !v.IsPrerelease)) .OrderByDescending(p => p.CommitNId) .Select(p => p.ToPackage(apiBase)) ; } 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 async Task GetPackageRegistrationIndexAsync (PackageRegistrationQuery query) { // RegistrationPageIndexAndQuery if (string.IsNullOrWhiteSpace(query.Query)) return null; query.Query = query.Query.ToLower(); var scope = await dbContext.Packages .Include(p => p.Versions) .Include(p => p.Owner) .Include(p => p.LatestCommit) .SingleOrDefaultAsync(p => p.Id.ToLower() == query.Query); if (scope==null) return null; if (scope.Versions.Count==0) return null; string bid = $"{apiBase}{ApiConfig.Registration}"; foreach (var version in scope.Versions) version.LatestCommit = dbContext.Commits.Single(c=>c.Id == version.CommitNId); return new PackageRegistration(apiBase, scope); } public async Task SearchPackageAsync(PackageRegistrationQuery query) { string bid = $"{apiBase}{ApiConfig.Registration}"; if (string.IsNullOrWhiteSpace(query.Query)) query.Query=""; var scope = dbContext.Packages .Include(p => p.Owner) .Include(p => p.Versions) .Include(p => p.LatestCommit) .Where(p => p.Id.StartsWith(query.Query) && (query.Prerelease || p.Versions.Any(p => !p.IsPrerelease))) .OrderBy(p => p.CommitNId); return new PackageSearchResult(await scope.Skip(query.Skip).Take(query.Take) .ToListAsync(), apiBase, scope.Count()); } } }