// Kyoo - A portable and vast media library solution. // Copyright (c) Kyoo. // // See AUTHORS.md and LICENSE file in the project root for full license information. // // Kyoo is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // any later version. // // Kyoo is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using Autofac.Features.Metadata; using JetBrains.Annotations; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models.Attributes; using Kyoo.Core.Models.Options; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; namespace Kyoo.Core.Controllers { /// /// A composite that merge every available /// using . /// public class FileSystemComposite : IFileSystem { /// /// The list of mapped to their metadata. /// 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. /// 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; } /// /// Retrieve the file system that should be used for a given path. /// /// /// The path that was requested. /// /// /// The path that the returned file system wants /// (respecting ). /// /// No file system was registered for the given path. /// The file system that should be used for a given path [NotNull] private IFileSystem _GetFileSystemForPath([NotNull] string path, [NotNull] out string usablePath) { Regex schemeMatcher = new(@"(.+)://(.*)", RegexOptions.Compiled); Match match = schemeMatcher.Match(path); if (!match.Success) { usablePath = path; Meta, FileSystemMetadataAttribute> defaultFs = _fileSystems .SingleOrDefault(x => x.Metadata.Scheme.Contains(string.Empty)); if (defaultFs == null) throw new ArgumentException($"No file system registered for the default scheme."); return defaultFs.Value.Invoke(); } string scheme = match.Groups[1].Value; Meta, FileSystemMetadataAttribute> ret = _fileSystems .SingleOrDefault(x => x.Metadata.Scheme.Contains(scheme)); if (ret == null) throw new ArgumentException($"No file system registered for the scheme: {scheme}."); usablePath = ret.Metadata.StripScheme ? match.Groups[2].Value : path; return ret.Value.Invoke(); } /// public IActionResult FileResult(string path, bool rangeSupport = false, string type = null) { if (path == null) return new NotFoundResult(); return _GetFileSystemForPath(path, out string relativePath) .FileResult(relativePath, rangeSupport, type); } /// public Task GetReader(string path) { if (path == null) throw new ArgumentNullException(nameof(path)); return _GetFileSystemForPath(path, out string relativePath) .GetReader(relativePath); } /// public Task GetReader(string path, AsyncRef mime) { if (path == null) throw new ArgumentNullException(nameof(path)); return _GetFileSystemForPath(path, out string relativePath) .GetReader(relativePath, mime); } /// public Task NewFile(string path) { if (path == null) throw new ArgumentNullException(nameof(path)); return _GetFileSystemForPath(path, out string relativePath) .NewFile(relativePath); } /// public Task CreateDirectory(string path) { if (path == null) throw new ArgumentNullException(nameof(path)); return _GetFileSystemForPath(path, out string relativePath) .CreateDirectory(relativePath); } /// public string Combine(params string[] paths) { return _GetFileSystemForPath(paths[0], out string relativePath) .Combine(paths[1..].Prepend(relativePath).ToArray()); } /// public Task> ListFiles(string path, SearchOption options = SearchOption.TopDirectoryOnly) { if (path == null) throw new ArgumentNullException(nameof(path)); return _GetFileSystemForPath(path, out string relativePath) .ListFiles(relativePath, options); } /// public Task Exists(string path) { if (path == null) throw new ArgumentNullException(nameof(path)); return _GetFileSystemForPath(path, out string relativePath) .Exists(relativePath); } /// public async Task GetExtraDirectory(T resource) { 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) ?? resource switch { Season season => await GetExtraDirectory(season.Show), Episode episode => await GetExtraDirectory(episode.Show), Track track => await GetExtraDirectory(track.Episode), IResource res => Combine(_options.CurrentValue.MetadataPath, typeof(T).Name.ToLowerInvariant(), res.Slug), _ => Combine(_options.CurrentValue.MetadataPath, typeof(T).Name.ToLowerInvariant()) }; return await CreateDirectory(path); } } }