Browse Source

Re-worked provider id's, api client, moved people to the api item wrapper and added server error handling

LukePulverenti Luke Pulverenti luke pulverenti 13 năm trước cách đây
mục cha
commit
3f1af19ce7
36 tập tin đã thay đổi với 474 bổ sung373 xóa
  1. 12 7
      MediaBrowser.Api/ApiService.cs
  2. 6 5
      MediaBrowser.Api/HttpHandlers/BaseMediaHandler.cs
  3. 6 8
      MediaBrowser.Api/HttpHandlers/GenresHandler.cs
  4. 60 15
      MediaBrowser.Api/HttpHandlers/ImageHandler.cs
  5. 11 6
      MediaBrowser.Api/HttpHandlers/ItemHandler.cs
  6. 6 8
      MediaBrowser.Api/HttpHandlers/ItemListHandler.cs
  7. 0 20
      MediaBrowser.Api/HttpHandlers/JsonHandler.cs
  8. 5 7
      MediaBrowser.Api/HttpHandlers/PersonHandler.cs
  9. 5 7
      MediaBrowser.Api/HttpHandlers/PluginConfigurationHandler.cs
  10. 17 19
      MediaBrowser.Api/HttpHandlers/PluginsHandler.cs
  11. 6 8
      MediaBrowser.Api/HttpHandlers/StudiosHandler.cs
  12. 5 7
      MediaBrowser.Api/HttpHandlers/UserConfigurationHandler.cs
  13. 5 7
      MediaBrowser.Api/HttpHandlers/UsersHandler.cs
  14. 6 8
      MediaBrowser.Api/HttpHandlers/YearsHandler.cs
  15. 2 2
      MediaBrowser.Api/ImageProcessor.cs
  16. 0 1
      MediaBrowser.Api/MediaBrowser.Api.csproj
  17. 25 9
      MediaBrowser.ApiInteraction/ApiClient.cs
  18. 0 52
      MediaBrowser.ApiInteraction/BaseClient.cs
  19. 11 0
      MediaBrowser.ApiInteraction/IHttpClient.cs
  20. 1 1
      MediaBrowser.ApiInteraction/MediaBrowser.ApiInteraction.csproj
  21. 1 8
      MediaBrowser.Common/Configuration/ApplicationPaths.cs
  22. 57 39
      MediaBrowser.Common/Net/Handlers/BaseHandler.cs
  23. 49 2
      MediaBrowser.Common/Net/Handlers/BaseJsonHandler.cs
  24. 79 79
      MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs
  25. 2 0
      MediaBrowser.Common/Net/MimeTypes.cs
  26. 27 1
      MediaBrowser.Controller/Xml/BaseItemXmlParser.cs
  27. 2 5
      MediaBrowser.Model/Entities/ApiBaseItem.cs
  28. 45 0
      MediaBrowser.Model/Entities/BaseItem.cs
  29. 11 0
      MediaBrowser.Model/Entities/MetadataProviders.cs
  30. 1 0
      MediaBrowser.Model/MediaBrowser.Model.csproj
  31. 0 1
      MediaBrowser.Movies/MediaBrowser.Movies.csproj
  32. 0 32
      MediaBrowser.Movies/Metadata/MovieXmlParser.cs
  33. 2 2
      MediaBrowser.Movies/Resolvers/MovieResolver.cs
  34. 2 4
      MediaBrowser.TV/Entities/Series.cs
  35. 1 2
      MediaBrowser.TV/Metadata/EpisodeXmlParser.cs
  36. 6 1
      MediaBrowser.TV/Metadata/SeriesXmlParser.cs

+ 12 - 7
MediaBrowser.Api/ApiService.cs

@@ -58,6 +58,8 @@ namespace MediaBrowser.Api
                 {
                     wrapper.Children = Kernel.Instance.GetParentalAllowedChildren(folder, userId).Select(c => GetSerializationObject(c, false, userId));
                 }
+
+                wrapper.People = item.People;
             }
 
             return wrapper;
@@ -136,15 +138,18 @@ namespace MediaBrowser.Api
 
                     _FFMpegPath = Path.Combine(FFMpegDirectory, filename);
 
-                    if (!File.Exists(_FFMpegPath))
+                    // Always re-extract the first time to handle new versions
+                    if (File.Exists(_FFMpegPath))
+                    {
+                        File.Delete(_FFMpegPath);
+                    }
+
+                    // Extract ffprobe
+                    using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("MediaBrowser.Api.FFMpeg." + filename))
                     {
-                        // Extract ffprobe
-                        using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("MediaBrowser.Api.FFMpeg." + filename))
+                        using (FileStream fileStream = new FileStream(_FFMpegPath, FileMode.Create))
                         {
-                            using (FileStream fileStream = new FileStream(_FFMpegPath, FileMode.Create))
-                            {
-                                stream.CopyTo(fileStream);
-                            }
+                            stream.CopyTo(fileStream);
                         }
                     }
                 }

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

@@ -107,17 +107,18 @@ namespace MediaBrowser.Api.HttpHandlers
             }
         }
 
-        public override void ProcessRequest(HttpListenerContext ctx)
+        public override async Task ProcessRequest(HttpListenerContext ctx)
         {
             HttpListenerContext = ctx;
 
             if (!RequiresConversion())
             {
-                new StaticFileHandler() { Path = LibraryItem.Path }.ProcessRequest(ctx);
-                return;
+                await new StaticFileHandler() { Path = LibraryItem.Path }.ProcessRequest(ctx);
+            }
+            else
+            {
+                await base.ProcessRequest(ctx);
             }
-
-            base.ProcessRequest(ctx);
         }
 
         protected abstract string GetCommandLineArguments();

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

