Browse Source

fix unit tests

pull/31/head
Jason Dove 6 years ago
parent
commit
fd8d464809
  1. 12
      ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs
  2. 26
      ErsatzTV.Core.Tests/Metadata/MovieFolderScannerTests.cs
  3. 2
      ErsatzTV.Core/Interfaces/Images/IImageCache.cs
  4. 13
      ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs
  5. 24
      ErsatzTV.Core/Metadata/LocalFolderScanner.cs
  6. 49
      ErsatzTV.Core/Metadata/LocalMetadataProvider.cs
  7. 24
      ErsatzTV.Infrastructure/Images/ImageCache.cs

12
ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using ErsatzTV.Core.Domain;
using System;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Metadata;
using FluentAssertions;
using NUnit.Framework;
@ -37,14 +38,13 @@ namespace ErsatzTV.Core.Tests.Metadata @@ -37,14 +38,13 @@ namespace ErsatzTV.Core.Tests.Metadata
2)]
public void GetFallbackMetadata_ShouldHandleVariousFormats(string path, string title, int season, int episode)
{
EpisodeMetadata metadata = FallbackMetadataProvider.GetFallbackMetadata(
new Episode
{ Path = path, LibraryPath = new LibraryPath() });
(EpisodeMetadata metadata, int episodeNumber) = FallbackMetadataProvider.GetFallbackMetadata(
new Episode { Path = path, LibraryPath = new LibraryPath() });
metadata.Title.Should().Be(title);
// TODO: how can we test season number? do we need to?
// metadata.Season.Should().Be(season);
// metadata.Episode.Should().Be(episode);
// TODO: how do we test season and episode number?
episodeNumber.Should().Be(episode);
}
}
}

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

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
@ -42,12 +43,20 @@ namespace ErsatzTV.Core.Tests.Metadata @@ -42,12 +43,20 @@ namespace ErsatzTV.Core.Tests.Metadata
_movieRepository = new Mock<IMovieRepository>();
_movieRepository.Setup(x => x.GetOrAdd(It.IsAny<LibraryPath>(), It.IsAny<string>()))
.Returns(
(LibraryPath _, string path) =>
Right<BaseError, Movie>(new Movie { Path = path }).AsTask());
(LibraryPath _, string path) => Right<BaseError, Movie>(new Movie { Path = path }).AsTask());
_localStatisticsProvider = new Mock<ILocalStatisticsProvider>();
_localMetadataProvider = new Mock<ILocalMetadataProvider>();
// fallback metadata adds metadata to a movie, so we need to replicate that here
_localMetadataProvider.Setup(x => x.RefreshFallbackMetadata(It.IsAny<MediaItem>()))
.Returns(
(MediaItem mediaItem) =>
{
((Movie) mediaItem).MovieMetadata = new List<MovieMetadata> { new() };
return Unit.Default.AsTask();
});
_imageCache = new Mock<IImageCache>();
_imageCache.Setup(
x => x.ResizeAndSaveImage(FakeLocalFileSystem.TestBytes, It.IsAny<int?>(), It.IsAny<int?>()))
@ -186,7 +195,7 @@ namespace ErsatzTV.Core.Tests.Metadata @@ -186,7 +195,7 @@ namespace ErsatzTV.Core.Tests.Metadata
MovieFolderScanner service = GetService(
new FakeFileEntry(moviePath),
new FakeFileEntry(posterPath)
new FakeFileEntry(posterPath) { LastWriteTime = DateTime.Now }
);
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
@ -206,7 +215,7 @@ namespace ErsatzTV.Core.Tests.Metadata @@ -206,7 +215,7 @@ namespace ErsatzTV.Core.Tests.Metadata
Times.Once);
_imageCache.Verify(
x => x.ResizeAndSaveImage(FakeLocalFileSystem.TestBytes, It.IsAny<int?>(), It.IsAny<int?>()),
x => x.CopyArtworkToCache(posterPath, ArtworkKind.Poster),
Times.Once);
}
@ -227,7 +236,7 @@ namespace ErsatzTV.Core.Tests.Metadata @@ -227,7 +236,7 @@ namespace ErsatzTV.Core.Tests.Metadata
MovieFolderScanner service = GetService(
new FakeFileEntry(moviePath),
new FakeFileEntry(posterPath)
new FakeFileEntry(posterPath) { LastWriteTime = DateTime.Now }
);
var libraryPath = new LibraryPath { Id = 1, Path = FakeRoot };
@ -247,7 +256,7 @@ namespace ErsatzTV.Core.Tests.Metadata @@ -247,7 +256,7 @@ namespace ErsatzTV.Core.Tests.Metadata
Times.Once);
_imageCache.Verify(
x => x.ResizeAndSaveImage(FakeLocalFileSystem.TestBytes, It.IsAny<int?>(), It.IsAny<int?>()),
x => x.CopyArtworkToCache(posterPath, ArtworkKind.Poster),
Times.Once);
}
@ -354,9 +363,6 @@ namespace ErsatzTV.Core.Tests.Metadata @@ -354,9 +363,6 @@ namespace ErsatzTV.Core.Tests.Metadata
}
private MovieFolderScanner GetService(params FakeFileEntry[] files) =>
// var mockImageCache = new Mock<IImageCache>();
// mockImageCache.Setup(i => i.ResizeAndSaveImage(It.IsAny<byte[]>(), It.IsAny<int?>(), It.IsAny<int?>()))
// .Returns(Right<BaseError, string>("image").AsTask());
new(
new FakeLocalFileSystem(new List<FakeFileEntry>(files)),
_movieRepository.Object,

2
ErsatzTV.Core/Interfaces/Images/IImageCache.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
using LanguageExt;
namespace ErsatzTV.Core.Interfaces.Images
@ -8,5 +9,6 @@ namespace ErsatzTV.Core.Interfaces.Images @@ -8,5 +9,6 @@ namespace ErsatzTV.Core.Interfaces.Images
Task<Either<BaseError, byte[]>> ResizeImage(byte[] imageBuffer, int height);
Task<Either<BaseError, string>> ResizeAndSaveImage(byte[] imageBuffer, int? height, int? width);
Task<Either<BaseError, string>> SaveImage(byte[] imageBuffer);
string CopyArtworkToCache(string path, ArtworkKind artworkKind);
}
}

