From a24592a8c45b06aea7a4b59bdc1c110c64f6ab32 Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Fri, 2 Aug 2024 10:46:52 -0500 Subject: [PATCH] add database cleaner (#1853) * fix broken tests * add database cleaner --- .../Scheduling/PlaylistEnumeratorTests.cs | 2 + ErsatzTV.Core/Domain/MediaItem/MediaItem.cs | 2 +- ErsatzTV.Core/SystemStartup.cs | 14 ++++++ .../Health/Checks/UnavailableHealthCheck.cs | 4 +- .../RunOnce/DatabaseCleanerService.cs | 50 +++++++++++++++++++ .../RunOnce/RebuildSearchIndexService.cs | 6 +++ ErsatzTV/Startup.cs | 1 + 7 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 ErsatzTV/Services/RunOnce/DatabaseCleanerService.cs diff --git a/ErsatzTV.Core.Tests/Scheduling/PlaylistEnumeratorTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlaylistEnumeratorTests.cs index b195d84a..effb19fa 100644 --- a/ErsatzTV.Core.Tests/Scheduling/PlaylistEnumeratorTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/PlaylistEnumeratorTests.cs @@ -60,6 +60,7 @@ public class PlaylistEnumeratorTests repo, playlistItemMap, new CollectionEnumeratorState(), + shufflePlaylistItems: false, CancellationToken.None); enumerator.MoveNext(); @@ -123,6 +124,7 @@ public class PlaylistEnumeratorTests repo, playlistItemMap, new CollectionEnumeratorState(), + shufflePlaylistItems: false, CancellationToken.None); enumerator.MoveNext(); diff --git a/ErsatzTV.Core/Domain/MediaItem/MediaItem.cs b/ErsatzTV.Core/Domain/MediaItem/MediaItem.cs index a1b4d814..a76be196 100644 --- a/ErsatzTV.Core/Domain/MediaItem/MediaItem.cs +++ b/ErsatzTV.Core/Domain/MediaItem/MediaItem.cs @@ -1,6 +1,6 @@ namespace ErsatzTV.Core.Domain; -public class MediaItem +public abstract class MediaItem { public int Id { get; set; } public int LibraryPathId { get; set; } diff --git a/ErsatzTV.Core/SystemStartup.cs b/ErsatzTV.Core/SystemStartup.cs index 9cfad7ac..34c12a49 100644 --- a/ErsatzTV.Core/SystemStartup.cs +++ b/ErsatzTV.Core/SystemStartup.cs @@ -3,11 +3,13 @@ namespace ErsatzTV.Core; public class SystemStartup : IDisposable { private readonly SemaphoreSlim _databaseStartup = new(0, 100); + private readonly SemaphoreSlim _databaseCleaned = new(0, 100); private readonly SemaphoreSlim _searchIndexStartup = new(0, 100); private bool _disposedValue; public bool IsDatabaseReady { get; private set; } + public bool IsDatabaseCleaned { get; private set; } public bool IsSearchIndexReady { get; private set; } public void Dispose() @@ -17,11 +19,15 @@ public class SystemStartup : IDisposable } public event EventHandler OnDatabaseReady; + public event EventHandler OnDatabaseCleaned; public event EventHandler OnSearchIndexReady; public async Task WaitForDatabase(CancellationToken cancellationToken) => await _databaseStartup.WaitAsync(cancellationToken); + public async Task WaitForDatabaseCleaned(CancellationToken cancellationToken) => + await _databaseCleaned.WaitAsync(cancellationToken); + public async Task WaitForSearchIndex(CancellationToken cancellationToken) => await _searchIndexStartup.WaitAsync(cancellationToken); @@ -32,6 +38,13 @@ public class SystemStartup : IDisposable OnDatabaseReady?.Invoke(this, EventArgs.Empty); } + public void DatabaseIsCleaned() + { + _databaseCleaned.Release(100); + IsDatabaseCleaned = true; + OnDatabaseCleaned?.Invoke(this, EventArgs.Empty); + } + public void SearchIndexIsReady() { _searchIndexStartup.Release(100); @@ -46,6 +59,7 @@ public class SystemStartup : IDisposable if (disposing) { _databaseStartup.Dispose(); + _databaseCleaned.Dispose(); _searchIndexStartup.Dispose(); } diff --git a/ErsatzTV.Infrastructure/Health/Checks/UnavailableHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/UnavailableHealthCheck.cs index 5d5d24f6..c0b617ac 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/UnavailableHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/UnavailableHealthCheck.cs @@ -50,7 +50,9 @@ public class UnavailableHealthCheck : BaseHealthCheck, IUnavailableHealthCheck .Include(mi => (mi as Show).ShowMetadata) .Include(mi => (mi as Season).Show) .ThenInclude(s => s.ShowMetadata) - .Include(mi => (mi as Season).SeasonMetadata); + .Include(mi => (mi as Season).SeasonMetadata) + .Include(mi => (mi as Image).MediaVersions) + .ThenInclude(mv => mv.MediaFiles); List five = await mediaItems .OrderBy(mi => mi.Id) diff --git a/ErsatzTV/Services/RunOnce/DatabaseCleanerService.cs b/ErsatzTV/Services/RunOnce/DatabaseCleanerService.cs new file mode 100644 index 00000000..09e55038 --- /dev/null +++ b/ErsatzTV/Services/RunOnce/DatabaseCleanerService.cs @@ -0,0 +1,50 @@ +using Dapper; +using ErsatzTV.Core; +using ErsatzTV.Infrastructure.Data; + +namespace ErsatzTV.Services.RunOnce; + +public class DatabaseCleanerService( + IServiceScopeFactory serviceScopeFactory, + ILogger logger, + SystemStartup systemStartup) + : BackgroundService +{ + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + await Task.Yield(); + + await systemStartup.WaitForDatabase(stoppingToken); + if (stoppingToken.IsCancellationRequested) + { + return; + } + + logger.LogInformation("Cleaning database"); + + using IServiceScope scope = serviceScopeFactory.CreateScope(); + await using TvContext dbContext = scope.ServiceProvider.GetRequiredService(); + + // some old version deleted items in a way that MediaItem was left over without + // any corresponding Movie/Show/etc. + // this cleans out that old invalid data + await dbContext.Connection.ExecuteAsync( + """ + delete + from MediaItem + where Id not in (select Id from Movie) + and Id not in (select Id from Show) + and Id not in (select Id from Season) + and Id not in (select Id from Episode) + and Id not in (select Id from OtherVideo) + and Id not in (select Id from MusicVideo) + and Id not in (select Id from Song) + and Id not in (select Id from Artist) + and Id not in (select Id from Image) + """); + + systemStartup.DatabaseIsCleaned(); + + logger.LogInformation("Done cleaning database"); + } +} diff --git a/ErsatzTV/Services/RunOnce/RebuildSearchIndexService.cs b/ErsatzTV/Services/RunOnce/RebuildSearchIndexService.cs index 6c4e1f91..7c7ea865 100644 --- a/ErsatzTV/Services/RunOnce/RebuildSearchIndexService.cs +++ b/ErsatzTV/Services/RunOnce/RebuildSearchIndexService.cs @@ -25,6 +25,12 @@ public class RebuildSearchIndexService : BackgroundService return; } + await _systemStartup.WaitForDatabaseCleaned(stoppingToken); + if (stoppingToken.IsCancellationRequested) + { + return; + } + using IServiceScope scope = _serviceScopeFactory.CreateScope(); IMediator mediator = scope.ServiceProvider.GetRequiredService(); await mediator.Send(new RebuildSearchIndex(), stoppingToken); diff --git a/ErsatzTV/Startup.cs b/ErsatzTV/Startup.cs index 834d0c47..60750eac 100644 --- a/ErsatzTV/Startup.cs +++ b/ErsatzTV/Startup.cs @@ -710,6 +710,7 @@ public class Startup // run-once/blocking startup services services.AddHostedService(); services.AddHostedService(); + services.AddHostedService(); services.AddHostedService(); services.AddHostedService(); services.AddHostedService();