diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b96a8344..ab167e9b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Sync Jellyfin and Emby library name and type changes - Library type (movies, shows) can only be changed when synchronization is *disabled* for the library in ETV - Fix some sequential and scripted playout build failures when using playlists or marathons +- Fix erasing playout items and erasing playout items and history so all related data is also erased + - This includes rerun history, unscheduled gaps, build status ## [26.1.1] - 2026-01-08 ### Fixed diff --git a/ErsatzTV.Application/Scheduling/Commands/ErasePlayoutHistoryHandler.cs b/ErsatzTV.Application/Scheduling/Commands/ErasePlayoutHistoryHandler.cs index 6a686627d..ec901e26c 100644 --- a/ErsatzTV.Application/Scheduling/Commands/ErasePlayoutHistoryHandler.cs +++ b/ErsatzTV.Application/Scheduling/Commands/ErasePlayoutHistoryHandler.cs @@ -21,27 +21,43 @@ public class ErasePlayoutHistoryHandler(IDbContextFactory dbContextFa foreach (Playout playout in maybePlayout) { + await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken); + int nextSeed = new Random().Next(); playout.Seed = nextSeed; + + // this deletes the owned PlayoutAnchor playout.Anchor = null; + playout.OnDemandCheckpoint = null; + await dbContext.SaveChangesAsync(cancellationToken); - await dbContext.Database.ExecuteSqlAsync( - $"DELETE FROM PlayoutItem WHERE PlayoutId = {playout.Id}", - cancellationToken); + await dbContext.PlayoutItems + .Where(pi => pi.PlayoutId == playout.Id) + .ExecuteDeleteAsync(cancellationToken); + + await dbContext.PlayoutHistory + .Where(ph => ph.PlayoutId == playout.Id) + .ExecuteDeleteAsync(cancellationToken); + + await dbContext.PlayoutProgramScheduleItemAnchors + .Where(a => a.PlayoutId == playout.Id) + .ExecuteDeleteAsync(cancellationToken); + + await dbContext.RerunHistory + .Where(rh => rh.PlayoutId == playout.Id) + .ExecuteDeleteAsync(cancellationToken); - await dbContext.Database.ExecuteSqlAsync( - $"DELETE FROM PlayoutHistory WHERE PlayoutId = {playout.Id}", - cancellationToken); + await dbContext.PlayoutGaps + .Where(pg => pg.PlayoutId == playout.Id) + .ExecuteDeleteAsync(cancellationToken); - await dbContext.Database.ExecuteSqlAsync( - $"DELETE FROM PlayoutAnchor WHERE PlayoutId = {playout.Id}", - cancellationToken); + await dbContext.PlayoutBuildStatus + .Where(pb => pb.PlayoutId == playout.Id) + .ExecuteDeleteAsync(cancellationToken); - await dbContext.Database.ExecuteSqlAsync( - $"DELETE FROM PlayoutProgramScheduleAnchor WHERE PlayoutId = {playout.Id}", - cancellationToken); + await transaction.CommitAsync(cancellationToken); } } } diff --git a/ErsatzTV.Application/Scheduling/Commands/ErasePlayoutItemsHandler.cs b/ErsatzTV.Application/Scheduling/Commands/ErasePlayoutItemsHandler.cs index 3b8125634..b003b8e76 100644 --- a/ErsatzTV.Application/Scheduling/Commands/ErasePlayoutItemsHandler.cs +++ b/ErsatzTV.Application/Scheduling/Commands/ErasePlayoutItemsHandler.cs @@ -1,7 +1,5 @@ using ErsatzTV.Core.Domain; -using ErsatzTV.Core.Domain.Scheduling; using ErsatzTV.Infrastructure.Data; -using ErsatzTV.Infrastructure.Extensions; using Microsoft.EntityFrameworkCore; namespace ErsatzTV.Application.Scheduling; @@ -14,39 +12,49 @@ public class ErasePlayoutItemsHandler(IDbContextFactory dbContextFact await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); Option maybePlayout = await dbContext.Playouts - .Include(p => p.Items) - .Include(p => p.PlayoutHistory) + .TagWithCallSite() + .AsNoTracking() .Filter(p => p.ScheduleKind == PlayoutScheduleKind.Block || p.ScheduleKind == PlayoutScheduleKind.Sequential || p.ScheduleKind == PlayoutScheduleKind.Scripted) - .SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId, cancellationToken); + .SingleOrDefaultAsync(p => p.Id == request.PlayoutId, cancellationToken); foreach (Playout playout in maybePlayout) { + await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken); + // find the earliest item that finishes after "now" - Option maybeFirstItem = playout.Items - .Filter(i => i.FinishOffset > DateTimeOffset.Now) - .OrderBy(i => i.StartOffset) - .HeadOrNone(); - - // delete all history starting with that item - // importantly, do NOT delete earlier history - foreach (PlayoutItem item in maybeFirstItem) + Option maybeFirstItem = await dbContext.PlayoutItems + .TagWithCallSite() + .AsNoTracking() + .Where(pi => pi.PlayoutId == playout.Id) + .Where(pi => pi.Finish > DateTime.UtcNow) + .OrderBy(i => i.Start) + .FirstOrDefaultAsync(cancellationToken); + + foreach (PlayoutItem firstItem in maybeFirstItem) { - var toRemove = playout.PlayoutHistory.Filter(h => h.When >= item.Start).ToList(); - foreach (PlayoutHistory history in toRemove) - { - playout.PlayoutHistory.Remove(history); - } + // delete all history starting with that item + // importantly, do NOT delete earlier history + await dbContext.PlayoutHistory + .Where(ph => ph.PlayoutId == playout.Id) + .Where(ph => ph.When >= firstItem.Start) + .ExecuteDeleteAsync(cancellationToken); } - // save history changes - await dbContext.SaveChangesAsync(cancellationToken); + await dbContext.PlayoutItems + .Where(pi => pi.PlayoutId == playout.Id) + .ExecuteDeleteAsync(cancellationToken); + + await dbContext.PlayoutGaps + .Where(ph => ph.PlayoutId == playout.Id) + .ExecuteDeleteAsync(cancellationToken); + + await dbContext.PlayoutBuildStatus + .Where(pb => pb.PlayoutId == playout.Id) + .ExecuteDeleteAsync(cancellationToken); - // delete all playout items - await dbContext.Database.ExecuteSqlAsync( - $"DELETE FROM PlayoutItem WHERE PlayoutId = {playout.Id}", - cancellationToken); + await transaction.CommitAsync(cancellationToken); } } }