search overhaul (#87)
* add letter bar with no links * use lucene for search, add paged search results * add search index version * index library_name; rebuild index when folder is missing * maintain index as local movies change * fix tests * maintain index as local shows change * maintain index as plex movies change * maintain index as plex shows change * code cleanup * add duplicate filter to search * add links to letter bar * code cleanup
This commit is contained in:
@@ -11,5 +11,6 @@
|
||||
public static ConfigElementKey FFmpegDefaultProfileId => new("ffmpeg.default_profile_id");
|
||||
public static ConfigElementKey FFmpegDefaultResolutionId => new("ffmpeg.default_resolution_id");
|
||||
public static ConfigElementKey FFmpegSaveReports => new("ffmpeg.save_reports");
|
||||
public static ConfigElementKey SearchIndexVersion => new("search_index.version");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace ErsatzTV.Core
|
||||
public static readonly string PlexSecretsPath = Path.Combine(AppDataFolder, "plex-secrets.json");
|
||||
|
||||
public static readonly string FFmpegReportsFolder = Path.Combine(AppDataFolder, "ffmpeg-reports");
|
||||
public static readonly string SearchIndexFolder = Path.Combine(AppDataFolder, "search-index");
|
||||
|
||||
public static readonly string ArtworkCacheFolder = Path.Combine(AppDataFolder, "cache", "artwork");
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace ErsatzTV.Core.Interfaces.Metadata
|
||||
{
|
||||
public interface ILocalFileSystem
|
||||
{
|
||||
Unit EnsureFolderExists(string folder);
|
||||
DateTime GetLastWriteTime(string path);
|
||||
bool IsLibraryPathAccessible(LibraryPath libraryPath);
|
||||
IEnumerable<string> ListSubdirectories(string folder);
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using LanguageExt;
|
||||
|
||||
namespace ErsatzTV.Core.Interfaces.Metadata
|
||||
{
|
||||
public interface ILocalMetadataProvider
|
||||
{
|
||||
Task<ShowMetadata> GetMetadataForShow(string showFolder);
|
||||
Task<Unit> RefreshSidecarMetadata(MediaItem mediaItem, string path);
|
||||
Task<Unit> RefreshSidecarMetadata(Show televisionShow, string showFolder);
|
||||
Task<Unit> RefreshFallbackMetadata(MediaItem mediaItem);
|
||||
Task<Unit> RefreshFallbackMetadata(Show televisionShow, string showFolder);
|
||||
Task<bool> RefreshSidecarMetadata(MediaItem mediaItem, string path);
|
||||
Task<bool> RefreshSidecarMetadata(Show televisionShow, string showFolder);
|
||||
Task<bool> RefreshFallbackMetadata(MediaItem mediaItem);
|
||||
Task<bool> RefreshFallbackMetadata(Show televisionShow, string showFolder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,6 @@ namespace ErsatzTV.Core.Interfaces.Metadata
|
||||
{
|
||||
public interface ILocalStatisticsProvider
|
||||
{
|
||||
Task<Either<BaseError, Unit>> RefreshStatistics(string ffprobePath, MediaItem mediaItem);
|
||||
Task<Either<BaseError, bool>> RefreshStatistics(string ffprobePath, MediaItem mediaItem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace ErsatzTV.Core.Interfaces.Repositories
|
||||
Task<List<LibraryPath>> GetLocalPaths(int libraryId);
|
||||
Task<Option<LibraryPath>> GetPath(int libraryPathId);
|
||||
Task<int> CountMediaItemsByPath(int libraryPathId);
|
||||
Task<List<int>> GetMediaIdsByLocalPath(int libraryPathId);
|
||||
Task DeleteLocalPath(int libraryPathId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace ErsatzTV.Core.Interfaces.Repositories
|
||||
Task Update(PlexLibrary plexMediaSourceLibrary);
|
||||
Task Delete(int mediaSourceId);
|
||||
Task<Unit> DeleteAllPlex();
|
||||
Task DisablePlexLibrarySync(List<int> libraryIds);
|
||||
Task<List<int>> DisablePlexLibrarySync(List<int> libraryIds);
|
||||
Task EnablePlexLibrarySync(IEnumerable<int> libraryIds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ namespace ErsatzTV.Core.Interfaces.Repositories
|
||||
{
|
||||
public interface IMetadataRepository
|
||||
{
|
||||
Task<Unit> RemoveGenre(Genre genre);
|
||||
Task<bool> RemoveGenre(Genre genre);
|
||||
Task<bool> Update(Domain.Metadata metadata);
|
||||
Task<bool> Add(Domain.Metadata metadata);
|
||||
Task<bool> UpdateLocalStatistics(MediaVersion mediaVersion);
|
||||
Task<Unit> UpdatePlexStatistics(MediaVersion mediaVersion);
|
||||
Task<bool> 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,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Metadata;
|
||||
using LanguageExt;
|
||||
|
||||
namespace ErsatzTV.Core.Interfaces.Repositories
|
||||
@@ -9,13 +10,14 @@ namespace ErsatzTV.Core.Interfaces.Repositories
|
||||
{
|
||||
Task<bool> AllMoviesExist(List<int> movieIds);
|
||||
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<Either<BaseError, MediaItemScanResult<Movie>>> GetOrAdd(LibraryPath libraryPath, string path);
|
||||
Task<Either<BaseError, MediaItemScanResult<PlexMovie>>> GetOrAdd(PlexLibrary library, PlexMovie item);
|
||||
Task<int> GetMovieCount();
|
||||
Task<List<MovieMetadata>> GetPagedMovies(int pageNumber, int pageSize);
|
||||
Task<List<MovieMetadata>> GetMoviesForCards(List<int> ids);
|
||||
Task<IEnumerable<string>> FindMoviePaths(LibraryPath libraryPath);
|
||||
Task<Unit> DeleteByPath(LibraryPath libraryPath, string path);
|
||||
Task<Unit> AddGenre(MovieMetadata metadata, Genre genre);
|
||||
Task<Unit> RemoveMissingPlexMovies(PlexLibrary library, List<string> movieKeys);
|
||||
Task<List<int>> DeleteByPath(LibraryPath libraryPath, string path);
|
||||
Task<bool> AddGenre(MovieMetadata metadata, Genre genre);
|
||||
Task<List<int>> RemoveMissingPlexMovies(PlexLibrary library, List<string> movieKeys);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace ErsatzTV.Core.Interfaces.Repositories
|
||||
{
|
||||
public interface ISearchRepository
|
||||
{
|
||||
public Task<List<MediaItem>> GetItemsToIndex();
|
||||
public Task<List<MediaItem>> SearchMediaItemsByTitle(string query);
|
||||
public Task<List<MediaItem>> SearchMediaItemsByGenre(string genre);
|
||||
public Task<List<MediaItem>> SearchMediaItemsByTag(string tag);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Metadata;
|
||||
using LanguageExt;
|
||||
|
||||
namespace ErsatzTV.Core.Interfaces.Repositories
|
||||
@@ -12,6 +13,7 @@ namespace ErsatzTV.Core.Interfaces.Repositories
|
||||
Task<Option<Show>> GetShow(int showId);
|
||||
Task<int> GetShowCount();
|
||||
Task<List<ShowMetadata>> GetPagedShows(int pageNumber, int pageSize);
|
||||
Task<List<ShowMetadata>> GetShowsForCards(List<int> ids);
|
||||
Task<List<Episode>> GetShowItems(int showId);
|
||||
Task<List<Season>> GetAllSeasons();
|
||||
Task<Option<Season>> GetSeason(int seasonId);
|
||||
@@ -22,18 +24,23 @@ namespace ErsatzTV.Core.Interfaces.Repositories
|
||||
Task<int> GetEpisodeCount(int seasonId);
|
||||
Task<List<EpisodeMetadata>> GetPagedEpisodes(int seasonId, int pageNumber, int pageSize);
|
||||
Task<Option<Show>> GetShowByMetadata(int libraryPathId, ShowMetadata metadata);
|
||||
Task<Either<BaseError, Show>> AddShow(int libraryPathId, string showFolder, ShowMetadata metadata);
|
||||
|
||||
Task<Either<BaseError, MediaItemScanResult<Show>>> AddShow(
|
||||
int libraryPathId,
|
||||
string showFolder,
|
||||
ShowMetadata metadata);
|
||||
|
||||
Task<Either<BaseError, Season>> GetOrAddSeason(Show show, int libraryPathId, int seasonNumber);
|
||||
Task<Either<BaseError, Episode>> GetOrAddEpisode(Season season, LibraryPath libraryPath, string path);
|
||||
Task<IEnumerable<string>> FindEpisodePaths(LibraryPath libraryPath);
|
||||
Task<Unit> DeleteByPath(LibraryPath libraryPath, string path);
|
||||
Task<Unit> DeleteEmptySeasons(LibraryPath libraryPath);
|
||||
Task<Unit> DeleteEmptyShows(LibraryPath libraryPath);
|
||||
Task<Either<BaseError, PlexShow>> GetOrAddPlexShow(PlexLibrary library, PlexShow item);
|
||||
Task<List<int>> DeleteEmptyShows(LibraryPath libraryPath);
|
||||
Task<Either<BaseError, MediaItemScanResult<PlexShow>>> GetOrAddPlexShow(PlexLibrary library, PlexShow item);
|
||||
Task<Either<BaseError, PlexSeason>> GetOrAddPlexSeason(PlexLibrary library, PlexSeason item);
|
||||
Task<Either<BaseError, PlexEpisode>> GetOrAddPlexEpisode(PlexLibrary library, PlexEpisode item);
|
||||
Task<Unit> AddGenre(ShowMetadata metadata, Genre genre);
|
||||
Task<Unit> RemoveMissingPlexShows(PlexLibrary library, List<string> showKeys);
|
||||
Task<bool> AddGenre(ShowMetadata metadata, Genre genre);
|
||||
Task<List<int>> 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);
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Search;
|
||||
using LanguageExt;
|
||||
|
||||
namespace ErsatzTV.Core.Interfaces.Search
|
||||
{
|
||||
public interface ISearchIndex
|
||||
{
|
||||
public int Version { get; }
|
||||
Task<bool> Initialize();
|
||||
Task<Unit> Rebuild(List<MediaItem> items);
|
||||
Task<Unit> AddItems(List<MediaItem> items);
|
||||
Task<Unit> UpdateItems(List<MediaItem> items);
|
||||
Task<Unit> RemoveItems(List<int> ids);
|
||||
Task<SearchResult> Search(string query, int skip, int limit, string searchField = "");
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,16 @@ namespace ErsatzTV.Core.Metadata
|
||||
{
|
||||
public class LocalFileSystem : ILocalFileSystem
|
||||
{
|
||||
public Unit EnsureFolderExists(string folder)
|
||||
{
|
||||
if (!Directory.Exists(folder))
|
||||
{
|
||||
Directory.CreateDirectory(folder);
|
||||
}
|
||||
|
||||
return Unit.Default;
|
||||
}
|
||||
|
||||
public DateTime GetLastWriteTime(string path) =>
|
||||
Try(File.GetLastWriteTimeUtc(path)).IfFail(() => DateTime.MinValue);
|
||||
|
||||
|
||||
@@ -66,12 +66,14 @@ namespace ErsatzTV.Core.Metadata
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected async Task<Either<BaseError, T>> UpdateStatistics<T>(T mediaItem, string ffprobePath)
|
||||
protected async Task<Either<BaseError, MediaItemScanResult<T>>> UpdateStatistics<T>(
|
||||
MediaItemScanResult<T> mediaItem,
|
||||
string ffprobePath)
|
||||
where T : MediaItem
|
||||
{
|
||||
try
|
||||
{
|
||||
MediaVersion version = mediaItem switch
|
||||
MediaVersion version = mediaItem.Item switch
|
||||
{
|
||||
Movie m => m.MediaVersions.Head(),
|
||||
Episode e => e.MediaVersions.Head(),
|
||||
@@ -83,9 +85,16 @@ namespace ErsatzTV.Core.Metadata
|
||||
if (version.DateUpdated < _localFileSystem.GetLastWriteTime(path))
|
||||
{
|
||||
_logger.LogDebug("Refreshing {Attribute} for {Path}", "Statistics", path);
|
||||
Either<BaseError, Unit> refreshResult =
|
||||
await _localStatisticsProvider.RefreshStatistics(ffprobePath, mediaItem);
|
||||
refreshResult.IfLeft(
|
||||
Either<BaseError, bool> refreshResult =
|
||||
await _localStatisticsProvider.RefreshStatistics(ffprobePath, mediaItem.Item);
|
||||
refreshResult.Match(
|
||||
result =>
|
||||
{
|
||||
if (result)
|
||||
{
|
||||
mediaItem.IsUpdated = true;
|
||||
}
|
||||
},
|
||||
error =>
|
||||
_logger.LogWarning(
|
||||
"Unable to refresh {Attribute} for media item {Path}. Error: {Error}",
|
||||
|
||||
@@ -22,19 +22,16 @@ namespace ErsatzTV.Core.Metadata
|
||||
private readonly ILocalFileSystem _localFileSystem;
|
||||
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;
|
||||
@@ -65,40 +62,47 @@ namespace ErsatzTV.Core.Metadata
|
||||
});
|
||||
}
|
||||
|
||||
public Task<Unit> RefreshSidecarMetadata(MediaItem mediaItem, string path) =>
|
||||
public Task<bool> RefreshSidecarMetadata(MediaItem mediaItem, string path) =>
|
||||
mediaItem switch
|
||||
{
|
||||
Episode e => LoadMetadata(e, path)
|
||||
.Bind(maybeMetadata => maybeMetadata.IfSomeAsync(metadata => ApplyMetadataUpdate(e, metadata))),
|
||||
.Bind(
|
||||
maybeMetadata => maybeMetadata.Match(
|
||||
metadata => ApplyMetadataUpdate(e, metadata),
|
||||
() => Task.FromResult(false))),
|
||||
Movie m => LoadMetadata(m, path)
|
||||
.Bind(maybeMetadata => maybeMetadata.IfSomeAsync(metadata => ApplyMetadataUpdate(m, metadata))),
|
||||
_ => Task.FromResult(Unit.Default)
|
||||
.Bind(
|
||||
maybeMetadata => maybeMetadata.Match(
|
||||
metadata => ApplyMetadataUpdate(m, metadata),
|
||||
() => Task.FromResult(false))),
|
||||
_ => Task.FromResult(false)
|
||||
};
|
||||
|
||||
public Task<Unit> RefreshSidecarMetadata(Show televisionShow, string showFolder) =>
|
||||
public Task<bool> RefreshSidecarMetadata(Show televisionShow, string showFolder) =>
|
||||
LoadMetadata(televisionShow, showFolder).Bind(
|
||||
maybeMetadata => maybeMetadata.IfSomeAsync(metadata => ApplyMetadataUpdate(televisionShow, metadata)));
|
||||
maybeMetadata => maybeMetadata.Match(
|
||||
metadata => ApplyMetadataUpdate(televisionShow, metadata),
|
||||
() => Task.FromResult(false)));
|
||||
|
||||
public Task<Unit> RefreshFallbackMetadata(MediaItem mediaItem) =>
|
||||
public Task<bool> RefreshFallbackMetadata(MediaItem mediaItem) =>
|
||||
mediaItem switch
|
||||
{
|
||||
Episode e => ApplyMetadataUpdate(e, _fallbackMetadataProvider.GetFallbackMetadata(e))
|
||||
.ToUnit(),
|
||||
Movie m => ApplyMetadataUpdate(m, _fallbackMetadataProvider.GetFallbackMetadata(m)).ToUnit(),
|
||||
_ => Task.FromResult(Unit.Default)
|
||||
Episode e => ApplyMetadataUpdate(e, _fallbackMetadataProvider.GetFallbackMetadata(e)),
|
||||
Movie m => ApplyMetadataUpdate(m, _fallbackMetadataProvider.GetFallbackMetadata(m)),
|
||||
_ => Task.FromResult(false)
|
||||
};
|
||||
|
||||
public Task<Unit> RefreshFallbackMetadata(Show televisionShow, string showFolder) =>
|
||||
ApplyMetadataUpdate(televisionShow, _fallbackMetadataProvider.GetFallbackMetadataForShow(showFolder))
|
||||
.ToUnit();
|
||||
public Task<bool> RefreshFallbackMetadata(Show televisionShow, string showFolder) =>
|
||||
ApplyMetadataUpdate(televisionShow, _fallbackMetadataProvider.GetFallbackMetadataForShow(showFolder));
|
||||
|
||||
private async Task ApplyMetadataUpdate(Episode episode, Tuple<EpisodeMetadata, int> metadataEpisodeNumber)
|
||||
private async Task<bool> ApplyMetadataUpdate(Episode episode, Tuple<EpisodeMetadata, int> metadataEpisodeNumber)
|
||||
{
|
||||
(EpisodeMetadata metadata, int episodeNumber) = metadataEpisodeNumber;
|
||||
if (episode.EpisodeNumber != episodeNumber)
|
||||
{
|
||||
await _televisionRepository.SetEpisodeNumber(episode, episodeNumber);
|
||||
}
|
||||
|
||||
await Optional(episode.EpisodeMetadata).Flatten().HeadOrNone().Match(
|
||||
existing =>
|
||||
{
|
||||
@@ -128,9 +132,11 @@ namespace ErsatzTV.Core.Metadata
|
||||
|
||||
return _metadataRepository.Add(metadata);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Task ApplyMetadataUpdate(Movie movie, MovieMetadata metadata) =>
|
||||
private Task<bool> ApplyMetadataUpdate(Movie movie, MovieMetadata metadata) =>
|
||||
Optional(movie.MovieMetadata).Flatten().HeadOrNone().Match(
|
||||
existing =>
|
||||
{
|
||||
@@ -185,7 +191,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
return _metadataRepository.Add(metadata);
|
||||
});
|
||||
|
||||
private Task ApplyMetadataUpdate(Show show, ShowMetadata metadata) =>
|
||||
private Task<bool> ApplyMetadataUpdate(Show show, ShowMetadata metadata) =>
|
||||
Optional(show.ShowMetadata).Flatten().HeadOrNone().Match(
|
||||
existing =>
|
||||
{
|
||||
|
||||
@@ -17,22 +17,19 @@ 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;
|
||||
}
|
||||
|
||||
public async Task<Either<BaseError, Unit>> RefreshStatistics(string ffprobePath, MediaItem mediaItem)
|
||||
public async Task<Either<BaseError, bool>> RefreshStatistics(string ffprobePath, MediaItem mediaItem)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -48,10 +45,10 @@ namespace ErsatzTV.Core.Metadata
|
||||
async ffprobe =>
|
||||
{
|
||||
MediaVersion version = ProjectToMediaVersion(ffprobe);
|
||||
await ApplyVersionUpdate(mediaItem, version, filePath);
|
||||
return Right<BaseError, Unit>(Unit.Default);
|
||||
bool result = await ApplyVersionUpdate(mediaItem, version, filePath);
|
||||
return Right<BaseError, bool>(result);
|
||||
},
|
||||
error => Task.FromResult(Left<BaseError, Unit>(error)));
|
||||
error => Task.FromResult(Left<BaseError, bool>(error)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
|
||||
namespace ErsatzTV.Core.Metadata
|
||||
{
|
||||
public class MediaItemScanResult<T> where T : MediaItem
|
||||
{
|
||||
public MediaItemScanResult(T item) => Item = item;
|
||||
|
||||
public T Item { get; }
|
||||
|
||||
public bool IsAdded { get; set; }
|
||||
public bool IsUpdated { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ using ErsatzTV.Core.Errors;
|
||||
using ErsatzTV.Core.Interfaces.Images;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using LanguageExt;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static LanguageExt.Prelude;
|
||||
@@ -21,6 +22,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
private readonly ILocalMetadataProvider _localMetadataProvider;
|
||||
private readonly ILogger<MovieFolderScanner> _logger;
|
||||
private readonly IMovieRepository _movieRepository;
|
||||
private readonly ISearchIndex _searchIndex;
|
||||
|
||||
public MovieFolderScanner(
|
||||
ILocalFileSystem localFileSystem,
|
||||
@@ -29,12 +31,14 @@ namespace ErsatzTV.Core.Metadata
|
||||
ILocalMetadataProvider localMetadataProvider,
|
||||
IMetadataRepository metadataRepository,
|
||||
IImageCache imageCache,
|
||||
ISearchIndex searchIndex,
|
||||
ILogger<MovieFolderScanner> logger)
|
||||
: base(localFileSystem, localStatisticsProvider, metadataRepository, imageCache, logger)
|
||||
{
|
||||
_localFileSystem = localFileSystem;
|
||||
_movieRepository = movieRepository;
|
||||
_localMetadataProvider = localMetadataProvider;
|
||||
_searchIndex = searchIndex;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -72,19 +76,33 @@ namespace ErsatzTV.Core.Metadata
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
foreach (string file in allFiles.OrderBy(identity))
|
||||
{
|
||||
// TODO: figure out how to rebuild playlists
|
||||
Either<BaseError, Movie> maybeMovie = await _movieRepository
|
||||
Either<BaseError, MediaItemScanResult<Movie>> maybeMovie = await _movieRepository
|
||||
.GetOrAdd(libraryPath, file)
|
||||
.BindT(movie => UpdateStatistics(movie, ffprobePath).MapT(_ => movie))
|
||||
.BindT(movie => UpdateStatistics(movie, ffprobePath))
|
||||
.BindT(UpdateMetadata)
|
||||
.BindT(movie => UpdateArtwork(movie, ArtworkKind.Poster))
|
||||
.BindT(movie => UpdateArtwork(movie, ArtworkKind.FanArt));
|
||||
|
||||
maybeMovie.IfLeft(
|
||||
error => _logger.LogWarning("Error processing movie at {Path}: {Error}", file, error.Value));
|
||||
await maybeMovie.Match(
|
||||
async result =>
|
||||
{
|
||||
if (result.IsAdded)
|
||||
{
|
||||
await _searchIndex.AddItems(new List<MediaItem> { result.Item });
|
||||
}
|
||||
else if (result.IsUpdated)
|
||||
{
|
||||
await _searchIndex.UpdateItems(new List<MediaItem> { result.Item });
|
||||
}
|
||||
},
|
||||
error =>
|
||||
{
|
||||
_logger.LogWarning("Error processing movie at {Path}: {Error}", file, error.Value);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,17 +111,20 @@ namespace ErsatzTV.Core.Metadata
|
||||
if (!_localFileSystem.FileExists(path))
|
||||
{
|
||||
_logger.LogInformation("Removing missing movie at {Path}", path);
|
||||
await _movieRepository.DeleteByPath(libraryPath, path);
|
||||
List<int> ids = await _movieRepository.DeleteByPath(libraryPath, path);
|
||||
await _searchIndex.RemoveItems(ids);
|
||||
}
|
||||
}
|
||||
|
||||
return Unit.Default;
|
||||
}
|
||||
|
||||
private async Task<Either<BaseError, Movie>> UpdateMetadata(Movie movie)
|
||||
private async Task<Either<BaseError, MediaItemScanResult<Movie>>> UpdateMetadata(
|
||||
MediaItemScanResult<Movie> result)
|
||||
{
|
||||
try
|
||||
{
|
||||
Movie movie = result.Item;
|
||||
await LocateNfoFile(movie).Match(
|
||||
async nfoFile =>
|
||||
{
|
||||
@@ -115,7 +136,10 @@ namespace ErsatzTV.Core.Metadata
|
||||
if (shouldUpdate)
|
||||
{
|
||||
_logger.LogDebug("Refreshing {Attribute} from {Path}", "Sidecar Metadata", nfoFile);
|
||||
await _localMetadataProvider.RefreshSidecarMetadata(movie, nfoFile);
|
||||
if (await _localMetadataProvider.RefreshSidecarMetadata(movie, nfoFile))
|
||||
{
|
||||
result.IsUpdated = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
async () =>
|
||||
@@ -124,11 +148,14 @@ namespace ErsatzTV.Core.Metadata
|
||||
{
|
||||
string path = movie.MediaVersions.Head().MediaFiles.Head().Path;
|
||||
_logger.LogDebug("Refreshing {Attribute} for {Path}", "Fallback Metadata", path);
|
||||
await _localMetadataProvider.RefreshFallbackMetadata(movie);
|
||||
if (await _localMetadataProvider.RefreshFallbackMetadata(movie))
|
||||
{
|
||||
result.IsUpdated = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return movie;
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -136,10 +163,13 @@ namespace ErsatzTV.Core.Metadata
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Either<BaseError, Movie>> UpdateArtwork(Movie movie, ArtworkKind artworkKind)
|
||||
private async Task<Either<BaseError, MediaItemScanResult<Movie>>> UpdateArtwork(
|
||||
MediaItemScanResult<Movie> result,
|
||||
ArtworkKind artworkKind)
|
||||
{
|
||||
try
|
||||
{
|
||||
Movie movie = result.Item;
|
||||
await LocateArtwork(movie, artworkKind).IfSomeAsync(
|
||||
async posterFile =>
|
||||
{
|
||||
@@ -147,7 +177,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
await RefreshArtwork(posterFile, metadata, artworkKind);
|
||||
});
|
||||
|
||||
return movie;
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ using ErsatzTV.Core.Errors;
|
||||
using ErsatzTV.Core.Interfaces.Images;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using LanguageExt;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static LanguageExt.Prelude;
|
||||
@@ -19,6 +20,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
private readonly ILocalFileSystem _localFileSystem;
|
||||
private readonly ILocalMetadataProvider _localMetadataProvider;
|
||||
private readonly ILogger<TelevisionFolderScanner> _logger;
|
||||
private readonly ISearchIndex _searchIndex;
|
||||
private readonly ITelevisionRepository _televisionRepository;
|
||||
|
||||
public TelevisionFolderScanner(
|
||||
@@ -28,6 +30,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
ILocalMetadataProvider localMetadataProvider,
|
||||
IMetadataRepository metadataRepository,
|
||||
IImageCache imageCache,
|
||||
ISearchIndex searchIndex,
|
||||
ILogger<TelevisionFolderScanner> logger) : base(
|
||||
localFileSystem,
|
||||
localStatisticsProvider,
|
||||
@@ -38,6 +41,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
_localFileSystem = localFileSystem;
|
||||
_televisionRepository = televisionRepository;
|
||||
_localMetadataProvider = localMetadataProvider;
|
||||
_searchIndex = searchIndex;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -55,14 +59,26 @@ namespace ErsatzTV.Core.Metadata
|
||||
|
||||
foreach (string showFolder in allShowFolders)
|
||||
{
|
||||
Either<BaseError, Show> maybeShow =
|
||||
Either<BaseError, MediaItemScanResult<Show>> maybeShow =
|
||||
await FindOrCreateShow(libraryPath.Id, showFolder)
|
||||
.BindT(show => UpdateMetadataForShow(show, showFolder))
|
||||
.BindT(show => UpdateArtworkForShow(show, showFolder, ArtworkKind.Poster))
|
||||
.BindT(show => UpdateArtworkForShow(show, showFolder, ArtworkKind.FanArt));
|
||||
|
||||
await maybeShow.Match(
|
||||
show => ScanSeasons(libraryPath, ffprobePath, show, showFolder),
|
||||
async result =>
|
||||
{
|
||||
if (result.IsAdded)
|
||||
{
|
||||
await _searchIndex.AddItems(new List<MediaItem> { result.Item });
|
||||
}
|
||||
else if (result.IsUpdated)
|
||||
{
|
||||
await _searchIndex.UpdateItems(new List<MediaItem> { result.Item });
|
||||
}
|
||||
|
||||
await ScanSeasons(libraryPath, ffprobePath, result.Item, showFolder);
|
||||
},
|
||||
_ => Task.FromResult(Unit.Default));
|
||||
}
|
||||
|
||||
@@ -76,19 +92,20 @@ namespace ErsatzTV.Core.Metadata
|
||||
}
|
||||
|
||||
await _televisionRepository.DeleteEmptySeasons(libraryPath);
|
||||
await _televisionRepository.DeleteEmptyShows(libraryPath);
|
||||
List<int> ids = await _televisionRepository.DeleteEmptyShows(libraryPath);
|
||||
await _searchIndex.RemoveItems(ids);
|
||||
|
||||
return Unit.Default;
|
||||
}
|
||||
|
||||
private async Task<Either<BaseError, Show>> FindOrCreateShow(
|
||||
private async Task<Either<BaseError, MediaItemScanResult<Show>>> FindOrCreateShow(
|
||||
int libraryPathId,
|
||||
string showFolder)
|
||||
{
|
||||
ShowMetadata metadata = await _localMetadataProvider.GetMetadataForShow(showFolder);
|
||||
Option<Show> maybeShow = await _televisionRepository.GetShowByMetadata(libraryPathId, metadata);
|
||||
return await maybeShow.Match(
|
||||
show => Right<BaseError, Show>(show).AsTask(),
|
||||
show => Right<BaseError, MediaItemScanResult<Show>>(new MediaItemScanResult<Show>(show)).AsTask(),
|
||||
async () => await _televisionRepository.AddShow(libraryPathId, showFolder, metadata));
|
||||
}
|
||||
|
||||
@@ -130,7 +147,9 @@ namespace ErsatzTV.Core.Metadata
|
||||
// TODO: figure out how to rebuild playlists
|
||||
Either<BaseError, Episode> maybeEpisode = await _televisionRepository
|
||||
.GetOrAddEpisode(season, libraryPath, file)
|
||||
.BindT(episode => UpdateStatistics(episode, ffprobePath).MapT(_ => episode))
|
||||
.BindT(
|
||||
episode => UpdateStatistics(new MediaItemScanResult<Episode>(episode), ffprobePath)
|
||||
.MapT(_ => episode))
|
||||
.BindT(UpdateMetadata)
|
||||
.BindT(UpdateThumbnail);
|
||||
|
||||
@@ -141,12 +160,13 @@ namespace ErsatzTV.Core.Metadata
|
||||
return Unit.Default;
|
||||
}
|
||||
|
||||
private async Task<Either<BaseError, Show>> UpdateMetadataForShow(
|
||||
Show show,
|
||||
private async Task<Either<BaseError, MediaItemScanResult<Show>>> UpdateMetadataForShow(
|
||||
MediaItemScanResult<Show> result,
|
||||
string showFolder)
|
||||
{
|
||||
try
|
||||
{
|
||||
Show show = result.Item;
|
||||
await LocateNfoFileForShow(showFolder).Match(
|
||||
async nfoFile =>
|
||||
{
|
||||
@@ -158,7 +178,10 @@ namespace ErsatzTV.Core.Metadata
|
||||
if (shouldUpdate)
|
||||
{
|
||||
_logger.LogDebug("Refreshing {Attribute} from {Path}", "Sidecar Metadata", nfoFile);
|
||||
await _localMetadataProvider.RefreshSidecarMetadata(show, nfoFile);
|
||||
if (await _localMetadataProvider.RefreshSidecarMetadata(show, nfoFile))
|
||||
{
|
||||
result.IsUpdated = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
async () =>
|
||||
@@ -166,11 +189,14 @@ namespace ErsatzTV.Core.Metadata
|
||||
if (!Optional(show.ShowMetadata).Flatten().Any())
|
||||
{
|
||||
_logger.LogDebug("Refreshing {Attribute} for {Path}", "Fallback Metadata", showFolder);
|
||||
await _localMetadataProvider.RefreshFallbackMetadata(show, showFolder);
|
||||
if (await _localMetadataProvider.RefreshFallbackMetadata(show, showFolder))
|
||||
{
|
||||
result.IsUpdated = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return show;
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -215,13 +241,14 @@ namespace ErsatzTV.Core.Metadata
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Either<BaseError, Show>> UpdateArtworkForShow(
|
||||
Show show,
|
||||
private async Task<Either<BaseError, MediaItemScanResult<Show>>> UpdateArtworkForShow(
|
||||
MediaItemScanResult<Show> result,
|
||||
string showFolder,
|
||||
ArtworkKind artworkKind)
|
||||
{
|
||||
try
|
||||
{
|
||||
Show show = result.Item;
|
||||
await LocateArtworkForShow(showFolder, artworkKind).IfSomeAsync(
|
||||
async posterFile =>
|
||||
{
|
||||
@@ -229,7 +256,7 @@ namespace ErsatzTV.Core.Metadata
|
||||
await RefreshArtwork(posterFile, metadata, artworkKind);
|
||||
});
|
||||
|
||||
return show;
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -4,9 +4,10 @@ using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Plex;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using ErsatzTV.Core.Metadata;
|
||||
using LanguageExt;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static LanguageExt.Prelude;
|
||||
|
||||
namespace ErsatzTV.Core.Plex
|
||||
{
|
||||
@@ -16,17 +17,20 @@ namespace ErsatzTV.Core.Plex
|
||||
private readonly IMetadataRepository _metadataRepository;
|
||||
private readonly IMovieRepository _movieRepository;
|
||||
private readonly IPlexServerApiClient _plexServerApiClient;
|
||||
private readonly ISearchIndex _searchIndex;
|
||||
|
||||
public PlexMovieLibraryScanner(
|
||||
IPlexServerApiClient plexServerApiClient,
|
||||
IMovieRepository movieRepository,
|
||||
IMetadataRepository metadataRepository,
|
||||
ISearchIndex searchIndex,
|
||||
ILogger<PlexMovieLibraryScanner> logger)
|
||||
: base(metadataRepository)
|
||||
{
|
||||
_plexServerApiClient = plexServerApiClient;
|
||||
_movieRepository = movieRepository;
|
||||
_metadataRepository = metadataRepository;
|
||||
_searchIndex = searchIndex;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -46,21 +50,37 @@ namespace ErsatzTV.Core.Plex
|
||||
foreach (PlexMovie incoming in movieEntries)
|
||||
{
|
||||
// TODO: figure out how to rebuild playlists
|
||||
Either<BaseError, PlexMovie> maybeMovie = await _movieRepository
|
||||
Either<BaseError, MediaItemScanResult<PlexMovie>> maybeMovie = await _movieRepository
|
||||
.GetOrAdd(plexMediaSourceLibrary, incoming)
|
||||
.BindT(existing => UpdateStatistics(existing, incoming, connection, token))
|
||||
.BindT(existing => UpdateMetadata(existing, incoming))
|
||||
.BindT(existing => UpdateArtwork(existing, incoming));
|
||||
|
||||
maybeMovie.IfLeft(
|
||||
error => _logger.LogWarning(
|
||||
"Error processing plex movie at {Key}: {Error}",
|
||||
incoming.Key,
|
||||
error.Value));
|
||||
await maybeMovie.Match(
|
||||
async result =>
|
||||
{
|
||||
if (result.IsAdded)
|
||||
{
|
||||
await _searchIndex.AddItems(new List<MediaItem> { result.Item });
|
||||
}
|
||||
else if (result.IsUpdated)
|
||||
{
|
||||
await _searchIndex.UpdateItems(new List<MediaItem> { result.Item });
|
||||
}
|
||||
},
|
||||
error =>
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Error processing plex movie at {Key}: {Error}",
|
||||
incoming.Key,
|
||||
error.Value);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
var movieKeys = movieEntries.Map(s => s.Key).ToList();
|
||||
await _movieRepository.RemoveMissingPlexMovies(plexMediaSourceLibrary, movieKeys);
|
||||
List<int> ids = await _movieRepository.RemoveMissingPlexMovies(plexMediaSourceLibrary, movieKeys);
|
||||
await _searchIndex.RemoveItems(ids);
|
||||
},
|
||||
error =>
|
||||
{
|
||||
@@ -75,12 +95,13 @@ namespace ErsatzTV.Core.Plex
|
||||
return Unit.Default;
|
||||
}
|
||||
|
||||
private async Task<Either<BaseError, PlexMovie>> UpdateStatistics(
|
||||
PlexMovie existing,
|
||||
private async Task<Either<BaseError, MediaItemScanResult<PlexMovie>>> UpdateStatistics(
|
||||
MediaItemScanResult<PlexMovie> result,
|
||||
PlexMovie incoming,
|
||||
PlexConnection connection,
|
||||
PlexServerAuthToken token)
|
||||
{
|
||||
PlexMovie existing = result.Item;
|
||||
MediaVersion existingVersion = existing.MediaVersions.Head();
|
||||
MediaVersion incomingVersion = incoming.MediaVersions.Head();
|
||||
|
||||
@@ -102,11 +123,14 @@ namespace ErsatzTV.Core.Plex
|
||||
_ => Task.CompletedTask);
|
||||
}
|
||||
|
||||
return Right<BaseError, PlexMovie>(existing);
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<Either<BaseError, PlexMovie>> UpdateMetadata(PlexMovie existing, PlexMovie incoming)
|
||||
private async Task<Either<BaseError, MediaItemScanResult<PlexMovie>>> UpdateMetadata(
|
||||
MediaItemScanResult<PlexMovie> result,
|
||||
PlexMovie incoming)
|
||||
{
|
||||
PlexMovie existing = result.Item;
|
||||
MovieMetadata existingMetadata = existing.MovieMetadata.Head();
|
||||
MovieMetadata incomingMetadata = incoming.MovieMetadata.Head();
|
||||
|
||||
@@ -117,7 +141,10 @@ namespace ErsatzTV.Core.Plex
|
||||
.ToList())
|
||||
{
|
||||
existingMetadata.Genres.Remove(genre);
|
||||
await _metadataRepository.RemoveGenre(genre);
|
||||
if (await _metadataRepository.RemoveGenre(genre))
|
||||
{
|
||||
result.IsUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Genre genre in incomingMetadata.Genres
|
||||
@@ -125,17 +152,23 @@ namespace ErsatzTV.Core.Plex
|
||||
.ToList())
|
||||
{
|
||||
existingMetadata.Genres.Add(genre);
|
||||
await _movieRepository.AddGenre(existingMetadata, genre);
|
||||
if (await _movieRepository.AddGenre(existingMetadata, genre))
|
||||
{
|
||||
result.IsUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO: update other metadata?
|
||||
}
|
||||
|
||||
return existing;
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<Either<BaseError, PlexMovie>> UpdateArtwork(PlexMovie existing, PlexMovie incoming)
|
||||
private async Task<Either<BaseError, MediaItemScanResult<PlexMovie>>> UpdateArtwork(
|
||||
MediaItemScanResult<PlexMovie> result,
|
||||
PlexMovie incoming)
|
||||
{
|
||||
PlexMovie existing = result.Item;
|
||||
MovieMetadata existingMetadata = existing.MovieMetadata.Head();
|
||||
MovieMetadata incomingMetadata = incoming.MovieMetadata.Head();
|
||||
|
||||
@@ -145,7 +178,7 @@ namespace ErsatzTV.Core.Plex
|
||||
await UpdateArtworkIfNeeded(existingMetadata, incomingMetadata, ArtworkKind.FanArt);
|
||||
}
|
||||
|
||||
return existing;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ using System.Threading.Tasks;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Plex;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Search;
|
||||
using ErsatzTV.Core.Metadata;
|
||||
using LanguageExt;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static LanguageExt.Prelude;
|
||||
@@ -15,18 +17,21 @@ namespace ErsatzTV.Core.Plex
|
||||
private readonly ILogger<PlexTelevisionLibraryScanner> _logger;
|
||||
private readonly IMetadataRepository _metadataRepository;
|
||||
private readonly IPlexServerApiClient _plexServerApiClient;
|
||||
private readonly ISearchIndex _searchIndex;
|
||||
private readonly ITelevisionRepository _televisionRepository;
|
||||
|
||||
public PlexTelevisionLibraryScanner(
|
||||
IPlexServerApiClient plexServerApiClient,
|
||||
ITelevisionRepository televisionRepository,
|
||||
IMetadataRepository metadataRepository,
|
||||
ISearchIndex searchIndex,
|
||||
ILogger<PlexTelevisionLibraryScanner> logger)
|
||||
: base(metadataRepository)
|
||||
{
|
||||
_plexServerApiClient = plexServerApiClient;
|
||||
_televisionRepository = televisionRepository;
|
||||
_metadataRepository = metadataRepository;
|
||||
_searchIndex = searchIndex;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -46,13 +51,25 @@ namespace ErsatzTV.Core.Plex
|
||||
foreach (PlexShow incoming in showEntries)
|
||||
{
|
||||
// TODO: figure out how to rebuild playlists
|
||||
Either<BaseError, PlexShow> maybeShow = await _televisionRepository
|
||||
Either<BaseError, MediaItemScanResult<PlexShow>> maybeShow = await _televisionRepository
|
||||
.GetOrAddPlexShow(plexMediaSourceLibrary, incoming)
|
||||
.BindT(existing => UpdateMetadata(existing, incoming))
|
||||
.BindT(existing => UpdateArtwork(existing, incoming));
|
||||
|
||||
await maybeShow.Match(
|
||||
async show => await ScanSeasons(plexMediaSourceLibrary, show, connection, token),
|
||||
async result =>
|
||||
{
|
||||
if (result.IsAdded)
|
||||
{
|
||||
await _searchIndex.AddItems(new List<MediaItem> { result.Item });
|
||||
}
|
||||
else if (result.IsUpdated)
|
||||
{
|
||||
await _searchIndex.UpdateItems(new List<MediaItem> { result.Item });
|
||||
}
|
||||
|
||||
await ScanSeasons(plexMediaSourceLibrary, result.Item, connection, token);
|
||||
},
|
||||
error =>
|
||||
{
|
||||
_logger.LogWarning(
|
||||
@@ -64,7 +81,9 @@ namespace ErsatzTV.Core.Plex
|
||||
}
|
||||
|
||||
var showKeys = showEntries.Map(s => s.Key).ToList();
|
||||
await _televisionRepository.RemoveMissingPlexShows(plexMediaSourceLibrary, showKeys);
|
||||
List<int> ids =
|
||||
await _televisionRepository.RemoveMissingPlexShows(plexMediaSourceLibrary, showKeys);
|
||||
await _searchIndex.RemoveItems(ids);
|
||||
|
||||
return Unit.Default;
|
||||
},
|
||||
@@ -79,8 +98,11 @@ namespace ErsatzTV.Core.Plex
|
||||
});
|
||||
}
|
||||
|
||||
private Task<Either<BaseError, PlexShow>> UpdateMetadata(PlexShow existing, PlexShow incoming)
|
||||
private async Task<Either<BaseError, MediaItemScanResult<PlexShow>>> UpdateMetadata(
|
||||
MediaItemScanResult<PlexShow> result,
|
||||
PlexShow incoming)
|
||||
{
|
||||
PlexShow existing = result.Item;
|
||||
ShowMetadata existingMetadata = existing.ShowMetadata.Head();
|
||||
ShowMetadata incomingMetadata = incoming.ShowMetadata.Head();
|
||||
|
||||
@@ -93,7 +115,10 @@ namespace ErsatzTV.Core.Plex
|
||||
.ToList())
|
||||
{
|
||||
existingMetadata.Genres.Remove(genre);
|
||||
_metadataRepository.RemoveGenre(genre);
|
||||
if (await _metadataRepository.RemoveGenre(genre))
|
||||
{
|
||||
result.IsUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Genre genre in incomingMetadata.Genres
|
||||
@@ -101,15 +126,21 @@ namespace ErsatzTV.Core.Plex
|
||||
.ToList())
|
||||
{
|
||||
existingMetadata.Genres.Add(genre);
|
||||
_televisionRepository.AddGenre(existingMetadata, genre);
|
||||
if (await _televisionRepository.AddGenre(existingMetadata, genre))
|
||||
{
|
||||
result.IsUpdated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Right<BaseError, PlexShow>(existing).AsTask();
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<Either<BaseError, PlexShow>> UpdateArtwork(PlexShow existing, PlexShow incoming)
|
||||
private async Task<Either<BaseError, MediaItemScanResult<PlexShow>>> UpdateArtwork(
|
||||
MediaItemScanResult<PlexShow> result,
|
||||
PlexShow incoming)
|
||||
{
|
||||
PlexShow existing = result.Item;
|
||||
ShowMetadata existingMetadata = existing.ShowMetadata.Head();
|
||||
ShowMetadata incomingMetadata = incoming.ShowMetadata.Head();
|
||||
|
||||
@@ -119,7 +150,7 @@ namespace ErsatzTV.Core.Plex
|
||||
await UpdateArtworkIfNeeded(existingMetadata, incomingMetadata, ArtworkKind.FanArt);
|
||||
}
|
||||
|
||||
return existing;
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<Either<BaseError, Unit>> ScanSeasons(
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace ErsatzTV.Core.Search
|
||||
{
|
||||
public record SearchItem(int Id);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ErsatzTV.Core.Search
|
||||
{
|
||||
public record SearchPageMap(Dictionary<char, int> PageMap);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using LanguageExt;
|
||||
|
||||
namespace ErsatzTV.Core.Search
|
||||
{
|
||||
public record SearchResult(List<SearchItem> Items, int TotalCount)
|
||||
{
|
||||
public Option<SearchPageMap> PageMap { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user