Pārlūkot izejas kodu

One async call leads to another, and another, all the way up the call stack...

LukePulverenti Luke Pulverenti luke pulverenti 13 gadi atpakaļ
vecāks
revīzija
937d27ae9d
38 mainītis faili ar 386 papildinājumiem un 366 dzēšanām
  1. 18 13
      MediaBrowser.Api/ApiService.cs
  2. 5 9
      MediaBrowser.Api/HttpHandlers/BaseMediaHandler.cs
  3. 5 4
      MediaBrowser.Api/HttpHandlers/GenreHandler.cs
  4. 6 21
      MediaBrowser.Api/HttpHandlers/GenresHandler.cs
  5. 47 59
      MediaBrowser.Api/HttpHandlers/ImageHandler.cs
  6. 3 2
      MediaBrowser.Api/HttpHandlers/ItemHandler.cs
  7. 4 3
      MediaBrowser.Api/HttpHandlers/ItemListHandler.cs
  8. 5 4
      MediaBrowser.Api/HttpHandlers/PersonHandler.cs
  9. 7 3
      MediaBrowser.Api/HttpHandlers/PluginConfigurationHandler.cs
  10. 19 15
      MediaBrowser.Api/HttpHandlers/PluginsHandler.cs
  11. 5 4
      MediaBrowser.Api/HttpHandlers/StudioHandler.cs
  12. 6 21
      MediaBrowser.Api/HttpHandlers/StudiosHandler.cs
  13. 6 2
      MediaBrowser.Api/HttpHandlers/UsersHandler.cs
  14. 5 4
      MediaBrowser.Api/HttpHandlers/YearHandler.cs
  15. 6 21
      MediaBrowser.Api/HttpHandlers/YearsHandler.cs
  16. 26 2
      MediaBrowser.Common/Kernel/BaseKernel.cs
  17. 3 3
      MediaBrowser.Common/Net/Handlers/BaseEmbeddedResourceHandler.cs
  18. 23 38
      MediaBrowser.Common/Net/Handlers/BaseHandler.cs
  19. 14 23
      MediaBrowser.Common/Net/Handlers/BaseJsonHandler.cs
  20. 26 30
      MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs
  21. 3 6
      MediaBrowser.Controller/IO/DirectoryWatchers.cs
  22. 42 0
      MediaBrowser.Controller/Kernel.cs
  23. 33 27
      MediaBrowser.Controller/Library/ItemController.cs
  24. 3 2
      MediaBrowser.Controller/Providers/AudioInfoProvider.cs
  25. 18 5
      MediaBrowser.Controller/Providers/BaseMetadataProvider.cs
  26. 2 2
      MediaBrowser.Controller/Providers/FolderProviderFromXml.cs
  27. 7 2
      MediaBrowser.Controller/Providers/ImageFromMediaLocationProvider.cs
  28. 9 2
      MediaBrowser.Controller/Providers/LocalTrailerProvider.cs
  29. 6 18
      MediaBrowser.Controller/Resolvers/BaseItemResolver.cs
  30. 2 2
      MediaBrowser.Controller/Resolvers/FolderResolver.cs
  31. 2 2
      MediaBrowser.Controller/Resolvers/VirtualFolderResolver.cs
  32. 1 1
      MediaBrowser.Controller/Xml/BaseItemXmlParser.cs
  33. 2 2
      MediaBrowser.Movies/Providers/MovieProviderFromXml.cs
  34. 2 2
      MediaBrowser.Movies/Resolvers/MovieResolver.cs
  35. 1 2
      MediaBrowser.ServerApplication/MainWindow.xaml.cs
  36. 6 4
      MediaBrowser.TV/Providers/EpisodeImageFromMediaLocationProvider.cs
  37. 6 4
      MediaBrowser.TV/Providers/EpisodeProviderFromXml.cs
  38. 2 2
      MediaBrowser.TV/Providers/SeriesProviderFromXml.cs

+ 18 - 13
MediaBrowser.Api/ApiService.cs

@@ -1,8 +1,7 @@
 using System;
-using System.IO;
+using System.Collections.Generic;
 using System.Linq;
-using System.Reflection;
-using MediaBrowser.Common.Configuration;
+using System.Threading.Tasks;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.DTO;
 using MediaBrowser.Model.Entities;
@@ -21,7 +20,7 @@ namespace MediaBrowser.Api
             return Kernel.Instance.GetItemById(guid);
         }
 
