using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Library { public class ItemController { #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 private async Task ResolveItem(ItemResolveEventArgs args) { // If that didn't pan out, try the slow ones foreach (IBaseItemResolver resolver in Kernel.Instance.EntityResolvers) { var item = await resolver.ResolvePath(args); if (item != null) { return item; } } return null; } /// /// Resolves a path into a BaseItem /// public async Task GetItem(Folder parent, string path) { return await GetItemInternal(parent, path, File.GetAttributes(path)); } /// /// Resolves a path into a BaseItem /// private async Task 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 = await ResolveItem(args); var folder = item as Folder; if (folder != null) { // If it's a folder look for child entities await AttachChildren(folder, fileSystemChildren); } return item; } /// /// Finds child BaseItems for a given Folder /// private async Task AttachChildren(Folder folder, IEnumerable> fileSystemChildren) { KeyValuePair[] fileSystemChildrenArray = fileSystemChildren.ToArray(); int count = fileSystemChildrenArray.Length; Task[] tasks = new Task[count]; for (int i = 0; i < count; i++) { var child = fileSystemChildrenArray[i]; tasks[i] = GetItemInternal(folder, child.Key, child.Value); } BaseItem[] baseItemChildren = await Task.WhenAll(tasks); // Sort them folder.Children = baseItemChildren.Where(i => i != null).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; } /// /// Gets a Person /// public async Task GetPerson(string name) { string path = Path.Combine(Kernel.Instance.ApplicationPaths.PeoplePath, name); return await GetImagesByNameItem(path, name); } /// /// Gets a Studio /// public async Task GetStudio(string name) { string path = Path.Combine(Kernel.Instance.ApplicationPaths.StudioPath, name); return await GetImagesByNameItem(path, name); } /// /// Gets a Genre /// public async Task GetGenre(string name) { string path = Path.Combine(Kernel.Instance.ApplicationPaths.GenrePath, name); return await GetImagesByNameItem(path, name); } /// /// Gets a Year /// public async Task GetYear(int value) { string path = Path.Combine(Kernel.Instance.ApplicationPaths.YearPath, value.ToString()); return await GetImagesByNameItem(path, value.ToString()); } private ConcurrentDictionary ImagesByNameItemCache = new ConcurrentDictionary(); /// /// Generically retrieves an IBN item /// private async Task GetImagesByNameItem(string path, string name) where T : BaseEntity, new() { string key = path.ToLower(); // Look for it in the cache, if it's not there, create it if (!ImagesByNameItemCache.ContainsKey(key)) { T obj = await CreateImagesByNameItem(path, name); ImagesByNameItemCache[key] = obj; return obj; } return ImagesByNameItemCache[key] as T; } /// /// Creates an IBN item based on a given path /// private async Task CreateImagesByNameItem(string path, string name) where T : BaseEntity, new() { T item = new T(); item.Name = name; item.Id = Kernel.GetMD5(path); if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } item.DateCreated = Directory.GetCreationTime(path); item.DateModified = Directory.GetLastAccessTime(path); if (File.Exists(Path.Combine(path, "folder.jpg"))) { item.PrimaryImagePath = Path.Combine(path, "folder.jpg"); } else if (File.Exists(Path.Combine(path, "folder.png"))) { item.PrimaryImagePath = Path.Combine(path, "folder.png"); } var b = false; if (b) { await Kernel.Instance.ExecuteMetadataProviders(item, null); } return item; } } }