@@ -1,20 +1,18 @@
 using System;
+using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Api.HttpHandlers
 {
-    public class GenresHandler : JsonHandler
+    public class GenresHandler : BaseJsonHandler
     {
-        protected sealed override object ObjectToSerialize
+        protected override object GetObjectToSerialize()
         {
-            get
-            {
-                Folder parent = ApiService.GetItemById(QueryString["id"]) as Folder;
-                Guid userId = Guid.Parse(QueryString["userid"]);
+            Folder parent = ApiService.GetItemById(QueryString["id"]) as Folder;
+            Guid userId = Guid.Parse(QueryString["userid"]);
 
-                return Kernel.Instance.GetAllGenres(parent, userId);
-            }
+            return Kernel.Instance.GetAllGenres(parent, userId);
         }
     }
 }

+ 60 - 15
MediaBrowser.Api/HttpHandlers/ImageHandler.cs

@@ -2,6 +2,8 @@
 using System.IO;
 using System.Linq;
 using System.Threading.Tasks;
+using MediaBrowser.Common.Logging;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.Entities;
@@ -10,12 +12,12 @@ namespace MediaBrowser.Api.HttpHandlers
 {
     public class ImageHandler : BaseHandler
     {
-        private string _ImagePath = string.Empty;
+        private string _ImagePath = null;
         private string ImagePath
         {
             get
             {
-                if (string.IsNullOrEmpty(_ImagePath))
+                if (_ImagePath == null)
                 {
                     _ImagePath = GetImagePath();
                 }
@@ -24,18 +26,61 @@ namespace MediaBrowser.Api.HttpHandlers
             }
         }
 
-        public override string ContentType
+        private Stream _SourceStream = null;
+        private Stream SourceStream
         {
             get
             {
-                string extension = Path.GetExtension(ImagePath);
+                EnsureSourceStream();
+
+                return _SourceStream;
+            }
+        }
 
-                if (extension.EndsWith("png", StringComparison.OrdinalIgnoreCase))
+
+        private bool _SourceStreamEnsured = false;
+        private void EnsureSourceStream()
+        {
+            if (!_SourceStreamEnsured)
+            {
+                try
+                {
+                    _SourceStream = File.OpenRead(ImagePath);
+                }
+                catch (FileNotFoundException ex)
+                {
+                    StatusCode = 404;
+                    Logger.LogException(ex);
+                }
+                catch (DirectoryNotFoundException ex)
+                {
+                    StatusCode = 404;
+                    Logger.LogException(ex);
+                }
+                catch (UnauthorizedAccessException ex)
+                {
+                    StatusCode = 403;
+                    Logger.LogException(ex);
+                }
+                finally
                 {
-                    return "image/png";
+                    _SourceStreamEnsured = true;
                 }
+            }
+        }
+        
+        public override string ContentType
+        {
+            get
+            {
+                EnsureSourceStream();
 
-                return "image/jpeg";
+                if (SourceStream == null)
+                {
+                    return null;
+                }
+                
+                return MimeTypes.GetMimeType(ImagePath);
             }
         }
 
@@ -49,14 +94,14 @@ namespace MediaBrowser.Api.HttpHandlers
 
         protected override DateTime? GetLastDateModified()
         {
-            try
-            {
-                return File.GetLastWriteTime(ImagePath);
-            }
-            catch
+            EnsureSourceStream();
+
+            if (SourceStream == null)
             {
-                return base.GetLastDateModified();
+                return null;
             }
+
+            return File.GetLastWriteTime(ImagePath);
         }
 
         private int? Height
@@ -142,7 +187,7 @@ namespace MediaBrowser.Api.HttpHandlers
 
                 if (string.IsNullOrEmpty(imageType))
                 {
-                    return Model.Entities.ImageType.Primary;
+                    return ImageType.Primary;
                 }
 
                 return (ImageType)Enum.Parse(typeof(ImageType), imageType, true);
@@ -153,7 +198,7 @@ namespace MediaBrowser.Api.HttpHandlers
         {
             return Task.Run(() =>
             {
-                ImageProcessor.ProcessImage(ImagePath, stream, Width, Height, MaxWidth, MaxHeight, Quality);
+                ImageProcessor.ProcessImage(SourceStream, stream, Width, Height, MaxWidth, MaxHeight, Quality);
             });
         }
 

+ 11 - 6
MediaBrowser.Api/HttpHandlers/ItemHandler.cs

@@ -1,18 +1,23 @@
 using System;
+using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Api.HttpHandlers
 {
-    public class ItemHandler : JsonHandler
+    public class ItemHandler : BaseJsonHandler
     {
-        protected sealed override object ObjectToSerialize
+        protected sealed override object GetObjectToSerialize()
         {
-            get
-            {
-                Guid userId = Guid.Parse(QueryString["userid"]);
+            Guid userId = Guid.Parse(QueryString["userid"]);
+
+            BaseItem item = ItemToSerialize;
 
-                return ApiService.GetSerializationObject(ItemToSerialize, true, userId);
+            if (item == null)
+            {
+                return null;
             }
+
+            return ApiService.GetSerializationObject(item, true, userId);
         }
 
         protected virtual BaseItem ItemToSerialize

+ 6 - 8
MediaBrowser.Api/HttpHandlers/ItemListHandler.cs

@@ -1,22 +1,20 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Api.HttpHandlers
 {
-    public abstract class ItemListHandler : JsonHandler
+    public abstract class ItemListHandler : BaseJsonHandler
     {
-        protected sealed override object ObjectToSerialize
+        protected override object GetObjectToSerialize()
         {
-            get
+            return ItemsToSerialize.Select(i =>
             {
-                return ItemsToSerialize.Select(i =>
-                {
-                    return ApiService.GetSerializationObject(i, false, UserId);
+                return ApiService.GetSerializationObject(i, false, UserId);
 
-                });
-            }
+            });
         }
 
         protected abstract IEnumerable<BaseItem> ItemsToSerialize

+ 0 - 20
MediaBrowser.Api/HttpHandlers/JsonHandler.cs

@@ -1,20 +0,0 @@
-using System.IO;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net.Handlers;
-using MediaBrowser.Common.Serialization;
-
-namespace MediaBrowser.Api.HttpHandlers
-{
-    public abstract class JsonHandler : BaseJsonHandler
-    {
-        protected abstract object ObjectToSerialize { get; }
-
-        protected override Task WriteResponseToOutputStream(Stream stream)
-        {
-            return Task.Run(() =>
-            {
-                JsonSerializer.SerializeToStream(ObjectToSerialize, stream);
-            });
-        }
-    }
-}

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

@@ -1,15 +1,13 @@
-using MediaBrowser.Controller;
+using MediaBrowser.Common.Net.Handlers;
+using MediaBrowser.Controller;
 
 namespace MediaBrowser.Api.HttpHandlers
 {
-    public class PersonHandler : JsonHandler
+    public class PersonHandler : BaseJsonHandler
     {
-        protected sealed override object ObjectToSerialize
+        protected override object GetObjectToSerialize()
         {
-            get
-            {
-                return Kernel.Instance.ItemController.GetPerson(QueryString["name"]);
-            }
+            return Kernel.Instance.ItemController.GetPerson(QueryString["name"]);
         }
     }
 }

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

@@ -1,19 +1,17 @@
 using System;
 using System.Linq;
+using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 
 namespace MediaBrowser.Api.HttpHandlers
 {
-    public class PluginConfigurationHandler : JsonHandler
+    public class PluginConfigurationHandler : BaseJsonHandler
     {
-        protected override object ObjectToSerialize
+        protected override object GetObjectToSerialize()
         {
-            get
-            {
-                string pluginName = QueryString["name"];
+            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;
         }
     }
 }

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

@@ -1,4 +1,5 @@
 using System.Linq;
+using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.Plugins;
 
@@ -7,31 +8,28 @@ namespace MediaBrowser.Api.HttpHandlers
     /// <summary>
     /// Provides information about installed plugins
     /// </summary>
-    public class PluginsHandler : JsonHandler
+    public class PluginsHandler : BaseJsonHandler
     {
-        protected override object ObjectToSerialize
+        protected override object GetObjectToSerialize()
         {
-            get
+            var plugins = Kernel.Instance.Plugins.Select(p =>
             {
-                var plugins = Kernel.Instance.Plugins.Select(p =>
+                return new PluginInfo()
                 {
-                    return new PluginInfo()
-                    {
-                        Path = p.Path,
-                        Name = p.Name,
-                        Enabled = p.Enabled,
-                        DownloadToUI = p.DownloadToUI,
-                        Version = p.Version
-                    };
-                });
+                    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);
-                }
-
-                return plugins;
+            if (QueryString["uionly"] == "1")
+            {
+                plugins = plugins.Where(p => p.DownloadToUI);
             }
+
+            return plugins;
         }
     }
 }

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

@@ -1,20 +1,18 @@
 using System;
+using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Api.HttpHandlers
 {
-    public class StudiosHandler : JsonHandler
+    public class StudiosHandler : BaseJsonHandler
     {
-        protected override object ObjectToSerialize
+        protected override object GetObjectToSerialize()
         {
-            get
-            {
-                Folder parent = ApiService.GetItemById(QueryString["id"]) as Folder;
-                Guid userId = Guid.Parse(QueryString["userid"]);
+            Folder parent = ApiService.GetItemById(QueryString["id"]) as Folder;
+            Guid userId = Guid.Parse(QueryString["userid"]);
 
-                return Kernel.Instance.GetAllStudios(parent, userId);
-            }
+            return Kernel.Instance.GetAllStudios(parent, userId);
         }
     }
 }

+ 5 - 7
MediaBrowser.Api/HttpHandlers/UserConfigurationHandler.cs

@@ -1,18 +1,16 @@
 using System;
+using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 
 namespace MediaBrowser.Api.HttpHandlers
 {
-    public class UserConfigurationHandler : JsonHandler
+    public class UserConfigurationHandler : BaseJsonHandler
     {
-        protected override object ObjectToSerialize
+        protected override object GetObjectToSerialize()
         {
-            get
-            {
-                Guid userId = Guid.Parse(QueryString["userid"]);
+            Guid userId = Guid.Parse(QueryString["userid"]);
 
-                return Kernel.Instance.GetUserConfiguration(userId);
-            }
+            return Kernel.Instance.GetUserConfiguration(userId);
         }
     }
 }

+ 5 - 7
MediaBrowser.Api/HttpHandlers/UsersHandler.cs

@@ -1,15 +1,13 @@
-using MediaBrowser.Controller;
+using MediaBrowser.Common.Net.Handlers;
+using MediaBrowser.Controller;
 
 namespace MediaBrowser.Api.HttpHandlers
 {
-    class UsersHandler : JsonHandler
+    class UsersHandler : BaseJsonHandler
     {
-        protected override object ObjectToSerialize
+        protected override object GetObjectToSerialize()
         {
-            get
-            {
-                return Kernel.Instance.Users;
-            }
+            return Kernel.Instance.Users;
         }
     }
 }

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

@@ -1,20 +1,18 @@
 using System;
+using MediaBrowser.Common.Net.Handlers;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Api.HttpHandlers
 {
-    public class YearsHandler : JsonHandler
+    public class YearsHandler : BaseJsonHandler
     {
-        protected override object ObjectToSerialize
+        protected override object GetObjectToSerialize()
         {
-            get
-            {
-                Folder parent = ApiService.GetItemById(QueryString["id"]) as Folder;
-                Guid userId = Guid.Parse(QueryString["userid"]);
+            Folder parent = ApiService.GetItemById(QueryString["id"]) as Folder;
+            Guid userId = Guid.Parse(QueryString["userid"]);
 
-                return Kernel.Instance.GetAllYears(parent, userId);
-            }
+            return Kernel.Instance.GetAllYears(parent, userId);
         }
     }
 }

+ 2 - 2
MediaBrowser.Api/ImageProcessor.cs

@@ -8,9 +8,9 @@ namespace MediaBrowser.Api
 {
     public static class ImageProcessor
     {
-        public static void ProcessImage(string path, Stream toStream, int? width, int? height, int? maxWidth, int? maxHeight, int? quality)
+        public static void ProcessImage(Stream sourceImageStream, Stream toStream, int? width, int? height, int? maxWidth, int? maxHeight, int? quality)
         {
-            Image originalImage = Image.FromFile(path);
+            Image originalImage = Image.FromStream(sourceImageStream);
 
             var newWidth = originalImage.Width;
             var newHeight = originalImage.Height;

+ 0 - 1
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -56,7 +56,6 @@
     <Compile Include="HttpHandlers\ItemHandler.cs" />
     <Compile Include="HttpHandlers\ItemListHandler.cs" />
     <Compile Include="HttpHandlers\ItemsWithPersonHandler.cs" />
-    <Compile Include="HttpHandlers\JsonHandler.cs" />
     <Compile Include="HttpHandlers\PersonHandler.cs" />
     <Compile Include="HttpHandlers\PluginConfigurationHandler.cs" />
     <Compile Include="HttpHandlers\PluginsHandler.cs" />

+ 25 - 9
MediaBrowser.ApiInteraction/ApiClient.cs

@@ -2,7 +2,6 @@
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
-using System.Net.Http;
 using System.Threading.Tasks;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
@@ -10,20 +9,32 @@ using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.ApiInteraction
 {
-    public class ApiClient : BaseClient
+    public class ApiClient : IDisposable
     {
-        public IJsonSerializer JsonSerializer { get; set; }
+        /// <summary>
+        /// Gets or sets the server host name (myserver or 192.168.x.x)
+        /// </summary>
+        public string ServerHostName { get; set; }
 
-        public ApiClient()
-            : base()
-        {
-        }
+        /// <summary>
+        /// Gets or sets the port number used by the API
+        /// </summary>
+        public int ServerApiPort { get; set; }
 
-        public ApiClient(HttpClientHandler handler)
-            : base(handler)
+        /// <summary>
+        /// Gets the current api url based on hostname and port.
+        /// </summary>
+        protected string ApiUrl
         {
+            get
+            {
+                return string.Format("http://{0}:{1}/mediabrowser/api", ServerHostName, ServerApiPort);
+            }
         }
 
+        public IHttpClient HttpClient { get; set; }
+        public IJsonSerializer JsonSerializer { get; set; }
+
         /// <summary>
         /// Gets an image url that can be used to download an image from the api
         /// </summary>
@@ -278,5 +289,10 @@ namespace MediaBrowser.ApiInteraction
                 return JsonSerializer.DeserializeFromStream<IEnumerable<ApiBaseItemWrapper<ApiBaseItem>>>(stream);
             }
         }
+
+        public void Dispose()
+        {
+            HttpClient.Dispose();
+        }
     }
 }

+ 0 - 52
MediaBrowser.ApiInteraction/BaseClient.cs

@@ -1,52 +0,0 @@
-using System;
-using System.Net;
-using System.Net.Http;
-
-namespace MediaBrowser.ApiInteraction
-{
-    /// <summary>
-    /// Provides a base class used by the api and image services
-    /// </summary>
-    public abstract class BaseClient : IDisposable
-    {
-        /// <summary>
-        /// Gets or sets the server host name (myserver or 192.168.x.x)
-        /// </summary>
-        public string ServerHostName { get; set; }
-
-        /// <summary>
-        /// Gets or sets the port number used by the API
-        /// </summary>
-        public int ServerApiPort { get; set; }
-
-        /// <summary>
-        /// Gets the current api url based on hostname and port.
-        /// </summary>
-        protected string ApiUrl
-        {
-            get
-            {
-                return string.Format("http://{0}:{1}/mediabrowser/api", ServerHostName, ServerApiPort);
-            }
-        }
-
-        protected HttpClient HttpClient { get; private set; }
-
-        public BaseClient()
-            : this(new HttpClientHandler())
-        {
-        }
-
-        public BaseClient(HttpClientHandler clientHandler)
-        {
-            clientHandler.AutomaticDecompression = DecompressionMethods.Deflate;
-
-            HttpClient = new HttpClient(clientHandler);
-        }
-
-        public void Dispose()
-        {
-            HttpClient.Dispose();
-        }
-    }
-}

+ 11 - 0
MediaBrowser.ApiInteraction/IHttpClient.cs

@@ -0,0 +1,11 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.ApiInteraction
+{
+    public interface IHttpClient : IDisposable  
+    {
+        Task<Stream> GetStreamAsync(string url);
+    }
+}

+ 1 - 1
MediaBrowser.ApiInteraction/MediaBrowser.ApiInteraction.csproj

@@ -40,7 +40,7 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="ApiClient.cs" />
-    <Compile Include="BaseClient.cs" />
+    <Compile Include="IHttpClient.cs" />
     <Compile Include="IJsonSerializer.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
   </ItemGroup>

+ 1 - 8
MediaBrowser.Common/Configuration/ApplicationPaths.cs

@@ -1,12 +1,5 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System.Configuration;
 using System.IO;
-using System.ComponentModel.Composition;
-using System.ComponentModel.Composition.Hosting;
-using System.Configuration;
 using System.Reflection;
 
 namespace MediaBrowser.Common.Configuration

+ 57 - 39
MediaBrowser.Common/Net/Handlers/BaseHandler.cs

@@ -185,7 +185,7 @@ namespace MediaBrowser.Common.Net.Handlers
             }
         }
 
-        public virtual void ProcessRequest(HttpListenerContext ctx)
+        public virtual async Task ProcessRequest(HttpListenerContext ctx)
         {
             HttpListenerContext = ctx;
 
@@ -196,46 +196,61 @@ namespace MediaBrowser.Common.Net.Handlers
 
             ctx.Response.KeepAlive = true;
 
-            if (SupportsByteRangeRequests && IsRangeRequest)
+            try
             {
-                ctx.Response.Headers["Accept-Ranges"] = "bytes";
-            }
-            
-            // Set the initial status code
-            // When serving a range request, we need to return status code 206 to indicate a partial response body
-            StatusCode = SupportsByteRangeRequests && IsRangeRequest ? 206 : 200;
+                if (SupportsByteRangeRequests && IsRangeRequest)
+                {
+                    ctx.Response.Headers["Accept-Ranges"] = "bytes";
+                }
 
-            ctx.Response.ContentType = ContentType;
+                // Set the initial status code
+                // When serving a range request, we need to return status code 206 to indicate a partial response body
+                StatusCode = SupportsByteRangeRequests && IsRangeRequest ? 206 : 200;
 
-            TimeSpan cacheDuration = CacheDuration;
+                ctx.Response.ContentType = ContentType;
 
-            if (ctx.Request.Headers.AllKeys.Contains("If-Modified-Since"))
-            {
-                DateTime ifModifiedSince;
+                TimeSpan cacheDuration = CacheDuration;
 
-                if (DateTime.TryParse(ctx.Request.Headers["If-Modified-Since"].Replace(" GMT", string.Empty), out ifModifiedSince))
+                if (ctx.Request.Headers.AllKeys.Contains("If-Modified-Since"))
                 {
-                    // If the cache hasn't expired yet just return a 304
-                    if (IsCacheValid(ifModifiedSince, cacheDuration, LastDateModified))
+                    DateTime ifModifiedSince;
+
+                    if (DateTime.TryParse(ctx.Request.Headers["If-Modified-Since"].Replace(" GMT", string.Empty), out ifModifiedSince))
                     {
-                        StatusCode = 304;
+                        // If the cache hasn't expired yet just return a 304
+                        if (IsCacheValid(ifModifiedSince, cacheDuration, LastDateModified))
+                        {
+                            StatusCode = 304;
+                        }
                     }
                 }
-            }
 
-            if (StatusCode == 200 || StatusCode == 206)
+                PrepareResponse();
+
+                if (IsResponseValid)
+                {
+                    await ProcessUncachedRequest(ctx, cacheDuration);
+                }
+                else
+                {
+                    ctx.Response.StatusCode = StatusCode;
+                    ctx.Response.SendChunked = false;
+                }
+            }
+            catch (Exception ex)
             {
-                ProcessUncachedResponse(ctx, cacheDuration);
+                // 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);
             }
-            else
+            finally
             {
-                ctx.Response.StatusCode = StatusCode;
-                ctx.Response.SendChunked = false;
                 DisposeResponseStream();
             }
         }
 
-        private async void ProcessUncachedResponse(HttpListenerContext ctx, TimeSpan cacheDuration)
+        private async Task ProcessUncachedRequest(HttpListenerContext ctx, TimeSpan cacheDuration)
         {
             long? totalContentLength = TotalContentLength;
 
@@ -269,7 +284,7 @@ namespace MediaBrowser.Common.Net.Handlers
             // Set the status code
             ctx.Response.StatusCode = StatusCode;
 
-            if (StatusCode == 200 || StatusCode == 206)
+            if (IsResponseValid)
             {
                 // Finally, write the response data
                 Stream outputStream = ctx.Response.OutputStream;
@@ -288,23 +303,11 @@ namespace MediaBrowser.Common.Net.Handlers
                     outputStream = CompressedStream;
                 }
 
-                try
-                {
-                    await WriteResponseToOutputStream(outputStream);
-                }
-                catch (Exception ex)
-                {
-                    Logger.LogException(ex);
-                }
-                finally
-                {
-                    DisposeResponseStream();
-                }
+                await WriteResponseToOutputStream(outputStream);
             }
             else
             {
                 ctx.Response.SendChunked = false;
-                DisposeResponseStream();
             }
         }
 
@@ -317,9 +320,16 @@ namespace MediaBrowser.Common.Net.Handlers
             response.Headers[HttpResponseHeader.LastModified] = lastModified.ToString("r");
         }
 
+        /// <summary>
+        /// Gives subclasses a chance to do and prep work, and also to validate data and set an error status code, if needed
+        /// </summary>
+        protected virtual void PrepareResponse()
+        {
+        }
+
         protected abstract Task WriteResponseToOutputStream(Stream stream);
 
-        private void DisposeResponseStream()
+        protected virtual void DisposeResponseStream()
         {
             if (CompressedStream != null)
             {
@@ -366,5 +376,13 @@ namespace MediaBrowser.Common.Net.Handlers
         {
             return null;
         }
+
+        private bool IsResponseValid
+        {
+            get
+            {
+                return StatusCode == 200 || StatusCode == 206;
+            }
+        }
     }
 }

+ 49 - 2
MediaBrowser.Common/Net/Handlers/BaseJsonHandler.cs

@@ -1,11 +1,58 @@
-
+using System.IO;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Serialization;
+
 namespace MediaBrowser.Common.Net.Handlers
 {
     public abstract class BaseJsonHandler : BaseHandler
     {
         public override string ContentType
         {
-            get { return "application/json"; }
+            get { return MimeTypes.JsonMimeType; }
+        }
+
+        private bool _ObjectToSerializeEnsured = false;
+        private object _ObjectToSerialize;
+     
+        private void EnsureObjectToSerialize()
+        {
+            if (!_ObjectToSerializeEnsured)
+            {
+                _ObjectToSerialize = GetObjectToSerialize();
+
+                if (_ObjectToSerialize == null)
+                {
+                    StatusCode = 404;
+                }
+
+                _ObjectToSerializeEnsured = true;
+            }
+        }
+
+        private object ObjectToSerialize
+        {
+            get
+            {
+                EnsureObjectToSerialize();
+                return _ObjectToSerialize;
+            }
+        }
+
+        protected abstract object GetObjectToSerialize();
+
+        protected override void PrepareResponse()
+        {
+            base.PrepareResponse();
+
+            EnsureObjectToSerialize();
+        }
+
+        protected override Task WriteResponseToOutputStream(Stream stream)
+        {
+            return Task.Run(() =>
+            {
+                JsonSerializer.SerializeToStream(ObjectToSerialize, stream);
+            });
         }
     }
 }

+ 79 - 79
MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs

@@ -28,37 +28,44 @@ namespace MediaBrowser.Common.Net.Handlers
             }
         }
 
