cache artwork async (#117)

This commit is contained in:
Jason Dove
2021-03-30 11:09:47 +00:00
committed by GitHub
parent 745b03af73
commit 9ea4459988
7 changed files with 113 additions and 53 deletions
@@ -56,8 +56,8 @@ namespace ErsatzTV.Core.Tests.Fakes
public Task<byte[]> ReadAllBytes(string path) => TestBytes.AsTask(); public Task<byte[]> ReadAllBytes(string path) => TestBytes.AsTask();
public Unit CopyFile(string source, string destination) => public Task<Either<BaseError, Unit>> CopyFile(string source, string destination) =>
Unit.Default; Task.FromResult(Right<BaseError, Unit>(Unit.Default));
private static List<DirectoryInfo> Split(DirectoryInfo path) private static List<DirectoryInfo> Split(DirectoryInfo path)
{ {
@@ -8,6 +8,6 @@ namespace ErsatzTV.Core.Interfaces.Images
{ {
Task<Either<BaseError, byte[]>> ResizeImage(byte[] imageBuffer, int height); Task<Either<BaseError, byte[]>> ResizeImage(byte[] imageBuffer, int height);
Task<Either<BaseError, string>> SaveArtworkToCache(byte[] imageBuffer, ArtworkKind artworkKind); Task<Either<BaseError, string>> SaveArtworkToCache(byte[] imageBuffer, ArtworkKind artworkKind);
string CopyArtworkToCache(string path, ArtworkKind artworkKind); Task<Either<BaseError, string>> CopyArtworkToCache(string path, ArtworkKind artworkKind);
} }
} }
@@ -15,6 +15,6 @@ namespace ErsatzTV.Core.Interfaces.Metadata
IEnumerable<string> ListFiles(string folder); IEnumerable<string> ListFiles(string folder);
bool FileExists(string path); bool FileExists(string path);
Task<byte[]> ReadAllBytes(string path); Task<byte[]> ReadAllBytes(string path);
Unit CopyFile(string source, string destination); Task<Either<BaseError, Unit>> CopyFile(string source, string destination);
} }
} }
+17 -8
View File
@@ -36,17 +36,26 @@ namespace ErsatzTV.Core.Metadata
public bool FileExists(string path) => File.Exists(path); public bool FileExists(string path) => File.Exists(path);
public Task<byte[]> ReadAllBytes(string path) => File.ReadAllBytesAsync(path); public Task<byte[]> ReadAllBytes(string path) => File.ReadAllBytesAsync(path);
public Unit CopyFile(string source, string destination) public async Task<Either<BaseError, Unit>> CopyFile(string source, string destination)
{ {
string directory = Path.GetDirectoryName(destination) ?? string.Empty; try
if (!Directory.Exists(directory))
{ {
Directory.CreateDirectory(directory); string directory = Path.GetDirectoryName(destination) ?? string.Empty;
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
await using FileStream sourceStream = File.OpenRead(source);
await using FileStream destinationStream = File.Create(destination);
await sourceStream.CopyToAsync(destinationStream);
return Unit.Default;
}
catch (Exception ex)
{
return BaseError.New(ex.ToString());
} }
File.Copy(source, destination, true);
return Unit.Default;
} }
} }
} }
+38 -21
View File
@@ -120,30 +120,47 @@ namespace ErsatzTV.Core.Metadata
if (shouldRefresh) if (shouldRefresh)
{ {
_logger.LogDebug("Refreshing {Attribute} from {Path}", artworkKind, artworkFile); try
string cacheName = _imageCache.CopyArtworkToCache(artworkFile, artworkKind); {
_logger.LogDebug("Refreshing {Attribute} from {Path}", artworkKind, artworkFile);
Either<BaseError, string> maybeCacheName =
await _imageCache.CopyArtworkToCache(artworkFile, artworkKind);
await maybeArtwork.Match( return await maybeCacheName.Match(
async artwork => async cacheName =>
{
artwork.Path = cacheName;
artwork.DateUpdated = lastWriteTime;
await _metadataRepository.UpdateArtworkPath(artwork);
},
async () =>
{
var artwork = new Artwork
{ {
Path = cacheName, await maybeArtwork.Match(
DateAdded = DateTime.UtcNow, async artwork =>
DateUpdated = lastWriteTime, {
ArtworkKind = artworkKind artwork.Path = cacheName;
}; artwork.DateUpdated = lastWriteTime;
metadata.Artwork.Add(artwork); await _metadataRepository.UpdateArtworkPath(artwork);
await _metadataRepository.AddArtwork(metadata, artwork); },
}); async () =>
{
var artwork = new Artwork
{
Path = cacheName,
DateAdded = DateTime.UtcNow,
DateUpdated = lastWriteTime,
ArtworkKind = artworkKind
};
metadata.Artwork.Add(artwork);
await _metadataRepository.AddArtwork(metadata, artwork);
});
return true; return true;
},
error =>
{
_logger.LogDebug("Failed to cache artwork from {Path}: {Error}", artworkFile, error.Value);
return Task.FromResult(false);
});
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Error refreshing artwork");
}
} }
return false; return false;
@@ -80,9 +80,22 @@ namespace ErsatzTV.Core.Metadata
await _searchIndex.UpdateItems(new List<MediaItem> { result.Item }); await _searchIndex.UpdateItems(new List<MediaItem> { result.Item });
} }
await ScanSeasons(libraryPath, ffprobePath, result.Item, showFolder, lastScan); await ScanSeasons(
libraryPath,
ffprobePath,
result.Item,
showFolder,
// force scanning all folders if we're adding a new show
result.IsAdded ? DateTimeOffset.MinValue : lastScan);
}, },
_ => Task.FromResult(Unit.Default)); error =>
{
_logger.LogWarning(
"Error processing show in folder {Folder}: {Error}",
showFolder,
error.Value);
return Task.FromResult(Unit.Default);
});
} }
foreach (string path in await _televisionRepository.FindEpisodePaths(libraryPath)) foreach (string path in await _televisionRepository.FindEpisodePaths(libraryPath))
@@ -132,7 +145,14 @@ namespace ErsatzTV.Core.Metadata
await maybeSeason.Match( await maybeSeason.Match(
season => ScanEpisodes(libraryPath, ffprobePath, season, seasonFolder, lastScan), season => ScanEpisodes(libraryPath, ffprobePath, season, seasonFolder, lastScan),
_ => Task.FromResult(Unit.Default)); error =>
{
_logger.LogWarning(
"Error processing season in folder {Folder}: {Error}",
seasonFolder,
error.Value);
return Task.FromResult(Unit.Default);
});
}); });
} }
+31 -17
View File
@@ -8,6 +8,7 @@ using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Images; using ErsatzTV.Core.Interfaces.Images;
using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Metadata;
using LanguageExt; using LanguageExt;
using Microsoft.Extensions.Logging;
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
@@ -18,10 +19,15 @@ namespace ErsatzTV.Infrastructure.Images
{ {
private static readonly SHA1CryptoServiceProvider Crypto; private static readonly SHA1CryptoServiceProvider Crypto;
private readonly ILocalFileSystem _localFileSystem; private readonly ILocalFileSystem _localFileSystem;
private readonly ILogger<ImageCache> _logger;
static ImageCache() => Crypto = new SHA1CryptoServiceProvider(); static ImageCache() => Crypto = new SHA1CryptoServiceProvider();
public ImageCache(ILocalFileSystem localFileSystem) => _localFileSystem = localFileSystem; public ImageCache(ILocalFileSystem localFileSystem, ILogger<ImageCache> logger)
{
_localFileSystem = localFileSystem;
_logger = logger;
}
public async Task<Either<BaseError, byte[]>> ResizeImage(byte[] imageBuffer, int height) public async Task<Either<BaseError, byte[]>> ResizeImage(byte[] imageBuffer, int height)
{ {
@@ -75,24 +81,32 @@ namespace ErsatzTV.Infrastructure.Images
} }
} }
public string CopyArtworkToCache(string path, ArtworkKind artworkKind) public async Task<Either<BaseError, string>> CopyArtworkToCache(string path, ArtworkKind artworkKind)
{ {
var filenameKey = $"{path}:{_localFileSystem.GetLastWriteTime(path).ToFileTimeUtc()}"; try
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), var filenameKey = $"{path}:{_localFileSystem.GetLastWriteTime(path).ToFileTimeUtc()}";
ArtworkKind.Thumbnail => Path.Combine(FileSystemLayout.ThumbnailCacheFolder, subfolder), byte[] hash = Crypto.ComputeHash(Encoding.UTF8.GetBytes(filenameKey));
ArtworkKind.Logo => Path.Combine(FileSystemLayout.LogoCacheFolder, subfolder), string hex = BitConverter.ToString(hash).Replace("-", string.Empty);
ArtworkKind.FanArt => Path.Combine(FileSystemLayout.FanArtCacheFolder, subfolder), string subfolder = hex.Substring(0, 2);
_ => FileSystemLayout.LegacyImageCacheFolder string baseFolder = artworkKind switch
}; {
string target = Path.Combine(baseFolder, hex); ArtworkKind.Poster => Path.Combine(FileSystemLayout.PosterCacheFolder, subfolder),
_localFileSystem.CopyFile(path, target); ArtworkKind.Thumbnail => Path.Combine(FileSystemLayout.ThumbnailCacheFolder, subfolder),
ArtworkKind.Logo => Path.Combine(FileSystemLayout.LogoCacheFolder, subfolder),
return hex; ArtworkKind.FanArt => Path.Combine(FileSystemLayout.FanArtCacheFolder, subfolder),
_ => FileSystemLayout.LegacyImageCacheFolder
};
string target = Path.Combine(baseFolder, hex);
Either<BaseError, Unit> maybeResult = await _localFileSystem.CopyFile(path, target);
return maybeResult.Match<Either<BaseError, string>>(
_ => hex,
error => error);
}
catch (Exception ex)
{
return BaseError.New(ex.ToString());
}
} }
} }
} }