Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
Pat Hartl
86ab116af8 Delete archives via interceptor rather than relying on service override 2025-12-06 14:17:15 -06:00
3 changed files with 110 additions and 23 deletions

View file

@ -92,28 +92,6 @@ namespace LANCommander.Server.Services
await context.UpdateRelationshipAsync(a => a.StorageLocation);
});
}
public override async Task DeleteAsync(Archive archive)
{
FileHelpers.DeleteIfExists(await GetArchiveFileLocationAsync(archive));
await cache.ExpireGameCacheAsync(archive.GameId);
await cache.ExpireArchiveCacheAsync(archive.Id);
await base.DeleteAsync(archive);
}
public async Task DeleteAsync(Archive archive, StorageLocation storageLocation = null)
{
if (storageLocation == null)
FileHelpers.DeleteIfExists(await GetArchiveFileLocationAsync(archive));
else
FileHelpers.DeleteIfExists(GetArchiveFileLocation(archive, storageLocation));
await cache.ExpireGameCacheAsync(archive.GameId);
await base.DeleteAsync(archive);
}
public async Task<SDK.Models.Manifest.Game> ReadManifestAsync(string objectKey)
{

View file

@ -0,0 +1,104 @@
using LANCommander.Helpers;
using LANCommander.Server.Data.Models;
using LANCommander.Server.Services.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using ZiggyCreatures.Caching.Fusion;
namespace LANCommander.Server.Services.Interceptors
{
/// <summary>
/// Ensures the file for an archive is deleted whenever dropped from the database
/// Also ensures that when games/redistributables are deleted, archives are deleted as well
/// </summary>
public class DeleteArchiveInterceptor(
ArchiveService archiveService,
IFusionCache cache) : SaveChangesInterceptor
{
private readonly List<Archive> _pendingArchives = new();
public override async ValueTask<int> SavedChangesAsync(SaveChangesCompletedEventData eventData, int result,
CancellationToken cancellationToken = new())
{
foreach (var archive in _pendingArchives)
{
FileHelpers.DeleteIfExists(await archiveService.GetArchiveFileLocationAsync(archive));
await cache.ExpireGameCacheAsync(archive.GameId);
await cache.ExpireArchiveCacheAsync(archive.Id);
}
return await base.SavedChangesAsync(eventData, result, cancellationToken);
}
public override async ValueTask<InterceptionResult<int>> SavingChangesAsync(
DbContextEventData eventData,
InterceptionResult<int> result,
CancellationToken cancellationToken = default)
{
var context = eventData.Context;
if (context is null)
return await base.SavingChangesAsync(eventData, result, cancellationToken);
var deletedGames = context.ChangeTracker
.Entries<Game>()
.Where(e => e.State == EntityState.Deleted)
.Select(e => e.Entity)
.ToList();
foreach (var game in deletedGames)
{
var archives = context.Entry(game).Collection(g => g.Archives!);
if (!archives.IsLoaded)
await archives.LoadAsync(cancellationToken);
if (game.Archives != null)
foreach (var archive in game.Archives)
{
var entry = context.Entry(archive);
if (entry.State == EntityState.Deleted || entry.State == EntityState.Unchanged)
entry.State = EntityState.Deleted;
}
}
var deletedRedistributables = context.ChangeTracker
.Entries<Redistributable>()
.Where(e => e.State == EntityState.Deleted)
.Select(e => e.Entity)
.ToList();
foreach (var redistributable in deletedRedistributables)
{
var archives = context.Entry(redistributable).Collection(r => r.Archives!);
if (!archives.IsLoaded)
await archives.LoadAsync(cancellationToken);
if (redistributable.Archives != null)
foreach (var archive in redistributable.Archives)
{
var entry = context.Entry(archive);
if (entry.State == EntityState.Deleted || entry.State == EntityState.Unchanged)
entry.State = EntityState.Deleted;
}
}
foreach (var entry in context.ChangeTracker.Entries<Archive>())
{
var storageLocation = entry.Reference(a => a.StorageLocation);
if (!storageLocation.IsLoaded)
await storageLocation.LoadAsync(cancellationToken);
if (entry.State == EntityState.Deleted)
_pendingArchives.Add(entry.Entity);
}
return await base.SavingChangesAsync(eventData, result, cancellationToken);
}
}
}

View file

@ -3,6 +3,7 @@ using LANCommander.Server.Data;
using LANCommander.Server.Data.Enums;
using LANCommander.Server.Data.Models;
using LANCommander.Server.Services;
using LANCommander.Server.Services.Interceptors;
using LANCommander.Server.Services.Models;
using LANCommander.Server.Settings.Enums;
using LANCommander.Server.Settings.Models;
@ -18,7 +19,11 @@ public static class Database
{
public static WebApplicationBuilder AddDatabase(this WebApplicationBuilder builder, string[] args)
{
builder.Services.AddDbContextFactory<DatabaseContext>();
builder.Services.AddScoped<DeleteArchiveInterceptor>();
builder.Services.AddDbContextFactory<DatabaseContext>((sp, options) =>
{
options.AddInterceptors(sp.GetRequiredService<DeleteArchiveInterceptor>());
});
builder.Services.AddDbContext<DatabaseContext>();
builder.Services.AddDatabaseDeveloperPageExceptionFilter();