-        private bool FileStreamDiscovered = false;
-        private FileStream _FileStream = null;
-        private FileStream FileStream
+        private bool _SourceStreamEnsured = false;
+        private Stream _SourceStream = null;
+        private Stream SourceStream
         {
             get
             {
-                if (!FileStreamDiscovered)
+                EnsureSourceStream();
+                return _SourceStream;
+            }
+        }
+
+        private void EnsureSourceStream()
+        {
+            if (!_SourceStreamEnsured)
+            {
+                try
                 {
-                    try
-                    {
-                        _FileStream = File.OpenRead(Path);
-                    }
-                    catch (FileNotFoundException)
-                    {
-                        StatusCode = 404;
-                    }
-                    catch (DirectoryNotFoundException)
-                    {
-                        StatusCode = 404;
-                    }
-                    catch (UnauthorizedAccessException)
-                    {
-                        StatusCode = 403;
-                    }
-                    finally
-                    {
-                        FileStreamDiscovered = true;
-                    }
+                    _SourceStream = File.OpenRead(Path);
+                }
+                catch (FileNotFoundException ex)
+                {
+                    StatusCode = 404;
+                    Logger.LogException(ex);
+                }
+                catch (DirectoryNotFoundException ex)
+                {
+                    StatusCode = 404;
+                    Logger.LogException(ex);
+                }
+                catch (UnauthorizedAccessException ex)
+                {
+                    StatusCode = 403;
+                    Logger.LogException(ex);
+                }
+                finally
+                {
+                    _SourceStreamEnsured = true;
                 }
-
-                return _FileStream;
             }
         }
 
