using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using MediaBrowser.Common.Events; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Library { public class ItemController { private List Resolvers = new List(); /// /// Registers a new BaseItem resolver. /// public void AddResovler() where TBaseItemType : BaseItem, new() where TResolverType : BaseItemResolver, new() { Resolvers.Insert(0, new TResolverType()); } #region PreBeginResolvePath Event /// /// Fires when a path is about to be resolved, but before child folders and files /// have been collected from the file system. /// This gives listeners a chance to cancel the operation and cause the path to be ignored. /// public event EventHandler PreBeginResolvePath; private bool OnPreBeginResolvePath(Folder parent, string path, FileAttributes attributes) { PreBeginResolveEventArgs args = new PreBeginResolveEventArgs() { Path = path, Parent = parent, FileAttributes = attributes, Cancel = false }; if (PreBeginResolvePath != null) { PreBeginResolvePath(this, args); } return !args.Cancel; } #endregion #region BeginResolvePath Event /// /// Fires when a path is about to be resolved, but after child folders and files /// have been collected from the file system. /// This gives listeners a chance to cancel the operation and cause the path to be ignored. /// public event EventHandler BeginResolvePath; private bool OnBeginResolvePath(ItemResolveEventArgs args) { if (BeginResolvePath != null) { BeginResolvePath(this, args); } return !args.Cancel; } #endregion #region Item Events /// /// Called when an item is being created. /// This should be used to fill item values, such as metadata /// public event EventHandler> ItemCreating; /// /// Called when an item has been created. /// This should be used to process or modify item values. /// public event EventHandler> ItemCreated; #endregion /// /// Called when an item has been created /// private void OnItemCreated(BaseItem item, Folder parent) { GenericItemEventArgs args = new GenericItemEventArgs { Item = item }; if (ItemCreating != null) { ItemCreating(this, args); } if (ItemCreated != null) { ItemCreated(this, args); } } private void FireCreateEventsRecursive(Folder folder, Folder parent) { OnItemCreated(folder, parent); int count = folder.Children.Length; Parallel.For(0, count, i => { BaseItem item = folder.Children[i]; Folder childFolder = item as Folder; if (childFolder != null) { FireCreateEventsRecursive(childFolder, folder); } else { OnItemCreated(item, folder); } }); } private BaseItem ResolveItem(ItemResolveEventArgs args) { // If that didn't pan out, try the slow ones foreach (IBaseItemResolver resolver in Resolvers) { var item = resolver.ResolvePath(args); if (item != null) { return item; } } return null; } /// /// Resolves a path into a BaseItem /// public BaseItem GetItem(string path) { return GetItem(null, path); } /// /// Resolves a path into a BaseItem /// public BaseItem GetItem(Folder parent, string path) { BaseItem item = GetItemInternal(parent, path, File.GetAttributes(path)); if (item != null) { var folder = item as Folder; if (folder != null) { FireCreateEventsRecursive(folder, parent); } else { OnItemCreated(item, parent); } } return item; } /// /// Resolves a path into a BaseItem /// private BaseItem GetItemInternal(Folder parent, string path, FileAttributes attributes) { if (!OnPreBeginResolvePath(parent, path, attributes)) { return null; } IEnumerable> fileSystemChildren; // Gather child folder and files if (attributes.HasFlag(FileAttributes.Directory)) { fileSystemChildren = Directory.GetFileSystemEntries(path, "*", SearchOption.TopDirectoryOnly).Select(f => new KeyValuePair(f, File.GetAttributes(f))); bool isVirtualFolder = parent != null && parent.IsRoot; fileSystemChildren = FilterChildFileSystemEntries(fileSystemChildren, isVirtualFolder); } else { fileSystemChildren = new KeyValuePair[] { }; } ItemResolveEventArgs args = new ItemResolveEventArgs() { Path = path, FileAttributes = attributes, FileSystemChildren = fileSystemChildren, Parent = parent, Cancel = false }; // Fire BeginResolvePath to see if anyone wants to cancel this operation if (!OnBeginResolvePath(args)) { return null; } BaseItem item = ResolveItem(args); var folder = item as Folder; if (folder != null) { // If it's a folder look for child entities AttachChildren(folder, fileSystemChildren); } return item; } /// /// Finds child BaseItems for a given Folder /// private void AttachChildren(Folder folder, IEnumerable> fileSystemChildren) { List baseItemChildren = new List(); int count = fileSystemChildren.Count(); // Resolve the child folder paths into entities Parallel.For(0, count, i => { KeyValuePair child = fileSystemChildren.ElementAt(i); BaseItem item = GetItemInternal(folder, child.Key, child.Value); if (item != null) { lock (baseItemChildren) { baseItemChildren.Add(item); } } }); // Sort them folder.Children = baseItemChildren.OrderBy(f => { return string.IsNullOrEmpty(f.SortName) ? f.Name : f.SortName; }).ToArray(); } /// /// Transforms shortcuts into their actual paths /// private List> FilterChildFileSystemEntries(IEnumerable> fileSystemChildren, bool flattenShortcuts) { List> returnFiles = new List>(); // Loop through each file foreach (KeyValuePair file in fileSystemChildren) { // Folders if (file.Value.HasFlag(FileAttributes.Directory)) { returnFiles.Add(file); } // If it's a shortcut, resolve it else if (Shortcut.IsShortcut(file.Key)) { string newPath = Shortcut.ResolveShortcut(file.Key); FileAttributes newPathAttributes = File.GetAttributes(newPath); // Find out if the shortcut is pointing to a directory or file if (newPathAttributes.HasFlag(FileAttributes.Directory)) { // If we're flattening then get the shortcut's children if (flattenShortcuts) { IEnumerable> newChildren = Directory.GetFileSystemEntries(newPath, "*", SearchOption.TopDirectoryOnly).Select(f => new KeyValuePair(f, File.GetAttributes(f))); returnFiles.AddRange(FilterChildFileSystemEntries(newChildren, false)); } else { returnFiles.Add(new KeyValuePair(newPath, newPathAttributes)); } } else { returnFiles.Add(new KeyValuePair(newPath, newPathAttributes)); } } else { returnFiles.Add(file); } } return returnFiles; } public Person GetPerson(string name) { // not yet implemented return null; } public Studio GetStudio(string name) { // not yet implemented return null; } public Genre GetGenre(string name) { // not yet implemented return null; } public Year GetYear(int value) { // not yet implemented return null; } } }