diff --git a/ErsatzTV.Core.Tests/Fakes/FakeLocalFileSystem.cs b/ErsatzTV.Core.Tests/Fakes/FakeLocalFileSystem.cs index 80962876c..2036ace2c 100644 --- a/ErsatzTV.Core.Tests/Fakes/FakeLocalFileSystem.cs +++ b/ErsatzTV.Core.Tests/Fakes/FakeLocalFileSystem.cs @@ -56,8 +56,8 @@ namespace ErsatzTV.Core.Tests.Fakes public Task ReadAllBytes(string path) => TestBytes.AsTask(); - public Unit CopyFile(string source, string destination) => - Unit.Default; + public Task> CopyFile(string source, string destination) => + Task.FromResult(Right(Unit.Default)); private static List Split(DirectoryInfo path) { diff --git a/ErsatzTV.Core/Interfaces/Images/IImageCache.cs b/ErsatzTV.Core/Interfaces/Images/IImageCache.cs index 5b8737d8c..84d359e76 100644 --- a/ErsatzTV.Core/Interfaces/Images/IImageCache.cs +++ b/ErsatzTV.Core/Interfaces/Images/IImageCache.cs @@ -8,6 +8,6 @@ namespace ErsatzTV.Core.Interfaces.Images { Task> ResizeImage(byte[] imageBuffer, int height); Task> SaveArtworkToCache(byte[] imageBuffer, ArtworkKind artworkKind); - string CopyArtworkToCache(string path, ArtworkKind artworkKind); + Task> CopyArtworkToCache(string path, ArtworkKind artworkKind); } } diff --git a/ErsatzTV.Core/Interfaces/Metadata/ILocalFileSystem.cs b/ErsatzTV.Core/Interfaces/Metadata/ILocalFileSystem.cs index ff6d84dc3..86b070dc1 100644 --- a/ErsatzTV.Core/Interfaces/Metadata/ILocalFileSystem.cs +++ b/ErsatzTV.Core/Interfaces/Metadata/ILocalFileSystem.cs @@ -15,6 +15,6 @@ namespace ErsatzTV.Core.Interfaces.Metadata IEnumerable ListFiles(string folder); bool FileExists(string path); Task ReadAllBytes(string path); - Unit CopyFile(string source, string destination); + Task> CopyFile(string source, string destination); } } diff --git a/ErsatzTV.Core/Metadata/LocalFileSystem.cs b/ErsatzTV.Core/Metadata/LocalFileSystem.cs index 6b2c81201..95d8fbd6e 100644 --- a/ErsatzTV.Core/Metadata/LocalFileSystem.cs +++ b/ErsatzTV.Core/Metadata/LocalFileSystem.cs @@ -36,17 +36,26 @@ namespace ErsatzTV.Core.Metadata public bool FileExists(string path) => File.Exists(path); public Task ReadAllBytes(string path) => File.ReadAllBytesAsync(path); - public Unit CopyFile(string source, string destination) + public async Task> CopyFile(string source, string destination) { - string directory = Path.GetDirectoryName(destination) ?? string.Empty; - if (!Directory.Exists(directory)) + try { - 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; } } } diff --git a/ErsatzTV.Core/Metadata/LocalFolderScanner.cs b/ErsatzTV.Core/Metadata/LocalFolderScanner.cs index f73e285d0..21f71e584 100644 --- a/ErsatzTV.Core/Metadata/LocalFolderScanner.cs +++ b/ErsatzTV.Core/Metadata/LocalFolderScanner.cs @@ -120,30 +120,47 @@ namespace ErsatzTV.Core.Metadata if (shouldRefresh) { - _logger.LogDebug("Refreshing {Attribute} from {Path}", artworkKind, artworkFile); - string cacheName = _imageCache.CopyArtworkToCache(artworkFile, artworkKind); + try + { + _logger.LogDebug("Refreshing {Attribute} from {Path}", artworkKind, artworkFile); + Either maybeCacheName = + await _imageCache.CopyArtworkToCache(artworkFile, artworkKind); - await maybeArtwork.Match( - async artwork => - { - artwork.Path = cacheName; - artwork.DateUpdated = lastWriteTime; - await _metadataRepository.UpdateArtworkPath(artwork); - }, - async () => - { - var artwork = new Artwork + return await maybeCacheName.Match( + async cacheName => { - Path = cacheName, - DateAdded = DateTime.UtcNow, - DateUpdated = lastWriteTime, - ArtworkKind = artworkKind - }; - metadata.Artwork.Add(artwork); - await _metadataRepository.AddArtwork(metadata, artwork); - }); + await maybeArtwork.Match( + async artwork => + { + artwork.Path = cacheName; + artwork.DateUpdated = lastWriteTime; + await _metadataRepository.UpdateArtworkPath(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; diff --git a/ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs b/ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs index 707534a17..fa2b5363b 100644 --- a/ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs +++ b/ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs @@ -80,9 +80,22 @@ namespace ErsatzTV.Core.Metadata await _searchIndex.UpdateItems(new List { 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)) @@ -132,7 +145,14 @@ namespace ErsatzTV.Core.Metadata await maybeSeason.Match( 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); + }); }); } diff --git a/ErsatzTV.Infrastructure/Images/ImageCache.cs b/ErsatzTV.Infrastructure/Images/ImageCache.cs index 050a88718..48ab613d7 100644 --- a/ErsatzTV.Infrastructure/Images/ImageCache.cs +++ b/ErsatzTV.Infrastructure/Images/ImageCache.cs @@ -8,6 +8,7 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Images; using ErsatzTV.Core.Interfaces.Metadata; using LanguageExt; +using Microsoft.Extensions.Logging; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Processing; @@ -18,10 +19,15 @@ namespace ErsatzTV.Infrastructure.Images { private static readonly SHA1CryptoServiceProvider Crypto; private readonly ILocalFileSystem _localFileSystem; + private readonly ILogger _logger; static ImageCache() => Crypto = new SHA1CryptoServiceProvider(); - public ImageCache(ILocalFileSystem localFileSystem) => _localFileSystem = localFileSystem; + public ImageCache(ILocalFileSystem localFileSystem, ILogger logger) + { + _localFileSystem = localFileSystem; + _logger = logger; + } public async Task> ResizeImage(byte[] imageBuffer, int height) { @@ -75,24 +81,32 @@ namespace ErsatzTV.Infrastructure.Images } } - public string CopyArtworkToCache(string path, ArtworkKind artworkKind) + public async Task> 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 + try { - ArtworkKind.Poster => Path.Combine(FileSystemLayout.PosterCacheFolder, subfolder), - ArtworkKind.Thumbnail => Path.Combine(FileSystemLayout.ThumbnailCacheFolder, subfolder), - ArtworkKind.Logo => Path.Combine(FileSystemLayout.LogoCacheFolder, subfolder), - ArtworkKind.FanArt => Path.Combine(FileSystemLayout.FanArtCacheFolder, subfolder), - _ => FileSystemLayout.LegacyImageCacheFolder - }; - string target = Path.Combine(baseFolder, hex); - _localFileSystem.CopyFile(path, target); - - return hex; + 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), + ArtworkKind.Logo => Path.Combine(FileSystemLayout.LogoCacheFolder, subfolder), + ArtworkKind.FanArt => Path.Combine(FileSystemLayout.FanArtCacheFolder, subfolder), + _ => FileSystemLayout.LegacyImageCacheFolder + }; + string target = Path.Combine(baseFolder, hex); + Either maybeResult = await _localFileSystem.CopyFile(path, target); + return maybeResult.Match>( + _ => hex, + error => error); + } + catch (Exception ex) + { + return BaseError.New(ex.ToString()); + } } } }