@@ -74,14 +81,14 @@ namespace MediaBrowser.Common.Net.Handlers
         {
             get
             {
-                string contentType = ContentType;
-
                 // 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))
                 {
@@ -95,26 +102,19 @@ namespace MediaBrowser.Common.Net.Handlers
 
         protected override long? GetTotalContentLength()
         {
-            try
-            {
-                return FileStream.Length;
-            }
-            catch
-            {
-                return base.GetTotalContentLength();
-            }
+            return SourceStream.Length;
         }
 
         protected override DateTime? GetLastDateModified()
         {
-            try
-            {
-                return File.GetLastWriteTime(Path);
-            }
-            catch
+            EnsureSourceStream();
+
+            if (SourceStream == null)
             {
-                return base.GetLastDateModified();
+                return null;
             }
+
+            return File.GetLastWriteTime(Path);
         }
 
         public override string ContentType
@@ -125,48 +125,48 @@ namespace MediaBrowser.Common.Net.Handlers
             }
         }
 
+        protected override void PrepareResponse()
+        {
+            base.PrepareResponse();
+
+            EnsureSourceStream();
+        }
+
         protected async override Task WriteResponseToOutputStream(Stream stream)
         {
-            try
+            if (IsRangeRequest)
             {
-                if (FileStream != null)
+                KeyValuePair<long, long?> requestedRange = RequestedRanges.First();
+
+                // If the requested range is "0-" and we know the total length, we can optimize by avoiding having to buffer the content into memory
+                if (requestedRange.Value == null && TotalContentLength != null)
                 {
-                    if (IsRangeRequest)
-                    {
-                        KeyValuePair<long, long?> requestedRange = RequestedRanges.First();
-
-                        // If the requested range is "0-" and we know the total length, we can optimize by avoiding having to buffer the content into memory
-                        if (requestedRange.Value == null && TotalContentLength != null)
-                        {
-                            await ServeCompleteRangeRequest(requestedRange, stream);
-                        }
-                        else if (TotalContentLength.HasValue)
-                        {
-                            // This will have to buffer a portion of the content into memory
-                            await ServePartialRangeRequestWithKnownTotalContentLength(requestedRange, stream);
-                        }
-                        else
-                        {
-                            // This will have to buffer the entire content into memory
-                            await ServePartialRangeRequestWithUnknownTotalContentLength(requestedRange, stream);
-                        }
-                    }
-                    else
-                    {
-                        await FileStream.CopyToAsync(stream);
-                    }
+                    await ServeCompleteRangeRequest(requestedRange, stream);
+                }
+                else if (TotalContentLength.HasValue)
+                {
+                    // This will have to buffer a portion of the content into memory
+                    await ServePartialRangeRequestWithKnownTotalContentLength(requestedRange, stream);
+                }
+                else
+                {
+                    // This will have to buffer the entire content into memory
+                    await ServePartialRangeRequestWithUnknownTotalContentLength(requestedRange, stream);
                 }
             }
-            catch (Exception ex)
+            else
             {
-                Logger.LogException("WriteResponseToOutputStream", ex);
+                await SourceStream.CopyToAsync(stream);
             }
-            finally
+        }
+
+        protected override void DisposeResponseStream()
+        {
+            base.DisposeResponseStream();
+
+            if (SourceStream != null)
             {
-                if (FileStream != null)
-                {
-                    FileStream.Dispose();
-                }
+                SourceStream.Dispose();
             }
         }
 
@@ -188,10 +188,10 @@ namespace MediaBrowser.Common.Net.Handlers
 
             if (rangeStart > 0)
             {
-                FileStream.Position = rangeStart;
+                SourceStream.Position = rangeStart;
             }
 
-            await FileStream.CopyToAsync(responseStream);
+            await SourceStream.CopyToAsync(responseStream);
         }
 
         /// <summary>