-        public static DTOBaseItem GetDTOBaseItem(BaseItem item, User user, 
+        public async static Task<DTOBaseItem> GetDTOBaseItem(BaseItem item, User user, 
             bool includeChildren = true, 
             bool includePeople = true)
         {
@@ -79,16 +78,16 @@ namespace MediaBrowser.Api
 
             dto.UserData = item.GetUserData(user);
 
-            AttachStudios(dto, item);
+            await AttachStudios(dto, item);
 
             if (includeChildren)
             {
-                AttachChildren(dto, item, user);
+                await AttachChildren(dto, item, user);
             }
 
             if (includePeople)
             {
-                AttachPeople(dto, item);
+                await AttachPeople(dto, item);
             }
 
             Folder folder = item as Folder;
@@ -104,18 +103,20 @@ namespace MediaBrowser.Api
             return dto;
         }
 
-        private static void AttachStudios(DTOBaseItem dto, BaseItem item)
+        private static async Task AttachStudios(DTOBaseItem dto, BaseItem item)
         {
             // Attach Studios by transforming them into BaseItemStudio (DTO)
             if (item.Studios != null)
             {
+                IEnumerable<Studio> entities = await Task.WhenAll<Studio>(item.Studios.Select(c => Kernel.Instance.ItemController.GetStudio(c)));
+                
                 dto.Studios = item.Studios.Select(s =>
                 {
                     BaseItemStudio baseItemStudio = new BaseItemStudio();
 
                     baseItemStudio.Name = s;
 
-                    Studio ibnObject = Kernel.Instance.ItemController.GetStudio(s);
+                    Studio ibnObject = entities.First(i => i.Name.Equals(s, StringComparison.OrdinalIgnoreCase));
 
                     if (ibnObject != null)
                     {
@@ -127,30 +128,34 @@ namespace MediaBrowser.Api
             }
         }
 
-        private static void AttachChildren(DTOBaseItem dto, BaseItem item, User user)
+        private static async Task AttachChildren(DTOBaseItem dto, BaseItem item, User user)
         {
             var folder = item as Folder;
 
             if (folder != null)
             {
-                dto.Children = folder.GetParentalAllowedChildren(user).Select(c => GetDTOBaseItem(c, user, false, false));
+                IEnumerable<BaseItem> children = folder.GetParentalAllowedChildren(user);
+
+                dto.Children = await Task.WhenAll<DTOBaseItem>(children.Select(c => GetDTOBaseItem(c, user, false, false)));
             }
 
             dto.LocalTrailers = item.LocalTrailers;
         }
 
-        private static void AttachPeople(DTOBaseItem dto, BaseItem item)
+        private static async Task AttachPeople(DTOBaseItem dto, BaseItem item)
         {
             // Attach People by transforming them into BaseItemPerson (DTO)
             if (item.People != null)
             {
+                IEnumerable<Person> entities = await Task.WhenAll<Person>(item.People.Select(c => Kernel.Instance.ItemController.GetPerson(c.Name)));
+
                 dto.People = item.People.Select(p =>
                 {
                     BaseItemPerson baseItemPerson = new BaseItemPerson();
 
                     baseItemPerson.PersonInfo = p;
 
-                    Person ibnObject = Kernel.Instance.ItemController.GetPerson(p.Name);
+                    Person ibnObject = entities.First(i => i.Name.Equals(p.Name, StringComparison.OrdinalIgnoreCase));
 
                     if (ibnObject != null)
                     {

+ 5 - 9
MediaBrowser.Api/HttpHandlers/BaseMediaHandler.cs

@@ -5,7 +5,6 @@ using System.IO;
 using System.Linq;
 using System.Net;
 using System.Threading.Tasks;
-using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Logging;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net.Handlers;
@@ -91,20 +90,17 @@ namespace MediaBrowser.Api.HttpHandlers
             }
         }
 
-        public override string ContentType
+        public override Task<string> GetContentType()
         {
-            get
+            return Task.Run(() =>
             {
                 return MimeTypes.GetMimeType("." + GetConversionOutputFormat());
-            }
+            });
         }
 
-        public override bool CompressResponse
+        public override bool ShouldCompressResponse(string contentType)
         {
-            get
-            {
-                return false;
-            }
+            return false;
         }
 
         public override async Task ProcessRequest(HttpListenerContext ctx)

+ 5 - 4
MediaBrowser.Api/HttpHandlers/GenreHandler.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading.Tasks;
 using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.DTO;
@@ -13,7 +14,7 @@ namespace MediaBrowser.Api.HttpHandlers
     /// </summary>
     public class GenreHandler : BaseJsonHandler<IBNItem<Genre>>
     {
-        protected override IBNItem<Genre> GetObjectToSerialize()
+        protected override async Task<IBNItem<Genre>> GetObjectToSerialize()
         {
             Folder parent = ApiService.GetItemById(QueryString["id"]) as Folder;
             Guid userId = Guid.Parse(QueryString["userid"]);
@@ -21,13 +22,13 @@ namespace MediaBrowser.Api.HttpHandlers
 
             string name = QueryString["name"];
 
-            return GetGenre(parent, user, name);
+            return await GetGenre(parent, user, name);
         }
 
         /// <summary>
         /// Gets a Genre
         /// </summary>
-        private IBNItem<Genre> GetGenre(Folder parent, User user, string name)
+        private async Task<IBNItem<Genre>> GetGenre(Folder parent, User user, string name)
         {
             int count = 0;
 
@@ -45,7 +46,7 @@ namespace MediaBrowser.Api.HttpHandlers
             // Get the original entity so that we can also supply the PrimaryImagePath
             return new IBNItem<Genre>()
             {
-                Item = Kernel.Instance.ItemController.GetGenre(name),
+                Item = await Kernel.Instance.ItemController.GetGenre(name),
                 BaseItemCount = count
             };
         }

+ 6 - 21
MediaBrowser.Api/HttpHandlers/GenresHandler.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading.Tasks;
 using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.DTO;
@@ -10,20 +11,20 @@ namespace MediaBrowser.Api.HttpHandlers
 {
     public class GenresHandler : BaseJsonHandler<IEnumerable<IBNItem<Genre>>>
     {
-        protected override IEnumerable<IBNItem<Genre>> GetObjectToSerialize()
+        protected override async Task<IEnumerable<IBNItem<Genre>>> GetObjectToSerialize()
         {
             Folder parent = ApiService.GetItemById(QueryString["id"]) as Folder;
             Guid userId = Guid.Parse(QueryString["userid"]);
             User user = Kernel.Instance.Users.First(u => u.Id == userId);
 
-            return GetAllGenres(parent, user);
+            return await GetAllGenres(parent, user);
         }
 
         /// <summary>
         /// Gets all genres from all recursive children of a folder
         /// The CategoryInfo class is used to keep track of the number of times each genres appears
         /// </summary>
-        private IEnumerable<IBNItem<Genre>> GetAllGenres(Folder parent, User user)
+        private async Task<IEnumerable<IBNItem<Genre>>> GetAllGenres(Folder parent, User user)
         {
             Dictionary<string, int> data = new Dictionary<string, int>();
 
@@ -52,25 +53,9 @@ namespace MediaBrowser.Api.HttpHandlers
                 }
             }
 
-            // Now go through the dictionary and create a Category for each genre
-            List<IBNItem<Genre>> list = new List<IBNItem<Genre>>();
+            IEnumerable<Genre> entities = await Task.WhenAll<Genre>(data.Keys.Select(key => { return Kernel.Instance.ItemController.GetGenre(key); }));
 
-            foreach (string key in data.Keys)
-            {
-                // Get the original entity so that we can also supply the PrimaryImagePath
-                Genre entity = Kernel.Instance.ItemController.GetGenre(key);
-
-                if (entity != null)
-                {
-                    list.Add(new IBNItem<Genre>()
-                    {
-                        Item = entity,
-                        BaseItemCount = data[key]
-                    });
-                }
-            }
-
-            return list;
+            return entities.Select(e => new IBNItem<Genre>() { Item = e, BaseItemCount = data[e.Name] });
         }
     }
 }

+ 47 - 59
MediaBrowser.Api/HttpHandlers/ImageHandler.cs

@@ -13,39 +13,57 @@ namespace MediaBrowser.Api.HttpHandlers
     public class ImageHandler : BaseHandler
     {
         private string _ImagePath = null;
-        private string ImagePath
+        private async Task<string> GetImagePath()
         {
-            get
+            if (_ImagePath == null)
             {
-                if (_ImagePath == null)
-                {
-                    _ImagePath = GetImagePath();
-                }
-
-                return _ImagePath;
+                _ImagePath = await DiscoverImagePath();
             }
+
+            return _ImagePath;
         }
 
-        private Stream _SourceStream = null;
-        private Stream SourceStream
+        private async Task<string> DiscoverImagePath()
         {
-            get
+            string path = QueryString["path"] ?? string.Empty;
+
+            if (!string.IsNullOrEmpty(path))
             {
-                EnsureSourceStream();
+                return path;
+            }
 
-                return _SourceStream;
+            string personName = QueryString["personname"];
+
+            if (!string.IsNullOrEmpty(personName))
+            {
+                Person person = await Kernel.Instance.ItemController.GetPerson(personName);
+                
+                return person.PrimaryImagePath;
             }
+
+            BaseItem item = ApiService.GetItemById(QueryString["id"]);
+
+            string imageIndex = QueryString["index"];
+            int index = string.IsNullOrEmpty(imageIndex) ? 0 : int.Parse(imageIndex);
+
+            return GetImagePathFromTypes(item, ImageType, index);
         }
 
+        private Stream _SourceStream = null;
+        private async Task<Stream> GetSourceStream()
+        {
+            await EnsureSourceStream();
+            return _SourceStream;
+        }
 
         private bool _SourceStreamEnsured = false;
-        private void EnsureSourceStream()
+        private async Task EnsureSourceStream()
         {
             if (!_SourceStreamEnsured)
             {
                 try
                 {
-                    _SourceStream = File.OpenRead(ImagePath);
+                    _SourceStream = File.OpenRead(await GetImagePath());
                 }
                 catch (FileNotFoundException ex)
                 {
@@ -68,20 +86,17 @@ namespace MediaBrowser.Api.HttpHandlers
                 }
             }
         }
-        
-        public override string ContentType
+
+        public async override Task<string> GetContentType()
         {
-            get
-            {
-                EnsureSourceStream();
+            await EnsureSourceStream();
 
-                if (SourceStream == null)
-                {
-                    return null;
-                }
-                
-                return MimeTypes.GetMimeType(ImagePath);
+            if (await GetSourceStream() == null)
+            {
+                return null;
             }
+
+            return MimeTypes.GetMimeType(await GetImagePath());
         }
 
         public override TimeSpan CacheDuration
@@ -92,16 +107,16 @@ namespace MediaBrowser.Api.HttpHandlers
             }
         }
 
-        protected override DateTime? GetLastDateModified()
+        protected async override Task<DateTime?> GetLastDateModified()
         {
-            EnsureSourceStream();
+            await EnsureSourceStream();
 
-            if (SourceStream == null)
+            if (await GetSourceStream() == null)
             {
                 return null;
             }
 
-            return File.GetLastWriteTime(ImagePath);
+            return File.GetLastWriteTime(await GetImagePath());
         }
 
         private int? Height
@@ -194,36 +209,9 @@ namespace MediaBrowser.Api.HttpHandlers
             }
         }
 
-        protected override Task WriteResponseToOutputStream(Stream stream)
+        protected override async Task WriteResponseToOutputStream(Stream stream)
         {
-            return Task.Run(() =>
-            {
-                ImageProcessor.ProcessImage(SourceStream, stream, Width, Height, MaxWidth, MaxHeight, Quality);
-            });
-        }
-
-        private string GetImagePath()
-        {
-            string path = QueryString["path"] ?? string.Empty;
-
-            if (!string.IsNullOrEmpty(path))
-            {
-                return path;
-            }
-
-            string personName = QueryString["personname"];
-
-            if (!string.IsNullOrEmpty(personName))
-            {
-                return Kernel.Instance.ItemController.GetPerson(personName).PrimaryImagePath;
-            }
-
-            BaseItem item = ApiService.GetItemById(QueryString["id"]);
-
-            string imageIndex = QueryString["index"];
-            int index = string.IsNullOrEmpty(imageIndex) ? 0 : int.Parse(imageIndex);
-
-            return GetImagePathFromTypes(item, ImageType, index);
+            ImageProcessor.ProcessImage(await GetSourceStream(), stream, Width, Height, MaxWidth, MaxHeight, Quality);
         }
 
         private string GetImagePathFromTypes(BaseItem item, ImageType imageType, int imageIndex)

+ 3 - 2
MediaBrowser.Api/HttpHandlers/ItemHandler.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Linq;
+using System.Threading.Tasks;
 using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.DTO;
@@ -9,7 +10,7 @@ namespace MediaBrowser.Api.HttpHandlers
 {
     public class ItemHandler : BaseJsonHandler<DTOBaseItem>
     {
-        protected sealed override DTOBaseItem GetObjectToSerialize()
+        protected async override Task<DTOBaseItem> GetObjectToSerialize()
         {
             Guid userId = Guid.Parse(QueryString["userid"]);
             User user = Kernel.Instance.Users.First(u => u.Id == userId);
@@ -21,7 +22,7 @@ namespace MediaBrowser.Api.HttpHandlers
                 return null;
             }
 
-            return ApiService.GetDTOBaseItem(item, user);
+            return await ApiService.GetDTOBaseItem(item, user);
         }
 
         protected virtual BaseItem ItemToSerialize

+ 4 - 3
MediaBrowser.Api/HttpHandlers/ItemListHandler.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading.Tasks;
 using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.DTO;
@@ -10,14 +11,14 @@ namespace MediaBrowser.Api.HttpHandlers
 {
     public class ItemListHandler : BaseJsonHandler<IEnumerable<DTOBaseItem>>
     {
-        protected override IEnumerable<DTOBaseItem> GetObjectToSerialize()
+        protected override async Task<IEnumerable<DTOBaseItem>> GetObjectToSerialize()
         {
             User user = Kernel.Instance.Users.First(u => u.Id == UserId);
 
-            return ItemsToSerialize.Select(i =>
+            return await Task.WhenAll<DTOBaseItem>(ItemsToSerialize.Select(i =>
             {
                 return ApiService.GetDTOBaseItem(i, user, includeChildren: false, includePeople: false);
-            });
+            }));
         }
 
         protected IEnumerable<BaseItem> ItemsToSerialize

+ 5 - 4
MediaBrowser.Api/HttpHandlers/PersonHandler.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading.Tasks;
 using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.DTO;
@@ -13,7 +14,7 @@ namespace MediaBrowser.Api.HttpHandlers
     /// </summary>
     public class PersonHandler : BaseJsonHandler<IBNItem<Person>>
     {
-        protected override IBNItem<Person> GetObjectToSerialize()
+        protected async override Task<IBNItem<Person>> GetObjectToSerialize()
         {
             Folder parent = ApiService.GetItemById(QueryString["id"]) as Folder;
             Guid userId = Guid.Parse(QueryString["userid"]);
@@ -21,13 +22,13 @@ namespace MediaBrowser.Api.HttpHandlers
 
             string name = QueryString["name"];
 
-            return GetPerson(parent, user, name);
+            return await GetPerson(parent, user, name);
         }
 
         /// <summary>
         /// Gets a Person
         /// </summary>
-        private IBNItem<Person> GetPerson(Folder parent, User user, string name)
+        private async Task<IBNItem<Person>> GetPerson(Folder parent, User user, string name)
         {
             int count = 0;
 
@@ -45,7 +46,7 @@ namespace MediaBrowser.Api.HttpHandlers
             // Get the original entity so that we can also supply the PrimaryImagePath
             return new IBNItem<Person>()
             {
-                Item = Kernel.Instance.ItemController.GetPerson(name),
+                Item = await Kernel.Instance.ItemController.GetPerson(name),
                 BaseItemCount = count
             };
         }

+ 7 - 3
MediaBrowser.Api/HttpHandlers/PluginConfigurationHandler.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Linq;
+using System.Threading.Tasks;
 using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.Plugins;
@@ -8,11 +9,14 @@ namespace MediaBrowser.Api.HttpHandlers
 {
     public class PluginConfigurationHandler : BaseJsonHandler<BasePluginConfiguration>
     {
-        protected override BasePluginConfiguration GetObjectToSerialize()
+        protected override Task<BasePluginConfiguration> GetObjectToSerialize()
         {
-            string pluginName = QueryString["name"];
+            return Task.Run(() =>
+            {
+                string pluginName = QueryString["name"];
 
-            return Kernel.Instance.Plugins.First(p => p.Name.Equals(pluginName, StringComparison.OrdinalIgnoreCase)).Configuration;
+                return Kernel.Instance.Plugins.First(p => p.Name.Equals(pluginName, StringComparison.OrdinalIgnoreCase)).Configuration;
+            });
         }
     }
 }

+ 19 - 15
MediaBrowser.Api/HttpHandlers/PluginsHandler.cs

@@ -1,5 +1,6 @@
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading.Tasks;
 using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.DTO;
@@ -11,26 +12,29 @@ namespace MediaBrowser.Api.HttpHandlers
     /// </summary>
     public class PluginsHandler : BaseJsonHandler<IEnumerable<PluginInfo>>
     {
-        protected override IEnumerable<PluginInfo> GetObjectToSerialize()
+        protected override Task<IEnumerable<PluginInfo>> GetObjectToSerialize()
         {
-            var plugins = Kernel.Instance.Plugins.Select(p =>
+            return Task.Run(() =>
             {
-                return new PluginInfo()
+                var plugins = Kernel.Instance.Plugins.Select(p =>
                 {
-                    Path = p.Path,
-                    Name = p.Name,
-                    Enabled = p.Enabled,
-                    DownloadToUI = p.DownloadToUI,
-                    Version = p.Version
-                };
-            });
+                    return new PluginInfo()
+                    {
+                        Path = p.Path,
+                        Name = p.Name,
+                        Enabled = p.Enabled,
+                        DownloadToUI = p.DownloadToUI,
+                        Version = p.Version
+                    };
+                });
 
-            if (QueryString["uionly"] == "1")
-            {
-                plugins = plugins.Where(p => p.DownloadToUI);
-            }
+                if (QueryString["uionly"] == "1")
+                {
+                    plugins = plugins.Where(p => p.DownloadToUI);
+                }
 
-            return plugins;
+                return plugins;
+            });
         }
     }
 }

+ 5 - 4
MediaBrowser.Api/HttpHandlers/StudioHandler.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading.Tasks;
 using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.DTO;
@@ -13,7 +14,7 @@ namespace MediaBrowser.Api.HttpHandlers
     /// </summary>
     public class StudioHandler : BaseJsonHandler<IBNItem<Studio>>
     {
-        protected override IBNItem<Studio> GetObjectToSerialize()
+        protected async override Task<IBNItem<Studio>> GetObjectToSerialize()
         {
             Folder parent = ApiService.GetItemById(QueryString["id"]) as Folder;
             Guid userId = Guid.Parse(QueryString["userid"]);
@@ -21,13 +22,13 @@ namespace MediaBrowser.Api.HttpHandlers
 
             string name = QueryString["name"];
 
-            return GetStudio(parent, user, name);
+            return await GetStudio(parent, user, name);
         }
 
         /// <summary>
         /// Gets a Studio
         /// </summary>
-        private IBNItem<Studio> GetStudio(Folder parent, User user, string name)
+        private async Task<IBNItem<Studio>> GetStudio(Folder parent, User user, string name)
         {
             int count = 0;
 
@@ -45,7 +46,7 @@ namespace MediaBrowser.Api.HttpHandlers
             // Get the original entity so that we can also supply the PrimaryImagePath
             return new IBNItem<Studio>()
             {
-                Item = Kernel.Instance.ItemController.GetStudio(name),
+                Item = await Kernel.Instance.ItemController.GetStudio(name),
                 BaseItemCount = count
             };
         }

+ 6 - 21
MediaBrowser.Api/HttpHandlers/StudiosHandler.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading.Tasks;
 using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.DTO;
@@ -10,20 +11,20 @@ namespace MediaBrowser.Api.HttpHandlers
 {
     public class StudiosHandler : BaseJsonHandler<IEnumerable<IBNItem<Studio>>>
     {
-        protected override IEnumerable<IBNItem<Studio>> GetObjectToSerialize()
+        protected override async Task<IEnumerable<IBNItem<Studio>>> GetObjectToSerialize()
         {
             Folder parent = ApiService.GetItemById(QueryString["id"]) as Folder;
             Guid userId = Guid.Parse(QueryString["userid"]);
             User user = Kernel.Instance.Users.First(u => u.Id == userId);
 
-            return GetAllStudios(parent, user);
+            return await GetAllStudios(parent, user);
         }
 
         /// <summary>
         /// Gets all studios from all recursive children of a folder
         /// The CategoryInfo class is used to keep track of the number of times each studio appears
         /// </summary>
-        private IEnumerable<IBNItem<Studio>> GetAllStudios(Folder parent, User user)
+        private async Task<IEnumerable<IBNItem<Studio>>> GetAllStudios(Folder parent, User user)
         {
             Dictionary<string, int> data = new Dictionary<string, int>();
 
@@ -52,25 +53,9 @@ namespace MediaBrowser.Api.HttpHandlers
                 }
             }
 
-            // Now go through the dictionary and create a Category for each studio
-            List<IBNItem<Studio>> list = new List<IBNItem<Studio>>();
+            IEnumerable<Studio> entities = await Task.WhenAll<Studio>(data.Keys.Select(key => { return Kernel.Instance.ItemController.GetStudio(key); }));
 
-            foreach (string key in data.Keys)
-            {
-                // Get the original entity so that we can also supply the PrimaryImagePath
-                Studio entity = Kernel.Instance.ItemController.GetStudio(key);
-
-                if (entity != null)
-                {
-                    list.Add(new IBNItem<Studio>()
-                    {
-                        Item = entity,
-                        BaseItemCount = data[key]
-                    });
-                }
-            }
-
-            return list;
+            return entities.Select(e => new IBNItem<Studio>() { Item = e, BaseItemCount = data[e.Name] });
         }
     }
 }

+ 6 - 2
MediaBrowser.Api/HttpHandlers/UsersHandler.cs

@@ -1,4 +1,5 @@
 using System.Collections.Generic;
+using System.Threading.Tasks;
 using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.Entities;
@@ -7,9 +8,12 @@ namespace MediaBrowser.Api.HttpHandlers
 {
     class UsersHandler : BaseJsonHandler<IEnumerable<User>>
     {
-        protected override IEnumerable<User> GetObjectToSerialize()
+        protected override Task<IEnumerable<User>> GetObjectToSerialize()
         {
-            return Kernel.Instance.Users;
+            return Task.Run(() =>
+            {
+                return Kernel.Instance.Users;
+            });
         }
     }
 }

+ 5 - 4
MediaBrowser.Api/HttpHandlers/YearHandler.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading.Tasks;
 using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.DTO;
@@ -13,7 +14,7 @@ namespace MediaBrowser.Api.HttpHandlers
     /// </summary>
     public class YearHandler : BaseJsonHandler<IBNItem<Year>>
     {
-        protected override IBNItem<Year> GetObjectToSerialize()
+        protected override async Task<IBNItem<Year>> GetObjectToSerialize()
         {
             Folder parent = ApiService.GetItemById(QueryString["id"]) as Folder;
             Guid userId = Guid.Parse(QueryString["userid"]);
@@ -21,13 +22,13 @@ namespace MediaBrowser.Api.HttpHandlers
 
             string year = QueryString["year"];
 
-            return GetYear(parent, user, int.Parse(year));
+            return await GetYear(parent, user, int.Parse(year));
         }
 
         /// <summary>
         /// Gets a Year
         /// </summary>
-        private IBNItem<Year> GetYear(Folder parent, User user, int year)
+        private async Task<IBNItem<Year>> GetYear(Folder parent, User user, int year)
         {
             int count = 0;
 
@@ -45,7 +46,7 @@ namespace MediaBrowser.Api.HttpHandlers
             // Get the original entity so that we can also supply the PrimaryImagePath
             return new IBNItem<Year>()
             {
-                Item = Kernel.Instance.ItemController.GetYear(year),
+                Item = await Kernel.Instance.ItemController.GetYear(year),
                 BaseItemCount = count
             };
         }

+ 6 - 21
MediaBrowser.Api/HttpHandlers/YearsHandler.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading.Tasks;
 using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.DTO;
@@ -10,20 +11,20 @@ namespace MediaBrowser.Api.HttpHandlers
 {
     public class YearsHandler : BaseJsonHandler<IEnumerable<IBNItem<Year>>>
     {
-        protected override IEnumerable<IBNItem<Year>> GetObjectToSerialize()
+        protected override async Task<IEnumerable<IBNItem<Year>>> GetObjectToSerialize()
         {
             Folder parent = ApiService.GetItemById(QueryString["id"]) as Folder;
             Guid userId = Guid.Parse(QueryString["userid"]);
             User user = Kernel.Instance.Users.First(u => u.Id == userId);
 
-            return GetAllYears(parent, user);
+            return await GetAllYears(parent, user);
         }
 
         /// <summary>
         /// Gets all years from all recursive children of a folder
         /// The CategoryInfo class is used to keep track of the number of times each year appears
         /// </summary>
-        private IEnumerable<IBNItem<Year>> GetAllYears(Folder parent, User user)
+        private async Task<IEnumerable<IBNItem<Year>>> GetAllYears(Folder parent, User user)
         {
             Dictionary<int, int> data = new Dictionary<int, int>();
 
@@ -49,25 +50,9 @@ namespace MediaBrowser.Api.HttpHandlers
                 }
             }
 
-            // Now go through the dictionary and create a Category for each studio
-            List<IBNItem<Year>> list = new List<IBNItem<Year>>();
+            IEnumerable<Year> entities = await Task.WhenAll<Year>(data.Keys.Select(key => { return Kernel.Instance.ItemController.GetYear(key); }));
 
-            foreach (int key in data.Keys)
-            {
-                // Get the original entity so that we can also supply the PrimaryImagePath
-                Year entity = Kernel.Instance.ItemController.GetYear(key);
-
-                if (entity != null)
-                {
-                    list.Add(new IBNItem<Year>()
-                    {
-                        Item = entity,
-                        BaseItemCount = data[key]
-                    });
-                }
-            }
-
-            return list;
+            return entities.Select(e => new IBNItem<Year>() { Item = e, BaseItemCount = data[int.Parse(e.Name)] });
         }
     }
 }

+ 26 - 2
MediaBrowser.Common/Kernel/BaseKernel.cs

@@ -2,17 +2,16 @@
 using System.Collections.Generic;
 using System.ComponentModel.Composition;
 using System.ComponentModel.Composition.Hosting;
-using System.Configuration;
 using System.IO;
 using System.Linq;
 using System.Reflection;
+using System.Threading.Tasks;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Logging;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Plugins;
 using MediaBrowser.Common.Serialization;
 using MediaBrowser.Model.Progress;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Common.Kernel
 {
@@ -93,6 +92,8 @@ namespace MediaBrowser.Common.Kernel
         /// </summary>
         protected void ReloadComposableParts()
         {
+            DisposeComposableParts();
+            
             // Gets all plugin assemblies by first reading all bytes of the .dll and calling Assembly.Load against that
             // This will prevent the .dll file from getting locked, and allow us to replace it when needed
             IEnumerable<Assembly> pluginAssemblies = Directory.GetFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.AllDirectories).Select(f => Assembly.Load(File.ReadAllBytes((f))));
@@ -203,10 +204,33 @@ namespace MediaBrowser.Common.Kernel
         /// </summary>
         public virtual void Dispose()
         {
+            DisposeComposableParts();
             DisposeHttpServer();
             DisposeLogger();
         }
 
+        /// <summary>
+        /// Disposes all objects gathered through MEF composable parts
+        /// </summary>
+        protected virtual void DisposeComposableParts()
+        {
+            DisposePlugins();
+        }
+
+        /// <summary>
+        /// Disposes all plugins
+        /// </summary>
+        private void DisposePlugins()
+        {
+            if (Plugins != null)
+            {
+                foreach (BasePlugin plugin in Plugins)
+                {
+                    plugin.Dispose();
+                }
+            }
+        }
+
         /// <summary>
         /// Disposes the current HttpServer
         /// </summary>

+ 3 - 3
MediaBrowser.Common/Net/Handlers/BaseEmbeddedResourceHandler.cs

@@ -14,9 +14,9 @@ namespace MediaBrowser.Common.Net.Handlers
 
         protected string ResourcePath { get; set; }
 
-        public override string ContentType
+        public override Task<string> GetContentType()
         {
-            get
+            return Task.Run(() =>
             {
                 string extension = Path.GetExtension(ResourcePath);
 
@@ -46,7 +46,7 @@ namespace MediaBrowser.Common.Net.Handlers
                 }
 
                 return "text/plain; charset=utf-8";
-            }
+            });
         }
 
         protected override Task WriteResponseToOutputStream(Stream stream)

