Files
isn/src/isnd/Services/PackageManager.cs
2024-03-10 20:10:05 +00:00

294 lines
11 KiB
C#

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<IsndSettings> siteConfigOptionsOptions)
{
this.dbContext = dbContext;
isndSettings = siteConfigOptionsOptions.Value;
apiBase = isndSettings.ExternalUrl + Constants.ApiVersionPrefix;
}
public IEnumerable<Resource> GetResources()
{
var res = new List<Resource>
{
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://<host>/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<PackageRegistration> GetCatalogIndexAsync()
{
return await ÛpdateCatalogForAsync(null);
}
public async Task<PackageRegistration> Û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<PackageDeletionReport> 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<PackageVersion> 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<Data.Catalog.Package> 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<PackageDeletionReport> 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<Data.Catalog.Package> 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<PackageRegistration> 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<PackageSearchResult> 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());
}
}
}