optimize local scanning (#84)
* optimize local scanning * fix artwork updates * fix adding genres and tags * fix movie fallback metadata
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Genre>();
|
||||
metadata.Tags = new List<Tag>();
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
|
||||
@@ -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<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
|
||||
_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;
|
||||
|
||||
@@ -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
|
||||
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
|
||||
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
|
||||
{
|
||||
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 };
|
||||
|
||||
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<ShowMetadata> { metadata };
|
||||
});
|
||||
|
||||
await _televisionRepository.Update(show);
|
||||
}
|
||||
return _metadataRepository.Add(metadata);
|
||||
});
|
||||
|
||||
private async Task<Option<MovieMetadata>> LoadMetadata(Movie mediaItem, string nfoFileName)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
|
||||
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
|
||||
async posterFile =>
|
||||
{
|
||||
MovieMetadata metadata = movie.MovieMetadata.Head();
|
||||
if (RefreshArtwork(posterFile, metadata, artworkKind))
|
||||
{
|
||||
await _movieRepository.Update(movie);
|
||||
}
|
||||
await RefreshArtwork(posterFile, metadata, artworkKind);
|
||||
});
|
||||
|
||||
return movie;
|
||||
|
||||
@@ -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
|
||||
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<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
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user