+ 23 - 38
MediaBrowser.Common/Net/Handlers/BaseHandler.cs

@@ -111,7 +111,7 @@ namespace MediaBrowser.Common.Net.Handlers
         /// <summary>
         /// Gets the MIME type to include in the response headers
         /// </summary>
-        public abstract string ContentType { get; }
+        public abstract Task<string> GetContentType();
 
         /// <summary>
         /// Gets the status code to include in the response headers
@@ -129,31 +129,9 @@ namespace MediaBrowser.Common.Net.Handlers
             }
         }
 
-        private bool _LastDateModifiedDiscovered = false;
-        private DateTime? _LastDateModified = null;
-        /// <summary>
-        /// Gets the last date modified of the content being returned, if this can be determined.
-        /// This will be used to invalidate the cache, so it's not needed if CacheDuration is 0.
-        /// </summary>
-        public DateTime? LastDateModified
+        public virtual bool ShouldCompressResponse(string contentType)
         {
-            get
-            {
-                if (!_LastDateModifiedDiscovered)
-                {
-                    _LastDateModified = GetLastDateModified();
-                }
-
-                return _LastDateModified;
-            }
-        }
-        
-        public virtual bool CompressResponse
-        {
-            get
-            {
-                return true;
-            }
+            return true;
         }
 
         private bool ClientSupportsCompression
