From 739d074bc65553d3fbd4d91168c04e6fbdb5930e Mon Sep 17 00:00:00 2001 From: Jason Dove Date: Fri, 19 Mar 2021 00:45:38 +0000 Subject: [PATCH] optimize local scanning (#84) * optimize local scanning * fix artwork updates * fix adding genres and tags * fix movie fallback metadata --- .../Fakes/FakeTelevisionRepository.cs | 14 +- .../Metadata/MovieFolderScannerTests.cs | 1 + .../Repositories/IMetadataRepository.cs | 5 +- .../Repositories/IMovieRepository.cs | 1 - .../Repositories/ITelevisionRepository.cs | 4 +- .../Metadata/FallbackMetadataProvider.cs | 3 + ErsatzTV.Core/Metadata/LocalFolderScanner.cs | 21 +- .../Metadata/LocalMetadataProvider.cs | 39 ++- .../Metadata/LocalStatisticsProvider.cs | 5 +- ErsatzTV.Core/Metadata/MovieFolderScanner.cs | 9 +- .../Metadata/TelevisionFolderScanner.cs | 24 +- ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs | 4 +- .../Plex/PlexTelevisionLibraryScanner.cs | 2 +- .../Repositories/MediaSourceRepository.cs | 5 + .../Data/Repositories/MetadataRepository.cs | 41 ++- .../Data/Repositories/MovieRepository.cs | 44 ++- .../Data/Repositories/TelevisionRepository.cs | 273 +++++++++++------- 17 files changed, 296 insertions(+), 199 deletions(-) diff --git a/ErsatzTV.Core.Tests/Fakes/FakeTelevisionRepository.cs b/ErsatzTV.Core.Tests/Fakes/FakeTelevisionRepository.cs index bf3a57155..27d450aff 100644 --- a/ErsatzTV.Core.Tests/Fakes/FakeTelevisionRepository.cs +++ b/ErsatzTV.Core.Tests/Fakes/FakeTelevisionRepository.cs @@ -11,12 +11,6 @@ namespace ErsatzTV.Core.Tests.Fakes { public Task AllShowsExist(List showIds) => throw new NotSupportedException(); - public Task Update(Show show) => throw new NotSupportedException(); - - public Task Update(Season season) => throw new NotSupportedException(); - - public Task Update(Episode episode) => throw new NotSupportedException(); - public Task> GetAllShows() => throw new NotSupportedException(); public Task> GetShow(int showId) => throw new NotSupportedException(); @@ -86,5 +80,13 @@ namespace ErsatzTV.Core.Tests.Fakes public Task RemoveMissingPlexEpisodes(string seasonKey, List episodeKeys) => throw new NotSupportedException(); + + public Task SetEpisodeNumber(Episode episode, int episodeNumber) => throw new NotSupportedException(); + + public Task Update(Show show) => throw new NotSupportedException(); + + public Task Update(Season season) => throw new NotSupportedException(); + + public Task Update(Episode episode) => throw new NotSupportedException(); } } diff --git a/ErsatzTV.Core.Tests/Metadata/MovieFolderScannerTests.cs b/ErsatzTV.Core.Tests/Metadata/MovieFolderScannerTests.cs index 945baf759..9534c66c8 100644 --- a/ErsatzTV.Core.Tests/Metadata/MovieFolderScannerTests.cs +++ b/ErsatzTV.Core.Tests/Metadata/MovieFolderScannerTests.cs @@ -417,6 +417,7 @@ namespace ErsatzTV.Core.Tests.Metadata _movieRepository.Object, _localStatisticsProvider.Object, _localMetadataProvider.Object, + new Mock().Object, _imageCache.Object, new Mock>().Object ); diff --git a/ErsatzTV.Core/Interfaces/Repositories/IMetadataRepository.cs b/ErsatzTV.Core/Interfaces/Repositories/IMetadataRepository.cs index fffb06e3c..d4408f174 100644 --- a/ErsatzTV.Core/Interfaces/Repositories/IMetadataRepository.cs +++ b/ErsatzTV.Core/Interfaces/Repositories/IMetadataRepository.cs @@ -7,7 +7,10 @@ namespace ErsatzTV.Core.Interfaces.Repositories public interface IMetadataRepository { Task RemoveGenre(Genre genre); - Task UpdateStatistics(MediaVersion mediaVersion); + Task Update(Domain.Metadata metadata); + Task Add(Domain.Metadata metadata); + Task UpdateLocalStatistics(MediaVersion mediaVersion); + Task UpdatePlexStatistics(MediaVersion mediaVersion); Task UpdateArtworkPath(Artwork artwork); Task AddArtwork(Domain.Metadata metadata, Artwork artwork); Task RemoveArtwork(Domain.Metadata metadata, ArtworkKind artworkKind); diff --git a/ErsatzTV.Core/Interfaces/Repositories/IMovieRepository.cs b/ErsatzTV.Core/Interfaces/Repositories/IMovieRepository.cs index 22e6d5f7d..5ce15b2ba 100644 --- a/ErsatzTV.Core/Interfaces/Repositories/IMovieRepository.cs +++ b/ErsatzTV.Core/Interfaces/Repositories/IMovieRepository.cs @@ -11,7 +11,6 @@ namespace ErsatzTV.Core.Interfaces.Repositories Task> GetMovie(int movieId); Task> GetOrAdd(LibraryPath libraryPath, string path); Task> GetOrAdd(PlexLibrary library, PlexMovie item); - Task Update(Movie movie); Task GetMovieCount(); Task> GetPagedMovies(int pageNumber, int pageSize); Task> FindMoviePaths(LibraryPath libraryPath); diff --git a/ErsatzTV.Core/Interfaces/Repositories/ITelevisionRepository.cs b/ErsatzTV.Core/Interfaces/Repositories/ITelevisionRepository.cs index 1816478c3..b5750cc60 100644 --- a/ErsatzTV.Core/Interfaces/Repositories/ITelevisionRepository.cs +++ b/ErsatzTV.Core/Interfaces/Repositories/ITelevisionRepository.cs @@ -8,9 +8,6 @@ namespace ErsatzTV.Core.Interfaces.Repositories public interface ITelevisionRepository { Task AllShowsExist(List showIds); - Task Update(Show show); - Task Update(Season season); - Task Update(Episode episode); Task> GetAllShows(); Task> GetShow(int showId); Task GetShowCount(); @@ -39,5 +36,6 @@ namespace ErsatzTV.Core.Interfaces.Repositories Task RemoveMissingPlexShows(PlexLibrary library, List showKeys); Task RemoveMissingPlexSeasons(string showKey, List seasonKeys); Task RemoveMissingPlexEpisodes(string seasonKey, List episodeKeys); + Task SetEpisodeNumber(Episode episode, int episodeNumber); } } diff --git a/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs b/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs index 15cdd7cf1..e5a99553e 100644 --- a/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs +++ b/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; using ErsatzTV.Core.Domain; @@ -86,6 +87,8 @@ namespace ErsatzTV.Core.Metadata metadata.Title = match.Groups[1].Value; metadata.Year = int.Parse(match.Groups[2].Value); metadata.ReleaseDate = new DateTime(int.Parse(match.Groups[2].Value), 1, 1); + metadata.Genres = new List(); + metadata.Tags = new List(); } } catch (Exception) diff --git a/ErsatzTV.Core/Metadata/LocalFolderScanner.cs b/ErsatzTV.Core/Metadata/LocalFolderScanner.cs index ad6e80d8b..6cc9a69cd 100644 --- a/ErsatzTV.Core/Metadata/LocalFolderScanner.cs +++ b/ErsatzTV.Core/Metadata/LocalFolderScanner.cs @@ -7,9 +7,9 @@ using System.Threading.Tasks; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Images; using ErsatzTV.Core.Interfaces.Metadata; +using ErsatzTV.Core.Interfaces.Repositories; using LanguageExt; using Microsoft.Extensions.Logging; -using static LanguageExt.Prelude; namespace ErsatzTV.Core.Metadata { @@ -48,17 +48,20 @@ namespace ErsatzTV.Core.Metadata private readonly ILocalFileSystem _localFileSystem; private readonly ILocalStatisticsProvider _localStatisticsProvider; private readonly ILogger _logger; + private readonly IMetadataRepository _metadataRepository; static LocalFolderScanner() => Crypto = new SHA1CryptoServiceProvider(); protected LocalFolderScanner( ILocalFileSystem localFileSystem, ILocalStatisticsProvider localStatisticsProvider, + IMetadataRepository metadataRepository, IImageCache imageCache, ILogger logger) { _localFileSystem = localFileSystem; _localStatisticsProvider = localStatisticsProvider; + _metadataRepository = metadataRepository; _imageCache = imageCache; _logger = logger; } @@ -99,17 +102,16 @@ namespace ErsatzTV.Core.Metadata } } - protected bool RefreshArtwork(string artworkFile, Domain.Metadata metadata, ArtworkKind artworkKind) + protected async Task RefreshArtwork(string artworkFile, Domain.Metadata metadata, ArtworkKind artworkKind) { DateTime lastWriteTime = _localFileSystem.GetLastWriteTime(artworkFile); metadata.Artwork ??= new List(); - Option maybeArtwork = - Optional(metadata.Artwork).Flatten().FirstOrDefault(a => a.ArtworkKind == artworkKind); + Option maybeArtwork = metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == artworkKind); bool shouldRefresh = maybeArtwork.Match( - artwork => artwork.DateUpdated < lastWriteTime, + artwork => lastWriteTime.Subtract(artwork.DateUpdated) > TimeSpan.FromSeconds(1), true); if (shouldRefresh) @@ -117,13 +119,14 @@ namespace ErsatzTV.Core.Metadata _logger.LogDebug("Refreshing {Attribute} from {Path}", artworkKind, artworkFile); string cacheName = _imageCache.CopyArtworkToCache(artworkFile, artworkKind); - maybeArtwork.Match( - artwork => + await maybeArtwork.Match( + async artwork => { artwork.Path = cacheName; artwork.DateUpdated = lastWriteTime; + await _metadataRepository.UpdateArtworkPath(artwork); }, - () => + async () => { var artwork = new Artwork { @@ -132,8 +135,8 @@ namespace ErsatzTV.Core.Metadata DateUpdated = lastWriteTime, ArtworkKind = artworkKind }; - metadata.Artwork.Add(artwork); + await _metadataRepository.AddArtwork(metadata, artwork); }); return true; diff --git a/ErsatzTV.Core/Metadata/LocalMetadataProvider.cs b/ErsatzTV.Core/Metadata/LocalMetadataProvider.cs index 6e4043a3e..f2ba13f09 100644 --- a/ErsatzTV.Core/Metadata/LocalMetadataProvider.cs +++ b/ErsatzTV.Core/Metadata/LocalMetadataProvider.cs @@ -23,16 +23,19 @@ namespace ErsatzTV.Core.Metadata private readonly ILogger _logger; private readonly IMediaItemRepository _mediaItemRepository; + private readonly IMetadataRepository _metadataRepository; private readonly ITelevisionRepository _televisionRepository; public LocalMetadataProvider( IMediaItemRepository mediaItemRepository, + IMetadataRepository metadataRepository, ITelevisionRepository televisionRepository, IFallbackMetadataProvider fallbackMetadataProvider, ILocalFileSystem localFileSystem, ILogger logger) { _mediaItemRepository = mediaItemRepository; + _metadataRepository = metadataRepository; _televisionRepository = televisionRepository; _fallbackMetadataProvider = fallbackMetadataProvider; _localFileSystem = localFileSystem; @@ -92,8 +95,11 @@ namespace ErsatzTV.Core.Metadata private async Task ApplyMetadataUpdate(Episode episode, Tuple metadataEpisodeNumber) { (EpisodeMetadata metadata, int episodeNumber) = metadataEpisodeNumber; - episode.EpisodeNumber = episodeNumber; - Optional(episode.EpisodeMetadata).Flatten().HeadOrNone().Match( + if (episode.EpisodeNumber != episodeNumber) + { + await _televisionRepository.SetEpisodeNumber(episode, episodeNumber); + } + await Optional(episode.EpisodeMetadata).Flatten().HeadOrNone().Match( existing => { existing.Outline = metadata.Outline; @@ -109,20 +115,22 @@ namespace ErsatzTV.Core.Metadata existing.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle) ? _fallbackMetadataProvider.GetSortTitle(metadata.Title) : metadata.SortTitle; + + return _metadataRepository.Update(existing); }, () => { metadata.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle) ? _fallbackMetadataProvider.GetSortTitle(metadata.Title) : metadata.SortTitle; + metadata.EpisodeId = episode.Id; episode.EpisodeMetadata = new List { metadata }; - }); - await _televisionRepository.Update(episode); + return _metadataRepository.Add(metadata); + }); } - private async Task ApplyMetadataUpdate(Movie movie, MovieMetadata metadata) - { + private Task ApplyMetadataUpdate(Movie movie, MovieMetadata metadata) => Optional(movie.MovieMetadata).Flatten().HeadOrNone().Match( existing => { @@ -163,20 +171,21 @@ namespace ErsatzTV.Core.Metadata { existing.Tags.Add(tag); } + + return _metadataRepository.Update(existing); }, () => { metadata.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle) ? _fallbackMetadataProvider.GetSortTitle(metadata.Title) : metadata.SortTitle; + metadata.MovieId = movie.Id; movie.MovieMetadata = new List { metadata }; + + return _metadataRepository.Add(metadata); }); - await _mediaItemRepository.Update(movie); - } - - private async Task ApplyMetadataUpdate(Show show, ShowMetadata metadata) - { + private Task ApplyMetadataUpdate(Show show, ShowMetadata metadata) => Optional(show.ShowMetadata).Flatten().HeadOrNone().Match( existing => { @@ -217,17 +226,19 @@ namespace ErsatzTV.Core.Metadata { existing.Tags.Add(tag); } + + return _metadataRepository.Update(existing); }, () => { metadata.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle) ? _fallbackMetadataProvider.GetSortTitle(metadata.Title) : metadata.SortTitle; + metadata.ShowId = show.Id; show.ShowMetadata = new List { metadata }; - }); - await _televisionRepository.Update(show); - } + return _metadataRepository.Add(metadata); + }); private async Task> LoadMetadata(Movie mediaItem, string nfoFileName) { diff --git a/ErsatzTV.Core/Metadata/LocalStatisticsProvider.cs b/ErsatzTV.Core/Metadata/LocalStatisticsProvider.cs index 1541f6870..8eacf5003 100644 --- a/ErsatzTV.Core/Metadata/LocalStatisticsProvider.cs +++ b/ErsatzTV.Core/Metadata/LocalStatisticsProvider.cs @@ -18,13 +18,16 @@ namespace ErsatzTV.Core.Metadata private readonly ILocalFileSystem _localFileSystem; private readonly ILogger _logger; private readonly IMediaItemRepository _mediaItemRepository; + private readonly IMetadataRepository _metadataRepository; public LocalStatisticsProvider( IMediaItemRepository mediaItemRepository, + IMetadataRepository metadataRepository, ILocalFileSystem localFileSystem, ILogger logger) { _mediaItemRepository = mediaItemRepository; + _metadataRepository = metadataRepository; _localFileSystem = localFileSystem; _logger = logger; } @@ -79,7 +82,7 @@ namespace ErsatzTV.Core.Metadata mediaItemVersion.VideoProfile = version.VideoProfile; mediaItemVersion.VideoScanKind = version.VideoScanKind; - return await _mediaItemRepository.Update(mediaItem) && durationChange; + return await _metadataRepository.UpdateLocalStatistics(mediaItemVersion) && durationChange; } private Task> GetProbeOutput(string ffprobePath, string filePath) diff --git a/ErsatzTV.Core/Metadata/MovieFolderScanner.cs b/ErsatzTV.Core/Metadata/MovieFolderScanner.cs index 020249629..2afdd23d1 100644 --- a/ErsatzTV.Core/Metadata/MovieFolderScanner.cs +++ b/ErsatzTV.Core/Metadata/MovieFolderScanner.cs @@ -27,9 +27,10 @@ namespace ErsatzTV.Core.Metadata IMovieRepository movieRepository, ILocalStatisticsProvider localStatisticsProvider, ILocalMetadataProvider localMetadataProvider, + IMetadataRepository metadataRepository, IImageCache imageCache, ILogger logger) - : base(localFileSystem, localStatisticsProvider, imageCache, logger) + : base(localFileSystem, localStatisticsProvider, metadataRepository, imageCache, logger) { _localFileSystem = localFileSystem; _movieRepository = movieRepository; @@ -74,7 +75,6 @@ namespace ErsatzTV.Core.Metadata foreach (string file in allFiles.OrderBy(identity)) { - // TODO: optimize dbcontext use here, do we need tracking? can we make partial updates with dapper? // TODO: figure out how to rebuild playlists Either maybeMovie = await _movieRepository .GetOrAdd(libraryPath, file) @@ -144,10 +144,7 @@ namespace ErsatzTV.Core.Metadata async posterFile => { MovieMetadata metadata = movie.MovieMetadata.Head(); - if (RefreshArtwork(posterFile, metadata, artworkKind)) - { - await _movieRepository.Update(movie); - } + await RefreshArtwork(posterFile, metadata, artworkKind); }); return movie; diff --git a/ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs b/ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs index bd8c75782..c8b97a2df 100644 --- a/ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs +++ b/ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs @@ -26,10 +26,12 @@ namespace ErsatzTV.Core.Metadata ITelevisionRepository televisionRepository, ILocalStatisticsProvider localStatisticsProvider, ILocalMetadataProvider localMetadataProvider, + IMetadataRepository metadataRepository, IImageCache imageCache, ILogger logger) : base( localFileSystem, localStatisticsProvider, + metadataRepository, imageCache, logger) { @@ -224,10 +226,7 @@ namespace ErsatzTV.Core.Metadata async posterFile => { ShowMetadata metadata = show.ShowMetadata.Head(); - if (RefreshArtwork(posterFile, metadata, artworkKind)) - { - await _televisionRepository.Update(show); - } + await RefreshArtwork(posterFile, metadata, artworkKind); }); return show; @@ -245,18 +244,8 @@ namespace ErsatzTV.Core.Metadata await LocatePoster(season, seasonFolder).IfSomeAsync( async posterFile => { - season.SeasonMetadata ??= new List(); - if (!season.SeasonMetadata.Any()) - { - season.SeasonMetadata.Add(new SeasonMetadata { SeasonId = season.Id }); - } - SeasonMetadata metadata = season.SeasonMetadata.Head(); - - if (RefreshArtwork(posterFile, metadata, ArtworkKind.Poster)) - { - await _televisionRepository.Update(season); - } + await RefreshArtwork(posterFile, metadata, ArtworkKind.Poster); }); return season; @@ -275,10 +264,7 @@ namespace ErsatzTV.Core.Metadata async posterFile => { EpisodeMetadata metadata = episode.EpisodeMetadata.Head(); - if (RefreshArtwork(posterFile, metadata, ArtworkKind.Thumbnail)) - { - await _televisionRepository.Update(episode); - } + await RefreshArtwork(posterFile, metadata, ArtworkKind.Thumbnail); }); return episode; diff --git a/ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs b/ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs index 8bc1b6410..8e2c25b36 100644 --- a/ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs +++ b/ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs @@ -97,7 +97,7 @@ namespace ErsatzTV.Core.Plex existingVersion.VideoScanKind = mediaVersion.VideoScanKind; existingVersion.DateUpdated = incomingVersion.DateUpdated; - await _metadataRepository.UpdateStatistics(existingVersion); + await _metadataRepository.UpdatePlexStatistics(existingVersion); }, _ => Task.CompletedTask); } @@ -127,6 +127,8 @@ namespace ErsatzTV.Core.Plex existingMetadata.Genres.Add(genre); await _movieRepository.AddGenre(existingMetadata, genre); } + + // TODO: update other metadata? } return existing; diff --git a/ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs b/ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs index fa8357462..d5de30869 100644 --- a/ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs +++ b/ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs @@ -257,7 +257,7 @@ namespace ErsatzTV.Core.Plex existingVersion.VideoScanKind = mediaVersion.VideoScanKind; existingVersion.DateUpdated = incomingVersion.DateUpdated; - await _metadataRepository.UpdateStatistics(existingVersion); + await _metadataRepository.UpdatePlexStatistics(existingVersion); }, _ => Task.CompletedTask); } diff --git a/ErsatzTV.Infrastructure/Data/Repositories/MediaSourceRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/MediaSourceRepository.cs index c8a5a4725..5c1dba40d 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/MediaSourceRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/MediaSourceRepository.cs @@ -171,8 +171,13 @@ namespace ErsatzTV.Infrastructure.Data.Repositories public async Task DeleteAllPlex() { await using TvContext context = _dbContextFactory.CreateDbContext(); + List allMediaSources = await context.PlexMediaSources.ToListAsync(); context.PlexMediaSources.RemoveRange(allMediaSources); + + List allPlexLibraries = await context.PlexLibraries.ToListAsync(); + context.PlexLibraries.RemoveRange(allPlexLibraries); + await context.SaveChangesAsync(); return Unit.Default; } diff --git a/ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs index af75f2a4b..42518bf89 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs @@ -4,19 +4,56 @@ using Dapper; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Repositories; using LanguageExt; +using Microsoft.EntityFrameworkCore; namespace ErsatzTV.Infrastructure.Data.Repositories { public class MetadataRepository : IMetadataRepository { private readonly IDbConnection _dbConnection; + private readonly IDbContextFactory _dbContextFactory; - public MetadataRepository(IDbConnection dbConnection) => _dbConnection = dbConnection; + public MetadataRepository(IDbContextFactory dbContextFactory, IDbConnection dbConnection) + { + _dbContextFactory = dbContextFactory; + _dbConnection = dbConnection; + } public Task RemoveGenre(Genre genre) => _dbConnection.ExecuteAsync("DELETE FROM Genre WHERE Id = @GenreId", new { GenreId = genre.Id }).ToUnit(); - public Task UpdateStatistics(MediaVersion mediaVersion) => + public async Task Update(Metadata metadata) + { + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + dbContext.Entry(metadata).State = EntityState.Modified; + return await dbContext.SaveChangesAsync() > 0; + } + + public async Task Add(Metadata metadata) + { + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + dbContext.Entry(metadata).State = EntityState.Added; + foreach (Genre genre in metadata.Genres) + { + dbContext.Entry(genre).State = EntityState.Added; + } + + foreach (Tag tag in metadata.Tags) + { + dbContext.Entry(tag).State = EntityState.Added; + } + + return await dbContext.SaveChangesAsync() > 0; + } + + public async Task UpdateLocalStatistics(MediaVersion mediaVersion) + { + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + dbContext.Entry(mediaVersion).State = EntityState.Modified; + return await dbContext.SaveChangesAsync() > 0; + } + + public Task UpdatePlexStatistics(MediaVersion mediaVersion) => _dbConnection.ExecuteAsync( @"UPDATE MediaVersion SET SampleAspectRatio = @SampleAspectRatio, diff --git a/ErsatzTV.Infrastructure/Data/Repositories/MovieRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/MovieRepository.cs index a795a7eae..d979996d0 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/MovieRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/MovieRepository.cs @@ -16,15 +16,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories public class MovieRepository : IMovieRepository { private readonly IDbConnection _dbConnection; - private readonly TvContext _dbContext; private readonly IDbContextFactory _dbContextFactory; - public MovieRepository( - TvContext dbContext, - IDbContextFactory dbContextFactory, - IDbConnection dbConnection) + public MovieRepository(IDbContextFactory dbContextFactory, IDbConnection dbConnection) { - _dbContext = dbContext; _dbContextFactory = dbContextFactory; _dbConnection = dbConnection; } @@ -52,7 +47,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories public async Task> GetOrAdd(LibraryPath libraryPath, string path) { - Option maybeExisting = await _dbContext.Movies + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + Option maybeExisting = await dbContext.Movies .Include(i => i.MovieMetadata) .ThenInclude(mm => mm.Artwork) .Include(i => i.MovieMetadata) @@ -67,7 +63,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories return await maybeExisting.Match( mediaItem => Right(mediaItem).AsTask(), - async () => await AddMovie(libraryPath.Id, path)); + async () => await AddMovie(dbContext, libraryPath.Id, path)); } public async Task> GetOrAdd(PlexLibrary library, PlexMovie item) @@ -89,26 +85,24 @@ namespace ErsatzTV.Infrastructure.Data.Repositories async () => await AddPlexMovie(context, library, item)); } - public async Task Update(Movie movie) - { - _dbContext.Movies.Update(movie); - return await _dbContext.SaveChangesAsync() > 0; - } - public Task GetMovieCount() => _dbConnection.QuerySingleAsync(@"SELECT COUNT(DISTINCT MovieId) FROM MovieMetadata"); - public Task> GetPagedMovies(int pageNumber, int pageSize) => - _dbContext.MovieMetadata.FromSqlRaw( + public async Task> GetPagedMovies(int pageNumber, int pageSize) + { + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + return await dbContext.MovieMetadata.FromSqlRaw( @"SELECT * FROM MovieMetadata WHERE Id IN (SELECT Id FROM MovieMetadata GROUP BY MovieId, MetadataKind HAVING MetadataKind = MAX(MetadataKind)) ORDER BY SortTitle LIMIT {0} OFFSET {1}", pageSize, (pageNumber - 1) * pageSize) + .AsNoTracking() .Include(mm => mm.Artwork) .OrderBy(mm => mm.SortTitle) .ToListAsync(); + } public Task> FindMoviePaths(LibraryPath libraryPath) => _dbConnection.QueryAsync( @@ -122,6 +116,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories public async Task DeleteByPath(LibraryPath libraryPath, string path) { + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); IEnumerable ids = await _dbConnection.QueryAsync( @"SELECT M.Id FROM Movie M @@ -133,11 +128,11 @@ namespace ErsatzTV.Infrastructure.Data.Repositories foreach (int movieId in ids) { - Movie movie = await _dbContext.Movies.FindAsync(movieId); - _dbContext.Movies.Remove(movie); + Movie movie = await dbContext.Movies.FindAsync(movieId); + dbContext.Movies.Remove(movie); } - await _dbContext.SaveChangesAsync(); + await dbContext.SaveChangesAsync(); return Unit.Default; } @@ -156,7 +151,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories WHERE lp.LibraryId = @LibraryId AND pm.Key not in @Keys)", new { LibraryId = library.Id, Keys = movieKeys }).ToUnit(); - private async Task> AddMovie(int libraryPathId, string path) + private static async Task> AddMovie( + TvContext dbContext, + int libraryPathId, + string path) { try { @@ -174,9 +172,9 @@ namespace ErsatzTV.Infrastructure.Data.Repositories } } }; - await _dbContext.Movies.AddAsync(movie); - await _dbContext.SaveChangesAsync(); - await _dbContext.Entry(movie).Reference(m => m.LibraryPath).LoadAsync(); + await dbContext.Movies.AddAsync(movie); + await dbContext.SaveChangesAsync(); + await dbContext.Entry(movie).Reference(m => m.LibraryPath).LoadAsync(); return movie; } catch (Exception ex) diff --git a/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs index b331a0f79..c9f2ddec2 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs @@ -16,15 +16,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories public class TelevisionRepository : ITelevisionRepository { private readonly IDbConnection _dbConnection; - private readonly TvContext _dbContext; private readonly IDbContextFactory _dbContextFactory; - public TelevisionRepository( - TvContext dbContext, - IDbConnection dbConnection, - IDbContextFactory dbContextFactory) + public TelevisionRepository(IDbConnection dbConnection, IDbContextFactory dbContextFactory) { - _dbContext = dbContext; _dbConnection = dbConnection; _dbContextFactory = dbContextFactory; } @@ -35,33 +30,20 @@ namespace ErsatzTV.Infrastructure.Data.Repositories new { ShowIds = showIds }) .Map(c => c == showIds.Count); - public async Task Update(Show show) + public async Task> GetAllShows() { - _dbContext.Shows.Update(show); - return await _dbContext.SaveChangesAsync() > 0; - } - - public async Task Update(Season season) - { - _dbContext.Seasons.Update(season); - return await _dbContext.SaveChangesAsync() > 0; - } - - public async Task Update(Episode episode) - { - _dbContext.Episodes.Update(episode); - return await _dbContext.SaveChangesAsync() > 0; - } - - public Task> GetAllShows() => - _dbContext.Shows + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + return await dbContext.Shows .AsNoTracking() .Include(s => s.ShowMetadata) .ThenInclude(sm => sm.Artwork) .ToListAsync(); + } - public Task> GetShow(int showId) => - _dbContext.Shows + public async Task> GetShow(int showId) + { + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + return await dbContext.Shows .AsNoTracking() .Filter(s => s.Id == showId) .Include(s => s.ShowMetadata) @@ -73,27 +55,37 @@ namespace ErsatzTV.Infrastructure.Data.Repositories .OrderBy(s => s.Id) .SingleOrDefaultAsync() .Map(Optional); + } - public Task GetShowCount() => - _dbContext.ShowMetadata + public async Task GetShowCount() + { + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + return await dbContext.ShowMetadata .AsNoTracking() .GroupBy(sm => new { sm.Title, sm.Year }) .CountAsync(); + } - public Task> GetPagedShows(int pageNumber, int pageSize) => - _dbContext.ShowMetadata.FromSqlRaw( + public async Task> GetPagedShows(int pageNumber, int pageSize) + { + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + return await dbContext.ShowMetadata.FromSqlRaw( @"SELECT * FROM ShowMetadata WHERE Id IN (SELECT MIN(Id) FROM ShowMetadata GROUP BY Title, Year, MetadataKind HAVING MetadataKind = MAX(MetadataKind)) ORDER BY SortTitle LIMIT {0} OFFSET {1}", pageSize, (pageNumber - 1) * pageSize) + .AsNoTracking() .Include(mm => mm.Artwork) .OrderBy(mm => mm.SortTitle) .ToListAsync(); + } - public Task> GetAllSeasons() => - _dbContext.Seasons + public async Task> GetAllSeasons() + { + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + return await dbContext.Seasons .AsNoTracking() .Include(s => s.SeasonMetadata) .ThenInclude(sm => sm.Artwork) @@ -101,9 +93,12 @@ namespace ErsatzTV.Infrastructure.Data.Repositories .ThenInclude(s => s.ShowMetadata) .ThenInclude(sm => sm.Artwork) .ToListAsync(); + } - public Task> GetSeason(int seasonId) => - _dbContext.Seasons + public async Task> GetSeason(int seasonId) + { + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + return await dbContext.Seasons .AsNoTracking() .Include(s => s.SeasonMetadata) .ThenInclude(sm => sm.Artwork) @@ -113,11 +108,15 @@ namespace ErsatzTV.Infrastructure.Data.Repositories .OrderBy(s => s.Id) .SingleOrDefaultAsync(s => s.Id == seasonId) .Map(Optional); + } - public Task GetSeasonCount(int showId) => - _dbContext.Seasons + public async Task GetSeasonCount(int showId) + { + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + return await dbContext.Seasons .AsNoTracking() .CountAsync(s => s.ShowId == showId); + } public async Task> GetPagedSeasons(int televisionShowId, int pageNumber, int pageSize) { @@ -129,7 +128,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories new { ShowId = televisionShowId }) .Map(results => results.ToList()); - return await _dbContext.Seasons + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + return await dbContext.Seasons .AsNoTracking() .Where(s => showIds.Contains(s.ShowId)) .Include(s => s.SeasonMetadata) @@ -142,8 +142,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories .ToListAsync(); } - public Task> GetEpisode(int episodeId) => - _dbContext.Episodes + public async Task> GetEpisode(int episodeId) + { + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + return await dbContext.Episodes .AsNoTracking() .Include(e => e.Season) .Include(e => e.EpisodeMetadata) @@ -151,14 +153,20 @@ namespace ErsatzTV.Infrastructure.Data.Repositories .OrderBy(s => s.Id) .SingleOrDefaultAsync(s => s.Id == episodeId) .Map(Optional); + } - public Task GetEpisodeCount(int seasonId) => - _dbContext.Episodes + public async Task GetEpisodeCount(int seasonId) + { + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + return await dbContext.Episodes .AsNoTracking() .CountAsync(e => e.SeasonId == seasonId); + } - public Task> GetPagedEpisodes(int seasonId, int pageNumber, int pageSize) => - _dbContext.EpisodeMetadata + public async Task> GetPagedEpisodes(int seasonId, int pageNumber, int pageSize) + { + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + return await dbContext.EpisodeMetadata .AsNoTracking() .Filter(em => em.Episode.SeasonId == seasonId) .Include(em => em.Artwork) @@ -170,10 +178,12 @@ namespace ErsatzTV.Infrastructure.Data.Repositories .Skip((pageNumber - 1) * pageSize) .Take(pageSize) .ToListAsync(); + } public async Task> GetShowByMetadata(int libraryPathId, ShowMetadata metadata) { - Option maybeId = await _dbContext.ShowMetadata + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + Option maybeId = await dbContext.ShowMetadata .Where(s => s.Title == metadata.Title && s.Year == metadata.Year) .Where(s => s.Show.LibraryPathId == libraryPathId) .SingleOrDefaultAsync() @@ -183,7 +193,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories return await maybeId.Match( id => { - return _dbContext.Shows + return dbContext.Shows + .AsNoTracking() .Include(s => s.ShowMetadata) .ThenInclude(sm => sm.Artwork) .Include(s => s.ShowMetadata) @@ -199,6 +210,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories public async Task> AddShow(int libraryPathId, string showFolder, ShowMetadata metadata) { + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + try { metadata.DateAdded = DateTime.UtcNow; @@ -211,8 +224,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories Seasons = new List() }; - await _dbContext.Shows.AddAsync(show); - await _dbContext.SaveChangesAsync(); + await dbContext.Shows.AddAsync(show); + await dbContext.SaveChangesAsync(); return show; } @@ -224,14 +237,17 @@ namespace ErsatzTV.Infrastructure.Data.Repositories public async Task> GetOrAddSeason(Show show, int libraryPathId, int seasonNumber) { - Option maybeExisting = await _dbContext.Seasons + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + Option maybeExisting = await dbContext.Seasons .Include(s => s.SeasonMetadata) .ThenInclude(sm => sm.Artwork) + .OrderBy(s => s.ShowId) + .ThenBy(s => s.SeasonNumber) .SingleOrDefaultAsync(s => s.ShowId == show.Id && s.SeasonNumber == seasonNumber); return await maybeExisting.Match( season => Right(season).AsTask(), - () => AddSeason(show, libraryPathId, seasonNumber)); + () => AddSeason(dbContext, show, libraryPathId, seasonNumber)); } public async Task> GetOrAddEpisode( @@ -239,7 +255,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories LibraryPath libraryPath, string path) { - Option maybeExisting = await _dbContext.Episodes + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + Option maybeExisting = await dbContext.Episodes .Include(i => i.EpisodeMetadata) .ThenInclude(em => em.Artwork) .Include(i => i.MediaVersions) @@ -249,7 +266,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories return await maybeExisting.Match( episode => Right(episode).AsTask(), - () => AddEpisode(season, libraryPath.Id, path)); + () => AddEpisode(dbContext, season, libraryPath.Id, path)); } public Task> FindEpisodePaths(LibraryPath libraryPath) => @@ -273,47 +290,46 @@ namespace ErsatzTV.Infrastructure.Data.Repositories WHERE MI.LibraryPathId = @LibraryPathId AND MF.Path = @Path", new { LibraryPathId = libraryPath.Id, Path = path }); + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); foreach (int episodeId in ids) { - Episode episode = await _dbContext.Episodes.FindAsync(episodeId); - _dbContext.Episodes.Remove(episode); + Episode episode = await dbContext.Episodes.FindAsync(episodeId); + dbContext.Episodes.Remove(episode); } - await _dbContext.SaveChangesAsync(); + await dbContext.SaveChangesAsync(); return Unit.Default; } - public Task DeleteEmptySeasons(LibraryPath libraryPath) => - _dbContext.Seasons + public async Task DeleteEmptySeasons(LibraryPath libraryPath) + { + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + List seasons = await dbContext.Seasons .Filter(s => s.LibraryPathId == libraryPath.Id) .Filter(s => s.Episodes.Count == 0) - .ToListAsync() - .Bind( - list => - { - _dbContext.Seasons.RemoveRange(list); - return _dbContext.SaveChangesAsync(); - }) - .ToUnit(); + .ToListAsync(); + dbContext.Seasons.RemoveRange(seasons); + await dbContext.SaveChangesAsync(); + return Unit.Default; + } - public Task DeleteEmptyShows(LibraryPath libraryPath) => - _dbContext.Shows + public async Task DeleteEmptyShows(LibraryPath libraryPath) + { + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + List shows = await dbContext.Shows .Filter(s => s.LibraryPathId == libraryPath.Id) .Filter(s => s.Seasons.Count == 0) - .ToListAsync() - .Bind( - list => - { - _dbContext.Shows.RemoveRange(list); - return _dbContext.SaveChangesAsync(); - }) - .ToUnit(); + .ToListAsync(); + dbContext.Shows.RemoveRange(shows); + await dbContext.SaveChangesAsync(); + return Unit.Default; + } public async Task> GetOrAddPlexShow(PlexLibrary library, PlexShow item) { - await using TvContext context = _dbContextFactory.CreateDbContext(); - Option maybeExisting = await context.PlexShows + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + Option maybeExisting = await dbContext.PlexShows .AsNoTracking() .Include(i => i.ShowMetadata) .ThenInclude(mm => mm.Genres) @@ -324,13 +340,13 @@ namespace ErsatzTV.Infrastructure.Data.Repositories return await maybeExisting.Match( plexShow => Right(plexShow).AsTask(), - async () => await AddPlexShow(context, library, item)); + async () => await AddPlexShow(dbContext, library, item)); } public async Task> GetOrAddPlexSeason(PlexLibrary library, PlexSeason item) { - await using TvContext context = _dbContextFactory.CreateDbContext(); - Option maybeExisting = await context.PlexSeasons + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + Option maybeExisting = await dbContext.PlexSeasons .AsNoTracking() .Include(i => i.SeasonMetadata) .ThenInclude(mm => mm.Artwork) @@ -339,13 +355,13 @@ namespace ErsatzTV.Infrastructure.Data.Repositories return await maybeExisting.Match( plexSeason => Right(plexSeason).AsTask(), - async () => await AddPlexSeason(context, library, item)); + async () => await AddPlexSeason(dbContext, library, item)); } public async Task> GetOrAddPlexEpisode(PlexLibrary library, PlexEpisode item) { - await using TvContext context = _dbContextFactory.CreateDbContext(); - Option maybeExisting = await context.PlexEpisodes + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + Option maybeExisting = await dbContext.PlexEpisodes .AsNoTracking() .Include(i => i.EpisodeMetadata) .ThenInclude(mm => mm.Artwork) @@ -356,7 +372,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories return await maybeExisting.Match( plexEpisode => Right(plexEpisode).AsTask(), - async () => await AddPlexEpisode(context, library, item)); + async () => await AddPlexEpisode(dbContext, library, item)); } public Task AddGenre(ShowMetadata metadata, Genre genre) => @@ -372,7 +388,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories INNER JOIN LibraryPath lp ON lp.Id = m.LibraryPathId WHERE lp.LibraryId = @LibraryId AND ps.Key not in @Keys)", new { LibraryId = library.Id, Keys = showKeys }).ToUnit(); - + public Task RemoveMissingPlexSeasons(string showKey, List seasonKeys) => _dbConnection.ExecuteAsync( @"DELETE FROM MediaItem WHERE Id IN @@ -382,7 +398,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories INNER JOIN PlexShow P on P.Id = s.ShowId WHERE P.Key = @ShowKey AND ps.Key not in @Keys)", new { ShowKey = showKey, Keys = seasonKeys }).ToUnit(); - + public Task RemoveMissingPlexEpisodes(string seasonKey, List episodeKeys) => _dbConnection.ExecuteAsync( @"DELETE FROM MediaItem WHERE Id IN @@ -393,6 +409,15 @@ namespace ErsatzTV.Infrastructure.Data.Repositories WHERE P.Key = @SeasonKey AND pe.Key not in @Keys)", new { SeasonKey = seasonKey, Keys = episodeKeys }).ToUnit(); + public async Task SetEpisodeNumber(Episode episode, int episodeNumber) + { + episode.EpisodeNumber = episodeNumber; + await _dbConnection.ExecuteAsync( + @"UPDATE Episode SET EpisodeNumber = @EpisodeNumber WHERE Id = @Id", + new { EpisodeNumber = episodeNumber, Id = episode.Id }); + return Unit.Default; + } + public async Task> GetShowItems(int showId) { IEnumerable ids = await _dbConnection.QueryAsync( @@ -402,7 +427,9 @@ namespace ErsatzTV.Infrastructure.Data.Repositories WHERE Show.Id = @ShowId", new { ShowId = showId }); - return await _dbContext.Episodes + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + return await dbContext.Episodes + .AsNoTracking() .Include(e => e.EpisodeMetadata) .Include(e => e.MediaVersions) .Include(e => e.Season) @@ -410,15 +437,23 @@ namespace ErsatzTV.Infrastructure.Data.Repositories .ToListAsync(); } - public Task> GetSeasonItems(int seasonId) => - _dbContext.Episodes + public async Task> GetSeasonItems(int seasonId) + { + await using TvContext dbContext = _dbContextFactory.CreateDbContext(); + return await dbContext.Episodes + .AsNoTracking() .Include(e => e.EpisodeMetadata) .Include(e => e.MediaVersions) .Include(e => e.Season) .Filter(e => e.SeasonId == seasonId) .ToListAsync(); + } - private async Task> AddSeason(Show show, int libraryPathId, int seasonNumber) + private static async Task> AddSeason( + TvContext dbContext, + Show show, + int libraryPathId, + int seasonNumber) { try { @@ -428,10 +463,13 @@ namespace ErsatzTV.Infrastructure.Data.Repositories ShowId = show.Id, SeasonNumber = seasonNumber, Episodes = new List(), - SeasonMetadata = new List() + SeasonMetadata = new List + { + new() + } }; - await _dbContext.Seasons.AddAsync(season); - await _dbContext.SaveChangesAsync(); + await dbContext.Seasons.AddAsync(season); + await dbContext.SaveChangesAsync(); return season; } catch (Exception ex) @@ -440,7 +478,11 @@ namespace ErsatzTV.Infrastructure.Data.Repositories } } - private async Task> AddEpisode(Season season, int libraryPathId, string path) + private static async Task> AddEpisode( + TvContext dbContext, + Season season, + int libraryPathId, + string path) { try { @@ -448,7 +490,14 @@ namespace ErsatzTV.Infrastructure.Data.Repositories { LibraryPathId = libraryPathId, SeasonId = season.Id, - EpisodeMetadata = new List(), + EpisodeMetadata = new List + { + new() + { + DateUpdated = DateTime.MinValue, + MetadataKind = MetadataKind.Fallback + } + }, MediaVersions = new List { new() @@ -460,8 +509,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories } } }; - await _dbContext.Episodes.AddAsync(episode); - await _dbContext.SaveChangesAsync(); + await dbContext.Episodes.AddAsync(episode); + await dbContext.SaveChangesAsync(); return episode; } catch (Exception ex) @@ -470,8 +519,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories } } - private async Task> AddPlexShow( - TvContext context, + private static async Task> AddPlexShow( + TvContext dbContext, PlexLibrary library, PlexShow item) { @@ -479,9 +528,9 @@ namespace ErsatzTV.Infrastructure.Data.Repositories { item.LibraryPathId = library.Paths.Head().Id; - await context.PlexShows.AddAsync(item); - await context.SaveChangesAsync(); - await context.Entry(item).Reference(i => i.LibraryPath).LoadAsync(); + await dbContext.PlexShows.AddAsync(item); + await dbContext.SaveChangesAsync(); + await dbContext.Entry(item).Reference(i => i.LibraryPath).LoadAsync(); return item; } catch (Exception ex) @@ -490,8 +539,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories } } - private async Task> AddPlexSeason( - TvContext context, + private static async Task> AddPlexSeason( + TvContext dbContext, PlexLibrary library, PlexSeason item) { @@ -499,9 +548,9 @@ namespace ErsatzTV.Infrastructure.Data.Repositories { item.LibraryPathId = library.Paths.Head().Id; - await context.PlexSeasons.AddAsync(item); - await context.SaveChangesAsync(); - await context.Entry(item).Reference(i => i.LibraryPath).LoadAsync(); + await dbContext.PlexSeasons.AddAsync(item); + await dbContext.SaveChangesAsync(); + await dbContext.Entry(item).Reference(i => i.LibraryPath).LoadAsync(); return item; } catch (Exception ex) @@ -510,8 +559,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories } } - private async Task> AddPlexEpisode( - TvContext context, + private static async Task> AddPlexEpisode( + TvContext dbContext, PlexLibrary library, PlexEpisode item) { @@ -519,9 +568,9 @@ namespace ErsatzTV.Infrastructure.Data.Repositories { item.LibraryPathId = library.Paths.Head().Id; - await context.PlexEpisodes.AddAsync(item); - await context.SaveChangesAsync(); - await context.Entry(item).Reference(i => i.LibraryPath).LoadAsync(); + await dbContext.PlexEpisodes.AddAsync(item); + await dbContext.SaveChangesAsync(); + await dbContext.Entry(item).Reference(i => i.LibraryPath).LoadAsync(); return item; } catch (Exception ex)