diff options
| author | Luke <luke.pulverenti@gmail.com> | 2017-03-12 15:28:28 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-03-12 15:28:28 -0400 |
| commit | 7ae1de52b45267e399b6cbbaa423665bb006bf79 (patch) | |
| tree | 227cb95db1ddc171c078ce9bc6f8f717cc2e9e20 | |
| parent | 3962361ad9940e0c8b2e6d4aea4fc6b1cdd88553 (diff) | |
| parent | 5e821947491c07eb65db4653db674af55bc4e90b (diff) | |
Merge pull request #2523 from MediaBrowser/dev
Dev
40 files changed, 532 insertions, 145 deletions
diff --git a/MediaBrowser.ServerApplication/Native/LnkShortcutHandler.cs b/Emby.Common.Implementations/IO/LnkShortcutHandler.cs index b4a87b9b4..5d5f46057 100644 --- a/MediaBrowser.ServerApplication/Native/LnkShortcutHandler.cs +++ b/Emby.Common.Implementations/IO/LnkShortcutHandler.cs @@ -6,7 +6,7 @@ using System.Security; using System.Text; using MediaBrowser.Model.IO; -namespace MediaBrowser.ServerApplication.Native +namespace Emby.Common.Implementations.IO { public class LnkShortcutHandler :IShortcutHandler { @@ -35,7 +35,6 @@ namespace MediaBrowser.ServerApplication.Native /// <summary> /// Class NativeMethods /// </summary> - [SuppressUnmanagedCodeSecurity] public static class NativeMethods { /// <summary> diff --git a/Emby.Common.Implementations/IO/ManagedFileSystem.cs b/Emby.Common.Implementations/IO/ManagedFileSystem.cs index 3fe20f659..2c9388a41 100644 --- a/Emby.Common.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Common.Implementations/IO/ManagedFileSystem.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.System; namespace Emby.Common.Implementations.IO { @@ -18,17 +19,17 @@ namespace Emby.Common.Implementations.IO private readonly bool _supportsAsyncFileStreams; private char[] _invalidFileNameChars; private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>(); - private bool EnableFileSystemRequestConcat = true; + private bool EnableFileSystemRequestConcat; private string _tempPath; - public ManagedFileSystem(ILogger logger, bool supportsAsyncFileStreams, bool enableManagedInvalidFileNameChars, bool enableFileSystemRequestConcat, string tempPath) + public ManagedFileSystem(ILogger logger, IEnvironmentInfo environmentInfo, string tempPath) { Logger = logger; - _supportsAsyncFileStreams = supportsAsyncFileStreams; + _supportsAsyncFileStreams = true; _tempPath = tempPath; - EnableFileSystemRequestConcat = enableFileSystemRequestConcat; - SetInvalidFileNameChars(enableManagedInvalidFileNameChars); + EnableFileSystemRequestConcat = false; + SetInvalidFileNameChars(environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows); } public void AddShortcutHandler(IShortcutHandler handler) diff --git a/Emby.Common.Implementations/Net/NetAcceptSocket.cs b/Emby.Common.Implementations/Net/NetAcceptSocket.cs index 731ad1b74..e21ffe553 100644 --- a/Emby.Common.Implementations/Net/NetAcceptSocket.cs +++ b/Emby.Common.Implementations/Net/NetAcceptSocket.cs @@ -2,6 +2,7 @@ using System.Net; using System.Net.Sockets; using System.Threading; +using System.Threading.Tasks; using Emby.Common.Implementations.Networking; using MediaBrowser.Model.Net; using MediaBrowser.Model.Logging; @@ -59,7 +60,7 @@ namespace Emby.Common.Implementations.Net #if NET46 Socket.Close(); #else - Socket.Dispose(); + Socket.Dispose(); #endif } @@ -96,6 +97,46 @@ namespace Emby.Common.Implementations.Net _acceptor.StartAccept(); } +#if NET46 + public Task SendFile(string path, byte[] preBuffer, byte[] postBuffer, CancellationToken cancellationToken) + { + var options = TransmitFileOptions.Disconnect | TransmitFileOptions.ReuseSocket | TransmitFileOptions.UseKernelApc; + + var completionSource = new TaskCompletionSource<bool>(); + + var result = Socket.BeginSendFile(path, preBuffer, postBuffer, options, new AsyncCallback(FileSendCallback), new Tuple<Socket, string, TaskCompletionSource<bool>>(Socket, path, completionSource)); + + return completionSource.Task; + } + + private void FileSendCallback(IAsyncResult ar) + { + // Retrieve the socket from the state object. + Tuple<Socket, string, TaskCompletionSource<bool>> data = (Tuple<Socket, string, TaskCompletionSource<bool>>)ar.AsyncState; + + var client = data.Item1; + var path = data.Item2; + var taskCompletion = data.Item3; + + // Complete sending the data to the remote device. + try { + client.EndSendFile(ar); + taskCompletion.TrySetResult(true); +} + catch(SocketException ex){ + _logger.Info("Socket.SendFile failed for {0}. error code {1}", path, ex.SocketErrorCode); + taskCompletion.TrySetException(ex); +}catch(Exception ex){ + taskCompletion.TrySetException(ex); +} + } +#else + public Task SendFile(string path, byte[] preBuffer, byte[] postBuffer, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } +#endif + public void Dispose() { Socket.Dispose(); diff --git a/Emby.Common.Implementations/Net/SocketFactory.cs b/Emby.Common.Implementations/Net/SocketFactory.cs index 523f4da85..021613e57 100644 --- a/Emby.Common.Implementations/Net/SocketFactory.cs +++ b/Emby.Common.Implementations/Net/SocketFactory.cs @@ -59,6 +59,7 @@ namespace Emby.Common.Implementations.Net if (remotePort < 0) throw new ArgumentException("remotePort cannot be less than zero.", "remotePort"); var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); + try { retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs index 7dbc7760b..971378ea7 100644 --- a/Emby.Server.Core/ApplicationHost.cs +++ b/Emby.Server.Core/ApplicationHost.cs @@ -61,7 +61,7 @@ using System.Threading; using System.Threading.Tasks; using Emby.Common.Implementations; using Emby.Common.Implementations.Archiving; -using Emby.Common.Implementations.Networking; +using Emby.Common.Implementations.IO; using Emby.Common.Implementations.Reflection; using Emby.Common.Implementations.Serialization; using Emby.Common.Implementations.TextEncoding; @@ -93,7 +93,7 @@ using Emby.Server.Implementations.Social; using Emby.Server.Implementations.Channels; using Emby.Server.Implementations.Collections; using Emby.Server.Implementations.Dto; -using Emby.Server.Implementations.EntryPoints; +using Emby.Server.Implementations.IO; using Emby.Server.Implementations.FileOrganization; using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.HttpServer.Security; @@ -294,6 +294,13 @@ namespace Emby.Server.Core ImageEncoder = imageEncoder; SetBaseExceptionMessage(); + + if (environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows) + { + fileSystem.AddShortcutHandler(new LnkShortcutHandler()); + } + + fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); } private Version _version; @@ -606,7 +613,7 @@ namespace Emby.Server.Core CertificatePath = GetCertificatePath(true); Certificate = GetCertificate(CertificatePath); - HttpServer = HttpServerFactory.CreateServer(this, LogManager, ServerConfigurationManager, NetworkManager, MemoryStreamFactory, "Emby", "web/index.html", textEncoding, SocketFactory, CryptographyProvider, JsonSerializer, XmlSerializer, EnvironmentInfo, Certificate, SupportsDualModeSockets); + HttpServer = HttpServerFactory.CreateServer(this, LogManager, ServerConfigurationManager, NetworkManager, MemoryStreamFactory, "Emby", "web/index.html", textEncoding, SocketFactory, CryptographyProvider, JsonSerializer, XmlSerializer, EnvironmentInfo, Certificate, FileSystemManager, SupportsDualModeSockets); HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); RegisterSingleInstance(HttpServer, false); progress.Report(10); diff --git a/Emby.Server.Core/HttpServerFactory.cs b/Emby.Server.Core/HttpServerFactory.cs index deed3c6f3..dfd435c33 100644 --- a/Emby.Server.Core/HttpServerFactory.cs +++ b/Emby.Server.Core/HttpServerFactory.cs @@ -45,6 +45,7 @@ namespace Emby.Server.Core IXmlSerializer xml, IEnvironmentInfo environment, ICertificate certificate, + IFileSystem fileSystem, bool enableDualModeSockets) { var logger = logManager.GetLogger("HttpServer"); @@ -65,7 +66,8 @@ namespace Emby.Server.Core certificate, new StreamFactory(), GetParseFn, - enableDualModeSockets); + enableDualModeSockets, + fileSystem); } private static Func<string, object> GetParseFn(Type propertyType) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 9affe0fdd..d873cd77f 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -84,6 +84,7 @@ <Compile Include="FileOrganization\NameUtils.cs" /> <Compile Include="FileOrganization\OrganizerScheduledTask.cs" /> <Compile Include="FileOrganization\TvFolderOrganizer.cs" /> + <Compile Include="HttpServer\FileWriter.cs" /> <Compile Include="HttpServer\GetSwaggerResource.cs" /> <Compile Include="HttpServer\HttpListenerHost.cs" /> <Compile Include="HttpServer\HttpResultFactory.cs" /> @@ -303,8 +304,8 @@ <HintPath>..\packages\Emby.XmlTv.1.0.7\lib\portable-net45+win8\Emby.XmlTv.dll</HintPath> <Private>True</Private> </Reference> - <Reference Include="MediaBrowser.Naming, Version=1.0.6201.24431, Culture=neutral, processorArchitecture=MSIL"> - <HintPath>..\packages\MediaBrowser.Naming.1.0.4\lib\portable-net45+win8\MediaBrowser.Naming.dll</HintPath> + <Reference Include="MediaBrowser.Naming, Version=1.0.6279.25941, Culture=neutral, processorArchitecture=MSIL"> + <HintPath>..\packages\MediaBrowser.Naming.1.0.5\lib\portable-net45+win8\MediaBrowser.Naming.dll</HintPath> <Private>True</Private> </Reference> <Reference Include="SQLitePCL.pretty, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs new file mode 100644 index 000000000..b80a40962 --- /dev/null +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Services; + +namespace Emby.Server.Implementations.HttpServer +{ + public class FileWriter : IHttpResult + { + private ILogger Logger { get; set; } + + private string RangeHeader { 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; } + + public Action OnComplete { get; set; } + public Action OnError { get; set; } + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + public List<Cookie> Cookies { get; private 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> Headers + { + get { return _options; } + } + + public string Path { get; set; } + + public FileWriter(string path, string contentType, string rangeHeader, ILogger logger, IFileSystem fileSystem) + { + if (string.IsNullOrEmpty(contentType)) + { + throw new ArgumentNullException("contentType"); + } + + Path = path; + Logger = logger; + RangeHeader = rangeHeader; + + Headers["Content-Type"] = contentType; + + TotalContentLength = fileSystem.GetFileInfo(path).Length; + + if (string.IsNullOrWhiteSpace(rangeHeader)) + { + Headers["Content-Length"] = TotalContentLength.ToString(UsCulture); + StatusCode = HttpStatusCode.OK; + } + else + { + Headers["Accept-Ranges"] = "bytes"; + StatusCode = HttpStatusCode.PartialContent; + SetRangeValues(); + } + + Cookies = new List<Cookie>(); + } + + /// <summary> + /// Sets the range values. + /// </summary> + private void SetRangeValues() + { + var requestedRange = RequestedRanges[0]; + + // 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 + Headers["Content-Length"] = RangeLength.ToString(UsCulture); + Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength); + } + + /// <summary> + /// The _requested ranges + /// </summary> + private List<KeyValuePair<long, long?>> _requestedRanges; + /// <summary> + /// Gets the requested ranges. + /// </summary> + /// <value>The requested ranges.</value> + protected List<KeyValuePair<long, long?>> RequestedRanges + { + get + { + if (_requestedRanges == null) + { + _requestedRanges = new List<KeyValuePair<long, long?>>(); + + // Example: bytes=0-,32-63 + var ranges = RangeHeader.Split('=')[1].Split(','); + + foreach (var range in ranges) + { + var vals = range.Split('-'); + + long start = 0; + long? end = null; + + if (!string.IsNullOrEmpty(vals[0])) + { + start = long.Parse(vals[0], UsCulture); + } + if (!string.IsNullOrEmpty(vals[1])) + { + end = long.Parse(vals[1], UsCulture); + } + + _requestedRanges.Add(new KeyValuePair<long, long?>(start, end)); + } + } + + return _requestedRanges; + } + } + + public async Task WriteToAsync(IResponse response, CancellationToken cancellationToken) + { + try + { + // Headers only + if (IsHeadRequest) + { + return; + } + + if (string.IsNullOrWhiteSpace(RangeHeader) || (RangeStart <= 0 && RangeEnd >= TotalContentLength - 1)) + { + Logger.Info("Transmit file {0}", Path); + await response.TransmitFile(Path, 0, 0, cancellationToken).ConfigureAwait(false); + return; + } + + await response.TransmitFile(Path, RangeStart, RangeEnd, cancellationToken).ConfigureAwait(false); + } + finally + { + if (OnComplete != null) + { + OnComplete(); + } + } + } + + public string ContentType { get; set; } + + public IRequest RequestContext { get; set; } + + public object Response { get; set; } + + public int Status { get; set; } + + public HttpStatusCode StatusCode + { + get { return (HttpStatusCode)Status; } + set { Status = (int)value; } + } + + public string StatusDescription { get; set; } + + } +} diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index b5a3c2992..6d15cc619 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -52,6 +52,7 @@ namespace Emby.Server.Implementations.HttpServer private readonly ISocketFactory _socketFactory; private readonly ICryptoProvider _cryptoProvider; + private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; private readonly IXmlSerializer _xmlSerializer; private readonly ICertificate _certificate; @@ -70,8 +71,7 @@ namespace Emby.Server.Implementations.HttpServer ILogger logger, IServerConfigurationManager config, string serviceName, - string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, IEnvironmentInfo environment, ICertificate certificate, IStreamFactory streamFactory, Func<Type, Func<string, object>> funcParseFn, bool enableDualModeSockets) - : base() + string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, IEnvironmentInfo environment, ICertificate certificate, IStreamFactory streamFactory, Func<Type, Func<string, object>> funcParseFn, bool enableDualModeSockets, IFileSystem fileSystem) { Instance = this; @@ -89,6 +89,7 @@ namespace Emby.Server.Implementations.HttpServer _streamFactory = streamFactory; _funcParseFn = funcParseFn; _enableDualModeSockets = enableDualModeSockets; + _fileSystem = fileSystem; _config = config; _logger = logger; @@ -226,7 +227,8 @@ namespace Emby.Server.Implementations.HttpServer _cryptoProvider, _streamFactory, _enableDualModeSockets, - GetRequest); + GetRequest, + _fileSystem); } private IHttpRequest GetRequest(HttpListenerContext httpContext) diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 6bfd83110..e3f105941 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -474,10 +474,6 @@ namespace Emby.Server.Implementations.HttpServer { throw new ArgumentNullException("cacheKey"); } - if (options.ContentFactory == null) - { - throw new ArgumentNullException("factoryFn"); - } var key = cacheKey.ToString("N"); @@ -560,30 +556,43 @@ namespace Emby.Server.Implementations.HttpServer { var rangeHeader = requestContext.Headers.Get("Range"); - var stream = await factoryFn().ConfigureAwait(false); + if (!isHeadRequest && !string.IsNullOrWhiteSpace(options.Path) && options.FileShare == FileShareMode.Read) + { + return new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem) + { + OnComplete = options.OnComplete, + OnError = options.OnError + }; + } if (!string.IsNullOrEmpty(rangeHeader)) { + var stream = await factoryFn().ConfigureAwait(false); + return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest, _logger) { OnComplete = options.OnComplete }; } + else + { + var stream = await factoryFn().ConfigureAwait(false); - responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture); + responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture); - if (isHeadRequest) - { - stream.Dispose(); + if (isHeadRequest) + { + stream.Dispose(); - return GetHttpResult(new byte[] { }, contentType, true); - } + return GetHttpResult(new byte[] { }, contentType, true); + } - return new StreamWriter(stream, contentType, _logger) - { - OnComplete = options.OnComplete, - OnError = options.OnError - }; + return new StreamWriter(stream, contentType, _logger) + { + OnComplete = options.OnComplete, + OnError = options.OnError + }; + } } using (var stream = await factoryFn().ConfigureAwait(false)) diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs index 652fc4f83..b11b2fe88 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpListener.cs @@ -27,10 +27,11 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp private readonly ISocketFactory _socketFactory; private readonly ICryptoProvider _cryptoProvider; private readonly IStreamFactory _streamFactory; + private readonly IFileSystem _fileSystem; private readonly Func<HttpListenerContext, IHttpRequest> _httpRequestFactory; private readonly bool _enableDualMode; - public WebSocketSharpListener(ILogger logger, ICertificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, bool enableDualMode, Func<HttpListenerContext, IHttpRequest> httpRequestFactory) + 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) { _logger = logger; _certificate = certificate; @@ -42,6 +43,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp _streamFactory = streamFactory; _enableDualMode = enableDualMode; _httpRequestFactory = httpRequestFactory; + _fileSystem = fileSystem; } public Action<Exception, IRequest, bool> ErrorHandler { get; set; } @@ -54,7 +56,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); + _listener = new HttpListener(_logger, _cryptoProvider, _streamFactory, _socketFactory, _networkManager, _textEncoding, _memoryStreamProvider, _fileSystem); _listener.EnableDualMode = _enableDualMode; diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs index 36f795411..a497ee715 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs @@ -3,6 +3,9 @@ using System.Collections.Generic; using System.IO; using System.Net; using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using SocketHttpListener.Net; using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse; @@ -189,5 +192,10 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp public void ClearCookies() { } + + public Task TransmitFile(string path, long offset, long count, CancellationToken cancellationToken) + { + return _response.TransmitFile(path, offset, count, cancellationToken); + } } } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 926d82f94..d9060a066 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1276,6 +1276,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return; } + var registration = await _liveTvManager.GetRegistrationInfo("dvr").ConfigureAwait(false); + if (!registration.IsValid) + { + _logger.Warn("Emby Premiere required to use Emby DVR."); + OnTimerOutOfDate(timer); + return; + } + var activeRecordingInfo = new ActiveRecordingInfo { CancellationTokenSource = new CancellationTokenSource(), @@ -2319,6 +2327,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { UpdateExistingTimerWithNewMetadata(existingTimer, timer); + // Needed by ShouldCancelTimerForSeriesTimer + timer.IsManual = existingTimer.IsManual; + if (ShouldCancelTimerForSeriesTimer(seriesTimer, timer)) { existingTimer.Status = RecordingStatus.Cancelled; diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs index d4ce1cabf..3c2af60db 100644 --- a/Emby.Server.Implementations/Services/ResponseHelper.cs +++ b/Emby.Server.Implementations/Services/ResponseHelper.cs @@ -12,45 +12,6 @@ namespace Emby.Server.Implementations.Services { public static class ResponseHelper { - private static async Task<bool> WriteToOutputStream(IResponse response, object result) - { - var asyncStreamWriter = result as IAsyncStreamWriter; - if (asyncStreamWriter != null) - { - await asyncStreamWriter.WriteToAsync(response.OutputStream, CancellationToken.None).ConfigureAwait(false); - return true; - } - - var streamWriter = result as IStreamWriter; - if (streamWriter != null) - { - streamWriter.WriteTo(response.OutputStream); - return true; - } - - var stream = result as Stream; - if (stream != null) - { - using (stream) - { - await stream.CopyToAsync(response.OutputStream).ConfigureAwait(false); - return true; - } - } - - var bytes = result as byte[]; - if (bytes != null) - { - response.ContentType = "application/octet-stream"; - response.SetContentLength(bytes.Length); - - await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); - return true; - } - - return false; - } - public static Task WriteToResponse(IResponse httpRes, IRequest httpReq, object result) { if (result == null) @@ -141,16 +102,51 @@ namespace Emby.Server.Implementations.Services response.ContentType += "; charset=utf-8"; } - var writeToOutputStreamResult = await WriteToOutputStream(response, result).ConfigureAwait(false); - if (writeToOutputStreamResult) + var asyncStreamWriter = result as IAsyncStreamWriter; + if (asyncStreamWriter != null) { + await asyncStreamWriter.WriteToAsync(response.OutputStream, CancellationToken.None).ConfigureAwait(false); + return; + } + + var streamWriter = result as IStreamWriter; + if (streamWriter != null) + { + streamWriter.WriteTo(response.OutputStream); + return; + } + + var fileWriter = result as FileWriter; + if (fileWriter != null) + { + await fileWriter.WriteToAsync(response, CancellationToken.None).ConfigureAwait(false); + return; + } + + var stream = result as Stream; + if (stream != null) + { + using (stream) + { + await stream.CopyToAsync(response.OutputStream).ConfigureAwait(false); + return; + } + } + + var bytes = result as byte[]; + if (bytes != null) + { + response.ContentType = "application/octet-stream"; + response.SetContentLength(bytes.Length); + + await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); return; } var responseText = result as string; if (responseText != null) { - var bytes = Encoding.UTF8.GetBytes(responseText); + bytes = Encoding.UTF8.GetBytes(responseText); response.SetContentLength(bytes.Length); await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); return; @@ -163,7 +159,7 @@ namespace Emby.Server.Implementations.Services { var contentType = request.ResponseContentType; var serializer = RequestHelper.GetResponseWriter(HttpListenerHost.Instance, contentType); - + using (var ms = new MemoryStream()) { serializer(result, ms); diff --git a/Emby.Server.Implementations/packages.config b/Emby.Server.Implementations/packages.config index 7e638e171..ccabbc27b 100644 --- a/Emby.Server.Implementations/packages.config +++ b/Emby.Server.Implementations/packages.config @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <packages> <package id="Emby.XmlTv" version="1.0.7" targetFramework="portable45-net45+win8" /> - <package id="MediaBrowser.Naming" version="1.0.4" targetFramework="portable45-net45+win8" /> + <package id="MediaBrowser.Naming" version="1.0.5" targetFramework="portable45-net45+win8" /> <package id="SQLitePCL.pretty" version="1.1.0" targetFramework="portable45-net45+win8" /> <package id="SQLitePCLRaw.core" version="1.1.2" targetFramework="portable45-net45+win8" /> <package id="UniversalDetector" version="1.0.1" targetFramework="portable45-net45+win8" /> diff --git a/MediaBrowser.Controller/Entities/IHasImages.cs b/MediaBrowser.Controller/Entities/IHasImages.cs index 4c033dc00..2104bee09 100644 --- a/MediaBrowser.Controller/Entities/IHasImages.cs +++ b/MediaBrowser.Controller/Entities/IHasImages.cs @@ -206,6 +206,8 @@ namespace MediaBrowser.Controller.Entities void SetImage(ItemImageInfo image, int index); double? GetDefaultPrimaryImageAspectRatio(); + + int? ProductionYear { get; set; } } public static class HasImagesExtensions diff --git a/MediaBrowser.Controller/Net/StaticResultOptions.cs b/MediaBrowser.Controller/Net/StaticResultOptions.cs index 272fa8b3b..62c13fbb5 100644 --- a/MediaBrowser.Controller/Net/StaticResultOptions.cs +++ b/MediaBrowser.Controller/Net/StaticResultOptions.cs @@ -23,21 +23,18 @@ namespace MediaBrowser.Controller.Net public Action OnComplete { get; set; } public Action OnError { get; set; } + public string Path { get; set; } + + public FileShareMode FileShare { get; set; } + public StaticResultOptions() { ResponseHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + FileShare = FileShareMode.Read; } } public class StaticFileResultOptions : StaticResultOptions { - public string Path { get; set; } - - public FileShareMode FileShare { get; set; } - - public StaticFileResultOptions() - { - FileShare = FileShareMode.Read; - } } } diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs index f6d1bb351..f90119cf3 100644 --- a/MediaBrowser.Model/IO/IFileSystem.cs +++ b/MediaBrowser.Model/IO/IFileSystem.cs @@ -10,6 +10,8 @@ namespace MediaBrowser.Model.IO /// </summary> public interface IFileSystem { + void AddShortcutHandler(IShortcutHandler handler); + /// <summary> /// Determines whether the specified filename is shortcut. /// </summary> diff --git a/MediaBrowser.Model/Net/IAcceptSocket.cs b/MediaBrowser.Model/Net/IAcceptSocket.cs index cac23b337..4262e2390 100644 --- a/MediaBrowser.Model/Net/IAcceptSocket.cs +++ b/MediaBrowser.Model/Net/IAcceptSocket.cs @@ -1,4 +1,6 @@ using System; +using System.Threading; +using System.Threading.Tasks; namespace MediaBrowser.Model.Net { @@ -13,6 +15,7 @@ namespace MediaBrowser.Model.Net void Bind(IpEndPointInfo endpoint); void Connect(IpEndPointInfo endPoint); void StartAccept(Action<IAcceptSocket> onAccept, Func<bool> isClosed); + Task SendFile(string path, byte[] preBuffer, byte[] postBuffer, CancellationToken cancellationToken); } public class SocketCreateException : Exception diff --git a/MediaBrowser.Model/Services/IAsyncStreamWriter.cs b/MediaBrowser.Model/Services/IAsyncStreamWriter.cs index b10e12813..f61a94f6e 100644 --- a/MediaBrowser.Model/Services/IAsyncStreamWriter.cs +++ b/MediaBrowser.Model/Services/IAsyncStreamWriter.cs @@ -8,4 +8,9 @@ namespace MediaBrowser.Model.Services { Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken); } + + public interface IFileWriter + { + Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken); + } } diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index e9a9f1c5b..40cef4ec0 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.IO; using System.Net; +using System.Threading; +using System.Threading.Tasks; namespace MediaBrowser.Model.Services { @@ -151,6 +153,7 @@ namespace MediaBrowser.Model.Services //Add Metadata to Response Dictionary<string, object> Items { get; } - } + Task TransmitFile(string path, long offset, long count, CancellationToken cancellationToken); + } } diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs index 46be61486..4a52b972f 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs @@ -119,7 +119,7 @@ namespace MediaBrowser.Providers.TV if (TvdbSeriesProvider.IsValidSeries(searchInfo.SeriesProviderIds) && (searchInfo.IndexNumber.HasValue || searchInfo.PremiereDate.HasValue)) { - await TvdbSeriesProvider.Current.EnsureSeriesInfo(searchInfo.SeriesProviderIds, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false); + await TvdbSeriesProvider.Current.EnsureSeriesInfo(searchInfo.SeriesProviderIds, null, null, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false); var seriesDataPath = TvdbSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, searchInfo.SeriesProviderIds); diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs index f6af365fd..72bd62d9f 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs @@ -389,7 +389,7 @@ namespace MediaBrowser.Providers.TV _fileSystem.CreateDirectory(seriesDataPath); - return TvdbSeriesProvider.Current.DownloadSeriesZip(id, MetadataProviders.Tvdb.ToString(), seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, cancellationToken); + return TvdbSeriesProvider.Current.DownloadSeriesZip(id, MetadataProviders.Tvdb.ToString(), null, null, seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, cancellationToken); } } } diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs index 2c4ccddd7..e68b7ad1d 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs @@ -75,7 +75,7 @@ namespace MediaBrowser.Providers.TV var seriesProviderIds = series.ProviderIds; var seasonNumber = season.IndexNumber.Value; - var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(seriesProviderIds, series.GetPreferredMetadataLanguage(), cancellationToken).ConfigureAwait(false); + var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(seriesProviderIds, series.Name, series.ProductionYear, series.GetPreferredMetadataLanguage(), cancellationToken).ConfigureAwait(false); if (!string.IsNullOrWhiteSpace(seriesDataPath)) { diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs index 97eedfa92..cdb9ac51e 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs @@ -70,7 +70,7 @@ namespace MediaBrowser.Providers.TV { var language = item.GetPreferredMetadataLanguage(); - var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(item.ProviderIds, language, cancellationToken).ConfigureAwait(false); + var seriesDataPath = await TvdbSeriesProvider.Current.EnsureSeriesInfo(item.ProviderIds, item.Name, item.ProductionYear, language, cancellationToken).ConfigureAwait(false); var path = Path.Combine(seriesDataPath, "banners.xml"); diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs index 44550d2be..c340d03f5 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs @@ -109,7 +109,7 @@ namespace MediaBrowser.Providers.TV if (IsValidSeries(itemId.ProviderIds)) { - await EnsureSeriesInfo(itemId.ProviderIds, itemId.MetadataLanguage, cancellationToken).ConfigureAwait(false); + await EnsureSeriesInfo(itemId.ProviderIds, itemId.Name, itemId.Year, itemId.MetadataLanguage, cancellationToken).ConfigureAwait(false); result.Item = new Series(); result.HasMetadata = true; @@ -160,19 +160,11 @@ namespace MediaBrowser.Providers.TV /// <summary> /// Downloads the series zip. /// </summary> - /// <param name="seriesId">The series id.</param> - /// <param name="idType">Type of the identifier.</param> - /// <param name="seriesDataPath">The series data path.</param> - /// <param name="lastTvDbUpdateTime">The last tv database update time.</param> - /// <param name="preferredMetadataLanguage">The preferred metadata language.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - /// <exception cref="System.ArgumentNullException">seriesId</exception> - internal async Task DownloadSeriesZip(string seriesId, string idType, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, CancellationToken cancellationToken) + internal async Task DownloadSeriesZip(string seriesId, string idType, string seriesName, int? seriesYear, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, CancellationToken cancellationToken) { try { - await DownloadSeriesZip(seriesId, idType, seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); + await DownloadSeriesZip(seriesId, idType, seriesName, seriesYear, seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); return; } catch (HttpException ex) @@ -185,11 +177,11 @@ namespace MediaBrowser.Providers.TV if (!string.Equals(preferredMetadataLanguage, "en", StringComparison.OrdinalIgnoreCase)) { - await DownloadSeriesZip(seriesId, idType, seriesDataPath, lastTvDbUpdateTime, "en", preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); + await DownloadSeriesZip(seriesId, idType, seriesName, seriesYear, seriesDataPath, lastTvDbUpdateTime, "en", preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); } } - private async Task DownloadSeriesZip(string seriesId, string idType, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, string saveAsMetadataLanguage, CancellationToken cancellationToken) + private async Task DownloadSeriesZip(string seriesId, string idType, string seriesName, int? seriesYear, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, string saveAsMetadataLanguage, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(seriesId)) { @@ -201,6 +193,23 @@ namespace MediaBrowser.Providers.TV seriesId = await GetSeriesByRemoteId(seriesId, idType, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); } + // If searching by remote id came up empty, then do a regular search + if (string.IsNullOrWhiteSpace(seriesId) && !string.IsNullOrWhiteSpace(seriesName)) + { + var searchInfo = new SeriesInfo + { + Name = seriesName, + Year = seriesYear, + MetadataLanguage = preferredMetadataLanguage + }; + var results = await GetSearchResults(searchInfo, cancellationToken).ConfigureAwait(false); + var result = results.FirstOrDefault(); + if (result != null) + { + seriesId = result.GetProviderId(MetadataProviders.Tvdb); + } + } + if (string.IsNullOrWhiteSpace(seriesId)) { throw new ArgumentNullException("seriesId"); @@ -380,7 +389,7 @@ namespace MediaBrowser.Providers.TV } private SemaphoreSlim _ensureSemaphore = new SemaphoreSlim(1, 1); - internal async Task<string> EnsureSeriesInfo(Dictionary<string, string> seriesProviderIds, string preferredMetadataLanguage, CancellationToken cancellationToken) + internal async Task<string> EnsureSeriesInfo(Dictionary<string, string> seriesProviderIds, string seriesName, int? seriesYear, string preferredMetadataLanguage, CancellationToken cancellationToken) { await _ensureSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); @@ -395,7 +404,7 @@ namespace MediaBrowser.Providers.TV // The post-scan task will take care of updates so we don't need to re-download here if (!IsCacheValid(seriesDataPath, preferredMetadataLanguage)) { - await DownloadSeriesZip(seriesId, MetadataProviders.Tvdb.ToString(), seriesDataPath, null, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); + await DownloadSeriesZip(seriesId, MetadataProviders.Tvdb.ToString(), seriesName, seriesYear, seriesDataPath, null, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); } return seriesDataPath; @@ -409,7 +418,7 @@ namespace MediaBrowser.Providers.TV // The post-scan task will take care of updates so we don't need to re-download here if (!IsCacheValid(seriesDataPath, preferredMetadataLanguage)) { - await DownloadSeriesZip(seriesId, MetadataProviders.Imdb.ToString(), seriesDataPath, null, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); + await DownloadSeriesZip(seriesId, MetadataProviders.Imdb.ToString(), seriesName, seriesYear, seriesDataPath, null, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); } return seriesDataPath; diff --git a/MediaBrowser.Server.Mac/Main.cs b/MediaBrowser.Server.Mac/Main.cs index debd5f539..d703f7d0d 100644 --- a/MediaBrowser.Server.Mac/Main.cs +++ b/MediaBrowser.Server.Mac/Main.cs @@ -105,12 +105,11 @@ namespace MediaBrowser.Server.Mac // Allow all https requests ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; }); - var fileSystem = new MonoFileSystem(logManager.GetLogger("FileSystem"), false, false, appPaths.TempDirectory); - fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); + var environmentInfo = GetEnvironmentInfo(); - _fileSystem = fileSystem; + var fileSystem = new MonoFileSystem(logManager.GetLogger("FileSystem"), environmentInfo, appPaths.TempDirectory); - var environmentInfo = GetEnvironmentInfo(); + _fileSystem = fileSystem; var imageEncoder = ImageEncoderHelper.GetImageEncoder(_logger, logManager, diff --git a/MediaBrowser.Server.Mono/Native/MonoFileSystem.cs b/MediaBrowser.Server.Mono/Native/MonoFileSystem.cs index a5dc691a7..91c064efe 100644 --- a/MediaBrowser.Server.Mono/Native/MonoFileSystem.cs +++ b/MediaBrowser.Server.Mono/Native/MonoFileSystem.cs @@ -1,13 +1,14 @@ using Emby.Common.Implementations.IO; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.System; using Mono.Unix.Native; namespace MediaBrowser.Server.Mono.Native { public class MonoFileSystem : ManagedFileSystem { - public MonoFileSystem(ILogger logger, bool supportsAsyncFileStreams, bool enableManagedInvalidFileNameChars, string tempPath) - : base(logger, supportsAsyncFileStreams, enableManagedInvalidFileNameChars, true, tempPath) + public MonoFileSystem(ILogger logger, IEnvironmentInfo environment, string tempPath) + : base(logger, environment, tempPath) { } diff --git a/MediaBrowser.Server.Mono/Program.cs b/MediaBrowser.Server.Mono/Program.cs index 649283410..66851f7e9 100644 --- a/MediaBrowser.Server.Mono/Program.cs +++ b/MediaBrowser.Server.Mono/Program.cs @@ -114,12 +114,11 @@ namespace MediaBrowser.Server.Mono // Allow all https requests ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; }); - var fileSystem = new MonoFileSystem(logManager.GetLogger("FileSystem"), false, false, appPaths.TempDirectory); - fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); + var environmentInfo = GetEnvironmentInfo(); - FileSystem = fileSystem; + var fileSystem = new MonoFileSystem(logManager.GetLogger("FileSystem"), environmentInfo, appPaths.TempDirectory); - var environmentInfo = GetEnvironmentInfo(); + FileSystem = fileSystem; var imageEncoder = ImageEncoderHelper.GetImageEncoder(_logger, logManager, fileSystem, options, () => _appHost.HttpClient, appPaths); diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index b41e7607c..b02c5d6ac 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -331,9 +331,9 @@ namespace MediaBrowser.ServerApplication /// <param name="options">The options.</param> private static void RunApplication(ServerApplicationPaths appPaths, ILogManager logManager, bool runService, StartupOptions options) { - var fileSystem = new ManagedFileSystem(logManager.GetLogger("FileSystem"), true, true, false, appPaths.TempDirectory); - fileSystem.AddShortcutHandler(new LnkShortcutHandler()); - fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); + var environmentInfo = new EnvironmentInfo(); + + var fileSystem = new ManagedFileSystem(logManager.GetLogger("FileSystem"), environmentInfo, appPaths.TempDirectory); var imageEncoder = ImageEncoderHelper.GetImageEncoder(_logger, logManager, fileSystem, options, () => _appHost.HttpClient, appPaths); @@ -345,7 +345,7 @@ namespace MediaBrowser.ServerApplication fileSystem, new PowerManagement(), "emby.windows.zip", - new EnvironmentInfo(), + environmentInfo, imageEncoder, new Server.Startup.Common.SystemEvents(logManager.GetLogger("SystemEvents")), new RecyclableMemoryStreamProvider(), diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index 02916f555..63e10df76 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -142,7 +142,6 @@ <DependentUpon>MainForm.cs</DependentUpon> </Compile> <Compile Include="MainStartup.cs" /> - <Compile Include="Native\LnkShortcutHandler.cs" /> <Compile Include="Native\PowerManagement.cs" /> <Compile Include="Native\Standby.cs" /> <Compile Include="Native\ServerAuthorization.cs" /> diff --git a/MediaBrowser.ServerApplication/WindowsAppHost.cs b/MediaBrowser.ServerApplication/WindowsAppHost.cs index 915a2fa86..2d3d8a85b 100644 --- a/MediaBrowser.ServerApplication/WindowsAppHost.cs +++ b/MediaBrowser.ServerApplication/WindowsAppHost.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.InteropServices.ComTypes; +using Emby.Common.Implementations.IO; using Emby.Server.CinemaMode; using Emby.Server.Connect; using Emby.Server.Core; diff --git a/SharedVersion.cs b/SharedVersion.cs index e3fd5e349..3f2c5d97e 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.7.2")] +[assembly: AssemblyVersion("3.2.7.3")] diff --git a/SocketHttpListener.Portable/Net/EndPointListener.cs b/SocketHttpListener.Portable/Net/EndPointListener.cs index c7642d5d1..4f1a17fc0 100644 --- a/SocketHttpListener.Portable/Net/EndPointListener.cs +++ b/SocketHttpListener.Portable/Net/EndPointListener.cs @@ -32,8 +32,9 @@ namespace SocketHttpListener.Net private readonly ISocketFactory _socketFactory; private readonly ITextEncoding _textEncoding; private readonly IMemoryStreamFactory _memoryStreamFactory; + private readonly 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) + 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) { this.listener = listener; _logger = logger; @@ -42,6 +43,7 @@ namespace SocketHttpListener.Net _socketFactory = socketFactory; _memoryStreamFactory = memoryStreamFactory; _textEncoding = textEncoding; + _fileSystem = fileSystem; this.secure = secure; this.cert = cert; @@ -107,7 +109,7 @@ namespace SocketHttpListener.Net return; } - HttpConnection conn = await HttpConnection.Create(_logger, accepted, listener, listener.secure, listener.cert, _cryptoProvider, _streamFactory, _memoryStreamFactory, _textEncoding).ConfigureAwait(false); + HttpConnection conn = await HttpConnection.Create(_logger, accepted, listener, listener.secure, listener.cert, _cryptoProvider, _streamFactory, _memoryStreamFactory, _textEncoding, _fileSystem).ConfigureAwait(false); //_logger.Debug("Adding unregistered connection to {0}. Id: {1}", accepted.RemoteEndPoint, connectionId); lock (listener.unregistered) diff --git a/SocketHttpListener.Portable/Net/EndPointManager.cs b/SocketHttpListener.Portable/Net/EndPointManager.cs index 797684b3e..11f774915 100644 --- a/SocketHttpListener.Portable/Net/EndPointManager.cs +++ b/SocketHttpListener.Portable/Net/EndPointManager.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net; using System.Reflection; using System.Threading.Tasks; +using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; using SocketHttpListener.Primitives; @@ -105,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); + epl = new EndPointListener(listener, addr, port, secure, listener.Certificate, logger, listener.CryptoProvider, listener.StreamFactory, listener.SocketFactory, listener.MemoryStreamFactory, listener.TextEncoding, listener.FileSystem); p[port] = epl; } diff --git a/SocketHttpListener.Portable/Net/HttpConnection.cs b/SocketHttpListener.Portable/Net/HttpConnection.cs index 4a2cf3f5b..5fe47fc63 100644 --- a/SocketHttpListener.Portable/Net/HttpConnection.cs +++ b/SocketHttpListener.Portable/Net/HttpConnection.cs @@ -35,13 +35,14 @@ namespace SocketHttpListener.Net ICertificate cert; Stream ssl_stream; - private ILogger _logger; + private readonly ILogger _logger; private readonly ICryptoProvider _cryptoProvider; private readonly IMemoryStreamFactory _memoryStreamFactory; private readonly ITextEncoding _textEncoding; private readonly IStreamFactory _streamFactory; + private readonly IFileSystem _fileSystem; - private HttpConnection(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding) + private HttpConnection(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem) { _logger = logger; this.sock = sock; @@ -51,6 +52,7 @@ namespace SocketHttpListener.Net _cryptoProvider = cryptoProvider; _memoryStreamFactory = memoryStreamFactory; _textEncoding = textEncoding; + _fileSystem = fileSystem; _streamFactory = streamFactory; } @@ -82,9 +84,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) + 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) { - var connection = new HttpConnection(logger, sock, epl, secure, cert, cryptoProvider, streamFactory, memoryStreamFactory, textEncoding); + var connection = new HttpConnection(logger, sock, epl, secure, cert, cryptoProvider, streamFactory, memoryStreamFactory, textEncoding, fileSystem); await connection.InitStream().ConfigureAwait(false); @@ -121,7 +123,7 @@ namespace SocketHttpListener.Net position = 0; input_state = InputState.RequestLine; line_state = LineState.None; - context = new HttpListenerContext(this, _logger, _cryptoProvider, _memoryStreamFactory, _textEncoding); + context = new HttpListenerContext(this, _logger, _cryptoProvider, _memoryStreamFactory, _textEncoding, _fileSystem); } public bool IsClosed @@ -213,7 +215,9 @@ namespace SocketHttpListener.Net if (context.Response.SendChunked || isExpect100Continue || context.Request.IsWebSocketRequest || true) { - o_stream = new ResponseStream(stream, context.Response, _memoryStreamFactory, _textEncoding); + var supportsDirectSocketAccess = !context.Response.SendChunked && !isExpect100Continue && !secure; + + o_stream = new ResponseStream(stream, context.Response, _memoryStreamFactory, _textEncoding, _fileSystem, sock, supportsDirectSocketAccess); } else { diff --git a/SocketHttpListener.Portable/Net/HttpListener.cs b/SocketHttpListener.Portable/Net/HttpListener.cs index 2b0f75d01..c2e7acd8e 100644 --- a/SocketHttpListener.Portable/Net/HttpListener.cs +++ b/SocketHttpListener.Portable/Net/HttpListener.cs @@ -18,6 +18,7 @@ namespace SocketHttpListener.Net internal ICryptoProvider CryptoProvider { get; private set; } internal IStreamFactory StreamFactory { get; private set; } internal ISocketFactory SocketFactory { get; private set; } + internal IFileSystem FileSystem { get; private set; } internal ITextEncoding TextEncoding { get; private set; } internal IMemoryStreamFactory MemoryStreamFactory { get; private set; } internal INetworkManager NetworkManager { get; private set; } @@ -39,7 +40,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) + public HttpListener(ILogger logger, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory, IFileSystem fileSystem) { _logger = logger; CryptoProvider = cryptoProvider; @@ -48,19 +49,20 @@ namespace SocketHttpListener.Net NetworkManager = networkManager; TextEncoding = textEncoding; MemoryStreamFactory = memoryStreamFactory; + FileSystem = fileSystem; 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) - :this(new NullLogger(), certificate, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory) + 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(ILogger logger, ICertificate certificate, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, ISocketFactory socketFactory, INetworkManager networkManager, ITextEncoding textEncoding, IMemoryStreamFactory memoryStreamFactory) - : this(logger, cryptoProvider, streamFactory, socketFactory, networkManager, textEncoding, memoryStreamFactory) + 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) { _certificate = certificate; } diff --git a/SocketHttpListener.Portable/Net/HttpListenerContext.cs b/SocketHttpListener.Portable/Net/HttpListenerContext.cs index 182fd2d2a..58d769f22 100644 --- a/SocketHttpListener.Portable/Net/HttpListenerContext.cs +++ b/SocketHttpListener.Portable/Net/HttpListenerContext.cs @@ -18,20 +18,18 @@ namespace SocketHttpListener.Net HttpConnection cnc; string error; int err_status = 400; - private readonly ILogger _logger; private readonly ICryptoProvider _cryptoProvider; private readonly IMemoryStreamFactory _memoryStreamFactory; private readonly ITextEncoding _textEncoding; - internal HttpListenerContext(HttpConnection cnc, ILogger logger, ICryptoProvider cryptoProvider, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding) + internal HttpListenerContext(HttpConnection cnc, ILogger logger, ICryptoProvider cryptoProvider, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem) { this.cnc = cnc; - _logger = logger; _cryptoProvider = cryptoProvider; _memoryStreamFactory = memoryStreamFactory; _textEncoding = textEncoding; request = new HttpListenerRequest(this, _textEncoding); - response = new HttpListenerResponse(this, _logger, _textEncoding); + response = new HttpListenerResponse(this, logger, _textEncoding, fileSystem); } internal int ErrorStatus diff --git a/SocketHttpListener.Portable/Net/HttpListenerResponse.cs b/SocketHttpListener.Portable/Net/HttpListenerResponse.cs index 9a5862cb9..d8011f05e 100644 --- a/SocketHttpListener.Portable/Net/HttpListenerResponse.cs +++ b/SocketHttpListener.Portable/Net/HttpListenerResponse.cs @@ -3,6 +3,9 @@ using System.Globalization; using System.IO; using System.Net; using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Text; using SocketHttpListener.Primitives; @@ -32,12 +35,14 @@ namespace SocketHttpListener.Net private readonly ILogger _logger; private readonly ITextEncoding _textEncoding; + private readonly IFileSystem _fileSystem; - internal HttpListenerResponse(HttpListenerContext context, ILogger logger, ITextEncoding textEncoding) + internal HttpListenerResponse(HttpListenerContext context, ILogger logger, ITextEncoding textEncoding, IFileSystem fileSystem) { this.context = context; _logger = logger; _textEncoding = textEncoding; + _fileSystem = fileSystem; } internal bool CloseConnection @@ -366,7 +371,7 @@ namespace SocketHttpListener.Net { if (chunked) { - return ; + return; } Version v = context.Request.ProtocolVersion; @@ -509,5 +514,10 @@ namespace SocketHttpListener.Net cookies.Add(cookie); } + + public Task TransmitFile(string path, long offset, long count, CancellationToken cancellationToken) + { + return ((ResponseStream)OutputStream).TransmitFile(path, offset, count, cancellationToken); + } } }
\ No newline at end of file diff --git a/SocketHttpListener.Portable/Net/ResponseStream.cs b/SocketHttpListener.Portable/Net/ResponseStream.cs index a79a18791..ccc0efc55 100644 --- a/SocketHttpListener.Portable/Net/ResponseStream.cs +++ b/SocketHttpListener.Portable/Net/ResponseStream.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.IO; +using MediaBrowser.Model.Net; using MediaBrowser.Model.Text; using SocketHttpListener.Primitives; @@ -22,12 +23,18 @@ namespace SocketHttpListener.Net Stream stream; private readonly IMemoryStreamFactory _memoryStreamFactory; private readonly ITextEncoding _textEncoding; + private readonly IFileSystem _fileSystem; + private readonly IAcceptSocket _socket; + private readonly bool _supportsDirectSocketAccess; - internal ResponseStream(Stream stream, HttpListenerResponse response, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding) + internal ResponseStream(Stream stream, HttpListenerResponse response, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding, IFileSystem fileSystem, IAcceptSocket socket, bool supportsDirectSocketAccess) { this.response = response; _memoryStreamFactory = memoryStreamFactory; _textEncoding = textEncoding; + _fileSystem = fileSystem; + _socket = socket; + _supportsDirectSocketAccess = supportsDirectSocketAccess; this.stream = stream; } @@ -299,5 +306,80 @@ namespace SocketHttpListener.Net { throw new NotSupportedException(); } + + public Task TransmitFile(string path, long offset, long count, CancellationToken cancellationToken) + { + //if (_supportsDirectSocketAccess && offset == 0 && count == 0 && !response.SendChunked) + //{ + // return TransmitFileOverSocket(path, offset, count, cancellationToken); + //} + return TransmitFileManaged(path, offset, count, cancellationToken); + } + + private readonly byte[] _emptyBuffer = new byte[] { }; + private async Task TransmitFileOverSocket(string path, long offset, long count, CancellationToken cancellationToken) + { + MemoryStream ms = GetHeaders(response, _memoryStreamFactory, false); + + var buffer = new byte[] {}; + if (ms != null) + { + ms.Position = 0; + + byte[] msBuffer; + _memoryStreamFactory.TryGetBuffer(ms, out msBuffer); + buffer = msBuffer; + } + + await _socket.SendFile(path, buffer, _emptyBuffer, cancellationToken).ConfigureAwait(false); + } + + private async Task TransmitFileManaged(string path, long offset, long count, CancellationToken cancellationToken) + { + var chunked = response.SendChunked; + + if (!chunked) + { + await WriteAsync(_emptyBuffer, 0, 0, cancellationToken).ConfigureAwait(false); + } + + using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true)) + { + if (offset > 0) + { + fs.Position = offset; + } + + var targetStream = chunked ? this : stream; + + if (count > 0) + { + await CopyToInternalAsync(fs, targetStream, count, cancellationToken).ConfigureAwait(false); + } + else + { + await fs.CopyToAsync(targetStream, 81920, cancellationToken).ConfigureAwait(false); + } + } + } + + private async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken) + { + var array = new byte[81920]; + int count; + while ((count = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0) + { + var bytesToCopy = Math.Min(count, copyLength); + + await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy), cancellationToken).ConfigureAwait(false); + + copyLength -= bytesToCopy; + + if (copyLength <= 0) + { + break; + } + } + } } } |
