From 17106ea5c72511f5871178c7f1def629c20191ac Mon Sep 17 00:00:00 2001 From: ebr11 Eric Reed spam Date: Mon, 17 Sep 2012 11:12:43 -0400 Subject: [PATCH 1/5] Initial commit changing to on-demand child loading and validations --- MediaBrowser.Api/ApiService.cs | 2 +- MediaBrowser.Api/HttpHandlers/GenreHandler.cs | 2 +- .../HttpHandlers/GenresHandler.cs | 2 +- .../HttpHandlers/PersonHandler.cs | 2 +- .../HttpHandlers/StudioHandler.cs | 2 +- .../HttpHandlers/StudiosHandler.cs | 2 +- MediaBrowser.Api/HttpHandlers/YearHandler.cs | 2 +- MediaBrowser.Api/HttpHandlers/YearsHandler.cs | 2 +- .../Extensions/BaseExtensions.cs | 31 ++ .../MediaBrowser.Common.csproj | 1 + MediaBrowser.Controller/Entities/BaseItem.cs | 21 ++ MediaBrowser.Controller/Entities/Folder.cs | 300 ++++++++++++++++-- .../IO/DirectoryWatchers.cs | 14 +- .../IO/FileSystemHelper.cs | 86 +++++ MediaBrowser.Controller/IO/Shortcut.cs | 2 +- MediaBrowser.Controller/Kernel.cs | 66 ++-- .../Library/ChildrenChangedEventArgs.cs | 34 ++ .../Library/ItemController.cs | 226 +++++-------- .../Library/ItemResolveEventArgs.cs | 49 +-- .../MediaBrowser.Controller.csproj | 3 + .../Resolvers/BaseItemResolver.cs | 3 +- .../Resolvers/EntityResolutionHelper.cs | 68 ++++ .../Resolvers/FolderResolver.cs | 2 +- .../Resolvers/Movies/MovieResolver.cs | 23 +- 24 files changed, 699 insertions(+), 246 deletions(-) create mode 100644 MediaBrowser.Common/Extensions/BaseExtensions.cs create mode 100644 MediaBrowser.Controller/IO/FileSystemHelper.cs create mode 100644 MediaBrowser.Controller/Library/ChildrenChangedEventArgs.cs create mode 100644 MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs diff --git a/MediaBrowser.Api/ApiService.cs b/MediaBrowser.Api/ApiService.cs index 88af0f8134..ee76b5de04 100644 --- a/MediaBrowser.Api/ApiService.cs +++ b/MediaBrowser.Api/ApiService.cs @@ -289,7 +289,7 @@ namespace MediaBrowser.Api if (folder != null) { - IEnumerable children = folder.GetParentalAllowedChildren(user); + IEnumerable children = folder.GetChildren(user); dto.Children = await Task.WhenAll(children.Select(c => GetDTOBaseItem(c, user, false, false))).ConfigureAwait(false); } diff --git a/MediaBrowser.Api/HttpHandlers/GenreHandler.cs b/MediaBrowser.Api/HttpHandlers/GenreHandler.cs index cd4d8b9438..424d344760 100644 --- a/MediaBrowser.Api/HttpHandlers/GenreHandler.cs +++ b/MediaBrowser.Api/HttpHandlers/GenreHandler.cs @@ -40,7 +40,7 @@ namespace MediaBrowser.Api.HttpHandlers int count = 0; // Get all the allowed recursive children - IEnumerable allItems = parent.GetParentalAllowedRecursiveChildren(user); + IEnumerable allItems = parent.GetRecursiveChildren(user); foreach (var item in allItems) { diff --git a/MediaBrowser.Api/HttpHandlers/GenresHandler.cs b/MediaBrowser.Api/HttpHandlers/GenresHandler.cs index 0ff48761ca..341f8a483f 100644 --- a/MediaBrowser.Api/HttpHandlers/GenresHandler.cs +++ b/MediaBrowser.Api/HttpHandlers/GenresHandler.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Api.HttpHandlers Dictionary data = new Dictionary(); // Get all the allowed recursive children - IEnumerable allItems = parent.GetParentalAllowedRecursiveChildren(user); + IEnumerable allItems = parent.GetRecursiveChildren(user); foreach (var item in allItems) { diff --git a/MediaBrowser.Api/HttpHandlers/PersonHandler.cs b/MediaBrowser.Api/HttpHandlers/PersonHandler.cs index b91ccb9275..1663cffb1b 100644 --- a/MediaBrowser.Api/HttpHandlers/PersonHandler.cs +++ b/MediaBrowser.Api/HttpHandlers/PersonHandler.cs @@ -38,7 +38,7 @@ namespace MediaBrowser.Api.HttpHandlers int count = 0; // Get all the allowed recursive children - IEnumerable allItems = parent.GetParentalAllowedRecursiveChildren(user); + IEnumerable allItems = parent.GetRecursiveChildren(user); foreach (var item in allItems) { diff --git a/MediaBrowser.Api/HttpHandlers/StudioHandler.cs b/MediaBrowser.Api/HttpHandlers/StudioHandler.cs index cf0b7d38d0..79f0578102 100644 --- a/MediaBrowser.Api/HttpHandlers/StudioHandler.cs +++ b/MediaBrowser.Api/HttpHandlers/StudioHandler.cs @@ -40,7 +40,7 @@ namespace MediaBrowser.Api.HttpHandlers int count = 0; // Get all the allowed recursive children - IEnumerable allItems = parent.GetParentalAllowedRecursiveChildren(user); + IEnumerable allItems = parent.GetRecursiveChildren(user); foreach (var item in allItems) { diff --git a/MediaBrowser.Api/HttpHandlers/StudiosHandler.cs b/MediaBrowser.Api/HttpHandlers/StudiosHandler.cs index f80bed0896..96d6e40a37 100644 --- a/MediaBrowser.Api/HttpHandlers/StudiosHandler.cs +++ b/MediaBrowser.Api/HttpHandlers/StudiosHandler.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Api.HttpHandlers Dictionary data = new Dictionary(); // Get all the allowed recursive children - IEnumerable allItems = parent.GetParentalAllowedRecursiveChildren(user); + IEnumerable allItems = parent.GetRecursiveChildren(user); foreach (var item in allItems) { diff --git a/MediaBrowser.Api/HttpHandlers/YearHandler.cs b/MediaBrowser.Api/HttpHandlers/YearHandler.cs index 1bdedf0c63..8390fcbc2c 100644 --- a/MediaBrowser.Api/HttpHandlers/YearHandler.cs +++ b/MediaBrowser.Api/HttpHandlers/YearHandler.cs @@ -38,7 +38,7 @@ namespace MediaBrowser.Api.HttpHandlers int count = 0; // Get all the allowed recursive children - IEnumerable allItems = parent.GetParentalAllowedRecursiveChildren(user); + IEnumerable allItems = parent.GetRecursiveChildren(user); foreach (var item in allItems) { diff --git a/MediaBrowser.Api/HttpHandlers/YearsHandler.cs b/MediaBrowser.Api/HttpHandlers/YearsHandler.cs index c33464c4cc..496821fb89 100644 --- a/MediaBrowser.Api/HttpHandlers/YearsHandler.cs +++ b/MediaBrowser.Api/HttpHandlers/YearsHandler.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Api.HttpHandlers Dictionary data = new Dictionary(); // Get all the allowed recursive children - IEnumerable allItems = parent.GetParentalAllowedRecursiveChildren(user); + IEnumerable allItems = parent.GetRecursiveChildren(user); foreach (var item in allItems) { diff --git a/MediaBrowser.Common/Extensions/BaseExtensions.cs b/MediaBrowser.Common/Extensions/BaseExtensions.cs new file mode 100644 index 0000000000..89cfbecad2 --- /dev/null +++ b/MediaBrowser.Common/Extensions/BaseExtensions.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Security.Cryptography; + +namespace MediaBrowser.Common.Extensions +{ + public static class BaseExtensions + { + static MD5CryptoServiceProvider md5Provider = new MD5CryptoServiceProvider(); + + public static Guid GetMD5(this string str) + { + lock (md5Provider) + { + return new Guid(md5Provider.ComputeHash(Encoding.Unicode.GetBytes(str))); + } + } + + public static bool ContainsStartsWith(this List lst, string value) + { + foreach (var str in lst) + { + if (str.StartsWith(value, StringComparison.OrdinalIgnoreCase)) return true; + } + return false; + } + } +} diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 9b801d9498..df404b9ea6 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -81,6 +81,7 @@ + diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 68a192065f..d135acf9cf 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -134,6 +134,27 @@ namespace MediaBrowser.Controller.Entities } } + /// + /// Determine if we have changed vs the passed in copy + /// + /// + /// + public virtual bool IsChanged(BaseItem original) + { + bool changed = original.DateModified != this.DateModified; + changed |= original.DateCreated != this.DateCreated; + return changed; + } + + /// + /// Refresh metadata on us by execution our provider chain + /// + /// true if a provider reports we changed + public bool RefreshMetadata() + { + return false; + } + /// /// Determines if the item is considered new based on user settings /// diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index a9c92c1fa4..0858500f0d 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -1,5 +1,10 @@ using MediaBrowser.Model.Entities; +using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Library; +using MediaBrowser.Common.Logging; +using MediaBrowser.Controller.Resolvers; using System; +using System.Threading.Tasks; using System.Collections.Generic; using System.Linq; @@ -7,6 +12,24 @@ namespace MediaBrowser.Controller.Entities { public class Folder : BaseItem { + #region Events + /// + /// Fires whenever a validation routine updates our children. The added and removed children are properties of the args. + /// *** Will fire asynchronously. *** + /// + public event EventHandler ChildrenChanged; + protected void OnChildrenChanged(ChildrenChangedEventArgs args) + { + if (ChildrenChanged != null) + { + Task.Run( () => ChildrenChanged(this, args)); + } + } + + #endregion + + public IEnumerable PhysicalLocations { get; set; } + public override bool IsFolder { get @@ -24,23 +47,262 @@ namespace MediaBrowser.Controller.Entities return Parent != null && Parent.IsRoot; } } + protected object childLock = new object(); + protected List children; + protected virtual List ActualChildren + { + get + { + if (children == null) + { + LoadChildren(); + } + return children; + } - public IEnumerable Children { get; set; } + set + { + children = value; + } + } + + /// + /// thread-safe access to the actual children of this folder - without regard to user + /// + public IEnumerable Children + { + get + { + lock (childLock) + return ActualChildren.ToList(); + } + } + + /// + /// thread-safe access to all recursive children of this folder - without regard to user + /// + public IEnumerable RecursiveChildren + { + get + { + foreach (var item in Children) + { + yield return item; + + var subFolder = item as Folder; + + if (subFolder != null) + { + foreach (var subitem in subFolder.RecursiveChildren) + { + yield return subitem; + } + } + } + } + } + + + /// + /// Loads and validates our children + /// + protected virtual void LoadChildren() + { + //first - load our children from the repo + lock (childLock) + children = GetCachedChildren(); + + //then kick off a validation against the actual file system + Task.Run(() => ValidateChildren()); + } + + protected bool ChildrenValidating = false; + + /// + /// 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*** + /// + /// + protected async virtual void ValidateChildren() + { + if (ChildrenValidating) return; //only ever want one of these going at once and don't want them to fire off in sequence so don't use lock + ChildrenValidating = true; + bool changed = false; //this will save us a little time at the end if nothing changes + var changedArgs = new ChildrenChangedEventArgs(this); + //get the current valid children from filesystem (or wherever) + var nonCachedChildren = await GetNonCachedChildren(); + if (nonCachedChildren == null) return; //nothing to validate + //build a dictionary of the current children we have now by Id so we can compare quickly and easily + Dictionary currentChildren; + lock (childLock) + currentChildren = ActualChildren.ToDictionary(i => i.Id); + + //create a list for our validated children + var validChildren = new List(); + //now traverse the valid children and find any changed or new items + foreach (var child in nonCachedChildren) + { + BaseItem currentChild; + currentChildren.TryGetValue(child.Id, out currentChild); + if (currentChild == null) + { + //brand new item - needs to be added + changed = true; + changedArgs.ItemsAdded.Add(child); + //Logger.LogInfo("New Item Added to Library: ("+child.GetType().Name+")"+ child.Name + "(" + child.Path + ")"); + //refresh it + child.RefreshMetadata(); + //save it in repo... + + //and add it to our valid children + validChildren.Add(child); + //fire an added event...? + //if it is a folder we need to validate its children as well + Folder folder = child as Folder; + if (folder != null) + { + folder.ValidateChildren(); + //probably need to refresh too... + } + } + else + { + //existing item - check if it has changed + if (currentChild.IsChanged(child)) + { + changed = true; + currentChild.RefreshMetadata(); + //save it in repo... + validChildren.Add(currentChild); + } + else + { + //current child that didn't change - just put it in the valid children + validChildren.Add(currentChild); + } + } + } + + //that's all the new and changed ones - now see if there are any that are missing + changedArgs.ItemsRemoved = currentChildren.Values.Except(validChildren); + changed |= changedArgs.ItemsRemoved != null; + + //now, if anything changed - replace our children + if (changed) + { + lock (childLock) + ActualChildren = validChildren; + //and save children in repo... + + //and fire event + this.OnChildrenChanged(changedArgs); + } + ChildrenValidating = false; + + } + + /// + /// Get the children of this folder from the actual file system + /// + /// + protected async virtual Task> GetNonCachedChildren() + { + ItemResolveEventArgs args = new ItemResolveEventArgs() + { + FileInfo = FileData.GetFileData(this.Path), + Parent = this.Parent, + Cancel = false, + Path = this.Path + }; + + // Gather child folder and files + if (args.IsDirectory) + { + args.FileSystemChildren = FileData.GetFileSystemEntries(this.Path, "*").ToArray(); + + bool isVirtualFolder = Parent != null && Parent.IsRoot; + args = FileSystemHelper.FilterChildFileSystemEntries(args, isVirtualFolder); + } + else + { + Logger.LogError("Folder has a path that is not a directory: " + this.Path); + return null; + } + + if (!EntityResolutionHelper.ShouldResolvePathContents(args)) + { + return null; + } + return (await Task.WhenAll(GetChildren(args.FileSystemChildren)).ConfigureAwait(false)) + .Where(i => i != null).OrderBy(f => + { + return string.IsNullOrEmpty(f.SortName) ? f.Name : f.SortName; + + }); + + } + + /// + /// Resolves a path into a BaseItem + /// + protected async Task GetChild(string path, WIN32_FIND_DATA? fileInfo = null) + { + ItemResolveEventArgs args = new ItemResolveEventArgs() + { + FileInfo = fileInfo ?? FileData.GetFileData(path), + Parent = this, + Cancel = false, + Path = path + }; + + args.FileSystemChildren = FileData.GetFileSystemEntries(path, "*").ToArray(); + args = FileSystemHelper.FilterChildFileSystemEntries(args, false); + + return Kernel.Instance.ResolveItem(args); + + } + + /// + /// Finds child BaseItems for a given Folder + /// + protected Task[] GetChildren(WIN32_FIND_DATA[] fileSystemChildren) + { + Task[] tasks = new Task[fileSystemChildren.Length]; + + for (int i = 0; i < fileSystemChildren.Length; i++) + { + var child = fileSystemChildren[i]; + + tasks[i] = GetChild(child.Path, child); + } + + return tasks; + } + + + /// + /// Get our children from the repo - stubbed for now + /// + /// + protected virtual List GetCachedChildren() + { + return new List(); + } /// /// Gets allowed children of an item /// - public IEnumerable GetParentalAllowedChildren(User user) + public IEnumerable GetChildren(User user) { - return Children.Where(c => c.IsParentalAllowed(user)); + return ActualChildren.Where(c => c.IsParentalAllowed(user)); } /// /// Gets allowed recursive children of an item /// - public IEnumerable GetParentalAllowedRecursiveChildren(User user) + public IEnumerable GetRecursiveChildren(User user) { - foreach (var item in GetParentalAllowedChildren(user)) + foreach (var item in GetChildren(user)) { yield return item; @@ -48,7 +310,7 @@ namespace MediaBrowser.Controller.Entities if (subFolder != null) { - foreach (var subitem in subFolder.GetParentalAllowedRecursiveChildren(user)) + foreach (var subitem in subFolder.GetRecursiveChildren(user)) { yield return subitem; } @@ -63,7 +325,7 @@ namespace MediaBrowser.Controller.Entities { ItemSpecialCounts counts = new ItemSpecialCounts(); - IEnumerable recursiveChildren = GetParentalAllowedRecursiveChildren(user); + IEnumerable recursiveChildren = GetRecursiveChildren(user); counts.RecentlyAddedItemCount = GetRecentlyAddedItems(recursiveChildren, user).Count(); counts.RecentlyAddedUnPlayedItemCount = GetRecentlyAddedUnplayedItems(recursiveChildren, user).Count(); @@ -78,7 +340,7 @@ namespace MediaBrowser.Controller.Entities /// public IEnumerable GetItemsWithGenre(string genre, User user) { - return GetParentalAllowedRecursiveChildren(user).Where(f => f.Genres != null && f.Genres.Any(s => s.Equals(genre, StringComparison.OrdinalIgnoreCase))); + return GetRecursiveChildren(user).Where(f => f.Genres != null && f.Genres.Any(s => s.Equals(genre, StringComparison.OrdinalIgnoreCase))); } /// @@ -86,7 +348,7 @@ namespace MediaBrowser.Controller.Entities /// public IEnumerable GetItemsWithYear(int year, User user) { - return GetParentalAllowedRecursiveChildren(user).Where(f => f.ProductionYear.HasValue && f.ProductionYear == year); + return GetRecursiveChildren(user).Where(f => f.ProductionYear.HasValue && f.ProductionYear == year); } /// @@ -94,7 +356,7 @@ namespace MediaBrowser.Controller.Entities /// public IEnumerable GetItemsWithStudio(string studio, User user) { - return GetParentalAllowedRecursiveChildren(user).Where(f => f.Studios != null && f.Studios.Any(s => s.Equals(studio, StringComparison.OrdinalIgnoreCase))); + return GetRecursiveChildren(user).Where(f => f.Studios != null && f.Studios.Any(s => s.Equals(studio, StringComparison.OrdinalIgnoreCase))); } /// @@ -102,7 +364,7 @@ namespace MediaBrowser.Controller.Entities /// public IEnumerable GetFavoriteItems(User user) { - return GetParentalAllowedRecursiveChildren(user).Where(c => + return GetRecursiveChildren(user).Where(c => { UserItemData data = c.GetUserData(user, false); @@ -120,7 +382,7 @@ namespace MediaBrowser.Controller.Entities /// public IEnumerable GetItemsWithPerson(string person, User user) { - return GetParentalAllowedRecursiveChildren(user).Where(c => + return GetRecursiveChildren(user).Where(c => { if (c.People != null) { @@ -137,7 +399,7 @@ namespace MediaBrowser.Controller.Entities /// Specify this to limit results to a specific PersonType public IEnumerable GetItemsWithPerson(string person, string personType, User user) { - return GetParentalAllowedRecursiveChildren(user).Where(c => + return GetRecursiveChildren(user).Where(c => { if (c.People != null) { @@ -153,7 +415,7 @@ namespace MediaBrowser.Controller.Entities /// public IEnumerable GetRecentlyAddedItems(User user) { - return GetRecentlyAddedItems(GetParentalAllowedRecursiveChildren(user), user); + return GetRecentlyAddedItems(GetRecursiveChildren(user), user); } /// @@ -161,7 +423,7 @@ namespace MediaBrowser.Controller.Entities /// public IEnumerable GetRecentlyAddedUnplayedItems(User user) { - return GetRecentlyAddedUnplayedItems(GetParentalAllowedRecursiveChildren(user), user); + return GetRecentlyAddedUnplayedItems(GetRecursiveChildren(user), user); } /// @@ -169,7 +431,7 @@ namespace MediaBrowser.Controller.Entities /// public IEnumerable GetInProgressItems(User user) { - return GetInProgressItems(GetParentalAllowedRecursiveChildren(user), user); + return GetInProgressItems(GetRecursiveChildren(user), user); } /// @@ -257,7 +519,7 @@ namespace MediaBrowser.Controller.Entities base.SetPlayedStatus(user, wasPlayed); // Now sweep through recursively and update status - foreach (BaseItem item in GetParentalAllowedChildren(user)) + foreach (BaseItem item in GetChildren(user)) { item.SetPlayedStatus(user, wasPlayed); } @@ -275,7 +537,7 @@ namespace MediaBrowser.Controller.Entities return result; } - foreach (BaseItem item in Children) + foreach (BaseItem item in ActualChildren) { result = item.FindItemById(id); @@ -298,7 +560,7 @@ namespace MediaBrowser.Controller.Entities return this; } - foreach (BaseItem item in Children) + foreach (BaseItem item in ActualChildren) { var folder = item as Folder; diff --git a/MediaBrowser.Controller/IO/DirectoryWatchers.cs b/MediaBrowser.Controller/IO/DirectoryWatchers.cs index 837c02cb90..8b1b8f6a94 100644 --- a/MediaBrowser.Controller/IO/DirectoryWatchers.cs +++ b/MediaBrowser.Controller/IO/DirectoryWatchers.cs @@ -1,4 +1,6 @@ using MediaBrowser.Controller.Entities; +using MediaBrowser.Common.Logging; +using MediaBrowser.Common.Extensions; using System; using System.Collections.Generic; using System.IO; @@ -26,22 +28,18 @@ namespace MediaBrowser.Controller.IO foreach (Folder folder in rootFolder.Children.OfType()) { - foreach (Folder subFolder in folder.Children.OfType()) + foreach (string path in folder.PhysicalLocations) { - if (Path.IsPathRooted(subFolder.Path)) + if (Path.IsPathRooted(path) && !pathsToWatch.ContainsStartsWith(path)) { - string parent = Path.GetDirectoryName(subFolder.Path); - - if (!pathsToWatch.Contains(parent)) - { - pathsToWatch.Add(parent); - } + pathsToWatch.Add(path); } } } foreach (string path in pathsToWatch) { + Logger.LogInfo("Watching directory " + path + " for changes."); FileSystemWatcher watcher = new FileSystemWatcher(path, "*"); watcher.IncludeSubdirectories = true; diff --git a/MediaBrowser.Controller/IO/FileSystemHelper.cs b/MediaBrowser.Controller/IO/FileSystemHelper.cs new file mode 100644 index 0000000000..0a80a4d97c --- /dev/null +++ b/MediaBrowser.Controller/IO/FileSystemHelper.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Controller.Library; + +namespace MediaBrowser.Controller.IO +{ + public static class FileSystemHelper + { + /// + /// Transforms shortcuts into their actual paths and filters out items that should be ignored + /// + public static ItemResolveEventArgs FilterChildFileSystemEntries(ItemResolveEventArgs args, bool flattenShortcuts) + { + + List returnChildren = new List(); + List resolvedShortcuts = new List(); + + foreach (var file in args.FileSystemChildren) + { + // If it's a shortcut, resolve it + if (Shortcut.IsShortcut(file.Path)) + { + string newPath = Shortcut.ResolveShortcut(file.Path); + WIN32_FIND_DATA newPathData = FileData.GetFileData(newPath); + + // Find out if the shortcut is pointing to a directory or file + if (newPathData.IsDirectory) + { + // add to our physical locations + args.AdditionalLocations.Add(newPath); + + // If we're flattening then get the shortcut's children + if (flattenShortcuts) + { + returnChildren.Add(file); + ItemResolveEventArgs newArgs = new ItemResolveEventArgs() + { + FileSystemChildren = FileData.GetFileSystemEntries(newPath, "*").ToArray() + }; + + resolvedShortcuts.AddRange(FilterChildFileSystemEntries(newArgs, false).FileSystemChildren); + } + else + { + returnChildren.Add(newPathData); + } + } + else + { + returnChildren.Add(newPathData); + } + } + else + { + //not a shortcut check to see if we should filter it out + if (EntityResolutionHelper.ShouldResolvePath(file)) + { + returnChildren.Add(file); + } + else + { + //filtered - see if it is one of our "indicator" folders and mark it now - no reason to search for it again + args.IsBDFolder |= file.cFileName.Equals("bdmv", StringComparison.OrdinalIgnoreCase); + args.IsDVDFolder |= file.cFileName.Equals("video_ts", StringComparison.OrdinalIgnoreCase); + } + } + } + + if (resolvedShortcuts.Count > 0) + { + resolvedShortcuts.InsertRange(0, returnChildren); + args.FileSystemChildren = resolvedShortcuts.ToArray(); + } + else + { + args.FileSystemChildren = returnChildren.ToArray(); + } + return args; + } + + } +} diff --git a/MediaBrowser.Controller/IO/Shortcut.cs b/MediaBrowser.Controller/IO/Shortcut.cs index 83e2c0eda3..92505e9b7b 100644 --- a/MediaBrowser.Controller/IO/Shortcut.cs +++ b/MediaBrowser.Controller/IO/Shortcut.cs @@ -179,7 +179,7 @@ namespace MediaBrowser.Controller.IO public static bool IsShortcut(string filename) { - return Path.GetExtension(filename).EndsWith("lnk", StringComparison.OrdinalIgnoreCase); + return filename != null ? Path.GetExtension(filename).EndsWith("lnk", StringComparison.OrdinalIgnoreCase) : false; } } } diff --git a/MediaBrowser.Controller/Kernel.cs b/MediaBrowser.Controller/Kernel.cs index 47a3773b34..13010ad8ec 100644 --- a/MediaBrowser.Controller/Kernel.cs +++ b/MediaBrowser.Controller/Kernel.cs @@ -10,6 +10,7 @@ using MediaBrowser.Controller.Weather; using MediaBrowser.Model.Authentication; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Progress; +using MediaBrowser.Common.Extensions; using System; using System.Collections.Generic; using System.ComponentModel.Composition; @@ -83,8 +84,6 @@ namespace MediaBrowser.Controller DirectoryWatchers = new DirectoryWatchers(); WeatherClient = new WeatherClient(); - ItemController.PreBeginResolvePath += ItemController_PreBeginResolvePath; - ItemController.BeginResolvePath += ItemController_BeginResolvePath; } public async override Task Init(IProgress progress) @@ -100,6 +99,13 @@ namespace MediaBrowser.Controller await ReloadRoot(allowInternetProviders: false).ConfigureAwait(false); progress.Report(new TaskProgress() { Description = "Loading Complete", PercentComplete = 100 }); + + //watch the root folder children for changes + RootFolder.ChildrenChanged += RootFolder_ChildrenChanged; + + System.Threading.Thread.Sleep(25000); + var allChildren = RootFolder.RecursiveChildren; + Logger.LogInfo(string.Format("Loading complete. Movies: {0} Episodes: {1}", allChildren.OfType().Count(), allChildren.OfType().Count())); } protected override void OnComposablePartsLoaded() @@ -114,46 +120,20 @@ namespace MediaBrowser.Controller MetadataProviders = MetadataProvidersEnumerable.OrderBy(e => e.Priority).ToArray(); } - /// - /// Fires when a path is about to be resolved, but before child folders and files - /// have been collected from the file system. - /// This gives us a chance to cancel it if needed, resulting in the path being ignored - /// - void ItemController_PreBeginResolvePath(object sender, PreBeginResolveEventArgs e) + public BaseItem ResolveItem(ItemResolveEventArgs args) { - // Ignore hidden files and folders - if (e.IsHidden || e.IsSystemFile) + // Try first priority resolvers + for (int i = 0; i < EntityResolvers.Length; i++) { - e.Cancel = true; - } + var item = EntityResolvers[i].ResolvePath(args); - // Ignore any folders named "trailers" - else if (Path.GetFileName(e.Path).Equals("trailers", StringComparison.OrdinalIgnoreCase)) - { - e.Cancel = true; - } - - // Don't try and resolve files within the season metadata folder - else if (Path.GetFileName(e.Path).Equals("metadata", StringComparison.OrdinalIgnoreCase) && e.IsDirectory) - { - if (e.Parent is Season || e.Parent is Series) + if (item != null) { - e.Cancel = true; + return item; } } - } - /// - /// Fires when a path is about to be resolved, but after child folders and files - /// This gives us a chance to cancel it if needed, resulting in the path being ignored - /// - void ItemController_BeginResolvePath(object sender, ItemResolveEventArgs e) - { - if (e.ContainsFile(".ignore")) - { - // Ignore any folders containing a file called .ignore - e.Cancel = true; - } + return null; } private void ReloadUsers() @@ -178,12 +158,11 @@ namespace MediaBrowser.Controller DirectoryWatchers.Start(); } - public static Guid GetMD5(string str) + void RootFolder_ChildrenChanged(object sender, ChildrenChangedEventArgs e) { - using (var provider = new MD5CryptoServiceProvider()) - { - return new Guid(provider.ComputeHash(Encoding.Unicode.GetBytes(str))); - } + //re-start the directory watchers + DirectoryWatchers.Stop(); + DirectoryWatchers.Start(); } /// @@ -222,7 +201,8 @@ namespace MediaBrowser.Controller } else { - result.Success = GetMD5((password ?? string.Empty)).ToString().Equals(user.Password); + password = password ?? string.Empty; + result.Success = password.GetMD5().ToString().Equals(user.Password); } // Update LastActivityDate and LastLoginDate, then save @@ -261,7 +241,7 @@ namespace MediaBrowser.Controller children.Insert(index, newItem); - item.Parent.Children = children.ToArray(); + //item.Parent.ActualChildren = children.ToArray(); } } @@ -297,7 +277,7 @@ namespace MediaBrowser.Controller user.Id = Guid.NewGuid(); user.LastLoginDate = DateTime.UtcNow.AddDays(-1); user.LastActivityDate = DateTime.UtcNow.AddHours(-3); - user.Password = GetMD5("1234").ToString(); + user.Password = ("1234").GetMD5().ToString(); list.Add(user); user = new User(); diff --git a/MediaBrowser.Controller/Library/ChildrenChangedEventArgs.cs b/MediaBrowser.Controller/Library/ChildrenChangedEventArgs.cs new file mode 100644 index 0000000000..462fcc6d69 --- /dev/null +++ b/MediaBrowser.Controller/Library/ChildrenChangedEventArgs.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; + +namespace MediaBrowser.Controller.Library +{ + public class ChildrenChangedEventArgs : EventArgs + { + public Folder Folder { get; set; } + public List ItemsAdded { get; set; } + public IEnumerable ItemsRemoved { get; set; } + + public ChildrenChangedEventArgs() + { + //initialize the list + ItemsAdded = new List(); + } + + /// + /// Create the args and set the folder property + /// + /// + public ChildrenChangedEventArgs(Folder folder) + { + //init the folder property + this.Folder = folder; + //init the list + ItemsAdded = new List(); + } + } +} diff --git a/MediaBrowser.Controller/Library/ItemController.cs b/MediaBrowser.Controller/Library/ItemController.cs index 9e0c94b793..7ef1c17a11 100644 --- a/MediaBrowser.Controller/Library/ItemController.cs +++ b/MediaBrowser.Controller/Library/ItemController.cs @@ -1,5 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Resolvers; +using MediaBrowser.Common.Extensions; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -11,57 +13,21 @@ 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(PreBeginResolveEventArgs args) - { - if (PreBeginResolvePath != null) - { - PreBeginResolvePath(this, args); - } + //private BaseItem ResolveItem(ItemResolveEventArgs args) + //{ + // // Try first priority resolvers + // for (int i = 0; i < Kernel.Instance.EntityResolvers.Length; i++) + // { + // var item = Kernel.Instance.EntityResolvers[i].ResolvePath(args); - return !args.Cancel; - } - #endregion + // if (item != null) + // { + // return item; + // } + // } - #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 BaseItem ResolveItem(ItemResolveEventArgs args) - { - // Try first priority resolvers - for (int i = 0; i < Kernel.Instance.EntityResolvers.Length; i++) - { - var item = Kernel.Instance.EntityResolvers[i].ResolvePath(args); - - if (item != null) - { - return item; - } - } - - return null; - } + // return null; + //} /// /// Resolves a path into a BaseItem @@ -76,128 +42,104 @@ namespace MediaBrowser.Controller.Library Path = path }; - if (!OnPreBeginResolvePath(args)) - { - return null; - } - - WIN32_FIND_DATA[] fileSystemChildren; - // Gather child folder and files if (args.IsDirectory) { - fileSystemChildren = FileData.GetFileSystemEntries(path, "*").ToArray(); + args.FileSystemChildren = FileData.GetFileSystemEntries(path, "*").ToArray(); bool isVirtualFolder = parent != null && parent.IsRoot; - fileSystemChildren = FilterChildFileSystemEntries(fileSystemChildren, isVirtualFolder); + args = FileSystemHelper.FilterChildFileSystemEntries(args, isVirtualFolder); } else { - fileSystemChildren = new WIN32_FIND_DATA[] { }; + args.FileSystemChildren = new WIN32_FIND_DATA[] { }; } - args.FileSystemChildren = fileSystemChildren; // Fire BeginResolvePath to see if anyone wants to cancel this operation - if (!OnBeginResolvePath(args)) + if (!EntityResolutionHelper.ShouldResolvePathContents(args)) { return null; } - BaseItem item = ResolveItem(args); - - if (item != null) - { - await Kernel.Instance.ExecuteMetadataProviders(item, args, allowInternetProviders: allowInternetProviders).ConfigureAwait(false); - - if (item.IsFolder) - { - // If it's a folder look for child entities - (item as Folder).Children = (await Task.WhenAll(GetChildren(item as Folder, fileSystemChildren, allowInternetProviders)).ConfigureAwait(false)) - .Where(i => i != null).OrderBy(f => - { - return string.IsNullOrEmpty(f.SortName) ? f.Name : f.SortName; - - }); - } - } + BaseItem item = Kernel.Instance.ResolveItem(args); return item; } - /// - /// Finds child BaseItems for a given Folder - /// - private Task[] GetChildren(Folder folder, WIN32_FIND_DATA[] fileSystemChildren, bool allowInternetProviders) - { - Task[] tasks = new Task[fileSystemChildren.Length]; + ///// + ///// Finds child BaseItems for a given Folder + ///// + //private Task[] GetChildren(Folder folder, WIN32_FIND_DATA[] fileSystemChildren, bool allowInternetProviders) + //{ + // Task[] tasks = new Task[fileSystemChildren.Length]; - for (int i = 0; i < fileSystemChildren.Length; i++) - { - var child = fileSystemChildren[i]; + // for (int i = 0; i < fileSystemChildren.Length; i++) + // { + // var child = fileSystemChildren[i]; - tasks[i] = GetItem(child.Path, folder, child, allowInternetProviders: allowInternetProviders); - } + // tasks[i] = GetItem(child.Path, folder, child, allowInternetProviders: allowInternetProviders); + // } - return tasks; - } + // return tasks; + //} - /// - /// Transforms shortcuts into their actual paths - /// - private WIN32_FIND_DATA[] FilterChildFileSystemEntries(WIN32_FIND_DATA[] fileSystemChildren, bool flattenShortcuts) - { - WIN32_FIND_DATA[] returnArray = new WIN32_FIND_DATA[fileSystemChildren.Length]; - List resolvedShortcuts = new List(); + ///// + ///// Transforms shortcuts into their actual paths + ///// + //private WIN32_FIND_DATA[] FilterChildFileSystemEntries(WIN32_FIND_DATA[] fileSystemChildren, bool flattenShortcuts) + //{ + // WIN32_FIND_DATA[] returnArray = new WIN32_FIND_DATA[fileSystemChildren.Length]; + // List resolvedShortcuts = new List(); - for (int i = 0; i < fileSystemChildren.Length; i++) - { - WIN32_FIND_DATA file = fileSystemChildren[i]; + // for (int i = 0; i < fileSystemChildren.Length; i++) + // { + // WIN32_FIND_DATA file = fileSystemChildren[i]; - // If it's a shortcut, resolve it - if (Shortcut.IsShortcut(file.Path)) - { - string newPath = Shortcut.ResolveShortcut(file.Path); - WIN32_FIND_DATA newPathData = FileData.GetFileData(newPath); + // // If it's a shortcut, resolve it + // if (Shortcut.IsShortcut(file.Path)) + // { + // string newPath = Shortcut.ResolveShortcut(file.Path); + // WIN32_FIND_DATA newPathData = FileData.GetFileData(newPath); - // Find out if the shortcut is pointing to a directory or file - if (newPathData.IsDirectory) - { - // If we're flattening then get the shortcut's children + // // Find out if the shortcut is pointing to a directory or file + // if (newPathData.IsDirectory) + // { + // // If we're flattening then get the shortcut's children - if (flattenShortcuts) - { - returnArray[i] = file; - WIN32_FIND_DATA[] newChildren = FileData.GetFileSystemEntries(newPath, "*").ToArray(); + // if (flattenShortcuts) + // { + // returnArray[i] = file; + // WIN32_FIND_DATA[] newChildren = FileData.GetFileSystemEntries(newPath, "*").ToArray(); - resolvedShortcuts.AddRange(FilterChildFileSystemEntries(newChildren, false)); - } - else - { - returnArray[i] = newPathData; - } - } - else - { - returnArray[i] = newPathData; - } - } - else - { - returnArray[i] = file; - } - } + // resolvedShortcuts.AddRange(FilterChildFileSystemEntries(newChildren, false)); + // } + // else + // { + // returnArray[i] = newPathData; + // } + // } + // else + // { + // returnArray[i] = newPathData; + // } + // } + // else + // { + // returnArray[i] = file; + // } + // } - if (resolvedShortcuts.Count > 0) - { - resolvedShortcuts.InsertRange(0, returnArray); - return resolvedShortcuts.ToArray(); - } - else - { - return returnArray; - } - } + // if (resolvedShortcuts.Count > 0) + // { + // resolvedShortcuts.InsertRange(0, returnArray); + // return resolvedShortcuts.ToArray(); + // } + // else + // { + // return returnArray; + // } + //} /// /// Gets a Person @@ -261,7 +203,7 @@ namespace MediaBrowser.Controller.Library T item = new T(); item.Name = name; - item.Id = Kernel.GetMD5(path); + item.Id = path.GetMD5(); if (!Directory.Exists(path)) { diff --git a/MediaBrowser.Controller/Library/ItemResolveEventArgs.cs b/MediaBrowser.Controller/Library/ItemResolveEventArgs.cs index 5d207de13e..4194517be7 100644 --- a/MediaBrowser.Controller/Library/ItemResolveEventArgs.cs +++ b/MediaBrowser.Controller/Library/ItemResolveEventArgs.cs @@ -1,5 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.IO; +using System.Collections.Generic; +using System.Linq; using System; using System.IO; @@ -12,32 +14,39 @@ namespace MediaBrowser.Controller.Library { public WIN32_FIND_DATA[] FileSystemChildren { get; set; } + protected List _additionalLocations = new List(); + public List AdditionalLocations + { + get + { + return _additionalLocations; + } + set + { + _additionalLocations = value; + } + } + + public IEnumerable PhysicalLocations + { + get + { + return (new List() {this.Path}).Concat(AdditionalLocations); + } + } + + public bool IsBDFolder { get; set; } + public bool IsDVDFolder { get; set; } + public WIN32_FIND_DATA? GetFileSystemEntry(string path) { - for (int i = 0; i < FileSystemChildren.Length; i++) - { - WIN32_FIND_DATA entry = FileSystemChildren[i]; - - if (entry.Path.Equals(path, StringComparison.OrdinalIgnoreCase)) - { - return entry; - } - } - - return null; + WIN32_FIND_DATA entry = FileSystemChildren.FirstOrDefault(f => f.Path.Equals(path, StringComparison.OrdinalIgnoreCase)); + return entry.cFileName != null ? (WIN32_FIND_DATA?)entry : null; } public bool ContainsFile(string name) { - for (int i = 0; i < FileSystemChildren.Length; i++) - { - if (FileSystemChildren[i].cFileName.Equals(name, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; + return FileSystemChildren.FirstOrDefault(f => f.cFileName.Equals(name, StringComparison.OrdinalIgnoreCase)).cFileName != null; } public bool ContainsFolder(string name) diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 8d1f4965a0..440a3d2dfa 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -74,6 +74,8 @@ + + @@ -81,6 +83,7 @@ + diff --git a/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs b/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs index 1508252ebb..7c9677e4e4 100644 --- a/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs +++ b/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; +using MediaBrowser.Common.Extensions; using System; using System.IO; @@ -39,7 +40,7 @@ namespace MediaBrowser.Controller.Resolvers item.Parent = args.Parent; } - item.Id = Kernel.GetMD5(item.Path); + item.Id = (item.GetType().FullName + item.Path).GetMD5(); } public BaseItem ResolvePath(ItemResolveEventArgs args) diff --git a/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs b/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs new file mode 100644 index 0000000000..76541e0efe --- /dev/null +++ b/MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.IO; +using MediaBrowser.Controller.IO; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Entities.TV; + +namespace MediaBrowser.Controller.Resolvers +{ + public static class EntityResolutionHelper + { + /// + /// Any folder named in this list will be ignored - can be added to at runtime for extensibility + /// + public static List IgnoreFolders = new List() + { + "trailers", + "metadata", + "bdmv", + "certificate", + "backup", + "video_ts", + "audio_ts", + "ps3_update", + "ps3_vprm" + }; + /// + /// Determines whether a path should be resolved or ignored entirely - called before we even look at the contents + /// + /// + /// false if the path should be ignored + public static bool ShouldResolvePath(WIN32_FIND_DATA path) + { + bool resolve = true; + // Ignore hidden files and folders + if (path.IsHidden || path.IsSystemFile) + { + resolve = false; + } + + // Ignore any folders in our list + else if (path.IsDirectory && IgnoreFolders.Contains(Path.GetFileName(path.Path), StringComparer.OrdinalIgnoreCase)) + { + resolve = false; + } + + return resolve; + } + + /// + /// Determines whether a path should be ignored based on its contents - called after the contents have been read + /// + public static bool ShouldResolvePathContents(ItemResolveEventArgs args) + { + bool resolve = true; + if (args.ContainsFile(".ignore")) + { + // Ignore any folders containing a file called .ignore + resolve = false; + } + + return resolve; + } + } +} diff --git a/MediaBrowser.Controller/Resolvers/FolderResolver.cs b/MediaBrowser.Controller/Resolvers/FolderResolver.cs index 028c85f862..408e513d44 100644 --- a/MediaBrowser.Controller/Resolvers/FolderResolver.cs +++ b/MediaBrowser.Controller/Resolvers/FolderResolver.cs @@ -16,7 +16,7 @@ namespace MediaBrowser.Controller.Resolvers { if (args.IsDirectory) { - return new Folder(); + return new Folder() { PhysicalLocations = args.PhysicalLocations }; } return null; diff --git a/MediaBrowser.Controller/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Controller/Resolvers/Movies/MovieResolver.cs index ae30a63872..85d9996aa2 100644 --- a/MediaBrowser.Controller/Resolvers/Movies/MovieResolver.cs +++ b/MediaBrowser.Controller/Resolvers/Movies/MovieResolver.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; using System.ComponentModel.Composition; +using System.Collections.Generic; namespace MediaBrowser.Controller.Resolvers.Movies { @@ -61,11 +62,27 @@ namespace MediaBrowser.Controller.Resolvers.Movies private Movie GetMovie(ItemResolveEventArgs args) { - // Loop through each child file/folder and see if we find a video - for (var i = 0; i < args.FileSystemChildren.Length; i++) + //first see if the discovery process has already determined we are a DVD or BD + if (args.IsDVDFolder) { - var child = args.FileSystemChildren[i]; + return new Movie() + { + Path = args.Path, + VideoType = VideoType.DVD + }; + } + else if (args.IsBDFolder) + { + return new Movie() + { + Path = args.Path, + VideoType = VideoType.BluRay + }; + } + // Loop through each child file/folder and see if we find a video + foreach (var child in args.FileSystemChildren) + { ItemResolveEventArgs childArgs = new ItemResolveEventArgs() { FileInfo = child, From 7cfa489c6ea7887982d4cb7643e23631448421e5 Mon Sep 17 00:00:00 2001 From: ebr11 Eric Reed spam Date: Mon, 17 Sep 2012 12:55:58 -0400 Subject: [PATCH 2/5] Attach ItemResolveEventArgs to BaseItem so providers can access them at any time --- MediaBrowser.Controller/Entities/BaseItem.cs | 8 ++++++++ MediaBrowser.Controller/Entities/Folder.cs | 4 ++++ MediaBrowser.Controller/Kernel.cs | 2 ++ 3 files changed, 14 insertions(+) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index d135acf9cf..17f32c0e6d 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1,4 +1,5 @@ using MediaBrowser.Model.Entities; +using MediaBrowser.Controller.Library; using System; using System.Collections.Generic; using System.Linq; @@ -7,6 +8,13 @@ namespace MediaBrowser.Controller.Entities { public abstract class BaseItem : BaseEntity, IHasProviderIds { + /// + /// We attach these to the item so that we only ever have to hit the file system once + /// (this includes the children of the containing folder) + /// Use ResolveArgs.FileSystemChildren to check for the existence of files instead of File.Exists + /// + public ItemResolveEventArgs ResolveArgs { get; set; } + public string SortName { get; set; } /// diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 0858500f0d..1e099e14d8 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -171,6 +171,10 @@ namespace MediaBrowser.Controller.Entities if (currentChild.IsChanged(child)) { changed = true; + //update resolve args and refresh meta + // Note - we are refreshing the existing child instead of the newly found one so the "Except" operation below + // will identify this item as the same one + currentChild.ResolveArgs = child.ResolveArgs; currentChild.RefreshMetadata(); //save it in repo... validChildren.Add(currentChild); diff --git a/MediaBrowser.Controller/Kernel.cs b/MediaBrowser.Controller/Kernel.cs index 13010ad8ec..e7b8435bb9 100644 --- a/MediaBrowser.Controller/Kernel.cs +++ b/MediaBrowser.Controller/Kernel.cs @@ -129,6 +129,7 @@ namespace MediaBrowser.Controller if (item != null) { + item.ResolveArgs = args; return item; } } @@ -160,6 +161,7 @@ namespace MediaBrowser.Controller void RootFolder_ChildrenChanged(object sender, ChildrenChangedEventArgs e) { + Logger.LogDebugInfo("Root Folder Children Changed. Added: " + e.ItemsAdded.Count + " Removed: " + e.ItemsRemoved.Count()); //re-start the directory watchers DirectoryWatchers.Stop(); DirectoryWatchers.Start(); From 922fd3acaed9c39849abcbfa60bc9040e791c383 Mon Sep 17 00:00:00 2001 From: ebr11 Eric Reed spam Date: Mon, 17 Sep 2012 13:05:42 -0400 Subject: [PATCH 3/5] Make ResolveArgs self-creating if need be --- MediaBrowser.Controller/Entities/BaseItem.cs | 25 +++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 17f32c0e6d..dc148da36b 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1,5 +1,6 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.IO; using System; using System.Collections.Generic; using System.Linq; @@ -8,12 +9,34 @@ namespace MediaBrowser.Controller.Entities { public abstract class BaseItem : BaseEntity, IHasProviderIds { + protected ItemResolveEventArgs _resolveArgs; /// /// We attach these to the item so that we only ever have to hit the file system once /// (this includes the children of the containing folder) /// Use ResolveArgs.FileSystemChildren to check for the existence of files instead of File.Exists /// - public ItemResolveEventArgs ResolveArgs { get; set; } + public ItemResolveEventArgs ResolveArgs + { + get + { + if (_resolveArgs == null) + { + _resolveArgs = new ItemResolveEventArgs() + { + FileInfo = FileData.GetFileData(this.Path), + Parent = this.Parent, + Cancel = false, + Path = this.Path + }; + _resolveArgs = FileSystemHelper.FilterChildFileSystemEntries(_resolveArgs, (this.Parent != null && this.Parent.IsRoot)); + } + return _resolveArgs; + } + set + { + _resolveArgs = value; + } + } public string SortName { get; set; } From 7186d661095ae935be3c74960d72a66548e41888 Mon Sep 17 00:00:00 2001 From: ebr11 Eric Reed spam Date: Mon, 17 Sep 2012 13:29:06 -0400 Subject: [PATCH 4/5] Add OnLibraryChanged event to server Kernel --- MediaBrowser.Controller/Entities/Folder.cs | 6 +++++- MediaBrowser.Controller/Kernel.cs | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 1e099e14d8..694ec1ca21 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -22,7 +22,11 @@ namespace MediaBrowser.Controller.Entities { if (ChildrenChanged != null) { - Task.Run( () => ChildrenChanged(this, args)); + Task.Run( () => + { + ChildrenChanged(this, args); + Kernel.Instance.OnLibraryChanged(args); + }); } } diff --git a/MediaBrowser.Controller/Kernel.cs b/MediaBrowser.Controller/Kernel.cs index e7b8435bb9..b8243d65fe 100644 --- a/MediaBrowser.Controller/Kernel.cs +++ b/MediaBrowser.Controller/Kernel.cs @@ -25,6 +25,21 @@ namespace MediaBrowser.Controller { public class Kernel : BaseKernel { + #region Events + /// + /// Fires whenever any validation routine adds or removes items. The added and removed items are properties of the args. + /// *** Will fire asynchronously. *** + /// + public event EventHandler LibraryChanged; + public void OnLibraryChanged(ChildrenChangedEventArgs args) + { + if (LibraryChanged != null) + { + Task.Run(() => LibraryChanged(this, args)); + } + } + + #endregion public static Kernel Instance { get; private set; } public ItemController ItemController { get; private set; } From 946c0e8256d61d5084efdd2196eef455fa13b89b Mon Sep 17 00:00:00 2001 From: ebr11 Eric Reed spam Date: Mon, 17 Sep 2012 16:08:32 -0400 Subject: [PATCH 5/5] Initial metadata provider hook in. No refresh intelligence yet. --- .../Entities/BaseEntity.cs | 48 +++++++++++++++++++ MediaBrowser.Controller/Entities/BaseItem.cs | 42 ---------------- MediaBrowser.Controller/Kernel.cs | 9 ++-- .../Library/ItemController.cs | 2 +- 4 files changed, 53 insertions(+), 48 deletions(-) diff --git a/MediaBrowser.Controller/Entities/BaseEntity.cs b/MediaBrowser.Controller/Entities/BaseEntity.cs index 53b42da01d..7f0ea12b80 100644 --- a/MediaBrowser.Controller/Entities/BaseEntity.cs +++ b/MediaBrowser.Controller/Entities/BaseEntity.cs @@ -1,4 +1,8 @@ using System; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.IO; namespace MediaBrowser.Controller.Entities { @@ -11,6 +15,10 @@ namespace MediaBrowser.Controller.Entities public Guid Id { get; set; } + public string Path { get; set; } + + public Folder Parent { get; set; } + public string PrimaryImagePath { get; set; } public DateTime DateCreated { get; set; } @@ -21,5 +29,45 @@ namespace MediaBrowser.Controller.Entities { return Name; } + + protected ItemResolveEventArgs _resolveArgs; + /// + /// We attach these to the item so that we only ever have to hit the file system once + /// (this includes the children of the containing folder) + /// Use ResolveArgs.FileSystemChildren to check for the existence of files instead of File.Exists + /// + public ItemResolveEventArgs ResolveArgs + { + get + { + if (_resolveArgs == null) + { + _resolveArgs = new ItemResolveEventArgs() + { + FileInfo = FileData.GetFileData(this.Path), + Parent = this.Parent, + Cancel = false, + Path = this.Path + }; + _resolveArgs = FileSystemHelper.FilterChildFileSystemEntries(_resolveArgs, (this.Parent != null && this.Parent.IsRoot)); + } + return _resolveArgs; + } + set + { + _resolveArgs = value; + } + } + + /// + /// Refresh metadata on us by execution our provider chain + /// + /// true if a provider reports we changed + public bool RefreshMetadata() + { + Kernel.Instance.ExecuteMetadataProviders(this).ConfigureAwait(false); + return true; + } + } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index dc148da36b..984584e9a5 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -9,35 +9,6 @@ namespace MediaBrowser.Controller.Entities { public abstract class BaseItem : BaseEntity, IHasProviderIds { - protected ItemResolveEventArgs _resolveArgs; - /// - /// We attach these to the item so that we only ever have to hit the file system once - /// (this includes the children of the containing folder) - /// Use ResolveArgs.FileSystemChildren to check for the existence of files instead of File.Exists - /// - public ItemResolveEventArgs ResolveArgs - { - get - { - if (_resolveArgs == null) - { - _resolveArgs = new ItemResolveEventArgs() - { - FileInfo = FileData.GetFileData(this.Path), - Parent = this.Parent, - Cancel = false, - Path = this.Path - }; - _resolveArgs = FileSystemHelper.FilterChildFileSystemEntries(_resolveArgs, (this.Parent != null && this.Parent.IsRoot)); - } - return _resolveArgs; - } - set - { - _resolveArgs = value; - } - } - public string SortName { get; set; } /// @@ -45,10 +16,6 @@ namespace MediaBrowser.Controller.Entities /// public DateTime? PremiereDate { get; set; } - public string Path { get; set; } - - public Folder Parent { get; set; } - public string LogoImagePath { get; set; } public string ArtImagePath { get; set; } @@ -177,15 +144,6 @@ namespace MediaBrowser.Controller.Entities return changed; } - /// - /// Refresh metadata on us by execution our provider chain - /// - /// true if a provider reports we changed - public bool RefreshMetadata() - { - return false; - } - /// /// Determines if the item is considered new based on user settings /// diff --git a/MediaBrowser.Controller/Kernel.cs b/MediaBrowser.Controller/Kernel.cs index b8243d65fe..cf4450aec5 100644 --- a/MediaBrowser.Controller/Kernel.cs +++ b/MediaBrowser.Controller/Kernel.cs @@ -118,9 +118,6 @@ namespace MediaBrowser.Controller //watch the root folder children for changes RootFolder.ChildrenChanged += RootFolder_ChildrenChanged; - System.Threading.Thread.Sleep(25000); - var allChildren = RootFolder.RecursiveChildren; - Logger.LogInfo(string.Format("Loading complete. Movies: {0} Episodes: {1}", allChildren.OfType().Count(), allChildren.OfType().Count())); } protected override void OnComposablePartsLoaded() @@ -180,6 +177,8 @@ namespace MediaBrowser.Controller //re-start the directory watchers DirectoryWatchers.Stop(); DirectoryWatchers.Start(); + var allChildren = RootFolder.RecursiveChildren; + Logger.LogInfo(string.Format("Loading complete. Movies: {0} Episodes: {1}", allChildren.OfType().Count(), allChildren.OfType().Count())); } /// @@ -328,7 +327,7 @@ namespace MediaBrowser.Controller /// /// Runs all metadata providers for an entity /// - internal async Task ExecuteMetadataProviders(BaseEntity item, ItemResolveEventArgs args, bool allowInternetProviders = true) + internal async Task ExecuteMetadataProviders(BaseEntity item, bool allowInternetProviders = true) { // Run them sequentially in order of priority for (int i = 0; i < MetadataProviders.Length; i++) @@ -349,7 +348,7 @@ namespace MediaBrowser.Controller try { - await provider.FetchAsync(item, args).ConfigureAwait(false); + await provider.FetchAsync(item, item.ResolveArgs).ConfigureAwait(false); } catch (Exception ex) { diff --git a/MediaBrowser.Controller/Library/ItemController.cs b/MediaBrowser.Controller/Library/ItemController.cs index 7ef1c17a11..dd08c193e7 100644 --- a/MediaBrowser.Controller/Library/ItemController.cs +++ b/MediaBrowser.Controller/Library/ItemController.cs @@ -217,7 +217,7 @@ namespace MediaBrowser.Controller.Library args.FileInfo = FileData.GetFileData(path); args.FileSystemChildren = FileData.GetFileSystemEntries(path, "*").ToArray(); - await Kernel.Instance.ExecuteMetadataProviders(item, args).ConfigureAwait(false); + await Kernel.Instance.ExecuteMetadataProviders(item).ConfigureAwait(false); return item; }