|
|
|
@ -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
|
|
|
|
|