using System; using System.IO; using System.Linq; using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.Logging; namespace Kyoo.Core.Controllers { /// /// Download images and retrieve the path of those images for a resource. /// public class ThumbnailsManager : IThumbnailsManager { /// /// The file manager used to download the image if the file is distant /// private readonly IFileSystem _files; /// /// A logger to report errors. /// private readonly ILogger _logger; /// /// Create a new . /// /// The file manager to use. /// A logger to report errors public ThumbnailsManager(IFileSystem files, ILogger logger) { _files = files; _logger = logger; } /// /// An helper function to download an image using a . /// /// The distant url of the image /// The local path of the image /// What is currently downloaded (used for errors) /// true if an image has been downloaded, false otherwise. private async Task _DownloadImage(string url, string localPath, string what) { if (url == localPath) return false; try { AsyncRef mime = new(); await using Stream reader = await _files.GetReader(url, mime); string extension = new FileExtensionContentTypeProvider() .Mappings.FirstOrDefault(x => x.Value == mime.Value) .Key; await using Stream local = await _files.NewFile(localPath + extension); await reader.CopyToAsync(local); return true; } catch (Exception ex) { _logger.LogError(ex, "{What} could not be downloaded", what); return false; } } /// public async Task DownloadImages(T item, bool alwaysDownload = false) where T : IThumbnails { if (item == null) throw new ArgumentNullException(nameof(item)); if (item.Images == null) return false; string name = item is IResource res ? res.Slug : "???"; bool ret = false; foreach ((int id, string image) in item.Images.Where(x => x.Value != null)) { string localPath = await _GetPrivateImagePath(item, id); if (alwaysDownload || !await _files.Exists(localPath)) ret |= await _DownloadImage(image, localPath, $"The image n°{id} of {name}"); } return ret; } /// /// Retrieve the local path of an image of the given item without an extension. /// /// The item to retrieve the poster from. /// The ID of the image. See for values. /// The type of the item /// The path of the image for the given resource, even if it does not exists private async Task _GetPrivateImagePath(T item, int imageID) { if (item == null) throw new ArgumentNullException(nameof(item)); string directory = await _files.GetExtraDirectory(item); string imageName = imageID switch { Images.Poster => "poster", Images.Logo => "logo", Images.Thumbnail => "thumbnail", Images.Trailer => "trailer", _ => $"{imageID}" }; switch (item) { case Season season: imageName = $"season-{season.SeasonNumber}-{imageName}"; break; case Episode episode: directory = await _files.CreateDirectory(_files.Combine(directory, "Thumbnails")); imageName = $"{Path.GetFileNameWithoutExtension(episode.Path)}-{imageName}"; break; } return _files.Combine(directory, imageName); } /// public async Task GetImagePath(T item, int imageID) where T : IThumbnails { string basePath = await _GetPrivateImagePath(item, imageID); string directory = Path.GetDirectoryName(basePath); string baseFile = Path.GetFileName(basePath); return (await _files.ListFiles(directory!)) .FirstOrDefault(x => Path.GetFileNameWithoutExtension(x) == baseFile); } } }