@@ -207,10 +185,12 @@ namespace MediaBrowser.Common.Net.Handlers
                 // When serving a range request, we need to return status code 206 to indicate a partial response body
                 StatusCode = SupportsByteRangeRequests && IsRangeRequest ? 206 : 200;
 
-                ctx.Response.ContentType = ContentType;
+                ctx.Response.ContentType = await GetContentType();
 
                 TimeSpan cacheDuration = CacheDuration;
 
+                DateTime? lastDateModified = await GetLastDateModified();
+
                 if (ctx.Request.Headers.AllKeys.Contains("If-Modified-Since"))
                 {
                     DateTime ifModifiedSince;
@@ -218,18 +198,20 @@ namespace MediaBrowser.Common.Net.Handlers
                     if (DateTime.TryParse(ctx.Request.Headers["If-Modified-Since"].Replace(" GMT", string.Empty), out ifModifiedSince))
                     {
                         // If the cache hasn't expired yet just return a 304
-                        if (IsCacheValid(ifModifiedSince, cacheDuration, LastDateModified))
+                        if (IsCacheValid(ifModifiedSince, cacheDuration, lastDateModified))
                         {
                             StatusCode = 304;
                         }
                     }
                 }
 
-                PrepareResponse();
+                await PrepareResponse();
 
                 if (IsResponseValid)
                 {
-                    await ProcessUncachedRequest(ctx, cacheDuration);
+                    bool compressResponse = ShouldCompressResponse(ctx.Response.ContentType) && ClientSupportsCompression;
+
+                    await ProcessUncachedRequest(ctx, compressResponse, cacheDuration, lastDateModified);
                 }
                 else
                 {
@@ -241,7 +223,7 @@ namespace MediaBrowser.Common.Net.Handlers
             {
                 // It might be too late if some response data has already been transmitted, but try to set this
                 ctx.Response.StatusCode = 500;
-                
+
                 Logger.LogException(ex);
             }
             finally
@@ -250,7 +232,7 @@ namespace MediaBrowser.Common.Net.Handlers
             }
         }
 
