Fix release pruning and remove QKeeper reference
This commit is contained in:
@@ -20,7 +20,7 @@ Do not put SSH passwords, private keys, or local `.env` files into application r
|
||||
|
||||
Every app must choose stable values:
|
||||
|
||||
- `slug`: public Argus id. Use lowercase letters, digits, and hyphens only, for example `keeper-android` or `my-desktop-app`.
|
||||
- `slug`: public Argus id. Use lowercase letters, digits, and hyphens only, for example `sample-android` or `my-desktop-app`.
|
||||
- `name`: display name.
|
||||
- `summary`: short catalog text.
|
||||
- `description`: full catalog text.
|
||||
|
||||
+113
-14
@@ -1,3 +1,5 @@
|
||||
using System.Data;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using Argus.Contracts;
|
||||
using Argus.Data;
|
||||
@@ -331,11 +333,11 @@ static IOrderedEnumerable<AppRelease> OrderReleases(IEnumerable<AppRelease> rele
|
||||
.ThenByDescending(x => x.PublishedAt)
|
||||
.ThenByDescending(x => x.Id);
|
||||
|
||||
static void DeleteStoredPackages(PackageStorageService storageService, IEnumerable<AppRelease> releases)
|
||||
static void DeleteStoredPackages(PackageStorageService storageService, IEnumerable<string> storedRelativePaths)
|
||||
{
|
||||
foreach (var release in releases)
|
||||
foreach (var storedRelativePath in storedRelativePaths)
|
||||
{
|
||||
storageService.TryDelete(release.StoredRelativePath);
|
||||
storageService.TryDelete(storedRelativePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,15 +353,9 @@ static async Task PruneOldReleasesAsync(
|
||||
throw new ArgumentOutOfRangeException(nameof(maxRetainedReleasesPerApp));
|
||||
}
|
||||
|
||||
IQueryable<AppRelease> releasesQuery = dbContext.Releases;
|
||||
if (catalogAppId is { } appId)
|
||||
{
|
||||
releasesQuery = releasesQuery.Where(x => x.CatalogAppId == appId);
|
||||
}
|
||||
|
||||
var releases = await releasesQuery.ToListAsync(cancellationToken);
|
||||
var releases = await LoadReleasePruneCandidatesAsync(dbContext, catalogAppId, cancellationToken);
|
||||
var releasesToPrune = releases
|
||||
.GroupBy(x => x.CatalogAppId)
|
||||
.GroupBy(x => x.CatalogAppId, StringComparer.OrdinalIgnoreCase)
|
||||
.SelectMany(group => group
|
||||
.OrderByDescending(x => x.PublishedAt)
|
||||
.ThenByDescending(x => x.Id)
|
||||
@@ -371,9 +367,105 @@ static async Task PruneOldReleasesAsync(
|
||||
return;
|
||||
}
|
||||
|
||||
dbContext.Releases.RemoveRange(releasesToPrune);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
DeleteStoredPackages(storageService, releasesToPrune);
|
||||
await DeleteReleaseRowsAsync(dbContext, releasesToPrune.Select(x => x.RowId), cancellationToken);
|
||||
DeleteStoredPackages(storageService, releasesToPrune.Select(x => x.StoredRelativePath));
|
||||
}
|
||||
|
||||
static async Task<IReadOnlyList<ReleasePruneCandidate>> LoadReleasePruneCandidatesAsync(
|
||||
ArgusDbContext dbContext,
|
||||
Guid? catalogAppId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var connection = dbContext.Database.GetDbConnection();
|
||||
var mustClose = connection.State != ConnectionState.Open;
|
||||
|
||||
if (mustClose)
|
||||
{
|
||||
await connection.OpenAsync(cancellationToken);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await using var command = connection.CreateCommand();
|
||||
command.CommandText =
|
||||
"""
|
||||
SELECT rowid, "CatalogAppId", "StoredRelativePath", "PublishedAt", "Id"
|
||||
FROM "Releases"
|
||||
WHERE $catalogAppId IS NULL OR lower("CatalogAppId") = lower($catalogAppId)
|
||||
""";
|
||||
|
||||
var catalogAppIdParameter = command.CreateParameter();
|
||||
catalogAppIdParameter.ParameterName = "$catalogAppId";
|
||||
catalogAppIdParameter.Value = catalogAppId?.ToString("D") ?? (object)DBNull.Value;
|
||||
command.Parameters.Add(catalogAppIdParameter);
|
||||
|
||||
var candidates = new List<ReleasePruneCandidate>();
|
||||
await using var reader = await command.ExecuteReaderAsync(cancellationToken);
|
||||
while (await reader.ReadAsync(cancellationToken))
|
||||
{
|
||||
candidates.Add(new ReleasePruneCandidate(
|
||||
reader.GetInt64(0),
|
||||
reader.GetString(1),
|
||||
reader.GetString(2),
|
||||
DateTimeOffset.Parse(reader.GetString(3), CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind),
|
||||
Guid.Parse(reader.GetString(4))));
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (mustClose)
|
||||
{
|
||||
await connection.CloseAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static async Task DeleteReleaseRowsAsync(
|
||||
ArgusDbContext dbContext,
|
||||
IEnumerable<long> rowIds,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var connection = dbContext.Database.GetDbConnection();
|
||||
var mustClose = connection.State != ConnectionState.Open;
|
||||
|
||||
if (mustClose)
|
||||
{
|
||||
await connection.OpenAsync(cancellationToken);
|
||||
}
|
||||
|
||||
await using var transaction = await connection.BeginTransactionAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
await using var command = connection.CreateCommand();
|
||||
command.Transaction = transaction;
|
||||
command.CommandText = """DELETE FROM "Releases" WHERE rowid = $rowid""";
|
||||
|
||||
var rowIdParameter = command.CreateParameter();
|
||||
rowIdParameter.ParameterName = "$rowid";
|
||||
command.Parameters.Add(rowIdParameter);
|
||||
|
||||
foreach (var rowId in rowIds)
|
||||
{
|
||||
rowIdParameter.Value = rowId;
|
||||
await command.ExecuteNonQueryAsync(cancellationToken);
|
||||
}
|
||||
|
||||
await transaction.CommitAsync(cancellationToken);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await transaction.RollbackAsync(cancellationToken);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (mustClose)
|
||||
{
|
||||
await connection.CloseAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void SetPackageCacheHeaders(HttpContext httpContext, bool immutable)
|
||||
@@ -388,3 +480,10 @@ static EntityTagHeaderValue CreatePackageEntityTag(string sha256) =>
|
||||
|
||||
static string? NormalizeFilter(string? value) =>
|
||||
string.IsNullOrWhiteSpace(value) ? null : IdentifierUtility.Normalize(value, string.Empty);
|
||||
|
||||
sealed record ReleasePruneCandidate(
|
||||
long RowId,
|
||||
string CatalogAppId,
|
||||
string StoredRelativePath,
|
||||
DateTimeOffset PublishedAt,
|
||||
Guid Id);
|
||||
|
||||
Reference in New Issue
Block a user