13
ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs

@ -3,6 +3,7 @@ using System.IO; @@ -3,6 +3,7 @@ using System.IO;
using System.Text.RegularExpressions;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Metadata;
using static LanguageExt.Prelude;
namespace ErsatzTV.Core.Metadata
{
@ -16,12 +17,12 @@ namespace ErsatzTV.Core.Metadata @@ -16,12 +17,12 @@ namespace ErsatzTV.Core.Metadata
return GetTelevisionShowMetadata(fileName, metadata);
}
public static EpisodeMetadata GetFallbackMetadata(Episode episode)
public static Tuple<EpisodeMetadata, int> GetFallbackMetadata(Episode episode)
{
string fileName = Path.GetFileName(episode.Path);
var metadata = new EpisodeMetadata
{ MetadataKind = MetadataKind.Fallback, Title = fileName ?? episode.Path };
return fileName != null ? GetEpisodeMetadata(fileName, metadata) : metadata;
return fileName != null ? GetEpisodeMetadata(fileName, metadata) : Tuple(metadata, 0);
}
public static MovieMetadata GetFallbackMetadata(Movie movie)
@ -32,7 +33,7 @@ namespace ErsatzTV.Core.Metadata @@ -32,7 +33,7 @@ namespace ErsatzTV.Core.Metadata
return fileName != null ? GetMovieMetadata(fileName, metadata) : metadata;
}
private static EpisodeMetadata GetEpisodeMetadata(string fileName, EpisodeMetadata metadata)
private static Tuple<EpisodeMetadata, int> GetEpisodeMetadata(string fileName, EpisodeMetadata metadata)
{
try
{
@ -41,9 +42,7 @@ namespace ErsatzTV.Core.Metadata @@ -41,9 +42,7 @@ namespace ErsatzTV.Core.Metadata
if (match.Success)
{
metadata.Title = match.Groups[1].Value;
// TODO: set episode number?
// metadata.Season = int.Parse(match.Groups[2].Value);
// metadata.Episode = int.Parse(match.Groups[3].Value);
return Tuple(metadata, int.Parse(match.Groups[3].Value));
}
}
catch (Exception)
@ -51,7 +50,7 @@ namespace ErsatzTV.Core.Metadata @@ -51,7 +50,7 @@ namespace ErsatzTV.Core.Metadata
// ignored
}
return metadata;
return Tuple(metadata, 0);
}
private static MovieMetadata GetMovieMetadata(string fileName, MovieMetadata metadata)

