From b50f78e5da6f3fdfc59e577ca61b88771da7d211 Mon Sep 17 00:00:00 2001 From: LukePulverenti Luke Pulverenti luke pulverenti Date: Thu, 12 Jul 2012 02:55:27 -0400 Subject: Initial check-in --- MediaBrowser.Controller/Library/ItemController.cs | 326 ++++++++++++++++++++++ 1 file changed, 326 insertions(+) create mode 100644 MediaBrowser.Controller/Library/ItemController.cs (limited to 'MediaBrowser.Controller/Library/ItemController.cs') diff --git a/MediaBrowser.Controller/Library/ItemController.cs b/MediaBrowser.Controller/Library/ItemController.cs new file mode 100644 index 000000000..422790c69 --- /dev/null +++ b/MediaBrowser.Controller/Library/ItemController.cs @@ -0,0 +1,326 @@ +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()); + } + + /// + /// Registers a new BaseItem resolver. + /// + public void RemoveResovler() + where TBaseItemType : BaseItem, new() + where TResolverType : BaseItemResolver, new() + { + IBaseItemResolver resolver = Resolvers.First(r => r.GetType() == typeof(TResolverType)); + + Resolvers.Remove(resolver); + } + + #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; + } + } +} -- cgit v1.2.3