using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; namespace MediaBrowser.Controller.Entities { /// /// 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 /// public class CollectionFolder : Folder, ICollectionFolder { public static IXmlSerializer XmlSerializer { get; set; } public CollectionFolder() { PhysicalLocationsList = new List(); PhysicalFolderIds = new List(); } [IgnoreDataMember] protected override bool SupportsShortcutChildren { get { return true; } } [IgnoreDataMember] public override bool SupportsPlayedStatus { get { return false; } } public override bool CanDelete() { return false; } public string CollectionType { get; set; } private static readonly Dictionary LibraryOptions = new Dictionary(); public LibraryOptions GetLibraryOptions() { return GetLibraryOptions(Path); } private static LibraryOptions LoadLibraryOptions(string path) { try { var result = XmlSerializer.DeserializeFromFile(typeof(LibraryOptions), GetLibraryOptionsPath(path)) as LibraryOptions; if (result == null) { return new LibraryOptions(); } return result; } catch (FileNotFoundException) { return new LibraryOptions(); } catch (IOException) { return new LibraryOptions(); } catch (Exception ex) { Logger.ErrorException("Error loading library options", ex); 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) { lock (LibraryOptions) { LibraryOptions options; if (!LibraryOptions.TryGetValue(path, out options)) { options = LoadLibraryOptions(path); LibraryOptions[path] = options; } return options; } } public static void SaveLibraryOptions(string path, LibraryOptions options) { lock (LibraryOptions) { LibraryOptions[path] = options; XmlSerializer.SerializeToFile(options, GetLibraryOptionsPath(path)); } } /// /// Allow different display preferences for each collection folder /// /// The display prefs id. [IgnoreDataMember] public override Guid DisplayPreferencesId { get { return Id; } } [IgnoreDataMember] public override IEnumerable PhysicalLocations { get { return PhysicalLocationsList; } } public override bool IsSaveLocalMetadataEnabled() { return true; } public List PhysicalLocationsList { get; set; } public List PhysicalFolderIds { get; set; } protected override IEnumerable GetFileSystemChildren(IDirectoryService directoryService) { return CreateResolveArgs(directoryService, true).FileSystemChildren; } 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; } } if (!changed) { var folderIds = PhysicalFolderIds.ToList(); var newFolderIds = GetPhysicalFolders(false).Select(i => i.Id).ToList(); if (!folderIds.SequenceEqual(newFolderIds)) { changed = true; } } return changed; } public override bool BeforeMetadataRefresh() { var changed = base.BeforeMetadataRefresh() || _requiresRefresh; _requiresRefresh = false; return changed; } protected override bool RefreshLinkedChildren(IEnumerable 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; var folderIds = PhysicalFolderIds.ToList(); var newFolderIds = physicalFolders.Select(i => i.Id).ToList(); if (!folderIds.SequenceEqual(newFolderIds)) { changed = true; if (setFolders) { PhysicalFolderIds = newFolderIds.ToList(); } } return changed; } internal override bool IsValidFromResolver(BaseItem newItem) { var newCollectionFolder = newItem as CollectionFolder; if (newCollectionFolder != null) { if (!string.Equals(CollectionType, newCollectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase)) { return false; } } return base.IsValidFromResolver(newItem); } private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService, bool setPhysicalLocations) { var path = ContainingFolderPath; var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService) { FileInfo = FileSystem.GetDirectoryInfo(path), Path = path, Parent = Parent, CollectionType = CollectionType }; // 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; } /// /// 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*** /// /// The progress. /// The cancellation token. /// if set to true [recursive]. /// if set to true [refresh child metadata]. /// The refresh options. /// The directory service. /// Task. protected override Task ValidateChildrenInternal(IProgress progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService) { return Task.FromResult(true); } /// /// Our children are actually just references to the ones in the physical root... /// /// The actual children. [IgnoreDataMember] public override IEnumerable Children { get { return GetActualChildren(); } } public IEnumerable GetActualChildren() { return GetPhysicalFolders(true).SelectMany(c => c.Children); } private IEnumerable GetPhysicalFolders(bool enableCache) { if (enableCache) { return PhysicalFolderIds.Select(i => LibraryManager.GetItemById(i)).OfType(); } var rootChildren = LibraryManager.RootFolder.Children .OfType() .ToList(); return PhysicalLocations.Where(i => !FileSystem.AreEqual(i, Path)).SelectMany(i => GetPhysicalParents(i, rootChildren)).DistinctBy(i => i.Id); } private IEnumerable GetPhysicalParents(string path, List rootChildren) { var result = rootChildren .Where(i => FileSystem.AreEqual(i.Path, path)) .ToList(); if (result.Count == 0) { var folder = LibraryManager.FindByPath(path, true) as Folder; if (folder != null) { result.Add(folder); } } return result; } [IgnoreDataMember] public override bool SupportsPeople { get { return false; } } } }