Bladeren bron

made compression and caching available to plugin api endpoints

Luke Pulverenti 12 jaren geleden
bovenliggende
commit
e2dcddc5ac
40 gewijzigde bestanden met toevoegingen van 1088 en 750 verwijderingen
  1. 65 4
      MediaBrowser.Api/BaseApiService.cs
  2. 1 2
      MediaBrowser.Api/EnvironmentService.cs
  3. 8 9
      MediaBrowser.Api/Images/ImageService.cs
  4. 16 1
      MediaBrowser.Api/Images/ImageWriter.cs
  5. 6 7
      MediaBrowser.Api/Library/LibraryService.cs
  6. 0 1
      MediaBrowser.Api/Library/LibraryStructureService.cs
  7. 0 1
      MediaBrowser.Api/LocalizationService.cs
  8. 3 30
      MediaBrowser.Api/MediaBrowser.Api.csproj
  9. 1 2
      MediaBrowser.Api/PackageService.cs
  10. 0 1
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  11. 1 1
      MediaBrowser.Api/Playback/Hls/AudioHlsService.cs
  12. 3 3
      MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
  13. 1 1
      MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
  14. 36 23
      MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
  15. 16 1
      MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
  16. 12 11
      MediaBrowser.Api/PluginService.cs
  17. 0 1
      MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs
  18. 1 2
      MediaBrowser.Api/UserLibrary/ItemsService.cs
  19. 0 1
      MediaBrowser.Api/UserService.cs
  20. 1 2
      MediaBrowser.Api/WeatherService.cs
  21. 0 3
      MediaBrowser.Api/packages.config
  22. 11 1
      MediaBrowser.Common/MediaBrowser.Common.csproj
  23. 17 0
      MediaBrowser.Common/Net/IHasResultFactory.cs
  24. 90 2
      MediaBrowser.Common/Net/IHttpResultFactory.cs
  25. 2 7
      MediaBrowser.Common/Net/IRestfulService.cs
  26. 0 28
      MediaBrowser.Common/Net/RouteInfo.cs
  27. 5 0
      MediaBrowser.Common/packages.config
  28. 0 470
      MediaBrowser.Server.Implementations/HttpServer/BaseRestService.cs
  29. 578 3
      MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
  30. 24 5
      MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs
  31. 108 82
      MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs
  32. 25 2
      MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs
  33. 13 4
      MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs
  34. 0 1
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  35. 1 1
      MediaBrowser.ServerApplication/ApplicationHost.cs
  36. 10 1
      MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
  37. 2 0
      MediaBrowser.ServerApplication/packages.config
  38. 31 9
      MediaBrowser.WebDashboard/Api/DashboardService.cs
  39. 0 24
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
  40. 0 3
      MediaBrowser.WebDashboard/packages.config

+ 65 - 4
MediaBrowser.Api/BaseApiService.cs

@@ -1,11 +1,10 @@
-using System.Collections.Generic;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Connectivity;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
-using MediaBrowser.Server.Implementations.HttpServer;
 using ServiceStack.Common.Web;
 using ServiceStack.Common.Web;
 using ServiceStack.ServiceHost;
 using ServiceStack.ServiceHost;
 using System;
 using System;
