Explorar o código

update file responses

Luke Pulverenti %!s(int64=8) %!d(string=hai) anos
pai
achega
198cb1bc9c

+ 19 - 2
Emby.Common.Implementations/IO/ManagedFileSystem.cs

@@ -392,10 +392,27 @@ namespace Emby.Common.Implementations.IO
 
             if (_supportsAsyncFileStreams && isAsync)
             {
-                return new FileStream(path, GetFileMode(mode), GetFileAccess(access), GetFileShare(share), 262144, true);
+                return GetFileStream(path, mode, access, share, FileOpenOptions.Asynchronous);
             }
 
-            return new FileStream(path, GetFileMode(mode), GetFileAccess(access), GetFileShare(share), 262144);
+            return GetFileStream(path, mode, access, share, FileOpenOptions.None);
+        }
+
+        public Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, FileOpenOptions fileOpenOptions)
+        {
+            if (_sharpCifsFileSystem.IsEnabledForPath(path))
+            {
+                return _sharpCifsFileSystem.GetFileStream(path, mode, access, share);
+            }
+
+            var defaultBufferSize = 4096;
+            return new FileStream(path, GetFileMode(mode), GetFileAccess(access), GetFileShare(share), defaultBufferSize, GetFileOptions(fileOpenOptions));
+        }
+
+        private FileOptions GetFileOptions(FileOpenOptions mode)
+        {
+            var val = (int)mode;
+            return (FileOptions)val;
         }
 
         private FileMode GetFileMode(FileOpenMode mode)

+ 65 - 0
Emby.Drawing.Skia/Emby.Drawing.Skia.csproj

@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <MinimumVisualStudioVersion>11.0</MinimumVisualStudioVersion>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{2312DA6D-FF86-4597-9777-BCEEC32D96DD}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>Emby.Drawing.Skia</RootNamespace>
+    <AssemblyName>Emby.Drawing.Skia</AssemblyName>
+    <DefaultLanguage>en-US</DefaultLanguage>
+    <FileAlignment>512</FileAlignment>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <TargetFrameworkProfile>Profile7</TargetFrameworkProfile>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <!-- A reference to the entire .NET Framework is automatically included -->
+    <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
+      <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
+      <Name>MediaBrowser.Common</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
+      <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
+      <Name>MediaBrowser.Controller</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
+      <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
+      <Name>MediaBrowser.Model</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="..\SharedVersion.cs">
+      <Link>Properties\SharedVersion.cs</Link>
+    </Compile>
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>

+ 25 - 0
Emby.Drawing.Skia/Properties/AssemblyInfo.cs

@@ -0,0 +1,25 @@
+using System.Resources;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Emby.Drawing.Skia")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Emby.Drawing.Skia")]
+[assembly: AssemblyCopyright("Copyright ©  2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: NeutralResourcesLanguage("en")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//

+ 2 - 1
Emby.Server.Core/ApplicationHost.cs

@@ -1132,7 +1132,8 @@ namespace Emby.Server.Core
                 // Custom cert
                 return new CertificateInfo
                 {
-                    Path = ServerConfigurationManager.Configuration.CertificatePath
+                    Path = ServerConfigurationManager.Configuration.CertificatePath,
+                    Password = ServerConfigurationManager.Configuration.CertificatePassword
                 };
             }
 

+ 6 - 3
Emby.Server.Implementations/HttpServer/HttpListenerHost.cs

@@ -228,7 +228,8 @@ namespace Emby.Server.Implementations.HttpServer
                 _streamFactory,
                 _enableDualModeSockets,
                 GetRequest,
-                _fileSystem);
+                _fileSystem,
+                _environment);
         }
 
         private IHttpRequest GetRequest(HttpListenerContext httpContext)
@@ -452,6 +453,7 @@ namespace Emby.Server.Implementations.HttpServer
             var date = DateTime.Now;
             var httpRes = httpReq.Response;
             bool enableLog = false;
+            bool logHeaders = false;
             string urlToLog = null;
             string remoteIp = null;
 
