diff options
| author | Luke <luke.pulverenti@gmail.com> | 2016-11-12 02:49:33 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2016-11-12 02:49:33 -0500 |
| commit | d5197eabb0b2ac375cd7610aa7cc4a7e61a88245 (patch) | |
| tree | 0f05bf569a19c75b9654af69d8356c67ff0c2de0 | |
| parent | 67b69ceb9418ef530296238d1776894b675f4c5e (diff) | |
| parent | 6865bb53530abbf703b590dd39cfa27274b03107 (diff) | |
Merge pull request #2283 from MediaBrowser/dev
Dev
29 files changed, 835 insertions, 612 deletions
diff --git a/Emby.Server.Core/ApplicationHost.cs b/Emby.Server.Core/ApplicationHost.cs index 9e0aee325..0c0ef894e 100644 --- a/Emby.Server.Core/ApplicationHost.cs +++ b/Emby.Server.Core/ApplicationHost.cs @@ -554,7 +554,7 @@ namespace Emby.Server.Core ZipClient = new ZipClient(FileSystemManager); RegisterSingleInstance(ZipClient); - RegisterSingleInstance<IHttpResultFactory>(new HttpResultFactory(LogManager, FileSystemManager, JsonSerializer, XmlSerializer)); + RegisterSingleInstance<IHttpResultFactory>(new HttpResultFactory(LogManager, FileSystemManager, JsonSerializer, MemoryStreamFactory)); RegisterSingleInstance<IServerApplicationHost>(this); RegisterSingleInstance<IServerApplicationPaths>(ApplicationPaths); @@ -614,6 +614,9 @@ namespace Emby.Server.Core RegisterSingleInstance<ISearchEngine>(() => new SearchEngine(LogManager, LibraryManager, UserManager)); + 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); HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); RegisterSingleInstance(HttpServer, false); @@ -995,9 +998,6 @@ namespace Emby.Server.Core /// </summary> private void StartServer() { - CertificatePath = GetCertificatePath(true); - Certificate = GetCertificate(CertificatePath); - try { ServerManager.Start(GetUrlPrefixes()); diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 64f498b12..41b7a4622 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using System.Text; using System.Threading.Tasks; using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.HttpServer.SocketSharp; @@ -86,9 +87,7 @@ namespace Emby.Server.Implementations.HttpServer public string GlobalResponse { get; set; } - public override void Configure() - { - var mapExceptionToStatusCode = new Dictionary<Type, int> + readonly Dictionary<Type, int> _mapExceptionToStatusCode = new Dictionary<Type, int> { {typeof (InvalidOperationException), 500}, {typeof (NotImplementedException), 500}, @@ -102,6 +101,8 @@ namespace Emby.Server.Implementations.HttpServer {typeof (NotSupportedException), 500} }; + public override void Configure() + { var requestFilters = _appHost.GetExports<IRequestFilter>().ToList(); foreach (var filter in requestFilters) { @@ -240,12 +241,15 @@ namespace Emby.Server.Implementations.HttpServer return; } - httpRes.StatusCode = 500; + int statusCode; + if (!_mapExceptionToStatusCode.TryGetValue(ex.GetType(), out statusCode)) + { + statusCode = 500; + } + httpRes.StatusCode = statusCode; httpRes.ContentType = "text/html"; - httpRes.Write(ex.Message); - - httpRes.Close(); + Write(httpRes, ex.Message); } catch { @@ -399,7 +403,7 @@ namespace Emby.Server.Implementations.HttpServer { httpRes.StatusCode = 400; httpRes.ContentType = "text/plain"; - httpRes.Write("Invalid host"); + Write(httpRes, "Invalid host"); return; } @@ -453,7 +457,7 @@ namespace Emby.Server.Implementations.HttpServer if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase)) { - httpRes.Write( + Write(httpRes, "<!doctype html><html><head><title>Emby</title></head><body>Please update your Emby bookmark to <a href=\"" + newUrl + "\">" + newUrl + "</a></body></html>"); return; @@ -470,7 +474,7 @@ namespace Emby.Server.Implementations.HttpServer if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase)) { - httpRes.Write( + Write(httpRes, "<!doctype html><html><head><title>Emby</title></head><body>Please update your Emby bookmark to <a href=\"" + newUrl + "\">" + newUrl + "</a></body></html>"); return; @@ -508,7 +512,7 @@ namespace Emby.Server.Implementations.HttpServer { httpRes.StatusCode = 503; httpRes.ContentType = "text/html"; - httpRes.Write(GlobalResponse); + Write(httpRes, GlobalResponse); return; } @@ -518,6 +522,10 @@ namespace Emby.Server.Implementations.HttpServer { await handler.ProcessRequestAsync(httpReq, httpRes, operationName).ConfigureAwait(false); } + else + { + ErrorHandler(new FileNotFoundException(), httpReq); + } } catch (Exception ex) { @@ -538,6 +546,15 @@ namespace Emby.Server.Implementations.HttpServer } } + private void Write(IResponse response, string text) + { + var bOutput = Encoding.UTF8.GetBytes(text); + response.SetContentLength(bOutput.Length); + + var outputStream = response.OutputStream; + outputStream.Write(bOutput, 0, bOutput.Length); + } + public static void RedirectToUrl(IResponse httpRes, string url) { httpRes.StatusCode = 302; diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index bbd556661..f65331ec7 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -34,19 +34,16 @@ namespace Emby.Server.Implementations.HttpServer private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; - private readonly IXmlSerializer _xmlSerializer; + private readonly IMemoryStreamFactory _memoryStreamFactory; /// <summary> /// Initializes a new instance of the <see cref="HttpResultFactory" /> class. /// </summary> - /// <param name="logManager">The log manager.</param> - /// <param name="fileSystem">The file system.</param> - /// <param name="jsonSerializer">The json serializer.</param> - public HttpResultFactory(ILogManager logManager, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer) + public HttpResultFactory(ILogManager logManager, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IMemoryStreamFactory memoryStreamFactory) { _fileSystem = fileSystem; _jsonSerializer = jsonSerializer; - _xmlSerializer = xmlSerializer; + _memoryStreamFactory = memoryStreamFactory; _logger = logManager.GetLogger("HttpResultFactory"); } @@ -59,17 +56,13 @@ namespace Emby.Server.Implementations.HttpServer /// <returns>System.Object.</returns> public object GetResult(object content, string contentType, IDictionary<string, string> responseHeaders = null) { - return GetHttpResult(content, contentType, responseHeaders); + return GetHttpResult(content, contentType, true, responseHeaders); } /// <summary> /// Gets the HTTP result. /// </summary> - /// <param name="content">The content.</param> - /// <param name="contentType">Type of the content.</param> - /// <param name="responseHeaders">The response headers.</param> - /// <returns>IHasHeaders.</returns> - private IHasHeaders GetHttpResult(object content, string contentType, IDictionary<string, string> responseHeaders = null) + private IHasHeaders GetHttpResult(object content, string contentType, bool addCachePrevention, IDictionary<string, string> responseHeaders = null) { IHasHeaders result; @@ -98,7 +91,7 @@ namespace Emby.Server.Implementations.HttpServer } else { - result = new HttpResult(content, contentType); + result = new HttpResult(content, contentType, HttpStatusCode.OK); } } } @@ -107,7 +100,11 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders = new Dictionary<string, string>(); } - responseHeaders["Expires"] = "-1"; + if (addCachePrevention) + { + responseHeaders["Expires"] = "-1"; + } + AddResponseHeaders(result, responseHeaders); return result; @@ -184,8 +181,6 @@ namespace Emby.Server.Implementations.HttpServer /// <returns></returns> public object ToOptimizedResult<T>(IRequest request, T dto) { - request.Response.Dto = dto; - var compressionType = GetCompressionType(request); if (compressionType == null) { @@ -204,6 +199,7 @@ namespace Emby.Server.Implementations.HttpServer } } + // Do not use the memoryStreamFactory here, they don't place nice with compression using (var ms = new MemoryStream()) { using (var compressionStream = GetCompressionStream(ms, compressionType)) @@ -213,12 +209,9 @@ namespace Emby.Server.Implementations.HttpServer var compressedBytes = ms.ToArray(); - var httpResult = new HttpResult(compressedBytes, request.ResponseContentType) - { - Status = request.Response.StatusCode - }; + var httpResult = new StreamWriter(compressedBytes, request.ResponseContentType, _logger); - httpResult.Headers["Content-Length"] = compressedBytes.Length.ToString(UsCulture); + //httpResult.Headers["Content-Length"] = compressedBytes.Length.ToString(UsCulture); httpResult.Headers["Content-Encoding"] = compressionType; return httpResult; @@ -226,6 +219,16 @@ namespace Emby.Server.Implementations.HttpServer } } + private static Stream GetCompressionStream(Stream outputStream, string compressionType) + { + if (compressionType == "deflate") + return new DeflateStream(outputStream, CompressionMode.Compress, true); + if (compressionType == "gzip") + return new GZipStream(outputStream, CompressionMode.Compress, true); + + throw new NotSupportedException(compressionType); + } + public static string GetRealContentType(string contentType) { return contentType == null @@ -233,7 +236,7 @@ namespace Emby.Server.Implementations.HttpServer : contentType.Split(';')[0].ToLower().Trim(); } - public static string SerializeToXmlString(object from) + private string SerializeToXmlString(object from) { using (var ms = new MemoryStream()) { @@ -253,16 +256,6 @@ namespace Emby.Server.Implementations.HttpServer } } - private static Stream GetCompressionStream(Stream outputStream, string compressionType) - { - if (compressionType == "deflate") - return new DeflateStream(outputStream, CompressionMode.Compress); - if (compressionType == "gzip") - return new GZipStream(outputStream, CompressionMode.Compress); - - throw new NotSupportedException(compressionType); - } - /// <summary> /// Gets the optimized result using cache. /// </summary> @@ -358,23 +351,7 @@ namespace Emby.Server.Implementations.HttpServer return hasHeaders; } - IHasHeaders httpResult; - - var stream = result as Stream; - - if (stream != null) - { - httpResult = new StreamWriter(stream, contentType, _logger); - } - else - { - // Otherwise wrap into an HttpResult - httpResult = new HttpResult(result, contentType ?? "text/html", HttpStatusCode.NotModified); - } - - AddResponseHeaders(httpResult, responseHeaders); - - return httpResult; + return GetHttpResult(result, contentType, false, responseHeaders); } /// <summary> @@ -603,7 +580,7 @@ namespace Emby.Server.Implementations.HttpServer { stream.Dispose(); - return GetHttpResult(new byte[] { }, contentType); + return GetHttpResult(new byte[] { }, contentType, true); } return new StreamWriter(stream, contentType, _logger) @@ -630,13 +607,13 @@ namespace Emby.Server.Implementations.HttpServer if (isHeadRequest) { - return GetHttpResult(new byte[] { }, contentType); + return GetHttpResult(new byte[] { }, contentType, true); } - return GetHttpResult(contents, contentType, responseHeaders); + return GetHttpResult(contents, contentType, true, responseHeaders); } - public static byte[] Compress(string text, string compressionType) + private byte[] Compress(string text, string compressionType) { if (compressionType == "deflate") return Deflate(text); @@ -647,12 +624,12 @@ namespace Emby.Server.Implementations.HttpServer throw new NotSupportedException(compressionType); } - public static byte[] Deflate(string text) + private byte[] Deflate(string text) { return Deflate(Encoding.UTF8.GetBytes(text)); } - public static byte[] Deflate(byte[] bytes) + private byte[] Deflate(byte[] bytes) { // In .NET FX incompat-ville, you can't access compressed bytes without closing DeflateStream // Which means we must use MemoryStream since you have to use ToArray() on a closed Stream @@ -666,12 +643,12 @@ namespace Emby.Server.Implementations.HttpServer } } - public static byte[] GZip(string text) + private byte[] GZip(string text) { return GZip(Encoding.UTF8.GetBytes(text)); } - public static byte[] GZip(byte[] buffer) + private byte[] GZip(byte[] buffer) { using (var ms = new MemoryStream()) using (var zipStream = new GZipStream(ms, CompressionMode.Compress)) diff --git a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs index de0b33fe3..a8b115056 100644 --- a/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpResponse.cs @@ -77,18 +77,6 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp get { return _response.OutputStream; } } - public object Dto { get; set; } - - public void Write(string text) - { - var bOutput = System.Text.Encoding.UTF8.GetBytes(text); - _response.ContentLength64 = bOutput.Length; - - var outputStream = _response.OutputStream; - outputStream.Write(bOutput, 0, bOutput.Length); - Close(); - } - public void Close() { if (!this.IsClosed) @@ -110,8 +98,10 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp { try { - response.OutputStream.Flush(); - response.OutputStream.Dispose(); + var outputStream = response.OutputStream; + + outputStream.Flush(); + outputStream.Dispose(); response.Close(); } catch (Exception ex) @@ -120,16 +110,6 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp } } - public void End() - { - Close(); - } - - public void Flush() - { - _response.OutputStream.Flush(); - } - public bool IsClosed { get; diff --git a/Emby.Server.Implementations/HttpServer/StreamWriter.cs b/Emby.Server.Implementations/HttpServer/StreamWriter.cs index 15488abaa..33378949c 100644 --- a/Emby.Server.Implementations/HttpServer/StreamWriter.cs +++ b/Emby.Server.Implementations/HttpServer/StreamWriter.cs @@ -25,6 +25,8 @@ namespace Emby.Server.Implementations.HttpServer /// <value>The source stream.</value> private Stream SourceStream { get; set; } + private byte[] SourceBytes { get; set; } + /// <summary> /// The _options /// </summary> @@ -40,7 +42,6 @@ namespace Emby.Server.Implementations.HttpServer public Action OnComplete { get; set; } public Action OnError { get; set; } - private readonly byte[] _bytes; /// <summary> /// Initializes a new instance of the <see cref="StreamWriter" /> class. @@ -73,14 +74,13 @@ namespace Emby.Server.Implementations.HttpServer /// <param name="contentType">Type of the content.</param> /// <param name="logger">The logger.</param> public StreamWriter(byte[] source, string contentType, ILogger logger) - : this(new MemoryStream(source), contentType, logger) { if (string.IsNullOrEmpty(contentType)) { throw new ArgumentNullException("contentType"); } - _bytes = source; + SourceBytes = source; Logger = logger; Headers["Content-Type"] = contentType; @@ -92,9 +92,11 @@ namespace Emby.Server.Implementations.HttpServer { try { - if (_bytes != null) + var bytes = SourceBytes; + + if (bytes != null) { - await responseStream.WriteAsync(_bytes, 0, _bytes.Length); + await responseStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); } else { diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 224cd056a..5a8be5a49 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -17,6 +17,7 @@ using MediaBrowser.Model.IO; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.IO; using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Net; namespace Emby.Server.Implementations.Images { @@ -146,7 +147,9 @@ namespace Emby.Server.Implementations.Images return ItemUpdateType.None; } - await ProviderManager.SaveImage(item, outputPath, "image/png", imageType, null, false, cancellationToken).ConfigureAwait(false); + var mimeType = MimeTypes.GetMimeType(outputPath); + + await ProviderManager.SaveImage(item, outputPath, mimeType, imageType, null, false, cancellationToken).ConfigureAwait(false); return ItemUpdateType.ImageUpdate; } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index b8b0cb73c..63356a845 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -941,21 +941,20 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV await _liveStreamsSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - var result = _liveStreams.FirstOrDefault(i => string.Equals(i.OriginalStreamId, streamId, StringComparison.OrdinalIgnoreCase)); - - if (result != null && result.EnableStreamSharing) + try { - var openedMediaSource = CloneMediaSource(result.OpenedMediaSource, result.EnableStreamSharing); - result.SharedStreamIds.Add(openedMediaSource.Id); - _liveStreamsSemaphore.Release(); + var result = _liveStreams.FirstOrDefault(i => string.Equals(i.OriginalStreamId, streamId, StringComparison.OrdinalIgnoreCase)); - _logger.Info("Live stream {0} consumer count is now {1}", streamId, result.ConsumerCount); + if (result != null && result.EnableStreamSharing) + { + var openedMediaSource = CloneMediaSource(result.OpenedMediaSource, result.EnableStreamSharing); + result.SharedStreamIds.Add(openedMediaSource.Id); - return new Tuple<LiveStream, MediaSourceInfo, ITunerHost>(result, openedMediaSource, result.TunerHost); - } + _logger.Info("Live stream {0} consumer count is now {1}", streamId, result.ConsumerCount); + + return new Tuple<LiveStream, MediaSourceInfo, ITunerHost>(result, openedMediaSource, result.TunerHost); + } - try - { foreach (var hostInstance in _liveTvManager.TunerHosts) { try @@ -1271,6 +1270,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV try { + var recorder = await GetRecorder().ConfigureAwait(false); + var allMediaSources = await GetChannelStreamMediaSources(timer.ChannelId, CancellationToken.None).ConfigureAwait(false); var liveStreamInfo = await GetChannelStreamInternal(timer.ChannelId, allMediaSources[0].Id, CancellationToken.None) @@ -1282,8 +1283,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV // HDHR doesn't seem to release the tuner right away after first probing with ffmpeg //await Task.Delay(3000, cancellationToken).ConfigureAwait(false); - var recorder = await GetRecorder().ConfigureAwait(false); - recordPath = recorder.GetOutputPath(mediaStreamInfo, recordPath); recordPath = EnsureFileUnique(recordPath, timer.Id); diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 960fe0739..300b67ae2 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -566,6 +566,23 @@ namespace Emby.Server.Implementations.Session } } + private BaseItem GetNowPlayingItem(SessionInfo session, string itemId) + { + var idGuid = new Guid(itemId); + + var item = session.FullNowPlayingItem; + if (item != null && item.Id == idGuid) + { + return item; + } + + item = _libraryManager.GetItemById(itemId); + + session.FullNowPlayingItem = item; + + return item; + } + /// <summary> /// Used to report that playback has started for an item /// </summary> @@ -583,7 +600,7 @@ namespace Emby.Server.Implementations.Session var libraryItem = string.IsNullOrWhiteSpace(info.ItemId) ? null - : _libraryManager.GetItemById(new Guid(info.ItemId)); + : GetNowPlayingItem(session, info.ItemId); await UpdateNowPlayingItem(session, info, libraryItem).ConfigureAwait(false); @@ -669,7 +686,7 @@ namespace Emby.Server.Implementations.Session var libraryItem = string.IsNullOrWhiteSpace(info.ItemId) ? null - : _libraryManager.GetItemById(new Guid(info.ItemId)); + : GetNowPlayingItem(session, info.ItemId); await UpdateNowPlayingItem(session, info, libraryItem).ConfigureAwait(false); @@ -773,7 +790,7 @@ namespace Emby.Server.Implementations.Session var libraryItem = string.IsNullOrWhiteSpace(info.ItemId) ? null - : _libraryManager.GetItemById(new Guid(info.ItemId)); + : GetNowPlayingItem(session, info.ItemId); // Normalize if (string.IsNullOrWhiteSpace(info.MediaSourceId)) @@ -1782,18 +1799,18 @@ namespace Emby.Server.Implementations.Session throw new ArgumentNullException("itemId"); } - var item = _libraryManager.GetItemById(new Guid(itemId)); + //var item = _libraryManager.GetItemById(new Guid(itemId)); - var info = GetItemInfo(item, null, null); + //var info = GetItemInfo(item, null, null); - ReportNowViewingItem(sessionId, info); + //ReportNowViewingItem(sessionId, info); } public void ReportNowViewingItem(string sessionId, BaseItemInfo item) { - var session = GetSession(sessionId); + //var session = GetSession(sessionId); - session.NowViewingItem = item; + //session.NowViewingItem = item; } public void ReportTranscodingInfo(string deviceId, TranscodingInfo info) diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index df491ce85..c1a7347b5 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -142,6 +142,7 @@ <Compile Include="System\ActivityLogWebSocketListener.cs" /> <Compile Include="System\SystemService.cs" /> <Compile Include="Movies\TrailersService.cs" /> + <Compile Include="TestService.cs" /> <Compile Include="TvShowsService.cs" /> <Compile Include="UserLibrary\ArtistsService.cs" /> <Compile Include="UserLibrary\BaseItemsByNameService.cs" /> diff --git a/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs b/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs index c4a25a926..6bb3b6b80 100644 --- a/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs +++ b/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs @@ -1,6 +1,8 @@ using MediaBrowser.Common.Net; using System.Collections.Generic; using System.IO; +using System.Threading; +using System.Threading.Tasks; using MediaBrowser.Model.Services; namespace MediaBrowser.Api.Playback @@ -8,7 +10,7 @@ namespace MediaBrowser.Api.Playback /// <summary> /// Class StaticRemoteStreamWriter /// </summary> - public class StaticRemoteStreamWriter : IStreamWriter, IHasHeaders + public class StaticRemoteStreamWriter : IAsyncStreamWriter, IHasHeaders { /// <summary> /// The _input stream @@ -34,15 +36,11 @@ namespace MediaBrowser.Api.Playback get { return _options; } } - /// <summary> - /// Writes to. - /// </summary> - /// <param name="responseStream">The response stream.</param> - public void WriteTo(Stream responseStream) + public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) { using (_response) { - _response.Content.CopyTo(responseStream, 819200); + await _response.Content.CopyToAsync(responseStream, 81920, cancellationToken).ConfigureAwait(false); } } } diff --git a/MediaBrowser.Api/TestService.cs b/MediaBrowser.Api/TestService.cs new file mode 100644 index 000000000..5340b816c --- /dev/null +++ b/MediaBrowser.Api/TestService.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Model.Services; + +namespace MediaBrowser.Api +{ + [Route("/Test/String", "GET")] + public class GetString + { + } + + [Route("/Test/OptimizedString", "GET")] + public class GetOptimizedString + { + } + + [Route("/Test/Bytes", "GET")] + public class GetBytes + { + } + + [Route("/Test/OptimizedBytes", "GET")] + public class GetOptimizedBytes + { + } + + [Route("/Test/Stream", "GET")] + public class GetStream + { + } + + [Route("/Test/OptimizedStream", "GET")] + public class GetOptimizedStream + { + } + + [Route("/Test/BytesWithContentType", "GET")] + public class GetBytesWithContentType + { + } + + public class TestService : BaseApiService + { + public object Get(GetString request) + { + return "Welcome to Emby!"; + } + public object Get(GetOptimizedString request) + { + return ToOptimizedResult("Welcome to Emby!"); + } + public object Get(GetBytes request) + { + return Encoding.UTF8.GetBytes("Welcome to Emby!"); + } + public object Get(GetOptimizedBytes request) + { + return ToOptimizedResult(Encoding.UTF8.GetBytes("Welcome to Emby!")); + } + public object Get(GetBytesWithContentType request) + { + return ApiEntryPoint.Instance.ResultFactory.GetResult(Encoding.UTF8.GetBytes("Welcome to Emby!"), "text/html"); + } + public object Get(GetStream request) + { + return new MemoryStream(Encoding.UTF8.GetBytes("Welcome to Emby!")); + } + public object Get(GetOptimizedStream request) + { + return ToOptimizedResult(new MemoryStream(Encoding.UTF8.GetBytes("Welcome to Emby!"))); + } + } +} diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index b3e82f925..343b15a04 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -3,6 +3,7 @@ using MediaBrowser.Model.Session; using System; using System.Collections.Generic; using System.Linq; +using MediaBrowser.Controller.Entities; namespace MediaBrowser.Controller.Session { @@ -106,7 +107,9 @@ namespace MediaBrowser.Controller.Session /// </summary> /// <value>The now playing item.</value> public BaseItemInfo NowPlayingItem { get; set; } - + + public BaseItem FullNowPlayingItem { get; set; } + /// <summary> /// Gets or sets the device id. /// </summary> diff --git a/MediaBrowser.Model/Services/IHttpResult.cs b/MediaBrowser.Model/Services/IHttpResult.cs index 36ffeb284..fcb137c6b 100644 --- a/MediaBrowser.Model/Services/IHttpResult.cs +++ b/MediaBrowser.Model/Services/IHttpResult.cs @@ -20,11 +20,6 @@ namespace MediaBrowser.Model.Services HttpStatusCode StatusCode { get; set; } /// <summary> - /// The HTTP Status Description - /// </summary> - string StatusDescription { get; set; } - - /// <summary> /// The HTTP Response ContentType /// </summary> string ContentType { get; set; } diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index 5a4d24007..e9a9f1c5b 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -137,47 +137,18 @@ namespace MediaBrowser.Model.Services Stream OutputStream { get; } /// <summary> - /// The Response DTO - /// </summary> - object Dto { get; set; } - - /// <summary> - /// Write once to the Response Stream then close it. - /// </summary> - /// <param name="text"></param> - void Write(string text); - - /// <summary> - /// Buffer the Response OutputStream so it can be written in 1 batch - /// </summary> - bool UseBufferedStream { get; set; } - - /// <summary> /// Signal that this response has been handled and no more processing should be done. /// When used in a request or response filter, no more filters or processing is done on this request. /// </summary> void Close(); /// <summary> - /// Calls Response.End() on ASP.NET HttpResponse otherwise is an alias for Close(). - /// Useful when you want to prevent ASP.NET to provide it's own custom error page. - /// </summary> - void End(); - - /// <summary> - /// Response.Flush() and OutputStream.Flush() seem to have different behaviour in ASP.NET - /// </summary> - void Flush(); - - /// <summary> /// Gets a value indicating whether this instance is closed. /// </summary> bool IsClosed { get; } void SetContentLength(long contentLength); - bool KeepAlive { get; set; } - //Add Metadata to Response Dictionary<string, object> Items { get; } } diff --git a/ServiceStack/Host/ContentTypes.cs b/ServiceStack/Host/ContentTypes.cs index 22fdc3e50..f7734a36b 100644 --- a/ServiceStack/Host/ContentTypes.cs +++ b/ServiceStack/Host/ContentTypes.cs @@ -13,37 +13,23 @@ namespace ServiceStack.Host public void SerializeToStream(IRequest req, object response, Stream responseStream) { var contentType = req.ResponseContentType; - var serializer = GetResponseSerializer(contentType); - if (serializer == null) - throw new NotSupportedException("ContentType not supported: " + contentType); - - var httpRes = new HttpResponseStreamWrapper(responseStream, req) - { - Dto = req.Response.Dto - }; - serializer(req, response, httpRes); - } - - public Action<IRequest, object, IResponse> GetResponseSerializer(string contentType) - { var serializer = GetStreamSerializer(contentType); - if (serializer == null) return null; - return (httpReq, dto, httpRes) => serializer(httpReq, dto, httpRes.OutputStream); + serializer(response, responseStream); } - public Action<IRequest, object, Stream> GetStreamSerializer(string contentType) + public static Action<object, Stream> GetStreamSerializer(string contentType) { switch (GetRealContentType(contentType)) { case "application/xml": case "text/xml": case "text/xml; charset=utf-8": //"text/xml; charset=utf-8" also matches xml - return (r, o, s) => ServiceStackHost.Instance.SerializeToXml(o, s); + return (o, s) => ServiceStackHost.Instance.SerializeToXml(o, s); case "application/json": case "text/json": - return (r, o, s) => ServiceStackHost.Instance.SerializeToJson(o, s); + return (o, s) => ServiceStackHost.Instance.SerializeToJson(o, s); } return null; diff --git a/ServiceStack/Host/HttpResponseStreamWrapper.cs b/ServiceStack/Host/HttpResponseStreamWrapper.cs deleted file mode 100644 index 33038da72..000000000 --- a/ServiceStack/Host/HttpResponseStreamWrapper.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Text; -using MediaBrowser.Model.Services; - -namespace ServiceStack.Host -{ - public class HttpResponseStreamWrapper : IHttpResponse - { - private static readonly UTF8Encoding UTF8EncodingWithoutBom = new UTF8Encoding(false); - - public HttpResponseStreamWrapper(Stream stream, IRequest request) - { - this.OutputStream = stream; - this.Request = request; - this.Headers = new Dictionary<string, string>(); - this.Items = new Dictionary<string, object>(); - } - - public Dictionary<string, string> Headers { get; set; } - - public object OriginalResponse - { - get { return null; } - } - - public IRequest Request { get; private set; } - - public int StatusCode { set; get; } - public string StatusDescription { set; get; } - public string ContentType { get; set; } - - public void AddHeader(string name, string value) - { - this.Headers[name] = value; - } - - public string GetHeader(string name) - { - return this.Headers[name]; - } - - public void Redirect(string url) - { - this.Headers["Location"] = url; - } - - public Stream OutputStream { get; private set; } - - public object Dto { get; set; } - - public void Write(string text) - { - var bytes = UTF8EncodingWithoutBom.GetBytes(text); - OutputStream.Write(bytes, 0, bytes.Length); - } - - public bool UseBufferedStream { get; set; } - - public void Close() - { - if (IsClosed) return; - - OutputStream.Dispose(); - IsClosed = true; - } - - public void End() - { - Close(); - } - - public void Flush() - { - OutputStream.Flush(); - } - - public bool IsClosed { get; private set; } - - public void SetContentLength(long contentLength) {} - - public bool KeepAlive { get; set; } - - public Dictionary<string, object> Items { get; private set; } - - public void SetCookie(Cookie cookie) - { - } - - public void ClearCookies() - { - } - } -}
\ No newline at end of file diff --git a/ServiceStack/Host/ServiceController.cs b/ServiceStack/Host/ServiceController.cs index 703f06365..7eb1253b3 100644 --- a/ServiceStack/Host/ServiceController.cs +++ b/ServiceStack/Host/ServiceController.cs @@ -210,9 +210,6 @@ namespace ServiceStack.Host //Executes the service and returns the result var response = await ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetOperationName()).ConfigureAwait(false); - if (req.Response.Dto == null) - req.Response.Dto = response; - return response; } } diff --git a/ServiceStack/HttpResponseExtensionsInternal.cs b/ServiceStack/HttpResponseExtensionsInternal.cs index 1195f63ca..318d62429 100644 --- a/ServiceStack/HttpResponseExtensionsInternal.cs +++ b/ServiceStack/HttpResponseExtensionsInternal.cs @@ -6,6 +6,7 @@ using System.IO; using System.Net; using System.Threading.Tasks; using System.Collections.Generic; +using System.Text; using System.Threading; using MediaBrowser.Model.Services; using ServiceStack.Host; @@ -33,8 +34,11 @@ namespace ServiceStack var stream = result as Stream; if (stream != null) { - WriteTo(stream, response.OutputStream); - return true; + using (stream) + { + await stream.CopyToAsync(response.OutputStream).ConfigureAwait(false); + return true; + } } var bytes = result as byte[]; @@ -43,35 +47,13 @@ namespace ServiceStack response.ContentType = "application/octet-stream"; response.SetContentLength(bytes.Length); - response.OutputStream.Write(bytes, 0, bytes.Length); + await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); return true; } return false; } - public static long WriteTo(Stream inStream, Stream outStream) - { - var memoryStream = inStream as MemoryStream; - if (memoryStream != null) - { - memoryStream.WriteTo(outStream); - return memoryStream.Position; - } - - var data = new byte[4096]; - long total = 0; - int bytesRead; - - while ((bytesRead = inStream.Read(data, 0, data.Length)) > 0) - { - outStream.Write(data, 0, bytesRead); - total += bytesRead; - } - - return total; - } - /// <summary> /// End a ServiceStack Request with no content /// </summary> @@ -85,7 +67,7 @@ namespace ServiceStack httpRes.SetContentLength(0); } - public static Task WriteToResponse(this IResponse httpRes, MediaBrowser.Model.Services.IRequest httpReq, object result) + public static Task WriteToResponse(this IResponse httpRes, IRequest httpReq, object result) { if (result == null) { @@ -98,19 +80,10 @@ namespace ServiceStack { httpResult.RequestContext = httpReq; httpReq.ResponseContentType = httpResult.ContentType ?? httpReq.ResponseContentType; - var httpResSerializer = ContentTypes.Instance.GetResponseSerializer(httpReq.ResponseContentType); - return httpRes.WriteToResponse(httpResult, httpResSerializer, httpReq); + return httpRes.WriteToResponseInternal(httpResult, httpReq); } - var serializer = ContentTypes.Instance.GetResponseSerializer(httpReq.ResponseContentType); - return httpRes.WriteToResponse(result, serializer, httpReq); - } - - private static object GetDto(object response) - { - if (response == null) return null; - var httpResult = response as IHttpResult; - return httpResult != null ? httpResult.Response : response; + return httpRes.WriteToResponseInternal(result, httpReq); } /// <summary> @@ -119,17 +92,11 @@ namespace ServiceStack /// </summary> /// <param name="response">The response.</param> /// <param name="result">Whether or not it was implicity handled by ServiceStack's built-in handlers.</param> - /// <param name="defaultAction">The default action.</param> /// <param name="request">The serialization context.</param> /// <returns></returns> - public static async Task WriteToResponse(this IResponse response, object result, Action<IRequest, object, IResponse> defaultAction, MediaBrowser.Model.Services.IRequest request) + private static async Task WriteToResponseInternal(this IResponse response, object result, IRequest request) { var defaultContentType = request.ResponseContentType; - if (result == null) - { - response.EndRequestWithNoContent(); - return; - } var httpResult = result as IHttpResult; if (httpResult != null) @@ -137,10 +104,8 @@ namespace ServiceStack if (httpResult.RequestContext == null) httpResult.RequestContext = request; - response.Dto = response.Dto ?? GetDto(httpResult); - response.StatusCode = httpResult.Status; - response.StatusDescription = httpResult.StatusDescription ?? httpResult.StatusCode.ToString(); + response.StatusDescription = httpResult.StatusCode.ToString(); if (string.IsNullOrEmpty(httpResult.ContentType)) { httpResult.ContentType = defaultContentType; @@ -159,21 +124,12 @@ namespace ServiceStack } } } - else - { - response.Dto = result; - } - /* Mono Error: Exception: Method not found: 'System.Web.HttpResponse.get_Headers' */ var responseOptions = result as IHasHeaders; if (responseOptions != null) { - //Reserving options with keys in the format 'xx.xxx' (No Http headers contain a '.' so its a safe restriction) - const string reservedOptions = "."; - foreach (var responseHeaders in responseOptions.Headers) { - if (responseHeaders.Key.Contains(reservedOptions)) continue; if (responseHeaders.Key == "Content-Length") { response.SetContentLength(long.Parse(responseHeaders.Value)); @@ -196,42 +152,41 @@ namespace ServiceStack response.ContentType += "; charset=utf-8"; } - var disposableResult = result as IDisposable; var writeToOutputStreamResult = await WriteToOutputStream(response, result).ConfigureAwait(false); if (writeToOutputStreamResult) { - response.Flush(); //required for Compression - if (disposableResult != null) disposableResult.Dispose(); return; } - if (httpResult != null) - result = httpResult.Response; - var responseText = result as string; if (responseText != null) { if (response.ContentType == null || response.ContentType == "text/html") response.ContentType = defaultContentType; - response.Write(responseText); + var bytes = Encoding.UTF8.GetBytes(responseText); + await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); return; } - if (defaultAction == null) - { - throw new ArgumentNullException("defaultAction", String.Format( - "As result '{0}' is not a supported responseType, a defaultAction must be supplied", - (result != null ? result.GetType().GetOperationName() : ""))); - } + await WriteObject(request, result, response).ConfigureAwait(false); + } + public static async Task WriteObject(IRequest request, object result, IResponse response) + { + var contentType = request.ResponseContentType; + var serializer = ContentTypes.GetStreamSerializer(contentType); + + using (var ms = new MemoryStream()) + { + serializer(result, ms); - if (result != null) - defaultAction(request, result, response); + ms.Position = 0; + response.SetContentLength(ms.Length); + await ms.CopyToAsync(response.OutputStream).ConfigureAwait(false); + } - if (disposableResult != null) - disposableResult.Dispose(); + //serializer(result, outputStream); } - } } diff --git a/ServiceStack/HttpResult.cs b/ServiceStack/HttpResult.cs index 23a5cdffb..3f86ffdf7 100644 --- a/ServiceStack/HttpResult.cs +++ b/ServiceStack/HttpResult.cs @@ -13,31 +13,7 @@ namespace ServiceStack public class HttpResult : IHttpResult, IAsyncStreamWriter { - public HttpResult() - : this((object)null, null) - { - } - - public HttpResult(object response) - : this(response, null) - { - } - - public HttpResult(object response, string contentType) - : this(response, contentType, HttpStatusCode.OK) - { - } - - public HttpResult(HttpStatusCode statusCode, string statusDescription) - : this() - { - StatusCode = statusCode; - StatusDescription = statusDescription; - } - - public HttpResult(object response, HttpStatusCode statusCode) - : this(response, null, statusCode) - { } + public object Response { get; set; } public HttpResult(object response, string contentType, HttpStatusCode statusCode) { @@ -49,102 +25,12 @@ namespace ServiceStack this.StatusCode = statusCode; } - public HttpResult(Stream responseStream, string contentType) - : this(null, contentType, HttpStatusCode.OK) - { - this.ResponseStream = responseStream; - } - - public HttpResult(string responseText, string contentType) - : this(null, contentType, HttpStatusCode.OK) - { - this.ResponseText = responseText; - } - - public HttpResult(byte[] responseBytes, string contentType) - : this(null, contentType, HttpStatusCode.OK) - { - this.ResponseStream = new MemoryStream(responseBytes); - } - - public string ResponseText { get; private set; } - - public Stream ResponseStream { get; private set; } - public string ContentType { get; set; } public IDictionary<string, string> Headers { get; private set; } public List<Cookie> Cookies { get; private set; } - public string ETag { get; set; } - - public TimeSpan? Age { get; set; } - - public TimeSpan? MaxAge { get; set; } - - public DateTime? Expires { get; set; } - - public DateTime? LastModified { get; set; } - - public Func<IDisposable> ResultScope { get; set; } - - public string Location - { - set - { - if (StatusCode == HttpStatusCode.OK) - StatusCode = HttpStatusCode.Redirect; - - this.Headers["Location"] = value; - } - } - - public void SetPermanentCookie(string name, string value) - { - SetCookie(name, value, DateTime.UtcNow.AddYears(20), null); - } - - public void SetPermanentCookie(string name, string value, string path) - { - SetCookie(name, value, DateTime.UtcNow.AddYears(20), path); - } - - public void SetSessionCookie(string name, string value) - { - SetSessionCookie(name, value, null); - } - - public void SetSessionCookie(string name, string value, string path) - { - path = path ?? "/"; - this.Headers["Set-Cookie"] = string.Format("{0}={1};path=" + path, name, value); - } - - public void SetCookie(string name, string value, TimeSpan expiresIn, string path) - { - var expiresAt = DateTime.UtcNow.Add(expiresIn); - SetCookie(name, value, expiresAt, path); - } - - public void SetCookie(string name, string value, DateTime expiresAt, string path, bool secure = false, bool httpOnly = false) - { - path = path ?? "/"; - var cookie = string.Format("{0}={1};expires={2};path={3}", name, value, expiresAt.ToString("R"), path); - if (secure) - cookie += ";Secure"; - if (httpOnly) - cookie += ";HttpOnly"; - - this.Headers["Set-Cookie"] = cookie; - } - - public void DeleteCookie(string name) - { - var cookie = string.Format("{0}=;expires={1};path=/", name, DateTime.UtcNow.AddDays(-1).ToString("R")); - this.Headers["Set-Cookie"] = cookie; - } - public int Status { get; set; } public HttpStatusCode StatusCode @@ -153,75 +39,12 @@ namespace ServiceStack set { Status = (int)value; } } - public string StatusDescription { get; set; } - - public object Response { get; set; } - - public MediaBrowser.Model.Services.IRequest RequestContext { get; set; } - - public string View { get; set; } - - public string Template { get; set; } - - public int PaddingLength { get; set; } + public IRequest RequestContext { get; set; } public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) { - try - { - await WriteToInternalAsync(responseStream, cancellationToken).ConfigureAwait(false); - responseStream.Flush(); - } - finally - { - DisposeStream(); - } - } - - public static Task WriteTo(Stream inStream, Stream outStream, CancellationToken cancellationToken) - { - var memoryStream = inStream as MemoryStream; - if (memoryStream != null) - { - memoryStream.WriteTo(outStream); - return Task.FromResult(true); - } - - return inStream.CopyToAsync(outStream, 81920, cancellationToken); - } - - public async Task WriteToInternalAsync(Stream responseStream, CancellationToken cancellationToken) - { var response = RequestContext != null ? RequestContext.Response : null; - if (this.ResponseStream != null) - { - if (response != null) - { - var ms = ResponseStream as MemoryStream; - if (ms != null) - { - response.SetContentLength(ms.Length); - - await ms.CopyToAsync(responseStream, 81920, cancellationToken).ConfigureAwait(false); - return; - } - } - - await WriteTo(this.ResponseStream, responseStream, cancellationToken).ConfigureAwait(false); - return; - } - - if (this.ResponseText != null) - { - var bytes = Encoding.UTF8.GetBytes(this.ResponseText); - if (response != null) - response.SetContentLength(bytes.Length); - - await responseStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); - return; - } - var bytesResponse = this.Response as byte[]; if (bytesResponse != null) { @@ -232,19 +55,7 @@ namespace ServiceStack return; } - ContentTypes.Instance.SerializeToStream(this.RequestContext, this.Response, responseStream); - } - - private void DisposeStream() - { - try - { - if (ResponseStream != null) - { - this.ResponseStream.Dispose(); - } - } - catch { /*ignore*/ } + await HttpResponseExtensionsInternal.WriteObject(this.RequestContext, this.Response, response).ConfigureAwait(false); } } } diff --git a/ServiceStack/ServiceStack.csproj b/ServiceStack/ServiceStack.csproj index 3402339a6..5413d4e55 100644 --- a/ServiceStack/ServiceStack.csproj +++ b/ServiceStack/ServiceStack.csproj @@ -73,7 +73,6 @@ <Compile Include="Host\ContentTypes.cs" /> <Compile Include="ReflectionExtensions.cs" /> <Compile Include="StringMapTypeDeserializer.cs" /> - <Compile Include="Host\HttpResponseStreamWrapper.cs" /> <Compile Include="HttpResult.cs" /> <Compile Include="ServiceStackHost.cs" /> <Compile Include="ServiceStackHost.Runtime.cs" /> diff --git a/SocketHttpListener.Portable/Ext.cs b/SocketHttpListener.Portable/Ext.cs index 303263d0b..87f0887ed 100644 --- a/SocketHttpListener.Portable/Ext.cs +++ b/SocketHttpListener.Portable/Ext.cs @@ -168,12 +168,6 @@ namespace SocketHttpListener : null; } - internal static void Close(this HttpListenerResponse response, HttpStatusCode code) - { - response.StatusCode = (int)code; - response.OutputStream.Dispose(); - } - internal static Stream Compress(this Stream stream, CompressionMethod method) { return method == CompressionMethod.Deflate diff --git a/SocketHttpListener.Portable/Net/EndPointListener.cs b/SocketHttpListener.Portable/Net/EndPointListener.cs index b50660ad0..52385e2ba 100644 --- a/SocketHttpListener.Portable/Net/EndPointListener.cs +++ b/SocketHttpListener.Portable/Net/EndPointListener.cs @@ -119,7 +119,6 @@ namespace SocketHttpListener.Net if (listener == null) return false; - context.Listener = listener; context.Connection.Prefix = prefix; return true; } @@ -129,7 +128,7 @@ namespace SocketHttpListener.Net if (context == null || context.Request == null) return; - context.Listener.UnregisterContext(context); + listener.UnregisterContext(context); } HttpListener SearchListener(Uri uri, out ListenerPrefix prefix) diff --git a/SocketHttpListener.Portable/Net/HttpConnection.cs b/SocketHttpListener.Portable/Net/HttpConnection.cs index d31da4132..db34c4218 100644 --- a/SocketHttpListener.Portable/Net/HttpConnection.cs +++ b/SocketHttpListener.Portable/Net/HttpConnection.cs @@ -1,7 +1,6 @@ using System; using System.IO; using System.Text; -using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.IO; @@ -210,12 +209,7 @@ namespace SocketHttpListener.Net // TODO: can we get this stream before reading the input? if (o_stream == null) { - HttpListener listener = context.Listener; - - if (listener == null) - return new ResponseStream(stream, context.Response, true, _memoryStreamFactory, _textEncoding); - - o_stream = new ResponseStream(stream, context.Response, listener.IgnoreWriteExceptions, _memoryStreamFactory, _textEncoding); + o_stream = new ResponseStream(stream, context.Response, _memoryStreamFactory, _textEncoding); } return o_stream; } @@ -257,7 +251,7 @@ namespace SocketHttpListener.Net Close(true); return; } - HttpListener listener = context.Listener; + HttpListener listener = epl.Listener; if (last_listener != listener) { RemoveConnection(); diff --git a/SocketHttpListener.Portable/Net/HttpListener.cs b/SocketHttpListener.Portable/Net/HttpListener.cs index 83660100a..2b0f75d01 100644 --- a/SocketHttpListener.Portable/Net/HttpListener.cs +++ b/SocketHttpListener.Portable/Net/HttpListener.cs @@ -28,7 +28,6 @@ namespace SocketHttpListener.Net HttpListenerPrefixCollection prefixes; AuthenticationSchemeSelector auth_selector; string realm; - bool ignore_write_exceptions; bool unsafe_ntlm_auth; bool listening; bool disposed; @@ -92,16 +91,6 @@ namespace SocketHttpListener.Net } } - public bool IgnoreWriteExceptions - { - get { return ignore_write_exceptions; } - set - { - CheckDisposed(); - ignore_write_exceptions = value; - } - } - public bool IsListening { get { return listening; } diff --git a/SocketHttpListener.Portable/Net/HttpListenerContext.cs b/SocketHttpListener.Portable/Net/HttpListenerContext.cs index 84c6a8c19..182fd2d2a 100644 --- a/SocketHttpListener.Portable/Net/HttpListenerContext.cs +++ b/SocketHttpListener.Portable/Net/HttpListenerContext.cs @@ -18,7 +18,6 @@ namespace SocketHttpListener.Net HttpConnection cnc; string error; int err_status = 400; - internal HttpListener Listener; private readonly ILogger _logger; private readonly ICryptoProvider _cryptoProvider; private readonly IMemoryStreamFactory _memoryStreamFactory; diff --git a/SocketHttpListener.Portable/Net/ResponseStream.cs b/SocketHttpListener.Portable/Net/ResponseStream.cs index 6ecbf9742..07788ea41 100644 --- a/SocketHttpListener.Portable/Net/ResponseStream.cs +++ b/SocketHttpListener.Portable/Net/ResponseStream.cs @@ -17,17 +17,15 @@ namespace SocketHttpListener.Net class ResponseStream : Stream { HttpListenerResponse response; - bool ignore_errors; bool disposed; bool trailer_sent; Stream stream; private readonly IMemoryStreamFactory _memoryStreamFactory; private readonly ITextEncoding _textEncoding; - internal ResponseStream(Stream stream, HttpListenerResponse response, bool ignore_errors, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding) + internal ResponseStream(Stream stream, HttpListenerResponse response, IMemoryStreamFactory memoryStreamFactory, ITextEncoding textEncoding) { this.response = response; - this.ignore_errors = ignore_errors; _memoryStreamFactory = memoryStreamFactory; _textEncoding = textEncoding; this.stream = stream; @@ -130,18 +128,7 @@ namespace SocketHttpListener.Net internal void InternalWrite(byte[] buffer, int offset, int count) { - if (ignore_errors) - { - try - { - stream.Write(buffer, offset, count); - } - catch { } - } - else - { - stream.Write(buffer, offset, count); - } + stream.Write(buffer, offset, count); } public override void Write(byte[] buffer, int offset, int count) @@ -214,23 +201,13 @@ namespace SocketHttpListener.Net InternalWrite(bytes, 0, bytes.Length); } - try - { - if (count > 0) - { - await stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); - } - - if (response.SendChunked) - stream.Write(crlf, 0, 2); - } - catch + if (count > 0) { - if (!ignore_errors) - { - throw; - } + await stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); } + + if (response.SendChunked) + stream.Write(crlf, 0, 2); } //public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, diff --git a/SocketHttpListener.Portable/Net/WebSockets/HttpListenerWebSocketContext.cs b/SocketHttpListener.Portable/Net/WebSockets/HttpListenerWebSocketContext.cs index 426e15ecd..034ac17d2 100644 --- a/SocketHttpListener.Portable/Net/WebSockets/HttpListenerWebSocketContext.cs +++ b/SocketHttpListener.Portable/Net/WebSockets/HttpListenerWebSocketContext.cs @@ -322,7 +322,8 @@ namespace SocketHttpListener.Net.WebSockets internal void Close(HttpStatusCode code) { - _context.Response.Close(code); + _context.Response.StatusCode = (int)code; + _context.Response.OutputStream.Dispose(); } #endregion diff --git a/SocketHttpListener.Portable/SocketHttpListener.Portable.csproj b/SocketHttpListener.Portable/SocketHttpListener.Portable.csproj index f7b3a643c..ee902462b 100644 --- a/SocketHttpListener.Portable/SocketHttpListener.Portable.csproj +++ b/SocketHttpListener.Portable/SocketHttpListener.Portable.csproj @@ -95,9 +95,8 @@ </ItemGroup> <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" /> <PropertyGroup> - <PostBuildEvent>if $(ConfigurationName) == Release ( -xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i -)</PostBuildEvent> + <PostBuildEvent> + </PostBuildEvent> </PropertyGroup> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. diff --git a/src/Emby.Server/Program.cs b/src/Emby.Server/Program.cs index 06aed1aeb..d364e7284 100644 --- a/src/Emby.Server/Program.cs +++ b/src/Emby.Server/Program.cs @@ -1,14 +1,592 @@ -using System; -using System.Collections.Generic; +using MediaBrowser.Model.Logging; +using MediaBrowser.Server.Implementations; +using MediaBrowser.Server.Startup.Common; +using MediaBrowser.ServerApplication.Native; +using MediaBrowser.ServerApplication.Splash; +using MediaBrowser.ServerApplication.Updates; +using Microsoft.Win32; +using System; +using System.Configuration.Install; +using System.Diagnostics; +using System.IO; using System.Linq; +using System.Management; +using System.Runtime.InteropServices; +using System.ServiceProcess; +using System.Text; +using System.Threading; using System.Threading.Tasks; +using System.Windows.Forms; +using Emby.Common.Implementations.EnvironmentInfo; +using Emby.Common.Implementations.IO; +using Emby.Common.Implementations.Logging; +using Emby.Common.Implementations.Networking; +using Emby.Common.Implementations.Security; +using Emby.Server.Core; +using Emby.Server.Core.Browser; +using Emby.Server.Implementations.IO; +using ImageMagickSharp; +using MediaBrowser.Common.Net; +using MediaBrowser.Server.Startup.Common.IO; namespace Emby.Server { public class Program { + private static ApplicationHost _appHost; + + private static ILogger _logger; + + private static bool _isRunningAsService = false; + private static bool _canRestartService = false; + private static bool _appHostDisposed; + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool SetDllDirectory(string lpPathName); + + /// <summary> + /// Defines the entry point of the application. + /// </summary> public static void Main(string[] args) { + var options = new StartupOptions(); + _isRunningAsService = options.ContainsOption("-service"); + + if (_isRunningAsService) + { + //_canRestartService = CanRestartWindowsService(); + } + + var currentProcess = Process.GetCurrentProcess(); + + var applicationPath = currentProcess.MainModule.FileName; + var architecturePath = Path.Combine(Path.GetDirectoryName(applicationPath), Environment.Is64BitProcess ? "x64" : "x86"); + + Wand.SetMagickCoderModulePath(architecturePath); + + var success = SetDllDirectory(architecturePath); + + var appPaths = CreateApplicationPaths(applicationPath, _isRunningAsService); + + var logManager = new NlogManager(appPaths.LogDirectoryPath, "server"); + logManager.ReloadLogger(LogSeverity.Debug); + logManager.AddConsoleOutput(); + + var logger = _logger = logManager.GetLogger("Main"); + + ApplicationHost.LogEnvironmentInfo(logger, appPaths, true); + + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + + if (IsAlreadyRunning(applicationPath, currentProcess)) + { + logger.Info("Shutting down because another instance of Emby Server is already running."); + return; + } + + if (PerformUpdateIfNeeded(appPaths, logger)) + { + logger.Info("Exiting to perform application update."); + return; + } + + try + { + RunApplication(appPaths, logManager, _isRunningAsService, options); + } + finally + { + OnServiceShutdown(); + } + } + + /// <summary> + /// Determines whether [is already running] [the specified current process]. + /// </summary> + /// <param name="applicationPath">The application path.</param> + /// <param name="currentProcess">The current process.</param> + /// <returns><c>true</c> if [is already running] [the specified current process]; otherwise, <c>false</c>.</returns> + private static bool IsAlreadyRunning(string applicationPath, Process currentProcess) + { + var duplicate = Process.GetProcesses().FirstOrDefault(i => + { + try + { + if (currentProcess.Id == i.Id) + { + return false; + } + } + catch (Exception) + { + return false; + } + + try + { + //_logger.Info("Module: {0}", i.MainModule.FileName); + if (string.Equals(applicationPath, i.MainModule.FileName, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + return false; + } + catch (Exception) + { + return false; + } + }); + + if (duplicate != null) + { + _logger.Info("Found a duplicate process. Giving it time to exit."); + + if (!duplicate.WaitForExit(30000)) + { + _logger.Info("The duplicate process did not exit."); + return true; + } + } + + if (!_isRunningAsService) + { + return false; + } + + return false; + } + + /// <summary> + /// Creates the application paths. + /// </summary> + /// <param name="applicationPath">The application path.</param> + /// <param name="runAsService">if set to <c>true</c> [run as service].</param> + /// <returns>ServerApplicationPaths.</returns> + private static ServerApplicationPaths CreateApplicationPaths(string applicationPath, bool runAsService) + { + var resourcesPath = Path.GetDirectoryName(applicationPath); + + if (runAsService) + { + var systemPath = Path.GetDirectoryName(applicationPath); + + var programDataPath = Path.GetDirectoryName(systemPath); + + return new ServerApplicationPaths(programDataPath, applicationPath, resourcesPath); + } + + return new ServerApplicationPaths(ApplicationPathHelper.GetProgramDataPath(applicationPath), applicationPath, resourcesPath); + } + + /// <summary> + /// Gets a value indicating whether this instance can self restart. + /// </summary> + /// <value><c>true</c> if this instance can self restart; otherwise, <c>false</c>.</value> + public static bool CanSelfRestart + { + get + { + if (_isRunningAsService) + { + return _canRestartService; + } + else + { + return true; + } + } + } + + /// <summary> + /// Gets a value indicating whether this instance can self update. + /// </summary> + /// <value><c>true</c> if this instance can self update; otherwise, <c>false</c>.</value> + public static bool CanSelfUpdate + { + get + { + if (_isRunningAsService) + { + return _canRestartService; + } + else + { + return true; + } + } + } + + private static readonly TaskCompletionSource<bool> ApplicationTaskCompletionSource = new TaskCompletionSource<bool>(); + + /// <summary> + /// Runs the application. + /// </summary> + /// <param name="appPaths">The app paths.</param> + /// <param name="logManager">The log manager.</param> + /// <param name="runService">if set to <c>true</c> [run service].</param> + /// <param name="options">The options.</param> + private static void RunApplication(ServerApplicationPaths appPaths, ILogManager logManager, bool runService, StartupOptions options) + { + var fileSystem = new WindowsFileSystem(logManager.GetLogger("FileSystem")); + fileSystem.AddShortcutHandler(new LnkShortcutHandler()); + fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); + + var nativeApp = new WindowsApp(fileSystem, _logger) + { + IsRunningAsService = runService + }; + + var imageEncoder = ImageEncoderHelper.GetImageEncoder(_logger, logManager, fileSystem, options, () => _appHost.HttpClient, appPaths); + + _appHost = new ApplicationHost(appPaths, + logManager, + options, + fileSystem, + nativeApp, + new PowerManagement(), + "emby.windows.zip", + new EnvironmentInfo(), + imageEncoder, + new Server.Startup.Common.SystemEvents(logManager.GetLogger("SystemEvents")), + new RecyclableMemoryStreamProvider(), + new NetworkManager(logManager.GetLogger("NetworkManager")), + GenerateCertificate, + () => Environment.UserDomainName); + + var initProgress = new Progress<double>(); + + if (!runService) + { + // Not crazy about this but it's the only way to suppress ffmpeg crash dialog boxes + SetErrorMode(ErrorModes.SEM_FAILCRITICALERRORS | ErrorModes.SEM_NOALIGNMENTFAULTEXCEPT | + ErrorModes.SEM_NOGPFAULTERRORBOX | ErrorModes.SEM_NOOPENFILEERRORBOX); + } + + var task = _appHost.Init(initProgress); + Task.WaitAll(task); + + task = task.ContinueWith(new Action<Task>(a => _appHost.RunStartupTasks()), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent); + + if (runService) + { + StartService(logManager); + } + else + { + Task.WaitAll(task); + + task = InstallVcredist2013IfNeeded(_appHost, _logger); + Task.WaitAll(task); + + Microsoft.Win32.SystemEvents.SessionEnding += SystemEvents_SessionEnding; + Microsoft.Win32.SystemEvents.SessionSwitch += SystemEvents_SessionSwitch; + + task = ApplicationTaskCompletionSource.Task; + Task.WaitAll(task); + } + } + + private static void GenerateCertificate(string certPath, string certHost) + { + CertificateGenerator.CreateSelfSignCertificatePfx(certPath, certHost, _logger); + } + + static void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e) + { + if (e.Reason == SessionSwitchReason.SessionLogon) + { + BrowserLauncher.OpenDashboard(_appHost); + } + } + + /// <summary> + /// Starts the service. + /// </summary> + private static void StartService(ILogManager logManager) + { + var service = new BackgroundService(logManager.GetLogger("Service")); + + service.Disposed += service_Disposed; + + ServiceBase.Run(service); + } + + /// <summary> + /// Handles the Disposed event of the service control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param> + static void service_Disposed(object sender, EventArgs e) + { + ApplicationTaskCompletionSource.SetResult(true); + OnServiceShutdown(); + } + + private static void OnServiceShutdown() + { + _logger.Info("Shutting down"); + + DisposeAppHost(); + } + + /// <summary> + /// Handles the SessionEnding event of the SystemEvents control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="SessionEndingEventArgs"/> instance containing the event data.</param> + static void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e) + { + if (e.Reason == SessionEndReasons.SystemShutdown || !_isRunningAsService) + { + Shutdown(); + } + } + + /// <summary> + /// Handles the UnhandledException event of the CurrentDomain control. + /// </summary> + /// <param name="sender">The source of the event.</param> + /// <param name="e">The <see cref="UnhandledExceptionEventArgs"/> instance containing the event data.</param> + static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + var exception = (Exception)e.ExceptionObject; + + new UnhandledExceptionWriter(_appHost.ServerConfigurationManager.ApplicationPaths, _logger, _appHost.LogManager).Log(exception); + + if (!_isRunningAsService) + { + MessageBox.Show("Unhandled exception: " + exception.Message); + } + + if (!Debugger.IsAttached) + { + Environment.Exit(Marshal.GetHRForException(exception)); + } + } + + /// <summary> + /// Performs the update if needed. + /// </summary> + /// <param name="appPaths">The app paths.</param> + /// <param name="logger">The logger.</param> + /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> + private static bool PerformUpdateIfNeeded(ServerApplicationPaths appPaths, ILogger logger) + { + // Look for the existence of an update archive + var updateArchive = Path.Combine(appPaths.TempUpdatePath, "MBServer" + ".zip"); + if (File.Exists(updateArchive)) + { + logger.Info("An update is available from {0}", updateArchive); + + // Update is there - execute update + try + { + var serviceName = _isRunningAsService ? BackgroundService.GetExistingServiceName() : string.Empty; + new ApplicationUpdater().UpdateApplication(appPaths, updateArchive, logger, serviceName); + + // And just let the app exit so it can update + return true; + } + catch (Exception e) + { + logger.ErrorException("Error starting updater.", e); + + MessageBox.Show(string.Format("Error attempting to update application.\n\n{0}\n\n{1}", e.GetType().Name, e.Message)); + } + } + + return false; + } + + public static void Shutdown() + { + if (_isRunningAsService) + { + ShutdownWindowsService(); + } + else + { + DisposeAppHost(); + + ShutdownWindowsApplication(); + } + } + + public static void Restart() + { + DisposeAppHost(); + + if (_isRunningAsService) + { + RestartWindowsService(); + } + else + { + //_logger.Info("Hiding server notify icon"); + //_serverNotifyIcon.Visible = false; + + _logger.Info("Starting new instance"); + //Application.Restart(); + Process.Start(_appHost.ServerConfigurationManager.ApplicationPaths.ApplicationPath); + + ShutdownWindowsApplication(); + } + } + + private static void DisposeAppHost() + { + if (!_appHostDisposed) + { + _logger.Info("Disposing app host"); + + _appHostDisposed = true; + _appHost.Dispose(); + } + } + + private static void ShutdownWindowsApplication() + { + //_logger.Info("Calling Application.Exit"); + //Application.Exit(); + + _logger.Info("Calling Environment.Exit"); + Environment.Exit(0); + + _logger.Info("Calling ApplicationTaskCompletionSource.SetResult"); + ApplicationTaskCompletionSource.SetResult(true); + } + + private static void ShutdownWindowsService() + { + } + + private static void RestartWindowsService() + { + } + + private static bool CanRestartWindowsService() + { + return false; + } + + private static async Task InstallVcredist2013IfNeeded(ApplicationHost appHost, ILogger logger) + { + // Reference + // http://stackoverflow.com/questions/12206314/detect-if-visual-c-redistributable-for-visual-studio-2012-is-installed + + try + { + var subkey = Environment.Is64BitProcess + ? "SOFTWARE\\WOW6432Node\\Microsoft\\VisualStudio\\12.0\\VC\\Runtimes\\x64" + : "SOFTWARE\\Microsoft\\VisualStudio\\12.0\\VC\\Runtimes\\x86"; + + using (RegistryKey ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default) + .OpenSubKey(subkey)) + { + if (ndpKey != null && ndpKey.GetValue("Version") != null) + { + var installedVersion = ((string)ndpKey.GetValue("Version")).TrimStart('v'); + if (installedVersion.StartsWith("12", StringComparison.OrdinalIgnoreCase)) + { + return; + } + } + } + } + catch (Exception ex) + { + logger.ErrorException("Error getting .NET Framework version", ex); + return; + } + + try + { + await InstallVcredist2013().ConfigureAwait(false); + } + catch (Exception ex) + { + logger.ErrorException("Error installing Visual Studio C++ runtime", ex); + } + } + + private async static Task InstallVcredist2013() + { + var httpClient = _appHost.HttpClient; + + var tmp = await httpClient.GetTempFile(new HttpRequestOptions + { + Url = GetVcredist2013Url(), + Progress = new Progress<double>() + + }).ConfigureAwait(false); + + var exePath = Path.ChangeExtension(tmp, ".exe"); + File.Copy(tmp, exePath); + + var startInfo = new ProcessStartInfo + { + FileName = exePath, + + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, + Verb = "runas", + ErrorDialog = false + }; + + _logger.Info("Running {0}", startInfo.FileName); + + using (var process = Process.Start(startInfo)) + { + process.WaitForExit(); + } + } + + private static string GetVcredist2013Url() + { + if (Environment.Is64BitProcess) + { + return "https://github.com/MediaBrowser/Emby.Resources/raw/master/vcredist2013/vcredist_x64.exe"; + } + + // TODO: ARM url - https://github.com/MediaBrowser/Emby.Resources/raw/master/vcredist2013/vcredist_arm.exe + + return "https://github.com/MediaBrowser/Emby.Resources/raw/master/vcredist2013/vcredist_x86.exe"; + } + + /// <summary> + /// Sets the error mode. + /// </summary> + /// <param name="uMode">The u mode.</param> + /// <returns>ErrorModes.</returns> + [DllImport("kernel32.dll")] + static extern ErrorModes SetErrorMode(ErrorModes uMode); + + /// <summary> + /// Enum ErrorModes + /// </summary> + [Flags] + public enum ErrorModes : uint + { + /// <summary> + /// The SYSTE m_ DEFAULT + /// </summary> + SYSTEM_DEFAULT = 0x0, + /// <summary> + /// The SE m_ FAILCRITICALERRORS + /// </summary> + SEM_FAILCRITICALERRORS = 0x0001, + /// <summary> + /// The SE m_ NOALIGNMENTFAULTEXCEPT + /// </summary> + SEM_NOALIGNMENTFAULTEXCEPT = 0x0004, + /// <summary> + /// The SE m_ NOGPFAULTERRORBOX + /// </summary> + SEM_NOGPFAULTERRORBOX = 0x0002, + /// <summary> + /// The SE m_ NOOPENFILEERRORBOX + /// </summary> + SEM_NOOPENFILEERRORBOX = 0x8000 } } } |
