From 825f1b12e78be16d30c7c80869977cab4780512c Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Fri, 2 Jan 2026 20:10:14 -0600 Subject: [PATCH] add timings for jf episode loading and saving during update --- .../JellyfinTelevisionRepository.cs | 271 +++++++++--------- 1 file changed, 140 insertions(+), 131 deletions(-) diff --git a/ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs index 749e08f95..3ec86ed4a 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs @@ -175,26 +175,32 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository MediaItemScanResult result; if (existingState.Etag != item.Etag || deepScan) { - JellyfinEpisode existing = await dbContext.JellyfinEpisodes - .TagWithCallSite() - .Include(e => e.EpisodeMetadata).ThenInclude(em => em.Artwork) - .Include(e => e.EpisodeMetadata).ThenInclude(em => em.Guids) - .Include(e => e.EpisodeMetadata).ThenInclude(em => em.Genres) - .Include(e => e.EpisodeMetadata).ThenInclude(em => em.Tags) - .Include(e => e.EpisodeMetadata).ThenInclude(em => em.Actors).ThenInclude(a => a.Artwork) - .Include(e => e.EpisodeMetadata).ThenInclude(em => em.Directors) - .Include(e => e.EpisodeMetadata).ThenInclude(em => em.Writers) - .Include(e => e.MediaVersions).ThenInclude(mv => mv.MediaFiles) - .Include(e => e.MediaVersions).ThenInclude(mv => mv.Streams) - .Include(e => e.MediaVersions).ThenInclude(mv => mv.Chapters) - .AsSplitQuery() - .SingleAsync(s => s.Id == existingId, cancellationToken); - - await dbContext.Entry(existing).Reference(e => e.Season).LoadAsync(cancellationToken); + JellyfinEpisode existing; + + using (ScanProfiler.Measure("DB Ep Upd Load")) + { + existing = await dbContext.JellyfinEpisodes + .TagWithCallSite() + .Include(e => e.EpisodeMetadata).ThenInclude(em => em.Artwork) + .Include(e => e.EpisodeMetadata).ThenInclude(em => em.Guids) + .Include(e => e.EpisodeMetadata).ThenInclude(em => em.Genres) + .Include(e => e.EpisodeMetadata).ThenInclude(em => em.Tags) + .Include(e => e.EpisodeMetadata).ThenInclude(em => em.Actors).ThenInclude(a => a.Artwork) + .Include(e => e.EpisodeMetadata).ThenInclude(em => em.Directors) + .Include(e => e.EpisodeMetadata).ThenInclude(em => em.Writers) + .Include(e => e.MediaVersions).ThenInclude(mv => mv.MediaFiles) + .Include(e => e.MediaVersions).ThenInclude(mv => mv.Streams) + .Include(e => e.MediaVersions).ThenInclude(mv => mv.Chapters) + .AsSplitQuery() + .SingleAsync(s => s.Id == existingId, cancellationToken); + + await dbContext.Entry(existing).Reference(e => e.Season).LoadAsync(cancellationToken); + } await UpdateEpisode(dbContext, existing, item, cancellationToken); - result = new MediaItemScanResult(existing) { IsAdded = false, IsUpdated = true }; + result = new MediaItemScanResult(existing) + { IsAdded = false, IsUpdated = true }; } else { @@ -800,137 +806,140 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository JellyfinEpisode incoming, CancellationToken cancellationToken) { - // library path is used for search indexing later - incoming.LibraryPathId = existing.LibraryPathId; - incoming.Id = existing.Id; - - // metadata - // TODO: multiple metadata? - EpisodeMetadata metadata = existing.EpisodeMetadata.Head(); - EpisodeMetadata incomingMetadata = incoming.EpisodeMetadata.Head(); - metadata.Title = incomingMetadata.Title; - metadata.SortTitle = incomingMetadata.SortTitle; - metadata.Plot = incomingMetadata.Plot; - metadata.Year = incomingMetadata.Year; - metadata.DateAdded = incomingMetadata.DateAdded; - metadata.DateUpdated = DateTime.UtcNow; - metadata.ReleaseDate = incomingMetadata.ReleaseDate; - metadata.EpisodeNumber = incomingMetadata.EpisodeNumber; - - // thumbnail - Artwork incomingThumbnail = - incomingMetadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Thumbnail); - if (incomingThumbnail != null) + using (ScanProfiler.Measure("DB Ep Upd Save")) { - Artwork thumbnail = metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Thumbnail); - if (thumbnail == null) + // library path is used for search indexing later + incoming.LibraryPathId = existing.LibraryPathId; + incoming.Id = existing.Id; + + // metadata + // TODO: multiple metadata? + EpisodeMetadata metadata = existing.EpisodeMetadata.Head(); + EpisodeMetadata incomingMetadata = incoming.EpisodeMetadata.Head(); + metadata.Title = incomingMetadata.Title; + metadata.SortTitle = incomingMetadata.SortTitle; + metadata.Plot = incomingMetadata.Plot; + metadata.Year = incomingMetadata.Year; + metadata.DateAdded = incomingMetadata.DateAdded; + metadata.DateUpdated = DateTime.UtcNow; + metadata.ReleaseDate = incomingMetadata.ReleaseDate; + metadata.EpisodeNumber = incomingMetadata.EpisodeNumber; + + // thumbnail + Artwork incomingThumbnail = + incomingMetadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Thumbnail); + if (incomingThumbnail != null) { - thumbnail = new Artwork { ArtworkKind = ArtworkKind.Thumbnail }; - metadata.Artwork.Add(thumbnail); - } + Artwork thumbnail = metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == ArtworkKind.Thumbnail); + if (thumbnail == null) + { + thumbnail = new Artwork { ArtworkKind = ArtworkKind.Thumbnail }; + metadata.Artwork.Add(thumbnail); + } - thumbnail.Path = incomingThumbnail.Path; - thumbnail.DateAdded = incomingThumbnail.DateAdded; - thumbnail.DateUpdated = incomingThumbnail.DateUpdated; - } + thumbnail.Path = incomingThumbnail.Path; + thumbnail.DateAdded = incomingThumbnail.DateAdded; + thumbnail.DateUpdated = incomingThumbnail.DateUpdated; + } - // directors - foreach (Director director in metadata.Directors - .Filter(d => incomingMetadata.Directors.All(d2 => d2.Name != d.Name)) - .ToList()) - { - metadata.Directors.Remove(director); - } + // directors + foreach (Director director in metadata.Directors + .Filter(d => incomingMetadata.Directors.All(d2 => d2.Name != d.Name)) + .ToList()) + { + metadata.Directors.Remove(director); + } - foreach (Director director in incomingMetadata.Directors - .Filter(d => metadata.Directors.All(d2 => d2.Name != d.Name)) - .ToList()) - { - metadata.Directors.Add(director); - } + foreach (Director director in incomingMetadata.Directors + .Filter(d => metadata.Directors.All(d2 => d2.Name != d.Name)) + .ToList()) + { + metadata.Directors.Add(director); + } - // writers - foreach (Writer writer in metadata.Writers - .Filter(w => incomingMetadata.Writers.All(w2 => w2.Name != w.Name)) - .ToList()) - { - metadata.Writers.Remove(writer); - } + // writers + foreach (Writer writer in metadata.Writers + .Filter(w => incomingMetadata.Writers.All(w2 => w2.Name != w.Name)) + .ToList()) + { + metadata.Writers.Remove(writer); + } - foreach (Writer writer in incomingMetadata.Writers - .Filter(w => metadata.Writers.All(w2 => w2.Name != w.Name)) - .ToList()) - { - metadata.Writers.Add(writer); - } + foreach (Writer writer in incomingMetadata.Writers + .Filter(w => metadata.Writers.All(w2 => w2.Name != w.Name)) + .ToList()) + { + metadata.Writers.Add(writer); + } - // guids - foreach (MetadataGuid guid in metadata.Guids - .Filter(g => incomingMetadata.Guids.All(g2 => g2.Guid != g.Guid)) - .ToList()) - { - metadata.Guids.Remove(guid); - } + // guids + foreach (MetadataGuid guid in metadata.Guids + .Filter(g => incomingMetadata.Guids.All(g2 => g2.Guid != g.Guid)) + .ToList()) + { + metadata.Guids.Remove(guid); + } - foreach (MetadataGuid guid in incomingMetadata.Guids - .Filter(g => metadata.Guids.All(g2 => g2.Guid != g.Guid)) - .ToList()) - { - metadata.Guids.Add(guid); - } + foreach (MetadataGuid guid in incomingMetadata.Guids + .Filter(g => metadata.Guids.All(g2 => g2.Guid != g.Guid)) + .ToList()) + { + metadata.Guids.Add(guid); + } - // genres - foreach (Genre genre in metadata.Genres - .Filter(g => incomingMetadata.Genres.All(g2 => g2.Name != g.Name)) - .ToList()) - { - metadata.Genres.Remove(genre); - } + // genres + foreach (Genre genre in metadata.Genres + .Filter(g => incomingMetadata.Genres.All(g2 => g2.Name != g.Name)) + .ToList()) + { + metadata.Genres.Remove(genre); + } - foreach (Genre genre in incomingMetadata.Genres - .Filter(g => metadata.Genres.All(g2 => g2.Name != g.Name)) - .ToList()) - { - metadata.Genres.Add(genre); - } + foreach (Genre genre in incomingMetadata.Genres + .Filter(g => metadata.Genres.All(g2 => g2.Name != g.Name)) + .ToList()) + { + metadata.Genres.Add(genre); + } - // tags - foreach (Tag tag in metadata.Tags - .Filter(g => incomingMetadata.Tags.All(g2 => g2.Name != g.Name)) - .Filter(g => g.ExternalCollectionId is null) - .ToList()) - { - metadata.Tags.Remove(tag); - } + // tags + foreach (Tag tag in metadata.Tags + .Filter(g => incomingMetadata.Tags.All(g2 => g2.Name != g.Name)) + .Filter(g => g.ExternalCollectionId is null) + .ToList()) + { + metadata.Tags.Remove(tag); + } - foreach (Tag tag in incomingMetadata.Tags - .Filter(g => metadata.Tags.All(g2 => g2.Name != g.Name)) - .ToList()) - { - metadata.Tags.Add(tag); - } + foreach (Tag tag in incomingMetadata.Tags + .Filter(g => metadata.Tags.All(g2 => g2.Name != g.Name)) + .ToList()) + { + metadata.Tags.Add(tag); + } - var paths = incomingMetadata.Artwork.Map(a => a.Path).ToList(); - foreach (Artwork artworkToRemove in metadata.Artwork - .Filter(a => !paths.Contains(a.Path)) - .ToList()) - { - metadata.Artwork.Remove(artworkToRemove); - } + var paths = incomingMetadata.Artwork.Map(a => a.Path).ToList(); + foreach (Artwork artworkToRemove in metadata.Artwork + .Filter(a => !paths.Contains(a.Path)) + .ToList()) + { + metadata.Artwork.Remove(artworkToRemove); + } - // version - MediaVersion version = existing.MediaVersions.Head(); - MediaVersion incomingVersion = incoming.MediaVersions.Head(); - version.Name = incomingVersion.Name; - version.DateAdded = incomingVersion.DateAdded; - version.Chapters = incomingVersion.Chapters; + // version + MediaVersion version = existing.MediaVersions.Head(); + MediaVersion incomingVersion = incoming.MediaVersions.Head(); + version.Name = incomingVersion.Name; + version.DateAdded = incomingVersion.DateAdded; + version.Chapters = incomingVersion.Chapters; - // media file - MediaFile file = version.MediaFiles.Head(); - MediaFile incomingFile = incomingVersion.MediaFiles.Head(); - file.Path = incomingFile.Path; + // media file + MediaFile file = version.MediaFiles.Head(); + MediaFile incomingFile = incomingVersion.MediaFiles.Head(); + file.Path = incomingFile.Path; - await dbContext.SaveChangesAsync(cancellationToken); + await dbContext.SaveChangesAsync(cancellationToken); + } } private static async Task>> AddShow(