123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373 |
- #nullable disable
- #pragma warning disable CS1591
- using System;
- using System.Collections.Concurrent;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Text.Json;
- using System.Text.Json.Serialization;
- using System.Threading;
- using System.Threading.Tasks;
- using Jellyfin.Data.Enums;
- using Jellyfin.Database.Implementations.Entities;
- using Jellyfin.Extensions.Json;
- using MediaBrowser.Controller.IO;
- using MediaBrowser.Controller.Library;
- using MediaBrowser.Controller.Providers;
- using MediaBrowser.Model.Configuration;
- using MediaBrowser.Model.IO;
- using MediaBrowser.Model.Serialization;
- using Microsoft.Extensions.Logging;
- namespace MediaBrowser.Controller.Entities
- {
- /// <summary>
- /// Specialized Folder class that points to a subset of the physical folders in the system.
- /// It is created from the user-specific folders within the system root.
- /// </summary>
- public class CollectionFolder : Folder, ICollectionFolder
- {
- private static readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
- private static readonly ConcurrentDictionary<string, LibraryOptions> _libraryOptions = new ConcurrentDictionary<string, LibraryOptions>();
- private bool _requiresRefresh;
- /// <summary>
- /// Initializes a new instance of the <see cref="CollectionFolder"/> class.
- /// </summary>
- public CollectionFolder()
- {
- PhysicalLocationsList = Array.Empty<string>();
- PhysicalFolderIds = Array.Empty<Guid>();
- }
- /// <summary>
- /// Gets the display preferences id.
- /// </summary>
- /// <remarks>
- /// Allow different display preferences for each collection folder.
- /// </remarks>
- /// <value>The display prefs id.</value>
- [JsonIgnore]
- public override Guid DisplayPreferencesId => Id;
- [JsonIgnore]
- public override string[] PhysicalLocations => PhysicalLocationsList;
- public string[] PhysicalLocationsList { get; set; }
- public Guid[] PhysicalFolderIds { get; set; }
- public static IXmlSerializer XmlSerializer { get; set; }
- public static IServerApplicationHost ApplicationHost { get; set; }
- [JsonIgnore]
- public override bool SupportsPlayedStatus => false;
- [JsonIgnore]
- public override bool SupportsInheritedParentImages => false;
- public CollectionType? CollectionType { get; set; }
- /// <summary>
- /// Gets the item's children.
- /// </summary>
- /// <remarks>
- /// Our children are actually just references to the ones in the physical root...
- /// </remarks>
- /// <value>The actual children.</value>
- [JsonIgnore]
- public override IEnumerable<BaseItem> Children => GetActualChildren();
- [JsonIgnore]
- public override bool SupportsPeople => false;
- public override bool CanDelete()
- {
- return false;
- }
- public LibraryOptions GetLibraryOptions()
- {
- return GetLibraryOptions(Path);
- }
- public override bool IsVisible(User user, bool skipAllowedTagsCheck = false)
- {
- if (GetLibraryOptions().Enabled)
- {
- return base.IsVisible(user, skipAllowedTagsCheck);
- }
- return false;
- }
- private static LibraryOptions LoadLibraryOptions(string path)
- {
- try
- {
- if (XmlSerializer.DeserializeFromFile(typeof(LibraryOptions), GetLibraryOptionsPath(path)) is not LibraryOptions result)
- {
- return new LibraryOptions();
- }
- foreach (var mediaPath in result.PathInfos)
- {
- if (!string.IsNullOrEmpty(mediaPath.Path))
- {
- mediaPath.Path = ApplicationHost.ExpandVirtualPath(mediaPath.Path);
- }
- }
- return result;
- }
- catch (FileNotFoundException)
- {
- return new LibraryOptions();
- }
- catch (IOException)
- {
- return new LibraryOptions();
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "Error loading library options");
- return new LibraryOptions();
- }
- }
- private static string GetLibraryOptionsPath(string path)
- {
- return System.IO.Path.Combine(path, "options.xml");
- }
- public void UpdateLibraryOptions(LibraryOptions options)
- {
- SaveLibraryOptions(Path, options);
- }
- public static LibraryOptions GetLibraryOptions(string path)
- => _libraryOptions.GetOrAdd(path, LoadLibraryOptions);
- public static void SaveLibraryOptions(string path, LibraryOptions options)
- {
- _libraryOptions[path] = options;
- var clone = JsonSerializer.Deserialize<LibraryOptions>(JsonSerializer.SerializeToUtf8Bytes(options, _jsonOptions), _jsonOptions);
- foreach (var mediaPath in clone.PathInfos)
- {
- if (!string.IsNullOrEmpty(mediaPath.Path))
- {
- mediaPath.Path = ApplicationHost.ReverseVirtualPath(mediaPath.Path);
- }
- }
- XmlSerializer.SerializeToFile(clone, GetLibraryOptionsPath(path));
- }
- public static void OnCollectionFolderChange()
- => _libraryOptions.Clear();
- public override bool IsSaveLocalMetadataEnabled()
- {
- return true;
- }
- protected override FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService)
- {
- return CreateResolveArgs(directoryService, true).FileSystemChildren;
- }
- public override bool RequiresRefresh()
- {
- var changed = base.RequiresRefresh() || _requiresRefresh;
- if (!changed)
- {
- var locations = PhysicalLocations;
- var newLocations = CreateResolveArgs(new DirectoryService(FileSystem), false).PhysicalLocations;
- if (!locations.SequenceEqual(newLocations))
- {
- changed = true;
- }
- }
- if (!changed)
- {
- var folderIds = PhysicalFolderIds;
- var newFolderIds = GetPhysicalFolders(false).Select(i => i.Id).ToList();
- if (!folderIds.SequenceEqual(newFolderIds))
- {
- changed = true;
- }
- }
- return changed;
- }
- public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
- {
- var changed = base.BeforeMetadataRefresh(replaceAllMetadata) || _requiresRefresh;
- _requiresRefresh = false;
- return changed;
- }
- public override double? GetRefreshProgress()
- {
- var folders = GetPhysicalFolders(true).ToList();
- double totalProgresses = 0;
- var foldersWithProgress = 0;
- foreach (var folder in folders)
- {
- var progress = ProviderManager.GetRefreshProgress(folder.Id);
- if (progress.HasValue)
- {
- totalProgresses += progress.Value;
- foldersWithProgress++;
- }
- }
- if (foldersWithProgress == 0)
- {
- return null;
- }
- return totalProgresses / foldersWithProgress;
- }
- protected override bool RefreshLinkedChildren(IEnumerable<FileSystemMetadata> fileSystemChildren)
- {
- return RefreshLinkedChildrenInternal(true);
- }
- private bool RefreshLinkedChildrenInternal(bool setFolders)
- {
- var physicalFolders = GetPhysicalFolders(false)
- .ToList();
- var linkedChildren = physicalFolders
- .SelectMany(c => c.LinkedChildren)
- .ToList();
- var changed = !linkedChildren.SequenceEqual(LinkedChildren, new LinkedChildComparer(FileSystem));
- LinkedChildren = linkedChildren.ToArray();
- var folderIds = PhysicalFolderIds;
- var newFolderIds = physicalFolders.Select(i => i.Id).ToArray();
- if (!folderIds.SequenceEqual(newFolderIds))
- {
- changed = true;
- if (setFolders)
- {
- PhysicalFolderIds = newFolderIds;
- }
- }
- return changed;
- }
- private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService, bool setPhysicalLocations)
- {
- var path = ContainingFolderPath;
- var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager)
- {
- FileInfo = FileSystem.GetDirectoryInfo(path),
- Parent = GetParent() as Folder,
- CollectionType = CollectionType
- };
- // Gather child folder and files
- if (args.IsDirectory)
- {
- var flattenFolderDepth = 0;
- var files = FileData.GetFilteredFileSystemEntries(directoryService, args.Path, FileSystem, ApplicationHost, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: true);
- args.FileSystemChildren = files;
- }
- _requiresRefresh = _requiresRefresh || !args.PhysicalLocations.SequenceEqual(PhysicalLocations);
- if (setPhysicalLocations)
- {
- PhysicalLocationsList = args.PhysicalLocations;
- }
- return args;
- }
- /// <summary>
- /// Compare our current children (presumably just read from the repo) with the current state of the file system and adjust for any changes
- /// ***Currently does not contain logic to maintain items that are unavailable in the file system***.
- /// </summary>
- /// <param name="progress">The progress.</param>
- /// <param name="recursive">if set to <c>true</c> [recursive].</param>
- /// <param name="refreshChildMetadata">if set to <c>true</c> [refresh child metadata].</param>
- /// <param name="allowRemoveRoot">remove item even this folder is root.</param>
- /// <param name="refreshOptions">The refresh options.</param>
- /// <param name="directoryService">The directory service.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- protected override Task ValidateChildrenInternal(IProgress<double> progress, bool recursive, bool refreshChildMetadata, bool allowRemoveRoot, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService, CancellationToken cancellationToken)
- {
- return Task.CompletedTask;
- }
- public IEnumerable<BaseItem> GetActualChildren()
- {
- return GetPhysicalFolders(true).SelectMany(c => c.Children);
- }
- public IEnumerable<Folder> GetPhysicalFolders()
- {
- return GetPhysicalFolders(true);
- }
- private IEnumerable<Folder> GetPhysicalFolders(bool enableCache)
- {
- if (enableCache)
- {
- return PhysicalFolderIds.Select(i => LibraryManager.GetItemById(i)).OfType<Folder>();
- }
- var rootChildren = LibraryManager.RootFolder.Children
- .OfType<Folder>()
- .ToList();
- return PhysicalLocations
- .Where(i => !FileSystem.AreEqual(i, Path))
- .SelectMany(i => GetPhysicalParents(i, rootChildren))
- .DistinctBy(x => x.Id);
- }
- private IEnumerable<Folder> GetPhysicalParents(string path, List<Folder> rootChildren)
- {
- var result = rootChildren
- .Where(i => FileSystem.AreEqual(i.Path, path))
- .ToList();
- if (result.Count == 0)
- {
- if (LibraryManager.FindByPath(path, true) is Folder folder)
- {
- result.Add(folder);
- }
- }
- return result;
- }
- }
- }
|