diff --git a/MediaBrowser.Api/ApiService.cs b/MediaBrowser.Api/ApiService.cs index c14705be67..7c52e0e5c6 100644 --- a/MediaBrowser.Api/ApiService.cs +++ b/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)) { - // Extract ffprobe - using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("MediaBrowser.Api.FFMpeg." + filename)) + File.Delete(_FFMpegPath); + } + + // 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); } } } diff --git a/MediaBrowser.Api/HttpHandlers/BaseMediaHandler.cs b/MediaBrowser.Api/HttpHandlers/BaseMediaHandler.cs index da397fec93..fd85993c8e 100644 --- a/MediaBrowser.Api/HttpHandlers/BaseMediaHandler.cs +++ b/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(); diff --git a/MediaBrowser.Api/HttpHandlers/GenresHandler.cs b/MediaBrowser.Api/HttpHandlers/GenresHandler.cs index e8e3fd80b7..0c38a23962 100644 --- a/MediaBrowser.Api/HttpHandlers/GenresHandler.cs +++ b/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); } } } diff --git a/MediaBrowser.Api/HttpHandlers/ImageHandler.cs b/MediaBrowser.Api/HttpHandlers/ImageHandler.cs index c53afb5c48..8264380980 100644 --- a/MediaBrowser.Api/HttpHandlers/ImageHandler.cs +++ b/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 } } + private Stream _SourceStream = null; + private Stream SourceStream + { + get + { + EnsureSourceStream(); + + return _SourceStream; + } + } + + + 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 + { + _SourceStreamEnsured = true; + } + } + } + public override string ContentType { get { - string extension = Path.GetExtension(ImagePath); + EnsureSourceStream(); - if (extension.EndsWith("png", StringComparison.OrdinalIgnoreCase)) + if (SourceStream == null) { - return "image/png"; + return null; } - - return "image/jpeg"; + + return MimeTypes.GetMimeType(ImagePath); } } @@ -49,14 +94,14 @@ namespace MediaBrowser.Api.HttpHandlers protected override DateTime? GetLastDateModified() { - try + EnsureSourceStream(); + + if (SourceStream == null) { - return File.GetLastWriteTime(ImagePath); - } - catch - { - 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); }); } diff --git a/MediaBrowser.Api/HttpHandlers/ItemHandler.cs b/MediaBrowser.Api/HttpHandlers/ItemHandler.cs index a54d65524f..28def271f8 100644 --- a/MediaBrowser.Api/HttpHandlers/ItemHandler.cs +++ b/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"]); - return ApiService.GetSerializationObject(ItemToSerialize, true, userId); + BaseItem item = ItemToSerialize; + + if (item == null) + { + return null; } + + return ApiService.GetSerializationObject(item, true, userId); } protected virtual BaseItem ItemToSerialize diff --git a/MediaBrowser.Api/HttpHandlers/ItemListHandler.cs b/MediaBrowser.Api/HttpHandlers/ItemListHandler.cs index a94c2061e1..0d9d3d0a89 100644 --- a/MediaBrowser.Api/HttpHandlers/ItemListHandler.cs +++ b/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 ItemsToSerialize diff --git a/MediaBrowser.Api/HttpHandlers/JsonHandler.cs b/MediaBrowser.Api/HttpHandlers/JsonHandler.cs deleted file mode 100644 index bffa841745..0000000000 --- a/MediaBrowser.Api/HttpHandlers/JsonHandler.cs +++ /dev/null @@ -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); - }); - } - } -} diff --git a/MediaBrowser.Api/HttpHandlers/PersonHandler.cs b/MediaBrowser.Api/HttpHandlers/PersonHandler.cs index c70ad39ca6..103e49cd2d 100644 --- a/MediaBrowser.Api/HttpHandlers/PersonHandler.cs +++ b/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"]); } } } diff --git a/MediaBrowser.Api/HttpHandlers/PluginConfigurationHandler.cs b/MediaBrowser.Api/HttpHandlers/PluginConfigurationHandler.cs index 5f5203dae0..4dc317ecc1 100644 --- a/MediaBrowser.Api/HttpHandlers/PluginConfigurationHandler.cs +++ b/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; } } } diff --git a/MediaBrowser.Api/HttpHandlers/PluginsHandler.cs b/MediaBrowser.Api/HttpHandlers/PluginsHandler.cs index 63797026d7..a44e126921 100644 --- a/MediaBrowser.Api/HttpHandlers/PluginsHandler.cs +++ b/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 /// /// Provides information about installed plugins /// - 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; } } } diff --git a/MediaBrowser.Api/HttpHandlers/StudiosHandler.cs b/MediaBrowser.Api/HttpHandlers/StudiosHandler.cs index ff48ae6e36..a381af15fe 100644 --- a/MediaBrowser.Api/HttpHandlers/StudiosHandler.cs +++ b/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); } } } diff --git a/MediaBrowser.Api/HttpHandlers/UserConfigurationHandler.cs b/MediaBrowser.Api/HttpHandlers/UserConfigurationHandler.cs index 06c3ea86dd..f9d3536b6d 100644 --- a/MediaBrowser.Api/HttpHandlers/UserConfigurationHandler.cs +++ b/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); } } } diff --git a/MediaBrowser.Api/HttpHandlers/UsersHandler.cs b/MediaBrowser.Api/HttpHandlers/UsersHandler.cs index d10561e435..64f68f62f1 100644 --- a/MediaBrowser.Api/HttpHandlers/UsersHandler.cs +++ b/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; } } } diff --git a/MediaBrowser.Api/HttpHandlers/YearsHandler.cs b/MediaBrowser.Api/HttpHandlers/YearsHandler.cs index 6f47870282..044866dca3 100644 --- a/MediaBrowser.Api/HttpHandlers/YearsHandler.cs +++ b/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); } } } diff --git a/MediaBrowser.Api/ImageProcessor.cs b/MediaBrowser.Api/ImageProcessor.cs index a798123e45..014f655984 100644 --- a/MediaBrowser.Api/ImageProcessor.cs +++ b/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; diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 4bd6d89e82..e6a1816f6d 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -56,7 +56,6 @@ - diff --git a/MediaBrowser.ApiInteraction/ApiClient.cs b/MediaBrowser.ApiInteraction/ApiClient.cs index 5685638f60..0750223b6d 100644 --- a/MediaBrowser.ApiInteraction/ApiClient.cs +++ b/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 { + /// + /// Gets or sets the server host name (myserver or 192.168.x.x) + /// + public string ServerHostName { get; set; } + + /// + /// Gets or sets the port number used by the API + /// + public int ServerApiPort { get; set; } + + /// + /// Gets the current api url based on hostname and port. + /// + protected string ApiUrl + { + get + { + return string.Format("http://{0}:{1}/mediabrowser/api", ServerHostName, ServerApiPort); + } + } + + public IHttpClient HttpClient { get; set; } public IJsonSerializer JsonSerializer { get; set; } - public ApiClient() - : base() - { - } - - public ApiClient(HttpClientHandler handler) - : base(handler) - { - } - /// /// Gets an image url that can be used to download an image from the api /// @@ -278,5 +289,10 @@ namespace MediaBrowser.ApiInteraction return JsonSerializer.DeserializeFromStream>>(stream); } } + + public void Dispose() + { + HttpClient.Dispose(); + } } } diff --git a/MediaBrowser.ApiInteraction/BaseClient.cs b/MediaBrowser.ApiInteraction/BaseClient.cs deleted file mode 100644 index 8799ca5412..0000000000 --- a/MediaBrowser.ApiInteraction/BaseClient.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; - -namespace MediaBrowser.ApiInteraction -{ - /// - /// Provides a base class used by the api and image services - /// - public abstract class BaseClient : IDisposable - { - /// - /// Gets or sets the server host name (myserver or 192.168.x.x) - /// - public string ServerHostName { get; set; } - - /// - /// Gets or sets the port number used by the API - /// - public int ServerApiPort { get; set; } - - /// - /// Gets the current api url based on hostname and port. - /// - 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(); - } - } -} diff --git a/MediaBrowser.ApiInteraction/IHttpClient.cs b/MediaBrowser.ApiInteraction/IHttpClient.cs new file mode 100644 index 0000000000..40659e0191 --- /dev/null +++ b/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 GetStreamAsync(string url); + } +} diff --git a/MediaBrowser.ApiInteraction/MediaBrowser.ApiInteraction.csproj b/MediaBrowser.ApiInteraction/MediaBrowser.ApiInteraction.csproj index 682d974da6..965b4487da 100644 --- a/MediaBrowser.ApiInteraction/MediaBrowser.ApiInteraction.csproj +++ b/MediaBrowser.ApiInteraction/MediaBrowser.ApiInteraction.csproj @@ -40,7 +40,7 @@ - + diff --git a/MediaBrowser.Common/Configuration/ApplicationPaths.cs b/MediaBrowser.Common/Configuration/ApplicationPaths.cs index dc87749cfb..efe6f0c9e0 100644 --- a/MediaBrowser.Common/Configuration/ApplicationPaths.cs +++ b/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 diff --git a/MediaBrowser.Common/Net/Handlers/BaseHandler.cs b/MediaBrowser.Common/Net/Handlers/BaseHandler.cs index 89803d8d74..f9ef065ecc 100644 --- a/MediaBrowser.Common/Net/Handlers/BaseHandler.cs +++ b/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; - - ctx.Response.ContentType = ContentType; - - TimeSpan cacheDuration = CacheDuration; - - if (ctx.Request.Headers.AllKeys.Contains("If-Modified-Since")) - { - DateTime ifModifiedSince; - - if (DateTime.TryParse(ctx.Request.Headers["If-Modified-Since"].Replace(" GMT", string.Empty), out ifModifiedSince)) + if (SupportsByteRangeRequests && IsRangeRequest) { - // If the cache hasn't expired yet just return a 304 - if (IsCacheValid(ifModifiedSince, cacheDuration, LastDateModified)) + 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; + + ctx.Response.ContentType = ContentType; + + TimeSpan cacheDuration = CacheDuration; + + if (ctx.Request.Headers.AllKeys.Contains("If-Modified-Since")) + { + 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) - { - ProcessUncachedResponse(ctx, cacheDuration); + PrepareResponse(); + + if (IsResponseValid) + { + await ProcessUncachedRequest(ctx, cacheDuration); + } + else + { + ctx.Response.StatusCode = StatusCode; + ctx.Response.SendChunked = false; + } } - else + catch (Exception ex) + { + // 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 { - 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"); } + /// + /// Gives subclasses a chance to do and prep work, and also to validate data and set an error status code, if needed + /// + 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; + } + } } } \ No newline at end of file diff --git a/MediaBrowser.Common/Net/Handlers/BaseJsonHandler.cs b/MediaBrowser.Common/Net/Handlers/BaseJsonHandler.cs index 30113198b9..03de398c90 100644 --- a/MediaBrowser.Common/Net/Handlers/BaseJsonHandler.cs +++ b/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); + }); } } } diff --git a/MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs b/MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs index 3eb908938f..5656f92739 100644 --- a/MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs +++ b/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) - { - try - { - _FileStream = File.OpenRead(Path); - } - catch (FileNotFoundException) - { - StatusCode = 404; - } - catch (DirectoryNotFoundException) - { - StatusCode = 404; - } - catch (UnauthorizedAccessException) - { - StatusCode = 403; - } - finally - { - FileStreamDiscovered = true; - } - } + EnsureSourceStream(); + return _SourceStream; + } + } - return _FileStream; + private void EnsureSourceStream() + { + if (!_SourceStreamEnsured) + { + try + { + _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; + } } } @@ -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 + EnsureSourceStream(); + + if (SourceStream == null) { - return File.GetLastWriteTime(Path); - } - catch - { - 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) - { - if (IsRangeRequest) - { - KeyValuePair requestedRange = RequestedRanges.First(); + KeyValuePair 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); - } - } - } - catch (Exception ex) - { - Logger.LogException("WriteResponseToOutputStream", ex); - } - finally - { - if (FileStream != null) + // 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) { - FileStream.Dispose(); + 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 SourceStream.CopyToAsync(stream); + } + } + + protected override void DisposeResponseStream() + { + base.DisposeResponseStream(); + + if (SourceStream != null) + { + 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); } /// @@ -200,7 +200,7 @@ namespace MediaBrowser.Common.Net.Handlers private async Task ServePartialRangeRequestWithUnknownTotalContentLength(KeyValuePair 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; diff --git a/MediaBrowser.Common/Net/MimeTypes.cs b/MediaBrowser.Common/Net/MimeTypes.cs index a49cbaa98c..41df463f71 100644 --- a/MediaBrowser.Common/Net/MimeTypes.cs +++ b/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); diff --git a/MediaBrowser.Controller/Xml/BaseItemXmlParser.cs b/MediaBrowser.Controller/Xml/BaseItemXmlParser.cs index de9a94b034..38b8584fc0 100644 --- a/MediaBrowser.Controller/Xml/BaseItemXmlParser.cs +++ b/MediaBrowser.Controller/Xml/BaseItemXmlParser.cs @@ -10,7 +10,7 @@ namespace MediaBrowser.Controller.Xml /// /// Provides a base class for parsing metadata xml /// - public abstract class BaseItemXmlParser + public class BaseItemXmlParser where T : BaseItem, new() { /// @@ -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; diff --git a/MediaBrowser.Model/Entities/ApiBaseItem.cs b/MediaBrowser.Model/Entities/ApiBaseItem.cs index e6bd716547..e40fdc25f8 100644 --- a/MediaBrowser.Model/Entities/ApiBaseItem.cs +++ b/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 AirDays { get; set; } public string AirTime { get; set; } - - // Movie - public string TmdbId { get; set; } - public string ImdbId { get; set; } } /// @@ -49,6 +44,8 @@ namespace MediaBrowser.Model.Entities return Type.Equals(type, StringComparison.OrdinalIgnoreCase); } + public IEnumerable People { get; set; } + /// /// If the item does not have a logo, this will hold the Id of the Parent that has one. /// diff --git a/MediaBrowser.Model/Entities/BaseItem.cs b/MediaBrowser.Model/Entities/BaseItem.cs index c6de496bdd..322bac564c 100644 --- a/MediaBrowser.Model/Entities/BaseItem.cs +++ b/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 People { get; set; } public IEnumerable Studios { get; set; } @@ -56,5 +57,49 @@ namespace MediaBrowser.Model.Entities public IEnumerable