Compare commits

...
12 changed files with 114 additions and 14 deletions
+2 -1
View File
@@ -17,7 +17,8 @@ namespace ErsatzTV.Application.Television
show.ShowMetadata.HeadOrNone().Map(GetFanArt).IfNone(string.Empty),
show.ShowMetadata.HeadOrNone().Map(m => m.Genres.Map(g => g.Name).ToList()).IfNone(new List<string>()),
show.ShowMetadata.HeadOrNone().Map(m => m.Tags.Map(g => g.Name).ToList()).IfNone(new List<string>()),
show.ShowMetadata.HeadOrNone().Map(m => m.Studios.Map(s => s.Name).ToList()).IfNone(new List<string>()));
show.ShowMetadata.HeadOrNone().Map(m => m.Studios.Map(s => s.Name).ToList())
.IfNone(new List<string>()));
internal static TelevisionSeasonViewModel ProjectToViewModel(Season season) =>
new(
@@ -238,6 +238,50 @@ namespace ErsatzTV.Core.Tests.Metadata
Times.Once);
}
[Test]
public async Task NewMovie_Statistics_And_FallbackMetadata_And_FolderPoster(
[ValueSource(typeof(LocalFolderScanner), nameof(LocalFolderScanner.VideoFileExtensions))]
string videoExtension,
[ValueSource(typeof(LocalFolderScanner), nameof(LocalFolderScanner.ImageFileExtensions))]
string imageExtension)
{
string moviePath = Path.Combine(
FakeRoot,
Path.Combine("Movie (2020)", $"Movie (2020){videoExtension}"));
string posterPath = Path.Combine(
Path.GetDirectoryName(moviePath) ?? string.Empty,
$"folder.{imageExtension}");
MovieFolderScanner service = GetService(
new FakeFileEntry(moviePath) { LastWriteTime = DateTime.Now },
new FakeFileEntry(posterPath) { LastWriteTime = DateTime.Now }
);
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
Either<BaseError, Unit> result = await service.ScanFolder(libraryPath, FFprobePath);
result.IsRight.Should().BeTrue();
_movieRepository.Verify(x => x.GetOrAdd(It.IsAny<LibraryPath>(), It.IsAny<string>()), Times.Once);
_movieRepository.Verify(x => x.GetOrAdd(libraryPath, moviePath), Times.Once);
_localStatisticsProvider.Verify(
x => x.RefreshStatistics(
FFprobePath,
It.Is<Movie>(i => i.MediaVersions.Head().MediaFiles.Head().Path == moviePath)),
Times.Once);
_localMetadataProvider.Verify(
x => x.RefreshFallbackMetadata(
It.Is<Movie>(i => i.MediaVersions.Head().MediaFiles.Head().Path == moviePath)),
Times.Once);
_imageCache.Verify(
x => x.CopyArtworkToCache(posterPath, ArtworkKind.Poster),
Times.Once);
}
[Test]
public async Task NewMovie_Statistics_And_FallbackMetadata_And_MovieNamePoster(
[ValueSource(typeof(LocalFolderScanner), nameof(LocalFolderScanner.VideoFileExtensions))]
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
using LanguageExt;
@@ -16,5 +17,8 @@ namespace ErsatzTV.Core.Interfaces.Repositories
Task<Unit> UpdateArtworkPath(Artwork artwork);
Task<Unit> AddArtwork(Domain.Metadata metadata, Artwork artwork);
Task<Unit> RemoveArtwork(Domain.Metadata metadata, ArtworkKind artworkKind);
Task<Unit> MarkAsUpdated(ShowMetadata metadata, DateTime dateUpdated);
Task<Unit> MarkAsUpdated(SeasonMetadata metadata, DateTime dateUpdated);
Task<Unit> MarkAsUpdated(MovieMetadata metadata, DateTime dateUpdated);
}
}
@@ -210,6 +210,14 @@ namespace ErsatzTV.Core.Metadata
ext => new[] { $"{segment}.{ext}", Path.GetFileNameWithoutExtension(path) + $"-{segment}.{ext}" })
.Map(f => Path.Combine(folder, f));
Option<string> result = possibleMoviePosters.Filter(p => _localFileSystem.FileExists(p)).HeadOrNone();
if (result.IsNone && artworkKind == ArtworkKind.Poster)
{
IEnumerable<string> possibleFolderPosters = ImageFileExtensions.Collect(
ext => new[] { $"folder.{ext}" })
.Map(f => Path.Combine(folder, f));
result = possibleFolderPosters.Filter(p => _localFileSystem.FileExists(p)).HeadOrNone();
}
return result;
}
}
@@ -319,15 +319,16 @@ namespace ErsatzTV.Core.Metadata
private Option<string> LocateArtworkForShow(string showFolder, ArtworkKind artworkKind)
{
string segment = artworkKind switch
string[] segments = artworkKind switch
{
ArtworkKind.Poster => "poster",
ArtworkKind.FanArt => "fanart",
ArtworkKind.Poster => new[] { "poster", "folder" },
ArtworkKind.FanArt => new[] { "fanart" },
_ => throw new ArgumentOutOfRangeException(nameof(artworkKind))
};
return ImageFileExtensions
.Map(ext => $"{segment}.{ext}")
.Map(ext => segments.Map(segment => $"{segment}.{ext}"))
.Flatten()
.Map(f => Path.Combine(showFolder, f))
.Filter(s => _localFileSystem.FileExists(s))
.HeadOrNone();
@@ -189,6 +189,8 @@ namespace ErsatzTV.Core.Plex
}
}
await _metadataRepository.MarkAsUpdated(existingMetadata, incomingMetadata.DateUpdated);
// TODO: update other metadata?
}
@@ -207,6 +209,7 @@ namespace ErsatzTV.Core.Plex
{
await UpdateArtworkIfNeeded(existingMetadata, incomingMetadata, ArtworkKind.Poster);
await UpdateArtworkIfNeeded(existingMetadata, incomingMetadata, ArtworkKind.FanArt);
await _metadataRepository.MarkAsUpdated(existingMetadata, incomingMetadata.DateUpdated);
}
return result;
@@ -131,7 +131,7 @@ namespace ErsatzTV.Core.Plex
result.IsUpdated = true;
}
}
foreach (Studio studio in existingMetadata.Studios
.Filter(s => incomingMetadata.Studios.All(s2 => s2.Name != s.Name))
.ToList())
@@ -153,6 +153,8 @@ namespace ErsatzTV.Core.Plex
result.IsUpdated = true;
}
}
await _metadataRepository.MarkAsUpdated(existingMetadata, incomingMetadata.DateUpdated);
}
return result;
@@ -170,6 +172,7 @@ namespace ErsatzTV.Core.Plex
{
await UpdateArtworkIfNeeded(existingMetadata, incomingMetadata, ArtworkKind.Poster);
await UpdateArtworkIfNeeded(existingMetadata, incomingMetadata, ArtworkKind.FanArt);
await _metadataRepository.MarkAsUpdated(existingMetadata, incomingMetadata.DateUpdated);
}
return result;
@@ -235,6 +238,7 @@ namespace ErsatzTV.Core.Plex
if (incomingMetadata.DateUpdated > existingMetadata.DateUpdated)
{
await UpdateArtworkIfNeeded(existingMetadata, incomingMetadata, ArtworkKind.Poster);
await _metadataRepository.MarkAsUpdated(existingMetadata, incomingMetadata.DateUpdated);
}
return existing;
@@ -196,6 +196,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
{
add.MediaSourceId = plexMediaSourceId;
dbContext.Entry(add).State = EntityState.Added;
foreach (LibraryPath path in add.Paths)
{
dbContext.Entry(path).State = EntityState.Added;
}
}
foreach (PlexLibrary delete in toDelete)
@@ -1,4 +1,5 @@
using System.Data;
using System;
using System.Data;
using System.Threading.Tasks;
using Dapper;
using ErsatzTV.Core.Domain;
@@ -109,6 +110,21 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
OR ShowMetadataId = @Id OR SeasonMetadataId = @Id OR EpisodeMetadataId = @Id)",
new { ArtworkKind = artworkKind, metadata.Id }).ToUnit();
public Task<Unit> MarkAsUpdated(ShowMetadata metadata, DateTime dateUpdated) =>
_dbConnection.ExecuteAsync(
@"UPDATE ShowMetadata SET DateUpdated = @DateUpdated WHERE Id = @Id",
new { DateUpdated = dateUpdated, metadata.Id }).ToUnit();
public Task<Unit> MarkAsUpdated(SeasonMetadata metadata, DateTime dateUpdated) =>
_dbConnection.ExecuteAsync(
@"UPDATE SeasonMetadata SET DateUpdated = @DateUpdated WHERE Id = @Id",
new { DateUpdated = dateUpdated, metadata.Id }).ToUnit();
public Task<Unit> MarkAsUpdated(MovieMetadata metadata, DateTime dateUpdated) =>
_dbConnection.ExecuteAsync(
@"UPDATE MovieMetadata SET DateUpdated = @DateUpdated WHERE Id = @Id",
new { DateUpdated = dateUpdated, metadata.Id }).ToUnit();
public Task<bool> RemoveGenre(Genre genre) =>
_dbConnection.ExecuteAsync("DELETE FROM Genre WHERE Id = @GenreId", new { GenreId = genre.Id })
.Map(result => result > 0);
@@ -256,7 +256,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
}
catch (Exception ex)
{
return BaseError.New(ex.Message);
return BaseError.New(ex.ToString());
}
}
}
@@ -285,9 +285,24 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.OrderBy(i => i.MediaVersions.First().MediaFiles.First().Path)
.SingleOrDefaultAsync(i => i.MediaVersions.First().MediaFiles.First().Path == path);
return await maybeExisting.Match(
episode => Right<BaseError, Episode>(episode).AsTask(),
() => AddEpisode(dbContext, season, libraryPath.Id, path));
return await maybeExisting.Match<Task<Either<BaseError, Episode>>>(
async episode =>
{
// move the file to the new season if needed
// this can happen when adding NFO metadata to existing content
if (episode.SeasonId != season.Id)
{
episode.SeasonId = season.Id;
episode.Season = season;
await _dbConnection.ExecuteAsync(
@"UPDATE Episode SET SeasonId = @SeasonId WHERE Id = @EpisodeId",
new { SeasonId = season.Id, EpisodeId = episode.Id });
}
return episode;
},
async () => await AddEpisode(dbContext, season, libraryPath.Id, path));
}
public Task<IEnumerable<string>> FindEpisodePaths(LibraryPath libraryPath) =>
@@ -545,7 +560,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
{
return BaseError.New("Multi-episode files are not yet supported");
}
var episode = new Episode
{
LibraryPathId = libraryPathId,
@@ -49,7 +49,7 @@ namespace ErsatzTV.Infrastructure.Search
private readonly ILogger<SearchIndex> _logger;
private readonly ISearchRepository _searchRepository;
public SearchIndex(
ILocalFileSystem localFileSystem,
ISearchRepository searchRepository,