diff --git a/Kyoo.Common/Controllers/IFileSystem.cs b/Kyoo.Common/Controllers/IFileSystem.cs index 2fb1c6d7..f0702e04 100644 --- a/Kyoo.Common/Controllers/IFileSystem.cs +++ b/Kyoo.Common/Controllers/IFileSystem.cs @@ -81,12 +81,13 @@ namespace Kyoo.Controllers public Task Exists([NotNull] string path); /// - /// Get the extra directory of a show. + /// Get the extra directory of a resource . /// This method is in this system to allow a filesystem to use a different metadata policy for one. /// It can be useful if the filesystem is readonly. /// - /// The show to proceed - /// The extra directory of the show - public string GetExtraDirectory([NotNull] Show show); + /// The resource to proceed + /// The type of the resource. + /// The extra directory of the resource. + public Task GetExtraDirectory([NotNull] T resource); } } \ No newline at end of file diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index 938d3029..8c3442e3 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -225,42 +225,51 @@ namespace Kyoo.Controllers /// /// The source object. /// A getter function for the member to load + /// + /// true if you want to load the relation even if it is not null, false otherwise. + /// /// The type of the source object /// The related resource's type /// The param - /// - /// - /// - Task Load([NotNull] T obj, Expression> member) + /// + /// + /// + Task Load([NotNull] T obj, Expression> member, bool force = false) where T : class, IResource - where T2 : class, IResource, new(); + where T2 : class, IResource; /// /// Load a collection of related resource /// /// The source object. /// A getter function for the member to load + /// + /// true if you want to load the relation even if it is not null, false otherwise. + /// /// The type of the source object /// The related resource's type /// The param - /// - /// - /// - Task Load([NotNull] T obj, Expression>> member) + /// + /// + /// + Task Load([NotNull] T obj, Expression>> member, bool force = false) where T : class, IResource - where T2 : class, new(); + where T2 : class; /// /// Load a related resource by it's name /// /// The source object. /// The name of the resource to load (case sensitive) + /// + /// true if you want to load the relation even if it is not null, false otherwise. + /// /// The type of the source object /// The param - /// - /// - /// - Task Load([NotNull] T obj, string memberName) + /// + /// + /// + Task Load([NotNull] T obj, string memberName, bool force = false) where T : class, IResource; /// @@ -268,10 +277,13 @@ namespace Kyoo.Controllers /// /// The source object. /// The name of the resource to load (case sensitive) - /// - /// - /// - Task Load([NotNull] IResource obj, string memberName); + /// + /// true if you want to load the relation even if it is not null, false otherwise. + /// + /// + /// + /// + Task Load([NotNull] IResource obj, string memberName, bool force = false); /// /// Get items (A wrapper arround shows or collections) from a library. diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index 5b051ed8..08475c32 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -162,34 +162,6 @@ namespace Kyoo.Controllers return await EpisodeRepository.GetOrDefault(showSlug, seasonNumber, episodeNumber); } - /// - public Task Load(T obj, Expression> member) - where T : class, IResource - where T2 : class, IResource, new() - { - if (member == null) - throw new ArgumentNullException(nameof(member)); - return Load(obj, Utility.GetPropertyName(member)); - } - - /// - public Task Load(T obj, Expression>> member) - where T : class, IResource - where T2 : class, new() - { - if (member == null) - throw new ArgumentNullException(nameof(member)); - return Load(obj, Utility.GetPropertyName(member)); - } - - /// - public async Task Load(T obj, string memberName) - where T : class, IResource - { - await Load(obj as IResource, memberName); - return obj; - } - /// /// Set relations between to objects. /// @@ -211,11 +183,46 @@ namespace Kyoo.Controllers } /// - public Task Load(IResource obj, string memberName) + public Task Load(T obj, Expression> member, bool force = false) + where T : class, IResource + where T2 : class, IResource + { + if (member == null) + throw new ArgumentNullException(nameof(member)); + return Load(obj, Utility.GetPropertyName(member), force); + } + + /// + public Task Load(T obj, Expression>> member, bool force = false) + where T : class, IResource + where T2 : class + { + if (member == null) + throw new ArgumentNullException(nameof(member)); + return Load(obj, Utility.GetPropertyName(member), force); + } + + /// + public async Task Load(T obj, string memberName, bool force = false) + where T : class, IResource + { + await Load(obj as IResource, memberName, force); + return obj; + } + + /// + public Task Load(IResource obj, string memberName, bool force = false) { if (obj == null) throw new ArgumentNullException(nameof(obj)); - + + object existingValue = obj.GetType() + .GetProperties() + .FirstOrDefault(x => string.Equals(x.Name, memberName, StringComparison.InvariantCultureIgnoreCase)) + ?.GetValue(obj); + if (existingValue != null && !force) + return Task.CompletedTask; + return (obj, member: memberName) switch { (Library l, nameof(Library.Providers)) => ProviderRepository diff --git a/Kyoo/Controllers/FileSystems/FileSystemComposite.cs b/Kyoo/Controllers/FileSystems/FileSystemComposite.cs index da98539f..c7bda72a 100644 --- a/Kyoo/Controllers/FileSystems/FileSystemComposite.cs +++ b/Kyoo/Controllers/FileSystems/FileSystemComposite.cs @@ -8,7 +8,9 @@ using Autofac.Features.Metadata; using JetBrains.Annotations; using Kyoo.Common.Models.Attributes; using Kyoo.Models; +using Kyoo.Models.Options; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; namespace Kyoo.Controllers { @@ -23,14 +25,31 @@ namespace Kyoo.Controllers /// private readonly ICollection, FileSystemMetadataAttribute>> _fileSystems; + /// + /// The library manager used to load shows to retrieve their path + /// (only if the option is set to metadata in show) + /// + private readonly ILibraryManager _libraryManager; + + /// + /// Options to check if the metadata should be kept in the show directory or in a kyoo's directory. + /// + private readonly IOptionsMonitor _options; + /// /// Create a new from a list of mapped to their /// metadata. /// /// The list of filesystem mapped to their metadata. - public FileSystemComposite(ICollection, FileSystemMetadataAttribute>> fileSystems) + /// The library manager used to load shows to retrieve their path. + /// The options to use. + public FileSystemComposite(ICollection, FileSystemMetadataAttribute>> fileSystems, + ILibraryManager libraryManager, + IOptionsMonitor options) { _fileSystems = fileSystems; + _libraryManager = libraryManager; + _options = options; } @@ -132,12 +151,37 @@ namespace Kyoo.Controllers } /// - public string GetExtraDirectory(Show show) + public async Task GetExtraDirectory(T resource) { - if (show == null) - throw new ArgumentNullException(nameof(show)); - return _GetFileSystemForPath(show.Path, out string _) - .GetExtraDirectory(show); + switch (resource) + { + case Season season: + await _libraryManager.Load(season, x => x.Show); + break; + case Episode episode: + await _libraryManager.Load(episode, x => x.Show); + break; + case Track track: + await _libraryManager.Load(track, x => x.Episode); + await _libraryManager.Load(track.Episode, x => x.Show); + break; + } + + IFileSystem fs = resource switch + { + Show show => _GetFileSystemForPath(show.Path, out string _), + Season season => _GetFileSystemForPath(season.Show.Path, out string _), + Episode episode => _GetFileSystemForPath(episode.Show.Path, out string _), + Track track => _GetFileSystemForPath(track.Episode.Show.Path, out string _), + _ => _GetFileSystemForPath(_options.CurrentValue.MetadataPath, out string _) + }; + string path = await fs.GetExtraDirectory(resource); + if (resource is IResource res) + path ??= Combine(_options.CurrentValue.MetadataPath, res.Slug, typeof(T).Name); + else + path ??= Combine(_options.CurrentValue.MetadataPath, typeof(T).Name); + await CreateDirectory(path); + return path; } } } \ No newline at end of file diff --git a/Kyoo/Controllers/FileSystems/HttpFileSystem.cs b/Kyoo/Controllers/FileSystems/HttpFileSystem.cs index 3d2ea991..5eeebbc2 100644 --- a/Kyoo/Controllers/FileSystems/HttpFileSystem.cs +++ b/Kyoo/Controllers/FileSystems/HttpFileSystem.cs @@ -76,7 +76,7 @@ namespace Kyoo.Controllers } /// - public string GetExtraDirectory(Show show) + public Task GetExtraDirectory(T resource) { throw new NotSupportedException("Extras can not be stored inside an http filesystem."); } @@ -117,7 +117,12 @@ namespace Kyoo.Controllers public Task ExecuteResultAsync(ActionContext context) { // TODO implement that, example: https://github.com/twitchax/AspNetCore.Proxy/blob/14dd0f212d7abb43ca1bf8c890d5efb95db66acb/src/Core/Extensions/Http.cs#L15 - throw new NotImplementedException(); + + // Silence unused warnings + if (_path != null || _rangeSupport || _type == null) + throw new NotImplementedException(); + else + throw new NotImplementedException(); } } } \ No newline at end of file diff --git a/Kyoo/Controllers/FileSystems/LocalFileSystem.cs b/Kyoo/Controllers/FileSystems/LocalFileSystem.cs index 3239c55f..9c085582 100644 --- a/Kyoo/Controllers/FileSystems/LocalFileSystem.cs +++ b/Kyoo/Controllers/FileSystems/LocalFileSystem.cs @@ -4,8 +4,10 @@ using System.IO; using System.Threading.Tasks; using Kyoo.Common.Models.Attributes; using Kyoo.Models; +using Kyoo.Models.Options; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.Options; namespace Kyoo.Controllers { @@ -20,6 +22,20 @@ namespace Kyoo.Controllers /// private FileExtensionContentTypeProvider _provider; + /// + /// Options to check if the metadata should be kept in the show directory or in a kyoo's directory. + /// + private readonly IOptionsMonitor _options; + + /// + /// Create a new with the specified options. + /// + /// The options to use. + public LocalFileSystem(IOptionsMonitor options) + { + _options = options; + } + /// /// Get the content type of a file using it's extension. /// @@ -104,11 +120,18 @@ namespace Kyoo.Controllers } /// - public string GetExtraDirectory(Show show) + public Task GetExtraDirectory(T resource) { - string path = Path.Combine(show.Path, "Extra"); - Directory.CreateDirectory(path); - return path; + if (!_options.CurrentValue.MetadataInShow) + return null; + return Task.FromResult(resource switch + { + Show show => Combine(show.Path, "Extra"), + Season season => Combine(season.Show.Path, "Extra", "Season"), + Episode episode => Combine(episode.Show.Path, "Extra", "Episode"), + Track track => Combine(track.Episode.Show.Path, "Track"), + _ => null + }); } } } \ No newline at end of file diff --git a/Kyoo/Controllers/ThumbnailsManager.cs b/Kyoo/Controllers/ThumbnailsManager.cs index 3f6ffe3a..f6be0171 100644 --- a/Kyoo/Controllers/ThumbnailsManager.cs +++ b/Kyoo/Controllers/ThumbnailsManager.cs @@ -3,9 +3,7 @@ using System; using System.IO; using System.Linq; using System.Threading.Tasks; -using Kyoo.Models.Options; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; namespace Kyoo.Controllers { @@ -22,37 +20,17 @@ namespace Kyoo.Controllers /// A logger to report errors. /// private readonly ILogger _logger; - /// - /// The options containing the base path of people images and provider logos. - /// - private readonly IOptionsMonitor _options; - /// - /// A library manager used to load episode and seasons shows if they are not loaded. - /// - private readonly Lazy _library; /// /// Create a new . /// /// The file manager to use. /// A logger to report errors - /// The options to use. - /// A library manager used to load shows if they are not loaded. public ThumbnailsManager(IFileSystem files, - ILogger logger, - IOptionsMonitor options, - Lazy library) + ILogger logger) { _files = files; _logger = logger; - _options = options; - _library = library; - - options.OnChange(x => - { - _files.CreateDirectory(x.PeoplePath); - _files.CreateDirectory(x.ProviderPath); - }); } /// @@ -119,35 +97,7 @@ namespace Kyoo.Controllers _ => $"{imageID}.jpg" }; - // TODO implement a generic way, probably need to rework IFileManager.GetExtraDirectory too. - switch (item) - { - case Show show: - return _files.Combine(_files.GetExtraDirectory(show), imageName); - - case Season season: - if (season.Show == null) - await _library.Value.Load(season, x => x.Show); - return _files.Combine( - _files.GetExtraDirectory(season.Show!), - $"season-{season.SeasonNumber}-{imageName}"); - - case Episode episode: - if (episode.Show == null) - await _library.Value.Load(episode, x => x.Show); - string dir = _files.Combine(_files.GetExtraDirectory(episode.Show!), "Thumbnails"); - await _files.CreateDirectory(dir); - return _files.Combine(dir, $"{Path.GetFileNameWithoutExtension(episode.Path)}-{imageName}"); - - case People actor: - return _files.Combine(_options.CurrentValue.PeoplePath, $"{actor.Slug}-{imageName}"); - - case Provider provider: - return _files.Combine(_options.CurrentValue.ProviderPath, $"{provider.Slug}-{imageName}"); - - default: - throw new NotSupportedException($"The type {typeof(T).Name} is not supported."); - } + return _files.Combine(await _files.GetExtraDirectory(item), imageName); } } } diff --git a/Kyoo/Controllers/Transcoder.cs b/Kyoo/Controllers/Transcoder.cs index 47291753..5942cbd2 100644 --- a/Kyoo/Controllers/Transcoder.cs +++ b/Kyoo/Controllers/Transcoder.cs @@ -88,10 +88,8 @@ namespace Kyoo.Controllers public async Task ExtractInfos(Episode episode, bool reextract) { - if (episode.Show == null) - await _library.Value.Load(episode, x => x.Show); - - string dir = _files.GetExtraDirectory(episode.Show); + await _library.Value.Load(episode, x => x.Show); + string dir = await _files.GetExtraDirectory(episode.Show); if (dir == null) throw new ArgumentException("Invalid path."); return await Task.Factory.StartNew( diff --git a/Kyoo/Models/Options/BasicOptions.cs b/Kyoo/Models/Options/BasicOptions.cs index 95d4873a..60e95ee2 100644 --- a/Kyoo/Models/Options/BasicOptions.cs +++ b/Kyoo/Models/Options/BasicOptions.cs @@ -25,16 +25,6 @@ namespace Kyoo.Models.Options /// public string PluginPath { get; set; } = "plugins/"; - /// - /// The path of the people pictures. - /// - public string PeoplePath { get; set; } = "people/"; - - /// - /// The path of providers icons. - /// - public string ProviderPath { get; set; } = "providers/"; - /// /// The temporary folder to cache transmuxed file. /// @@ -44,5 +34,22 @@ namespace Kyoo.Models.Options /// The temporary folder to cache transcoded file. /// public string TranscodePath { get; set; } = "cached/transcode"; + + /// + /// true if the metadata of a show/season/episode should be stored in the same directory as video files, + /// false to save them in a kyoo specific directory. + /// + /// + /// Some file systems might discard this option to store them somewhere else. + /// For example, readonly file systems will probably store them in a kyoo specific directory. + /// + public bool MetadataInShow { get; set; } = true; + + /// + /// The path for metadata if they are not stored near show (see ). + /// Some resources can't be stored near a show and they are stored in this directory + /// (like ). + /// + public string MetadataPath { get; set; } = "metadata/"; } } \ No newline at end of file diff --git a/Kyoo/Views/ShowApi.cs b/Kyoo/Views/ShowApi.cs index df12c876..0c8dde65 100644 --- a/Kyoo/Views/ShowApi.cs +++ b/Kyoo/Views/ShowApi.cs @@ -383,7 +383,7 @@ namespace Kyoo.Api try { Show show = await _libraryManager.Get(slug); - string path = Path.Combine(_files.GetExtraDirectory(show), "Attachments"); + string path = _files.Combine(await _files.GetExtraDirectory(show), "Attachments"); return (await _files.ListFiles(path)) .ToDictionary(Path.GetFileNameWithoutExtension, x => $"{BaseURL}api/shows/{slug}/fonts/{Path.GetFileName(x)}"); @@ -402,7 +402,7 @@ namespace Kyoo.Api try { Show show = await _libraryManager.Get(showSlug); - string path = Path.Combine(_files.GetExtraDirectory(show), "Attachments", slug); + string path = _files.Combine(await _files.GetExtraDirectory(show), "Attachments", slug); return _files.FileResult(path); } catch (ItemNotFoundException) diff --git a/Kyoo/settings.json b/Kyoo/settings.json index 8529a35a..1ddc8cf1 100644 --- a/Kyoo/settings.json +++ b/Kyoo/settings.json @@ -3,10 +3,10 @@ "url": "http://*:5000", "publicUrl": "http://localhost:5000/", "pluginsPath": "plugins/", - "peoplePath": "people/", - "providerPath": "providers/", "transmuxPath": "cached/transmux", - "transcodePath": "cached/transcode" + "transcodePath": "cached/transcode", + "metadataInShow": true, + "metadataPath": "metadata/" }, "database": {