cache artwork async (#117)
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user