-        private async Task ProcessUncachedRequest(HttpListenerContext ctx, TimeSpan cacheDuration)
+        private async Task ProcessUncachedRequest(HttpListenerContext ctx, bool compressResponse, TimeSpan cacheDuration, DateTime? lastDateModified)
         {
             long? totalContentLength = TotalContentLength;
 
@@ -270,7 +252,7 @@ namespace MediaBrowser.Common.Net.Handlers
             }
 
             // Add the compression header
-            if (CompressResponse && ClientSupportsCompression)
+            if (compressResponse)
             {
                 ctx.Response.AddHeader("Content-Encoding", CompressionMethod);
             }
@@ -278,7 +260,7 @@ namespace MediaBrowser.Common.Net.Handlers
             // Add caching headers
             if (cacheDuration.Ticks > 0)
             {
-                CacheResponse(ctx.Response, cacheDuration, LastDateModified);
+                CacheResponse(ctx.Response, cacheDuration, lastDateModified);
             }
 
             // Set the status code
@@ -289,7 +271,7 @@ namespace MediaBrowser.Common.Net.Handlers
                 // Finally, write the response data
                 Stream outputStream = ctx.Response.OutputStream;
 
-                if (CompressResponse && ClientSupportsCompression)
+                if (compressResponse)
                 {
                     if (CompressionMethod.Equals("deflate", StringComparison.OrdinalIgnoreCase))
                     {
@@ -321,10 +303,11 @@ namespace MediaBrowser.Common.Net.Handlers
         }
 
         /// <summary>
-        /// Gives subclasses a chance to do and prep work, and also to validate data and set an error status code, if needed
+        /// Gives subclasses a chance to do any prep work, and also to validate data and set an error status code, if needed
         /// </summary>
-        protected virtual void PrepareResponse()
+        protected virtual Task PrepareResponse()
         {
+            return Task.Run(() => { });
         }
 
         protected abstract Task WriteResponseToOutputStream(Stream stream);
@@ -372,9 +355,11 @@ namespace MediaBrowser.Common.Net.Handlers
             return null;
         }
 
