using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; using CommonIO; using MediaBrowser.Controller.Providers; namespace MediaBrowser.Controller.Entities { /// /// Specialized folder that can have items added to it's children by external entities. /// Used for our RootFolder so plug-ins can add items. /// public class AggregateFolder : Folder { public AggregateFolder() { PhysicalLocationsList = new List(); } /// /// We don't support manual shortcuts /// protected override bool SupportsShortcutChildren { get { return false; } } [IgnoreDataMember] public override bool IsPhysicalRoot { get { return true; } } public override bool CanDelete() { return false; } /// /// The _virtual children /// private readonly ConcurrentBag _virtualChildren = new ConcurrentBag(); /// /// Gets the virtual children. /// /// The virtual children. public ConcurrentBag VirtualChildren { get { return _virtualChildren; } } [IgnoreDataMember] public override IEnumerable PhysicalLocations { get { return PhysicalLocationsList; } } public List PhysicalLocationsList { get; set; } protected override IEnumerable GetFileSystemChildren(IDirectoryService directoryService) { return CreateResolveArgs(directoryService, true).FileSystemChildren; } private List _childrenIds = null; private readonly object _childIdsLock = new object(); protected override IEnumerable LoadChildren() { lock (_childIdsLock) { if (_childrenIds == null || _childrenIds.Count == 0) { var list = base.LoadChildren().ToList(); _childrenIds = list.Select(i => i.Id).ToList(); return list; } return _childrenIds.Select(LibraryManager.GetItemById).Where(i => i != null).ToList(); } } private void ClearCache() { lock (_childIdsLock) { _childrenIds = null; } } private bool _requiresRefresh; public override bool RequiresRefresh() { var changed = base.RequiresRefresh() || _requiresRefresh; if (!changed) { var locations = PhysicalLocations.ToList(); var newLocations = CreateResolveArgs(new DirectoryService(Logger, FileSystem), false).PhysicalLocations.ToList(); if (!locations.SequenceEqual(newLocations)) { changed = true; } } return changed; } public override bool BeforeMetadataRefresh() { ClearCache(); var changed = base.BeforeMetadataRefresh() || _requiresRefresh; _requiresRefresh = false; return changed; } private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService, bool setPhysicalLocations) { ClearCache(); var path = ContainingFolderPath; var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService) { FileInfo = FileSystem.GetDirectoryInfo(path), Path = path, Parent = Parent }; // Gather child folder and files if (args.IsDirectory) { var isPhysicalRoot = args.IsPhysicalRoot; // When resolving the root, we need it's grandchildren (children of user views) var flattenFolderDepth = isPhysicalRoot ? 2 : 0; var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(directoryService, args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf); // Need to remove subpaths that may have been resolved from shortcuts // Example: if \\server\movies exists, then strip out \\server\movies\action if (isPhysicalRoot) { var paths = LibraryManager.NormalizeRootPathList(fileSystemDictionary.Values); fileSystemDictionary = paths.ToDictionary(i => i.FullName); } args.FileSystemDictionary = fileSystemDictionary; } _requiresRefresh = _requiresRefresh || !args.PhysicalLocations.SequenceEqual(PhysicalLocations); if (setPhysicalLocations) { PhysicalLocationsList = args.PhysicalLocations.ToList(); } return args; } protected override IEnumerable GetNonCachedChildren(IDirectoryService directoryService) { return base.GetNonCachedChildren(directoryService).Concat(_virtualChildren); } protected override async Task ValidateChildrenInternal(IProgress progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService) { ClearCache(); await base.ValidateChildrenInternal(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService) .ConfigureAwait(false); ClearCache(); } /// /// Adds the virtual child. /// /// The child. /// public void AddVirtualChild(BaseItem child) { if (child == null) { throw new ArgumentNullException(); } _virtualChildren.Add(child); } /// /// Finds the virtual child. /// /// The id. /// BaseItem. /// id public BaseItem FindVirtualChild(Guid id) { if (id == Guid.Empty) { throw new ArgumentNullException("id"); } return _virtualChildren.FirstOrDefault(i => i.Id == id); } } }