+using System.Collections.Generic;
 
 
 namespace MediaBrowser.Api
 namespace MediaBrowser.Api
 {
 {
@@ -13,8 +12,70 @@ namespace MediaBrowser.Api
     /// Class BaseApiService
     /// Class BaseApiService
     /// </summary>
     /// </summary>
     [RequestFilter]
     [RequestFilter]
-    public class BaseApiService : BaseRestService
+    public class BaseApiService : IHasResultFactory, IRestfulService
     {
     {
+        /// <summary>
+        /// Gets or sets the logger.
+        /// </summary>
+        /// <value>The logger.</value>
+        public ILogger Logger { get; set; }
+
+        /// <summary>
+        /// Gets or sets the HTTP result factory.
+        /// </summary>
+        /// <value>The HTTP result factory.</value>
+        public IHttpResultFactory ResultFactory { get; set; }
+
+        /// <summary>
+        /// Gets or sets the request context.
+        /// </summary>
+        /// <value>The request context.</value>
+        public IRequestContext RequestContext { get; set; }
+        
+        /// <summary>
+        /// To the optimized result.
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="result">The result.</param>
+        /// <returns>System.Object.</returns>
+        protected object ToOptimizedResult<T>(T result)
+            where T : class
+        {
+            return ResultFactory.GetOptimizedResult(RequestContext, result);
+        }
+
+        /// <summary>
+        /// To the optimized result using cache.
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="cacheKey">The cache key.</param>
+        /// <param name="lastDateModified">The last date modified.</param>
+        /// <param name="cacheDuration">Duration of the cache.</param>
+        /// <param name="factoryFn">The factory fn.</param>
+        /// <returns>System.Object.</returns>
+        /// <exception cref="System.ArgumentNullException">cacheKey</exception>
+        protected object ToOptimizedResultUsingCache<T>(Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func<T> factoryFn)
+               where T : class
+        {
+            return ResultFactory.GetOptimizedResultUsingCache(RequestContext, cacheKey, lastDateModified, cacheDuration, factoryFn);
+        }
+
+        /// <summary>
+        /// To the cached result.
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="cacheKey">The cache key.</param>
+        /// <param name="lastDateModified">The last date modified.</param>
+        /// <param name="cacheDuration">Duration of the cache.</param>
+        /// <param name="factoryFn">The factory fn.</param>
+        /// <param name="contentType">Type of the content.</param>
+        /// <returns>System.Object.</returns>
+        /// <exception cref="System.ArgumentNullException">cacheKey</exception>
+        protected object ToCachedResult<T>(Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func<T> factoryFn, string contentType)
+          where T : class
+        {
+            return ResultFactory.GetCachedResult(RequestContext, cacheKey, lastDateModified, cacheDuration, factoryFn, contentType);
+        }
     }
     }
 
 
     /// <summary>
     /// <summary>

+ 1 - 2
MediaBrowser.Api/EnvironmentService.cs

@@ -2,7 +2,6 @@
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Net;
-using MediaBrowser.Server.Implementations.HttpServer;
 using ServiceStack.ServiceHost;
 using ServiceStack.ServiceHost;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -16,7 +15,7 @@ namespace MediaBrowser.Api
     /// Class GetDirectoryContents
     /// Class GetDirectoryContents
     /// </summary>
     /// </summary>
     [Route("/Environment/DirectoryContents", "GET")]
     [Route("/Environment/DirectoryContents", "GET")]
-    [ServiceStack.ServiceHost.Api(Description = "Gets the contents of a given directory in the file system")]
+    [Api(Description = "Gets the contents of a given directory in the file system")]
     public class GetDirectoryContents : IReturn<List<FileSystemEntryInfo>>
     public class GetDirectoryContents : IReturn<List<FileSystemEntryInfo>>
     {
     {
         /// <summary>
         /// <summary>

+ 8 - 9
MediaBrowser.Api/Images/ImageService.cs

@@ -5,7 +5,6 @@ using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Server.Implementations.HttpServer;
 using ServiceStack.ServiceHost;
 using ServiceStack.ServiceHost;
 using ServiceStack.Text.Controller;
 using ServiceStack.Text.Controller;
 using System;
 using System;
@@ -21,7 +20,7 @@ namespace MediaBrowser.Api.Images
     /// </summary>
     /// </summary>
     [Route("/Items/{Id}/Images/{Type}", "GET")]
     [Route("/Items/{Id}/Images/{Type}", "GET")]
     [Route("/Items/{Id}/Images/{Type}/{Index}", "GET")]
     [Route("/Items/{Id}/Images/{Type}/{Index}", "GET")]
-    [ServiceStack.ServiceHost.Api(Description = "Gets an item image")]
+    [Api(Description = "Gets an item image")]
     public class GetItemImage : ImageRequest
     public class GetItemImage : ImageRequest
     {
     {
         /// <summary>
         /// <summary>
@@ -37,7 +36,7 @@ namespace MediaBrowser.Api.Images
     /// </summary>
     /// </summary>
     [Route("/Persons/{Name}/Images/{Type}", "GET")]
     [Route("/Persons/{Name}/Images/{Type}", "GET")]
     [Route("/Persons/{Name}/Images/{Type}/{Index}", "GET")]
     [Route("/Persons/{Name}/Images/{Type}/{Index}", "GET")]
-    [ServiceStack.ServiceHost.Api(Description = "Gets a person image")]
+    [Api(Description = "Gets a person image")]
     public class GetPersonImage : ImageRequest
     public class GetPersonImage : ImageRequest
     {
     {
         /// <summary>
         /// <summary>
@@ -53,7 +52,7 @@ namespace MediaBrowser.Api.Images
     /// </summary>
     /// </summary>
     [Route("/Studios/{Name}/Images/{Type}", "GET")]
     [Route("/Studios/{Name}/Images/{Type}", "GET")]
     [Route("/Studios/{Name}/Images/{Type}/{Index}", "GET")]
     [Route("/Studios/{Name}/Images/{Type}/{Index}", "GET")]
-    [ServiceStack.ServiceHost.Api(Description = "Gets a studio image")]
+    [Api(Description = "Gets a studio image")]
     public class GetStudioImage : ImageRequest
     public class GetStudioImage : ImageRequest
     {
     {
         /// <summary>
         /// <summary>
@@ -69,7 +68,7 @@ namespace MediaBrowser.Api.Images
     /// </summary>
     /// </summary>
     [Route("/Genres/{Name}/Images/{Type}", "GET")]
     [Route("/Genres/{Name}/Images/{Type}", "GET")]
     [Route("/Genres/{Name}/Images/{Type}/{Index}", "GET")]
     [Route("/Genres/{Name}/Images/{Type}/{Index}", "GET")]
-    [ServiceStack.ServiceHost.Api(Description = "Gets a genre image")]
+    [Api(Description = "Gets a genre image")]
     public class GetGenreImage : ImageRequest
     public class GetGenreImage : ImageRequest
     {
     {
         /// <summary>
         /// <summary>
@@ -85,7 +84,7 @@ namespace MediaBrowser.Api.Images
     /// </summary>
     /// </summary>
     [Route("/Years/{Year}/Images/{Type}", "GET")]
     [Route("/Years/{Year}/Images/{Type}", "GET")]
     [Route("/Years/{Year}/Images/{Type}/{Index}", "GET")]
     [Route("/Years/{Year}/Images/{Type}/{Index}", "GET")]
-    [ServiceStack.ServiceHost.Api(Description = "Gets a year image")]
+    [Api(Description = "Gets a year image")]
     public class GetYearImage : ImageRequest
     public class GetYearImage : ImageRequest
     {
     {
         /// <summary>
         /// <summary>
@@ -101,7 +100,7 @@ namespace MediaBrowser.Api.Images
     /// </summary>
     /// </summary>
     [Route("/Users/{Id}/Images/{Type}", "GET")]
     [Route("/Users/{Id}/Images/{Type}", "GET")]
     [Route("/Users/{Id}/Images/{Type}/{Index}", "GET")]
     [Route("/Users/{Id}/Images/{Type}/{Index}", "GET")]
-    [ServiceStack.ServiceHost.Api(Description = "Gets a user image")]
+    [Api(Description = "Gets a user image")]
     public class GetUserImage : ImageRequest
     public class GetUserImage : ImageRequest
     {
     {
         /// <summary>
         /// <summary>
@@ -117,7 +116,7 @@ namespace MediaBrowser.Api.Images
     /// </summary>
     /// </summary>
     [Route("/Users/{Id}/Images/{Type}", "DELETE")]
     [Route("/Users/{Id}/Images/{Type}", "DELETE")]
     [Route("/Users/{Id}/Images/{Type}/{Index}", "DELETE")]
     [Route("/Users/{Id}/Images/{Type}/{Index}", "DELETE")]
-    [ServiceStack.ServiceHost.Api(Description = "Deletes a user image")]
+    [Api(Description = "Deletes a user image")]
     public class DeleteUserImage : DeleteImageRequest, IReturnVoid
     public class DeleteUserImage : DeleteImageRequest, IReturnVoid
     {
     {
         /// <summary>
         /// <summary>
@@ -130,7 +129,7 @@ namespace MediaBrowser.Api.Images
 
 
     [Route("/Users/{Id}/Images/{Type}", "POST")]
     [Route("/Users/{Id}/Images/{Type}", "POST")]
     [Route("/Users/{Id}/Images/{Type}/{Index}", "POST")]
     [Route("/Users/{Id}/Images/{Type}/{Index}", "POST")]
-    [ServiceStack.ServiceHost.Api(Description = "Posts a user image")]
+    [Api(Description = "Posts a user image")]
     public class PostUserImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid
     public class PostUserImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid
     {
     {
         /// <summary>
         /// <summary>

+ 16 - 1
MediaBrowser.Api/Images/ImageWriter.cs

@@ -1,7 +1,9 @@
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using ServiceStack.Service;
 using ServiceStack.Service;
+using ServiceStack.ServiceHost;
 using System;
 using System;
+using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
@@ -10,7 +12,7 @@ namespace MediaBrowser.Api.Images
     /// <summary>
     /// <summary>
     /// Class ImageWriter
     /// Class ImageWriter
     /// </summary>
     /// </summary>
-    public class ImageWriter : IStreamWriter
+    public class ImageWriter : IStreamWriter, IHasOptions
     {
     {
         /// <summary>
         /// <summary>
         /// Gets or sets the request.
         /// Gets or sets the request.
@@ -32,6 +34,19 @@ namespace MediaBrowser.Api.Images
         /// </summary>
         /// </summary>
         public DateTime OriginalImageDateModified;
         public DateTime OriginalImageDateModified;
 
 
+        /// <summary>
+        /// The _options
+        /// </summary>
+        private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
+        /// <summary>
+        /// Gets the options.
+        /// </summary>
+        /// <value>The options.</value>
+        public IDictionary<string, string> Options
+        {
+            get { return _options; }
+        }
+
         /// <summary>
         /// <summary>
         /// Writes to.
         /// Writes to.
         /// </summary>
         /// </summary>

+ 6 - 7
MediaBrowser.Api/Library/LibraryService.cs

@@ -3,7 +3,6 @@ using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Querying;
-using MediaBrowser.Server.Implementations.HttpServer;
 using ServiceStack.ServiceHost;
 using ServiceStack.ServiceHost;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -15,7 +14,7 @@ namespace MediaBrowser.Api.Library
     /// Class GetPhyscialPaths
     /// Class GetPhyscialPaths
     /// </summary>
     /// </summary>
     [Route("/Library/PhysicalPaths", "GET")]
     [Route("/Library/PhysicalPaths", "GET")]
-    [ServiceStack.ServiceHost.Api(Description = "Gets a list of physical paths from virtual folders")]
+    [Api(Description = "Gets a list of physical paths from virtual folders")]
     public class GetPhyscialPaths : IReturn<List<string>>
     public class GetPhyscialPaths : IReturn<List<string>>
     {
     {
     }
     }
@@ -24,7 +23,7 @@ namespace MediaBrowser.Api.Library
     /// Class GetItemTypes
     /// Class GetItemTypes
     /// </summary>
     /// </summary>
     [Route("/Library/ItemTypes", "GET")]
     [Route("/Library/ItemTypes", "GET")]
-    [ServiceStack.ServiceHost.Api(Description = "Gets a list of BaseItem types")]
+    [Api(Description = "Gets a list of BaseItem types")]
     public class GetItemTypes : IReturn<List<string>>
     public class GetItemTypes : IReturn<List<string>>
     {
     {
         /// <summary>
         /// <summary>
@@ -39,7 +38,7 @@ namespace MediaBrowser.Api.Library
     /// Class GetPerson
     /// Class GetPerson
     /// </summary>
     /// </summary>
     [Route("/Persons/{Name}", "GET")]
     [Route("/Persons/{Name}", "GET")]
-    [ServiceStack.ServiceHost.Api(Description = "Gets a person, by name")]
+    [Api(Description = "Gets a person, by name")]
     public class GetPerson : IReturn<BaseItemDto>
     public class GetPerson : IReturn<BaseItemDto>
     {
     {
         /// <summary>
         /// <summary>
@@ -54,7 +53,7 @@ namespace MediaBrowser.Api.Library
     /// Class GetStudio
     /// Class GetStudio
     /// </summary>
     /// </summary>
     [Route("/Studios/{Name}", "GET")]
     [Route("/Studios/{Name}", "GET")]
-    [ServiceStack.ServiceHost.Api(Description = "Gets a studio, by name")]
+    [Api(Description = "Gets a studio, by name")]
     public class GetStudio : IReturn<BaseItemDto>
     public class GetStudio : IReturn<BaseItemDto>
     {
     {
         /// <summary>
         /// <summary>
@@ -69,7 +68,7 @@ namespace MediaBrowser.Api.Library
     /// Class GetGenre
     /// Class GetGenre
     /// </summary>
     /// </summary>
     [Route("/Genres/{Name}", "GET")]
     [Route("/Genres/{Name}", "GET")]
-    [ServiceStack.ServiceHost.Api(Description = "Gets a genre, by name")]
+    [Api(Description = "Gets a genre, by name")]
     public class GetGenre : IReturn<BaseItemDto>
     public class GetGenre : IReturn<BaseItemDto>
     {
     {
         /// <summary>
         /// <summary>
@@ -84,7 +83,7 @@ namespace MediaBrowser.Api.Library
     /// Class GetYear
     /// Class GetYear
     /// </summary>
     /// </summary>
     [Route("/Years/{Year}", "GET")]
     [Route("/Years/{Year}", "GET")]
-    [ServiceStack.ServiceHost.Api(Description = "Gets a year")]
+    [Api(Description = "Gets a year")]
     public class GetYear : IReturn<BaseItemDto>
     public class GetYear : IReturn<BaseItemDto>
     {
     {
         /// <summary>
         /// <summary>

+ 0 - 1
MediaBrowser.Api/Library/LibraryStructureService.cs

@@ -1,7 +1,6 @@
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Server.Implementations.HttpServer;
 using ServiceStack.ServiceHost;
 using ServiceStack.ServiceHost;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;

+ 0 - 1
MediaBrowser.Api/LocalizationService.cs

@@ -1,7 +1,6 @@
 using MediaBrowser.Controller.Localization;
 using MediaBrowser.Controller.Localization;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Globalization;
-using MediaBrowser.Server.Implementations.HttpServer;
 using MoreLinq;
 using MoreLinq;
 using ServiceStack.ServiceHost;
 using ServiceStack.ServiceHost;
 using System.Collections.Generic;
 using System.Collections.Generic;

+ 3 - 30
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -39,36 +39,13 @@
     <Reference Include="MoreLinq">
     <Reference Include="MoreLinq">
       <HintPath>..\packages\morelinq.1.0.15631-beta\lib\net35\MoreLinq.dll</HintPath>
       <HintPath>..\packages\morelinq.1.0.15631-beta\lib\net35\MoreLinq.dll</HintPath>
     </Reference>
     </Reference>
-    <Reference Include="ServiceStack, Version=3.9.42.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\ServiceStack.3.9.42\lib\net35\ServiceStack.dll</HintPath>
-    </Reference>
-    <Reference Include="ServiceStack.Common, Version=3.9.42.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
+    <Reference Include="ServiceStack.Common">
       <HintPath>..\packages\ServiceStack.Common.3.9.42\lib\net35\ServiceStack.Common.dll</HintPath>
       <HintPath>..\packages\ServiceStack.Common.3.9.42\lib\net35\ServiceStack.Common.dll</HintPath>
     </Reference>
     </Reference>
-    <Reference Include="ServiceStack.Interfaces, Version=3.9.42.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
+    <Reference Include="ServiceStack.Interfaces">
       <HintPath>..\packages\ServiceStack.Common.3.9.42\lib\net35\ServiceStack.Interfaces.dll</HintPath>
       <HintPath>..\packages\ServiceStack.Common.3.9.42\lib\net35\ServiceStack.Interfaces.dll</HintPath>
     </Reference>
     </Reference>
-    <Reference Include="ServiceStack.OrmLite, Version=3.9.42.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\ServiceStack.OrmLite.SqlServer.3.9.42\lib\ServiceStack.OrmLite.dll</HintPath>
-    </Reference>
-    <Reference Include="ServiceStack.OrmLite.SqlServer, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\ServiceStack.OrmLite.SqlServer.3.9.42\lib\ServiceStack.OrmLite.SqlServer.dll</HintPath>
-    </Reference>
-    <Reference Include="ServiceStack.Redis, Version=3.9.42.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\ServiceStack.Redis.3.9.42\lib\net35\ServiceStack.Redis.dll</HintPath>
-    </Reference>
-    <Reference Include="ServiceStack.ServiceInterface, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\ServiceStack.3.9.42\lib\net35\ServiceStack.ServiceInterface.dll</HintPath>
-    </Reference>
-    <Reference Include="ServiceStack.Text, Version=3.9.42.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
+    <Reference Include="ServiceStack.Text">
       <HintPath>..\packages\ServiceStack.Text.3.9.42\lib\net35\ServiceStack.Text.dll</HintPath>
       <HintPath>..\packages\ServiceStack.Text.3.9.42\lib\net35\ServiceStack.Text.dll</HintPath>
     </Reference>
     </Reference>
     <Reference Include="System" />
     <Reference Include="System" />
@@ -133,10 +110,6 @@
       <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
       <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
       <Name>MediaBrowser.Model</Name>
       <Name>MediaBrowser.Model</Name>
     </ProjectReference>
     </ProjectReference>
-    <ProjectReference Include="..\MediaBrowser.Server.Implementations\MediaBrowser.Server.Implementations.csproj">
-      <Project>{2e781478-814d-4a48-9d80-bff206441a65}</Project>
-      <Name>MediaBrowser.Server.Implementations</Name>
-    </ProjectReference>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <None Include="packages.config" />
     <None Include="packages.config" />

+ 1 - 2
MediaBrowser.Api/PackageService.cs

@@ -2,7 +2,6 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Updates;
 using MediaBrowser.Controller.Updates;
 using MediaBrowser.Model.Updates;
 using MediaBrowser.Model.Updates;
-using MediaBrowser.Server.Implementations.HttpServer;
 using ServiceStack.ServiceHost;
 using ServiceStack.ServiceHost;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -16,7 +15,7 @@ namespace MediaBrowser.Api
     /// Class GetPackage
     /// Class GetPackage
     /// </summary>
     /// </summary>
     [Route("/Packages/{Name}", "GET")]
     [Route("/Packages/{Name}", "GET")]
-    [ServiceStack.ServiceHost.Api(("Gets a package, by name"))]
+    [Api(("Gets a package, by name"))]
     public class GetPackage : IReturn<PackageInfo>
     public class GetPackage : IReturn<PackageInfo>
     {
     {
         /// <summary>
         /// <summary>

+ 0 - 1
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -6,7 +6,6 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Drawing;
 using MediaBrowser.Model.Drawing;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Server.Implementations.HttpServer;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.ComponentModel;

+ 1 - 1
MediaBrowser.Api/Playback/Hls/AudioHlsService.cs

@@ -44,7 +44,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
 
             file = Path.Combine(ApplicationPaths.EncodedMediaCachePath, file);
             file = Path.Combine(ApplicationPaths.EncodedMediaCachePath, file);
 
 
-            return ToStaticFileResult(file);
+            return ResultFactory.GetStaticFileResult(RequestContext, file);
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 3 - 3
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.IO;
+using System.Collections.Generic;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
@@ -86,8 +87,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
 
             try
             try
             {
             {
-                Response.ContentType = MimeTypes.GetMimeType("playlist.m3u8");
-                return playlistText;
+                return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
             }
             }
             finally
             finally
             {
             {

+ 1 - 1
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -36,7 +36,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
 
             file = Path.Combine(ApplicationPaths.EncodedMediaCachePath, file);
             file = Path.Combine(ApplicationPaths.EncodedMediaCachePath, file);
 
 
-            return ToStaticFileResult(file);
+            return ResultFactory.GetStaticFileResult(RequestContext, file);
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 36 - 23
MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs

@@ -1,11 +1,12 @@
-using System;
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Dto;
+using System;
+using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
@@ -16,7 +17,7 @@ namespace MediaBrowser.Api.Playback.Progressive
     /// </summary>
     /// </summary>
     public abstract class BaseProgressiveStreamingService : BaseStreamingService
     public abstract class BaseProgressiveStreamingService : BaseStreamingService
     {
     {
-        protected BaseProgressiveStreamingService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager) : 
+        protected BaseProgressiveStreamingService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager) :
             base(appPaths, userManager, libraryManager, isoManager)
             base(appPaths, userManager, libraryManager, isoManager)
         {
         {
         }
         }
@@ -85,18 +86,21 @@ namespace MediaBrowser.Api.Playback.Progressive
         /// <summary>
         /// <summary>
         /// Adds the dlna headers.
         /// Adds the dlna headers.
         /// </summary>
         /// </summary>
-        private bool AddDlnaHeaders(StreamState state)
+        /// <param name="state">The state.</param>
+        /// <param name="responseHeaders">The response headers.</param>
+        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
+        private void AddDlnaHeaders(StreamState state, IDictionary<string,string> responseHeaders)
         {
         {
             var timeSeek = RequestContext.GetHeader("TimeSeekRange.dlna.org");
             var timeSeek = RequestContext.GetHeader("TimeSeekRange.dlna.org");
 
 
             if (!string.IsNullOrEmpty(timeSeek))
             if (!string.IsNullOrEmpty(timeSeek))
             {
             {
-                Response.StatusCode = 406;
-                return false;
+                ResultFactory.ThrowError(406, "Time seek not supported during encoding.", responseHeaders);
+                return;
             }
             }
 
 
             var transferMode = RequestContext.GetHeader("transferMode.dlna.org");
             var transferMode = RequestContext.GetHeader("transferMode.dlna.org");
-            Response.AddHeader("transferMode.dlna.org", string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode);
+            responseHeaders["transferMode.dlna.org"] = string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode;
 
 
             var contentFeatures = string.Empty;
             var contentFeatures = string.Empty;
             var extension = GetOutputFileExtension(state);
             var extension = GetOutputFileExtension(state);
@@ -140,10 +144,8 @@ namespace MediaBrowser.Api.Playback.Progressive
 
 
             if (!string.IsNullOrEmpty(contentFeatures))
             if (!string.IsNullOrEmpty(contentFeatures))
             {
             {
-                Response.AddHeader("ContentFeatures.DLNA.ORG", contentFeatures);
+                responseHeaders["ContentFeatures.DLNA.ORG"] = contentFeatures;
             }
             }
-
-            return true;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -165,45 +167,45 @@ namespace MediaBrowser.Api.Playback.Progressive
         {
         {
             var state = GetState(request);
             var state = GetState(request);
 
 
-            if (!AddDlnaHeaders(state))
-            {
-                return null;
-            }
+            var responseHeaders = new Dictionary<string, string>();
+
+            AddDlnaHeaders(state, responseHeaders);
 
 
             if (request.Static)
             if (request.Static)
             {
             {
-                return ToStaticFileResult(state.Item.Path, isHeadRequest);
+                return ResultFactory.GetStaticFileResult(RequestContext, state.Item.Path, responseHeaders, isHeadRequest);
             }
             }
 
 
             var outputPath = GetOutputFilePath(state);
             var outputPath = GetOutputFilePath(state);
 
 
             if (File.Exists(outputPath) && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive))
             if (File.Exists(outputPath) && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive))
             {
             {
-                return ToStaticFileResult(outputPath, isHeadRequest);
+                return ResultFactory.GetStaticFileResult(RequestContext, outputPath, responseHeaders, isHeadRequest);
             }
             }
 
 
-            Response.AddHeader("Accept-Ranges", "none");
-
-            return GetStreamResult(state, isHeadRequest).Result;
+            return GetStreamResult(state, responseHeaders, isHeadRequest).Result;
         }
         }
 
 
         /// <summary>
         /// <summary>
         /// Gets the stream result.
         /// Gets the stream result.
         /// </summary>
         /// </summary>
         /// <param name="state">The state.</param>
         /// <param name="state">The state.</param>
+        /// <param name="responseHeaders">The response headers.</param>
         /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
         /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
         /// <returns>Task{System.Object}.</returns>
         /// <returns>Task{System.Object}.</returns>
-        private async Task<ProgressiveStreamWriter> GetStreamResult(StreamState state, bool isHeadRequest)
+        private async Task<object> GetStreamResult(StreamState state, IDictionary<string,string> responseHeaders, bool isHeadRequest)
         {
         {
             // Use the command line args with a dummy playlist path
             // Use the command line args with a dummy playlist path
             var outputPath = GetOutputFilePath(state);
             var outputPath = GetOutputFilePath(state);
 
 
-            Response.ContentType = MimeTypes.GetMimeType(outputPath);
+            var contentType = MimeTypes.GetMimeType(outputPath);
 
 
             // Headers only
             // Headers only
             if (isHeadRequest)
             if (isHeadRequest)
             {
             {
-                return null;
+                responseHeaders["Accept-Ranges"] = "none";
+
+                return ResultFactory.GetResult(null, contentType, responseHeaders);
             }
             }
 
 
             if (!File.Exists(outputPath))
             if (!File.Exists(outputPath))
@@ -215,7 +217,18 @@ namespace MediaBrowser.Api.Playback.Progressive
                 ApiEntryPoint.Instance.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
                 ApiEntryPoint.Instance.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
             }
             }
 
 
-            return new ProgressiveStreamWriter(outputPath, state, Logger);
+            var result = new ProgressiveStreamWriter(outputPath, state, Logger);
+
+            result.Options["Accept-Ranges"] = "none";
+            result.Options["Content-Type"] = contentType;
+
+            // Add the response headers to the result object
+            foreach (var item in responseHeaders)
+            {
+                result.Options[item.Key] = item.Value;
+            }
+
+            return result;
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 16 - 1
MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs

@@ -1,18 +1,33 @@
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using ServiceStack.Service;
 using ServiceStack.Service;
+using ServiceStack.ServiceHost;
 using System;
 using System;
+using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Api.Playback.Progressive
 namespace MediaBrowser.Api.Playback.Progressive
 {
 {
-    public class ProgressiveStreamWriter : IStreamWriter
+    public class ProgressiveStreamWriter : IStreamWriter, IHasOptions
     {
     {
         private string Path { get; set; }
         private string Path { get; set; }
         private StreamState State { get; set; }
         private StreamState State { get; set; }
         private ILogger Logger { get; set; }
         private ILogger Logger { get; set; }
 
 
+        /// <summary>
+        /// The _options
+        /// </summary>
+        private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
+        /// <summary>
+        /// Gets the options.
+        /// </summary>
+        /// <value>The options.</value>
+        public IDictionary<string, string> Options
+        {
+            get { return _options; }
+        }
+
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="ProgressiveStreamWriter" /> class.
         /// Initializes a new instance of the <see cref="ProgressiveStreamWriter" /> class.
         /// </summary>
         /// </summary>

+ 12 - 11
MediaBrowser.Api/PluginService.cs

@@ -5,7 +5,6 @@ using MediaBrowser.Controller.Updates;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Plugins;
 using MediaBrowser.Model.Plugins;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Serialization;
-using MediaBrowser.Server.Implementations.HttpServer;
 using ServiceStack.ServiceHost;
 using ServiceStack.ServiceHost;
 using ServiceStack.Text.Controller;
 using ServiceStack.Text.Controller;
 using System;
 using System;
@@ -19,7 +18,7 @@ namespace MediaBrowser.Api
     /// Class Plugins
     /// Class Plugins
     /// </summary>
     /// </summary>
     [Route("/Plugins", "GET")]
     [Route("/Plugins", "GET")]
-    [ServiceStack.ServiceHost.Api(("Gets a list of currently installed plugins"))]
+    [Api(("Gets a list of currently installed plugins"))]
     public class GetPlugins : IReturn<List<PluginInfo>>
     public class GetPlugins : IReturn<List<PluginInfo>>
     {
     {
     }
     }
@@ -28,7 +27,7 @@ namespace MediaBrowser.Api
     /// Class GetPluginAssembly
     /// Class GetPluginAssembly
     /// </summary>
     /// </summary>
     [Route("/Plugins/{Id}/Assembly", "GET")]
     [Route("/Plugins/{Id}/Assembly", "GET")]
-    [ServiceStack.ServiceHost.Api(("Gets a plugin assembly file"))]
+    [Api(("Gets a plugin assembly file"))]
     public class GetPluginAssembly
     public class GetPluginAssembly
     {
     {
         /// <summary>
         /// <summary>
@@ -58,7 +57,7 @@ namespace MediaBrowser.Api
     /// Class GetPluginConfiguration
     /// Class GetPluginConfiguration
     /// </summary>
     /// </summary>
     [Route("/Plugins/{Id}/Configuration", "GET")]
     [Route("/Plugins/{Id}/Configuration", "GET")]
-    [ServiceStack.ServiceHost.Api(("Gets a plugin's configuration"))]
+    [Api(("Gets a plugin's configuration"))]
     public class GetPluginConfiguration
     public class GetPluginConfiguration
     {
     {
         /// <summary>
         /// <summary>
@@ -73,7 +72,7 @@ namespace MediaBrowser.Api
     /// Class UpdatePluginConfiguration
     /// Class UpdatePluginConfiguration
     /// </summary>
     /// </summary>
     [Route("/Plugins/{Id}/Configuration", "POST")]
     [Route("/Plugins/{Id}/Configuration", "POST")]
-    [ServiceStack.ServiceHost.Api(("Updates a plugin's configuration"))]
+    [Api(("Updates a plugin's configuration"))]
     public class UpdatePluginConfiguration : IRequiresRequestStream, IReturnVoid
     public class UpdatePluginConfiguration : IRequiresRequestStream, IReturnVoid
     {
     {
         /// <summary>
         /// <summary>
@@ -94,7 +93,7 @@ namespace MediaBrowser.Api
     /// Class GetPluginConfigurationFile
     /// Class GetPluginConfigurationFile
     /// </summary>
     /// </summary>
     [Route("/Plugins/{Id}/ConfigurationFile", "GET")]
     [Route("/Plugins/{Id}/ConfigurationFile", "GET")]
-    [ServiceStack.ServiceHost.Api(("Gets a plugin's configuration file, in plain text"))]
+    [Api(("Gets a plugin's configuration file, in plain text"))]
     public class GetPluginConfigurationFile
     public class GetPluginConfigurationFile
     {
     {
         /// <summary>
         /// <summary>
@@ -109,7 +108,8 @@ namespace MediaBrowser.Api
     /// Class GetPluginSecurityInfo
     /// Class GetPluginSecurityInfo
     /// </summary>
     /// </summary>
     [Route("/Plugins/SecurityInfo", "GET")]
     [Route("/Plugins/SecurityInfo", "GET")]
-    [ServiceStack.ServiceHost.Api(("Gets plugin registration information"))]
+    [Api(("Gets plugin registration information"))]
+    [Restrict(VisibilityTo = EndpointAttributes.None)]
     public class GetPluginSecurityInfo : IReturn<PluginSecurityInfo>
     public class GetPluginSecurityInfo : IReturn<PluginSecurityInfo>
     {
     {
     }
     }
@@ -118,7 +118,8 @@ namespace MediaBrowser.Api
     /// Class UpdatePluginSecurityInfo
     /// Class UpdatePluginSecurityInfo
     /// </summary>
     /// </summary>
     [Route("/Plugins/SecurityInfo", "POST")]
     [Route("/Plugins/SecurityInfo", "POST")]
-    [ServiceStack.ServiceHost.Api(("Updates plugin registration information"))]
+    [Api("Updates plugin registration information")]
+    [Restrict(VisibilityTo = EndpointAttributes.None)]
     public class UpdatePluginSecurityInfo : PluginSecurityInfo, IReturnVoid
     public class UpdatePluginSecurityInfo : PluginSecurityInfo, IReturnVoid
     {
     {
     }
     }
@@ -171,7 +172,7 @@ namespace MediaBrowser.Api
         public object Get(GetPlugins request)
         public object Get(GetPlugins request)
         {
         {
             var result = _appHost.Plugins.OrderBy(p => p.Name).Select(p => p.GetPluginInfo()).ToList();
             var result = _appHost.Plugins.OrderBy(p => p.Name).Select(p => p.GetPluginInfo()).ToList();
-            
+
             return ToOptimizedResult(result);
             return ToOptimizedResult(result);
         }
         }
 
 
@@ -184,7 +185,7 @@ namespace MediaBrowser.Api
         {
         {
             var plugin = _appHost.Plugins.First(p => p.Id == request.Id);
             var plugin = _appHost.Plugins.First(p => p.Id == request.Id);
 
 
-            return ToStaticFileResult(plugin.AssemblyFilePath);
+            return ResultFactory.GetStaticFileResult(RequestContext, plugin.AssemblyFilePath);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -212,7 +213,7 @@ namespace MediaBrowser.Api
         {
         {
             var plugin = _appHost.Plugins.First(p => p.Id == request.Id);
             var plugin = _appHost.Plugins.First(p => p.Id == request.Id);
 
 
-            return ToStaticFileResult(plugin.ConfigurationFilePath);
+            return ResultFactory.GetStaticFileResult(RequestContext, plugin.ConfigurationFilePath);
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 0 - 1
MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs

@@ -1,7 +1,6 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Model.Tasks;
 using MediaBrowser.Model.Tasks;
-using MediaBrowser.Server.Implementations.HttpServer;
 using ServiceStack.ServiceHost;
 using ServiceStack.ServiceHost;
 using ServiceStack.Text.Controller;
 using ServiceStack.Text.Controller;
 using System;
 using System;

+ 1 - 2
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -2,7 +2,6 @@
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Querying;
-using MediaBrowser.Server.Implementations.HttpServer;
 using ServiceStack.ServiceHost;
 using ServiceStack.ServiceHost;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -15,7 +14,7 @@ namespace MediaBrowser.Api.UserLibrary
     /// Class GetItems
     /// Class GetItems
     /// </summary>
     /// </summary>
     [Route("/Users/{UserId}/Items", "GET")]
     [Route("/Users/{UserId}/Items", "GET")]
-    [ServiceStack.ServiceHost.Api(Description = "Gets items based on a query.")]
+    [Api(Description = "Gets items based on a query.")]
     public class GetItems : BaseItemsRequest, IReturn<ItemsResult>
     public class GetItems : BaseItemsRequest, IReturn<ItemsResult>
     {
     {
         /// <summary>
         /// <summary>

+ 0 - 1
MediaBrowser.Api/UserService.cs

@@ -2,7 +2,6 @@
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Serialization;
-using MediaBrowser.Server.Implementations.HttpServer;
 using ServiceStack.ServiceHost;
 using ServiceStack.ServiceHost;
 using ServiceStack.Text.Controller;
 using ServiceStack.Text.Controller;
 using System;
 using System;

+ 1 - 2
MediaBrowser.Api/WeatherService.cs

@@ -1,6 +1,5 @@
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
 using MediaBrowser.Model.Weather;
 using MediaBrowser.Model.Weather;
-using MediaBrowser.Server.Implementations.HttpServer;
 using ServiceStack.ServiceHost;
 using ServiceStack.ServiceHost;
 using System.Linq;
 using System.Linq;
 using System.Threading;
 using System.Threading;
@@ -11,7 +10,7 @@ namespace MediaBrowser.Api
     /// Class Weather
     /// Class Weather
     /// </summary>
     /// </summary>
     [Route("/Weather", "GET")]
     [Route("/Weather", "GET")]
-    [ServiceStack.ServiceHost.Api(Description = "Gets weather information for a given location")]
+    [Api(Description = "Gets weather information for a given location")]
     public class GetWeather : IReturn<WeatherInfo>
     public class GetWeather : IReturn<WeatherInfo>
     {
     {
         /// <summary>
         /// <summary>

+ 0 - 3
MediaBrowser.Api/packages.config

@@ -1,9 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
 <packages>
   <package id="morelinq" version="1.0.15631-beta" targetFramework="net45" />
   <package id="morelinq" version="1.0.15631-beta" targetFramework="net45" />
-  <package id="ServiceStack" version="3.9.42" targetFramework="net45" />
   <package id="ServiceStack.Common" version="3.9.42" targetFramework="net45" />
   <package id="ServiceStack.Common" version="3.9.42" targetFramework="net45" />
-  <package id="ServiceStack.OrmLite.SqlServer" version="3.9.42" targetFramework="net45" />
-  <package id="ServiceStack.Redis" version="3.9.42" targetFramework="net45" />
   <package id="ServiceStack.Text" version="3.9.42" targetFramework="net45" />
   <package id="ServiceStack.Text" version="3.9.42" targetFramework="net45" />
 </packages>
 </packages>

+ 11 - 1
MediaBrowser.Common/MediaBrowser.Common.csproj

@@ -38,6 +38,15 @@
     </ApplicationIcon>
     </ApplicationIcon>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup>
   <ItemGroup>
+    <Reference Include="ServiceStack.Common">
+      <HintPath>..\packages\ServiceStack.Common.3.9.42\lib\net35\ServiceStack.Common.dll</HintPath>
+    </Reference>
+    <Reference Include="ServiceStack.Interfaces">
+      <HintPath>..\packages\ServiceStack.Common.3.9.42\lib\net35\ServiceStack.Interfaces.dll</HintPath>
+    </Reference>
+    <Reference Include="ServiceStack.Text">
+      <HintPath>..\packages\ServiceStack.Text.3.9.42\lib\net35\ServiceStack.Text.dll</HintPath>
+    </Reference>
     <Reference Include="System" />
     <Reference Include="System" />
     <Reference Include="System.Core" />
     <Reference Include="System.Core" />
     <Reference Include="Microsoft.CSharp" />
     <Reference Include="Microsoft.CSharp" />
@@ -62,6 +71,7 @@
     <Compile Include="Net\BasePeriodicWebSocketListener.cs" />
     <Compile Include="Net\BasePeriodicWebSocketListener.cs" />
     <Compile Include="Configuration\IApplicationPaths.cs" />
     <Compile Include="Configuration\IApplicationPaths.cs" />
     <Compile Include="Net\HttpRequestOptions.cs" />
     <Compile Include="Net\HttpRequestOptions.cs" />
+    <Compile Include="Net\IHasResultFactory.cs" />
     <Compile Include="Net\IHttpResultFactory.cs" />
     <Compile Include="Net\IHttpResultFactory.cs" />
     <Compile Include="Net\IServerManager.cs" />
     <Compile Include="Net\IServerManager.cs" />
     <Compile Include="Net\IWebSocketListener.cs" />
     <Compile Include="Net\IWebSocketListener.cs" />
@@ -75,7 +85,6 @@
     <Compile Include="Net\IWebSocketConnection.cs" />
     <Compile Include="Net\IWebSocketConnection.cs" />
     <Compile Include="Net\IWebSocketServer.cs" />
     <Compile Include="Net\IWebSocketServer.cs" />
     <Compile Include="Net\MimeTypes.cs" />
     <Compile Include="Net\MimeTypes.cs" />
-    <Compile Include="Net\RouteInfo.cs" />
     <Compile Include="Net\UdpMessageReceivedEventArgs.cs" />
     <Compile Include="Net\UdpMessageReceivedEventArgs.cs" />
     <Compile Include="Net\WebSocketConnectEventArgs.cs" />
     <Compile Include="Net\WebSocketConnectEventArgs.cs" />
     <Compile Include="Net\WebSocketMessageType.cs" />
     <Compile Include="Net\WebSocketMessageType.cs" />
@@ -107,6 +116,7 @@
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <None Include="app.config" />
     <None Include="app.config" />
+    <None Include="packages.config" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">

+ 17 - 0
MediaBrowser.Common/Net/IHasResultFactory.cs

@@ -0,0 +1,17 @@
+using ServiceStack.ServiceHost;
+
+namespace MediaBrowser.Common.Net
+{
+    /// <summary>
+    /// Interface IHasResultFactory
+    /// Services that require a ResultFactory should implement this
+    /// </summary>
+    public interface IHasResultFactory : IRequiresRequestContext
+    {
+        /// <summary>
+        /// Gets or sets the result factory.
+        /// </summary>
+        /// <value>The result factory.</value>
+        IHttpResultFactory ResultFactory { get; set; }
+    }
+}

+ 90 - 2
MediaBrowser.Common/Net/IHttpResultFactory.cs

@@ -1,9 +1,97 @@
-using System.IO;
+using ServiceStack.ServiceHost;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Common.Net
 namespace MediaBrowser.Common.Net
 {
 {
+    /// <summary>
+    /// Interface IHttpResultFactory
+    /// </summary>
     public interface IHttpResultFactory
     public interface IHttpResultFactory
     {
     {
-        object GetResult(Stream stream, string contentType);
+        /// <summary>
+        /// Throws the error.
+        /// </summary>
+        /// <param name="statusCode">The status code.</param>
+        /// <param name="errorMessage">The error message.</param>
+        /// <param name="responseHeaders">The response headers.</param>
+        void ThrowError(int statusCode, string errorMessage, IDictionary<string, string> responseHeaders = null);
+        
+        /// <summary>
+        /// Gets the result.
+        /// </summary>
+        /// <param name="content">The content.</param>
+        /// <param name="contentType">Type of the content.</param>
+        /// <param name="responseHeaders">The response headers.</param>
+        /// <returns>System.Object.</returns>
+        object GetResult(object content, string contentType, IDictionary<string,string> responseHeaders = null);
+
+        /// <summary>
+        /// Gets the optimized result.
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="requestContext">The request context.</param>
+        /// <param name="result">The result.</param>
+        /// <param name="responseHeaders">The response headers.</param>
+        /// <returns>System.Object.</returns>
+        object GetOptimizedResult<T>(IRequestContext requestContext, T result, IDictionary<string, string> responseHeaders = null)
+            where T : class;
+
+        /// <summary>
+        /// Gets the optimized result using cache.
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="requestContext">The request context.</param>
+        /// <param name="cacheKey">The cache key.</param>
+        /// <param name="lastDateModified">The last date modified.</param>
+        /// <param name="cacheDuration">Duration of the cache.</param>
+        /// <param name="factoryFn">The factory function that creates the response object.</param>
+        /// <param name="responseHeaders">The response headers.</param>
+        /// <returns>System.Object.</returns>
+        object GetOptimizedResultUsingCache<T>(IRequestContext requestContext, Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func<T> factoryFn, IDictionary<string, string> responseHeaders = null)
+            where T : class;
+
+        /// <summary>
+        /// Gets the cached result.
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="requestContext">The request context.</param>
+        /// <param name="cacheKey">The cache key.</param>
+        /// <param name="lastDateModified">The last date modified.</param>
+        /// <param name="cacheDuration">Duration of the cache.</param>
+        /// <param name="factoryFn">The factory fn.</param>
+        /// <param name="contentType">Type of the content.</param>
+        /// <param name="responseHeaders">The response headers.</param>
+        /// <returns>System.Object.</returns>
+        object GetCachedResult<T>(IRequestContext requestContext, Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func<T> factoryFn, string contentType, IDictionary<string, string> responseHeaders = null)
+            where T : class;
+
+        /// <summary>
+        /// Gets the static result.
+        /// </summary>
+        /// <param name="requestContext">The request context.</param>
+        /// <param name="cacheKey">The cache key.</param>
+        /// <param name="lastDateModified">The last date modified.</param>
+        /// <param name="cacheDuration">Duration of the cache.</param>
+        /// <param name="contentType">Type of the content.</param>
+        /// <param name="factoryFn">The factory fn.</param>
+        /// <param name="responseHeaders">The response headers.</param>
+        /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
+        /// <returns>System.Object.</returns>
+        object GetStaticResult(IRequestContext requestContext, Guid cacheKey, DateTime? lastDateModified,
+                               TimeSpan? cacheDuration, string contentType, Func<Task<Stream>> factoryFn,
+                               IDictionary<string, string> responseHeaders = null, bool isHeadRequest = false);
+
+        /// <summary>
+        /// Gets the static file result.
+        /// </summary>
+        /// <param name="requestContext">The request context.</param>
+        /// <param name="path">The path.</param>
+        /// <param name="responseHeaders">The response headers.</param>
+        /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
+        /// <returns>System.Object.</returns>
+        object GetStaticFileResult(IRequestContext requestContext, string path, IDictionary<string, string> responseHeaders = null, bool isHeadRequest = false);
     }
     }
 }
 }

+ 2 - 7
MediaBrowser.Common/Net/IRestfulService.cs

@@ -1,16 +1,11 @@
-using System.Collections.Generic;
+using ServiceStack.ServiceHost;
 
 
 namespace MediaBrowser.Common.Net
 namespace MediaBrowser.Common.Net
 {
 {
     /// <summary>
     /// <summary>
     /// Interface IRestfulService
     /// Interface IRestfulService
     /// </summary>
     /// </summary>
-    public interface IRestfulService
+    public interface IRestfulService : IService
     {
     {
-        /// <summary>
-        /// Gets the routes.
-        /// </summary>
-        /// <returns>IEnumerable{RouteInfo}.</returns>
-        IEnumerable<RouteInfo> GetRoutes();
     }
     }
 }
 }

+ 0 - 28
MediaBrowser.Common/Net/RouteInfo.cs

@@ -1,28 +0,0 @@
-using System;
-
-namespace MediaBrowser.Common.Net
-{
-    /// <summary>
-    /// Class RouteInfo
-    /// </summary>
-    public class RouteInfo
-    {
-        /// <summary>
-        /// Gets or sets the path.
-        /// </summary>
-        /// <value>The path.</value>
-        public string Path { get; set; }
-
-        /// <summary>
-        /// Gets or sets the verbs.
-        /// </summary>
-        /// <value>The verbs.</value>
-        public string Verbs { get; set; }
-
-        /// <summary>
-        /// Gets or sets the type of the request.
-        /// </summary>
-        /// <value>The type of the request.</value>
-        public Type RequestType { get; set; }
-    }
-}

+ 5 - 0
MediaBrowser.Common/packages.config

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="ServiceStack.Common" version="3.9.42" targetFramework="net45" />
+  <package id="ServiceStack.Text" version="3.9.42" targetFramework="net45" />
+</packages>

+ 0 - 470
MediaBrowser.Server.Implementations/HttpServer/BaseRestService.cs

@@ -1,470 +0,0 @@
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.IO;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Model.Logging;
-using ServiceStack.Common;
-using ServiceStack.Common.Web;
-using ServiceStack.ServiceHost;
-using ServiceStack.ServiceInterface;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Net;
-using System.Threading.Tasks;
-using MimeTypes = MediaBrowser.Common.Net.MimeTypes;
-
-namespace MediaBrowser.Server.Implementations.HttpServer
-{
-    /// <summary>
-    /// Class BaseRestService
-    /// </summary>
-    public class BaseRestService : Service, IRestfulService
-    {
-        /// <summary>
-        /// Gets or sets the logger.
-        /// </summary>
-        /// <value>The logger.</value>
-        public ILogger Logger { get; set; }
-
-        /// <summary>
-        /// Gets a value indicating whether this instance is range request.
-        /// </summary>
-        /// <value><c>true</c> if this instance is range request; otherwise, <c>false</c>.</value>
-        protected bool IsRangeRequest
-        {
-            get
-            {
-                return !string.IsNullOrEmpty(RequestContext.GetHeader("Range"));
-            }
-        }
-
-        /// <summary>
-        /// To the optimized result.
-        /// </summary>
-        /// <typeparam name="T"></typeparam>
-        /// <param name="result">The result.</param>
-        /// <returns>System.Object.</returns>
-        /// <exception cref="System.ArgumentNullException">result</exception>
-        protected object ToOptimizedResult<T>(T result)
-            where T : class
-        {
-            if (result == null)
-            {
-                throw new ArgumentNullException("result");
-            }
-            
-            return RequestContext.ToOptimizedResult(result);
-        }
-
-        /// <summary>
-        /// To the optimized result using cache.
-        /// </summary>
-        /// <typeparam name="T"></typeparam>
-        /// <param name="cacheKey">The cache key.</param>
-        /// <param name="lastDateModified">The last date modified.</param>
-        /// <param name="cacheDuration">Duration of the cache.</param>
-        /// <param name="factoryFn">The factory fn.</param>
-        /// <returns>System.Object.</returns>
-        /// <exception cref="System.ArgumentNullException">cacheKey</exception>
-        protected object ToOptimizedResultUsingCache<T>(Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func<T> factoryFn)
-               where T : class
-        {
-            if (cacheKey == Guid.Empty)
-            {
-                throw new ArgumentNullException("cacheKey");
-            }
-            if (factoryFn == null)
-            {
-                throw new ArgumentNullException("factoryFn");
-            }
-
-            var key = cacheKey.ToString("N");
-
-            var result = PreProcessCachedResult(cacheKey, key, lastDateModified, cacheDuration);
-
-            if (result != null)
-            {
-                // Return null so that service stack won't do anything
-                return null;
-            }
-
-            return ToOptimizedResult(factoryFn());
-        }
-
-        /// <summary>
-        /// To the cached result.
-        /// </summary>
-        /// <typeparam name="T"></typeparam>
-        /// <param name="cacheKey">The cache key.</param>
-        /// <param name="lastDateModified">The last date modified.</param>
-        /// <param name="cacheDuration">Duration of the cache.</param>
-        /// <param name="factoryFn">The factory fn.</param>
-        /// <param name="contentType">Type of the content.</param>
-        /// <returns>System.Object.</returns>
-        /// <exception cref="System.ArgumentNullException">cacheKey</exception>
-        protected object ToCachedResult<T>(Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func<T> factoryFn, string contentType)
-          where T : class
-        {
-            if (cacheKey == Guid.Empty)
-            {
-                throw new ArgumentNullException("cacheKey");
-            }
-            if (factoryFn == null)
-            {
-                throw new ArgumentNullException("factoryFn");
-            }
-
-            Response.ContentType = contentType;
-            
-            var key = cacheKey.ToString("N");
-
-            var result = PreProcessCachedResult(cacheKey, key, lastDateModified, cacheDuration);
-
-            if (result != null)
-            {
-                // Return null so that service stack won't do anything
-                return null;
-            }
-
-            return factoryFn();
-        }
-
-        /// <summary>
-        /// To the static file result.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <param name="headersOnly">if set to <c>true</c> [headers only].</param>
-        /// <returns>System.Object.</returns>
-        /// <exception cref="System.ArgumentNullException">path</exception>
-        protected object ToStaticFileResult(string path, bool headersOnly = false)
-        {
-            if (string.IsNullOrEmpty(path))
-            {
-                throw new ArgumentNullException("path");
-            }
-
-            var dateModified = File.GetLastWriteTimeUtc(path);
-
-            var cacheKey = path + dateModified.Ticks;
-
-            return ToStaticResult(cacheKey.GetMD5(), dateModified, null, MimeTypes.GetMimeType(path), () => Task.FromResult(GetFileStream(path)), headersOnly);
-        }
-
-        /// <summary>
-        /// Gets the file stream.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <returns>Stream.</returns>
-        private Stream GetFileStream(string path)
-        {
-            return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous);
-        }
-
-        /// <summary>
-        /// To the static result.
-        /// </summary>
-        /// <param name="cacheKey">The cache key.</param>
-        /// <param name="lastDateModified">The last date modified.</param>
-        /// <param name="cacheDuration">Duration of the cache.</param>
-        /// <param name="contentType">Type of the content.</param>
-        /// <param name="factoryFn">The factory fn.</param>
-        /// <param name="headersOnly">if set to <c>true</c> [headers only].</param>
-        /// <returns>System.Object.</returns>
-        /// <exception cref="System.ArgumentNullException">cacheKey</exception>
-        protected object ToStaticResult(Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType, Func<Task<Stream>> factoryFn, bool headersOnly = false)
-        {
-            if (cacheKey == Guid.Empty)
-            {
-                throw new ArgumentNullException("cacheKey");
-            }
-            if (factoryFn == null)
-            {
-                throw new ArgumentNullException("factoryFn");
-            }
-
-            var key = cacheKey.ToString("N");
-
-            Response.ContentType = contentType;
-            
-            var result = PreProcessCachedResult(cacheKey, key, lastDateModified, cacheDuration);
-
-            if (result != null)
-            {
-                // Return null so that service stack won't do anything
-                return null;
-            }
-
-            var compress = ShouldCompressResponse(contentType);
-
-            return ToStaticResult(contentType, factoryFn, compress, headersOnly).Result;
-        }
-
-        /// <summary>
-        /// Shoulds the compress response.
-        /// </summary>
-        /// <param name="contentType">Type of the content.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        private bool ShouldCompressResponse(string contentType)
-        {
-            // It will take some work to support compression with byte range requests
-            if (IsRangeRequest)
-            {
-                return false;
-            }
-
-            // Don't compress media
-            if (contentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) || contentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase))
-            {
-                return false;
-            }
-
-            // Don't compress images
-            if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
-            {
-                return false;
-            }
-
-            if (contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase))
-            {
-                return false;
-            }
-            if (contentType.StartsWith("application/", StringComparison.OrdinalIgnoreCase))
-            {
-                return false;
-            }
-
-            return true;
-        }
-
-        /// <summary>
-        /// To the static result.
-        /// </summary>
-        /// <param name="contentType">Type of the content.</param>
-        /// <param name="factoryFn">The factory fn.</param>
-        /// <param name="compress">if set to <c>true</c> [compress].</param>
-        /// <param name="headersOnly">if set to <c>true</c> [headers only].</param>
-        /// <returns>System.Object.</returns>
-        private async Task<object> ToStaticResult(string contentType, Func<Task<Stream>> factoryFn, bool compress, bool headersOnly = false)
-        {
-            if (!compress || string.IsNullOrEmpty(RequestContext.CompressionType))
-            {
-                Response.ContentType = contentType;
-
-                var stream = await factoryFn().ConfigureAwait(false);
-
-                var httpListenerResponse = (HttpListenerResponse) Response.OriginalResponse;
-                httpListenerResponse.SendChunked = false;
-
-                if (IsRangeRequest)
-                {
-                    return new RangeRequestWriter(RequestContext.GetHeader("Range"), httpListenerResponse, stream, headersOnly);
-                }
-             
-                httpListenerResponse.ContentLength64 = stream.Length;
-                return headersOnly ? null : new StreamWriter(stream, Logger);
-            }
-
-            if (headersOnly)
-            {
-                return null;
-            }
-
-            string content;
-
-            using (var stream = await factoryFn().ConfigureAwait(false))
-            {
-                using (var reader = new StreamReader(stream))
-                {
-                    content = await reader.ReadToEndAsync().ConfigureAwait(false);
-                }
-            }
-
-            var contents = content.Compress(RequestContext.CompressionType);
-
-            return new CompressedResult(contents, RequestContext.CompressionType, contentType);
-        }
-
-        /// <summary>
-        /// Pres the process optimized result.
-        /// </summary>
-        /// <param name="cacheKey">The cache key.</param>
-        /// <param name="cacheKeyString">The cache key string.</param>
-        /// <param name="lastDateModified">The last date modified.</param>
-        /// <param name="cacheDuration">Duration of the cache.</param>
-        /// <returns>System.Object.</returns>
-        private object PreProcessCachedResult(Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration)
-        {
-            Response.AddHeader("ETag", cacheKeyString);
-
-            if (IsNotModified(cacheKey, lastDateModified, cacheDuration))
-            {
-                AddAgeHeader(lastDateModified);
-                AddExpiresHeader(cacheKeyString, cacheDuration);
-                //ctx.Response.SendChunked = false;
-
-                Response.StatusCode = 304;
-
-                return new byte[]{};
-            }
-
-            SetCachingHeaders(cacheKeyString, lastDateModified, cacheDuration);
-
-            return null;
-        }
-
-        /// <summary>
-        /// Determines whether [is not modified] [the specified cache key].
-        /// </summary>
-        /// <param name="cacheKey">The cache key.</param>
-        /// <param name="lastDateModified">The last date modified.</param>
-        /// <param name="cacheDuration">Duration of the cache.</param>
-        /// <returns><c>true</c> if [is not modified] [the specified cache key]; otherwise, <c>false</c>.</returns>
-        private bool IsNotModified(Guid? cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration)
-        {
-            var isNotModified = true;
-
-            var ifModifiedSinceHeader = RequestContext.GetHeader("If-Modified-Since");
-
-            if (!string.IsNullOrEmpty(ifModifiedSinceHeader))
-            {
-                DateTime ifModifiedSince;
-
-                if (DateTime.TryParse(ifModifiedSinceHeader, out ifModifiedSince))
-                {
-                    isNotModified = IsNotModified(ifModifiedSince.ToUniversalTime(), cacheDuration, lastDateModified);
-                }
-            }
-
-            var ifNoneMatchHeader = RequestContext.GetHeader("If-None-Match");
-            
-            // Validate If-None-Match
-            if (isNotModified && (cacheKey.HasValue || !string.IsNullOrEmpty(ifNoneMatchHeader)))
-            {
-                Guid ifNoneMatch;
-
-                if (Guid.TryParse(ifNoneMatchHeader ?? string.Empty, out ifNoneMatch))
-                {
-                    if (cacheKey.HasValue && cacheKey.Value == ifNoneMatch)
-                    {
-                        return true;
-                    }
-                }
-            }
-
-            return false;
-        }
-
-        /// <summary>
-        /// Determines whether [is not modified] [the specified if modified since].
-        /// </summary>
-        /// <param name="ifModifiedSince">If modified since.</param>
-        /// <param name="cacheDuration">Duration of the cache.</param>
-        /// <param name="dateModified">The date modified.</param>
-        /// <returns><c>true</c> if [is not modified] [the specified if modified since]; otherwise, <c>false</c>.</returns>
-        private bool IsNotModified(DateTime ifModifiedSince, TimeSpan? cacheDuration, DateTime? dateModified)
-        {
-            if (dateModified.HasValue)
-            {
-                var lastModified = NormalizeDateForComparison(dateModified.Value);
-                ifModifiedSince = NormalizeDateForComparison(ifModifiedSince);
-
-                return lastModified <= ifModifiedSince;
-            }
-
-            if (cacheDuration.HasValue)
-            {
-                var cacheExpirationDate = ifModifiedSince.Add(cacheDuration.Value);
-
-                if (DateTime.UtcNow < cacheExpirationDate)
-                {
-                    return true;
-                }
-            }
-
-            return false;
-        }
-
-
-        /// <summary>
-        /// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that
-        /// </summary>
-        /// <param name="date">The date.</param>
-        /// <returns>DateTime.</returns>
-        private DateTime NormalizeDateForComparison(DateTime date)
-        {
-            return new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Kind);
-        }
-
-        /// <summary>
-        /// Sets the caching headers.
-        /// </summary>
-        /// <param name="cacheKey">The cache key.</param>
-        /// <param name="lastDateModified">The last date modified.</param>
-        /// <param name="cacheDuration">Duration of the cache.</param>
-        private void SetCachingHeaders(string cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration)
-        {
-            // Don't specify both last modified and Etag, unless caching unconditionally. They are redundant
-            // https://developers.google.com/speed/docs/best-practices/caching#LeverageBrowserCaching
-            if (lastDateModified.HasValue && (string.IsNullOrEmpty(cacheKey) || cacheDuration.HasValue))
-            {
-                AddAgeHeader(lastDateModified);
-                Response.AddHeader("LastModified", lastDateModified.Value.ToString("r"));
-            }
-
-            if (cacheDuration.HasValue)
-            {
-                Response.AddHeader("Cache-Control", "public, max-age=" + Convert.ToInt32(cacheDuration.Value.TotalSeconds));
-            }
-            else if (!string.IsNullOrEmpty(cacheKey))
-            {
-                Response.AddHeader("Cache-Control", "public");
-            }
-            else
-            {
-                Response.AddHeader("Cache-Control", "no-cache, no-store, must-revalidate");
-                Response.AddHeader("pragma", "no-cache, no-store, must-revalidate");
-            }
-
-            AddExpiresHeader(cacheKey, cacheDuration);
-        }
-
-        /// <summary>
-        /// Adds the expires header.
-        /// </summary>
-        /// <param name="cacheKey">The cache key.</param>
-        /// <param name="cacheDuration">Duration of the cache.</param>
-        private void AddExpiresHeader(string cacheKey, TimeSpan? cacheDuration)
-        {
-            if (cacheDuration.HasValue)
-            {
-                Response.AddHeader("Expires", DateTime.UtcNow.Add(cacheDuration.Value).ToString("r"));
-            }
-            else if (string.IsNullOrEmpty(cacheKey))
-            {
-                Response.AddHeader("Expires", "-1");
-            }
-        }
-
-        /// <summary>
-        /// Adds the age header.
-        /// </summary>
-        /// <param name="lastDateModified">The last date modified.</param>
-        private void AddAgeHeader(DateTime? lastDateModified)
-        {
-            if (lastDateModified.HasValue)
-            {
-                Response.AddHeader("Age", Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture));
-            }
-        }
-
-        /// <summary>
-        /// Gets the routes.
-        /// </summary>
-        /// <returns>IEnumerable{RouteInfo}.</returns>
-        public virtual IEnumerable<RouteInfo> GetRoutes()
-        {
-            return new RouteInfo[] {};
-        }
-    }
-}

+ 578 - 3
MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs

@@ -1,14 +1,589 @@
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Model.Logging;
+using ServiceStack.Common;
 using ServiceStack.Common.Web;
 using ServiceStack.Common.Web;
+using ServiceStack.ServiceHost;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
 using System.IO;
 using System.IO;
+using System.Net;
+using System.Threading.Tasks;
+using MimeTypes = MediaBrowser.Common.Net.MimeTypes;
 
 
 namespace MediaBrowser.Server.Implementations.HttpServer
 namespace MediaBrowser.Server.Implementations.HttpServer
 {
 {
+    /// <summary>
+    /// Class HttpResultFactory
+    /// </summary>
     public class HttpResultFactory : IHttpResultFactory
     public class HttpResultFactory : IHttpResultFactory
     {
     {
-        public object GetResult(Stream stream, string contentType)
+        /// <summary>
+        /// The _logger
+        /// </summary>
+        private readonly ILogger _logger;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="HttpResultFactory"/> class.
+        /// </summary>
+        /// <param name="logManager">The log manager.</param>
+        public HttpResultFactory(ILogManager logManager)
+        {
+            _logger = logManager.GetLogger("HttpResultFactory");
+        }
+
+        /// <summary>
+        /// Gets the result.
+        /// </summary>
+        /// <param name="content">The content.</param>
+        /// <param name="contentType">Type of the content.</param>
+        /// <param name="responseHeaders">The response headers.</param>
+        /// <returns>System.Object.</returns>
+        public object GetResult(object content, string contentType, IDictionary<string, string> responseHeaders = null)
+        {
+            var result = new HttpResult(content, contentType);
+
+            if (responseHeaders != null)
+            {
+                AddResponseHeaders(result, responseHeaders);
+            }
+
+            return result;
+        }
+
+        /// <summary>
+        /// Gets the optimized result.
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="requestContext">The request context.</param>
+        /// <param name="result">The result.</param>
+        /// <param name="responseHeaders">The response headers.</param>
+        /// <returns>System.Object.</returns>
+        /// <exception cref="System.ArgumentNullException">result</exception>
+        public object GetOptimizedResult<T>(IRequestContext requestContext, T result, IDictionary<string, string> responseHeaders = null)
+            where T : class
+        {
+            if (result == null)
+            {
+                throw new ArgumentNullException("result");
+            }
+
+            var optimizedResult = requestContext.ToOptimizedResult(result);
+
+            if (responseHeaders != null)
+            {
+                // Apply headers
+                var hasOptions = optimizedResult as IHasOptions;
+
+                if (hasOptions != null)
+                {
+                    AddResponseHeaders(hasOptions, responseHeaders);
+                }
+            }
+
+            return optimizedResult;
+        }
+
+        /// <summary>
+        /// Gets the optimized result using cache.
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="requestContext">The request context.</param>
+        /// <param name="cacheKey">The cache key.</param>
+        /// <param name="lastDateModified">The last date modified.</param>
+        /// <param name="cacheDuration">Duration of the cache.</param>
+        /// <param name="factoryFn">The factory fn.</param>
+        /// <param name="responseHeaders">The response headers.</param>
+        /// <returns>System.Object.</returns>
+        /// <exception cref="System.ArgumentNullException">
+        /// cacheKey
+        /// or
+        /// factoryFn
+        /// </exception>
+        public object GetOptimizedResultUsingCache<T>(IRequestContext requestContext, Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func<T> factoryFn, IDictionary<string, string> responseHeaders = null)
+               where T : class
+        {
+            if (cacheKey == Guid.Empty)
+            {
+                throw new ArgumentNullException("cacheKey");
+            }
+            if (factoryFn == null)
+            {
+                throw new ArgumentNullException("factoryFn");
+            }
+
+            var key = cacheKey.ToString("N");
+
+            if (responseHeaders == null)
+            {
+                responseHeaders = new Dictionary<string, string>();
+            }
+
+            // See if the result is already cached in the browser
+            var result = GetCachedResult(requestContext, responseHeaders, cacheKey, key, lastDateModified, cacheDuration, null);
+
+            if (result != null)
+            {
+                return result;
+            }
+
+            return GetOptimizedResult(requestContext, factoryFn(), responseHeaders);
+        }
+
+        /// <summary>
+        /// To the cached result.
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="requestContext">The request context.</param>
+        /// <param name="cacheKey">The cache key.</param>
+        /// <param name="lastDateModified">The last date modified.</param>
+        /// <param name="cacheDuration">Duration of the cache.</param>
+        /// <param name="factoryFn">The factory fn.</param>
+        /// <param name="contentType">Type of the content.</param>
+        /// <param name="responseHeaders">The response headers.</param>
+        /// <returns>System.Object.</returns>
+        /// <exception cref="System.ArgumentNullException">cacheKey</exception>
+        public object GetCachedResult<T>(IRequestContext requestContext, Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func<T> factoryFn, string contentType, IDictionary<string, string> responseHeaders = null)
+          where T : class
+        {
+            if (cacheKey == Guid.Empty)
+            {
+                throw new ArgumentNullException("cacheKey");
+            }
+            if (factoryFn == null)
+            {
+                throw new ArgumentNullException("factoryFn");
+            }
+
+            var key = cacheKey.ToString("N");
+
+            if (responseHeaders == null)
+            {
+                responseHeaders = new Dictionary<string, string>();
+            }
+
+            // See if the result is already cached in the browser
+            var result = GetCachedResult(requestContext, responseHeaders, cacheKey, key, lastDateModified, cacheDuration, contentType);
+
+            if (result != null)
+            {
+                return result;
+            }
+
+            result = factoryFn();
+
+            // Apply caching headers
+            var hasOptions = result as IHasOptions;
+
+            if (hasOptions != null)
+            {
+                AddResponseHeaders(hasOptions, responseHeaders);
+                return hasOptions;
+            }
+
+            // Otherwise wrap into an HttpResult
+            var httpResult = new HttpResult(result, contentType ?? "text/html", HttpStatusCode.NotModified);
+
+            AddResponseHeaders(httpResult, responseHeaders);
+
+            return httpResult;
+        }
+
+        /// <summary>
+        /// Pres the process optimized result.
+        /// </summary>
+        /// <param name="requestContext">The request context.</param>
+        /// <param name="responseHeaders">The responseHeaders.</param>
+        /// <param name="cacheKey">The cache key.</param>
+        /// <param name="cacheKeyString">The cache key string.</param>
+        /// <param name="lastDateModified">The last date modified.</param>
+        /// <param name="cacheDuration">Duration of the cache.</param>
+        /// <param name="contentType">Type of the content.</param>
+        /// <returns>System.Object.</returns>
+        private object GetCachedResult(IRequestContext requestContext, IDictionary<string, string> responseHeaders, Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType)
+        {
+            responseHeaders["ETag"] = cacheKeyString;
+
+            if (IsNotModified(requestContext, cacheKey, lastDateModified, cacheDuration))
+            {
+                AddAgeHeader(responseHeaders, lastDateModified);
+                AddExpiresHeader(responseHeaders, cacheKeyString, cacheDuration);
+
+                var result = new HttpResult(new byte[] { }, contentType ?? "text/html", HttpStatusCode.NotModified);
+
+                AddResponseHeaders(result, responseHeaders);
+
+                return result;
+            }
+
+            AddCachingHeaders(responseHeaders, cacheKeyString, lastDateModified, cacheDuration);
+
+            return null;
+        }
+
+        /// <summary>
+        /// Gets the static file result.
+        /// </summary>
+        /// <param name="requestContext">The request context.</param>
+        /// <param name="path">The path.</param>
+        /// <param name="responseHeaders">The response headers.</param>
+        /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
+        /// <returns>System.Object.</returns>
+        /// <exception cref="System.ArgumentNullException">path</exception>
+        public object GetStaticFileResult(IRequestContext requestContext, string path, IDictionary<string, string> responseHeaders = null, bool isHeadRequest = false)
         {
         {
-            return new HttpResult(stream, contentType);
+            if (string.IsNullOrEmpty(path))
+            {
+                throw new ArgumentNullException("path");
+            }
+
+            var dateModified = File.GetLastWriteTimeUtc(path);
+
+            var cacheKey = path + dateModified.Ticks;
+
+            return GetStaticResult(requestContext, cacheKey.GetMD5(), dateModified, null, MimeTypes.GetMimeType(path), () => Task.FromResult(GetFileStream(path)), responseHeaders, isHeadRequest);
+        }
+
+        /// <summary>
+        /// Gets the file stream.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        /// <returns>Stream.</returns>
+        private Stream GetFileStream(string path)
+        {
+            return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous);
+        }
+
+        /// <summary>
+        /// Gets the static result.
+        /// </summary>
+        /// <param name="requestContext">The request context.</param>
+        /// <param name="cacheKey">The cache key.</param>
+        /// <param name="lastDateModified">The last date modified.</param>
+        /// <param name="cacheDuration">Duration of the cache.</param>
+        /// <param name="contentType">Type of the content.</param>
+        /// <param name="factoryFn">The factory fn.</param>
+        /// <param name="responseHeaders">The response headers.</param>
+        /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
+        /// <returns>System.Object.</returns>
+        /// <exception cref="System.ArgumentNullException">cacheKey
+        /// or
+        /// factoryFn</exception>
+        public object GetStaticResult(IRequestContext requestContext, Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType, Func<Task<Stream>> factoryFn, IDictionary<string, string> responseHeaders = null, bool isHeadRequest = false)
+        {
+            if (cacheKey == Guid.Empty)
+            {
+                throw new ArgumentNullException("cacheKey");
+            }
+            if (factoryFn == null)
+            {
+                throw new ArgumentNullException("factoryFn");
+            }
+
+            var key = cacheKey.ToString("N");
+
+            if (responseHeaders == null)
+            {
+                responseHeaders = new Dictionary<string, string>();
+            }
+
+            // See if the result is already cached in the browser
+            var result = GetCachedResult(requestContext, responseHeaders, cacheKey, key, lastDateModified, cacheDuration, contentType);
+
+            if (result != null)
+            {
+                return result;
+            }
+
+            var compress = ShouldCompressResponse(requestContext, contentType);
+
+            var hasOptions = GetStaticResult(requestContext, responseHeaders, contentType, factoryFn, compress, isHeadRequest).Result;
+
+            AddResponseHeaders(hasOptions, responseHeaders);
+
+            return hasOptions;
+        }
+
+        /// <summary>
+        /// Shoulds the compress response.
+        /// </summary>
+        /// <param name="requestContext">The request context.</param>
+        /// <param name="contentType">Type of the content.</param>
+        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
+        private bool ShouldCompressResponse(IRequestContext requestContext, string contentType)
+        {
+            // It will take some work to support compression with byte range requests
+            if (!string.IsNullOrEmpty(requestContext.GetHeader("Range")))
+            {
+                return false;
+            }
+
+            // Don't compress media
+            if (contentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) || contentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase))
+            {
+                return false;
+            }
+
+            // Don't compress images
+            if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
+            {
+                return false;
+            }
+
+            if (contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase))
+            {
+                return false;
+            }
+            if (contentType.StartsWith("application/", StringComparison.OrdinalIgnoreCase))
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// The us culture
+        /// </summary>
+        private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
+
+        /// <summary>
+        /// Gets the static result.
+        /// </summary>
+        /// <param name="requestContext">The request context.</param>
+        /// <param name="responseHeaders">The response headers.</param>
+        /// <param name="contentType">Type of the content.</param>
+        /// <param name="factoryFn">The factory fn.</param>
+        /// <param name="compress">if set to <c>true</c> [compress].</param>
+        /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
+        /// <returns>Task{IHasOptions}.</returns>
+        private async Task<IHasOptions> GetStaticResult(IRequestContext requestContext, IDictionary<string, string> responseHeaders, string contentType, Func<Task<Stream>> factoryFn, bool compress, bool isHeadRequest)
+        {
+            if (!compress || string.IsNullOrEmpty(requestContext.CompressionType))
+            {
+                var stream = await factoryFn().ConfigureAwait(false);
+
+                var rangeHeader = requestContext.GetHeader("Range");
+
+                if (!string.IsNullOrEmpty(rangeHeader))
+                {
+                    return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest);
+                }
+
+                responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture);
+
+                if (isHeadRequest)
+                {
+                    return new HttpResult(new byte[] { }, contentType);
+                }
+
+                return new StreamWriter(stream, contentType, _logger);
+            }
+
+            if (isHeadRequest)
+            {
+                return new HttpResult(new byte[] { }, contentType);
+            }
+
+            string content;
+
+            using (var stream = await factoryFn().ConfigureAwait(false))
+            {
+                using (var reader = new StreamReader(stream))
+                {
+                    content = await reader.ReadToEndAsync().ConfigureAwait(false);
+                }
+            }
+
+            var contents = content.Compress(requestContext.CompressionType);
+
+            return new CompressedResult(contents, requestContext.CompressionType, contentType);
+        }
+
+        /// <summary>
+        /// Adds the caching responseHeaders.
+        /// </summary>
+        /// <param name="responseHeaders">The responseHeaders.</param>
+        /// <param name="cacheKey">The cache key.</param>
+        /// <param name="lastDateModified">The last date modified.</param>
+        /// <param name="cacheDuration">Duration of the cache.</param>
+        private void AddCachingHeaders(IDictionary<string, string> responseHeaders, string cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration)
+        {
+            // Don't specify both last modified and Etag, unless caching unconditionally. They are redundant
+            // https://developers.google.com/speed/docs/best-practices/caching#LeverageBrowserCaching
+            if (lastDateModified.HasValue && (string.IsNullOrEmpty(cacheKey) || cacheDuration.HasValue))
+            {
+                AddAgeHeader(responseHeaders, lastDateModified);
+                responseHeaders["LastModified"] = lastDateModified.Value.ToString("r");
+            }
+
+            if (cacheDuration.HasValue)
+            {
+                responseHeaders["Cache-Control"] = "public, max-age=" + Convert.ToInt32(cacheDuration.Value.TotalSeconds);
+            }
+            else if (!string.IsNullOrEmpty(cacheKey))
+            {
+                responseHeaders["Cache-Control"] = "public";
+            }
+            else
+            {
+                responseHeaders["Cache-Control"] = "no-cache, no-store, must-revalidate";
+                responseHeaders["pragma"] = "no-cache, no-store, must-revalidate";
+            }
+
+            AddExpiresHeader(responseHeaders, cacheKey, cacheDuration);
+        }
+
+        /// <summary>
+        /// Adds the expires header.
+        /// </summary>
+        /// <param name="responseHeaders">The responseHeaders.</param>
+        /// <param name="cacheKey">The cache key.</param>
+        /// <param name="cacheDuration">Duration of the cache.</param>
+        private void AddExpiresHeader(IDictionary<string, string> responseHeaders, string cacheKey, TimeSpan? cacheDuration)
+        {
+            if (cacheDuration.HasValue)
+            {
+                responseHeaders["Expires"] = DateTime.UtcNow.Add(cacheDuration.Value).ToString("r");
+            }
+            else if (string.IsNullOrEmpty(cacheKey))
+            {
+                responseHeaders["Expires"] = "-1";
+            }
+        }
+
+        /// <summary>
+        /// Adds the age header.
+        /// </summary>
+        /// <param name="responseHeaders">The responseHeaders.</param>
+        /// <param name="lastDateModified">The last date modified.</param>
+        private void AddAgeHeader(IDictionary<string, string> responseHeaders, DateTime? lastDateModified)
+        {
+            if (lastDateModified.HasValue)
+            {
+                responseHeaders["Age"] = Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture);
+            }
+        }
+        /// <summary>
+        /// Determines whether [is not modified] [the specified cache key].
+        /// </summary>
+        /// <param name="requestContext">The request context.</param>
+        /// <param name="cacheKey">The cache key.</param>
+        /// <param name="lastDateModified">The last date modified.</param>
+        /// <param name="cacheDuration">Duration of the cache.</param>
+        /// <returns><c>true</c> if [is not modified] [the specified cache key]; otherwise, <c>false</c>.</returns>
+        private bool IsNotModified(IRequestContext requestContext, Guid? cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration)
+        {
+            var isNotModified = true;
+
+            var ifModifiedSinceHeader = requestContext.GetHeader("If-Modified-Since");
+
+            if (!string.IsNullOrEmpty(ifModifiedSinceHeader))
+            {
+                DateTime ifModifiedSince;
+
+                if (DateTime.TryParse(ifModifiedSinceHeader, out ifModifiedSince))
+                {
+                    isNotModified = IsNotModified(ifModifiedSince.ToUniversalTime(), cacheDuration, lastDateModified);
+                }
+            }
+
+            var ifNoneMatchHeader = requestContext.GetHeader("If-None-Match");
+
+            // Validate If-None-Match
+            if (isNotModified && (cacheKey.HasValue || !string.IsNullOrEmpty(ifNoneMatchHeader)))
+            {
+                Guid ifNoneMatch;
+
+                if (Guid.TryParse(ifNoneMatchHeader ?? string.Empty, out ifNoneMatch))
+                {
+                    if (cacheKey.HasValue && cacheKey.Value == ifNoneMatch)
+                    {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Determines whether [is not modified] [the specified if modified since].
+        /// </summary>
+        /// <param name="ifModifiedSince">If modified since.</param>
+        /// <param name="cacheDuration">Duration of the cache.</param>
+        /// <param name="dateModified">The date modified.</param>
+        /// <returns><c>true</c> if [is not modified] [the specified if modified since]; otherwise, <c>false</c>.</returns>
+        private bool IsNotModified(DateTime ifModifiedSince, TimeSpan? cacheDuration, DateTime? dateModified)
+        {
+            if (dateModified.HasValue)
+            {
+                var lastModified = NormalizeDateForComparison(dateModified.Value);
+                ifModifiedSince = NormalizeDateForComparison(ifModifiedSince);
+
+                return lastModified <= ifModifiedSince;
+            }
+
+            if (cacheDuration.HasValue)
+            {
+                var cacheExpirationDate = ifModifiedSince.Add(cacheDuration.Value);
+
+                if (DateTime.UtcNow < cacheExpirationDate)
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+
+        /// <summary>
+        /// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that
+        /// </summary>
+        /// <param name="date">The date.</param>
+        /// <returns>DateTime.</returns>
+        private DateTime NormalizeDateForComparison(DateTime date)
+        {
+            return new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Kind);
+        }
+
+        /// <summary>
+        /// Adds the response headers.
+        /// </summary>
+        /// <param name="hasOptions">The has options.</param>
+        /// <param name="responseHeaders">The response headers.</param>
+        private void AddResponseHeaders(IHasOptions hasOptions, IDictionary<string, string> responseHeaders)
+        {
+            foreach (var item in responseHeaders)
+            {
+                hasOptions.Options[item.Key] = item.Value;
+            }
+        }
+
+        /// <summary>
+        /// Gets the error result.
+        /// </summary>
+        /// <param name="statusCode">The status code.</param>
+        /// <param name="errorMessage">The error message.</param>
+        /// <param name="responseHeaders">The response headers.</param>
+        /// <returns>System.Object.</returns>
+        public void ThrowError(int statusCode, string errorMessage, IDictionary<string, string> responseHeaders = null)
+        {
+            var error = new HttpError
+            {
+                Status = statusCode,
+                ErrorCode = errorMessage
+            };
+
+            if (responseHeaders != null)
+            {
+                AddResponseHeaders(error, responseHeaders);
+            }
+
+            throw error;
         }
         }
     }
     }
 }
 }

+ 24 - 5
MediaBrowser.Server.Implementations/HttpServer/HttpServer.cs

@@ -174,6 +174,30 @@ namespace MediaBrowser.Server.Implementations.HttpServer
                         // This is a good choice for applications that are singly homed and depend on public proxies for user locality.                        
                         // This is a good choice for applications that are singly homed and depend on public proxies for user locality.                        
                         res.AddHeader("Vary", "Accept-Encoding");
                         res.AddHeader("Vary", "Accept-Encoding");
                     }
                     }
+
+                    var hasOptions = dto as IHasOptions;
+
+                    if (hasOptions != null)
+                    {
+                        // Content length has to be explicitly set on on HttpListenerResponse or it won't be happy
+                        string contentLength;
+
+                        if (hasOptions.Options.TryGetValue("Content-Length", out contentLength) && !string.IsNullOrEmpty(contentLength))
+                        {
+                            var length = long.Parse(contentLength);
+
+                            if (length > 0)
+                            {
+                                var response = (HttpListenerResponse) res.OriginalResponse;
+
+                                response.ContentLength64 = length;
+
+                                // Disable chunked encoding. Technically this is only needed when using Content-Range, but
+                                // anytime we know the content length there's no need for it
+                                response.SendChunked = false;
+                            }
+                        }
+                    }
                 });
                 });
         }
         }
 
 
@@ -532,11 +556,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
 
 
             EndpointHost.ConfigureHost(this, ServerName, CreateServiceManager());
             EndpointHost.ConfigureHost(this, ServerName, CreateServiceManager());
             ContentTypeFilters.Register(ContentType.ProtoBuf, (reqCtx, res, stream) => ProtobufSerializer.SerializeToStream(res, stream), (type, stream) => ProtobufSerializer.DeserializeFromStream(stream, type));
             ContentTypeFilters.Register(ContentType.ProtoBuf, (reqCtx, res, stream) => ProtobufSerializer.SerializeToStream(res, stream), (type, stream) => ProtobufSerializer.DeserializeFromStream(stream, type));
-            
-            foreach (var route in services.SelectMany(i => i.GetRoutes()))
-            {
-                Routes.Add(route.RequestType, route.Path, route.Verbs);
-            }
 
 
             Init();
             Init();
         }
         }

+ 108 - 82
MediaBrowser.Server.Implementations/HttpServer/RangeRequestWriter.cs

@@ -1,6 +1,8 @@
 using ServiceStack.Service;
 using ServiceStack.Service;
+using ServiceStack.ServiceHost;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Globalization;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Net;
 using System.Net;
@@ -8,30 +10,105 @@ using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Server.Implementations.HttpServer
 namespace MediaBrowser.Server.Implementations.HttpServer
 {
 {
-    public class RangeRequestWriter : IStreamWriter
+    public class RangeRequestWriter : IStreamWriter, IHttpResult
     {
     {
         /// <summary>
         /// <summary>
         /// Gets or sets the source stream.
         /// Gets or sets the source stream.
         /// </summary>
         /// </summary>
         /// <value>The source stream.</value>
         /// <value>The source stream.</value>
         private Stream SourceStream { get; set; }
         private Stream SourceStream { get; set; }
-        private HttpListenerResponse Response { get; set; }
         private string RangeHeader { get; set; }
         private string RangeHeader { get; set; }
         private bool IsHeadRequest { get; set; }
         private bool IsHeadRequest { get; set; }
 
 
+        private long RangeStart { get; set; }
+        private long RangeEnd { get; set; }
+        private long RangeLength { get; set; }
+        private long TotalContentLength { get; set; }
+
+        /// <summary>
+        /// The _options
+        /// </summary>
+        private readonly Dictionary<string, string> _options = new Dictionary<string, string>();
+
+        /// <summary>
+        /// The us culture
+        /// </summary>
+        private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
+
+        /// <summary>
+        /// Additional HTTP Headers
+        /// </summary>
+        /// <value>The headers.</value>
+        public Dictionary<string, string> Headers
+        {
+            get { return _options; }
+        }
+
+        /// <summary>
+        /// Gets the options.
+        /// </summary>
+        /// <value>The options.</value>
+        public IDictionary<string, string> Options
+        {
+            get { return Headers; }
+        }
+
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="StreamWriter" /> class.
         /// Initializes a new instance of the <see cref="StreamWriter" /> class.
         /// </summary>
         /// </summary>
         /// <param name="rangeHeader">The range header.</param>
         /// <param name="rangeHeader">The range header.</param>
-        /// <param name="response">The response.</param>
         /// <param name="source">The source.</param>
         /// <param name="source">The source.</param>
+        /// <param name="contentType">Type of the content.</param>
         /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
         /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
-        public RangeRequestWriter(string rangeHeader, HttpListenerResponse response, Stream source, bool isHeadRequest)
+        public RangeRequestWriter(string rangeHeader, Stream source, string contentType, bool isHeadRequest)
         {
         {
+            if (string.IsNullOrEmpty(contentType))
+            {
+                throw new ArgumentNullException("contentType");
+            }
+            
             RangeHeader = rangeHeader;
             RangeHeader = rangeHeader;
-            Response = response;
             SourceStream = source;
             SourceStream = source;
             IsHeadRequest = isHeadRequest;
             IsHeadRequest = isHeadRequest;
+
+            ContentType = contentType;
+            Options["Content-Type"] = contentType;
+            Options["Accept-Ranges"] = "bytes";
+            StatusCode = HttpStatusCode.PartialContent;
+
+            SetRangeValues();
+        }
+
+        /// <summary>
+        /// Sets the range values.
+        /// </summary>
+        private void SetRangeValues()
+        {
+            var requestedRange = RequestedRanges.First();
+
+            TotalContentLength = SourceStream.Length;
+
+            // If the requested range is "0-", we can optimize by just doing a stream copy
+            if (!requestedRange.Value.HasValue)
+            {
+                RangeEnd = TotalContentLength - 1;
+            }
+            else
+            {
+                RangeEnd = requestedRange.Value.Value;
+            }
+
+            RangeStart = requestedRange.Key;
+            RangeLength = 1 + RangeEnd - RangeStart;
+            
+            // Content-Length is the length of what we're serving, not the original content
+            Options["Content-Length"] = RangeLength.ToString(UsCulture);
+            Options["Content-Range"] = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength);
+            
+            if (RangeStart > 0)
+            {
+                SourceStream.Position = RangeStart;
+            }
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -42,7 +119,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
         /// Gets the requested ranges.
         /// Gets the requested ranges.
         /// </summary>
         /// </summary>
         /// <value>The requested ranges.</value>
         /// <value>The requested ranges.</value>
-        protected IEnumerable<KeyValuePair<long, long?>> RequestedRanges
+        protected List<KeyValuePair<long, long?>> RequestedRanges
         {
         {
             get
             get
             {
             {
@@ -83,9 +160,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
         /// <param name="responseStream">The response stream.</param>
         /// <param name="responseStream">The response stream.</param>
         public void WriteTo(Stream responseStream)
         public void WriteTo(Stream responseStream)
         {
         {
-            Response.Headers["Accept-Ranges"] = "bytes";
-            Response.StatusCode = 206;
-            
             var task = WriteToAsync(responseStream);
             var task = WriteToAsync(responseStream);
 
 
             Task.WaitAll(task);
             Task.WaitAll(task);
@@ -98,94 +172,46 @@ namespace MediaBrowser.Server.Implementations.HttpServer
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         private async Task WriteToAsync(Stream responseStream)
         private async Task WriteToAsync(Stream responseStream)
         {
         {
-            using (var source = SourceStream)
+            // Headers only
+            if (IsHeadRequest)
             {
             {
-                var requestedRange = RequestedRanges.First();
-
-                var totalLength = SourceStream.Length;
+                return;
+            }
 
 
+            using (var source = SourceStream)
+            {
                 // If the requested range is "0-", we can optimize by just doing a stream copy
                 // If the requested range is "0-", we can optimize by just doing a stream copy
-                if (!requestedRange.Value.HasValue)
+                if (RangeEnd == TotalContentLength - 1)
                 {
                 {
-                    await ServeCompleteRangeRequest(source, requestedRange, responseStream, totalLength).ConfigureAwait(false);
+                    await source.CopyToAsync(responseStream).ConfigureAwait(false);
                 }
                 }
+                else
+                {
+                    // Read the bytes we need
+                    var buffer = new byte[Convert.ToInt32(RangeLength)];
+                    await source.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
 
 
-                // This will have to buffer a portion of the content into memory
-                await ServePartialRangeRequest(source, requestedRange.Key, requestedRange.Value.Value, responseStream, totalLength).ConfigureAwait(false);
+                    await responseStream.WriteAsync(buffer, 0, Convert.ToInt32(RangeLength)).ConfigureAwait(false);
+                }
             }
             }
         }
         }
 
 
-        /// <summary>
-        /// Handles a range request of "bytes=0-"
-        /// This will serve the complete content and add the content-range header
-        /// </summary>
-        /// <param name="sourceStream">The source stream.</param>
-        /// <param name="requestedRange">The requested range.</param>
-        /// <param name="responseStream">The response stream.</param>
-        /// <param name="totalContentLength">Total length of the content.</param>
-        /// <returns>Task.</returns>
-        private Task ServeCompleteRangeRequest(Stream sourceStream, KeyValuePair<long, long?> requestedRange, Stream responseStream, long totalContentLength)
-        {
-            var rangeStart = requestedRange.Key;
-            var rangeEnd = totalContentLength - 1;
-            var rangeLength = 1 + rangeEnd - rangeStart;
+        public string ContentType { get; set; }
 
 
-            // Content-Length is the length of what we're serving, not the original content
-            Response.ContentLength64 = rangeLength;
-            Response.Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", rangeStart, rangeEnd, totalContentLength);
+        public IRequestContext RequestContext { get; set; }
 
 
-            // Headers only
-            if (IsHeadRequest)
-            {
-                return Task.FromResult(true);
-            }
+        public object Response { get; set; }
 
 
-            if (rangeStart > 0)
-            {
-                sourceStream.Position = rangeStart;
-            }
+        public IContentTypeWriter ResponseFilter { get; set; }
 
 
-            return sourceStream.CopyToAsync(responseStream);
-        }
+        public int Status { get; set; }
 
 
-        /// <summary>
-        /// Serves a partial range request
-        /// </summary>
-        /// <param name="sourceStream">The source stream.</param>
-        /// <param name="rangeStart">The range start.</param>
-        /// <param name="rangeEnd">The range end.</param>
-        /// <param name="responseStream">The response stream.</param>
-        /// <param name="totalContentLength">Total length of the content.</param>
-        /// <returns>Task.</returns>
-        private async Task ServePartialRangeRequest(Stream sourceStream, long rangeStart, long rangeEnd, Stream responseStream, long totalContentLength)
+        public HttpStatusCode StatusCode
         {
         {
-            var rangeLength = 1 + rangeEnd - rangeStart;
-
-            // Content-Length is the length of what we're serving, not the original content
-            Response.ContentLength64 = rangeLength;
-            Response.Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", rangeStart, rangeEnd, totalContentLength);
-
-            // Headers only
-            if (IsHeadRequest)
-            {
-                return;
-            }
-
-            sourceStream.Position = rangeStart;
-
-            // Fast track to just copy the stream to the end
-            if (rangeEnd == totalContentLength - 1)
-            {
-                await sourceStream.CopyToAsync(responseStream).ConfigureAwait(false);
-            }
-            else
-            {
-                // Read the bytes we need
-                var buffer = new byte[Convert.ToInt32(rangeLength)];
-                await sourceStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
-
-                await responseStream.WriteAsync(buffer, 0, Convert.ToInt32(rangeLength)).ConfigureAwait(false);
-            }
+            get { return (HttpStatusCode)Status; }
+            set { Status = (int)value; }
         }
         }
+
+        public string StatusDescription { get; set; }
     }
     }
 }
 }

+ 25 - 2
MediaBrowser.Server.Implementations/HttpServer/StreamWriter.cs

@@ -1,6 +1,8 @@
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using ServiceStack.Service;
 using ServiceStack.Service;
+using ServiceStack.ServiceHost;
 using System;
 using System;
+using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
@@ -9,7 +11,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
     /// <summary>
     /// <summary>
     /// Class StreamWriter
     /// Class StreamWriter
     /// </summary>
     /// </summary>
-    public class StreamWriter : IStreamWriter
+    public class StreamWriter : IStreamWriter, IHasOptions
     {
     {
         private ILogger Logger { get; set; }
         private ILogger Logger { get; set; }
         
         
@@ -19,15 +21,36 @@ namespace MediaBrowser.Server.Implementations.HttpServer
         /// <value>The source stream.</value>
         /// <value>The source stream.</value>
         public Stream SourceStream { get; set; }
         public Stream SourceStream { get; set; }
 
 
+        /// <summary>
+        /// The _options
+        /// </summary>
+        private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
+        /// <summary>
+        /// Gets the options.
+        /// </summary>
+        /// <value>The options.</value>
+        public IDictionary<string, string> Options
+        {
+            get { return _options; }
+        }
+
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="StreamWriter" /> class.
         /// Initializes a new instance of the <see cref="StreamWriter" /> class.
         /// </summary>
         /// </summary>
         /// <param name="source">The source.</param>
         /// <param name="source">The source.</param>
+        /// <param name="contentType">Type of the content.</param>
         /// <param name="logger">The logger.</param>
         /// <param name="logger">The logger.</param>
-        public StreamWriter(Stream source, ILogger logger)
+        public StreamWriter(Stream source, string contentType, ILogger logger)
         {
         {
+            if (string.IsNullOrEmpty(contentType))
+            {
+                throw new ArgumentNullException("contentType");
+            }
+
             SourceStream = source;
             SourceStream = source;
             Logger = logger;
             Logger = logger;
+
+            Options["Content-Type"] = contentType;
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 13 - 4
MediaBrowser.Server.Implementations/HttpServer/SwaggerService.cs

@@ -1,4 +1,5 @@
-using ServiceStack.ServiceHost;
+using MediaBrowser.Common.Net;
+using ServiceStack.ServiceHost;
 using System.Diagnostics;
 using System.Diagnostics;
 using System.IO;
 using System.IO;
 
 
@@ -16,9 +17,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer
         /// <value>The name.</value>
         /// <value>The name.</value>
         public string ResourceName { get; set; }
         public string ResourceName { get; set; }
     }
     }
-    
-    public class SwaggerService : BaseRestService
+
+    public class SwaggerService : IRequiresRequestContext, IRestfulService
     {
     {
+        public IHttpResultFactory HttpResultFactory { get; set; }
+        
         /// <summary>
         /// <summary>
         /// Gets the specified request.
         /// Gets the specified request.
         /// </summary>
         /// </summary>
@@ -32,7 +35,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer
 
 
             var requestedFile = Path.Combine(swaggerDirectory, request.ResourceName.Replace('/', '\\'));
             var requestedFile = Path.Combine(swaggerDirectory, request.ResourceName.Replace('/', '\\'));
 
 
-            return ToStaticFileResult(requestedFile);
+            return HttpResultFactory.GetStaticFileResult(RequestContext, requestedFile);
         }
         }
+
+        /// <summary>
+        /// Gets or sets the request context.
+        /// </summary>
+        /// <value>The request context.</value>
+        public IRequestContext RequestContext { get; set; }
     }
     }
 }
 }

+ 0 - 1
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -115,7 +115,6 @@
     </Compile>
     </Compile>
     <Compile Include="BdInfo\BdInfoExaminer.cs" />
     <Compile Include="BdInfo\BdInfoExaminer.cs" />
     <Compile Include="Configuration\ServerConfigurationManager.cs" />
     <Compile Include="Configuration\ServerConfigurationManager.cs" />
-    <Compile Include="HttpServer\BaseRestService.cs" />
     <Compile Include="HttpServer\HttpResultFactory.cs" />
     <Compile Include="HttpServer\HttpResultFactory.cs" />
     <Compile Include="HttpServer\HttpServer.cs" />
     <Compile Include="HttpServer\HttpServer.cs" />
     <Compile Include="HttpServer\NativeWebSocket.cs" />
     <Compile Include="HttpServer\NativeWebSocket.cs" />

+ 1 - 1
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -163,7 +163,7 @@ namespace MediaBrowser.ServerApplication
 
 
             await base.RegisterResources().ConfigureAwait(false);
             await base.RegisterResources().ConfigureAwait(false);
 
 
-            RegisterSingleInstance<IHttpResultFactory>(new HttpResultFactory());
+            RegisterSingleInstance<IHttpResultFactory>(new HttpResultFactory(LogManager));
 
 
             RegisterSingleInstance<IServerApplicationHost>(this);
             RegisterSingleInstance<IServerApplicationHost>(this);
             RegisterSingleInstance<IServerApplicationPaths>(ApplicationPaths);
             RegisterSingleInstance<IServerApplicationPaths>(ApplicationPaths);

+ 10 - 1
MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj

@@ -135,6 +135,15 @@
       <SpecificVersion>False</SpecificVersion>
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\MediaBrowser.IsoMounting.3.0.51\lib\net45\pfmclrapi.dll</HintPath>
       <HintPath>..\packages\MediaBrowser.IsoMounting.3.0.51\lib\net45\pfmclrapi.dll</HintPath>
     </Reference>
     </Reference>
+    <Reference Include="ServiceStack.Common">
+      <HintPath>..\packages\ServiceStack.Common.3.9.42\lib\net35\ServiceStack.Common.dll</HintPath>
+    </Reference>
+    <Reference Include="ServiceStack.Interfaces">
+      <HintPath>..\packages\ServiceStack.Common.3.9.42\lib\net35\ServiceStack.Interfaces.dll</HintPath>
+    </Reference>
+    <Reference Include="ServiceStack.Text">
+      <HintPath>..\packages\ServiceStack.Text.3.9.42\lib\net35\ServiceStack.Text.dll</HintPath>
+    </Reference>
     <Reference Include="SimpleInjector, Version=2.0.0.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
     <Reference Include="SimpleInjector, Version=2.0.0.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\SimpleInjector.2.0.0-beta5\lib\net40-client\SimpleInjector.dll</HintPath>
       <HintPath>..\packages\SimpleInjector.2.0.0-beta5\lib\net40-client\SimpleInjector.dll</HintPath>
@@ -405,7 +414,7 @@ mkdir "$(SolutionDir)..\Deploy\Server\System\CorePlugins"
 xcopy "$(TargetDir)CorePlugins" "$(SolutionDir)..\Deploy\Server\System\CorePlugins" /y
 xcopy "$(TargetDir)CorePlugins" "$(SolutionDir)..\Deploy\Server\System\CorePlugins" /y
 
 
 mkdir "$(SolutionDir)..\Deploy\Server\System\dashboard-ui"
 mkdir "$(SolutionDir)..\Deploy\Server\System\dashboard-ui"
-xcopy "$(TargetDir)dashboard-ui" "$(SolutionDir)..\Deploy\Server\System\dashboard-ui" /y
+xcopy "$(TargetDir)dashboard-ui" "$(SolutionDir)..\Deploy\Server\System\dashboard-ui" /y /s
 
 
 del "$(SolutionDir)..\Deploy\MBServer.zip"
 del "$(SolutionDir)..\Deploy\MBServer.zip"
 "$(SolutionDir)ThirdParty\7zip\7za" a -tzip "$(SolutionDir)..\Deploy\MBServer.zip" "$(SolutionDir)..\Deploy\Server\*"
 "$(SolutionDir)ThirdParty\7zip\7za" a -tzip "$(SolutionDir)..\Deploy\MBServer.zip" "$(SolutionDir)..\Deploy\Server\*"

+ 2 - 0
MediaBrowser.ServerApplication/packages.config

@@ -4,6 +4,8 @@
   <package id="Hardcodet.Wpf.TaskbarNotification" version="1.0.4.0" targetFramework="net45" />
   <package id="Hardcodet.Wpf.TaskbarNotification" version="1.0.4.0" targetFramework="net45" />
   <package id="MediaBrowser.IsoMounting" version="3.0.51" targetFramework="net45" />
   <package id="MediaBrowser.IsoMounting" version="3.0.51" targetFramework="net45" />
   <package id="NLog" version="2.0.0.2000" targetFramework="net45" />
   <package id="NLog" version="2.0.0.2000" targetFramework="net45" />
+  <package id="ServiceStack.Common" version="3.9.42" targetFramework="net45" />
+  <package id="ServiceStack.Text" version="3.9.42" targetFramework="net45" />
   <package id="SimpleInjector" version="2.0.0-beta5" targetFramework="net45" />
   <package id="SimpleInjector" version="2.0.0-beta5" targetFramework="net45" />
   <package id="System.Data.SQLite" version="1.0.84.0" targetFramework="net45" />
   <package id="System.Data.SQLite" version="1.0.84.0" targetFramework="net45" />
 </packages>
 </packages>

+ 31 - 9
MediaBrowser.WebDashboard/Api/DashboardService.cs

@@ -1,5 +1,4 @@
-using System.Diagnostics;
-using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Common.ScheduledTasks;
@@ -9,11 +8,11 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Tasks;
 using MediaBrowser.Model.Tasks;
-using MediaBrowser.Server.Implementations.HttpServer;
 using ServiceStack.ServiceHost;
 using ServiceStack.ServiceHost;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.ComponentModel.Composition;
 using System.ComponentModel.Composition;
+using System.Diagnostics;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Reflection;
 using System.Reflection;
@@ -27,6 +26,7 @@ namespace MediaBrowser.WebDashboard.Api
     /// Class GetDashboardConfigurationPages
     /// Class GetDashboardConfigurationPages
     /// </summary>
     /// </summary>
     [Route("/dashboard/ConfigurationPages", "GET")]
     [Route("/dashboard/ConfigurationPages", "GET")]
+    [Restrict(VisibilityTo = EndpointAttributes.None)]
     public class GetDashboardConfigurationPages : IReturn<List<ConfigurationPageInfo>>
     public class GetDashboardConfigurationPages : IReturn<List<ConfigurationPageInfo>>
     {
     {
         /// <summary>
         /// <summary>
@@ -40,6 +40,7 @@ namespace MediaBrowser.WebDashboard.Api
     /// Class GetDashboardConfigurationPage
     /// Class GetDashboardConfigurationPage
     /// </summary>
     /// </summary>
     [Route("/dashboard/ConfigurationPage", "GET")]
     [Route("/dashboard/ConfigurationPage", "GET")]
+    [Restrict(VisibilityTo = EndpointAttributes.None)]
     public class GetDashboardConfigurationPage
     public class GetDashboardConfigurationPage
     {
     {
         /// <summary>
         /// <summary>
@@ -53,6 +54,7 @@ namespace MediaBrowser.WebDashboard.Api
     /// Class GetDashboardResource
     /// Class GetDashboardResource
     /// </summary>
     /// </summary>
     [Route("/dashboard/{ResourceName*}", "GET")]
     [Route("/dashboard/{ResourceName*}", "GET")]
+    [Restrict(VisibilityTo = EndpointAttributes.None)]
     public class GetDashboardResource
     public class GetDashboardResource
     {
     {
         /// <summary>
         /// <summary>
@@ -71,6 +73,7 @@ namespace MediaBrowser.WebDashboard.Api
     /// Class GetDashboardInfo
     /// Class GetDashboardInfo
     /// </summary>
     /// </summary>
     [Route("/dashboard/dashboardInfo", "GET")]
     [Route("/dashboard/dashboardInfo", "GET")]
+    [Restrict(VisibilityTo = EndpointAttributes.None)]
     public class GetDashboardInfo : IReturn<DashboardInfo>
     public class GetDashboardInfo : IReturn<DashboardInfo>
     {
     {
     }
     }
@@ -79,8 +82,26 @@ namespace MediaBrowser.WebDashboard.Api
     /// Class DashboardService
     /// Class DashboardService
     /// </summary>
     /// </summary>
     [Export(typeof(IRestfulService))]
     [Export(typeof(IRestfulService))]
-    public class DashboardService : BaseRestService
+    public class DashboardService : IRestfulService, IHasResultFactory
     {
     {
+        /// <summary>
+        /// Gets or sets the logger.
+        /// </summary>
+        /// <value>The logger.</value>
+        public ILogger Logger { get; set; }
+
+        /// <summary>
+        /// Gets or sets the HTTP result factory.
+        /// </summary>
+        /// <value>The HTTP result factory.</value>
+        public IHttpResultFactory ResultFactory { get; set; }
+
+        /// <summary>
+        /// Gets or sets the request context.
+        /// </summary>
+        /// <value>The request context.</value>
+        public IRequestContext RequestContext { get; set; }
+        
         /// <summary>
         /// <summary>
         /// Gets or sets the task manager.
         /// Gets or sets the task manager.
         /// </summary>
         /// </summary>
@@ -172,7 +193,7 @@ namespace MediaBrowser.WebDashboard.Api
         {
         {
             var page = ServerEntryPoint.Instance.PluginConfigurationPages.First(p => p.Name.Equals(request.Name, StringComparison.OrdinalIgnoreCase));
             var page = ServerEntryPoint.Instance.PluginConfigurationPages.First(p => p.Name.Equals(request.Name, StringComparison.OrdinalIgnoreCase));
 
 
-            return ToStaticResult(page.Plugin.Version.ToString().GetMD5(), page.Plugin.AssemblyDateLastModified, null, MimeTypes.GetMimeType("page.html"), () => ModifyHtml(page.GetHtmlStream()));
+            return ResultFactory.GetStaticResult(RequestContext, page.Plugin.Version.ToString().GetMD5(), page.Plugin.AssemblyDateLastModified, null, MimeTypes.GetMimeType("page.html"), () => ModifyHtml(page.GetHtmlStream()));
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -189,7 +210,7 @@ namespace MediaBrowser.WebDashboard.Api
                 pages = pages.Where(p => p.ConfigurationPageType == request.PageType.Value);
                 pages = pages.Where(p => p.ConfigurationPageType == request.PageType.Value);
             }
             }
 
 
-            return ToOptimizedResult(pages.Select(p => new ConfigurationPageInfo(p)).ToList());
+            return ResultFactory.GetOptimizedResult(RequestContext, pages.Select(p => new ConfigurationPageInfo(p)).ToList());
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -207,8 +228,7 @@ namespace MediaBrowser.WebDashboard.Api
             // But always cache images to simulate production
             // But always cache images to simulate production
             if (!_serverConfigurationManager.Configuration.EnableDashboardResponseCaching && !contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
             if (!_serverConfigurationManager.Configuration.EnableDashboardResponseCaching && !contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
             {
             {
-                Response.ContentType = contentType;
-                return GetResourceStream(path).Result;
+                return ResultFactory.GetResult(GetResourceStream(path).Result, contentType);
             }
             }
 
 
             TimeSpan? cacheDuration = null;
             TimeSpan? cacheDuration = null;
@@ -224,7 +244,7 @@ namespace MediaBrowser.WebDashboard.Api
 
 
             var cacheKey = (assembly.Version + path).GetMD5();
             var cacheKey = (assembly.Version + path).GetMD5();
 
 
-            return ToStaticResult(cacheKey, null, cacheDuration, contentType, () => GetResourceStream(path));
+            return ResultFactory.GetStaticResult(RequestContext, cacheKey, null, cacheDuration, contentType, () => GetResourceStream(path));
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -385,6 +405,7 @@ namespace MediaBrowser.WebDashboard.Api
             var files = new[]
             var files = new[]
                             {
                             {
                                 "http://code.jquery.com/mobile/1.3.0/jquery.mobile-1.3.0.min.css",
                                 "http://code.jquery.com/mobile/1.3.0/jquery.mobile-1.3.0.min.css",
+                                "http://vjs.zencdn.net/c/video-js.css",
                                 "thirdparty/jqm-icon-pack-3.0/font-awesome/jqm-icon-pack-3.0.0-fa.css",
                                 "thirdparty/jqm-icon-pack-3.0/font-awesome/jqm-icon-pack-3.0.0-fa.css",
                                 "css/site.css" + versionString
                                 "css/site.css" + versionString
                             };
                             };
@@ -407,6 +428,7 @@ namespace MediaBrowser.WebDashboard.Api
                             {
                             {
                                 "http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js", 
                                 "http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js", 
                                 "http://code.jquery.com/mobile/1.3.0/jquery.mobile-1.3.0.min.js",
                                 "http://code.jquery.com/mobile/1.3.0/jquery.mobile-1.3.0.min.js",
+                                "http://vjs.zencdn.net/c/video.js",
                                 "scripts/all.js" + versionString
                                 "scripts/all.js" + versionString
             };
             };
 
 

+ 0 - 24
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

@@ -35,10 +35,6 @@
     <RunPostBuildEvent>Always</RunPostBuildEvent>
     <RunPostBuildEvent>Always</RunPostBuildEvent>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup>
   <ItemGroup>
-    <Reference Include="ServiceStack, Version=3.9.42.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\ServiceStack.3.9.42\lib\net35\ServiceStack.dll</HintPath>
-    </Reference>
     <Reference Include="ServiceStack.Common, Version=3.9.42.0, Culture=neutral, processorArchitecture=MSIL">
     <Reference Include="ServiceStack.Common, Version=3.9.42.0, Culture=neutral, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\ServiceStack.Common.3.9.42\lib\net35\ServiceStack.Common.dll</HintPath>
       <HintPath>..\packages\ServiceStack.Common.3.9.42\lib\net35\ServiceStack.Common.dll</HintPath>
@@ -47,22 +43,6 @@
       <SpecificVersion>False</SpecificVersion>
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\ServiceStack.Common.3.9.42\lib\net35\ServiceStack.Interfaces.dll</HintPath>
       <HintPath>..\packages\ServiceStack.Common.3.9.42\lib\net35\ServiceStack.Interfaces.dll</HintPath>
     </Reference>
     </Reference>
-    <Reference Include="ServiceStack.OrmLite, Version=3.9.42.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\ServiceStack.OrmLite.SqlServer.3.9.42\lib\ServiceStack.OrmLite.dll</HintPath>
-    </Reference>
-    <Reference Include="ServiceStack.OrmLite.SqlServer, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\ServiceStack.OrmLite.SqlServer.3.9.42\lib\ServiceStack.OrmLite.SqlServer.dll</HintPath>
-    </Reference>
-    <Reference Include="ServiceStack.Redis, Version=3.9.42.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\ServiceStack.Redis.3.9.42\lib\net35\ServiceStack.Redis.dll</HintPath>
-    </Reference>
-    <Reference Include="ServiceStack.ServiceInterface, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\ServiceStack.3.9.42\lib\net35\ServiceStack.ServiceInterface.dll</HintPath>
-    </Reference>
     <Reference Include="ServiceStack.Text, Version=3.9.42.0, Culture=neutral, processorArchitecture=MSIL">
     <Reference Include="ServiceStack.Text, Version=3.9.42.0, Culture=neutral, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\packages\ServiceStack.Text.3.9.42\lib\net35\ServiceStack.Text.dll</HintPath>
       <HintPath>..\packages\ServiceStack.Text.3.9.42\lib\net35\ServiceStack.Text.dll</HintPath>
@@ -101,10 +81,6 @@
       <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
       <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
       <Name>MediaBrowser.Model</Name>
       <Name>MediaBrowser.Model</Name>
     </ProjectReference>
     </ProjectReference>
-    <ProjectReference Include="..\MediaBrowser.Server.Implementations\MediaBrowser.Server.Implementations.csproj">
-      <Project>{2e781478-814d-4a48-9d80-bff206441a65}</Project>
-      <Name>MediaBrowser.Server.Implementations</Name>
-    </ProjectReference>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <Content Include="dashboard-ui\index.html">
     <Content Include="dashboard-ui\index.html">

+ 0 - 3
MediaBrowser.WebDashboard/packages.config

@@ -1,9 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
 <packages>
   <package id="MediaBrowser.ApiClient.Javascript" version="3.0.50" targetFramework="net45" />
   <package id="MediaBrowser.ApiClient.Javascript" version="3.0.50" targetFramework="net45" />
-  <package id="ServiceStack" version="3.9.42" targetFramework="net45" />
   <package id="ServiceStack.Common" version="3.9.42" targetFramework="net45" />
   <package id="ServiceStack.Common" version="3.9.42" targetFramework="net45" />
-  <package id="ServiceStack.OrmLite.SqlServer" version="3.9.42" targetFramework="net45" />
-  <package id="ServiceStack.Redis" version="3.9.42" targetFramework="net45" />
   <package id="ServiceStack.Text" version="3.9.42" targetFramework="net45" />
   <package id="ServiceStack.Text" version="3.9.42" targetFramework="net45" />
 </packages>
 </packages>