-        protected virtual DateTime? GetLastDateModified()
+        protected virtual Task<DateTime?> GetLastDateModified()
         {
-            return null;
+            DateTime? value = null;
+
+            return Task.Run<DateTime?>(() => { return value; });
         }
 
         private bool IsResponseValid

+ 14 - 23
MediaBrowser.Common/Net/Handlers/BaseJsonHandler.cs

@@ -6,19 +6,22 @@ namespace MediaBrowser.Common.Net.Handlers
 {
     public abstract class BaseJsonHandler<T> : BaseHandler
     {
-        public override string ContentType
+        public override Task<string> GetContentType()
         {
-            get { return MimeTypes.JsonMimeType; }
+            return Task.Run(() =>
+            {
+                return MimeTypes.JsonMimeType;
+            });
         }
 
         private bool _ObjectToSerializeEnsured = false;
         private T _ObjectToSerialize;
      
-        private void EnsureObjectToSerialize()
+        private async Task EnsureObjectToSerialize()
         {
             if (!_ObjectToSerializeEnsured)
             {
-                _ObjectToSerialize = GetObjectToSerialize();
+                _ObjectToSerialize = await GetObjectToSerialize();
 
                 if (_ObjectToSerialize == null)
                 {
@@ -29,30 +32,18 @@ namespace MediaBrowser.Common.Net.Handlers
             }
         }
 
-        private T ObjectToSerialize
-        {
-            get
-            {
-                EnsureObjectToSerialize();
-                return _ObjectToSerialize;
-            }
-        }
+        protected abstract Task<T> GetObjectToSerialize();
 
-        protected abstract T GetObjectToSerialize();
-
-        protected override void PrepareResponse()
+        protected override async Task PrepareResponse()
         {
-            base.PrepareResponse();
-
-            EnsureObjectToSerialize();
+            await EnsureObjectToSerialize();
         }
 
-        protected override Task WriteResponseToOutputStream(Stream stream)
+        protected async override Task WriteResponseToOutputStream(Stream stream)
         {
-            return Task.Run(() =>
-            {
-                JsonSerializer.SerializeToStream<T>(ObjectToSerialize, stream);
-            });
+            await EnsureObjectToSerialize();
+
+            JsonSerializer.SerializeToStream<T>(_ObjectToSerialize, stream);
         }
     }
 }

+ 26 - 30
MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs

@@ -77,27 +77,22 @@ namespace MediaBrowser.Common.Net.Handlers
             }
         }
 
-        public override bool CompressResponse
+        public override bool ShouldCompressResponse(string contentType)
         {
-            get
+            // Can't compress these
+            if (IsRangeRequest)
             {
-                // Can't compress these
-                if (IsRangeRequest)
-                {
-                    return false;
-                }
-
-                string contentType = ContentType;
-
-                // Don't compress media
-                if (contentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) || contentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase))
-                {
-                    return false;
-                }
+                return false;
+            }
 
-                // It will take some work to support compression within this handler
+            // Don't compress media
+            if (contentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) || contentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase))
+            {
                 return false;
             }
+
+            // It will take some work to support compression within this handler
+            return false;
         }
 
         protected override long? GetTotalContentLength()
@@ -105,31 +100,32 @@ namespace MediaBrowser.Common.Net.Handlers
             return SourceStream.Length;
         }
 
-        protected override DateTime? GetLastDateModified()
+        protected override Task<DateTime?> GetLastDateModified()
         {
-            EnsureSourceStream();
-
-            if (SourceStream == null)
+            return Task.Run<DateTime?>(() =>
             {
-                return null;
-            }
+                EnsureSourceStream();
+
+                if (SourceStream == null)
+                {
+                    return null;
+                }
 
-            return File.GetLastWriteTime(Path);
+                return File.GetLastWriteTime(Path);
+            });
         }
 
-        public override string ContentType
+        public override Task<string> GetContentType()
         {
-            get
+            return Task.Run(() =>
             {
                 return MimeTypes.GetMimeType(Path);
-            }
+            });
         }
 
-        protected override void PrepareResponse()
+        protected override Task PrepareResponse()
         {
-            base.PrepareResponse();
-
-            EnsureSourceStream();
+            return Task.Run(() => { EnsureSourceStream(); });
         }
 
         protected async override Task WriteResponseToOutputStream(Stream stream)

+ 3 - 6
MediaBrowser.Controller/IO/DirectoryWatchers.cs

@@ -75,7 +75,7 @@ namespace MediaBrowser.Controller.IO
             }
         }
 
-        private void TimerStopped(object stateInfo)
+        private async void TimerStopped(object stateInfo)
         {
             updateTimer.Dispose();
             updateTimer = null;
@@ -83,7 +83,7 @@ namespace MediaBrowser.Controller.IO
             List<string> paths = affectedPaths;
             affectedPaths = new List<string>();
 
-            //ProcessPathChanges(paths);
+            await ProcessPathChanges(paths);
         }
 
         private async Task ProcessPathChanges(IEnumerable<string> paths)
@@ -109,10 +109,7 @@ namespace MediaBrowser.Controller.IO
             }
             else
             {
-                /*Parallel.For(0, itemsToRefresh.Count, i =>
-                {
-                    Kernel.Instance.ReloadItem(itemsToRefresh[i]);
-                });*/
+                await Task.WhenAll(itemsToRefresh.Select(i => Kernel.Instance.ReloadItem(i)));
             }
         }
 

+ 42 - 0
MediaBrowser.Controller/Kernel.cs

@@ -248,5 +248,47 @@ namespace MediaBrowser.Controller
 
             return list;
         }
+
+        internal async Task ExecuteMetadataProviders(BaseEntity item, ItemResolveEventArgs args)
+        {
+            var supportedProviders = Kernel.Instance.MetadataProviders.Where(i => i.Supports(item));
+
+            // Start with non-internet providers. Run them sequentially
+            foreach (BaseMetadataProvider provider in supportedProviders.Where(i => !i.RequiresInternet))
+            {
+                await provider.Fetch(item, args);
+            }
+
+            var internetProviders = supportedProviders.Where(i => i.RequiresInternet);
+
+            if (internetProviders.Any())
+            {
+                // Now execute internet providers in parallel
+                await Task.WhenAll(
+                    internetProviders.Select(i => i.Fetch(item, args))
+                    );
+            }
+        }
+
+        protected override void DisposeComposableParts()
+        {
+            base.DisposeComposableParts();
+
+            DisposeProviders();
+        }
+
+        /// <summary>
+        /// Disposes all providers
+        /// </summary>
+        private void DisposeProviders()
+        {
+            if (MetadataProviders != null)
+            {
+                foreach (var provider in MetadataProviders)
+                {
+                    provider.Dispose();
+                }
+            }
+        }
     }
 }

+ 33 - 27
MediaBrowser.Controller/Library/ItemController.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
@@ -217,49 +218,49 @@ namespace MediaBrowser.Controller.Library
         /// <summary>
         /// Gets a Person
         /// </summary>
-        public Person GetPerson(string name)
+        public async Task<Person> GetPerson(string name)
         {
             string path = Path.Combine(Kernel.Instance.ApplicationPaths.PeoplePath, name);
 
-            return GetImagesByNameItem<Person>(path, name);
+            return await GetImagesByNameItem<Person>(path, name);
         }
 
         /// <summary>
         /// Gets a Studio
         /// </summary>
-        public Studio GetStudio(string name)
+        public async Task<Studio> GetStudio(string name)
         {
             string path = Path.Combine(Kernel.Instance.ApplicationPaths.StudioPath, name);
 
-            return GetImagesByNameItem<Studio>(path, name);
+            return await GetImagesByNameItem<Studio>(path, name);
         }
 
         /// <summary>
         /// Gets a Genre
         /// </summary>