24
ErsatzTV.Core/Metadata/LocalFolderScanner.cs

@ -3,13 +3,13 @@ using System.Collections.Generic; @@ -3,13 +3,13 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Images;
using ErsatzTV.Core.Interfaces.Metadata;
using LanguageExt;
using Microsoft.Extensions.Logging;
using static LanguageExt.Prelude;
namespace ErsatzTV.Core.Metadata
{
@ -91,7 +91,7 @@ namespace ErsatzTV.Core.Metadata @@ -91,7 +91,7 @@ namespace ErsatzTV.Core.Metadata
metadata.Artwork ??= new List<Artwork>();
Option<Artwork> maybePoster =
metadata.Artwork.FirstOrDefault(a => a.ArtworkKind == artworkKind);
Optional(metadata.Artwork).Flatten().FirstOrDefault(a => a.ArtworkKind == artworkKind);
bool shouldRefresh = maybePoster.Match(
artwork => artwork.DateUpdated < lastWriteTime,
@ -100,7 +100,7 @@ namespace ErsatzTV.Core.Metadata @@ -100,7 +100,7 @@ namespace ErsatzTV.Core.Metadata
if (shouldRefresh)
{
_logger.LogDebug("Refreshing {Attribute} from {Path}", artworkKind, artworkFile);
string cacheName = CopyArtworkToCache(artworkFile, artworkKind);
string cacheName = _imageCache.CopyArtworkToCache(artworkFile, artworkKind);
maybePoster.Match(
artwork =>
@ -126,23 +126,5 @@ namespace ErsatzTV.Core.Metadata @@ -126,23 +126,5 @@ namespace ErsatzTV.Core.Metadata
return false;
}
private string CopyArtworkToCache(string path, ArtworkKind artworkKind)
{
var filenameKey = $"{path}:{_localFileSystem.GetLastWriteTime(path).ToFileTimeUtc()}";
byte[] hash = Crypto.ComputeHash(Encoding.UTF8.GetBytes(filenameKey));
string hex = BitConverter.ToString(hash).Replace("-", string.Empty);
string subfolder = hex.Substring(0, 2);
string baseFolder = artworkKind switch
{
ArtworkKind.Poster => Path.Combine(FileSystemLayout.PosterCacheFolder, subfolder),
ArtworkKind.Thumbnail => Path.Combine(FileSystemLayout.ThumbnailCacheFolder, subfolder),
_ => FileSystemLayout.ImageCacheFolder
};
string target = Path.Combine(baseFolder, hex);
_localFileSystem.CopyFile(path, target);
return hex;
}
}
}

49
ErsatzTV.Core/Metadata/LocalMetadataProvider.cs

@ -83,8 +83,10 @@ namespace ErsatzTV.Core.Metadata @@ -83,8 +83,10 @@ namespace ErsatzTV.Core.Metadata
ApplyMetadataUpdate(televisionShow, _fallbackMetadataProvider.GetFallbackMetadataForShow(showFolder))
.ToUnit();
private async Task ApplyMetadataUpdate(Episode episode, EpisodeMetadata 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(
existing =>
{
@ -169,16 +171,10 @@ namespace ErsatzTV.Core.Metadata @@ -169,16 +171,10 @@ namespace ErsatzTV.Core.Metadata
return None;
}
// if (!(mediaItem.Source is LocalMediaSource))
// {
// _logger.LogDebug("Media source {Name} is not a local media source", mediaItem.Source.Name);
// return None;
// }
return await LoadMovieMetadata(mediaItem, nfoFileName);
}
private async Task<Option<EpisodeMetadata>> LoadMetadata(Episode mediaItem, string nfoFileName)
private async Task<Option<Tuple<EpisodeMetadata, int>>> LoadMetadata(Episode mediaItem, string nfoFileName)
{
if (nfoFileName == null || !File.Exists(nfoFileName))
{
@ -186,18 +182,10 @@ namespace ErsatzTV.Core.Metadata @@ -186,18 +182,10 @@ namespace ErsatzTV.Core.Metadata
return None;
}
// if (!(mediaItem.Source is LocalMediaSource))
// {
// _logger.LogDebug("Media source {Name} is not a local media source", mediaItem.Source.Name);
// return None;
// }
return await LoadEpisodeMetadata(mediaItem, nfoFileName);
}
private async Task<Option<ShowMetadata>> LoadMetadata(
Show televisionShow,
string nfoFileName)
private async Task<Option<ShowMetadata>> LoadMetadata(Show televisionShow, string nfoFileName)
{
if (nfoFileName == null || !File.Exists(nfoFileName))
{
@ -235,32 +223,31 @@ namespace ErsatzTV.Core.Metadata @@ -235,32 +223,31 @@ namespace ErsatzTV.Core.Metadata
}
}
private async Task<Option<EpisodeMetadata>> LoadEpisodeMetadata(
Episode mediaItem,
string nfoFileName)
private async Task<Option<Tuple<EpisodeMetadata, int>>> LoadEpisodeMetadata(Episode episode, string nfoFileName)
{
try
{
await using FileStream fileStream = File.Open(nfoFileName, FileMode.Open, FileAccess.Read);
Option<TvShowEpisodeNfo> maybeNfo = EpisodeSerializer.Deserialize(fileStream) as TvShowEpisodeNfo;
return maybeNfo.Match<Option<EpisodeMetadata>>(
nfo => new EpisodeMetadata
return maybeNfo.Match<Option<Tuple<EpisodeMetadata, int>>>(
nfo =>
{
MetadataKind = MetadataKind.Sidecar,
DateUpdated = File.GetLastWriteTimeUtc(nfoFileName),
Title = nfo.Title,
ReleaseDate = GetAired(nfo.Aired),
// Episode = nfo.Episode,
// Season = nfo.Season,
// TODO: save episode number somewhere?
Plot = nfo.Plot
var metadata = new EpisodeMetadata
{
MetadataKind = MetadataKind.Sidecar,
DateUpdated = File.GetLastWriteTimeUtc(nfoFileName),
Title = nfo.Title,
ReleaseDate = GetAired(nfo.Aired),
Plot = nfo.Plot
};
return Tuple(metadata, nfo.Episode);
},
None);
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Failed to read TV episode nfo metadata from {Path}", nfoFileName);
return FallbackMetadataProvider.GetFallbackMetadata(mediaItem);
return FallbackMetadataProvider.GetFallbackMetadata(episode);
}
}

24
ErsatzTV.Infrastructure/Images/ImageCache.cs

@ -1,9 +1,12 @@ @@ -1,9 +1,12 @@
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Images;
using ErsatzTV.Core.Interfaces.Metadata;
using LanguageExt;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
@ -14,9 +17,12 @@ namespace ErsatzTV.Infrastructure.Images @@ -14,9 +17,12 @@ namespace ErsatzTV.Infrastructure.Images
public class ImageCache : IImageCache
{
private static readonly SHA1CryptoServiceProvider Crypto;
private readonly ILocalFileSystem _localFileSystem;
static ImageCache() => Crypto = new SHA1CryptoServiceProvider();
public ImageCache(ILocalFileSystem localFileSystem) => _localFileSystem = localFileSystem;
public async Task<Either<BaseError, byte[]>> ResizeImage(byte[] imageBuffer, int height)
{
await using var inStream = new MemoryStream(imageBuffer);
@ -81,5 +87,23 @@ namespace ErsatzTV.Infrastructure.Images @@ -81,5 +87,23 @@ namespace ErsatzTV.Infrastructure.Images
return BaseError.New(ex.Message);
}
}
public string CopyArtworkToCache(string path, ArtworkKind artworkKind)
{
var filenameKey = $"{path}:{_localFileSystem.GetLastWriteTime(path).ToFileTimeUtc()}";
byte[] hash = Crypto.ComputeHash(Encoding.UTF8.GetBytes(filenameKey));
string hex = BitConverter.ToString(hash).Replace("-", string.Empty);
string subfolder = hex.Substring(0, 2);
string baseFolder = artworkKind switch
{
ArtworkKind.Poster => Path.Combine(FileSystemLayout.PosterCacheFolder, subfolder),
ArtworkKind.Thumbnail => Path.Combine(FileSystemLayout.ThumbnailCacheFolder, subfolder),
_ => FileSystemLayout.ImageCacheFolder
};
string target = Path.Combine(baseFolder, hex);
_localFileSystem.CopyFile(path, target);
return hex;
}
}
}

Loading…
Cancel
Save