Browse Source

use separate load queries for jf episode inserts and updates

pull/2751/head
Jason Dove 7 months ago
parent
commit
6330e4d05f
No known key found for this signature in database
  1. 98
      ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs
  2. 36
      ErsatzTV.Scanner/Core/Metadata/MediaServerTelevisionLibraryScanner.cs
  3. 1
      ErsatzTV.Scanner/Program.cs

98
ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs

@ -5,7 +5,6 @@ using ErsatzTV.Core.Errors;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Jellyfin; using ErsatzTV.Core.Jellyfin;
using ErsatzTV.Core.Metadata; using ErsatzTV.Core.Metadata;
using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -91,8 +90,6 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Option<JellyfinShow> maybeExisting = await dbContext.JellyfinShows Option<JellyfinShow> maybeExisting = await dbContext.JellyfinShows
.TagWithCallSite() .TagWithCallSite()
.Include(m => m.LibraryPath)
.ThenInclude(lp => lp.Library)
.Include(m => m.ShowMetadata) .Include(m => m.ShowMetadata)
.ThenInclude(mm => mm.Genres) .ThenInclude(mm => mm.Genres)
.Include(m => m.ShowMetadata) .Include(m => m.ShowMetadata)
@ -105,9 +102,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository
.ThenInclude(mm => mm.Artwork) .ThenInclude(mm => mm.Artwork)
.Include(m => m.ShowMetadata) .Include(m => m.ShowMetadata)
.ThenInclude(mm => mm.Guids) .ThenInclude(mm => mm.Guids)
.Include(m => m.TraktListItems) .SingleOrDefaultAsync(s => s.ItemId == item.ItemId, cancellationToken);
.ThenInclude(tli => tli.TraktList)
.SelectOneAsync(s => s.ItemId, s => s.ItemId == item.ItemId, cancellationToken);
foreach (JellyfinShow jellyfinShow in maybeExisting) foreach (JellyfinShow jellyfinShow in maybeExisting)
{ {
@ -135,12 +130,11 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Option<JellyfinSeason> maybeExisting = await dbContext.JellyfinSeasons Option<JellyfinSeason> maybeExisting = await dbContext.JellyfinSeasons
.TagWithCallSite() .TagWithCallSite()
.Include(m => m.LibraryPath)
.Include(m => m.SeasonMetadata) .Include(m => m.SeasonMetadata)
.ThenInclude(mm => mm.Artwork) .ThenInclude(mm => mm.Artwork)
.Include(m => m.SeasonMetadata) .Include(m => m.SeasonMetadata)
.ThenInclude(mm => mm.Guids) .ThenInclude(mm => mm.Guids)
.SelectOneAsync(s => s.ItemId, s => s.ItemId == item.ItemId, cancellationToken); .SingleOrDefaultAsync(s => s.ItemId == item.ItemId, cancellationToken);
foreach (JellyfinSeason jellyfinSeason in maybeExisting) foreach (JellyfinSeason jellyfinSeason in maybeExisting)
{ {
@ -167,44 +161,56 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository
using (ScanProfiler.Measure("DB Ins/Upd Episode")) using (ScanProfiler.Measure("DB Ins/Upd Episode"))
{ {
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Option<JellyfinEpisode> maybeExisting = await dbContext.JellyfinEpisodes
Option<dynamic> maybeExistingState = await dbContext.JellyfinEpisodes
.TagWithCallSite() .TagWithCallSite()
.Include(m => m.LibraryPath) .Where(s => s.ItemId == item.ItemId)
.ThenInclude(lp => lp.Library) .Select(s => new { s.Id, s.Etag })
.Include(m => m.MediaVersions) .SingleOrDefaultAsync(cancellationToken);
.ThenInclude(mv => mv.MediaFiles)
.Include(m => m.MediaVersions) foreach (dynamic existingState in maybeExistingState)
.ThenInclude(mv => mv.Streams)
.Include(m => m.MediaVersions)
.ThenInclude(mv => mv.Chapters)
.Include(m => m.EpisodeMetadata)
.ThenInclude(mm => mm.Artwork)
.Include(m => m.EpisodeMetadata)
.ThenInclude(mm => mm.Guids)
.Include(m => m.EpisodeMetadata)
.ThenInclude(mm => mm.Genres)
.Include(m => m.EpisodeMetadata)
.ThenInclude(mm => mm.Tags)
.Include(m => m.EpisodeMetadata)
.ThenInclude(mm => mm.Studios)
.Include(m => m.EpisodeMetadata)
.ThenInclude(mm => mm.Actors)
.Include(m => m.EpisodeMetadata)
.ThenInclude(mm => mm.Directors)
.Include(m => m.EpisodeMetadata)
.ThenInclude(mm => mm.Writers)
.Include(m => m.Season)
.Include(m => m.TraktListItems)
.ThenInclude(tli => tli.TraktList)
.SelectOneAsync(s => s.ItemId, s => s.ItemId == item.ItemId, cancellationToken);
foreach (JellyfinEpisode jellyfinEpisode in maybeExisting)
{ {
var result = new MediaItemScanResult<JellyfinEpisode>(jellyfinEpisode) { IsAdded = false }; int existingId = existingState.Id;
if (jellyfinEpisode.Etag != item.Etag || deepScan)
MediaItemScanResult<JellyfinEpisode> result;
if (existingState.Etag != item.Etag || deepScan)
{ {
await UpdateEpisode(dbContext, jellyfinEpisode, item, cancellationToken); JellyfinEpisode existing = await dbContext.JellyfinEpisodes
result.IsUpdated = true; .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<JellyfinEpisode>(existing) { IsAdded = false, IsUpdated = true };
}
else
{
JellyfinEpisode existing = await dbContext.JellyfinEpisodes
.AsNoTracking()
.TagWithCallSite()
.Include(e => e.EpisodeMetadata).ThenInclude(em => em.Actors)
.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);
result = new MediaItemScanResult<JellyfinEpisode>(existing) { IsAdded = false };
} }
return result; return result;
@ -548,7 +554,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
// library path is used for search indexing later // library path is used for search indexing later
incoming.LibraryPath = existing.LibraryPath; incoming.LibraryPathId = existing.LibraryPathId;
incoming.Id = existing.Id; incoming.Id = existing.Id;
// metadata // metadata
@ -696,7 +702,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
// library path is used for search indexing later // library path is used for search indexing later
incoming.LibraryPath = existing.LibraryPath; incoming.LibraryPathId = existing.LibraryPathId;
incoming.Id = existing.Id; incoming.Id = existing.Id;
existing.SeasonNumber = incoming.SeasonNumber; existing.SeasonNumber = incoming.SeasonNumber;
@ -795,7 +801,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
// library path is used for search indexing later // library path is used for search indexing later
incoming.LibraryPath = existing.LibraryPath; incoming.LibraryPathId = existing.LibraryPathId;
incoming.Id = existing.Id; incoming.Id = existing.Id;
// metadata // metadata

36
ErsatzTV.Scanner/Core/Metadata/MediaServerTelevisionLibraryScanner.cs

@ -464,7 +464,7 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
incoming, incoming,
deepScan, deepScan,
None)) None))
.BindT(existing => UpdateSubtitles(existing, cancellationToken)) .BindT(existing => UpdateSubtitles(existing, incoming, deepScan, cancellationToken))
.BindT(existing => UpdateChapters(existing, cancellationToken)); .BindT(existing => UpdateChapters(existing, cancellationToken));
} }
@ -831,29 +831,37 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
private async Task<Either<BaseError, MediaItemScanResult<TEpisode>>> UpdateSubtitles( private async Task<Either<BaseError, MediaItemScanResult<TEpisode>>> UpdateSubtitles(
MediaItemScanResult<TEpisode> existing, MediaItemScanResult<TEpisode> existing,
TEpisode incoming,
bool deepScan,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
try try
{ {
using (ScanProfiler.Measure("Update Episode Subtitles")) if (deepScan || existing.IsAdded || MediaServerEtag(existing.Item) != MediaServerEtag(incoming))
{ {
MediaVersion version = existing.Item.GetHeadVersion(); using (ScanProfiler.Measure("Update Episode Subtitles"))
Option<EpisodeMetadata> maybeMetadata = existing.Item.EpisodeMetadata.HeadOrNone();
foreach (EpisodeMetadata metadata in maybeMetadata)
{ {
List<Subtitle> subtitles = version.Streams MediaVersion version = existing.Item.GetHeadVersion();
.Filter(s => s.MediaStreamKind is MediaStreamKind.Subtitle or MediaStreamKind.ExternalSubtitle) Option<EpisodeMetadata> maybeMetadata = existing.Item.EpisodeMetadata.HeadOrNone();
.Map(Subtitle.FromMediaStream) foreach (EpisodeMetadata metadata in maybeMetadata)
.ToList();
if (await _metadataRepository.UpdateSubtitles(metadata, subtitles, cancellationToken))
{ {
return existing; List<Subtitle> subtitles = version.Streams
.Filter(s =>
s.MediaStreamKind is MediaStreamKind.Subtitle or MediaStreamKind.ExternalSubtitle)
.Map(Subtitle.FromMediaStream)
.ToList();
if (await _metadataRepository.UpdateSubtitles(metadata, subtitles, cancellationToken))
{
return existing;
}
} }
}
return BaseError.New("Failed to update media server subtitles"); return BaseError.New("Failed to update media server subtitles");
}
} }
return existing;
} }
catch (Exception ex) catch (Exception ex)
{ {

1
ErsatzTV.Scanner/Program.cs

@ -62,6 +62,7 @@ public class Program
Log.Logger = new LoggerConfiguration() Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug() .MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", LogEventLevel.Error) .MinimumLevel.Override("Microsoft", LogEventLevel.Error)
.MinimumLevel.Override("System.Net.Http.HttpClient", LogEventLevel.Warning)
.Enrich.FromLogContext() .Enrich.FromLogContext()
.WriteTo.Console(new CompactJsonFormatter(), standardErrorFromLevel: LogEventLevel.Debug) .WriteTo.Console(new CompactJsonFormatter(), standardErrorFromLevel: LogEventLevel.Debug)
.CreateLogger(); .CreateLogger();

Loading…
Cancel
Save