@@ -200,7 +200,7 @@ namespace MediaBrowser.Common.Net.Handlers
         private async Task ServePartialRangeRequestWithUnknownTotalContentLength(KeyValuePair<long, long?> requestedRange, Stream responseStream)
         {
             // Read the entire stream so that we can determine the length
-            byte[] bytes = await ReadBytes(FileStream, 0, null);
+            byte[] bytes = await ReadBytes(SourceStream, 0, null);
 
             long totalContentLength = bytes.LongLength;
 
@@ -226,7 +226,7 @@ namespace MediaBrowser.Common.Net.Handlers
             long rangeLength = 1 + rangeEnd - rangeStart;
 
             // Only read the bytes we need
-            byte[] bytes = await ReadBytes(FileStream, Convert.ToInt32(rangeStart), Convert.ToInt32(rangeLength));
+            byte[] bytes = await ReadBytes(SourceStream, Convert.ToInt32(rangeStart), Convert.ToInt32(rangeLength));
 
             // Content-Length is the length of what we're serving, not the original content
             HttpListenerContext.Response.ContentLength64 = rangeLength;

+ 2 - 0
MediaBrowser.Common/Net/MimeTypes.cs

@@ -5,6 +5,8 @@ namespace MediaBrowser.Common.Net
 {
     public static class MimeTypes
     {
+        public static string JsonMimeType = "application/json";
+
         public static string GetMimeType(string path)
         {
             string ext = Path.GetExtension(path);

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

@@ -10,7 +10,7 @@ namespace MediaBrowser.Controller.Xml
     /// <summary>
     /// Provides a base class for parsing metadata xml
     /// </summary>
-    public abstract class BaseItemXmlParser<T>
+    public class BaseItemXmlParser<T>
         where T : BaseItem, new()
     {
         /// <summary>
@@ -215,6 +215,32 @@ namespace MediaBrowser.Controller.Xml
                         break;
                     }
 
+                case "TMDbId":
+                    string tmdb = reader.ReadString();
+                    if (!string.IsNullOrWhiteSpace(tmdb))
+                    {
+                        item.SetProviderId(MetadataProviders.Tmdb, tmdb);
+                    }
+                    break;
+
+                case "TVcomId":
+                    string TVcomId = reader.ReadString();
+                    if (!string.IsNullOrWhiteSpace(TVcomId))
+                    {
+                        item.SetProviderId(MetadataProviders.Tvcom, TVcomId);
+                    }
+                    break;
+
+                case "IMDB_ID":
+                case "IMDB":
+                case "IMDbId":
+                    string IMDbId = reader.ReadString();
+                    if (!string.IsNullOrWhiteSpace(IMDbId))
+                    {
+                        item.SetProviderId(MetadataProviders.Imdb, IMDbId);
+                    }
+                    break;
+
                 case "Genres":
                     FetchFromGenresNode(reader.ReadSubtree(), item);
                     break;

+ 2 - 5
MediaBrowser.Model/Entities/ApiBaseItem.cs

@@ -11,14 +11,9 @@ namespace MediaBrowser.Model.Entities
     public class ApiBaseItem : BaseItem
     {
         // TV Series
-        public string TvdbId { get; set; }
         public string Status { get; set; }
         public IEnumerable<DayOfWeek> AirDays { get; set; }
         public string AirTime { get; set; }
-
-        // Movie
-        public string TmdbId { get; set; }
-        public string ImdbId { get; set; }
     }
 
     /// <summary>
@@ -49,6 +44,8 @@ namespace MediaBrowser.Model.Entities
             return Type.Equals(type, StringComparison.OrdinalIgnoreCase);
         }
 
+        public IEnumerable<PersonInfo> People { get; set; }
+
         /// <summary>
         /// If the item does not have a logo, this will hold the Id of the Parent that has one.
         /// </summary>

+ 45 - 0
MediaBrowser.Model/Entities/BaseItem.cs

@@ -33,6 +33,7 @@ namespace MediaBrowser.Model.Entities
         public string Overview { get; set; }
         public string Tagline { get; set; }
 
+        [IgnoreDataMember]
         public IEnumerable<PersonInfo> People { get; set; }
 
         public IEnumerable<string> Studios { get; set; }
@@ -56,5 +57,49 @@ namespace MediaBrowser.Model.Entities
         public IEnumerable<Video> LocalTrailers { get; set; }
 
         public string TrailerUrl { get; set; }
+
+        public Dictionary<string, string> ProviderIds { get; set; }
+
+        /// <summary>
+        /// Gets a provider id
+        /// </summary>
+        public string GetProviderId(MetadataProviders provider)
+        {
+            return GetProviderId(provider.ToString());
+        }
+
+        /// <summary>
+        /// Gets a provider id
+        /// </summary>
+        public string GetProviderId(string name)
+        {
+            if (ProviderIds == null)
+            {
+                return null;
+            }
+
+            return ProviderIds[name];
+        }
+
+        /// <summary>
+        /// Sets a provider id
+        /// </summary>
+        public void SetProviderId(string name, string value)
+        {
+            if (ProviderIds == null)
+            {
+                ProviderIds = new Dictionary<string, string>();
+            }
+
+            ProviderIds[name] = value;
+        }
+
+        /// <summary>
+        /// Sets a provider id
+        /// </summary>
+        public void SetProviderId(MetadataProviders provider, string value)
+        {
+            SetProviderId(provider.ToString(), value);
+        }
     }
 }

+ 11 - 0
MediaBrowser.Model/Entities/MetadataProviders.cs

@@ -0,0 +1,11 @@
+
+namespace MediaBrowser.Model.Entities
+{
+    public enum MetadataProviders
+    {
+        Imdb,
+        Tmdb,
+        Tvdb,
+        Tvcom
+    }
+}

+ 1 - 0
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -41,6 +41,7 @@
     <Compile Include="Entities\Folder.cs" />
     <Compile Include="Entities\Genre.cs" />
     <Compile Include="Entities\ImageType.cs" />
+    <Compile Include="Entities\MetadataProviders.cs" />
     <Compile Include="Entities\Person.cs" />
     <Compile Include="Entities\Studio.cs" />
     <Compile Include="Entities\Video.cs" />

+ 0 - 1
MediaBrowser.Movies/MediaBrowser.Movies.csproj

@@ -45,7 +45,6 @@
     <Compile Include="Resolvers\BoxSetResolver.cs" />
     <Compile Include="Entities\Movie.cs" />
     <Compile Include="Resolvers\MovieResolver.cs" />
-    <Compile Include="Metadata\MovieXmlParser.cs" />
     <Compile Include="Plugin.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
   </ItemGroup>

+ 0 - 32
MediaBrowser.Movies/Metadata/MovieXmlParser.cs

@@ -1,32 +0,0 @@
-using System.Xml;
-using MediaBrowser.Controller.Xml;
-using MediaBrowser.Movies.Entities;
-
-namespace MediaBrowser.Movies.Metadata
-{
-    public class MovieXmlParser : BaseItemXmlParser<Movie>
-    {
-        protected override void FetchDataFromXmlNode(XmlReader reader, Movie item)
-        {
-            switch (reader.Name)
-            {
-                case "TMDbId":
-                    item.TmdbId = reader.ReadString();
-                    break;
-
-                case "IMDB":
-                case "IMDbId":
-                    string IMDbId = reader.ReadString();
-                    if (!string.IsNullOrWhiteSpace(IMDbId))
-                    {
-                        item.ImdbId = IMDbId;
-                    }
-                    break;
-
-                default:
-                    base.FetchDataFromXmlNode(reader, item);
-                    break;
-            }
-        }
-    }
-}

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

@@ -6,9 +6,9 @@ using System.Linq;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Events;
 using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.Controller.Xml;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Movies.Entities;
-using MediaBrowser.Movies.Metadata;
 
 namespace MediaBrowser.Movies.Resolvers
 {
@@ -97,7 +97,7 @@ namespace MediaBrowser.Movies.Resolvers
 
             if (metadataFile.HasValue)
             {
-                new MovieXmlParser().Fetch(item, metadataFile.Value.Key);
+                new BaseItemXmlParser<Movie>().Fetch(item, metadataFile.Value.Key);
             }
 
             PopulateBonusFeatures(item, args);

+ 2 - 4
MediaBrowser.TV/Entities/Series.cs

@@ -1,13 +1,11 @@
-using MediaBrowser.Model.Entities;
-using System;
-using System.Linq;
+using System;
 using System.Collections.Generic;
+using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.TV.Entities
 {
     public class Series : Folder
     {
-        public string TvdbId { get; set; }
         public string Status { get; set; }
         public IEnumerable<DayOfWeek> AirDays { get; set; }
         public string AirTime { get; set; }

+ 1 - 2
MediaBrowser.TV/Metadata/EpisodeXmlParser.cs

@@ -1,5 +1,4 @@
-using System;
-using System.IO;
+using System.IO;
 using System.Xml;
 using MediaBrowser.Controller.Xml;
 using MediaBrowser.TV.Entities;

+ 6 - 1
MediaBrowser.TV/Metadata/SeriesXmlParser.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Xml;
 using MediaBrowser.Controller.Xml;
+using MediaBrowser.Model.Entities;
 using MediaBrowser.TV.Entities;
 
 namespace MediaBrowser.TV.Metadata
@@ -12,7 +13,11 @@ namespace MediaBrowser.TV.Metadata
             switch (reader.Name)
             {
                 case "id":
-                    item.TvdbId = reader.ReadString();
+                    string id = reader.ReadString();
+                    if (!string.IsNullOrWhiteSpace(id))
+                    {
+                        item.SetProviderId(MetadataProviders.Tvdb, id);
+                    }
                     break;
 
                 case "Airs_DayOfWeek":