From 95cceb95b9b6caa987ba97e4897acdc348708835 Mon Sep 17 00:00:00 2001 From: Jason Dove Date: Fri, 28 May 2021 05:38:58 -0500 Subject: [PATCH] regularly delete orphaned artwork from db (#223) --- .../Commands/DeleteOrphanedArtwork.cs | 7 ++ .../Commands/DeleteOrphanedArtworkHandler.cs | 23 +++++++ .../Repositories/IArtworkRepository.cs | 13 ++++ .../Data/Repositories/ArtworkRepository.cs | 65 +++++++++++++++++++ ErsatzTV/Services/SchedulerService.cs | 5 ++ ErsatzTV/Services/WorkerService.cs | 5 ++ ErsatzTV/Startup.cs | 1 + 7 files changed, 119 insertions(+) create mode 100644 ErsatzTV.Application/Maintenance/Commands/DeleteOrphanedArtwork.cs create mode 100644 ErsatzTV.Application/Maintenance/Commands/DeleteOrphanedArtworkHandler.cs create mode 100644 ErsatzTV.Core/Interfaces/Repositories/IArtworkRepository.cs create mode 100644 ErsatzTV.Infrastructure/Data/Repositories/ArtworkRepository.cs diff --git a/ErsatzTV.Application/Maintenance/Commands/DeleteOrphanedArtwork.cs b/ErsatzTV.Application/Maintenance/Commands/DeleteOrphanedArtwork.cs new file mode 100644 index 000000000..8455fb8aa --- /dev/null +++ b/ErsatzTV.Application/Maintenance/Commands/DeleteOrphanedArtwork.cs @@ -0,0 +1,7 @@ +using ErsatzTV.Core; +using LanguageExt; + +namespace ErsatzTV.Application.Maintenance.Commands +{ + public record DeleteOrphanedArtwork : MediatR.IRequest>, IBackgroundServiceRequest; +} diff --git a/ErsatzTV.Application/Maintenance/Commands/DeleteOrphanedArtworkHandler.cs b/ErsatzTV.Application/Maintenance/Commands/DeleteOrphanedArtworkHandler.cs new file mode 100644 index 000000000..5cf2e26c2 --- /dev/null +++ b/ErsatzTV.Application/Maintenance/Commands/DeleteOrphanedArtworkHandler.cs @@ -0,0 +1,23 @@ +using System.Threading; +using System.Threading.Tasks; +using ErsatzTV.Core; +using ErsatzTV.Core.Interfaces.Repositories; +using LanguageExt; +using static LanguageExt.Prelude; + +namespace ErsatzTV.Application.Maintenance.Commands +{ + public class DeleteOrphanedArtworkHandler : MediatR.IRequestHandler> + { + private readonly IArtworkRepository _artworkRepository; + + public DeleteOrphanedArtworkHandler(IArtworkRepository artworkRepository) => + _artworkRepository = artworkRepository; + + public Task> + Handle(DeleteOrphanedArtwork request, CancellationToken cancellationToken) => + _artworkRepository.GetOrphanedArtwork() + .Bind(_artworkRepository.Delete) + .Map(_ => Right(Unit.Default)); + } +} diff --git a/ErsatzTV.Core/Interfaces/Repositories/IArtworkRepository.cs b/ErsatzTV.Core/Interfaces/Repositories/IArtworkRepository.cs new file mode 100644 index 000000000..67fec6ee1 --- /dev/null +++ b/ErsatzTV.Core/Interfaces/Repositories/IArtworkRepository.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ErsatzTV.Core.Domain; +using LanguageExt; + +namespace ErsatzTV.Core.Interfaces.Repositories +{ + public interface IArtworkRepository + { + Task> GetOrphanedArtwork(); + Task Delete(List artwork); + } +} diff --git a/ErsatzTV.Infrastructure/Data/Repositories/ArtworkRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/ArtworkRepository.cs new file mode 100644 index 000000000..a6e352411 --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Repositories/ArtworkRepository.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using Dapper; +using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Interfaces.Repositories; +using LanguageExt; + +namespace ErsatzTV.Infrastructure.Data.Repositories +{ + public class ArtworkRepository : IArtworkRepository + { + private readonly IDbConnection _dbConnection; + + public ArtworkRepository(IDbConnection dbConnection) => _dbConnection = dbConnection; + + public Task> GetOrphanedArtwork() => + _dbConnection.QueryAsync( + @"SELECT Artwork.Id, Artwork.Path FROM Artwork + LEFT OUTER JOIN Actor A on Artwork.Id = A.ArtworkId + LEFT OUTER JOIN ArtistMetadata AM on A.ArtistMetadataId = AM.Id + LEFT OUTER JOIN EpisodeMetadata EM on A.EpisodeMetadataId = EM.Id + LEFT OUTER JOIN SeasonMetadata SM on A.SeasonMetadataId = SM.Id + LEFT OUTER JOIN ShowMetadata S on A.ShowMetadataId = S.Id + LEFT OUTER JOIN MovieMetadata MM on A.MovieMetadataId = MM.Id + LEFT OUTER JOIN MusicVideoMetadata MVM on A.MusicVideoMetadataId = MVM.Id + WHERE A.Id IS NULL AND AM.Id IS NULL AND EM.Id IS NULL AND SM.Id IS NULL + AND S.Id IS NULL AND MM.Id IS NULL AND MVM.Id IS NULL") + .Map(result => result.ToList()); + + public async Task Delete(List artwork) + { + IEnumerable> chunks = Chunk(artwork.Map(a => a.Id), 100); + foreach (List chunk in chunks) + { + await _dbConnection.ExecuteAsync( + "DELETE FROM Artwork WHERE Id IN @Ids", + new { Ids = chunk }); + } + + return Unit.Default; + } + + private static IEnumerable> Chunk(IEnumerable collection, int size) + { + var count = 0; + var chunk = new List(size); + + foreach (T element in collection) + { + if (count++ == size) + { + yield return chunk; + chunk = new List(size); + count = 1; + } + + chunk.Add(element); + } + + yield return chunk; + } + } +} diff --git a/ErsatzTV/Services/SchedulerService.cs b/ErsatzTV/Services/SchedulerService.cs index 3ad2f1240..d4450fe07 100644 --- a/ErsatzTV/Services/SchedulerService.cs +++ b/ErsatzTV/Services/SchedulerService.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using ErsatzTV.Application; +using ErsatzTV.Application.Maintenance.Commands; using ErsatzTV.Application.MediaSources.Commands; using ErsatzTV.Application.Playouts.Commands; using ErsatzTV.Application.Plex.Commands; @@ -58,6 +59,7 @@ namespace ErsatzTV.Services { try { + await DeleteOrphanedArtwork(cancellationToken); await RebuildSearchIndex(cancellationToken); await BuildPlayouts(cancellationToken); await ScanLocalMediaSources(cancellationToken); @@ -124,5 +126,8 @@ namespace ErsatzTV.Services private ValueTask RebuildSearchIndex(CancellationToken cancellationToken) => _workerChannel.WriteAsync(new RebuildSearchIndex(), cancellationToken); + + private ValueTask DeleteOrphanedArtwork(CancellationToken cancellationToken) => + _workerChannel.WriteAsync(new DeleteOrphanedArtwork(), cancellationToken); } } diff --git a/ErsatzTV/Services/WorkerService.cs b/ErsatzTV/Services/WorkerService.cs index cdde0a643..8c64cf9cc 100644 --- a/ErsatzTV/Services/WorkerService.cs +++ b/ErsatzTV/Services/WorkerService.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using ErsatzTV.Application; +using ErsatzTV.Application.Maintenance.Commands; using ErsatzTV.Application.MediaSources.Commands; using ErsatzTV.Application.Playouts.Commands; using ErsatzTV.Application.Search.Commands; @@ -72,6 +73,10 @@ namespace ErsatzTV.Services case RebuildSearchIndex rebuildSearchIndex: await mediator.Send(rebuildSearchIndex, cancellationToken); break; + case DeleteOrphanedArtwork deleteOrphanedArtwork: + _logger.LogInformation("Deleting orphaned artwork from the database"); + await mediator.Send(deleteOrphanedArtwork, cancellationToken); + break; } } catch (Exception ex) diff --git a/ErsatzTV/Startup.cs b/ErsatzTV/Startup.cs index a38a3f400..62e5819a7 100644 --- a/ErsatzTV/Startup.cs +++ b/ErsatzTV/Startup.cs @@ -214,6 +214,7 @@ namespace ErsatzTV services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped();