Browse Source

optimize local scanning (#84)

* optimize local scanning

* fix artwork updates

* fix adding genres and tags

* fix movie fallback metadata
pull/85/head
Jason Dove 5 years ago committed by GitHub
parent
commit
739d074bc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      ErsatzTV.Core.Tests/Fakes/FakeTelevisionRepository.cs
  2. 1
      ErsatzTV.Core.Tests/Metadata/MovieFolderScannerTests.cs
  3. 5
      ErsatzTV.Core/Interfaces/Repositories/IMetadataRepository.cs
  4. 1
      ErsatzTV.Core/Interfaces/Repositories/IMovieRepository.cs
  5. 4
      ErsatzTV.Core/Interfaces/Repositories/ITelevisionRepository.cs
  6. 3
      ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs
  7. 21
      ErsatzTV.Core/Metadata/LocalFolderScanner.cs
  8. 39
      ErsatzTV.Core/Metadata/LocalMetadataProvider.cs
  9. 5
      ErsatzTV.Core/Metadata/LocalStatisticsProvider.cs
  10. 9
      ErsatzTV.Core/Metadata/MovieFolderScanner.cs
  11. 24
      ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs
  12. 4
      ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs
  13. 2
      ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs
  14. 5
      ErsatzTV.Infrastructure/Data/Repositories/MediaSourceRepository.cs
  15. 41
      ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs
  16. 44
      ErsatzTV.Infrastructure/Data/Repositories/MovieRepository.cs
  17. 273
      ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs

14
ErsatzTV.Core.Tests/Fakes/FakeTelevisionRepository.cs

@ -11,12 +11,6 @@ namespace ErsatzTV.Core.Tests.Fakes @@ -11,12 +11,6 @@ namespace ErsatzTV.Core.Tests.Fakes
{
public Task<bool> AllShowsExist(List<int> showIds) => throw new NotSupportedException();
public Task<bool> Update(Show show) => throw new NotSupportedException();
public Task<bool> Update(Season season) => throw new NotSupportedException();
public Task<bool> Update(Episode episode) => throw new NotSupportedException();
public Task<List<Show>> GetAllShows() => throw new NotSupportedException();
public Task<Option<Show>> GetShow(int showId) => throw new NotSupportedException();
@ -86,5 +80,13 @@ namespace ErsatzTV.Core.Tests.Fakes @@ -86,5 +80,13 @@ namespace ErsatzTV.Core.Tests.Fakes
public Task<Unit> RemoveMissingPlexEpisodes(string seasonKey, List<string> episodeKeys) =>
throw new NotSupportedException();
public Task<Unit> SetEpisodeNumber(Episode episode, int episodeNumber) => throw new NotSupportedException();
public Task<bool> Update(Show show) => throw new NotSupportedException();
public Task<bool> Update(Season season) => throw new NotSupportedException();
public Task<bool> Update(Episode episode) => throw new NotSupportedException();
}
}

1
ErsatzTV.Core.Tests/Metadata/MovieFolderScannerTests.cs

@ -417,6 +417,7 @@ namespace ErsatzTV.Core.Tests.Metadata @@ -417,6 +417,7 @@ namespace ErsatzTV.Core.Tests.Metadata
_movieRepository.Object,
_localStatisticsProvider.Object,
_localMetadataProvider.Object,
new Mock<IMetadataRepository>().Object,
_imageCache.Object,
new Mock<ILogger<MovieFolderScanner>>().Object
);

5
ErsatzTV.Core/Interfaces/Repositories/IMetadataRepository.cs

@ -7,7 +7,10 @@ namespace ErsatzTV.Core.Interfaces.Repositories @@ -7,7 +7,10 @@ namespace ErsatzTV.Core.Interfaces.Repositories
public interface IMetadataRepository
{
Task<Unit> RemoveGenre(Genre genre);
Task<Unit> UpdateStatistics(MediaVersion mediaVersion);
Task<bool> Update(Domain.Metadata metadata);
Task<bool> Add(Domain.Metadata metadata);
Task<bool> UpdateLocalStatistics(MediaVersion mediaVersion);
Task<Unit> UpdatePlexStatistics(MediaVersion mediaVersion);
Task<Unit> UpdateArtworkPath(Artwork artwork);
Task<Unit> AddArtwork(Domain.Metadata metadata, Artwork artwork);
Task<Unit> RemoveArtwork(Domain.Metadata metadata, ArtworkKind artworkKind);

1
ErsatzTV.Core/Interfaces/Repositories/IMovieRepository.cs

@ -11,7 +11,6 @@ namespace ErsatzTV.Core.Interfaces.Repositories @@ -11,7 +11,6 @@ namespace ErsatzTV.Core.Interfaces.Repositories
Task<Option<Movie>> GetMovie(int movieId);
Task<Either<BaseError, Movie>> GetOrAdd(LibraryPath libraryPath, string path);
Task<Either<BaseError, PlexMovie>> GetOrAdd(PlexLibrary library, PlexMovie item);
Task<bool> Update(Movie movie);
Task<int> GetMovieCount();
Task<List<MovieMetadata>> GetPagedMovies(int pageNumber, int pageSize);
Task<IEnumerable<string>> FindMoviePaths(LibraryPath libraryPath);

4
ErsatzTV.Core/Interfaces/Repositories/ITelevisionRepository.cs

@ -8,9 +8,6 @@ namespace ErsatzTV.Core.Interfaces.Repositories @@ -8,9 +8,6 @@ namespace ErsatzTV.Core.Interfaces.Repositories
public interface ITelevisionRepository
{
Task<bool> AllShowsExist(List<int> showIds);
Task<bool> Update(Show show);
Task<bool> Update(Season season);
Task<bool> Update(Episode episode);
Task<List<Show>> GetAllShows();
Task<Option<Show>> GetShow(int showId);
Task<int> GetShowCount();
@ -39,5 +36,6 @@ namespace ErsatzTV.Core.Interfaces.Repositories @@ -39,5 +36,6 @@ namespace ErsatzTV.Core.Interfaces.Repositories
Task<Unit> RemoveMissingPlexShows(PlexLibrary library, List<string> showKeys);
Task<Unit> RemoveMissingPlexSeasons(string showKey, List<string> seasonKeys);
Task<Unit> RemoveMissingPlexEpisodes(string seasonKey, List<string> episodeKeys);
Task<Unit> SetEpisodeNumber(Episode episode, int episodeNumber);
}
}

3
ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs

@ -1,4 +1,5 @@ @@ -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 @@ -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<Genre>();
metadata.Tags = new List<Tag>();
}
}
catch (Exception)

21
ErsatzTV.Core/Metadata/LocalFolderScanner.cs

@ -7,9 +7,9 @@ using System.Threading.Tasks; @@ -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 @@ -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 @@ -99,17 +102,16 @@ namespace ErsatzTV.Core.Metadata
}
}
protected bool RefreshArtwork(string artworkFile, Domain.Metadata metadata, ArtworkKind artworkKind)
protected async Task<bool> RefreshArtwork(string artworkFile, Domain.Metadata metadata, ArtworkKind artworkKind)
{
DateTime lastWriteTime = _localFileSystem.GetLastWriteTime(artworkFile);
metadata.Artwork ??= new List<Artwork>();
Option<Artwork> maybeArtwork =
Optional(metadata.Artwork).Flatten().FirstOrDefault(a => a.ArtworkKind == artworkKind);
Option<Artwork> 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 @@ -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 @@ -132,8 +135,8 @@ namespace ErsatzTV.Core.Metadata
DateUpdated = lastWriteTime,
ArtworkKind = artworkKind
};
metadata.Artwork.Add(artwork);
await _metadataRepository.AddArtwork(metadata, artwork);
});
return true;

39
ErsatzTV.Core/Metadata/LocalMetadataProvider.cs

@ -23,16 +23,19 @@ namespace ErsatzTV.Core.Metadata @@ -23,16 +23,19 @@ namespace ErsatzTV.Core.Metadata
private readonly ILogger<LocalMetadataProvider> _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<LocalMetadataProvider> logger)
{
_mediaItemRepository = mediaItemRepository;
_metadataRepository = metadataRepository;
_televisionRepository = televisionRepository;
_fallbackMetadataProvider = fallbackMetadataProvider;
_localFileSystem = localFileSystem;
@ -92,8 +95,11 @@ namespace ErsatzTV.Core.Metadata @@ -92,8 +95,11 @@ namespace ErsatzTV.Core.Metadata
private async Task ApplyMetadataUpdate(Episode episode, Tuple<EpisodeMetadata, int> 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 @@ -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<EpisodeMetadata> { 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 @@ -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<MovieMetadata> { metadata };
});
await _mediaItemRepository.Update(movie);
}
return _metadataRepository.Add(metadata);
});
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 @@ -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<ShowMetadata> { metadata };
});
await _televisionRepository.Update(show);
}
return _metadataRepository.Add(metadata);
});
private async Task<Option<MovieMetadata>> LoadMetadata(Movie mediaItem, string nfoFileName)
{

5
ErsatzTV.Core/Metadata/LocalStatisticsProvider.cs

@ -18,13 +18,16 @@ namespace ErsatzTV.Core.Metadata @@ -18,13 +18,16 @@ namespace ErsatzTV.Core.Metadata
private readonly ILocalFileSystem _localFileSystem;
private readonly ILogger<LocalStatisticsProvider> _logger;
private readonly IMediaItemRepository _mediaItemRepository;
private readonly IMetadataRepository _metadataRepository;
public LocalStatisticsProvider(
IMediaItemRepository mediaItemRepository,
IMetadataRepository metadataRepository,
ILocalFileSystem localFileSystem,
ILogger<LocalStatisticsProvider> logger)
{
_mediaItemRepository = mediaItemRepository;
_metadataRepository = metadataRepository;
_localFileSystem = localFileSystem;
_logger = logger;
}
@ -79,7 +82,7 @@ namespace ErsatzTV.Core.Metadata @@ -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<Either<BaseError, FFprobe>> GetProbeOutput(string ffprobePath, string filePath)

9
ErsatzTV.Core/Metadata/MovieFolderScanner.cs

@ -27,9 +27,10 @@ namespace ErsatzTV.Core.Metadata @@ -27,9 +27,10 @@ namespace ErsatzTV.Core.Metadata
IMovieRepository movieRepository,
ILocalStatisticsProvider localStatisticsProvider,
ILocalMetadataProvider localMetadataProvider,
IMetadataRepository metadataRepository,
IImageCache imageCache,
ILogger<MovieFolderScanner> logger)
: base(localFileSystem, localStatisticsProvider, imageCache, logger)
: base(localFileSystem, localStatisticsProvider, metadataRepository, imageCache, logger)
{
_localFileSystem = localFileSystem;
_movieRepository = movieRepository;
@ -74,7 +75,6 @@ namespace ErsatzTV.Core.Metadata @@ -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<BaseError, Movie> maybeMovie = await _movieRepository
.GetOrAdd(libraryPath, file)
@ -144,10 +144,7 @@ namespace ErsatzTV.Core.Metadata @@ -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;

24
ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs

@ -26,10 +26,12 @@ namespace ErsatzTV.Core.Metadata @@ -26,10 +26,12 @@ namespace ErsatzTV.Core.Metadata
ITelevisionRepository televisionRepository,
ILocalStatisticsProvider localStatisticsProvider,
ILocalMetadataProvider localMetadataProvider,
IMetadataRepository metadataRepository,
IImageCache imageCache,
ILogger<TelevisionFolderScanner> logger) : base(
localFileSystem,
localStatisticsProvider,
metadataRepository,
imageCache,
logger)
{
@ -224,10 +226,7 @@ namespace ErsatzTV.Core.Metadata @@ -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 @@ -245,18 +244,8 @@ namespace ErsatzTV.Core.Metadata
await LocatePoster(season, seasonFolder).IfSomeAsync(
async posterFile =>
{
season.SeasonMetadata ??= new List<SeasonMetadata>();
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 @@ -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;

4
ErsatzTV.Core/Plex/PlexMovieLibraryScanner.cs

@ -97,7 +97,7 @@ namespace ErsatzTV.Core.Plex @@ -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 @@ -127,6 +127,8 @@ namespace ErsatzTV.Core.Plex
existingMetadata.Genres.Add(genre);
await _movieRepository.AddGenre(existingMetadata, genre);
}
// TODO: update other metadata?
}
return existing;

2
ErsatzTV.Core/Plex/PlexTelevisionLibraryScanner.cs

@ -257,7 +257,7 @@ namespace ErsatzTV.Core.Plex @@ -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);
}

5
ErsatzTV.Infrastructure/Data/Repositories/MediaSourceRepository.cs

@ -171,8 +171,13 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -171,8 +171,13 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
public async Task<Unit> DeleteAllPlex()
{
await using TvContext context = _dbContextFactory.CreateDbContext();
List<PlexMediaSource> allMediaSources = await context.PlexMediaSources.ToListAsync();
context.PlexMediaSources.RemoveRange(allMediaSources);
List<PlexLibrary> allPlexLibraries = await context.PlexLibraries.ToListAsync();
context.PlexLibraries.RemoveRange(allPlexLibraries);
await context.SaveChangesAsync();
return Unit.Default;
}

41
ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs

@ -4,19 +4,56 @@ using Dapper; @@ -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<TvContext> _dbContextFactory;
public MetadataRepository(IDbConnection dbConnection) => _dbConnection = dbConnection;
public MetadataRepository(IDbContextFactory<TvContext> dbContextFactory, IDbConnection dbConnection)
{
_dbContextFactory = dbContextFactory;
_dbConnection = dbConnection;
}
public Task<Unit> RemoveGenre(Genre genre) =>
_dbConnection.ExecuteAsync("DELETE FROM Genre WHERE Id = @GenreId", new { GenreId = genre.Id }).ToUnit();
public Task<Unit> UpdateStatistics(MediaVersion mediaVersion) =>
public async Task<bool> Update(Metadata metadata)
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
dbContext.Entry(metadata).State = EntityState.Modified;
return await dbContext.SaveChangesAsync() > 0;
}
public async Task<bool> 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<bool> UpdateLocalStatistics(MediaVersion mediaVersion)
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
dbContext.Entry(mediaVersion).State = EntityState.Modified;
return await dbContext.SaveChangesAsync() > 0;
}
public Task<Unit> UpdatePlexStatistics(MediaVersion mediaVersion) =>
_dbConnection.ExecuteAsync(
@"UPDATE MediaVersion SET
SampleAspectRatio = @SampleAspectRatio,

44
ErsatzTV.Infrastructure/Data/Repositories/MovieRepository.cs

@ -16,15 +16,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -16,15 +16,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
public class MovieRepository : IMovieRepository
{
private readonly IDbConnection _dbConnection;
private readonly TvContext _dbContext;
private readonly IDbContextFactory<TvContext> _dbContextFactory;
public MovieRepository(
TvContext dbContext,
IDbContextFactory<TvContext> dbContextFactory,
IDbConnection dbConnection)
public MovieRepository(IDbContextFactory<TvContext> dbContextFactory, IDbConnection dbConnection)
{
_dbContext = dbContext;
_dbContextFactory = dbContextFactory;
_dbConnection = dbConnection;
}
@ -52,7 +47,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -52,7 +47,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
public async Task<Either<BaseError, Movie>> GetOrAdd(LibraryPath libraryPath, string path)
{
Option<Movie> maybeExisting = await _dbContext.Movies
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
Option<Movie> 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 @@ -67,7 +63,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
return await maybeExisting.Match(
mediaItem => Right<BaseError, Movie>(mediaItem).AsTask(),
async () => await AddMovie(libraryPath.Id, path));
async () => await AddMovie(dbContext, libraryPath.Id, path));
}
public async Task<Either<BaseError, PlexMovie>> GetOrAdd(PlexLibrary library, PlexMovie item)
@ -89,26 +85,24 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -89,26 +85,24 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
async () => await AddPlexMovie(context, library, item));
}
public async Task<bool> Update(Movie movie)
{
_dbContext.Movies.Update(movie);
return await _dbContext.SaveChangesAsync() > 0;
}
public Task<int> GetMovieCount() =>
_dbConnection.QuerySingleAsync<int>(@"SELECT COUNT(DISTINCT MovieId) FROM MovieMetadata");
public Task<List<MovieMetadata>> GetPagedMovies(int pageNumber, int pageSize) =>
_dbContext.MovieMetadata.FromSqlRaw(
public async Task<List<MovieMetadata>> 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<IEnumerable<string>> FindMoviePaths(LibraryPath libraryPath) =>
_dbConnection.QueryAsync<string>(
@ -122,6 +116,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -122,6 +116,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
public async Task<Unit> DeleteByPath(LibraryPath libraryPath, string path)
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
IEnumerable<int> ids = await _dbConnection.QueryAsync<int>(
@"SELECT M.Id
FROM Movie M
@ -133,11 +128,11 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -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 @@ -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<Either<BaseError, Movie>> AddMovie(int libraryPathId, string path)
private static async Task<Either<BaseError, Movie>> AddMovie(
TvContext dbContext,
int libraryPathId,
string path)
{
try
{
@ -174,9 +172,9 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -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)

273
ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs

@ -16,15 +16,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -16,15 +16,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
public class TelevisionRepository : ITelevisionRepository
{
private readonly IDbConnection _dbConnection;
private readonly TvContext _dbContext;
private readonly IDbContextFactory<TvContext> _dbContextFactory;
public TelevisionRepository(
TvContext dbContext,
IDbConnection dbConnection,
IDbContextFactory<TvContext> dbContextFactory)
public TelevisionRepository(IDbConnection dbConnection, IDbContextFactory<TvContext> dbContextFactory)
{
_dbContext = dbContext;
_dbConnection = dbConnection;
_dbContextFactory = dbContextFactory;
}
@ -35,33 +30,20 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -35,33 +30,20 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
new { ShowIds = showIds })
.Map(c => c == showIds.Count);
public async Task<bool> Update(Show show)
{
_dbContext.Shows.Update(show);
return await _dbContext.SaveChangesAsync() > 0;
}
public async Task<bool> Update(Season season)
public async Task<List<Show>> GetAllShows()
{
_dbContext.Seasons.Update(season);
return await _dbContext.SaveChangesAsync() > 0;
}
public async Task<bool> Update(Episode episode)
{
_dbContext.Episodes.Update(episode);
return await _dbContext.SaveChangesAsync() > 0;
}
public Task<List<Show>> 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<Option<Show>> GetShow(int showId) =>
_dbContext.Shows
public async Task<Option<Show>> 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 @@ -73,27 +55,37 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.OrderBy(s => s.Id)
.SingleOrDefaultAsync()
.Map(Optional);
}
public Task<int> GetShowCount() =>
_dbContext.ShowMetadata
public async Task<int> GetShowCount()
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
return await dbContext.ShowMetadata
.AsNoTracking()
.GroupBy(sm => new { sm.Title, sm.Year })
.CountAsync();
}
public Task<List<ShowMetadata>> GetPagedShows(int pageNumber, int pageSize) =>
_dbContext.ShowMetadata.FromSqlRaw(
public async Task<List<ShowMetadata>> 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<List<Season>> GetAllSeasons() =>
_dbContext.Seasons
public async Task<List<Season>> 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 @@ -101,9 +93,12 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.ThenInclude(s => s.ShowMetadata)
.ThenInclude(sm => sm.Artwork)
.ToListAsync();
}
public Task<Option<Season>> GetSeason(int seasonId) =>
_dbContext.Seasons
public async Task<Option<Season>> 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 @@ -113,11 +108,15 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.OrderBy(s => s.Id)
.SingleOrDefaultAsync(s => s.Id == seasonId)
.Map(Optional);
}
public Task<int> GetSeasonCount(int showId) =>
_dbContext.Seasons
public async Task<int> GetSeasonCount(int showId)
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
return await dbContext.Seasons
.AsNoTracking()
.CountAsync(s => s.ShowId == showId);
}
public async Task<List<Season>> GetPagedSeasons(int televisionShowId, int pageNumber, int pageSize)
{
@ -129,7 +128,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -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 @@ -142,8 +142,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.ToListAsync();
}
public Task<Option<Episode>> GetEpisode(int episodeId) =>
_dbContext.Episodes
public async Task<Option<Episode>> 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 @@ -151,14 +153,20 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.OrderBy(s => s.Id)
.SingleOrDefaultAsync(s => s.Id == episodeId)
.Map(Optional);
}
public Task<int> GetEpisodeCount(int seasonId) =>
_dbContext.Episodes
public async Task<int> GetEpisodeCount(int seasonId)
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
return await dbContext.Episodes
.AsNoTracking()
.CountAsync(e => e.SeasonId == seasonId);
}
public Task<List<EpisodeMetadata>> GetPagedEpisodes(int seasonId, int pageNumber, int pageSize) =>
_dbContext.EpisodeMetadata
public async Task<List<EpisodeMetadata>> 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 @@ -170,10 +178,12 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
}
public async Task<Option<Show>> GetShowByMetadata(int libraryPathId, ShowMetadata metadata)
{
Option<int> maybeId = await _dbContext.ShowMetadata
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
Option<int> 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 @@ -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 @@ -199,6 +210,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
public async Task<Either<BaseError, Show>> 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 @@ -211,8 +224,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
Seasons = new List<Season>()
};
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 @@ -224,14 +237,17 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
public async Task<Either<BaseError, Season>> GetOrAddSeason(Show show, int libraryPathId, int seasonNumber)
{
Option<Season> maybeExisting = await _dbContext.Seasons
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
Option<Season> 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<BaseError, Season>(season).AsTask(),
() => AddSeason(show, libraryPathId, seasonNumber));
() => AddSeason(dbContext, show, libraryPathId, seasonNumber));
}
public async Task<Either<BaseError, Episode>> GetOrAddEpisode(
@ -239,7 +255,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -239,7 +255,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
LibraryPath libraryPath,
string path)
{
Option<Episode> maybeExisting = await _dbContext.Episodes
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
Option<Episode> 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 @@ -249,7 +266,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
return await maybeExisting.Match(
episode => Right<BaseError, Episode>(episode).AsTask(),
() => AddEpisode(season, libraryPath.Id, path));
() => AddEpisode(dbContext, season, libraryPath.Id, path));
}
public Task<IEnumerable<string>> FindEpisodePaths(LibraryPath libraryPath) =>
@ -273,47 +290,46 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -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<Unit> DeleteEmptySeasons(LibraryPath libraryPath) =>
_dbContext.Seasons
public async Task<Unit> DeleteEmptySeasons(LibraryPath libraryPath)
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
List<Season> 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<Unit> DeleteEmptyShows(LibraryPath libraryPath) =>
_dbContext.Shows
public async Task<Unit> DeleteEmptyShows(LibraryPath libraryPath)
{
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
List<Show> 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<Either<BaseError, PlexShow>> GetOrAddPlexShow(PlexLibrary library, PlexShow item)
{
await using TvContext context = _dbContextFactory.CreateDbContext();
Option<PlexShow> maybeExisting = await context.PlexShows
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
Option<PlexShow> maybeExisting = await dbContext.PlexShows
.AsNoTracking()
.Include(i => i.ShowMetadata)
.ThenInclude(mm => mm.Genres)
@ -324,13 +340,13 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -324,13 +340,13 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
return await maybeExisting.Match(
plexShow => Right<BaseError, PlexShow>(plexShow).AsTask(),
async () => await AddPlexShow(context, library, item));
async () => await AddPlexShow(dbContext, library, item));
}
public async Task<Either<BaseError, PlexSeason>> GetOrAddPlexSeason(PlexLibrary library, PlexSeason item)
{
await using TvContext context = _dbContextFactory.CreateDbContext();
Option<PlexSeason> maybeExisting = await context.PlexSeasons
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
Option<PlexSeason> maybeExisting = await dbContext.PlexSeasons
.AsNoTracking()
.Include(i => i.SeasonMetadata)
.ThenInclude(mm => mm.Artwork)
@ -339,13 +355,13 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -339,13 +355,13 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
return await maybeExisting.Match(
plexSeason => Right<BaseError, PlexSeason>(plexSeason).AsTask(),
async () => await AddPlexSeason(context, library, item));
async () => await AddPlexSeason(dbContext, library, item));
}
public async Task<Either<BaseError, PlexEpisode>> GetOrAddPlexEpisode(PlexLibrary library, PlexEpisode item)
{
await using TvContext context = _dbContextFactory.CreateDbContext();
Option<PlexEpisode> maybeExisting = await context.PlexEpisodes
await using TvContext dbContext = _dbContextFactory.CreateDbContext();
Option<PlexEpisode> maybeExisting = await dbContext.PlexEpisodes
.AsNoTracking()
.Include(i => i.EpisodeMetadata)
.ThenInclude(mm => mm.Artwork)
@ -356,7 +372,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -356,7 +372,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
return await maybeExisting.Match(
plexEpisode => Right<BaseError, PlexEpisode>(plexEpisode).AsTask(),
async () => await AddPlexEpisode(context, library, item));
async () => await AddPlexEpisode(dbContext, library, item));
}
public Task<Unit> AddGenre(ShowMetadata metadata, Genre genre) =>
@ -372,7 +388,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -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<Unit> RemoveMissingPlexSeasons(string showKey, List<string> seasonKeys) =>
_dbConnection.ExecuteAsync(
@"DELETE FROM MediaItem WHERE Id IN
@ -382,7 +398,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -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<Unit> RemoveMissingPlexEpisodes(string seasonKey, List<string> episodeKeys) =>
_dbConnection.ExecuteAsync(
@"DELETE FROM MediaItem WHERE Id IN
@ -393,6 +409,15 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -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<Unit> 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<List<Episode>> GetShowItems(int showId)
{
IEnumerable<int> ids = await _dbConnection.QueryAsync<int>(
@ -402,7 +427,9 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -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 @@ -410,15 +437,23 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.ToListAsync();
}
public Task<List<Episode>> GetSeasonItems(int seasonId) =>
_dbContext.Episodes
public async Task<List<Episode>> 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<Either<BaseError, Season>> AddSeason(Show show, int libraryPathId, int seasonNumber)
private static async Task<Either<BaseError, Season>> AddSeason(
TvContext dbContext,
Show show,
int libraryPathId,
int seasonNumber)
{
try
{
@ -428,10 +463,13 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -428,10 +463,13 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
ShowId = show.Id,
SeasonNumber = seasonNumber,
Episodes = new List<Episode>(),
SeasonMetadata = new List<SeasonMetadata>()
SeasonMetadata = new List<SeasonMetadata>
{
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 @@ -440,7 +478,11 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
}
}
private async Task<Either<BaseError, Episode>> AddEpisode(Season season, int libraryPathId, string path)
private static async Task<Either<BaseError, Episode>> AddEpisode(
TvContext dbContext,
Season season,
int libraryPathId,
string path)
{
try
{
@ -448,7 +490,14 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -448,7 +490,14 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
{
LibraryPathId = libraryPathId,
SeasonId = season.Id,
EpisodeMetadata = new List<EpisodeMetadata>(),
EpisodeMetadata = new List<EpisodeMetadata>
{
new()
{
DateUpdated = DateTime.MinValue,
MetadataKind = MetadataKind.Fallback
}
},
MediaVersions = new List<MediaVersion>
{
new()
@ -460,8 +509,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -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 @@ -470,8 +519,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
}
}
private async Task<Either<BaseError, PlexShow>> AddPlexShow(
TvContext context,
private static async Task<Either<BaseError, PlexShow>> AddPlexShow(
TvContext dbContext,
PlexLibrary library,
PlexShow item)
{
@ -479,9 +528,9 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -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 @@ -490,8 +539,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
}
}
private async Task<Either<BaseError, PlexSeason>> AddPlexSeason(
TvContext context,
private static async Task<Either<BaseError, PlexSeason>> AddPlexSeason(
TvContext dbContext,
PlexLibrary library,
PlexSeason item)
{
@ -499,9 +548,9 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -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 @@ -510,8 +559,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
}
}
private async Task<Either<BaseError, PlexEpisode>> AddPlexEpisode(
TvContext context,
private static async Task<Either<BaseError, PlexEpisode>> AddPlexEpisode(
TvContext dbContext,
PlexLibrary library,
PlexEpisode item)
{
@ -519,9 +568,9 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -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)

Loading…
Cancel
Save