diff --git a/MediaBrowser.ServerApplication/Native/LnkShortcutHandler.cs b/Emby.Common.Implementations/IO/LnkShortcutHandler.cs
similarity index 99%
rename from MediaBrowser.ServerApplication/Native/LnkShortcutHandler.cs
rename to Emby.Common.Implementations/IO/LnkShortcutHandler.cs
index b4a87b9b48..5d5f460574 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
///
/// Class NativeMethods
///
- [SuppressUnmanagedCodeSecurity]
public static class NativeMethods
{
///
diff --git a/Emby.Common.Implementations/IO/ManagedFileSystem.cs b/Emby.Common.Implementations/IO/ManagedFileSystem.cs
index 3fe20f6596..2c9388a414 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 _shortcutHandlers = new List();
- 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 731ad1b741..e21ffe553a 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();
+
+ var result = Socket.BeginSendFile(path, preBuffer, postBuffer, options, new AsyncCallback(FileSendCallback), new Tuple>(Socket, path, completionSource));
+
+ return completionSource.Task;
+ }
+
+ private void FileSendCallback(IAsyncResult ar)
+ {
+ // Retrieve the socket from the state object.
+ Tuple> data = (Tuple>)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 523f4da855..021613e57f 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 7dbc7760b8..971378ea7d 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 deed3c6f35..dfd435c337 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 GetParseFn(Type propertyType)
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 9affe0fdd8..d873cd77f4 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -84,6 +84,7 @@
+
@@ -303,8 +304,8 @@
..\packages\Emby.XmlTv.1.0.7\lib\portable-net45+win8\Emby.XmlTv.dll
True
-
- ..\packages\MediaBrowser.Naming.1.0.4\lib\portable-net45+win8\MediaBrowser.Naming.dll
+
+ ..\packages\MediaBrowser.Naming.1.0.5\lib\portable-net45+win8\MediaBrowser.Naming.dll
True
diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs
new file mode 100644
index 0000000000..b80a40962d
--- /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 Cookies { get; private set; }
+
+ ///
+ /// The _options
+ ///
+ private readonly IDictionary _options = new Dictionary();
+ ///
+ /// Gets the options.
+ ///
+ /// The options.
+ public IDictionary 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();
+ }
+
+ ///
+ /// Sets the range values.
+ ///
+ 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);
+ }
+
+ ///
+ /// The _requested ranges
+ ///
+ private List> _requestedRanges;
+ ///
+ /// Gets the requested ranges.
+ ///
+ /// The requested ranges.
+ protected List> RequestedRanges
+ {
+ get
+ {
+ if (_requestedRanges == null)
+ {
+ _requestedRanges = new List>();
+
+ // 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(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 b5a3c2992e..6d15cc6191 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> 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> 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 6bfd831100..e3f105941f 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 652fc4f837..b11b2fe88f 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 _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 httpRequestFactory)
+ public WebSocketSharpListener(ILogger logger, ICertificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, bool enableDualMode, Func 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 ErrorHandler { get; set; }
@@ -54,7 +56,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
public void Start(IEnumerable 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 36f7954116..a497ee7155 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 926d82f94b..d9060a0663 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 d4ce1cabfe..3c2af60db0 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 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 7e638e1715..ccabbc27b3 100644
--- a/Emby.Server.Implementations/packages.config
+++ b/Emby.Server.Implementations/packages.config
@@ -1,7 +1,7 @@
-
+
diff --git a/MediaBrowser.Controller/Entities/IHasImages.cs b/MediaBrowser.Controller/Entities/IHasImages.cs
index 4c033dc008..2104bee09f 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 272fa8b3ba..62c13fbb53 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(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 f6d1bb3515..f90119cf3a 100644
--- a/MediaBrowser.Model/IO/IFileSystem.cs
+++ b/MediaBrowser.Model/IO/IFileSystem.cs
@@ -10,6 +10,8 @@ namespace MediaBrowser.Model.IO
///
public interface IFileSystem
{
+ void AddShortcutHandler(IShortcutHandler handler);
+
///
/// Determines whether the specified filename is shortcut.
///
diff --git a/MediaBrowser.Model/Net/IAcceptSocket.cs b/MediaBrowser.Model/Net/IAcceptSocket.cs
index cac23b337f..4262e23901 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 onAccept, Func 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 b10e12813a..f61a94f6ea 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 e9a9f1c5b5..40cef4ec08 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 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 46be614863..4a52b972fc 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 f6af365fdb..72bd62d9f3 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 2c4ccddd72..e68b7ad1d6 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 97eedfa926..cdb9ac51ed 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 44550d2bec..c340d03f5c 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
///
/// Downloads the series zip.
///
- /// The series id.
- /// Type of the identifier.
- /// The series data path.
- /// The last tv database update time.
- /// The preferred metadata language.
- /// The cancellation token.
- /// Task.
- /// seriesId
- 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 EnsureSeriesInfo(Dictionary seriesProviderIds, string preferredMetadataLanguage, CancellationToken cancellationToken)
+ internal async Task EnsureSeriesInfo(Dictionary 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 debd5f539f..d703f7d0d3 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 a5dc691a75..91c064efec 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 649283410f..66851f7e94 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 b41e7607ce..b02c5d6ac0 100644
--- a/MediaBrowser.ServerApplication/MainStartup.cs
+++ b/MediaBrowser.ServerApplication/MainStartup.cs
@@ -331,9 +331,9 @@ namespace MediaBrowser.ServerApplication
/// The options.
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 02916f5559..63e10df769 100644
--- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
+++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
@@ -142,7 +142,6 @@
MainForm.cs
-
diff --git a/MediaBrowser.ServerApplication/WindowsAppHost.cs b/MediaBrowser.ServerApplication/WindowsAppHost.cs
index 915a2fa86c..2d3d8a85b7 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 e3fd5e3493..3f2c5d97e9 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 c7642d5d1e..4f1a17fc0d 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 797684b3e2..11f7749153 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 4a2cf3f5b8..5fe47fc635 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 Create(ILogger logger, IAcceptSocket sock, EndPointListener epl, bool secure, ICertificate cert, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding)
+ public static async Task 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 2b0f75d01f..c2e7acd8e7 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 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();
connections = new Dictionary();
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 182fd2d2a4..58d769f22a 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 9a5862cb94..d8011f05ef 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 a79a187912..ccc0efc55c 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;
+ }
+ }
+ }
}
}