@@ -490,13 +492,14 @@ namespace Emby.Server.Implementations.HttpServer
                 var urlString = url.OriginalString;
                 enableLog = EnableLogging(urlString, localPath);
                 urlToLog = urlString;
+                 logHeaders = enableLog && urlToLog.IndexOf("/videos/", StringComparison.OrdinalIgnoreCase) != -1;
 
                 if (enableLog)
                 {
                     urlToLog = GetUrlToLog(urlString);
                     remoteIp = httpReq.RemoteIp;
 
-                    LoggerUtils.LogRequest(_logger, urlToLog, httpReq.HttpMethod, httpReq.UserAgent);
+                    LoggerUtils.LogRequest(_logger, urlToLog, httpReq.HttpMethod, httpReq.UserAgent, logHeaders ? httpReq.Headers : null);
                 }
 
                 if (string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase) ||
@@ -611,7 +614,7 @@ namespace Emby.Server.Implementations.HttpServer
 
                     var duration = DateTime.Now - date;
 
-                    LoggerUtils.LogResponse(_logger, statusCode, urlToLog, remoteIp, duration);
+                    LoggerUtils.LogResponse(_logger, statusCode, urlToLog, remoteIp, duration, logHeaders ? httpRes.Headers : null);
                 }
             }
         }

+ 18 - 28
Emby.Server.Implementations/HttpServer/HttpResultFactory.cs

@@ -353,31 +353,28 @@ namespace Emby.Server.Implementations.HttpServer
         /// <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(IRequest requestContext, IDictionary<string, string> responseHeaders, Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType)
         {
             responseHeaders["ETag"] = string.Format("\"{0}\"", cacheKeyString);
 
-            if (IsNotModified(requestContext, cacheKey, lastDateModified, cacheDuration))
+            var noCache = (requestContext.Headers.Get("Cache-Control") ?? string.Empty).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1;
+
+            if (!noCache)
             {
-                AddAgeHeader(responseHeaders, lastDateModified);
-                AddExpiresHeader(responseHeaders, cacheKeyString, cacheDuration);
+                if (IsNotModified(requestContext, cacheKey, lastDateModified, cacheDuration))
+                {
+                    AddAgeHeader(responseHeaders, lastDateModified);
+                    AddExpiresHeader(responseHeaders, cacheKeyString, cacheDuration, noCache);
 
-                var result = new HttpResult(new byte[] { }, contentType ?? "text/html", HttpStatusCode.NotModified);
+                    var result = new HttpResult(new byte[] { }, contentType ?? "text/html", HttpStatusCode.NotModified);
 
-                AddResponseHeaders(result, responseHeaders);
+                    AddResponseHeaders(result, responseHeaders);
 
-                return result;
+                    return result;
+                }
             }
 
-            AddCachingHeaders(responseHeaders, cacheKeyString, lastDateModified, cacheDuration);
+            AddCachingHeaders(responseHeaders, cacheKeyString, lastDateModified, cacheDuration, noCache);
 
             return null;
         }
@@ -673,11 +670,7 @@ namespace Emby.Server.Implementations.HttpServer
         /// <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)
+        private void AddCachingHeaders(IDictionary<string, string> responseHeaders, string cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, bool noCache)
         {
             // Don't specify both last modified and Etag, unless caching unconditionally. They are redundant
             // https://developers.google.com/speed/docs/best-practices/caching#LeverageBrowserCaching
@@ -687,11 +680,11 @@ namespace Emby.Server.Implementations.HttpServer
                 responseHeaders["Last-Modified"] = lastDateModified.Value.ToString("r");
             }
 
-            if (cacheDuration.HasValue)
+            if (!noCache && cacheDuration.HasValue)
             {
                 responseHeaders["Cache-Control"] = "public, max-age=" + Convert.ToInt32(cacheDuration.Value.TotalSeconds);
             }
-            else if (!string.IsNullOrEmpty(cacheKey))
+            else if (!noCache && !string.IsNullOrEmpty(cacheKey))
             {
                 responseHeaders["Cache-Control"] = "public";
             }
@@ -701,18 +694,15 @@ namespace Emby.Server.Implementations.HttpServer
                 responseHeaders["pragma"] = "no-cache, no-store, must-revalidate";
             }
 
-            AddExpiresHeader(responseHeaders, cacheKey, cacheDuration);
+            AddExpiresHeader(responseHeaders, cacheKey, cacheDuration, noCache);
         }
 
         /// <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)
+        private void AddExpiresHeader(IDictionary<string, string> responseHeaders, string cacheKey, TimeSpan? cacheDuration, bool noCache)
         {
-            if (cacheDuration.HasValue)
+            if (!noCache && cacheDuration.HasValue)
             {
                 responseHeaders["Expires"] = DateTime.UtcNow.Add(cacheDuration.Value).ToString("r");
             }

+ 16 - 4
Emby.Server.Implementations/HttpServer/LoggerUtils.cs

@@ -1,6 +1,8 @@
 using MediaBrowser.Model.Logging;
 using System;
 using System.Globalization;
+using System.Linq;
+using MediaBrowser.Model.Services;
 using SocketHttpListener.Net;
 
 namespace Emby.Server.Implementations.HttpServer
@@ -19,9 +21,18 @@ namespace Emby.Server.Implementations.HttpServer
             logger.Info("{0} {1}. UserAgent: {2}", request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, url, request.UserAgent ?? string.Empty);
         }
 
-        public static void LogRequest(ILogger logger, string url, string method, string userAgent)
+        public static void LogRequest(ILogger logger, string url, string method, string userAgent, QueryParamCollection headers)
         {
-            logger.Info("{0} {1}. UserAgent: {2}", "HTTP " + method, url, userAgent ?? string.Empty);
+            if (headers == null)
+            {
+                logger.Info("{0} {1}. UserAgent: {2}", "HTTP " + method, url, userAgent ?? string.Empty);
+            }
+            else
+            {
+                var headerText = string.Join(", ", headers.Select(i => i.Name + "=" + i.Value).ToArray());
+
+                logger.Info("HTTP {0} {1}. {2}", method, url, headerText);
+            }
         }
 
         /// <summary>
@@ -32,12 +43,13 @@ namespace Emby.Server.Implementations.HttpServer
         /// <param name="url">The URL.</param>
         /// <param name="endPoint">The end point.</param>
         /// <param name="duration">The duration.</param>
-        public static void LogResponse(ILogger logger, int statusCode, string url, string endPoint, TimeSpan duration)
+        public static void LogResponse(ILogger logger, int statusCode, string url, string endPoint, TimeSpan duration, QueryParamCollection headers)
         {
             var durationMs = duration.TotalMilliseconds;
             var logSuffix = durationMs >= 1000 && durationMs < 60000 ? "ms (slow)" : "ms";
 
-            logger.Info("HTTP Response {0} to {1}. Time: {2}{3}. {4}", statusCode, endPoint, Convert.ToInt32(durationMs).ToString(CultureInfo.InvariantCulture), logSuffix, url);
+            var headerText = headers == null ? string.Empty : "Headers: " + string.Join(", ", headers.Where(i => i.Name.IndexOf("Access-", StringComparison.OrdinalIgnoreCase) == -1).Select(i => i.Name + "=" + i.Value).ToArray());
+            logger.Info("HTTP Response {0} to {1}. Time: {2}{3}. {4} {5}", statusCode, endPoint, Convert.ToInt32(durationMs).ToString(CultureInfo.InvariantCulture), logSuffix, url, headerText);
         }
     }
 }

+ 5 - 2
Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs

@@ -10,6 +10,7 @@ using MediaBrowser.Model.Cryptography;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Services;
+using MediaBrowser.Model.System;
 using MediaBrowser.Model.Text;
 using SocketHttpListener.Primitives;
 
@@ -30,8 +31,9 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
         private readonly IFileSystem _fileSystem;
         private readonly Func<HttpListenerContext, IHttpRequest> _httpRequestFactory;
         private readonly bool _enableDualMode;
+        private readonly IEnvironmentInfo _environment;
 
-        public WebSocketSharpListener(ILogger logger, ICertificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, bool enableDualMode, Func<HttpListenerContext, IHttpRequest> httpRequestFactory, IFileSystem fileSystem)
+        public WebSocketSharpListener(ILogger logger, ICertificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, bool enableDualMode, Func<HttpListenerContext, IHttpRequest> httpRequestFactory, IFileSystem fileSystem, IEnvironmentInfo environment)
         {
             _logger = logger;
             _certificate = certificate;
@@ -44,6 +46,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
             _enableDualMode = enableDualMode;
             _httpRequestFactory = httpRequestFactory;
             _fileSystem = fileSystem;
+            _environment = environment;
         }
 
         public Action<Exception, IRequest, bool> ErrorHandler { get; set; }
@@ -56,7 +59,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
         public void Start(IEnumerable<string> urlPrefixes)
         {
             if (_listener == null)
-                _listener = new HttpListener(_logger, _cryptoProvider, _streamFactory, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider, _fileSystem);
+                _listener = new HttpListener(_logger, _cryptoProvider, _streamFactory, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider, _fileSystem, _environment);
 
             _listener.EnableDualMode = _enableDualMode;
 

+ 9 - 0
Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs

@@ -7,6 +7,7 @@ using System.Threading;
 using System.Threading.Tasks;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Services;
 using SocketHttpListener.Net;
 using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse;
 using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse;
@@ -66,6 +67,14 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
             _response.AddHeader(name, value);
         }
 
+        public QueryParamCollection Headers
+        {
+            get
+            {
+                return _response.Headers;
+            }
+        }
+
         public string GetHeader(string name)
         {
             return _response.Headers[name];

+ 1 - 1
Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs

@@ -258,7 +258,7 @@ namespace Emby.Server.Implementations.Images
                 {
                     return await CreateSquareCollage(item, itemsWithImages, outputPath).ConfigureAwait(false);
                 }
-                if (item is Playlist || item is MusicGenre || item is Genre || item is GameGenre)
+                if (item is Playlist || item is MusicGenre || item is Genre || item is GameGenre || item is PhotoAlbum)
                 {
                     return await CreateSquareCollage(item, itemsWithImages, outputPath).ConfigureAwait(false);
                 }

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

@@ -671,12 +671,15 @@ namespace MediaBrowser.Api.Playback
                 request.AudioCodec = EncodingHelper.InferAudioCodec(url);
             }
 
+            var enableDlnaHeaders = !string.IsNullOrWhiteSpace(request.Params) /*||
+                                    string.Equals(Request.Headers.Get("GetContentFeatures.DLNA.ORG"), "1", StringComparison.OrdinalIgnoreCase)*/;
+
             var state = new StreamState(MediaSourceManager, Logger, TranscodingJobType)
             {
                 Request = request,
                 RequestedUrl = url,
                 UserAgent = Request.UserAgent,
-                EnableDlnaHeaders = !string.IsNullOrWhiteSpace(request.Params)
+                EnableDlnaHeaders = enableDlnaHeaders
             };
 
             var auth = AuthorizationContext.GetAuthorizationInfo(Request);

+ 1 - 0
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -55,6 +55,7 @@ namespace MediaBrowser.Model.Configuration
         /// </summary>
         /// <value>The value pointing to the file system where the ssl certiifcate is located..</value>
         public string CertificatePath { get; set; }
+        public string CertificatePassword { get; set; }
 
         /// <summary>
         /// Gets or sets a value indicating whether this instance is port authorized.

+ 44 - 0
MediaBrowser.Model/IO/IFileSystem.cs

@@ -108,6 +108,8 @@ namespace MediaBrowser.Model.IO
         /// <returns>FileStream.</returns>
         Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, bool isAsync = false);
 
+        Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, FileOpenOptions fileOpenOptions);
+
         /// <summary>
         /// Opens the read.
         /// </summary>
@@ -402,4 +404,46 @@ namespace MediaBrowser.Model.IO
         ReadWrite = 3
     }
 
+    //
+    // Summary:
+    //     Represents advanced options for creating a System.IO.FileStream object.
+    [Flags]
+    public enum FileOpenOptions
+    {
+        //
+        // Summary:
+        //     Indicates that the system should write through any intermediate cache and go
+        //     directly to disk.
+        WriteThrough = int.MinValue,
+        //
+        // Summary:
+        //     Indicates that no additional options should be used when creating a System.IO.FileStream
+        //     object.
+        None = 0,
+        //
+        // Summary:
+        //     Indicates that a file is encrypted and can be decrypted only by using the same
+        //     user account used for encryption.
+        Encrypted = 16384,
+        //
+        // Summary:
+        //     Indicates that a file is automatically deleted when it is no longer in use.
+        DeleteOnClose = 67108864,
+        //
+        // Summary:
+        //     Indicates that the file is to be accessed sequentially from beginning to end.
+        //     The system can use this as a hint to optimize file caching. If an application
+        //     moves the file pointer for random access, optimum caching may not occur; however,
+        //     correct operation is still guaranteed.
+        SequentialScan = 134217728,
+        //
+        // Summary:
+        //     Indicates that the file is accessed randomly. The system can use this as a hint
+        //     to optimize file caching.
+        RandomAccess = 268435456,
+        //
+        // Summary:
+        //     Indicates that a file can be used for asynchronous reading and writing.
+        Asynchronous = 1073741824
+    }
 }

+ 2 - 0
MediaBrowser.Model/Services/IRequest.cs

@@ -155,6 +155,8 @@ namespace MediaBrowser.Model.Services
         //Add Metadata to Response
         Dictionary<string, object> Items { get; }
 
+        QueryParamCollection Headers { get; }
+
         Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken);
     }
 }

+ 15 - 0
MediaBrowser.Providers/Manager/ProviderUtils.cs

@@ -200,6 +200,7 @@ namespace MediaBrowser.Providers.Manager
             MergeCriticRating(source, target, lockedFields, replaceData);
             MergeAwards(source, target, lockedFields, replaceData);
             MergeTrailers(source, target, lockedFields, replaceData);
+            MergeVideoInfo(source, target, lockedFields, replaceData);
 
             if (mergeMetadataSettings)
             {
@@ -307,5 +308,19 @@ namespace MediaBrowser.Providers.Manager
                 }
             }
         }
+
+        private static void MergeVideoInfo(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
+        {
+            var sourceCast = source as Video;
+            var targetCast = target as Video;
+
+            if (sourceCast != null && targetCast != null)
+            {
+                if (replaceData || targetCast.Video3DFormat == null)
+                {
+                    targetCast.Video3DFormat = sourceCast.Video3DFormat;
+                }
+            }
+        }
     }
 }

+ 42 - 0
MediaBrowser.sln

@@ -80,6 +80,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing.Net", "Emby.Dr
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SocketHttpListener.Portable", "SocketHttpListener.Portable\SocketHttpListener.Portable.csproj", "{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing.Skia", "Emby.Drawing.Skia\Emby.Drawing.Skia.csproj", "{2312DA6D-FF86-4597-9777-BCEEC32D96DD}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -1099,6 +1101,46 @@ Global
 		{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Signed|x64.Build.0 = Release|Any CPU
 		{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Signed|x86.ActiveCfg = Release|Any CPU
 		{4F26D5D8-A7B0-42B3-BA42-7CB7D245934E}.Signed|x86.Build.0 = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Debug|Win32.ActiveCfg = Debug|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Debug|Win32.Build.0 = Debug|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Debug|x64.Build.0 = Debug|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Debug|x86.Build.0 = Debug|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release Mono|Any CPU.Build.0 = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release Mono|Mixed Platforms.ActiveCfg = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release Mono|Mixed Platforms.Build.0 = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release Mono|Win32.ActiveCfg = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release Mono|Win32.Build.0 = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release Mono|x64.ActiveCfg = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release Mono|x64.Build.0 = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release Mono|x86.ActiveCfg = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release Mono|x86.Build.0 = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release|Any CPU.Build.0 = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release|Win32.ActiveCfg = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release|Win32.Build.0 = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release|x64.ActiveCfg = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release|x64.Build.0 = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release|x86.ActiveCfg = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Release|x86.Build.0 = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Signed|Any CPU.ActiveCfg = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Signed|Any CPU.Build.0 = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Signed|Mixed Platforms.ActiveCfg = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Signed|Mixed Platforms.Build.0 = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Signed|Win32.ActiveCfg = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Signed|Win32.Build.0 = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Signed|x64.ActiveCfg = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Signed|x64.Build.0 = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Signed|x86.ActiveCfg = Release|Any CPU
+		{2312DA6D-FF86-4597-9777-BCEEC32D96DD}.Signed|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 5 - 2
SocketHttpListener.Portable/Net/EndPointListener.cs

@@ -8,6 +8,7 @@ using MediaBrowser.Model.Cryptography;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Net;
+using MediaBrowser.Model.System;
 using MediaBrowser.Model.Text;
 using SocketHttpListener.Primitives;
 
@@ -33,8 +34,9 @@ namespace SocketHttpListener.Net
         private readonly ITextEncoding _textEncoding;
         private readonly IMemoryStreamFactory _memoryStreamFactory;
         private readonly IFileSystem _fileSystem;
+        private readonly IEnvironmentInfo _environment;
 
-        public EndPointListener(HttpListener listener, IpAddressInfo addr, int port, bool secure, ICertificate cert, ILogger logger, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem)
+        public EndPointListener(HttpListener listener, IpAddressInfo addr, int port, bool secure, ICertificate cert, ILogger logger, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment)
         {
             this.listener = listener;
             _logger = logger;
@@ -44,6 +46,7 @@ namespace SocketHttpListener.Net
             _memoryStreamFactory = memoryStreamFactory;
             _textEncoding = textEncoding;
             _fileSystem = fileSystem;
+            _environment = environment;
 
             this.secure = secure;
             this.cert = cert;
@@ -109,7 +112,7 @@ namespace SocketHttpListener.Net
                     return;
                 }
 
-                HttpConnection conn = await HttpConnection.Create(_logger, accepted, listener, listener.secure, listener.cert, _cryptoProvider, _streamFactory, _memoryStreamFactory, _textEncoding, _fileSystem).ConfigureAwait(false);
+                HttpConnection conn = await HttpConnection.Create(_logger, accepted, listener, listener.secure, listener.cert, _cryptoProvider, _streamFactory, _memoryStreamFactory, _textEncoding, _fileSystem, _environment).ConfigureAwait(false);
 
                 //_logger.Debug("Adding unregistered connection to {0}. Id: {1}", accepted.RemoteEndPoint, connectionId);
                 lock (listener.unregistered)

+ 1 - 1
SocketHttpListener.Portable/Net/EndPointManager.cs

@@ -106,7 +106,7 @@ namespace SocketHttpListener.Net
             }
             else
             {
-                epl = new EndPointListener(listener, addr, port, secure, listener.Certificate, logger, listener.CryptoProvider, listener.StreamFactory, listener.SocketFactory, listener.MemoryStreamFactory, listener.TextEncoding, listener.FileSystem);
+                epl = new EndPointListener(listener, addr, port, secure, listener.Certificate, logger, listener.CryptoProvider, listener.StreamFactory, listener.SocketFactory, listener.MemoryStreamFactory, listener.TextEncoding, listener.FileSystem, listener.EnvironmentInfo);
                 p[port] = epl;
             }
 

+ 7 - 4
SocketHttpListener.Portable/Net/HttpConnection.cs

@@ -6,6 +6,7 @@ using MediaBrowser.Model.Cryptography;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Net;
+using MediaBrowser.Model.System;
 using MediaBrowser.Model.Text;
 using SocketHttpListener.Primitives;
 
@@ -41,8 +42,9 @@ namespace SocketHttpListener.Net
         private readonly ITextEncoding _textEncoding;
         private readonly IStreamFactory _streamFactory;
         private readonly IFileSystem _fileSystem;
+        private readonly IEnvironmentInfo _environment;
 
-        private HttpConnection(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem)
+        private HttpConnection(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment)
         {
             _logger = logger;
             this.sock = sock;
@@ -53,6 +55,7 @@ namespace SocketHttpListener.Net
             _memoryStreamFactory = memoryStreamFactory;
             _textEncoding = textEncoding;
             _fileSystem = fileSystem;
+            _environment = environment;
             _streamFactory = streamFactory;
         }
 
@@ -84,9 +87,9 @@ namespace SocketHttpListener.Net
             Init();
         }
 
-        public static async Task<HttpConnection> Create(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem)
+        public static async Task<HttpConnection> Create(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IEnvironmentInfo environment)
         {
-            var connection = new HttpConnection(logger, sock, epl, secure, cert, cryptoProvider, streamFactory, memoryStreamFactory, textEncoding, fileSystem);
+            var connection = new HttpConnection(logger, sock, epl, secure, cert, cryptoProvider, streamFactory, memoryStreamFactory, textEncoding, fileSystem, environment);
 
             await connection.InitStream().ConfigureAwait(false);
 
@@ -217,7 +220,7 @@ namespace SocketHttpListener.Net
                 {
                     var supportsDirectSocketAccess = !context.Response.SendChunked && !isExpect100Continue && !secure;
 
-                    o_stream = new ResponseStream(stream, context.Response, _memoryStreamFactory, _textEncoding, _fileSystem, sock, supportsDirectSocketAccess, _logger);
+                    o_stream = new ResponseStream(stream, context.Response, _memoryStreamFactory, _textEncoding, _fileSystem, sock, supportsDirectSocketAccess, _logger, _environment);
                 }
                 else
                 {

+ 8 - 5
SocketHttpListener.Portable/Net/HttpListener.cs

@@ -8,6 +8,7 @@ using MediaBrowser.Model.Cryptography;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Net;
+using MediaBrowser.Model.System;
 using MediaBrowser.Model.Text;
 using SocketHttpListener.Primitives;
 
@@ -22,6 +23,7 @@ namespace SocketHttpListener.Net
         internal ITextEncoding TextEncoding { get; private set; }
         internal IMemoryStreamFactory MemoryStreamFactory { get; private set; }
         internal INetworkManager NetworkManager { get; private set; }
+        internal IEnvironmentInfo EnvironmentInfo { get; private set; }
 
         public bool EnableDualMode { get; set; }
 
@@ -40,7 +42,7 @@ namespace SocketHttpListener.Net
 
         public Action<HttpListenerContext> OnContext { get; set; }
 
-        public HttpListener(ILogger logger, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem)
+        public HttpListener(ILogger logger, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem, IEnvironmentInfo environmentInfo)
         {
             _logger = logger;
             CryptoProvider = cryptoProvider;
@@ -50,19 +52,20 @@ namespace SocketHttpListener.Net
             TextEncoding = textEncoding;
             MemoryStreamFactory = memoryStreamFactory;
             FileSystem = fileSystem;
+            EnvironmentInfo = environmentInfo;
             prefixes = new HttpListenerPrefixCollection(logger, this);
             registry = new Dictionary<HttpListenerContext, HttpListenerContext>();
             connections = new Dictionary<HttpConnection, HttpConnection>();
             auth_schemes = AuthenticationSchemes.Anonymous;
         }
 
-        public HttpListener(ICertificate certificate, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem)
-            :this(new NullLogger(), certificate, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory, fileSystem)
+        public HttpListener(ICertificate certificate, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem, IEnvironmentInfo environmentInfo)
+            :this(new NullLogger(), certificate, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory, fileSystem, environmentInfo)
         {
         }
 
-        public HttpListener(ILogger logger, ICertificate certificate, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem)
-            : this(logger, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory, fileSystem)
+        public HttpListener(ILogger logger, ICertificate certificate, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem, IEnvironmentInfo environmentInfo)
+            : this(logger, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory, fileSystem, environmentInfo)
         {
             _certificate = certificate;
         }

+ 63 - 5
SocketHttpListener.Portable/Net/ResponseStream.cs

@@ -7,6 +7,7 @@ using System.Threading.Tasks;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Net;
+using MediaBrowser.Model.System;
 using MediaBrowser.Model.Text;
 using SocketHttpListener.Primitives;
 
@@ -28,8 +29,9 @@ namespace SocketHttpListener.Net
         private readonly IAcceptSocket _socket;
         private readonly bool _supportsDirectSocketAccess;
         private readonly ILogger _logger;
+        private readonly IEnvironmentInfo _environment;
 
-        internal ResponseStream(Stream stream, HttpListenerResponse response, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IAcceptSocket socket, bool supportsDirectSocketAccess, ILogger logger)
+        internal ResponseStream(Stream stream, HttpListenerResponse response, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IAcceptSocket socket, bool supportsDirectSocketAccess, ILogger logger, IEnvironmentInfo environment)
         {
             this.response = response;
             _memoryStreamFactory = memoryStreamFactory;
@@ -38,6 +40,7 @@ namespace SocketHttpListener.Net
             _socket = socket;
             _supportsDirectSocketAccess = supportsDirectSocketAccess;
             _logger = logger;
+            _environment = environment;
             this.stream = stream;
         }
 
@@ -344,7 +347,20 @@ namespace SocketHttpListener.Net
 
         private async Task TransmitFileManaged(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
         {
-            using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, true))
+            var allowAsync = _environment.OperatingSystem != OperatingSystem.Windows;
+
+            var fileOpenOptions = offset > 0
+                ? FileOpenOptions.RandomAccess
+                : FileOpenOptions.SequentialScan;
+
+            if (allowAsync)
+            {
+                fileOpenOptions |= FileOpenOptions.Asynchronous;
+            }
+
+            // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
+
+            using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions))
             {
                 if (offset > 0)
                 {
@@ -355,11 +371,53 @@ namespace SocketHttpListener.Net
 
                 if (count > 0)
                 {
-                    await CopyToInternalAsync(fs, targetStream, count, cancellationToken).ConfigureAwait(false);
+                    if (allowAsync)
+                    {
+                        await CopyToInternalAsync(fs, targetStream, count, cancellationToken).ConfigureAwait(false);
+                    }
+                    else
+                    {
+                        await CopyToInternalAsyncWithSyncRead(fs, targetStream, count, cancellationToken).ConfigureAwait(false);
+                    }
                 }
                 else
                 {
-                    await fs.CopyToAsync(targetStream, 81920, cancellationToken).ConfigureAwait(false);
+                    if (allowAsync)
+                    {
+                        await fs.CopyToAsync(targetStream, 81920, cancellationToken).ConfigureAwait(false);
+                    }
+                    else
+                    {
+                        fs.CopyTo(targetStream, 81920);
+                    }
+                }
+            }
+        }
+
+        private static async Task CopyToInternalAsyncWithSyncRead(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
+        {
+            var array = new byte[81920];
+            int bytesRead;
+
+            while ((bytesRead = source.Read(array, 0, array.Length)) != 0)
+            {
+                if (bytesRead == 0)
+                {
+                    break;
+                }
+
+                var bytesToWrite = Math.Min(bytesRead, copyLength);
+
+                if (bytesToWrite > 0)
+                {
+                    await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
+                }
+
+                copyLength -= bytesToWrite;
+
+                if (copyLength <= 0)
+                {
+                    break;
                 }
             }
         }
@@ -368,7 +426,7 @@ namespace SocketHttpListener.Net
         {
             var array = new byte[81920];
             int bytesRead;
-            
+
             while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0)
             {
                 if (bytesRead == 0)