-        public Genre GetGenre(string name)
+        public async Task<Genre> GetGenre(string name)
         {
             string path = Path.Combine(Kernel.Instance.ApplicationPaths.GenrePath, name);
 
-            return GetImagesByNameItem<Genre>(path, name);
+            return await GetImagesByNameItem<Genre>(path, name);
         }
 
         /// <summary>
         /// Gets a Year
         /// </summary>
-        public Year GetYear(int value)
+        public async Task<Year> GetYear(int value)
         {
             string path = Path.Combine(Kernel.Instance.ApplicationPaths.YearPath, value.ToString());
 
-            return GetImagesByNameItem<Year>(path, value.ToString());
+            return await GetImagesByNameItem<Year>(path, value.ToString());
         }
 
-        private Dictionary<string, object> ImagesByNameItemCache = new Dictionary<string, object>();
+        private ConcurrentDictionary<string, object> ImagesByNameItemCache = new ConcurrentDictionary<string, object>();
 
         /// <summary>
         /// Generically retrieves an IBN item
         /// </summary>
-        private T GetImagesByNameItem<T>(string path, string name)
+        private async Task<T> GetImagesByNameItem<T>(string path, string name)
             where T : BaseEntity, new()
         {
             string key = path.ToLower();
@@ -267,7 +268,9 @@ namespace MediaBrowser.Controller.Library
             // Look for it in the cache, if it's not there, create it
             if (!ImagesByNameItemCache.ContainsKey(key))
             {
-                ImagesByNameItemCache[key] = CreateImagesByNameItem<T>(path, name);
+                T obj = await CreateImagesByNameItem<T>(path, name);
+                ImagesByNameItemCache[key] = obj;
+                return obj;
             }
 
             return ImagesByNameItemCache[key] as T;
@@ -276,7 +279,7 @@ namespace MediaBrowser.Controller.Library
         /// <summary>
         /// Creates an IBN item based on a given path
         /// </summary>
-        private T CreateImagesByNameItem<T>(string path, string name)
+        private async Task<T> CreateImagesByNameItem<T>(string path, string name)
             where T : BaseEntity, new()
         {
             T item = new T();
@@ -284,25 +287,28 @@ namespace MediaBrowser.Controller.Library
             item.Name = name;
             item.Id = Kernel.GetMD5(path);
 
-            if (Directory.Exists(path))
+            if (!Directory.Exists(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");
-                }
+                Directory.CreateDirectory(path);
             }
-            else
+
+            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")))
             {
-                DateTime now = DateTime.Now;
+                item.PrimaryImagePath = Path.Combine(path, "folder.png");
+            }
+
+            var b = false;
 
-                item.DateCreated = now;
-                item.DateModified = now;
+            if (b)
+            {
+                await Kernel.Instance.ExecuteMetadataProviders(item, null);
             }
 
             return item;

+ 3 - 2
MediaBrowser.Controller/Providers/AudioInfoProvider.cs

@@ -12,12 +12,12 @@ namespace MediaBrowser.Controller.Providers
     [Export(typeof(BaseMetadataProvider))]
     public class AudioInfoProvider : BaseMetadataProvider
     {
-        public override bool Supports(BaseItem item)
+        public override bool Supports(BaseEntity item)
         {
             return item is Audio;
         }
 
-        public async override Task Fetch(BaseItem item, ItemResolveEventArgs args)
+        public async override Task Fetch(BaseEntity item, ItemResolveEventArgs args)
         {
             Audio audio = item as Audio;
 
@@ -62,6 +62,7 @@ namespace MediaBrowser.Controller.Providers
         {
             base.Init();
 
+            // Do this now so that we don't have to do this on every operation, which would require us to create a lock in order to maintain thread-safety
             for (int i = 0; i <= 9; i++)
             {
                 EnsureDirectory(Path.Combine(Kernel.Instance.ApplicationPaths.FFProbeAudioCacheDirectory, i.ToString()));

+ 18 - 5
MediaBrowser.Controller/Providers/BaseMetadataProvider.cs

@@ -1,10 +1,11 @@
-using System.Threading.Tasks;
+using System;
+using System.Threading.Tasks;
 using MediaBrowser.Controller.Events;
 using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Controller.Providers
 {
-    public abstract class BaseMetadataProvider
+    public abstract class BaseMetadataProvider : IDisposable
     {
         /// <summary>
         /// If the provider needs any startup routines, add them here
@@ -13,11 +14,23 @@ namespace MediaBrowser.Controller.Providers
         {
         }
 
-        public virtual bool Supports(BaseItem item)
+        /// <summary>
+        /// Disposes anything created during Init
+        /// </summary>
+        public virtual void Dispose()
+        {
+        }
+
+        public abstract bool Supports(BaseEntity item);
+
+        public virtual bool RequiresInternet
         {
-            return true;
+            get
+            {
+                return false;
+            }
         }
 
-        public abstract Task Fetch(BaseItem item, ItemResolveEventArgs args);
+        public abstract Task Fetch(BaseEntity item, ItemResolveEventArgs args);
     }
 }

+ 2 - 2
MediaBrowser.Controller/Providers/FolderProviderFromXml.cs

@@ -9,12 +9,12 @@ namespace MediaBrowser.Controller.Providers
     [Export(typeof(BaseMetadataProvider))]
     public class FolderProviderFromXml : BaseMetadataProvider
     {
-        public override bool Supports(BaseItem item)
+        public override bool Supports(BaseEntity item)
         {
             return item is Folder;
         }
 
-        public async override Task Fetch(BaseItem item, ItemResolveEventArgs args)
+        public async override Task Fetch(BaseEntity item, ItemResolveEventArgs args)
         {
             var metadataFile = args.GetFileByName("folder.xml");
 

+ 7 - 2
MediaBrowser.Controller/Providers/ImageFromMediaLocationProvider.cs

@@ -12,13 +12,18 @@ namespace MediaBrowser.Controller.Providers
     [Export(typeof(BaseMetadataProvider))]
     public class ImageFromMediaLocationProvider : BaseMetadataProvider
     {
-        public override Task Fetch(BaseItem item, ItemResolveEventArgs args)
+        public override bool Supports(BaseEntity item)
+        {
+            return item is BaseItem;
+        }
+        
+        public override Task Fetch(BaseEntity item, ItemResolveEventArgs args)
         {
             return Task.Run(() =>
             {
                 if (args.IsFolder)
                 {
-                    PopulateImages(item, args);
+                    PopulateImages(item as BaseItem, args);
                 }
             });
         }

+ 9 - 2
MediaBrowser.Controller/Providers/LocalTrailerProvider.cs

@@ -10,8 +10,15 @@ namespace MediaBrowser.Controller.Providers
     [Export(typeof(BaseMetadataProvider))]
     public class LocalTrailerProvider : BaseMetadataProvider
     {
-        public async override Task Fetch(BaseItem item, ItemResolveEventArgs args)
+        public override bool Supports(BaseEntity item)
         {
+            return item is BaseItem;
+        }
+
+        public async override Task Fetch(BaseEntity item, ItemResolveEventArgs args)
+        {
+            BaseItem baseItem = item as BaseItem;
+
             var trailerPath = args.GetFolderByName("trailers");
 
             if (trailerPath.HasValue)
@@ -32,7 +39,7 @@ namespace MediaBrowser.Controller.Providers
                     }
                 }
 
-                item.LocalTrailers = localTrailers;
+                baseItem.LocalTrailers = localTrailers;
             }
         }
     }

+ 6 - 18
MediaBrowser.Controller/Resolvers/BaseItemResolver.cs

@@ -2,13 +2,12 @@
 using System.IO;
 using System.Threading.Tasks;
 using MediaBrowser.Controller.Events;
-using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Controller.Resolvers
 {
     public abstract class BaseItemResolver<T> : IBaseItemResolver
-        where T : BaseItem, new ()
+        where T : BaseItem, new()
     {
         protected virtual T Resolve(ItemResolveEventArgs args)
         {
@@ -18,7 +17,7 @@ namespace MediaBrowser.Controller.Resolvers
         /// <summary>
         /// Sets initial values on the newly resolved item
         /// </summary>
-        protected virtual void SetItemValues(T item, ItemResolveEventArgs args)
+        protected virtual void SetInitialItemValues(T item, ItemResolveEventArgs args)
         {
             // If the subclass didn't specify this
             if (string.IsNullOrEmpty(item.Path))
@@ -38,35 +37,24 @@ namespace MediaBrowser.Controller.Resolvers
         public async Task<BaseItem> ResolvePath(ItemResolveEventArgs args)
         {
             T item = Resolve(args);
-            
+
             if (item != null)
             {
                 // Set initial values on the newly resolved item
-                SetItemValues(item, args);
+                SetInitialItemValues(item, args);
 
                 // Make sure the item has a name
                 EnsureName(item);
 
                 // Make sure DateCreated and DateModified have values
                 EnsureDates(item);
-                
-                await FetchMetadataFromProviders(item, args);
+
+                await Kernel.Instance.ExecuteMetadataProviders(item, args);
             }
 
             return item;
         }
 
-        private async Task FetchMetadataFromProviders(T item, ItemResolveEventArgs args)
-        {
-            foreach (BaseMetadataProvider provider in Kernel.Instance.MetadataProviders)
-            {
-                if (provider.Supports(item))
-                {
-                    await provider.Fetch(item, args);
-                }
-            }
-        }
-
         private void EnsureName(T item)
         {
             // If the subclass didn't supply a name, add it here

+ 2 - 2
MediaBrowser.Controller/Resolvers/FolderResolver.cs

@@ -21,9 +21,9 @@ namespace MediaBrowser.Controller.Resolvers
     public abstract class BaseFolderResolver<TItemType> : BaseItemResolver<TItemType>
         where TItemType : Folder, new()
     {
-        protected override void SetItemValues(TItemType item, ItemResolveEventArgs args)
+        protected override void SetInitialItemValues(TItemType item, ItemResolveEventArgs args)
         {
-            base.SetItemValues(item, args);
+            base.SetInitialItemValues(item, args);
 
             item.IsRoot = args.Parent == null;
         }

+ 2 - 2
MediaBrowser.Controller/Resolvers/VirtualFolderResolver.cs

@@ -18,7 +18,7 @@ namespace MediaBrowser.Controller.Resolvers
             return null;
         }
 
-        protected override void SetItemValues(VirtualFolder item, ItemResolveEventArgs args)
+        protected override void SetInitialItemValues(VirtualFolder item, ItemResolveEventArgs args)
         {
             // Set the name initially by stripping off the [CollectionType=...]
             // The name can always be overridden later by folder.xml
@@ -34,7 +34,7 @@ namespace MediaBrowser.Controller.Resolvers
                 item.CollectionType = pathName.Substring(index + srch.Length).TrimEnd(']');
             }
 
-            base.SetItemValues(item, args);
+            base.SetInitialItemValues(item, args);
         }
 
     }

+ 1 - 1
MediaBrowser.Controller/Xml/BaseItemXmlParser.cs

@@ -2,9 +2,9 @@
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
+using System.Threading.Tasks;
 using System.Xml;
 using MediaBrowser.Model.Entities;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Controller.Xml
 {

+ 2 - 2
MediaBrowser.Movies/Providers/MovieProviderFromXml.cs

@@ -11,12 +11,12 @@ namespace MediaBrowser.Movies.Providers
     [Export(typeof(BaseMetadataProvider))]
     public class MovieProviderFromXml : BaseMetadataProvider
     {
-        public override bool Supports(BaseItem item)
+        public override bool Supports(BaseEntity item)
         {
             return item is Movie;
         }
 
-        public async override Task Fetch(BaseItem item, ItemResolveEventArgs args)
+        public async override Task Fetch(BaseEntity item, ItemResolveEventArgs args)
         {
             var metadataFile = args.GetFileByName("movie.xml");
 

+ 2 - 2
MediaBrowser.Movies/Resolvers/MovieResolver.cs

@@ -88,9 +88,9 @@ namespace MediaBrowser.Movies.Resolvers
             }
         }
 
-        protected override void SetItemValues(Movie item, ItemResolveEventArgs args)
+        protected override void SetInitialItemValues(Movie item, ItemResolveEventArgs args)
         {
-            base.SetItemValues(item, args);
+            base.SetInitialItemValues(item, args);
 
             PopulateBonusFeatures(item, args);
         }

+ 1 - 2
MediaBrowser.ServerApplication/MainWindow.xaml.cs

@@ -2,10 +2,9 @@
 using System.Diagnostics;
 using System.Windows;
 using MediaBrowser.Common.Logging;
+using MediaBrowser.Common.UI;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.Progress;
-using System.Threading.Tasks;
-using MediaBrowser.Common.UI;
 
 namespace MediaBrowser.ServerApplication
 {

+ 6 - 4
MediaBrowser.TV/Providers/EpisodeImageFromMediaLocationProvider.cs

@@ -13,22 +13,24 @@ namespace MediaBrowser.TV.Providers
     [Export(typeof(BaseMetadataProvider))]
     public class EpisodeImageFromMediaLocationProvider : BaseMetadataProvider
     {
-        public override bool Supports(BaseItem item)
+        public override bool Supports(BaseEntity item)
         {
             return item is Episode;
         }
 
-        public override Task Fetch(BaseItem item, ItemResolveEventArgs args)
+        public override Task Fetch(BaseEntity item, ItemResolveEventArgs args)
         {
             return Task.Run(() =>
             {
+                Episode episode = item as Episode;
+
                 string metadataFolder = Path.Combine(args.Parent.Path, "metadata");
 
-                string episodeFileName = Path.GetFileName(item.Path);
+                string episodeFileName = Path.GetFileName(episode.Path);
 
                 Season season = args.Parent as Season;
 
-                SetPrimaryImagePath(item as Episode, season, metadataFolder, episodeFileName);
+                SetPrimaryImagePath(episode, season, metadataFolder, episodeFileName);
             });
         }
 

+ 6 - 4
MediaBrowser.TV/Providers/EpisodeProviderFromXml.cs

@@ -14,20 +14,22 @@ namespace MediaBrowser.TV.Providers
     [Export(typeof(BaseMetadataProvider))]
     public class EpisodeProviderFromXml : BaseMetadataProvider
     {
-        public override bool Supports(BaseItem item)
+        public override bool Supports(BaseEntity item)
         {
             return item is Episode;
         }
 
-        public async override Task Fetch(BaseItem item, ItemResolveEventArgs args)
+        public async override Task Fetch(BaseEntity item, ItemResolveEventArgs args)
         {
             string metadataFolder = Path.Combine(args.Parent.Path, "metadata");
 
-            string episodeFileName = Path.GetFileName(item.Path);
+            Episode episode = item as Episode;
+
+            string episodeFileName = Path.GetFileName(episode.Path);
 
             string metadataFile = Path.Combine(metadataFolder, Path.ChangeExtension(episodeFileName, ".xml"));
 
-            await FetchMetadata(item as Episode, args.Parent as Season, metadataFile);
+            await FetchMetadata(episode, args.Parent as Season, metadataFile);
         }
 
         private async Task FetchMetadata(Episode item, Season season, string metadataFile)

+ 2 - 2
MediaBrowser.TV/Providers/SeriesProviderFromXml.cs

@@ -11,12 +11,12 @@ namespace MediaBrowser.TV.Providers
     [Export(typeof(BaseMetadataProvider))]
     public class SeriesProviderFromXml : BaseMetadataProvider
     {
-        public override bool Supports(BaseItem item)
+        public override bool Supports(BaseEntity item)
         {
             return item is Series;
         }
 
-        public async override Task Fetch(BaseItem item, ItemResolveEventArgs args)
+        public async override Task Fetch(BaseEntity item, ItemResolveEventArgs args)
         {
             var metadataFile = args.GetFileByName("series.xml");