From 1731bf7372a13ea8c656eb9f895508b7b4c66784 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 26 Feb 2019 20:47:23 +0100 Subject: Remove ordering items --- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index fa0ab62d32..03e7b26545 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1090,7 +1090,7 @@ namespace Emby.Server.Implementations.Session await SendMessageToSession(session, "Play", command, cancellationToken).ConfigureAwait(false); } - private IList TranslateItemForPlayback(Guid id, User user) + private IEnumerable TranslateItemForPlayback(Guid id, User user) { var item = _libraryManager.GetItemById(id); -- cgit v1.2.3 From 27e7e792b3d95912787c613f849548809d48f6b1 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 14:23:39 +0100 Subject: Replace some usage of QueryParamCollection --- Emby.Dlna/Api/DlnaServerService.cs | 10 +-- Emby.Dlna/ConnectionManager/ConnectionManager.cs | 3 +- Emby.Dlna/ContentDirectory/ContentDirectory.cs | 2 +- Emby.Dlna/ControlRequest.cs | 5 +- Emby.Dlna/DlnaManager.cs | 17 +++-- Emby.Dlna/IUpnpService.cs | 5 +- .../MediaReceiverRegistrar.cs | 3 +- .../HttpServer/HttpListenerHost.cs | 7 +- .../HttpServer/HttpResultFactory.cs | 19 +++-- .../HttpServer/WebSocketConnection.cs | 3 +- .../Net/WebSocketConnectEventArgs.cs | 3 +- .../Services/ServiceHandler.cs | 2 +- .../Session/SessionWebSocketListener.cs | 3 +- .../SocketSharp/RequestMono.cs | 2 - .../SocketSharp/WebSocketSharpListener.cs | 6 +- .../SocketSharp/WebSocketSharpRequest.cs | 12 ++-- .../SocketSharp/WebSocketSharpResponse.cs | 83 +--------------------- MediaBrowser.Api/Playback/BaseStreamingService.cs | 6 +- MediaBrowser.Controller/Dlna/IDlnaManager.cs | 5 +- .../Net/IWebSocketConnection.cs | 3 +- .../Net/WebSocketConnectEventArgs.cs | 6 +- MediaBrowser.Model/Services/IHttpRequest.cs | 5 -- MediaBrowser.Model/Services/IHttpResponse.cs | 12 ---- MediaBrowser.Model/Services/IRequest.cs | 21 ++---- .../Services/QueryParamCollection.cs | 20 ------ 25 files changed, 66 insertions(+), 197 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Dlna/Api/DlnaServerService.cs b/Emby.Dlna/Api/DlnaServerService.cs index 68bf801637..8bf3797f85 100644 --- a/Emby.Dlna/Api/DlnaServerService.cs +++ b/Emby.Dlna/Api/DlnaServerService.cs @@ -136,7 +136,7 @@ namespace Emby.Dlna.Api { var url = Request.AbsoluteUri; var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase)); - var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers.ToDictionary(), request.UuId, serverAddress); + var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, request.UuId, serverAddress); var cacheLength = TimeSpan.FromDays(1); var cacheKey = Request.RawUrl.GetMD5(); @@ -147,21 +147,21 @@ namespace Emby.Dlna.Api public object Get(GetContentDirectory request) { - var xml = ContentDirectory.GetServiceXml(Request.Headers.ToDictionary()); + var xml = ContentDirectory.GetServiceXml(); return _resultFactory.GetResult(Request, xml, XMLContentType); } public object Get(GetMediaReceiverRegistrar request) { - var xml = MediaReceiverRegistrar.GetServiceXml(Request.Headers.ToDictionary()); + var xml = MediaReceiverRegistrar.GetServiceXml(); return _resultFactory.GetResult(Request, xml, XMLContentType); } public object Get(GetConnnectionManager request) { - var xml = ConnectionManager.GetServiceXml(Request.Headers.ToDictionary()); + var xml = ConnectionManager.GetServiceXml(); return _resultFactory.GetResult(Request, xml, XMLContentType); } @@ -193,7 +193,7 @@ namespace Emby.Dlna.Api return service.ProcessControlRequest(new ControlRequest { - Headers = Request.Headers.ToDictionary(), + Headers = Request.Headers, InputXml = requestStream, TargetServerUuId = id, RequestedUrl = Request.AbsoluteUri diff --git a/Emby.Dlna/ConnectionManager/ConnectionManager.cs b/Emby.Dlna/ConnectionManager/ConnectionManager.cs index cc427f2a15..e138b91d6a 100644 --- a/Emby.Dlna/ConnectionManager/ConnectionManager.cs +++ b/Emby.Dlna/ConnectionManager/ConnectionManager.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using Emby.Dlna.Service; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; @@ -24,7 +23,7 @@ namespace Emby.Dlna.ConnectionManager XmlReaderSettingsFactory = xmlReaderSettingsFactory; } - public string GetServiceXml(IDictionary headers) + public string GetServiceXml() { return new ConnectionManagerXmlBuilder().GetXml(); } diff --git a/Emby.Dlna/ContentDirectory/ContentDirectory.cs b/Emby.Dlna/ContentDirectory/ContentDirectory.cs index b0fec90e69..867e6112fd 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectory.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectory.cs @@ -65,7 +65,7 @@ namespace Emby.Dlna.ContentDirectory } } - public string GetServiceXml(IDictionary headers) + public string GetServiceXml() { return new ContentDirectoryXmlBuilder().GetXml(); } diff --git a/Emby.Dlna/ControlRequest.cs b/Emby.Dlna/ControlRequest.cs index afd9a0b874..907d437f85 100644 --- a/Emby.Dlna/ControlRequest.cs +++ b/Emby.Dlna/ControlRequest.cs @@ -1,11 +1,12 @@ using System.Collections.Generic; using System.IO; +using Microsoft.AspNetCore.Http; namespace Emby.Dlna { public class ControlRequest { - public IDictionary Headers { get; set; } + public IHeaderDictionary Headers { get; set; } public Stream InputXml { get; set; } @@ -15,7 +16,7 @@ namespace Emby.Dlna public ControlRequest() { - Headers = new Dictionary(); + Headers = new HeaderDictionary(); } } } diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index f53d274516..770a901521 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -17,7 +17,9 @@ using MediaBrowser.Model.Drawing; using MediaBrowser.Model.IO; using MediaBrowser.Model.Reflection; using MediaBrowser.Model.Serialization; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; namespace Emby.Dlna { @@ -205,16 +207,13 @@ namespace Emby.Dlna } } - public DeviceProfile GetProfile(IDictionary headers) + public DeviceProfile GetProfile(IHeaderDictionary headers) { if (headers == null) { throw new ArgumentNullException(nameof(headers)); } - // Convert to case insensitive - headers = new Dictionary(headers, StringComparer.OrdinalIgnoreCase); - var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification)); if (profile != null) @@ -230,12 +229,12 @@ namespace Emby.Dlna return profile; } - private bool IsMatch(IDictionary headers, DeviceIdentification profileInfo) + private bool IsMatch(IHeaderDictionary headers, DeviceIdentification profileInfo) { return profileInfo.Headers.Any(i => IsMatch(headers, i)); } - private bool IsMatch(IDictionary headers, HttpHeaderInfo header) + private bool IsMatch(IHeaderDictionary headers, HttpHeaderInfo header) { // Handle invalid user setup if (string.IsNullOrEmpty(header.Name)) @@ -243,14 +242,14 @@ namespace Emby.Dlna return false; } - if (headers.TryGetValue(header.Name, out string value)) + if (headers.TryGetValue(header.Name, out StringValues value)) { switch (header.Match) { case HeaderMatchType.Equals: return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase); case HeaderMatchType.Substring: - var isMatch = value.IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1; + var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1; //_logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch); return isMatch; case HeaderMatchType.Regex: @@ -493,7 +492,7 @@ namespace Emby.Dlna internal string Path { get; set; } } - public string GetServerDescriptionXml(IDictionary headers, string serverUuId, string serverAddress) + public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress) { var profile = GetProfile(headers) ?? GetDefaultProfile(); diff --git a/Emby.Dlna/IUpnpService.cs b/Emby.Dlna/IUpnpService.cs index ab8aa46192..ae90e95c79 100644 --- a/Emby.Dlna/IUpnpService.cs +++ b/Emby.Dlna/IUpnpService.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; - namespace Emby.Dlna { public interface IUpnpService @@ -7,9 +5,8 @@ namespace Emby.Dlna /// /// Gets the content directory XML. /// - /// The headers. /// System.String. - string GetServiceXml(IDictionary headers); + string GetServiceXml(); /// /// Processes the control request. diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs index 2b84528eab..9c6022b6cb 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs @@ -3,6 +3,7 @@ using Emby.Dlna.Service; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Xml; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; namespace Emby.Dlna.MediaReceiverRegistrar @@ -19,7 +20,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar XmlReaderSettingsFactory = xmlReaderSettingsFactory; } - public string GetServiceXml(IDictionary headers) + public string GetServiceXml() { return new MediaReceiverRegistrarXmlBuilder().GetXml(); } diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 2abc6c2f4f..70753e5634 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -22,6 +22,7 @@ using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Internal; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -154,7 +155,7 @@ namespace Emby.Server.Implementations.HttpServer { OnReceive = ProcessWebSocketMessageReceived, Url = e.Url, - QueryString = e.QueryString ?? new QueryParamCollection() + QueryString = e.QueryString ?? new QueryCollection() }; connection.Closed += Connection_Closed; @@ -606,8 +607,8 @@ namespace Emby.Server.Implementations.HttpServer } finally { - httpRes.Close(); - + // TODO + httpRes.IsClosed = true; stopWatch.Stop(); var elapsed = stopWatch.Elapsed; if (elapsed.TotalMilliseconds > 500) diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 09cdbc3c26..52c8221f68 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -16,6 +16,8 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; using IRequest = MediaBrowser.Model.Services.IRequest; using MimeTypes = MediaBrowser.Model.Net.MimeTypes; @@ -246,9 +248,9 @@ namespace Emby.Server.Implementations.HttpServer private static string GetCompressionType(IRequest request) { - var acceptEncoding = request.Headers["Accept-Encoding"]; + var acceptEncoding = request.Headers["Accept-Encoding"].ToString(); - if (acceptEncoding != null) + if (string.IsNullOrEmpty(acceptEncoding)) { //if (_brotliCompressor != null && acceptEncoding.IndexOf("br", StringComparison.OrdinalIgnoreCase) != -1) // return "br"; @@ -424,12 +426,12 @@ namespace Emby.Server.Implementations.HttpServer /// private object GetCachedResult(IRequest requestContext, IDictionary responseHeaders, StaticResultOptions options) { - bool noCache = (requestContext.Headers.Get("Cache-Control") ?? string.Empty).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1; + bool noCache = (requestContext.Headers[HeaderNames.CacheControl].ToString()).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1; AddCachingHeaders(responseHeaders, options.CacheDuration, noCache, options.DateLastModified); if (!noCache) { - DateTime.TryParse(requestContext.Headers.Get("If-Modified-Since"), out var ifModifiedSinceHeader); + DateTime.TryParse(requestContext.Headers[HeaderNames.IfModifiedSince], out var ifModifiedSinceHeader); if (IsNotModified(ifModifiedSinceHeader, options.CacheDuration, options.DateLastModified)) { @@ -530,7 +532,7 @@ namespace Emby.Server.Implementations.HttpServer options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary(StringComparer.OrdinalIgnoreCase); var contentType = options.ContentType; - if (!string.IsNullOrEmpty(requestContext.Headers.Get("If-Modified-Since"))) + if (!StringValues.IsNullOrEmpty(requestContext.Headers[HeaderNames.IfModifiedSince])) { // See if the result is already cached in the browser var result = GetCachedResult(requestContext, options.ResponseHeaders, options); @@ -548,7 +550,7 @@ namespace Emby.Server.Implementations.HttpServer AddCachingHeaders(responseHeaders, options.CacheDuration, false, options.DateLastModified); AddAgeHeader(responseHeaders, options.DateLastModified); - var rangeHeader = requestContext.Headers.Get("Range"); + var rangeHeader = requestContext.Headers["Range"]; if (!isHeadRequest && !string.IsNullOrEmpty(options.Path)) { @@ -609,11 +611,6 @@ namespace Emby.Server.Implementations.HttpServer } } - /// - /// The us culture - /// - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - /// /// Adds the caching responseHeaders. /// diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index e9d0bac74d..2bf460bd11 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -8,6 +8,7 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using UtfUnknown; @@ -67,7 +68,7 @@ namespace Emby.Server.Implementations.HttpServer /// Gets or sets the query string. /// /// The query string. - public QueryParamCollection QueryString { get; set; } + public IQueryCollection QueryString { get; set; } /// /// Initializes a new instance of the class. diff --git a/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs b/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs index 666f1f6011..e3047d3926 100644 --- a/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs +++ b/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs @@ -1,6 +1,7 @@ using System; using System.Net.WebSockets; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; namespace Emby.Server.Implementations.Net { @@ -15,7 +16,7 @@ namespace Emby.Server.Implementations.Net /// Gets or sets the query string. /// /// The query string. - public QueryParamCollection QueryString { get; set; } + public IQueryCollection QueryString { get; set; } /// /// Gets or sets the web socket. /// diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs index 7e836e22c3..3c8adfc983 100644 --- a/Emby.Server.Implementations/Services/ServiceHandler.cs +++ b/Emby.Server.Implementations/Services/ServiceHandler.cs @@ -154,7 +154,7 @@ namespace Emby.Server.Implementations.Services { if (name == null) continue; //thank you ASP.NET - var values = request.QueryString.GetValues(name); + var values = request.QueryString[name]; if (values.Count == 1) { map[name] = values[0]; diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 24903f5e8c..a551433ed9 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Model.Events; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Session @@ -62,7 +63,7 @@ namespace Emby.Server.Implementations.Session } } - private SessionInfo GetSession(QueryParamCollection queryString, string remoteEndpoint) + private SessionInfo GetSession(IQueryCollection queryString, string remoteEndpoint) { if (queryString == null) { diff --git a/Emby.Server.Implementations/SocketSharp/RequestMono.cs b/Emby.Server.Implementations/SocketSharp/RequestMono.cs index 113f76b10b..f73adc5ff8 100644 --- a/Emby.Server.Implementations/SocketSharp/RequestMono.cs +++ b/Emby.Server.Implementations/SocketSharp/RequestMono.cs @@ -118,8 +118,6 @@ namespace Emby.Server.Implementations.SocketSharp public string Authorization => StringValues.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"].ToString(); - protected bool validate_cookies { get; set; } - protected bool validate_query_string { get; set; } protected bool validate_form { get; set; } protected bool checked_form { get; set; } diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 77469244b9..9f046c3fd0 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -52,12 +52,10 @@ using Microsoft.Extensions.Logging; var endpoint = ctx.Connection.RemoteIpAddress.ToString(); var url = ctx.Request.GetDisplayUrl(); - var queryString = new QueryParamCollection(ctx.Request.Query); - var connectingArgs = new WebSocketConnectingEventArgs { Url = url, - QueryString = queryString, + QueryString = ctx.Request.Query, Endpoint = endpoint }; @@ -73,7 +71,7 @@ using Microsoft.Extensions.Logging; WebSocketConnected(new WebSocketConnectEventArgs { Url = url, - QueryString = queryString, + QueryString = ctx.Request.Query, WebSocket = socket, Endpoint = endpoint }); diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index bddccf68ba..24fd36062f 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -13,7 +13,6 @@ using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using IHttpFile = MediaBrowser.Model.Services.IHttpFile; using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest; -using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; using IResponse = MediaBrowser.Model.Services.IResponse; namespace Emby.Server.Implementations.SocketSharp @@ -21,7 +20,7 @@ namespace Emby.Server.Implementations.SocketSharp public partial class WebSocketSharpRequest : IHttpRequest { private readonly HttpRequest request; - private readonly IHttpResponse response; + private readonly IResponse response; public WebSocketSharpRequest(HttpRequest httpContext, HttpResponse response, string operationName, ILogger logger) { @@ -34,11 +33,9 @@ namespace Emby.Server.Implementations.SocketSharp public HttpRequest HttpRequest => request; - public object OriginalRequest => request; - public IResponse Response => response; - public IHttpResponse HttpResponse => response; + public IResponse HttpResponse => response; public string OperationName { get; set; } @@ -396,10 +393,9 @@ namespace Emby.Server.Implementations.SocketSharp public string UserAgent => request.Headers[HeaderNames.UserAgent]; - public QueryParamCollection Headers => new QueryParamCollection(request.Headers); + public IHeaderDictionary Headers => request.Headers; - private QueryParamCollection queryString; - public QueryParamCollection QueryString => queryString ?? (queryString = new QueryParamCollection(request.Query)); + public IQueryCollection QueryString => request.Query; public bool IsLocal => string.Equals(request.HttpContext.Connection.LocalIpAddress.ToString(), request.HttpContext.Connection.RemoteIpAddress.ToString()); diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs index f9ecb52a55..c4fbaddd3e 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs @@ -1,23 +1,18 @@ using System; using System.Collections.Generic; using System.IO; -using System.Net; -using System.Net.Sockets; using System.Runtime.InteropServices; -using System.Text; using System.Threading; using System.Threading.Tasks; -using Emby.Server.Implementations; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; using IRequest = MediaBrowser.Model.Services.IRequest; namespace Emby.Server.Implementations.SocketSharp { - public class WebSocketSharpResponse : IHttpResponse + public class WebSocketSharpResponse : IResponse { private readonly ILogger _logger; @@ -51,42 +46,7 @@ namespace Emby.Server.Implementations.SocketSharp set => _response.ContentType = value; } - public QueryParamCollection Headers => new QueryParamCollection(_response.Headers); - - private static string AsHeaderValue(Cookie cookie) - { - DateTime defaultExpires = DateTime.MinValue; - - var path = cookie.Expires == defaultExpires - ? "/" - : cookie.Path ?? "/"; - - var sb = new StringBuilder(); - - sb.Append($"{cookie.Name}={cookie.Value};path={path}"); - - if (cookie.Expires != defaultExpires) - { - sb.Append($";expires={cookie.Expires:R}"); - } - - if (!string.IsNullOrEmpty(cookie.Domain)) - { - sb.Append($";domain={cookie.Domain}"); - } - - if (cookie.Secure) - { - sb.Append(";Secure"); - } - - if (cookie.HttpOnly) - { - sb.Append(";HttpOnly"); - } - - return sb.ToString(); - } + public IHeaderDictionary Headers => _response.Headers; public void AddHeader(string name, string value) { @@ -111,51 +71,14 @@ namespace Emby.Server.Implementations.SocketSharp public Stream OutputStream => _response.Body; - public void Close() - { - if (!this.IsClosed) - { - this.IsClosed = true; - - try - { - var response = this._response; - - var outputStream = response.Body; - - // This is needed with compression - outputStream.Flush(); - outputStream.Dispose(); - } - catch (SocketException) - { - } - catch (Exception ex) - { - _logger.LogError(ex, "Error in HttpListenerResponseWrapper"); - } - } - } - public bool IsClosed { get; - private set; - } - - public void SetCookie(Cookie cookie) - { - var cookieStr = AsHeaderValue(cookie); - _response.Headers.Add("Set-Cookie", cookieStr); + set; } public bool SendChunked { get; set; } - public bool KeepAlive { get; set; } - - public void ClearCookies() - { - } const int StreamCopyToBufferSize = 81920; public async Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken) { diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index a6be071b84..ae259a4f59 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -609,12 +609,12 @@ namespace MediaBrowser.Api.Playback { foreach (var param in Request.QueryString) { - if (char.IsLower(param.Name[0])) + if (char.IsLower(param.Key[0])) { // This was probably not parsed initially and should be a StreamOptions // TODO: This should be incorporated either in the lower framework for parsing requests // or the generated URL should correctly serialize it - request.StreamOptions[param.Name] = param.Value; + request.StreamOptions[param.Key] = param.Value; } } } @@ -867,7 +867,7 @@ namespace MediaBrowser.Api.Playback private void ApplyDeviceProfileSettings(StreamState state) { - var headers = Request.Headers.ToDictionary(); + var headers = Request.Headers; if (!string.IsNullOrWhiteSpace(state.Request.DeviceProfileId)) { diff --git a/MediaBrowser.Controller/Dlna/IDlnaManager.cs b/MediaBrowser.Controller/Dlna/IDlnaManager.cs index a6ee7c5050..41a7686a37 100644 --- a/MediaBrowser.Controller/Dlna/IDlnaManager.cs +++ b/MediaBrowser.Controller/Dlna/IDlnaManager.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.Dlna; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Dlna { @@ -17,7 +18,7 @@ namespace MediaBrowser.Controller.Dlna /// /// The headers. /// DeviceProfile. - DeviceProfile GetProfile(IDictionary headers); + DeviceProfile GetProfile(IHeaderDictionary headers); /// /// Gets the default profile. @@ -64,7 +65,7 @@ namespace MediaBrowser.Controller.Dlna /// The server uu identifier. /// The server address. /// System.String. - string GetServerDescriptionXml(IDictionary headers, string serverUuId, string serverAddress); + string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress); /// /// Gets the icon. diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs index a09b2f7a22..566897b31f 100644 --- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs +++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Net; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net { @@ -35,7 +36,7 @@ namespace MediaBrowser.Controller.Net /// Gets or sets the query string. /// /// The query string. - QueryParamCollection QueryString { get; set; } + IQueryCollection QueryString { get; set; } /// /// Gets or sets the receive action. diff --git a/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs b/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs index f26b764bb3..107e674216 100644 --- a/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs +++ b/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs @@ -1,5 +1,7 @@ using System; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Internal; namespace MediaBrowser.Controller.Net { @@ -22,7 +24,7 @@ namespace MediaBrowser.Controller.Net /// Gets or sets the query string. /// /// The query string. - public QueryParamCollection QueryString { get; set; } + public IQueryCollection QueryString { get; set; } /// /// Gets or sets a value indicating whether [allow connection]. /// @@ -31,7 +33,7 @@ namespace MediaBrowser.Controller.Net public WebSocketConnectingEventArgs() { - QueryString = new QueryParamCollection(); + QueryString = new QueryCollection(); AllowConnection = true; } } diff --git a/MediaBrowser.Model/Services/IHttpRequest.cs b/MediaBrowser.Model/Services/IHttpRequest.cs index 579f80c968..50c6076f30 100644 --- a/MediaBrowser.Model/Services/IHttpRequest.cs +++ b/MediaBrowser.Model/Services/IHttpRequest.cs @@ -2,11 +2,6 @@ namespace MediaBrowser.Model.Services { public interface IHttpRequest : IRequest { - /// - /// The HttpResponse - /// - IHttpResponse HttpResponse { get; } - /// /// The HTTP Verb /// diff --git a/MediaBrowser.Model/Services/IHttpResponse.cs b/MediaBrowser.Model/Services/IHttpResponse.cs index a8b79f3949..b99d12525d 100644 --- a/MediaBrowser.Model/Services/IHttpResponse.cs +++ b/MediaBrowser.Model/Services/IHttpResponse.cs @@ -4,17 +4,5 @@ namespace MediaBrowser.Model.Services { public interface IHttpResponse : IResponse { - //ICookies Cookies { get; } - - /// - /// Adds a new Set-Cookie instruction to Response - /// - /// - void SetCookie(Cookie cookie); - - /// - /// Removes all pending Set-Cookie instructions - /// - void ClearCookies(); } } diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index 0fd4ea37b1..edb5a2509b 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -1,20 +1,15 @@ using System; using System.Collections.Generic; using System.IO; -using System.Net; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.IO; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Model.Services { public interface IRequest { - /// - /// The underlying ASP.NET or HttpListener HttpRequest - /// - object OriginalRequest { get; } - IResponse Response { get; } /// @@ -51,9 +46,9 @@ namespace MediaBrowser.Model.Services /// Dictionary Items { get; } - QueryParamCollection Headers { get; } + IHeaderDictionary Headers { get; } - QueryParamCollection QueryString { get; } + IQueryCollection QueryString { get; } Task GetFormData(); @@ -122,21 +117,15 @@ namespace MediaBrowser.Model.Services Stream OutputStream { get; } - /// - /// 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. - /// - void Close(); - /// /// Gets a value indicating whether this instance is closed. /// - bool IsClosed { get; } + bool IsClosed { get; set; } //Add Metadata to Response Dictionary Items { get; } - QueryParamCollection Headers { get; } + IHeaderDictionary Headers { get; } Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken); diff --git a/MediaBrowser.Model/Services/QueryParamCollection.cs b/MediaBrowser.Model/Services/QueryParamCollection.cs index 9f23b2420f..4631a3b633 100644 --- a/MediaBrowser.Model/Services/QueryParamCollection.cs +++ b/MediaBrowser.Model/Services/QueryParamCollection.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Linq; -using System.Net; using MediaBrowser.Model.Dto; -using Microsoft.AspNetCore.Http; namespace MediaBrowser.Model.Services { @@ -13,23 +10,6 @@ namespace MediaBrowser.Model.Services { public QueryParamCollection() { - - } - - public QueryParamCollection(IHeaderDictionary headers) - { - foreach (var pair in headers) - { - Add(pair.Key, pair.Value); - } - } - - public QueryParamCollection(IQueryCollection queryCollection) - { - foreach (var pair in queryCollection) - { - Add(pair.Key, pair.Value); - } } private static StringComparison GetStringComparison() -- cgit v1.2.3 From bf43dc00bb193fb4c7aca882aadbe086ed5a1815 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 13 Mar 2019 22:32:52 +0100 Subject: More warning fixes --- Emby.Server.Implementations/ApplicationHost.cs | 209 +++++++------- .../Diagnostics/CommonProcess.cs | 72 +++-- .../Library/LibraryManager.cs | 319 +++++++++++---------- .../Localization/LocalizationManager.cs | 37 +-- .../Session/SessionManager.cs | 244 +++++++++------- .../Sorting/AlbumArtistComparer.cs | 2 +- .../Sorting/SeriesSortNameComparer.cs | 14 +- MediaBrowser.Api/BaseApiService.cs | 35 +-- MediaBrowser.Api/UserLibrary/ArtistsService.cs | 10 - MediaBrowser.Providers/Manager/ProviderManager.cs | 39 +-- .../Music/MusicBrainzAlbumProvider.cs | 24 +- .../Music/MusicBrainzArtistProvider.cs | 18 +- 12 files changed, 534 insertions(+), 489 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 1810064e69..94eaffa479 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -34,7 +34,6 @@ using Emby.Server.Implementations.IO; using Emby.Server.Implementations.Library; using Emby.Server.Implementations.LiveTv; using Emby.Server.Implementations.Localization; -using Emby.Server.Implementations.Middleware; using Emby.Server.Implementations.Net; using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.ScheduledTasks; @@ -161,7 +160,7 @@ namespace Emby.Server.Implementations public event EventHandler> ApplicationUpdated; /// - /// Gets or sets a value indicating whether this instance has changes that require the entire application to restart. + /// Gets a value indicating whether this instance has changes that require the entire application to restart. /// /// true if this instance has pending application restart; otherwise, false. public bool HasPendingRestart { get; private set; } @@ -175,7 +174,7 @@ namespace Emby.Server.Implementations protected ILogger Logger { get; set; } /// - /// Gets or sets the plugins. + /// Gets the plugins. /// /// The plugins. public IPlugin[] Plugins { get; protected set; } @@ -187,13 +186,13 @@ namespace Emby.Server.Implementations public ILoggerFactory LoggerFactory { get; protected set; } /// - /// Gets the application paths. + /// Gets or sets the application paths. /// /// The application paths. protected ServerApplicationPaths ApplicationPaths { get; set; } /// - /// Gets all concrete types. + /// Gets or sets all concrete types. /// /// All concrete types. public Type[] AllConcreteTypes { get; protected set; } @@ -201,7 +200,7 @@ namespace Emby.Server.Implementations /// /// The disposable parts /// - protected readonly List DisposableParts = new List(); + protected readonly List _disposableParts = new List(); /// /// Gets the configuration manager. @@ -240,27 +239,33 @@ namespace Emby.Server.Implementations /// /// The user manager. public IUserManager UserManager { get; set; } + /// /// Gets or sets the library manager. /// /// The library manager. internal ILibraryManager LibraryManager { get; set; } + /// /// Gets or sets the directory watchers. /// /// The directory watchers. private ILibraryMonitor LibraryMonitor { get; set; } + /// /// Gets or sets the provider manager. /// /// The provider manager. private IProviderManager ProviderManager { get; set; } + /// /// Gets or sets the HTTP server. /// /// The HTTP server. private IHttpServer HttpServer { get; set; } + private IDtoService DtoService { get; set; } + public IImageProcessor ImageProcessor { get; set; } /// @@ -268,6 +273,7 @@ namespace Emby.Server.Implementations /// /// The media encoder. private IMediaEncoder MediaEncoder { get; set; } + private ISubtitleEncoder SubtitleEncoder { get; set; } private ISessionManager SessionManager { get; set; } @@ -277,6 +283,7 @@ namespace Emby.Server.Implementations public LocalizationManager LocalizationManager { get; set; } private IEncodingManager EncodingManager { get; set; } + private IChannelManager ChannelManager { get; set; } /// @@ -284,20 +291,29 @@ namespace Emby.Server.Implementations /// /// The user data repository. private IUserDataManager UserDataManager { get; set; } + private IUserRepository UserRepository { get; set; } + internal SqliteItemRepository ItemRepository { get; set; } private INotificationManager NotificationManager { get; set; } + private ISubtitleManager SubtitleManager { get; set; } + private IChapterManager ChapterManager { get; set; } + private IDeviceManager DeviceManager { get; set; } internal IUserViewManager UserViewManager { get; set; } private IAuthenticationRepository AuthenticationRepository { get; set; } + private ITVSeriesManager TVSeriesManager { get; set; } + private ICollectionManager CollectionManager { get; set; } + private IMediaSourceManager MediaSourceManager { get; set; } + private IPlaylistManager PlaylistManager { get; set; } private readonly IConfiguration _configuration; @@ -313,28 +329,37 @@ namespace Emby.Server.Implementations /// /// The zip client. protected IZipClient ZipClient { get; private set; } + protected IHttpResultFactory HttpResultFactory { get; private set; } + protected IAuthService AuthService { get; private set; } - public IStartupOptions StartupOptions { get; private set; } + public IStartupOptions StartupOptions { get; } internal IImageEncoder ImageEncoder { get; private set; } protected IProcessFactory ProcessFactory { get; private set; } + protected ICryptoProvider CryptographyProvider = new CryptographyProvider(); protected readonly IXmlSerializer XmlSerializer; protected ISocketFactory SocketFactory { get; private set; } + protected ITaskManager TaskManager { get; private set; } + public IHttpClient HttpClient { get; private set; } + protected INetworkManager NetworkManager { get; set; } + public IJsonSerializer JsonSerializer { get; private set; } + protected IIsoManager IsoManager { get; private set; } /// /// Initializes a new instance of the class. /// - public ApplicationHost(ServerApplicationPaths applicationPaths, + public ApplicationHost( + ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IStartupOptions options, IFileSystem fileSystem, @@ -395,7 +420,7 @@ namespace Emby.Server.Implementations _validAddressResults.Clear(); } - public string ApplicationVersion => typeof(ApplicationHost).Assembly.GetName().Version.ToString(3); + public string ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3); /// /// Gets the current application user agent @@ -404,13 +429,16 @@ namespace Emby.Server.Implementations public string ApplicationUserAgent => Name.Replace(' ','-') + "/" + ApplicationVersion; private string _productName; + /// /// Gets the current application name /// /// The application name. - public string ApplicationProductName => _productName ?? (_productName = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName); + public string ApplicationProductName + => _productName ?? (_productName = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName); private DeviceId _deviceId; + public string SystemId { get @@ -441,15 +469,15 @@ namespace Emby.Server.Implementations /// /// Creates an instance of type and resolves all constructor dependencies /// - /// The type. - /// System.Object. + /// /// The type + /// T public T CreateInstance() => ActivatorUtilities.CreateInstance(_serviceProvider); /// /// Creates the instance safe. /// - /// The type information. + /// The type. /// System.Object. protected object CreateInstanceSafe(Type type) { @@ -468,14 +496,14 @@ namespace Emby.Server.Implementations /// /// Resolves this instance. /// - /// + /// The type /// ``0. public T Resolve() => _serviceProvider.GetService(); /// /// Gets the export types. /// - /// + /// The type /// IEnumerable{Type}. public IEnumerable GetExportTypes() { @@ -487,22 +515,22 @@ namespace Emby.Server.Implementations /// /// Gets the exports. /// - /// + /// The type /// if set to true [manage lifetime]. /// IEnumerable{``0}. public IEnumerable GetExports(bool manageLifetime = true) { var parts = GetExportTypes() - .Select(x => CreateInstanceSafe(x)) + .Select(CreateInstanceSafe) .Where(i => i != null) .Cast() .ToList(); // Convert to list so this isn't executed for each iteration if (manageLifetime) { - lock (DisposableParts) + lock (_disposableParts) { - DisposableParts.AddRange(parts.OfType()); + _disposableParts.AddRange(parts.OfType()); } } @@ -522,29 +550,20 @@ namespace Emby.Server.Implementations MediaEncoder.SetFFmpegPath(); - //if (string.IsNullOrWhiteSpace(MediaEncoder.EncoderPath)) - //{ - // if (ServerConfigurationManager.Configuration.IsStartupWizardCompleted) - // { - // ServerConfigurationManager.Configuration.IsStartupWizardCompleted = false; - // ServerConfigurationManager.SaveConfiguration(); - // } - //} - Logger.LogInformation("ServerId: {0}", SystemId); - var entryPoints = GetExports(); + var entryPoints = GetExports().ToList(); var stopWatch = new Stopwatch(); stopWatch.Start(); - await Task.WhenAll(StartEntryPoints(entryPoints, true)); + await Task.WhenAll(StartEntryPoints(entryPoints, true)).ConfigureAwait(false); Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed); Logger.LogInformation("Core startup complete"); HttpServer.GlobalResponse = null; stopWatch.Restart(); - await Task.WhenAll(StartEntryPoints(entryPoints, false)); + await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false); Logger.LogInformation("Executed all post-startup entry points in {Elapsed:g}", stopWatch.Elapsed); stopWatch.Stop(); } @@ -594,7 +613,7 @@ namespace Emby.Server.Implementations SetHttpLimit(); - await RegisterResources(serviceCollection); + await RegisterResources(serviceCollection).ConfigureAwait(false); FindParts(); @@ -631,7 +650,7 @@ namespace Emby.Server.Implementations }) .Build(); - await host.StartAsync(); + await host.StartAsync().ConfigureAwait(false); } private async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) @@ -729,8 +748,8 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(ServerConfigurationManager); - LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer, LoggerFactory); - await LocalizationManager.LoadAll(); + LocalizationManager = new LocalizationManager(ServerConfigurationManager, JsonSerializer, LoggerFactory); + await LocalizationManager.LoadAll().ConfigureAwait(false); serviceCollection.AddSingleton(LocalizationManager); serviceCollection.AddSingleton(new BdInfoExaminer(FileSystemManager)); @@ -769,16 +788,19 @@ namespace Emby.Server.Implementations CertificateInfo = GetCertificateInfo(true); Certificate = GetCertificate(CertificateInfo); - HttpServer = new HttpListenerHost(this, + HttpServer = new HttpListenerHost( + this, LoggerFactory, ServerConfigurationManager, _configuration, NetworkManager, JsonSerializer, XmlSerializer, - CreateHttpListener()); + CreateHttpListener()) + { + GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading") + }; - HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); serviceCollection.AddSingleton(HttpServer); ImageProcessor = GetImageProcessor(); @@ -933,7 +955,7 @@ namespace Emby.Server.Implementations var password = string.IsNullOrWhiteSpace(info.Password) ? null : info.Password; var localCert = new X509Certificate2(certificateLocation, password); - //localCert.PrivateKey = PrivateKey.CreateFromFile(pvk_file).RSA; + // localCert.PrivateKey = PrivateKey.CreateFromFile(pvk_file).RSA; if (!localCert.HasPrivateKey) { Logger.LogError("No private key included in SSL cert {CertificateLocation}.", certificateLocation); @@ -1034,13 +1056,15 @@ namespace Emby.Server.Implementations HttpServer.Init(GetExports(false), GetExports(), GetUrlPrefixes()); - LibraryManager.AddParts(GetExports(), + LibraryManager.AddParts( + GetExports(), GetExports(), GetExports(), GetExports(), GetExports()); - ProviderManager.AddParts(GetExports(), + ProviderManager.AddParts( + GetExports(), GetExports(), GetExports(), GetExports(), @@ -1121,6 +1145,7 @@ namespace Emby.Server.Implementations } private CertificateInfo CertificateInfo { get; set; } + protected X509Certificate2 Certificate { get; private set; } private IEnumerable GetUrlPrefixes() @@ -1131,7 +1156,7 @@ namespace Emby.Server.Implementations { var prefixes = new List { - "http://"+i+":" + HttpPort + "/" + "http://" + i + ":" + HttpPort + "/" }; if (CertificateInfo != null) @@ -1160,30 +1185,12 @@ namespace Emby.Server.Implementations // Generate self-signed cert var certHost = GetHostnameFromExternalDns(ServerConfigurationManager.Configuration.WanDdns); var certPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.ProgramDataPath, "ssl", "cert_" + (certHost + "2").GetMD5().ToString("N") + ".pfx"); - var password = "embycert"; - - //if (generateCertificate) - //{ - // if (!File.Exists(certPath)) - // { - // FileSystemManager.CreateDirectory(FileSystemManager.GetDirectoryName(certPath)); - - // try - // { - // CertificateGenerator.CreateSelfSignCertificatePfx(certPath, certHost, password, Logger); - // } - // catch (Exception ex) - // { - // Logger.LogError(ex, "Error creating ssl cert"); - // return null; - // } - // } - //} + const string Password = "embycert"; return new CertificateInfo { Path = certPath, - Password = password + Password = Password }; } @@ -1218,9 +1225,9 @@ namespace Emby.Server.Implementations requiresRestart = true; } - var currentCertPath = CertificateInfo == null ? null : CertificateInfo.Path; + var currentCertPath = CertificateInfo?.Path; var newCertInfo = GetCertificateInfo(false); - var newCertPath = newCertInfo == null ? null : newCertInfo.Path; + var newCertPath = newCertInfo?.Path; if (!string.Equals(currentCertPath, newCertPath, StringComparison.OrdinalIgnoreCase)) { @@ -1353,6 +1360,7 @@ namespace Emby.Server.Implementations /// /// Gets the system status. /// + /// The cancellation token /// SystemInfo. public async Task GetSystemInfo(CancellationToken cancellationToken) { @@ -1447,19 +1455,19 @@ namespace Emby.Server.Implementations public async Task GetWanApiUrl(CancellationToken cancellationToken) { - const string url = "http://ipv4.icanhazip.com"; + const string Url = "http://ipv4.icanhazip.com"; try { using (var response = await HttpClient.Get(new HttpRequestOptions { - Url = url, + Url = Url, LogErrorResponseBody = false, LogErrors = false, LogRequest = false, TimeoutMs = 10000, BufferContent = false, CancellationToken = cancellationToken - })) + }).ConfigureAwait(false)) { return GetLocalApiUrl(response.ReadToEnd().Trim()); } @@ -1547,10 +1555,12 @@ namespace Emby.Server.Implementations { return result; } + return null; } private readonly ConcurrentDictionary _validAddressResults = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private async Task IsIpAddressValidAsync(IpAddressInfo address, CancellationToken cancellationToken) { if (address.Equals(IpAddressInfo.Loopback) || @@ -1567,26 +1577,26 @@ namespace Emby.Server.Implementations return cachedResult; } - var logPing = false; - #if DEBUG - logPing = true; + const bool LogPing = true; +#else + const bool LogPing = false; #endif try { - using (var response = await HttpClient.SendAsync(new HttpRequestOptions - { - Url = apiUrl, - LogErrorResponseBody = false, - LogErrors = logPing, - LogRequest = logPing, - TimeoutMs = 5000, - BufferContent = false, - - CancellationToken = cancellationToken - - }, "POST").ConfigureAwait(false)) + using (var response = await HttpClient.SendAsync( + new HttpRequestOptions + { + Url = apiUrl, + LogErrorResponseBody = false, + LogErrors = LogPing, + LogRequest = LogPing, + TimeoutMs = 5000, + BufferContent = false, + + CancellationToken = cancellationToken + }, "POST").ConfigureAwait(false)) { using (var reader = new StreamReader(response.Content)) { @@ -1651,6 +1661,7 @@ namespace Emby.Server.Implementations public event EventHandler HasUpdateAvailableChanged; private bool _hasUpdateAvailable; + public bool HasUpdateAvailable { get => _hasUpdateAvailable; @@ -1711,7 +1722,7 @@ namespace Emby.Server.Implementations var process = ProcessFactory.Create(new ProcessOptions { FileName = url, - //EnableRaisingEvents = true, + EnableRaisingEvents = true, UseShellExecute = true, ErrorDialog = false }); @@ -1746,26 +1757,25 @@ namespace Emby.Server.Implementations { Logger.LogInformation("Application has been updated to version {0}", package.versionStr); - ApplicationUpdated?.Invoke(this, new GenericEventArgs - { - Argument = package - }); + ApplicationUpdated?.Invoke( + this, + new GenericEventArgs() + { + Argument = package + }); NotifyPendingRestart(); } - private bool _disposed; + private bool _disposed = false; + /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { - if (!_disposed) - { - _disposed = true; - - Dispose(true); - } + Dispose(true); + GC.SuppressFinalize(this); } /// @@ -1774,14 +1784,19 @@ namespace Emby.Server.Implementations /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool dispose) { + if (_disposed) + { + return; + } + if (dispose) { var type = GetType(); Logger.LogInformation("Disposing {Type}", type.Name); - var parts = DisposableParts.Distinct().Where(i => i.GetType() != type).ToList(); - DisposableParts.Clear(); + var parts = _disposableParts.Distinct().Where(i => i.GetType() != type).ToList(); + _disposableParts.Clear(); foreach (var part in parts) { @@ -1797,6 +1812,8 @@ namespace Emby.Server.Implementations } } } + + _disposed = true; } } diff --git a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs index 2c4ef170dc..175a8f3ce4 100644 --- a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs +++ b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs @@ -9,14 +9,14 @@ namespace Emby.Server.Implementations.Diagnostics { public class CommonProcess : IProcess { - public event EventHandler Exited; - - private readonly ProcessOptions _options; private readonly Process _process; + private bool _disposed = false; + private bool _hasExited; + public CommonProcess(ProcessOptions options) { - _options = options; + StartInfo = options; var startInfo = new ProcessStartInfo { @@ -27,10 +27,10 @@ namespace Emby.Server.Implementations.Diagnostics CreateNoWindow = options.CreateNoWindow, RedirectStandardError = options.RedirectStandardError, RedirectStandardInput = options.RedirectStandardInput, - RedirectStandardOutput = options.RedirectStandardOutput + RedirectStandardOutput = options.RedirectStandardOutput, + ErrorDialog = options.ErrorDialog }; - startInfo.ErrorDialog = options.ErrorDialog; if (options.IsHidden) { @@ -45,11 +45,22 @@ namespace Emby.Server.Implementations.Diagnostics if (options.EnableRaisingEvents) { _process.EnableRaisingEvents = true; - _process.Exited += _process_Exited; + _process.Exited += OnProcessExited; } } - private bool _hasExited; + public event EventHandler Exited; + + public ProcessOptions StartInfo { get; } + + public StreamWriter StandardInput => _process.StandardInput; + + public StreamReader StandardError => _process.StandardError; + + public StreamReader StandardOutput => _process.StandardOutput; + + public int ExitCode => _process.ExitCode; + private bool HasExited { get @@ -72,25 +83,6 @@ namespace Emby.Server.Implementations.Diagnostics } } - private void _process_Exited(object sender, EventArgs e) - { - _hasExited = true; - if (Exited != null) - { - Exited(this, e); - } - } - - public ProcessOptions StartInfo => _options; - - public StreamWriter StandardInput => _process.StandardInput; - - public StreamReader StandardError => _process.StandardError; - - public StreamReader StandardOutput => _process.StandardOutput; - - public int ExitCode => _process.ExitCode; - public void Start() { _process.Start(); @@ -108,7 +100,7 @@ namespace Emby.Server.Implementations.Diagnostics public Task WaitForExitAsync(int timeMs) { - //Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true. + // Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true. if (HasExited) { @@ -130,7 +122,29 @@ namespace Emby.Server.Implementations.Diagnostics public void Dispose() { - _process?.Dispose(); + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + _process?.Dispose(); + } + + _disposed = true; + } + + private void OnProcessExited(object sender, EventArgs e) + { + _hasExited = true; + Exited?.Invoke(this, e); } } } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 6591d54c56..1673e37776 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -58,22 +58,23 @@ namespace Emby.Server.Implementations.Library private ILibraryPostScanTask[] PostscanTasks { get; set; } /// - /// Gets the intro providers. + /// Gets or sets the intro providers. /// /// The intro providers. private IIntroProvider[] IntroProviders { get; set; } /// - /// Gets the list of entity resolution ignore rules + /// Gets or sets the list of entity resolution ignore rules /// /// The entity resolution ignore rules. private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; } /// - /// Gets the list of currently registered entity resolvers + /// Gets or sets the list of currently registered entity resolvers /// /// The entity resolvers enumerable. private IItemResolver[] EntityResolvers { get; set; } + private IMultiItemResolver[] MultiItemResolvers { get; set; } /// @@ -83,7 +84,7 @@ namespace Emby.Server.Implementations.Library private IBaseItemComparer[] Comparers { get; set; } /// - /// Gets the active item repository + /// Gets or sets the active item repository /// /// The item repository. public IItemRepository ItemRepository { get; set; } @@ -133,12 +134,14 @@ namespace Emby.Server.Implementations.Library private readonly Func _providerManagerFactory; private readonly Func _userviewManager; public bool IsScanRunning { get; private set; } + private IServerApplicationHost _appHost; /// /// The _library items cache /// private readonly ConcurrentDictionary _libraryItemsCache; + /// /// Gets the library items cache. /// @@ -150,7 +153,8 @@ namespace Emby.Server.Implementations.Library /// /// Initializes a new instance of the class. /// - /// The logger. + /// The application host + /// The logger factory. /// The task manager. /// The user manager. /// The configuration manager. @@ -167,6 +171,7 @@ namespace Emby.Server.Implementations.Library Func providerManagerFactory, Func userviewManager) { + _appHost = appHost; _logger = loggerFactory.CreateLogger(nameof(LibraryManager)); _taskManager = taskManager; _userManager = userManager; @@ -176,7 +181,7 @@ namespace Emby.Server.Implementations.Library _fileSystem = fileSystem; _providerManagerFactory = providerManagerFactory; _userviewManager = userviewManager; - _appHost = appHost; + _libraryItemsCache = new ConcurrentDictionary(); ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated; @@ -191,8 +196,9 @@ namespace Emby.Server.Implementations.Library /// The resolvers. /// The intro providers. /// The item comparers. - /// The postscan tasks. - public void AddParts(IEnumerable rules, + /// The post scan tasks. + public void AddParts( + IEnumerable rules, IEnumerable resolvers, IEnumerable introProviders, IEnumerable itemComparers, @@ -203,24 +209,19 @@ namespace Emby.Server.Implementations.Library MultiItemResolvers = EntityResolvers.OfType().ToArray(); IntroProviders = introProviders.ToArray(); Comparers = itemComparers.ToArray(); - - PostscanTasks = postscanTasks.OrderBy(i => - { - var hasOrder = i as IHasOrder; - - return hasOrder == null ? 0 : hasOrder.Order; - - }).ToArray(); + PostscanTasks = postscanTasks.ToArray(); } /// /// The _root folder /// private volatile AggregateFolder _rootFolder; + /// /// The _root folder sync lock /// private readonly object _rootFolderSyncLock = new object(); + /// /// Gets the root folder. /// @@ -239,11 +240,13 @@ namespace Emby.Server.Implementations.Library } } } + return _rootFolder; } } private bool _wizardCompleted; + /// /// Records the configuration values. /// @@ -258,7 +261,7 @@ namespace Emby.Server.Implementations.Library /// /// The sender. /// The instance containing the event data. - void ConfigurationUpdated(object sender, EventArgs e) + private void ConfigurationUpdated(object sender, EventArgs e) { var config = ConfigurationManager.Configuration; @@ -335,12 +338,14 @@ namespace Emby.Server.Implementations.Library // channel no longer installed } } + options.DeleteFileLocation = false; } if (item is LiveTvProgram) { - _logger.LogDebug("Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}", + _logger.LogDebug( + "Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}", item.GetType().Name, item.Name ?? "Unknown name", item.Path ?? string.Empty, @@ -348,7 +353,8 @@ namespace Emby.Server.Implementations.Library } else { - _logger.LogInformation("Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}", + _logger.LogInformation( + "Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}", item.GetType().Name, item.Name ?? "Unknown name", item.Path ?? string.Empty, @@ -488,12 +494,13 @@ namespace Emby.Server.Implementations.Library { throw new ArgumentNullException(nameof(key)); } + if (type == null) { throw new ArgumentNullException(nameof(type)); } - if (key.StartsWith(ConfigurationManager.ApplicationPaths.ProgramDataPath)) + if (key.StartsWith(ConfigurationManager.ApplicationPaths.ProgramDataPath, StringComparison.Ordinal)) { // Try to normalize paths located underneath program-data in an attempt to make them more portable key = key.Substring(ConfigurationManager.ApplicationPaths.ProgramDataPath.Length) @@ -511,13 +518,11 @@ namespace Emby.Server.Implementations.Library return key.GetMD5(); } - public BaseItem ResolvePath(FileSystemMetadata fileInfo, - Folder parent = null) - { - return ResolvePath(fileInfo, new DirectoryService(_logger, _fileSystem), null, parent); - } + public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null) + => ResolvePath(fileInfo, new DirectoryService(_logger, _fileSystem), null, parent); - private BaseItem ResolvePath(FileSystemMetadata fileInfo, + private BaseItem ResolvePath( + FileSystemMetadata fileInfo, IDirectoryService directoryService, IItemResolver[] resolvers, Folder parent = null, @@ -572,7 +577,7 @@ namespace Emby.Server.Implementations.Library { _logger.LogError(ex, "Error in GetFilteredFileSystemEntries isPhysicalRoot: {0} IsVf: {1}", isPhysicalRoot, isVf); - files = new FileSystemMetadata[] { }; + files = Array.Empty(); } else { @@ -600,13 +605,7 @@ namespace Emby.Server.Implementations.Library } public bool IgnoreFile(FileSystemMetadata file, BaseItem parent) - { - if (EntityResolutionIgnoreRules.Any(r => r.ShouldIgnore(file, parent))) - { - return true; - } - return false; - } + => EntityResolutionIgnoreRules.Any(r => r.ShouldIgnore(file, parent)); public List NormalizeRootPathList(IEnumerable paths) { @@ -646,7 +645,8 @@ namespace Emby.Server.Implementations.Library return ResolvePaths(files, directoryService, parent, libraryOptions, collectionType, EntityResolvers); } - public IEnumerable ResolvePaths(IEnumerable files, + public IEnumerable ResolvePaths( + IEnumerable files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, @@ -672,6 +672,7 @@ namespace Emby.Server.Implementations.Library { ResolverHelper.SetInitialItemValues(item, parent, _fileSystem, this, directoryService); } + items.AddRange(ResolveFileList(result.ExtraFiles, directoryService, parent, collectionType, resolvers, libraryOptions)); return items; } @@ -681,7 +682,8 @@ namespace Emby.Server.Implementations.Library return ResolveFileList(fileList, directoryService, parent, collectionType, resolvers, libraryOptions); } - private IEnumerable ResolveFileList(IEnumerable fileList, + private IEnumerable ResolveFileList( + IEnumerable fileList, IDirectoryService directoryService, Folder parent, string collectionType, @@ -766,6 +768,7 @@ namespace Emby.Server.Implementations.Library private volatile UserRootFolder _userRootFolder; private readonly object _syncLock = new object(); + public Folder GetUserRootFolder() { if (_userRootFolder == null) @@ -810,8 +813,6 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(path)); } - //_logger.LogInformation("FindByPath {0}", path); - var query = new InternalItemsQuery { Path = path, @@ -885,7 +886,6 @@ namespace Emby.Server.Implementations.Library /// /// The value. /// Task{Year}. - /// public Year GetYear(int value) { if (value <= 0) @@ -1027,20 +1027,25 @@ namespace Emby.Server.Implementations.Library private async Task ValidateTopLibraryFolders(CancellationToken cancellationToken) { - var rootChildren = RootFolder.Children.ToList(); - rootChildren = GetUserRootFolder().Children.ToList(); - await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false); // Start by just validating the children of the root, but go no further - await RootFolder.ValidateChildren(new SimpleProgress(), cancellationToken, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), recursive: false); + await RootFolder.ValidateChildren( + new SimpleProgress(), + cancellationToken, + new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), + recursive: false).ConfigureAwait(false); await GetUserRootFolder().RefreshMetadata(cancellationToken).ConfigureAwait(false); - await GetUserRootFolder().ValidateChildren(new SimpleProgress(), cancellationToken, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), recursive: false).ConfigureAwait(false); + await GetUserRootFolder().ValidateChildren( + new SimpleProgress(), + cancellationToken, + new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), + recursive: false).ConfigureAwait(false); // Quickly scan CollectionFolders for changes - foreach (var folder in GetUserRootFolder().Children.OfType().ToList()) + foreach (var folder in GetUserRootFolder().Children.OfType()) { await folder.RefreshMetadata(cancellationToken).ConfigureAwait(false); } @@ -1204,7 +1209,7 @@ namespace Emby.Server.Implementations.Library private string GetCollectionType(string path) { return _fileSystem.GetFilePaths(path, new[] { ".collection" }, true, false) - .Select(i => Path.GetFileNameWithoutExtension(i)) + .Select(Path.GetFileNameWithoutExtension) .FirstOrDefault(i => !string.IsNullOrEmpty(i)); } @@ -1218,7 +1223,7 @@ namespace Emby.Server.Implementations.Library { if (id == Guid.Empty) { - throw new ArgumentException(nameof(id), "Guid can't be empty"); + throw new ArgumentException("Guid can't be empty", nameof(id)); } if (LibraryItemsCache.TryGetValue(id, out BaseItem item)) @@ -1386,17 +1391,7 @@ namespace Emby.Server.Implementations.Library var parents = query.AncestorIds.Select(i => GetItemById(i)).ToList(); - if (parents.All(i => - { - if (i is ICollectionFolder || i is UserView) - { - return true; - } - - //_logger.LogDebug("Query requires ancestor query due to type: " + i.GetType().Name); - return false; - - })) + if (parents.All(i => i is ICollectionFolder || i is UserView)) { // Optimize by querying against top level views query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray(); @@ -1452,17 +1447,7 @@ namespace Emby.Server.Implementations.Library private void SetTopParentIdsOrAncestors(InternalItemsQuery query, List parents) { - if (parents.All(i => - { - if (i is ICollectionFolder || i is UserView) - { - return true; - } - - //_logger.LogDebug("Query requires ancestor query due to type: " + i.GetType().Name); - return false; - - })) + if (parents.All(i => i is ICollectionFolder || i is UserView)) { // Optimize by querying against top level views query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray(); @@ -1511,11 +1496,9 @@ namespace Emby.Server.Implementations.Library private IEnumerable GetTopParentIdsForQuery(BaseItem item, User user) { - var view = item as UserView; - - if (view != null) + if (item is UserView view) { - if (string.Equals(view.ViewType, CollectionType.LiveTv)) + if (string.Equals(view.ViewType, CollectionType.LiveTv, StringComparison.Ordinal)) { return new[] { view.Id }; } @@ -1528,8 +1511,10 @@ namespace Emby.Server.Implementations.Library { return GetTopParentIdsForQuery(displayParent, user); } + return Array.Empty(); } + if (!view.ParentId.Equals(Guid.Empty)) { var displayParent = GetItemById(view.ParentId); @@ -1537,6 +1522,7 @@ namespace Emby.Server.Implementations.Library { return GetTopParentIdsForQuery(displayParent, user); } + return Array.Empty(); } @@ -1550,11 +1536,11 @@ namespace Emby.Server.Implementations.Library .Where(i => user.IsFolderGrouped(i.Id)) .SelectMany(i => GetTopParentIdsForQuery(i, user)); } + return Array.Empty(); } - var collectionFolder = item as CollectionFolder; - if (collectionFolder != null) + if (item is CollectionFolder collectionFolder) { return collectionFolder.PhysicalFolderIds; } @@ -1564,6 +1550,7 @@ namespace Emby.Server.Implementations.Library { return new[] { topParent.Id }; } + return Array.Empty(); } @@ -1760,19 +1747,16 @@ namespace Emby.Server.Implementations.Library { var comparer = Comparers.FirstOrDefault(c => string.Equals(name, c.Name, StringComparison.OrdinalIgnoreCase)); - if (comparer != null) + // If it requires a user, create a new one, and assign the user + if (comparer is IUserBaseItemComparer) { - // If it requires a user, create a new one, and assign the user - if (comparer is IUserBaseItemComparer) - { - var userComparer = (IUserBaseItemComparer)Activator.CreateInstance(comparer.GetType()); + var userComparer = (IUserBaseItemComparer)Activator.CreateInstance(comparer.GetType()); - userComparer.User = user; - userComparer.UserManager = _userManager; - userComparer.UserDataRepository = _userDataRepository; + userComparer.User = user; + userComparer.UserManager = _userManager; + userComparer.UserDataRepository = _userDataRepository; - return userComparer; - } + return userComparer; } return comparer; @@ -1783,7 +1767,6 @@ namespace Emby.Server.Implementations.Library /// /// The item. /// The parent item. - /// Task. public void CreateItem(BaseItem item, BaseItem parent) { CreateItems(new[] { item }, parent, CancellationToken.None); @@ -1793,20 +1776,23 @@ namespace Emby.Server.Implementations.Library /// Creates the items. /// /// The items. + /// The parent item /// The cancellation token. - /// Task. public void CreateItems(IEnumerable items, BaseItem parent, CancellationToken cancellationToken) { - ItemRepository.SaveItems(items, cancellationToken); + // Don't iterate multiple times + var itemsList = items.ToList(); + + ItemRepository.SaveItems(itemsList, cancellationToken); - foreach (var item in items) + foreach (var item in itemsList) { RegisterItem(item); } if (ItemAdded != null) { - foreach (var item in items) + foreach (var item in itemsList) { // With the live tv guide this just creates too much noise if (item.SourceType != SourceType.Library) @@ -1816,11 +1802,13 @@ namespace Emby.Server.Implementations.Library try { - ItemAdded(this, new ItemChangeEventArgs - { - Item = item, - Parent = parent ?? item.GetParent() - }); + ItemAdded( + this, + new ItemChangeEventArgs + { + Item = item, + Parent = parent ?? item.GetParent() + }); } catch (Exception ex) { @@ -1842,7 +1830,10 @@ namespace Emby.Server.Implementations.Library /// public void UpdateItems(IEnumerable items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) { - foreach (var item in items) + // Don't iterate multiple times + var itemsList = items.ToList(); + + foreach (var item in itemsList) { if (item.IsFileProtocol) { @@ -1854,14 +1845,11 @@ namespace Emby.Server.Implementations.Library RegisterItem(item); } - //var logName = item.LocationType == LocationType.Remote ? item.Name ?? item.Path : item.Path ?? item.Name; - //_logger.LogDebug("Saving {0} to database.", logName); - - ItemRepository.SaveItems(items, cancellationToken); + ItemRepository.SaveItems(itemsList, cancellationToken); if (ItemUpdated != null) { - foreach (var item in items) + foreach (var item in itemsList) { // With the live tv guide this just creates too much noise if (item.SourceType != SourceType.Library) @@ -1871,12 +1859,14 @@ namespace Emby.Server.Implementations.Library try { - ItemUpdated(this, new ItemChangeEventArgs - { - Item = item, - Parent = parent, - UpdateReason = updateReason - }); + ItemUpdated( + this, + new ItemChangeEventArgs + { + Item = item, + Parent = parent, + UpdateReason = updateReason + }); } catch (Exception ex) { @@ -1890,9 +1880,9 @@ namespace Emby.Server.Implementations.Library /// Updates the item. /// /// The item. + /// The parent item. /// The update reason. /// The cancellation token. - /// Task. public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) { UpdateItems(new [] { item }, parent, updateReason, cancellationToken); @@ -1902,17 +1892,20 @@ namespace Emby.Server.Implementations.Library /// Reports the item removed. /// /// The item. + /// The parent item. public void ReportItemRemoved(BaseItem item, BaseItem parent) { if (ItemRemoved != null) { try { - ItemRemoved(this, new ItemChangeEventArgs - { - Item = item, - Parent = parent - }); + ItemRemoved( + this, + new ItemChangeEventArgs + { + Item = item, + Parent = parent + }); } catch (Exception ex) { @@ -2038,8 +2031,7 @@ namespace Emby.Server.Implementations.Library public string GetConfiguredContentType(BaseItem item, bool inheritConfiguredPath) { - var collectionFolder = item as ICollectionFolder; - if (collectionFolder != null) + if (item is ICollectionFolder collectionFolder) { return collectionFolder.CollectionType; } @@ -2049,13 +2041,11 @@ namespace Emby.Server.Implementations.Library private string GetContentTypeOverride(string path, bool inherit) { - var nameValuePair = ConfigurationManager.Configuration.ContentTypes.FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path) || (inherit && !string.IsNullOrEmpty(i.Name) && _fileSystem.ContainsSubPath(i.Name, path))); - if (nameValuePair != null) - { - return nameValuePair.Value; - } - - return null; + var nameValuePair = ConfigurationManager.Configuration.ContentTypes + .FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path) + || (inherit && !string.IsNullOrEmpty(i.Name) + && _fileSystem.ContainsSubPath(i.Name, path))); + return nameValuePair?.Value; } private string GetTopFolderContentType(BaseItem item) @@ -2072,6 +2062,7 @@ namespace Emby.Server.Implementations.Library { break; } + item = parent; } @@ -2083,9 +2074,9 @@ namespace Emby.Server.Implementations.Library } private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24); - //private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromMinutes(1); - public UserView GetNamedView(User user, + public UserView GetNamedView( + User user, string name, string viewType, string sortName) @@ -2093,13 +2084,15 @@ namespace Emby.Server.Implementations.Library return GetNamedView(user, name, Guid.Empty, viewType, sortName); } - public UserView GetNamedView(string name, + public UserView GetNamedView( + string name, string viewType, string sortName) { - var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, - "views", - _fileSystem.GetValidFilename(viewType)); + var path = Path.Combine( + ConfigurationManager.ApplicationPaths.InternalMetadataPath, + "views", + _fileSystem.GetValidFilename(viewType)); var id = GetNewItemId(path + "_namedview_" + name, typeof(UserView)); @@ -2135,7 +2128,8 @@ namespace Emby.Server.Implementations.Library return item; } - public UserView GetNamedView(User user, + public UserView GetNamedView( + User user, string name, Guid parentId, string viewType, @@ -2164,10 +2158,10 @@ namespace Emby.Server.Implementations.Library Name = name, ViewType = viewType, ForcedSortName = sortName, - UserId = user.Id + UserId = user.Id, + DisplayParentId = parentId }; - item.DisplayParentId = parentId; CreateItem(item, null); @@ -2184,20 +2178,24 @@ namespace Emby.Server.Implementations.Library if (refresh) { - _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) - { - // Need to force save to increment DateLastSaved - ForceSave = true + _providerManagerFactory().QueueRefresh( + item.Id, + new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) + { + // Need to force save to increment DateLastSaved + ForceSave = true - }, RefreshPriority.Normal); + }, + RefreshPriority.Normal); } return item; } - public UserView GetShadowView(BaseItem parent, - string viewType, - string sortName) + public UserView GetShadowView( + BaseItem parent, + string viewType, + string sortName) { if (parent == null) { @@ -2248,18 +2246,21 @@ namespace Emby.Server.Implementations.Library if (refresh) { - _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) - { - // Need to force save to increment DateLastSaved - ForceSave = true - - }, RefreshPriority.Normal); + _providerManagerFactory().QueueRefresh( + item.Id, + new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) + { + // Need to force save to increment DateLastSaved + ForceSave = true + }, + RefreshPriority.Normal); } return item; } - public UserView GetNamedView(string name, + public UserView GetNamedView( + string name, Guid parentId, string viewType, string sortName, @@ -2322,17 +2323,21 @@ namespace Emby.Server.Implementations.Library if (refresh) { - _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) - { - // Need to force save to increment DateLastSaved - ForceSave = true - }, RefreshPriority.Normal); + _providerManagerFactory().QueueRefresh( + item.Id, + new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) + { + // Need to force save to increment DateLastSaved + ForceSave = true + }, + RefreshPriority.Normal); } return item; } - public void AddExternalSubtitleStreams(List streams, + public void AddExternalSubtitleStreams( + List streams, string videoPath, string[] files) { @@ -2436,6 +2441,7 @@ namespace Emby.Server.Implementations.Library { changed = true; } + episode.IndexNumber = episodeInfo.EpisodeNumber; } @@ -2445,6 +2451,7 @@ namespace Emby.Server.Implementations.Library { changed = true; } + episode.IndexNumberEnd = episodeInfo.EndingEpsiodeNumber; } @@ -2454,6 +2461,7 @@ namespace Emby.Server.Implementations.Library { changed = true; } + episode.ParentIndexNumber = episodeInfo.SeasonNumber; } } @@ -2483,6 +2491,7 @@ namespace Emby.Server.Implementations.Library private NamingOptions _namingOptions; private string[] _videoFileExtensions; + private NamingOptions GetNamingOptionsInternal() { if (_namingOptions == null) @@ -2679,7 +2688,7 @@ namespace Emby.Server.Implementations.Library var newPath = path.Replace(from, to, StringComparison.OrdinalIgnoreCase); var changed = false; - if (!string.Equals(newPath, path)) + if (!string.Equals(newPath, path, StringComparison.Ordinal)) { if (to.IndexOf('/') != -1) { @@ -2803,6 +2812,7 @@ namespace Emby.Server.Implementations.Library { continue; } + throw; } } @@ -2907,6 +2917,7 @@ namespace Emby.Server.Implementations.Library } private const string ShortcutFileExtension = ".mblink"; + public void AddMediaPath(string virtualFolderName, MediaPathInfo pathInfo) { AddMediaPathInternal(virtualFolderName, pathInfo, true); @@ -2923,7 +2934,7 @@ namespace Emby.Server.Implementations.Library if (string.IsNullOrWhiteSpace(path)) { - throw new ArgumentNullException(nameof(path)); + throw new ArgumentException(nameof(path)); } if (!Directory.Exists(path)) diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 762649b716..8c49b64055 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -11,7 +11,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; @@ -35,7 +34,6 @@ namespace Emby.Server.Implementations.Localization private readonly Dictionary> _allParentalRatings = new Dictionary>(StringComparer.OrdinalIgnoreCase); - private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; private readonly ILogger _logger; private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly; @@ -44,40 +42,38 @@ namespace Emby.Server.Implementations.Localization /// Initializes a new instance of the class. /// /// The configuration manager. - /// The file system. /// The json serializer. + /// The logger factory public LocalizationManager( IServerConfigurationManager configurationManager, - IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILoggerFactory loggerFactory) { _configurationManager = configurationManager; - _fileSystem = fileSystem; _jsonSerializer = jsonSerializer; _logger = loggerFactory.CreateLogger(nameof(LocalizationManager)); } public async Task LoadAll() { - const string ratingsResource = "Emby.Server.Implementations.Localization.Ratings."; + const string RatingsResource = "Emby.Server.Implementations.Localization.Ratings."; // Extract from the assembly foreach (var resource in _assembly.GetManifestResourceNames()) { - if (!resource.StartsWith(ratingsResource)) + if (!resource.StartsWith(RatingsResource, StringComparison.Ordinal)) { continue; } - string countryCode = resource.Substring(ratingsResource.Length, 2); + string countryCode = resource.Substring(RatingsResource.Length, 2); var dict = new Dictionary(StringComparer.OrdinalIgnoreCase); using (var str = _assembly.GetManifestResourceStream(resource)) using (var reader = new StreamReader(str)) { string line; - while ((line = await reader.ReadLineAsync()) != null) + while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) != null) { if (string.IsNullOrWhiteSpace(line)) { @@ -102,7 +98,7 @@ namespace Emby.Server.Implementations.Localization _allParentalRatings[countryCode] = dict; } - await LoadCultures(); + await LoadCultures().ConfigureAwait(false); } public string NormalizeFormKD(string text) @@ -121,14 +117,14 @@ namespace Emby.Server.Implementations.Localization { List list = new List(); - const string path = "Emby.Server.Implementations.Localization.iso6392.txt"; + const string ResourcePath = "Emby.Server.Implementations.Localization.iso6392.txt"; - using (var stream = _assembly.GetManifestResourceStream(path)) + using (var stream = _assembly.GetManifestResourceStream(ResourcePath)) using (var reader = new StreamReader(stream)) { while (!reader.EndOfStream) { - var line = await reader.ReadLineAsync(); + var line = await reader.ReadLineAsync().ConfigureAwait(false); if (string.IsNullOrWhiteSpace(line)) { @@ -154,11 +150,11 @@ namespace Emby.Server.Implementations.Localization string[] threeletterNames; if (string.IsNullOrWhiteSpace(parts[1])) { - threeletterNames = new [] { parts[0] }; + threeletterNames = new[] { parts[0] }; } else { - threeletterNames = new [] { parts[0], parts[1] }; + threeletterNames = new[] { parts[0], parts[1] }; } list.Add(new CultureDto @@ -218,6 +214,7 @@ namespace Emby.Server.Implementations.Localization /// Gets the ratings. /// /// The country code. + /// The ratings private Dictionary GetRatings(string countryCode) { _allParentalRatings.TryGetValue(countryCode, out var value); @@ -227,9 +224,12 @@ namespace Emby.Server.Implementations.Localization private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" }; + /// /// /// Gets the rating level. /// + /// Rating field + /// The rating level> public int? GetRatingLevel(string rating) { if (string.IsNullOrEmpty(rating)) @@ -301,6 +301,7 @@ namespace Emby.Server.Implementations.Localization { culture = _configurationManager.Configuration.UICulture; } + if (string.IsNullOrEmpty(culture)) { culture = DefaultCulture; @@ -346,8 +347,8 @@ namespace Emby.Server.Implementations.Localization var namespaceName = GetType().Namespace + "." + prefix; - await CopyInto(dictionary, namespaceName + "." + baseFilename); - await CopyInto(dictionary, namespaceName + "." + GetResourceFilename(culture)); + await CopyInto(dictionary, namespaceName + "." + baseFilename).ConfigureAwait(false); + await CopyInto(dictionary, namespaceName + "." + GetResourceFilename(culture)).ConfigureAwait(false); return dictionary; } @@ -359,7 +360,7 @@ namespace Emby.Server.Implementations.Localization // If a Culture doesn't have a translation the stream will be null and it defaults to en-us further up the chain if (stream != null) { - var dict = await _jsonSerializer.DeserializeFromStreamAsync>(stream); + var dict = await _jsonSerializer.DeserializeFromStreamAsync>(stream).ConfigureAwait(false); foreach (var key in dict.Keys) { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 03e7b26545..985748caf2 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -116,14 +116,14 @@ namespace Emby.Server.Implementations.Session _authRepo = authRepo; _deviceManager = deviceManager; _mediaSourceManager = mediaSourceManager; - _deviceManager.DeviceOptionsUpdated += _deviceManager_DeviceOptionsUpdated; + _deviceManager.DeviceOptionsUpdated += OnDeviceManagerDeviceOptionsUpdated; } - private void _deviceManager_DeviceOptionsUpdated(object sender, GenericEventArgs> e) + private void OnDeviceManagerDeviceOptionsUpdated(object sender, GenericEventArgs> e) { foreach (var session in Sessions) { - if (string.Equals(session.DeviceId, e.Argument.Item1)) + if (string.Equals(session.DeviceId, e.Argument.Item1, StringComparison.Ordinal)) { if (!string.IsNullOrWhiteSpace(e.Argument.Item2.CustomName)) { @@ -138,11 +138,29 @@ namespace Emby.Server.Implementations.Session } } - private bool _disposed; + private bool _disposed = false; + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + // TODO: dispose stuff + } + + _deviceManager.DeviceOptionsUpdated -= OnDeviceManagerDeviceOptionsUpdated; + _disposed = true; - _deviceManager.DeviceOptionsUpdated -= _deviceManager_DeviceOptionsUpdated; } public void CheckDisposed() @@ -157,7 +175,7 @@ namespace Emby.Server.Implementations.Session /// Gets all connections. /// /// All connections. - public IEnumerable Sessions => _activeConnections.Values.OrderByDescending(c => c.LastActivityDate).ToList(); + public IEnumerable Sessions => _activeConnections.Values.OrderByDescending(c => c.LastActivityDate); private void OnSessionStarted(SessionInfo info) { @@ -171,20 +189,27 @@ namespace Emby.Server.Implementations.Session } } - EventHelper.QueueEventIfNotNull(SessionStarted, this, new SessionEventArgs - { - SessionInfo = info - - }, _logger); + EventHelper.QueueEventIfNotNull( + SessionStarted, + this, + new SessionEventArgs + { + SessionInfo = info + }, + _logger); } private void OnSessionEnded(SessionInfo info) { - EventHelper.QueueEventIfNotNull(SessionEnded, this, new SessionEventArgs - { - SessionInfo = info + EventHelper.QueueEventIfNotNull( + SessionEnded, + this, + new SessionEventArgs + { + SessionInfo = info - }, _logger); + }, + _logger); info.Dispose(); } @@ -192,9 +217,6 @@ namespace Emby.Server.Implementations.Session public void UpdateDeviceName(string sessionId, string deviceName) { var session = GetSession(sessionId); - - var key = GetSessionKey(session.Client, session.DeviceId); - if (session != null) { session.DeviceName = deviceName; @@ -210,10 +232,10 @@ namespace Emby.Server.Implementations.Session /// Name of the device. /// The remote end point. /// The user. - /// Task. + /// SessionInfo. /// user - /// - public SessionInfo LogSessionActivity(string appName, + public SessionInfo LogSessionActivity( + string appName, string appVersion, string deviceId, string deviceName, @@ -226,10 +248,12 @@ namespace Emby.Server.Implementations.Session { throw new ArgumentNullException(nameof(appName)); } + if (string.IsNullOrEmpty(appVersion)) { throw new ArgumentNullException(nameof(appVersion)); } + if (string.IsNullOrEmpty(deviceId)) { throw new ArgumentNullException(nameof(deviceId)); @@ -260,10 +284,12 @@ namespace Emby.Server.Implementations.Session if ((activityDate - lastActivityDate).TotalSeconds > 10) { - SessionActivity?.Invoke(this, new SessionEventArgs - { - SessionInfo = session - }); + SessionActivity?.Invoke( + this, + new SessionEventArgs + { + SessionInfo = session + }); } return session; @@ -304,6 +330,7 @@ namespace Emby.Server.Implementations.Session /// /// Updates the now playing item id. /// + /// Task. private async Task UpdateNowPlayingItem(SessionInfo session, PlaybackProgressInfo info, BaseItem libraryItem, bool updateLastCheckInTime) { if (string.IsNullOrEmpty(info.MediaSourceId)) @@ -418,7 +445,7 @@ namespace Emby.Server.Implementations.Session }); sessionInfo.UserId = user == null ? Guid.Empty : user.Id; - sessionInfo.UserName = user == null ? null : user.Name; + sessionInfo.UserName = user?.Name; sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user, ImageType.Primary); sessionInfo.RemoteEndPoint = remoteEndPoint; sessionInfo.Client = appName; @@ -432,7 +459,7 @@ namespace Emby.Server.Implementations.Session if (user == null) { - sessionInfo.AdditionalUsers = new SessionUserInfo[] { }; + sessionInfo.AdditionalUsers = Array.Empty(); } return sessionInfo; @@ -449,9 +476,9 @@ namespace Emby.Server.Implementations.Session ServerId = _appHost.SystemId }; - var username = user == null ? null : user.Name; + var username = user?.Name; - sessionInfo.UserId = user == null ? Guid.Empty : user.Id; + sessionInfo.UserId = user?.Id ?? Guid.Empty; sessionInfo.UserName = username; sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user, ImageType.Primary); sessionInfo.RemoteEndPoint = remoteEndPoint; @@ -508,6 +535,7 @@ namespace Emby.Server.Implementations.Session _idleTimer = new Timer(CheckForIdlePlayback, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)); } } + private void StopIdleCheckTimer() { if (_idleTimer != null) @@ -539,9 +567,9 @@ namespace Emby.Server.Implementations.Session Item = session.NowPlayingItem, ItemId = session.NowPlayingItem == null ? Guid.Empty : session.NowPlayingItem.Id, SessionId = session.Id, - MediaSourceId = session.PlayState == null ? null : session.PlayState.MediaSourceId, - PositionTicks = session.PlayState == null ? null : session.PlayState.PositionTicks - }); + MediaSourceId = session.PlayState?.MediaSourceId, + PositionTicks = session.PlayState?.PositionTicks + }).ConfigureAwait(false); } catch (Exception ex) { @@ -616,18 +644,22 @@ namespace Emby.Server.Implementations.Session // Nothing to save here // Fire events to inform plugins - EventHelper.QueueEventIfNotNull(PlaybackStart, this, new PlaybackProgressEventArgs - { - Item = libraryItem, - Users = users, - MediaSourceId = info.MediaSourceId, - MediaInfo = info.Item, - DeviceName = session.DeviceName, - ClientName = session.Client, - DeviceId = session.DeviceId, - Session = session - - }, _logger); + EventHelper.QueueEventIfNotNull( + PlaybackStart, + this, + new PlaybackProgressEventArgs + { + Item = libraryItem, + Users = users, + MediaSourceId = info.MediaSourceId, + MediaInfo = info.Item, + DeviceName = session.DeviceName, + ClientName = session.Client, + DeviceId = session.DeviceId, + Session = session + + }, + _logger); StartIdleCheckTimer(); } @@ -667,6 +699,7 @@ namespace Emby.Server.Implementations.Session /// /// Used to report playback progress for an item /// + /// Task. public async Task OnPlaybackProgress(PlaybackProgressInfo info, bool isAutomated) { CheckDisposed(); @@ -695,21 +728,23 @@ namespace Emby.Server.Implementations.Session } } - PlaybackProgress?.Invoke(this, new PlaybackProgressEventArgs - { - Item = libraryItem, - Users = users, - PlaybackPositionTicks = session.PlayState.PositionTicks, - MediaSourceId = session.PlayState.MediaSourceId, - MediaInfo = info.Item, - DeviceName = session.DeviceName, - ClientName = session.Client, - DeviceId = session.DeviceId, - IsPaused = info.IsPaused, - PlaySessionId = info.PlaySessionId, - IsAutomated = isAutomated, - Session = session - }); + PlaybackProgress?.Invoke( + this, + new PlaybackProgressEventArgs + { + Item = libraryItem, + Users = users, + PlaybackPositionTicks = session.PlayState.PositionTicks, + MediaSourceId = session.PlayState.MediaSourceId, + MediaInfo = info.Item, + DeviceName = session.DeviceName, + ClientName = session.Client, + DeviceId = session.DeviceId, + IsPaused = info.IsPaused, + PlaySessionId = info.PlaySessionId, + IsAutomated = isAutomated, + Session = session + }); if (!isAutomated) { @@ -830,8 +865,7 @@ namespace Emby.Server.Implementations.Session { MediaSourceInfo mediaSource = null; - var hasMediaSources = libraryItem as IHasMediaSources; - if (hasMediaSources != null) + if (libraryItem is IHasMediaSources hasMediaSources) { mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false); } @@ -848,7 +882,8 @@ namespace Emby.Server.Implementations.Session { var msString = info.PositionTicks.HasValue ? (info.PositionTicks.Value / 10000).ToString(CultureInfo.InvariantCulture) : "unknown"; - _logger.LogInformation("Playback stopped reported by app {0} {1} playing {2}. Stopped at {3} ms", + _logger.LogInformation( + "Playback stopped reported by app {0} {1} playing {2}. Stopped at {3} ms", session.Client, session.ApplicationVersion, info.Item.Name, @@ -887,20 +922,24 @@ namespace Emby.Server.Implementations.Session } } - EventHelper.QueueEventIfNotNull(PlaybackStopped, this, new PlaybackStopEventArgs - { - Item = libraryItem, - Users = users, - PlaybackPositionTicks = info.PositionTicks, - PlayedToCompletion = playedToCompletion, - MediaSourceId = info.MediaSourceId, - MediaInfo = info.Item, - DeviceName = session.DeviceName, - ClientName = session.Client, - DeviceId = session.DeviceId, - Session = session + EventHelper.QueueEventIfNotNull( + PlaybackStopped, + this, + new PlaybackStopEventArgs + { + Item = libraryItem, + Users = users, + PlaybackPositionTicks = info.PositionTicks, + PlayedToCompletion = playedToCompletion, + MediaSourceId = info.MediaSourceId, + MediaInfo = info.Item, + DeviceName = session.DeviceName, + ClientName = session.Client, + DeviceId = session.DeviceId, + Session = session - }, _logger); + }, + _logger); } private bool OnPlaybackStopped(User user, BaseItem item, long? positionTicks, bool playbackFailed) @@ -936,11 +975,10 @@ namespace Emby.Server.Implementations.Session /// The session identifier. /// if set to true [throw on missing]. /// SessionInfo. - /// + /// sessionId private SessionInfo GetSession(string sessionId, bool throwOnMissing = true) { - var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId)); - + var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId, StringComparison.Ordinal)); if (session == null && throwOnMissing) { throw new ResourceNotFoundException(string.Format("Session {0} not found.", sessionId)); @@ -952,7 +990,7 @@ namespace Emby.Server.Implementations.Session private SessionInfo GetSessionToRemoteControl(string sessionId) { // Accept either device id or session id - var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId)); + var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId, StringComparison.Ordinal)); if (session == null) { @@ -1061,10 +1099,12 @@ namespace Emby.Server.Implementations.Session var series = episode.Series; if (series != null) { - var episodes = series.GetEpisodes(user, new DtoOptions(false) - { - EnableImages = false - }) + var episodes = series.GetEpisodes( + user, + new DtoOptions(false) + { + EnableImages = false + }) .Where(i => !i.IsVirtualItem) .SkipWhile(i => i.Id != episode.Id) .ToList(); @@ -1100,9 +1140,7 @@ namespace Emby.Server.Implementations.Session return new List(); } - var byName = item as IItemByName; - - if (byName != null) + if (item is IItemByName byName) { return byName.GetTaggedItems(new InternalItemsQuery(user) { @@ -1152,7 +1190,7 @@ namespace Emby.Server.Implementations.Session if (item == null) { - _logger.LogError("A non-existant item Id {0} was passed into TranslateItemForInstantMix", id); + _logger.LogError("A non-existent item Id {0} was passed into TranslateItemForInstantMix", id); return new List(); } @@ -1163,13 +1201,15 @@ namespace Emby.Server.Implementations.Session { var generalCommand = new GeneralCommand { - Name = GeneralCommandType.DisplayContent.ToString() + Name = GeneralCommandType.DisplayContent.ToString(), + Arguments = + { + ["ItemId"] = command.ItemId, + ["ItemName"] = command.ItemName, + ["ItemType"] = command.ItemType + } }; - generalCommand.Arguments["ItemId"] = command.ItemId; - generalCommand.Arguments["ItemName"] = command.ItemName; - generalCommand.Arguments["ItemType"] = command.ItemType; - return SendGeneralCommand(controllingSessionId, sessionId, generalCommand, cancellationToken); } @@ -1410,7 +1450,8 @@ namespace Emby.Server.Implementations.Session var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName); - var session = LogSessionActivity(request.App, + var session = LogSessionActivity( + request.App, request.AppVersion, request.DeviceId, request.DeviceName, @@ -1454,9 +1495,9 @@ namespace Emby.Server.Implementations.Session { Logout(auth); } - catch + catch (Exception ex) { - + _logger.LogError(ex, "Error while logging out."); } } } @@ -1572,7 +1613,8 @@ namespace Emby.Server.Implementations.Session ReportCapabilities(session, capabilities, true); } - private void ReportCapabilities(SessionInfo session, + private void ReportCapabilities( + SessionInfo session, ClientCapabilities capabilities, bool saveCapabilities) { @@ -1580,10 +1622,12 @@ namespace Emby.Server.Implementations.Session if (saveCapabilities) { - CapabilitiesChanged?.Invoke(this, new SessionEventArgs - { - SessionInfo = session - }); + CapabilitiesChanged?.Invoke( + this, + new SessionEventArgs + { + SessionInfo = session + }); try { diff --git a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs index 535f123f90..0804b01fca 100644 --- a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs +++ b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs @@ -32,7 +32,7 @@ namespace Emby.Server.Implementations.Sorting { var audio = x as IHasAlbumArtist; - return audio != null ? audio.AlbumArtists.FirstOrDefault() : null; + return audio?.AlbumArtists.FirstOrDefault(); } /// diff --git a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs index 46e0dd9186..504b6d2838 100644 --- a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs @@ -18,17 +18,17 @@ namespace Emby.Server.Implementations.Sorting return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); } - private static string GetValue(BaseItem item) - { - var hasSeries = item as IHasSeries; - - return hasSeries != null ? hasSeries.FindSeriesSortName() : null; - } - /// /// Gets the name. /// /// The name. public string Name => ItemSortBy.SeriesSortName; + + private static string GetValue(BaseItem item) + { + var hasSeries = item as IHasSeries; + + return hasSeries?.FindSeriesSortName(); + } } } diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 69673a49c1..49f8c6ace0 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -165,6 +165,7 @@ namespace MediaBrowser.Api { options.ImageTypeLimit = hasDtoOptions.ImageTypeLimit.Value; } + if (hasDtoOptions.EnableUserData.HasValue) { options.EnableUserData = hasDtoOptions.EnableUserData.Value; @@ -307,7 +308,7 @@ namespace MediaBrowser.Api return pathInfo[index]; } - private string[] Parse(string pathUri) + private static string[] Parse(string pathUri) { var actionParts = pathUri.Split(new[] { "://" }, StringSplitOptions.None); @@ -329,38 +330,32 @@ namespace MediaBrowser.Api /// protected BaseItem GetItemByName(string name, string type, ILibraryManager libraryManager, DtoOptions dtoOptions) { - BaseItem item; - - if (type.IndexOf("Person", StringComparison.OrdinalIgnoreCase) == 0) - { - item = GetPerson(name, libraryManager, dtoOptions); - } - else if (type.IndexOf("Artist", StringComparison.OrdinalIgnoreCase) == 0) + if (type.Equals("Person", StringComparison.OrdinalIgnoreCase)) { - item = GetArtist(name, libraryManager, dtoOptions); + return GetPerson(name, libraryManager, dtoOptions); } - else if (type.IndexOf("Genre", StringComparison.OrdinalIgnoreCase) == 0) + else if (type.Equals("Artist", StringComparison.OrdinalIgnoreCase)) { - item = GetGenre(name, libraryManager, dtoOptions); + return GetArtist(name, libraryManager, dtoOptions); } - else if (type.IndexOf("MusicGenre", StringComparison.OrdinalIgnoreCase) == 0) + else if (type.Equals("Genre", StringComparison.OrdinalIgnoreCase)) { - item = GetMusicGenre(name, libraryManager, dtoOptions); + return GetGenre(name, libraryManager, dtoOptions); } - else if (type.IndexOf("Studio", StringComparison.OrdinalIgnoreCase) == 0) + else if (type.Equals("MusicGenre", StringComparison.OrdinalIgnoreCase)) { - item = GetStudio(name, libraryManager, dtoOptions); + return GetMusicGenre(name, libraryManager, dtoOptions); } - else if (type.IndexOf("Year", StringComparison.OrdinalIgnoreCase) == 0) + else if (type.Equals("Studio", StringComparison.OrdinalIgnoreCase)) { - item = libraryManager.GetYear(int.Parse(name)); + return GetStudio(name, libraryManager, dtoOptions); } - else + else if (type.Equals("Year", StringComparison.OrdinalIgnoreCase)) { - throw new ArgumentException(); + return libraryManager.GetYear(int.Parse(name)); } - return item; + throw new ArgumentException("Invalid type", nameof(type)); } } } diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs index 7a8455ff26..a30f8adfed 100644 --- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs +++ b/MediaBrowser.Api/UserLibrary/ArtistsService.cs @@ -87,11 +87,6 @@ namespace MediaBrowser.Api.UserLibrary /// System.Object. public object Get(GetArtists request) { - if (string.IsNullOrWhiteSpace(request.IncludeItemTypes)) - { - //request.IncludeItemTypes = "Audio,MusicVideo"; - } - return GetResultSlim(request); } @@ -102,11 +97,6 @@ namespace MediaBrowser.Api.UserLibrary /// System.Object. public object Get(GetAlbumArtists request) { - if (string.IsNullOrWhiteSpace(request.IncludeItemTypes)) - { - //request.IncludeItemTypes = "Audio,MusicVideo"; - } - var result = GetResultSlim(request); return ToOptimizedResult(result); diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index f26087fda3..860ea13cf6 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -942,10 +942,7 @@ namespace MediaBrowser.Providers.Manager _activeRefreshes[id] = 0; } - if (RefreshStarted != null) - { - RefreshStarted(this, new GenericEventArgs(item)); - } + RefreshStarted?.Invoke(this, new GenericEventArgs(item)); } public void OnRefreshComplete(BaseItem item) @@ -956,10 +953,7 @@ namespace MediaBrowser.Providers.Manager _activeRefreshes.Remove(item.Id); } - if (RefreshCompleted != null) - { - RefreshCompleted(this, new GenericEventArgs(item)); - } + RefreshCompleted?.Invoke(this, new GenericEventArgs(item)); } public double? GetRefreshProgress(Guid id) @@ -986,10 +980,7 @@ namespace MediaBrowser.Providers.Manager { _activeRefreshes[id] = progress; - if (RefreshProgress != null) - { - RefreshProgress(this, new GenericEventArgs>(new Tuple(item, progress))); - } + RefreshProgress?.Invoke(this, new GenericEventArgs>(new Tuple(item, progress))); } else { @@ -1079,17 +1070,14 @@ namespace MediaBrowser.Providers.Manager await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false); // Collection folders don't validate their children so we'll have to simulate that here - var collectionFolder = item as CollectionFolder; - if (collectionFolder != null) + if (item is CollectionFolder collectionFolder) { await RefreshCollectionFolderChildren(options, collectionFolder, cancellationToken).ConfigureAwait(false); } else { - var folder = item as Folder; - - if (folder != null) + if (item is Folder folder) { await folder.ValidateChildren(new SimpleProgress(), cancellationToken, options).ConfigureAwait(false); } @@ -1098,16 +1086,11 @@ namespace MediaBrowser.Providers.Manager private async Task RefreshCollectionFolderChildren(MetadataRefreshOptions options, CollectionFolder collectionFolder, CancellationToken cancellationToken) { - foreach (var child in collectionFolder.GetPhysicalFolders().ToList()) + foreach (var child in collectionFolder.GetPhysicalFolders()) { await child.RefreshMetadata(options, cancellationToken).ConfigureAwait(false); - if (child.IsFolder) - { - var folder = (Folder)child; - - await folder.ValidateChildren(new SimpleProgress(), cancellationToken, options, true).ConfigureAwait(false); - } + await child.ValidateChildren(new SimpleProgress(), cancellationToken, options, true).ConfigureAwait(false); } } @@ -1116,20 +1099,18 @@ namespace MediaBrowser.Providers.Manager var albums = _libraryManagerFactory() .GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { typeof(MusicAlbum).Name }, + IncludeItemTypes = new[] { nameof(MusicAlbum) }, ArtistIds = new[] { item.Id }, DtoOptions = new DtoOptions(false) { EnableImages = false } }) - .OfType() - .ToList(); + .OfType(); var musicArtists = albums .Select(i => i.MusicArtist) - .Where(i => i != null) - .ToList(); + .Where(i => i != null); var musicArtistRefreshTasks = musicArtists.Select(i => i.ValidateChildren(new SimpleProgress(), cancellationToken, options, true)); diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index aee9921034..f86cdeab9f 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -53,7 +53,7 @@ namespace MediaBrowser.Providers.Music var releaseId = searchInfo.GetReleaseId(); var releaseGroupId = searchInfo.GetReleaseGroupId(); - string url = null; + string url; var isNameSearch = false; bool forceMusicBrainzProper = false; @@ -100,10 +100,10 @@ namespace MediaBrowser.Providers.Music } } - return new List(); + return Enumerable.Empty(); } - private List GetResultsFromResponse(Stream stream) + private IEnumerable GetResultsFromResponse(Stream stream) { using (var oReader = new StreamReader(stream, Encoding.UTF8)) { @@ -149,7 +149,7 @@ namespace MediaBrowser.Providers.Music return result; - }).ToList(); + }); } } } @@ -301,7 +301,7 @@ namespace MediaBrowser.Providers.Music public List> Artists = new List>(); - public static List Parse(XmlReader reader) + public static IEnumerable Parse(XmlReader reader) { reader.MoveToContent(); reader.Read(); @@ -338,13 +338,11 @@ namespace MediaBrowser.Providers.Music } } - return new List(); + return Enumerable.Empty(); } - private static List ParseReleaseList(XmlReader reader) + private static IEnumerable ParseReleaseList(XmlReader reader) { - var list = new List(); - reader.MoveToContent(); reader.Read(); @@ -369,7 +367,7 @@ namespace MediaBrowser.Providers.Music var release = ParseRelease(subReader, releaseId); if (release != null) { - list.Add(release); + yield return release; } } break; @@ -386,8 +384,6 @@ namespace MediaBrowser.Providers.Music reader.Read(); } } - - return list; } private static ReleaseResult ParseRelease(XmlReader reader, string releaseId) @@ -552,7 +548,7 @@ namespace MediaBrowser.Providers.Music return (null, null); } - private static ValueTuple ParseArtistArtistCredit(XmlReader reader, string artistId) + private static (string name, string id) ParseArtistArtistCredit(XmlReader reader, string artistId) { reader.MoveToContent(); reader.Read(); @@ -586,7 +582,7 @@ namespace MediaBrowser.Providers.Music } } - return new ValueTuple(name, artistId); + return (name, artistId); } private async Task GetReleaseIdFromReleaseGroupId(string releaseGroupId, CancellationToken cancellationToken) diff --git a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs index 924b712507..55aab7778a 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs @@ -50,7 +50,7 @@ namespace MediaBrowser.Providers.Music { using (var stream = response.Content) { - var results = GetResultsFromResponse(stream); + var results = GetResultsFromResponse(stream).ToList(); if (results.Count > 0) { @@ -74,10 +74,10 @@ namespace MediaBrowser.Providers.Music } } - return new List(); + return Enumerable.Empty(); } - private List GetResultsFromResponse(Stream stream) + private IEnumerable GetResultsFromResponse(Stream stream) { using (var oReader = new StreamReader(stream, Encoding.UTF8)) { @@ -126,15 +126,13 @@ namespace MediaBrowser.Providers.Music } } - return new List(); + return Enumerable.Empty(); } } } - private List ParseArtistList(XmlReader reader) + private IEnumerable ParseArtistList(XmlReader reader) { - var list = new List(); - reader.MoveToContent(); reader.Read(); @@ -159,7 +157,7 @@ namespace MediaBrowser.Providers.Music var artist = ParseArtist(subReader, mbzId); if (artist != null) { - list.Add(artist); + yield return artist; } } break; @@ -176,8 +174,6 @@ namespace MediaBrowser.Providers.Music reader.Read(); } } - - return list; } private RemoteSearchResult ParseArtist(XmlReader reader, string artistId) @@ -277,7 +273,7 @@ namespace MediaBrowser.Providers.Music /// /// The name. /// System.String. - private string UrlEncode(string name) + private static string UrlEncode(string name) { return WebUtility.UrlEncode(name); } -- cgit v1.2.3 From 6566c9136057f7b272b0d35501ed85034034d11e Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 25 Mar 2019 21:27:03 +0100 Subject: Seperate changes from #1023 The unrelated changes from #1023 (and more) --- .../Activity/ActivityLogEntryPoint.cs | 33 ++++++--- Emby.Server.Implementations/ApplicationHost.cs | 5 +- .../Channels/ChannelManager.cs | 9 --- Emby.Server.Implementations/Dto/DtoService.cs | 28 +++----- .../Security/AuthenticationRepository.cs | 4 -- .../Services/StringMapTypeDeserializer.cs | 6 +- .../Session/SessionManager.cs | 78 +++++++--------------- MediaBrowser.Controller/Entities/UserView.cs | 5 +- .../Entities/UserViewBuilder.cs | 12 ++-- 9 files changed, 72 insertions(+), 108 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 98cd97c318..f32ad7196e 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -3,12 +3,10 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; using MediaBrowser.Controller; using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -29,31 +27,39 @@ namespace Emby.Server.Implementations.Activity { public class ActivityLogEntryPoint : IServerEntryPoint { + private readonly ILogger _logger; private readonly IInstallationManager _installationManager; private readonly ISessionManager _sessionManager; private readonly ITaskManager _taskManager; private readonly IActivityManager _activityManager; private readonly ILocalizationManager _localization; - private readonly ILibraryManager _libraryManager; private readonly ISubtitleManager _subManager; private readonly IUserManager _userManager; - private readonly IServerConfigurationManager _config; private readonly IServerApplicationHost _appHost; private readonly IDeviceManager _deviceManager; - public ActivityLogEntryPoint(ISessionManager sessionManager, IDeviceManager deviceManager, ITaskManager taskManager, IActivityManager activityManager, ILocalizationManager localization, IInstallationManager installationManager, ILibraryManager libraryManager, ISubtitleManager subManager, IUserManager userManager, IServerConfigurationManager config, IServerApplicationHost appHost) + public ActivityLogEntryPoint( + ILogger logger, + ISessionManager sessionManager, + IDeviceManager deviceManager, + ITaskManager taskManager, + IActivityManager activityManager, + ILocalizationManager localization, + IInstallationManager installationManager, + ISubtitleManager subManager, + IUserManager userManager, + IServerApplicationHost appHost) { + _logger = logger; _sessionManager = sessionManager; + _deviceManager = deviceManager; _taskManager = taskManager; _activityManager = activityManager; _localization = localization; _installationManager = installationManager; - _libraryManager = libraryManager; _subManager = subManager; _userManager = userManager; - _config = config; _appHost = appHost; - _deviceManager = deviceManager; } public Task RunAsync() @@ -124,7 +130,7 @@ namespace Emby.Server.Implementations.Activity if (item == null) { - //_logger.LogWarning("PlaybackStopped reported with null media info."); + _logger.LogWarning("PlaybackStopped reported with null media info."); return; } @@ -155,7 +161,7 @@ namespace Emby.Server.Implementations.Activity if (item == null) { - //_logger.LogWarning("PlaybackStart reported with null media info."); + _logger.LogWarning("PlaybackStart reported with null media info."); return; } @@ -203,6 +209,7 @@ namespace Emby.Server.Implementations.Activity { return NotificationType.AudioPlayback.ToString(); } + if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) { return NotificationType.VideoPlayback.ToString(); @@ -217,6 +224,7 @@ namespace Emby.Server.Implementations.Activity { return NotificationType.AudioPlaybackStopped.ToString(); } + if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) { return NotificationType.VideoPlaybackStopped.ToString(); @@ -415,6 +423,7 @@ namespace Emby.Server.Implementations.Activity { vals.Add(e.Result.ErrorMessage); } + if (!string.IsNullOrEmpty(e.Result.LongErrorMessage)) { vals.Add(e.Result.LongErrorMessage); @@ -424,7 +433,7 @@ namespace Emby.Server.Implementations.Activity { Name = string.Format(_localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name), Type = NotificationType.TaskFailed.ToString(), - Overview = string.Join(Environment.NewLine, vals.ToArray()), + Overview = string.Join(Environment.NewLine, vals), ShortOverview = runningTime, Severity = LogLevel.Error }); @@ -503,6 +512,7 @@ namespace Emby.Server.Implementations.Activity { values.Add(CreateValueString(span.Hours, "hour")); } + // Number of minutes if (span.Minutes >= 1) { @@ -526,6 +536,7 @@ namespace Emby.Server.Implementations.Activity builder.Append(values[i]); } + // Return result return builder.ToString(); } diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 484942946c..ff49c74a3b 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -831,10 +831,10 @@ namespace Emby.Server.Implementations DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ProviderManager, this, () => MediaSourceManager, () => LiveTvManager); serviceCollection.AddSingleton(DtoService); - ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager); + ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, ProviderManager); serviceCollection.AddSingleton(ChannelManager); - SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager); + SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, this, AuthenticationRepository, DeviceManager, MediaSourceManager); serviceCollection.AddSingleton(SessionManager); serviceCollection.AddSingleton( @@ -1035,7 +1035,6 @@ namespace Emby.Server.Implementations Video.LiveTvManager = LiveTvManager; Folder.UserViewManager = UserViewManager; UserView.TVSeriesManager = TVSeriesManager; - UserView.PlaylistManager = PlaylistManager; UserView.CollectionManager = CollectionManager; BaseItem.MediaSourceManager = MediaSourceManager; CollectionFolder.XmlSerializer = XmlSerializer; diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 7e50650d70..e9961e8bd7 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; @@ -20,7 +19,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Channels; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; @@ -40,11 +38,8 @@ namespace Emby.Server.Implementations.Channels private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; - private readonly IHttpClient _httpClient; private readonly IProviderManager _providerManager; - private readonly ILocalizationManager _localization; - public ChannelManager( IUserManager userManager, IDtoService dtoService, @@ -54,8 +49,6 @@ namespace Emby.Server.Implementations.Channels IFileSystem fileSystem, IUserDataManager userDataManager, IJsonSerializer jsonSerializer, - ILocalizationManager localization, - IHttpClient httpClient, IProviderManager providerManager) { _userManager = userManager; @@ -66,8 +59,6 @@ namespace Emby.Server.Implementations.Channels _fileSystem = fileSystem; _userDataManager = userDataManager; _jsonSerializer = jsonSerializer; - _localization = localization; - _httpClient = httpClient; _providerManager = providerManager; } diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 7b28a22a84..2f1b60be9c 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -89,14 +89,11 @@ namespace Emby.Server.Implementations.Dto var channelTuples = new List>(); var index = 0; - var allCollectionFolders = _libraryManager.GetUserRootFolder().Children.OfType().ToList(); - foreach (var item in items) { - var dto = GetBaseItemDtoInternal(item, options, allCollectionFolders, user, owner); + var dto = GetBaseItemDtoInternal(item, options, user, owner); - var tvChannel = item as LiveTvChannel; - if (tvChannel != null) + if (item is LiveTvChannel tvChannel) { channelTuples.Add(new Tuple(dto, tvChannel)); } @@ -105,9 +102,7 @@ namespace Emby.Server.Implementations.Dto programTuples.Add(new Tuple(item, dto)); } - var byName = item as IItemByName; - - if (byName != null) + if (item is IItemByName byName) { if (options.ContainsField(ItemFields.ItemCounts)) { @@ -130,8 +125,7 @@ namespace Emby.Server.Implementations.Dto if (programTuples.Count > 0) { - var task = _livetvManager().AddInfoToProgramDto(programTuples, options.Fields, user); - Task.WaitAll(task); + _livetvManager().AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult(); } if (channelTuples.Count > 0) @@ -144,8 +138,7 @@ namespace Emby.Server.Implementations.Dto public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null) { - var allCollectionFolders = _libraryManager.GetUserRootFolder().Children.OfType().ToList(); - var dto = GetBaseItemDtoInternal(item, options, allCollectionFolders, user, owner); + var dto = GetBaseItemDtoInternal(item, options, user, owner); var tvChannel = item as LiveTvChannel; if (tvChannel != null) { @@ -188,7 +181,7 @@ namespace Emby.Server.Implementations.Dto }); } - private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, List allCollectionFolders, User user = null, BaseItem owner = null) + private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null) { var dto = new BaseItemDto { @@ -312,6 +305,7 @@ namespace Emby.Server.Implementations.Dto { path = path.TrimStart('.'); } + if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparer.OrdinalIgnoreCase)) { fileExtensionContainer = path; @@ -325,8 +319,7 @@ namespace Emby.Server.Implementations.Dto public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List taggedItems, User user = null) { - var allCollectionFolders = _libraryManager.GetUserRootFolder().Children.OfType().ToList(); - var dto = GetBaseItemDtoInternal(item, options, allCollectionFolders, user); + var dto = GetBaseItemDtoInternal(item, options, user); if (taggedItems != null && options.ContainsField(ItemFields.ItemCounts)) { @@ -1051,14 +1044,15 @@ namespace Emby.Server.Implementations.Dto } else { - mediaStreams = dto.MediaSources.Where(i => string.Equals(i.Id, item.Id.ToString("N"), StringComparison.OrdinalIgnoreCase)) + string id = item.Id.ToString("N"); + mediaStreams = dto.MediaSources.Where(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)) .SelectMany(i => i.MediaStreams) .ToArray(); } } else { - mediaStreams = _mediaSourceManager().GetStaticMediaSources(item, true).First().MediaStreams.ToArray(); + mediaStreams = _mediaSourceManager().GetStaticMediaSources(item, true)[0].MediaStreams.ToArray(); } dto.MediaStreams = mediaStreams; diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index c81a937672..29b8dfd3d7 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -15,13 +15,9 @@ namespace Emby.Server.Implementations.Security { public class AuthenticationRepository : BaseSqliteRepository, IAuthenticationRepository { - private readonly IServerConfigurationManager _config; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - public AuthenticationRepository(ILoggerFactory loggerFactory, IServerConfigurationManager config) : base(loggerFactory.CreateLogger(nameof(AuthenticationRepository))) { - _config = config; DbFilePath = Path.Combine(config.ApplicationPaths.DataPath, "authentication.db"); } diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs index f835aa1b5b..7d42b2b510 100644 --- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs +++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs @@ -48,7 +48,7 @@ namespace Emby.Server.Implementations.Services foreach (var propertyInfo in RestPath.GetSerializableProperties(type)) { - var propertySetFn = TypeAccessor.GetSetPropertyMethod(type, propertyInfo); + var propertySetFn = TypeAccessor.GetSetPropertyMethod(propertyInfo); var propertyType = propertyInfo.PropertyType; var propertyParseStringFn = GetParseFn(propertyType); var propertySerializer = new PropertySerializerEntry(propertySetFn, propertyParseStringFn, propertyType); @@ -110,9 +110,9 @@ namespace Emby.Server.Implementations.Services } } - internal class TypeAccessor + internal static class TypeAccessor { - public static Action GetSetPropertyMethod(Type type, PropertyInfo propertyInfo) + public static Action GetSetPropertyMethod(PropertyInfo propertyInfo) { if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Length > 0) { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 985748caf2..dc23551db5 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -7,7 +7,6 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Devices; @@ -25,7 +24,6 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Events; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; @@ -53,8 +51,6 @@ namespace Emby.Server.Implementations.Session private readonly IImageProcessor _imageProcessor; private readonly IMediaSourceManager _mediaSourceManager; - private readonly IHttpClient _httpClient; - private readonly IJsonSerializer _jsonSerializer; private readonly IServerApplicationHost _appHost; private readonly IAuthenticationRepository _authRepo; @@ -96,9 +92,7 @@ namespace Emby.Server.Implementations.Session IMusicManager musicManager, IDtoService dtoService, IImageProcessor imageProcessor, - IJsonSerializer jsonSerializer, IServerApplicationHost appHost, - IHttpClient httpClient, IAuthenticationRepository authRepo, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager) @@ -110,9 +104,7 @@ namespace Emby.Server.Implementations.Session _musicManager = musicManager; _dtoService = dtoService; _imageProcessor = imageProcessor; - _jsonSerializer = jsonSerializer; _appHost = appHost; - _httpClient = httpClient; _authRepo = authRepo; _deviceManager = deviceManager; _mediaSourceManager = mediaSourceManager; @@ -347,8 +339,7 @@ namespace Emby.Server.Implementations.Session var runtimeTicks = libraryItem.RunTimeTicks; MediaSourceInfo mediaSource = null; - var hasMediaSources = libraryItem as IHasMediaSources; - if (hasMediaSources != null) + if (libraryItem is IHasMediaSources hasMediaSources) { mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false); @@ -1841,64 +1832,49 @@ namespace Emby.Server.Implementations.Session var data = dataFn(); - var tasks = sessions.Select(session => Task.Run(async () => + IEnumerable GetTasks() { - try - { - await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) + foreach (var session in sessions) { - _logger.LogError("Error sending message", ex); + yield return SendMessageToSession(session, name, data, cancellationToken); } + } - }, cancellationToken)).ToArray(); - - return Task.WhenAll(tasks); + return Task.WhenAll(GetTasks()); } public Task SendMessageToUserSessions(List userIds, string name, T data, CancellationToken cancellationToken) { CheckDisposed(); - var sessions = Sessions.Where(i => userIds.Any(i.ContainsUser)).ToList(); + var sessions = Sessions.Where(i => userIds.Any(i.ContainsUser)); - var tasks = sessions.Select(session => Task.Run(async () => + IEnumerable GetTasks() { - try - { - await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) + foreach (var session in sessions) { - _logger.LogError("Error sending message", ex); + yield return SendMessageToSession(session, name, data, cancellationToken); } + } - }, cancellationToken)).ToArray(); - - return Task.WhenAll(tasks); + return Task.WhenAll(GetTasks()); } public Task SendMessageToUserDeviceSessions(string deviceId, string name, T data, CancellationToken cancellationToken) { CheckDisposed(); - var sessions = Sessions.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)).ToList(); + var sessions = Sessions.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)); - var tasks = sessions.Select(session => Task.Run(async () => + IEnumerable GetTasks() { - try - { - await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) + foreach (var session in sessions) { - _logger.LogError("Error sending message", ex); + yield return SendMessageToSession(session, name, data, cancellationToken); } + } - }, cancellationToken)).ToArray(); - - return Task.WhenAll(tasks); + return Task.WhenAll(GetTasks()); } public Task SendMessageToUserDeviceAndAdminSessions(string deviceId, string name, T data, CancellationToken cancellationToken) @@ -1906,23 +1882,17 @@ namespace Emby.Server.Implementations.Session CheckDisposed(); var sessions = Sessions - .Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase) || IsAdminSession(i)) - .ToList(); + .Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase) || IsAdminSession(i)); - var tasks = sessions.Select(session => Task.Run(async () => + IEnumerable GetTasks() { - try - { - await SendMessageToSession(session, name, data, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) + foreach (var session in sessions) { - _logger.LogError("Error sending message", ex); + yield return SendMessageToSession(session, name, data, cancellationToken); } + } - }, cancellationToken)).ToArray(); - - return Task.WhenAll(tasks); + return Task.WhenAll(GetTasks()); } private bool IsAdminSession(SessionInfo s) diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index 3e2191376b..4a6d32dcec 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; @@ -17,7 +16,6 @@ namespace MediaBrowser.Controller.Entities public Guid? UserId { get; set; } public static ITVSeriesManager TVSeriesManager; - public static IPlaylistManager PlaylistManager; [IgnoreDataMember] public string CollectionType => ViewType; @@ -38,6 +36,7 @@ namespace MediaBrowser.Controller.Entities { list.Add(Id); } + return list; } @@ -65,7 +64,7 @@ namespace MediaBrowser.Controller.Entities parent = LibraryManager.GetItemById(ParentId) as Folder ?? parent; } - return new UserViewBuilder(UserViewManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, ConfigurationManager, PlaylistManager) + return new UserViewBuilder(UserViewManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, ConfigurationManager) .GetUserItems(parent, this, CollectionType, query); } diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 683218a9e9..e483c8f349 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -5,7 +5,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; @@ -21,9 +20,14 @@ namespace MediaBrowser.Controller.Entities private readonly IUserDataManager _userDataManager; private readonly ITVSeriesManager _tvSeriesManager; private readonly IServerConfigurationManager _config; - private readonly IPlaylistManager _playlistManager; - public UserViewBuilder(IUserViewManager userViewManager, ILibraryManager libraryManager, ILogger logger, IUserDataManager userDataManager, ITVSeriesManager tvSeriesManager, IServerConfigurationManager config, IPlaylistManager playlistManager) + public UserViewBuilder( + IUserViewManager userViewManager, + ILibraryManager libraryManager, + ILogger logger, + IUserDataManager userDataManager, + ITVSeriesManager tvSeriesManager, + IServerConfigurationManager config) { _userViewManager = userViewManager; _libraryManager = libraryManager; @@ -31,7 +35,6 @@ namespace MediaBrowser.Controller.Entities _userDataManager = userDataManager; _tvSeriesManager = tvSeriesManager; _config = config; - _playlistManager = playlistManager; } public QueryResult GetUserItems(Folder queryParent, Folder displayParent, string viewType, InternalItemsQuery query) @@ -110,6 +113,7 @@ namespace MediaBrowser.Controller.Entities { return GetResult(GetMediaFolders(user).OfType().SelectMany(i => i.GetChildren(user, true)), queryParent, query); } + return queryParent.GetItems(query); } } -- cgit v1.2.3 From 2696ac5eacfb4702d629bc06a8b42b868c316116 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 24 Feb 2019 03:16:19 +0100 Subject: Lower the amount of running tasks --- .../HttpServer/HttpListenerHost.cs | 15 +++------ .../Session/SessionWebSocketListener.cs | 6 ++-- .../SocketSharp/RequestMono.cs | 16 ++------- .../SocketSharp/WebSocketSharpRequest.cs | 39 +++++++++++----------- Jellyfin.Server/Program.cs | 6 ++-- MediaBrowser.Api/Library/LibraryService.cs | 19 +++++------ .../Net/BasePeriodicWebSocketListener.cs | 4 +-- MediaBrowser.Controller/Net/IWebSocketListener.cs | 2 +- RSSDP/HttpParserBase.cs | 6 ++-- RSSDP/SsdpCommunicationsServer.cs | 21 +++++------- 10 files changed, 53 insertions(+), 81 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index e8d47cad52..9214f2afe2 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -11,7 +11,6 @@ using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Net; using Emby.Server.Implementations.Services; -using Emby.Server.Implementations.SocketSharp; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -823,19 +822,15 @@ namespace Emby.Server.Implementations.HttpServer Logger.LogDebug("Websocket message received: {0}", result.MessageType); - var tasks = _webSocketListeners.Select(i => Task.Run(async () => + IEnumerable GetTasks() { - try - { - await i.ProcessMessage(result).ConfigureAwait(false); - } - catch (Exception ex) + foreach (var x in _webSocketListeners) { - Logger.LogError(ex, "{0} failed processing WebSocket message {1}", i.GetType().Name, result.MessageType ?? string.Empty); + yield return x.ProcessMessageAsync(result); } - })); + } - return Task.WhenAll(tasks); + return Task.WhenAll(GetTasks()); } public void Dispose() diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index a551433ed9..63ec757626 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -89,10 +89,8 @@ namespace Emby.Server.Implementations.Session /// /// The message. /// Task. - public Task ProcessMessage(WebSocketMessageInfo message) - { - return Task.CompletedTask; - } + public Task ProcessMessageAsync(WebSocketMessageInfo message) + => Task.CompletedTask; private void EnsureController(SessionInfo session, IWebSocketConnection connection) { diff --git a/Emby.Server.Implementations/SocketSharp/RequestMono.cs b/Emby.Server.Implementations/SocketSharp/RequestMono.cs index 373f6d7580..ec637186f0 100644 --- a/Emby.Server.Implementations/SocketSharp/RequestMono.cs +++ b/Emby.Server.Implementations/SocketSharp/RequestMono.cs @@ -86,8 +86,7 @@ namespace Emby.Server.Implementations.SocketSharp else { // We use a substream, as in 2.x we will support large uploads streamed to disk, - var sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length); - files[e.Name] = sub; + files[e.Name] = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length); } } } @@ -374,7 +373,7 @@ namespace Emby.Server.Implementations.SocketSharp var elem = new Element(); ReadOnlySpan header; - while ((header = ReadHeaders().AsSpan()) != null) + while ((header = ReadLine().AsSpan()).Length != 0) { if (header.StartsWith("Content-Disposition:".AsSpan(), StringComparison.OrdinalIgnoreCase)) { @@ -513,17 +512,6 @@ namespace Emby.Server.Implementations.SocketSharp return false; } - private string ReadHeaders() - { - string s = ReadLine(); - if (s.Length == 0) - { - return null; - } - - return s; - } - private static bool CompareBytes(byte[] orig, byte[] other) { for (int i = orig.Length - 1; i >= 0; i--) diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index e0a0ee2861..6fdc6a3c8d 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Net; +using System.Linq; using System.Text; -using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Logging; @@ -474,27 +474,28 @@ namespace Emby.Server.Implementations.SocketSharp { get { - if (httpFiles == null) + if (httpFiles != null) { - if (files == null) - { - return httpFiles = Array.Empty(); - } + return httpFiles; + } - httpFiles = new IHttpFile[files.Count]; - var i = 0; - foreach (var pair in files) + if (files == null) + { + return httpFiles = Array.Empty(); + } + + var values = files.Values; + httpFiles = new IHttpFile[values.Count]; + for (int i = 0; i < values.Count; i++) + { + var reqFile = values.ElementAt(i); + httpFiles[i] = new HttpFile { - var reqFile = pair.Value; - httpFiles[i] = new HttpFile - { - ContentType = reqFile.ContentType, - ContentLength = reqFile.ContentLength, - FileName = reqFile.FileName, - InputStream = reqFile.InputStream, - }; - i++; - } + ContentType = reqFile.ContentType, + ContentLength = reqFile.ContentLength, + FileName = reqFile.FileName, + InputStream = reqFile.InputStream, + }; } return httpFiles; diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 82a76c6378..fbeb7a2e82 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -37,7 +37,7 @@ namespace Jellyfin.Server private static bool _restartOnShutdown; private static IConfiguration appConfig; - public static async Task Main(string[] args) + public static Task Main(string[] args) { // For backwards compatibility. // Modify any input arguments now which start with single-hyphen to POSIX standard @@ -51,8 +51,8 @@ namespace Jellyfin.Server } // Parse the command line arguments and either start the app or exit indicating error - await Parser.Default.ParseArguments(args) - .MapResult(StartApp, _ => Task.CompletedTask).ConfigureAwait(false); + return Parser.Default.ParseArguments(args) + .MapResult(StartApp, _ => Task.CompletedTask); } public static void Shutdown() diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 8eefbdf2ca..8a5a793dfd 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -988,19 +988,16 @@ namespace MediaBrowser.Api.Library /// Posts the specified request. /// /// The request. - public void Post(RefreshLibrary request) + public async Task Post(RefreshLibrary request) { - Task.Run(() => + try { - try - { - _libraryManager.ValidateMediaLibrary(new SimpleProgress(), CancellationToken.None); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error refreshing library"); - } - }); + await _libraryManager.ValidateMediaLibrary(new SimpleProgress(), CancellationToken.None).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error refreshing library"); + } } /// diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index 8444125462..ee5c1a165a 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.Controller.Net /// /// The message. /// Task. - public Task ProcessMessage(WebSocketMessageInfo message) + public Task ProcessMessageAsync(WebSocketMessageInfo message) { if (message == null) { @@ -74,7 +74,7 @@ namespace MediaBrowser.Controller.Net Stop(message); } - return Task.FromResult(true); + return Task.CompletedTask; } protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); diff --git a/MediaBrowser.Controller/Net/IWebSocketListener.cs b/MediaBrowser.Controller/Net/IWebSocketListener.cs index e38f0e259f..0f472a2bc7 100644 --- a/MediaBrowser.Controller/Net/IWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/IWebSocketListener.cs @@ -12,6 +12,6 @@ namespace MediaBrowser.Controller.Net /// /// The message. /// Task. - Task ProcessMessage(WebSocketMessageInfo message); + Task ProcessMessageAsync(WebSocketMessageInfo message); } } diff --git a/RSSDP/HttpParserBase.cs b/RSSDP/HttpParserBase.cs index 18712470da..76d816e7bd 100644 --- a/RSSDP/HttpParserBase.cs +++ b/RSSDP/HttpParserBase.cs @@ -23,8 +23,6 @@ namespace Rssdp.Infrastructure #region Public Methods - private static byte[] EmptyByteArray = new byte[]{}; - /// /// Parses the provided into either a or object. /// @@ -46,7 +44,7 @@ namespace Rssdp.Infrastructure if (data.Length == 0) throw new ArgumentException("data cannot be an empty string.", nameof(data)); if (!LineTerminators.Any(data.Contains)) throw new ArgumentException("data is not a valid request, it does not contain any CRLF/LF terminators.", nameof(data)); - using (var retVal = new ByteArrayContent(EmptyByteArray)) + using (var retVal = new ByteArrayContent(Array.Empty())) { var lines = data.Split(LineTerminators, StringSplitOptions.None); @@ -209,4 +207,4 @@ namespace Rssdp.Infrastructure #endregion } -} \ No newline at end of file +} diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index d9a4b6ac01..5d2afc37a0 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -355,7 +355,7 @@ namespace Rssdp.Infrastructure { var socket = _SocketFactory.CreateUdpMulticastSocket(SsdpConstants.MulticastLocalAdminAddress, _MulticastTtl, SsdpConstants.MulticastPort); - ListenToSocket(socket); + _ = ListenToSocketInternal(socket); return socket; } @@ -389,19 +389,12 @@ namespace Rssdp.Infrastructure foreach (var socket in sockets) { - ListenToSocket(socket); + _ = ListenToSocketInternal(socket); } return sockets; } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "t", Justification = "Capturing task to local variable removes compiler warning, task is not otherwise required.")] - private void ListenToSocket(ISocket socket) - { - // Tasks are captured to local variables even if we don't use them just to avoid compiler warnings. - var t = Task.Run(() => ListenToSocketInternal(socket)); - } - private async Task ListenToSocketInternal(ISocket socket) { var cancelled = false; @@ -448,10 +441,10 @@ namespace Rssdp.Infrastructure private void ProcessMessage(string data, IpEndPointInfo endPoint, IpAddressInfo receivedOnLocalIpAddress) { - //Responses start with the HTTP version, prefixed with HTTP/ while - //requests start with a method which can vary and might be one we haven't - //seen/don't know. We'll check if this message is a request or a response - //by checking for the HTTP/ prefix on the start of the message. + // Responses start with the HTTP version, prefixed with HTTP/ while + // requests start with a method which can vary and might be one we haven't + // seen/don't know. We'll check if this message is a request or a response + // by checking for the HTTP/ prefix on the start of the message. if (data.StartsWith("HTTP/", StringComparison.OrdinalIgnoreCase)) { HttpResponseMessage responseMessage = null; @@ -465,7 +458,9 @@ namespace Rssdp.Infrastructure } if (responseMessage != null) + { OnResponseReceived(responseMessage, endPoint, receivedOnLocalIpAddress); + } } else { -- cgit v1.2.3 From 8ed5d154b746c086ccb6b1163772ea4d05067abd Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 27 Mar 2019 16:07:08 +0100 Subject: Remove duplicate code --- .../Session/SessionManager.cs | 62 ++++++++-------------- 1 file changed, 23 insertions(+), 39 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index dc23551db5..b7974dd7e8 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1037,6 +1037,25 @@ namespace Emby.Server.Implementations.Session } } + private static Task SendMessageToSessions(IEnumerable sessions, string name, T data, CancellationToken cancellationToken) + { + IEnumerable GetTasks() + { + foreach (var session in sessions) + { + var controllers = session.SessionControllers; + var messageId = Guid.NewGuid().ToString("N"); + + foreach (var controller in controllers) + { + yield return controller.SendMessage(name, messageId, data, controllers, cancellationToken); + } + } + } + + return Task.WhenAll(GetTasks()); + } + public async Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken) { CheckDisposed(); @@ -1832,15 +1851,7 @@ namespace Emby.Server.Implementations.Session var data = dataFn(); - IEnumerable GetTasks() - { - foreach (var session in sessions) - { - yield return SendMessageToSession(session, name, data, cancellationToken); - } - } - - return Task.WhenAll(GetTasks()); + return SendMessageToSessions(sessions, name, data, cancellationToken); } public Task SendMessageToUserSessions(List userIds, string name, T data, CancellationToken cancellationToken) @@ -1848,16 +1859,7 @@ namespace Emby.Server.Implementations.Session CheckDisposed(); var sessions = Sessions.Where(i => userIds.Any(i.ContainsUser)); - - IEnumerable GetTasks() - { - foreach (var session in sessions) - { - yield return SendMessageToSession(session, name, data, cancellationToken); - } - } - - return Task.WhenAll(GetTasks()); + return SendMessageToSessions(sessions, name, data, cancellationToken); } public Task SendMessageToUserDeviceSessions(string deviceId, string name, T data, CancellationToken cancellationToken) @@ -1865,16 +1867,7 @@ namespace Emby.Server.Implementations.Session CheckDisposed(); var sessions = Sessions.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)); - - IEnumerable GetTasks() - { - foreach (var session in sessions) - { - yield return SendMessageToSession(session, name, data, cancellationToken); - } - } - - return Task.WhenAll(GetTasks()); + return SendMessageToSessions(sessions, name, data, cancellationToken); } public Task SendMessageToUserDeviceAndAdminSessions(string deviceId, string name, T data, CancellationToken cancellationToken) @@ -1883,16 +1876,7 @@ namespace Emby.Server.Implementations.Session var sessions = Sessions .Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase) || IsAdminSession(i)); - - IEnumerable GetTasks() - { - foreach (var session in sessions) - { - yield return SendMessageToSession(session, name, data, cancellationToken); - } - } - - return Task.WhenAll(GetTasks()); + return SendMessageToSessions(sessions, name, data, cancellationToken); } private bool IsAdminSession(SessionInfo s) -- cgit v1.2.3 From 6c0e2e249ddbab24c67b655b701e473d9343c2ef Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 27 Mar 2019 16:13:36 +0100 Subject: Even more duplicate code removed --- .../Session/SessionManager.cs | 56 +++------------------- 1 file changed, 6 insertions(+), 50 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index b7974dd7e8..6b9573b1dd 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1242,12 +1242,13 @@ namespace Emby.Server.Implementations.Session return SendMessageToSession(session, "Playstate", command, cancellationToken); } - private void AssertCanControl(SessionInfo session, SessionInfo controllingSession) + private static void AssertCanControl(SessionInfo session, SessionInfo controllingSession) { if (session == null) { throw new ArgumentNullException(nameof(session)); } + if (controllingSession == null) { throw new ArgumentNullException(nameof(controllingSession)); @@ -1259,26 +1260,11 @@ namespace Emby.Server.Implementations.Session /// /// The cancellation token. /// Task. - public async Task SendRestartRequiredNotification(CancellationToken cancellationToken) + public Task SendRestartRequiredNotification(CancellationToken cancellationToken) { CheckDisposed(); - var sessions = Sessions.ToList(); - - var tasks = sessions.Select(session => Task.Run(async () => - { - try - { - await SendMessageToSession(session, "RestartRequired", string.Empty, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError("Error in SendRestartRequiredNotification.", ex); - } - - }, cancellationToken)).ToArray(); - - await Task.WhenAll(tasks).ConfigureAwait(false); + return SendMessageToSessions(Sessions, "RestartRequired", string.Empty, cancellationToken); } /// @@ -1290,22 +1276,7 @@ namespace Emby.Server.Implementations.Session { CheckDisposed(); - var sessions = Sessions.ToList(); - - var tasks = sessions.Select(session => Task.Run(async () => - { - try - { - await SendMessageToSession(session, "ServerShuttingDown", string.Empty, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError("Error in SendServerShutdownNotification.", ex); - } - - }, cancellationToken)).ToArray(); - - return Task.WhenAll(tasks); + return SendMessageToSessions(Sessions, "ServerShuttingDown", string.Empty, cancellationToken); } /// @@ -1319,22 +1290,7 @@ namespace Emby.Server.Implementations.Session _logger.LogDebug("Beginning SendServerRestartNotification"); - var sessions = Sessions.ToList(); - - var tasks = sessions.Select(session => Task.Run(async () => - { - try - { - await SendMessageToSession(session, "ServerRestarting", string.Empty, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError("Error in SendServerRestartNotification.", ex); - } - - }, cancellationToken)).ToArray(); - - return Task.WhenAll(tasks); + return SendMessageToSessions(Sessions, "ServerRestarting", string.Empty, cancellationToken); } /// -- cgit v1.2.3 From b69b19ddce63651306bde494adfa656fd658b230 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 27 Mar 2019 16:28:52 +0100 Subject: Move messageId out of outer loop --- Emby.Server.Implementations/Session/SessionManager.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 6b9573b1dd..53ed5fc223 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1041,11 +1041,10 @@ namespace Emby.Server.Implementations.Session { IEnumerable GetTasks() { + var messageId = Guid.NewGuid().ToString("N"); foreach (var session in sessions) { var controllers = session.SessionControllers; - var messageId = Guid.NewGuid().ToString("N"); - foreach (var controller in controllers) { yield return controller.SendMessage(name, messageId, data, controllers, cancellationToken); -- cgit v1.2.3 From 6032f31aa660e3b0fe1936217109f9fb47853ba3 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 28 Feb 2019 23:22:57 +0100 Subject: Use CultureInvariant string conversion for Guids --- Emby.Dlna/Didl/DidlBuilder.cs | 4 ++-- Emby.Dlna/DlnaManager.cs | 5 ++-- Emby.Dlna/Eventing/EventManager.cs | 2 +- Emby.Dlna/Main/DlnaEntryPoint.cs | 3 ++- Emby.Dlna/PlayTo/PlayToManager.cs | 5 ++-- Emby.Drawing/ImageProcessor.cs | 6 ++--- Emby.Notifications/NotificationManager.cs | 5 ++-- .../Activity/ActivityLogEntryPoint.cs | 4 ++-- .../Activity/ActivityRepository.cs | 4 ++-- Emby.Server.Implementations/ApplicationHost.cs | 2 +- .../Channels/ChannelManager.cs | 17 +++++++------- .../Collections/CollectionManager.cs | 3 ++- .../Data/SqliteDisplayPreferencesRepository.cs | 3 ++- .../Data/SqliteItemRepository.cs | 19 +++++++-------- Emby.Server.Implementations/Devices/DeviceId.cs | 4 ++-- .../Devices/DeviceManager.cs | 4 ++-- Emby.Server.Implementations/Dto/DtoService.cs | 17 +++++++------- .../EntryPoints/LibraryChangedNotifier.cs | 16 ++++++------- .../EntryPoints/ServerEventNotifier.cs | 3 ++- .../EntryPoints/UserDataChangeNotifier.cs | 6 ++--- .../HttpClientManager/HttpClientManager.cs | 2 +- .../IO/ManagedFileSystem.cs | 3 ++- .../Images/BaseDynamicImageProvider.cs | 3 ++- .../Library/ExclusiveLiveStream.cs | 3 ++- .../Library/LibraryManager.cs | 14 +++++------ .../Library/LiveStreamHelper.cs | 2 +- .../Library/MediaSourceManager.cs | 6 ++--- .../Library/UserDataManager.cs | 2 +- .../Library/UserViewManager.cs | 9 ++++---- .../Library/Validators/ArtistsValidator.cs | 3 ++- .../Library/Validators/PeopleValidator.cs | 4 ++-- .../Library/Validators/StudiosValidator.cs | 3 ++- .../LiveTv/EmbyTV/EmbyTV.cs | 8 +++---- .../LiveTv/Listings/XmlTvListingsProvider.cs | 4 ++-- .../LiveTv/LiveTvDtoService.cs | 27 +++++++++++----------- .../LiveTv/LiveTvManager.cs | 23 +++++++++--------- .../LiveTv/LiveTvMediaSourceProvider.cs | 3 ++- .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 3 ++- .../LiveTv/TunerHosts/LiveStream.cs | 3 ++- .../LiveTv/TunerHosts/M3UTunerHost.cs | 7 +++--- .../LiveTv/TunerHosts/M3uParser.cs | 4 ++-- .../Playlists/PlaylistManager.cs | 7 +++--- .../ScheduledTasks/ScheduledTaskWorker.cs | 4 ++-- .../Security/AuthenticationRepository.cs | 2 +- .../Serialization/JsonSerializer.cs | 3 ++- .../Session/HttpSessionController.cs | 2 +- .../Session/SessionManager.cs | 14 ++++++----- Emby.Server.Implementations/TV/TVSeriesManager.cs | 3 ++- MediaBrowser.Api/Images/ImageService.cs | 3 ++- .../Library/LibraryStructureService.cs | 3 ++- MediaBrowser.Api/Movies/MoviesService.cs | 6 ++--- MediaBrowser.Api/Playback/BaseStreamingService.cs | 8 ++++++- MediaBrowser.Api/Playback/MediaInfoService.cs | 3 ++- MediaBrowser.Api/SearchService.cs | 5 ++-- MediaBrowser.Api/Session/SessionsService.cs | 3 ++- MediaBrowser.Api/Subtitles/SubtitleService.cs | 4 ++-- MediaBrowser.Api/TvShowsService.cs | 3 ++- MediaBrowser.Api/UserLibrary/ItemsService.cs | 2 +- MediaBrowser.Api/UserLibrary/UserViewsService.cs | 3 ++- MediaBrowser.Api/VideosService.cs | 3 ++- MediaBrowser.Controller/Channels/Channel.cs | 7 +++--- MediaBrowser.Controller/Entities/BaseItem.cs | 14 +++++------ MediaBrowser.Controller/Entities/Folder.cs | 5 ++-- MediaBrowser.Controller/Entities/LinkedChild.cs | 5 ++-- MediaBrowser.Controller/Entities/TV/Series.cs | 3 ++- MediaBrowser.Controller/Entities/User.cs | 3 ++- .../Entities/UserViewBuilder.cs | 3 ++- MediaBrowser.Controller/LiveTv/LiveTvChannel.cs | 4 ++-- MediaBrowser.Controller/LiveTv/LiveTvProgram.cs | 3 ++- MediaBrowser.Controller/Playlists/Playlist.cs | 3 ++- .../Providers/MetadataResult.cs | 3 ++- MediaBrowser.Providers/Manager/ProviderManager.cs | 8 +++---- .../MediaInfo/AudioImageProvider.cs | 7 +++--- .../Subtitles/SubtitleManager.cs | 5 ++-- jellyfin.ruleset | 4 ++++ 75 files changed, 240 insertions(+), 186 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index a21aff9f91..26adfde839 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -1082,7 +1082,7 @@ namespace Emby.Dlna.Didl public static string GetClientId(Guid idValue, StubType? stubType) { - var id = idValue.ToString("N"); + var id = idValue.ToString("N", CultureInfo.InvariantCulture); if (stubType.HasValue) { @@ -1096,7 +1096,7 @@ namespace Emby.Dlna.Didl { var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0", _serverAddress, - info.ItemId.ToString("N"), + info.ItemId.ToString("N", CultureInfo.InvariantCulture), info.Type, info.ImageTag, format, diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 2b76d27025..d5d788021d 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Reflection; @@ -300,7 +301,7 @@ namespace Emby.Dlna profile = ReserializeProfile(tempProfile); - profile.Id = path.ToLowerInvariant().GetMD5().ToString("N"); + profile.Id = path.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture); _profiles[path] = new Tuple(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile); @@ -352,7 +353,7 @@ namespace Emby.Dlna Info = new DeviceProfileInfo { - Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N"), + Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture), Name = _fileSystem.GetFileNameWithoutExtension(file), Type = type } diff --git a/Emby.Dlna/Eventing/EventManager.cs b/Emby.Dlna/Eventing/EventManager.cs index b4ff3ec1d5..4b542a820c 100644 --- a/Emby.Dlna/Eventing/EventManager.cs +++ b/Emby.Dlna/Eventing/EventManager.cs @@ -55,7 +55,7 @@ namespace Emby.Dlna.Eventing public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl) { var timeout = ParseTimeout(requestedTimeoutString) ?? 300; - var id = "uuid:" + Guid.NewGuid().ToString("N"); + var id = "uuid:" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); // Remove logging for now because some devices are sending this very frequently // TODO re-enable with dlna debug logging setting diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 206a873e1d..77bde0ca24 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -1,5 +1,6 @@ using System; using System.Net.Sockets; +using System.Globalization; using System.Threading; using System.Threading.Tasks; using Emby.Dlna.PlayTo; @@ -307,7 +308,7 @@ namespace Emby.Dlna.Main { guid = text.GetMD5(); } - return guid.ToString("N"); + return guid.ToString("N", CultureInfo.InvariantCulture); } private void SetProperies(SsdpDevice device, string fullDeviceType) diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index c0a4418711..a3a013096c 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using System.Net; using System.Threading; @@ -141,7 +142,7 @@ namespace Emby.Dlna.PlayTo return usn; } - return usn.GetMD5().ToString("N"); + return usn.GetMD5().ToString("N", CultureInfo.InvariantCulture); } private async Task AddDevice(UpnpDeviceInfo info, string location, CancellationToken cancellationToken) @@ -156,7 +157,7 @@ namespace Emby.Dlna.PlayTo } else { - uuid = location.GetMD5().ToString("N"); + uuid = location.GetMD5().ToString("N", CultureInfo.InvariantCulture); } var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersion, uuid, null, uri.OriginalString, null); diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 6d209d8d01..a7d95eb209 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -454,14 +454,14 @@ namespace Emby.Drawing // Optimization if (imageEnhancers.Length == 0) { - return (originalImagePath + dateModified.Ticks).GetMD5().ToString("N"); + return (originalImagePath + dateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture); } // Cache name is created with supported enhancers combined with the last config change so we pick up new config changes var cacheKeys = imageEnhancers.Select(i => i.GetConfigurationCacheKey(item, imageType)).ToList(); cacheKeys.Add(originalImagePath + dateModified.Ticks); - return string.Join("|", cacheKeys).GetMD5().ToString("N"); + return string.Join("|", cacheKeys).GetMD5().ToString("N", CultureInfo.InvariantCulture); } private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified) @@ -480,7 +480,7 @@ namespace Emby.Drawing { try { - string filename = (originalImagePath + dateModified.Ticks.ToString(UsCulture)).GetMD5().ToString("N"); + string filename = (originalImagePath + dateModified.Ticks.ToString(UsCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture); string cacheExtension = _mediaEncoder().SupportsEncoder("libwebp") ? ".webp" : ".png"; var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension); diff --git a/Emby.Notifications/NotificationManager.cs b/Emby.Notifications/NotificationManager.cs index 3d1d4722dd..a767e541e1 100644 --- a/Emby.Notifications/NotificationManager.cs +++ b/Emby.Notifications/NotificationManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -101,7 +102,7 @@ namespace Emby.Notifications var config = GetConfiguration(); return _userManager.Users - .Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N"), i.Policy)) + .Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N", CultureInfo.InvariantCulture), i.Policy)) .Select(i => i.Id); } @@ -197,7 +198,7 @@ namespace Emby.Notifications return _services.Select(i => new NameIdPair { Name = i.Name, - Id = i.Name.GetMD5().ToString("N") + Id = i.Name.GetMD5().ToString("N", CultureInfo.InvariantCulture) }).OrderBy(i => i.Name); } diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 0530a251cc..3e44c9c0a2 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -75,7 +76,6 @@ namespace Emby.Server.Implementations.Activity _sessionManager.AuthenticationFailed += OnAuthenticationFailed; _sessionManager.AuthenticationSucceeded += OnAuthenticationSucceeded; _sessionManager.SessionEnded += OnSessionEnded; - _sessionManager.PlaybackStart += OnPlaybackStart; _sessionManager.PlaybackStopped += OnPlaybackStopped; @@ -117,7 +117,7 @@ namespace Emby.Server.Implementations.Activity { Name = string.Format(_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"), e.Provider, Notifications.Notifications.GetItemName(e.Item)), Type = "SubtitleDownloadFailure", - ItemId = e.Item.Id.ToString("N"), + ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture), ShortOverview = e.Exception.Message }); } diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs index de46ab9655..91371b8336 100644 --- a/Emby.Server.Implementations/Activity/ActivityRepository.cs +++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs @@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Activity } else { - statement.TryBind("@UserId", entry.UserId.ToString("N")); + statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture)); } statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue()); @@ -141,7 +141,7 @@ namespace Emby.Server.Implementations.Activity } else { - statement.TryBind("@UserId", entry.UserId.ToString("N")); + statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture)); } statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue()); diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ef2f59d303..4ff90a04b0 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1220,7 +1220,7 @@ namespace Emby.Server.Implementations // Generate self-signed cert var certHost = GetHostnameFromExternalDns(ServerConfigurationManager.Configuration.WanDdns); - var certPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.ProgramDataPath, "ssl", "cert_" + (certHost + "2").GetMD5().ToString("N") + ".pfx"); + var certPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.ProgramDataPath, "ssl", "cert_" + (certHost + "2").GetMD5().ToString("N", CultureInfo.InvariantCulture) + ".pfx"); const string Password = "embycert"; return new CertificateInfo diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index e9961e8bd7..8e5f5b5617 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -206,7 +207,7 @@ namespace Emby.Server.Implementations.Channels try { - return GetChannelProvider(i).IsEnabledFor(user.Id.ToString("N")); + return GetChannelProvider(i).IsEnabledFor(user.Id.ToString("N", CultureInfo.InvariantCulture)); } catch { @@ -511,7 +512,7 @@ namespace Emby.Server.Implementations.Channels IncludeItemTypes = new[] { typeof(Channel).Name }, OrderBy = new ValueTuple[] { new ValueTuple(ItemSortBy.SortName, SortOrder.Ascending) } - }).Select(i => GetChannelFeatures(i.ToString("N"))).ToArray(); + }).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray(); } public ChannelFeatures GetChannelFeatures(string id) @@ -552,7 +553,7 @@ namespace Emby.Server.Implementations.Channels SupportsSortOrderToggle = features.SupportsSortOrderToggle, SupportsLatestMedia = supportsLatest, Name = channel.Name, - Id = channel.Id.ToString("N"), + Id = channel.Id.ToString("N", CultureInfo.InvariantCulture), SupportsContentDownloading = features.SupportsContentDownloading, AutoRefreshLevels = features.AutoRefreshLevels }; @@ -740,7 +741,7 @@ namespace Emby.Server.Implementations.Channels bool sortDescending, CancellationToken cancellationToken) { - var userId = user == null ? null : user.Id.ToString("N"); + var userId = user == null ? null : user.Id.ToString("N", CultureInfo.InvariantCulture); var cacheLength = CacheLength; var cachePath = GetChannelDataCachePath(channel, userId, externalFolderId, sortField, sortDescending); @@ -836,7 +837,7 @@ namespace Emby.Server.Implementations.Channels ChannelItemSortField? sortField, bool sortDescending) { - var channelId = GetInternalChannelId(channel.Name).ToString("N"); + var channelId = GetInternalChannelId(channel.Name).ToString("N", CultureInfo.InvariantCulture); var userCacheKey = string.Empty; @@ -846,10 +847,10 @@ namespace Emby.Server.Implementations.Channels userCacheKey = hasCacheKey.GetCacheKey(userId) ?? string.Empty; } - var filename = string.IsNullOrEmpty(externalFolderId) ? "root" : externalFolderId.GetMD5().ToString("N"); + var filename = string.IsNullOrEmpty(externalFolderId) ? "root" : externalFolderId.GetMD5().ToString("N", CultureInfo.InvariantCulture); filename += userCacheKey; - var version = ((channel.DataVersion ?? string.Empty) + "2").GetMD5().ToString("N"); + var version = ((channel.DataVersion ?? string.Empty) + "2").GetMD5().ToString("N", CultureInfo.InvariantCulture); if (sortField.HasValue) { @@ -860,7 +861,7 @@ namespace Emby.Server.Implementations.Channels filename += "-sortDescending"; } - filename = filename.GetMD5().ToString("N"); + filename = filename.GetMD5().ToString("N", CultureInfo.InvariantCulture); return Path.Combine(_config.ApplicationPaths.CachePath, "channels", diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 2b99e0ddfc..bb5057b1c2 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -182,7 +183,7 @@ namespace Emby.Server.Implementations.Collections public void AddToCollection(Guid collectionId, IEnumerable ids) { - AddToCollection(collectionId, ids.Select(i => i.ToString("N")), true, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))); + AddToCollection(collectionId, ids.Select(i => i.ToString("N", CultureInfo.InvariantCulture)), true, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem))); } private void AddToCollection(Guid collectionId, IEnumerable ids, bool fireEvent, MetadataRefreshOptions refreshOptions) diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs index 01ef9851dc..87096e72f5 100644 --- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Threading; using MediaBrowser.Common.Configuration; @@ -182,7 +183,7 @@ namespace Emby.Server.Implementations.Data return new DisplayPreferences { - Id = guidId.ToString("N") + Id = guidId.ToString("N", CultureInfo.InvariantCulture) }; } } diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 1cefcec7ca..bb4c34f02f 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -696,7 +696,7 @@ namespace Emby.Server.Implementations.Data saveItemStatement.TryBindNull("@EndDate"); } - saveItemStatement.TryBind("@ChannelId", item.ChannelId.Equals(Guid.Empty) ? null : item.ChannelId.ToString("N")); + saveItemStatement.TryBind("@ChannelId", item.ChannelId.Equals(Guid.Empty) ? null : item.ChannelId.ToString("N", CultureInfo.InvariantCulture)); if (item is IHasProgramAttributes hasProgramAttributes) { @@ -851,7 +851,7 @@ namespace Emby.Server.Implementations.Data } else { - saveItemStatement.TryBind("@TopParentId", topParent.Id.ToString("N")); + saveItemStatement.TryBind("@TopParentId", topParent.Id.ToString("N", CultureInfo.InvariantCulture)); } if (item is Trailer trailer && trailer.TrailerTypes.Length > 0) @@ -3548,12 +3548,12 @@ namespace Emby.Server.Implementations.Data whereClauses.Add("ChannelId=@ChannelId"); if (statement != null) { - statement.TryBind("@ChannelId", query.ChannelIds[0].ToString("N")); + statement.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture)); } } else if (query.ChannelIds.Length > 1) { - var inClause = string.Join(",", query.ChannelIds.Select(i => "'" + i.ToString("N") + "'")); + var inClause = string.Join(",", query.ChannelIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'")); whereClauses.Add($"ChannelId in ({inClause})"); } @@ -4537,12 +4537,12 @@ namespace Emby.Server.Implementations.Data } if (statement != null) { - statement.TryBind("@TopParentId", queryTopParentIds[0].ToString("N")); + statement.TryBind("@TopParentId", queryTopParentIds[0].ToString("N", CultureInfo.InvariantCulture)); } } else if (queryTopParentIds.Length > 1) { - var val = string.Join(",", queryTopParentIds.Select(i => "'" + i.ToString("N") + "'")); + var val = string.Join(",", queryTopParentIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'")); if (enableItemsByName && includedItemByNameTypes.Count == 1) { @@ -4574,7 +4574,7 @@ namespace Emby.Server.Implementations.Data } if (query.AncestorIds.Length > 1) { - var inClause = string.Join(",", query.AncestorIds.Select(i => "'" + i.ToString("N") + "'")); + var inClause = string.Join(",", query.AncestorIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'")); whereClauses.Add(string.Format("Guid in (select itemId from AncestorIds where AncestorIdText in ({0}))", inClause)); } if (!string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey)) @@ -4637,7 +4637,7 @@ namespace Emby.Server.Implementations.Data foreach (var folderId in query.BoxSetLibraryFolders) { - folderIdQueries.Add("data like '%" + folderId.ToString("N") + "%'"); + folderIdQueries.Add("data like '%" + folderId.ToString("N", CultureInfo.InvariantCulture) + "%'"); } whereClauses.Add("(" + string.Join(" OR ", folderIdQueries) + ")"); @@ -5161,7 +5161,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type var ancestorId = ancestorIds[i]; statement.TryBind("@AncestorId" + index, ancestorId.ToGuidBlob()); - statement.TryBind("@AncestorIdText" + index, ancestorId.ToString("N")); + statement.TryBind("@AncestorIdText" + index, ancestorId.ToString("N", CultureInfo.InvariantCulture)); } statement.Reset(); @@ -5579,6 +5579,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { counts.TrailerCount = value; } + counts.ItemCount += value; } diff --git a/Emby.Server.Implementations/Devices/DeviceId.cs b/Emby.Server.Implementations/Devices/DeviceId.cs index 495c3436ab..7344dc72f3 100644 --- a/Emby.Server.Implementations/Devices/DeviceId.cs +++ b/Emby.Server.Implementations/Devices/DeviceId.cs @@ -1,8 +1,8 @@ using System; +using System.Globalization; using System.IO; using System.Text; using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Devices @@ -67,7 +67,7 @@ namespace Emby.Server.Implementations.Devices private static string GetNewId() { - return Guid.NewGuid().ToString("N"); + return Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); } private string GetDeviceId() diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index 7d6529a675..d1704b3736 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Entities; @@ -195,7 +195,7 @@ namespace Emby.Server.Implementations.Devices private string GetDevicePath(string id) { - return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N")); + return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N", CultureInfo.InvariantCulture)); } public ContentUploadHistory GetCameraUploadHistory(string deviceId) diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 2f1b60be9c..6e7aa13139 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -213,7 +214,7 @@ namespace Emby.Server.Implementations.Dto if (options.ContainsField(ItemFields.DisplayPreferencesId)) { - dto.DisplayPreferencesId = item.DisplayPreferencesId.ToString("N"); + dto.DisplayPreferencesId = item.DisplayPreferencesId.ToString("N", CultureInfo.InvariantCulture); } if (user != null) @@ -444,7 +445,7 @@ namespace Emby.Server.Implementations.Dto /// item public string GetDtoId(BaseItem item) { - return item.Id.ToString("N"); + return item.Id.ToString("N", CultureInfo.InvariantCulture); } private static void SetBookProperties(BaseItemDto dto, Book item) @@ -608,7 +609,7 @@ namespace Emby.Server.Implementations.Dto if (dictionary.TryGetValue(person.Name, out Person entity)) { baseItemPerson.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary); - baseItemPerson.Id = entity.Id.ToString("N"); + baseItemPerson.Id = entity.Id.ToString("N", CultureInfo.InvariantCulture); list.Add(baseItemPerson); } } @@ -893,7 +894,7 @@ namespace Emby.Server.Implementations.Dto //var artistItems = _libraryManager.GetArtists(new InternalItemsQuery //{ // EnableTotalRecordCount = false, - // ItemIds = new[] { item.Id.ToString("N") } + // ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) } //}); //dto.ArtistItems = artistItems.Items @@ -903,7 +904,7 @@ namespace Emby.Server.Implementations.Dto // return new NameIdPair // { // Name = artist.Name, - // Id = artist.Id.ToString("N") + // Id = artist.Id.ToString("N", CultureInfo.InvariantCulture) // }; // }) // .ToList(); @@ -946,7 +947,7 @@ namespace Emby.Server.Implementations.Dto //var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery //{ // EnableTotalRecordCount = false, - // ItemIds = new[] { item.Id.ToString("N") } + // ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) } //}); //dto.AlbumArtists = artistItems.Items @@ -956,7 +957,7 @@ namespace Emby.Server.Implementations.Dto // return new NameIdPair // { // Name = artist.Name, - // Id = artist.Id.ToString("N") + // Id = artist.Id.ToString("N", CultureInfo.InvariantCulture) // }; // }) // .ToList(); @@ -1044,7 +1045,7 @@ namespace Emby.Server.Implementations.Dto } else { - string id = item.Id.ToString("N"); + string id = item.Id.ToString("N", CultureInfo.InvariantCulture); mediaStreams = dto.MediaSources.Where(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)) .SelectMany(i => i.MediaStreams) .ToArray(); diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 8369f4f593..9c0db2cf5e 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -100,7 +100,7 @@ namespace Emby.Server.Implementations.EntryPoints _lastProgressMessageTimes[item.Id] = DateTime.UtcNow; var dict = new Dictionary(); - dict["ItemId"] = item.Id.ToString("N"); + dict["ItemId"] = item.Id.ToString("N", CultureInfo.InvariantCulture); dict["Progress"] = progress.ToString(CultureInfo.InvariantCulture); try @@ -116,7 +116,7 @@ namespace Emby.Server.Implementations.EntryPoints foreach (var collectionFolder in collectionFolders) { var collectionFolderDict = new Dictionary(); - collectionFolderDict["ItemId"] = collectionFolder.Id.ToString("N"); + collectionFolderDict["ItemId"] = collectionFolder.Id.ToString("N", CultureInfo.InvariantCulture); collectionFolderDict["Progress"] = (collectionFolder.GetRefreshProgress() ?? 0).ToString(CultureInfo.InvariantCulture); try @@ -378,15 +378,15 @@ namespace Emby.Server.Implementations.EntryPoints return new LibraryUpdateInfo { - ItemsAdded = itemsAdded.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(), + ItemsAdded = itemsAdded.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(), - ItemsUpdated = itemsUpdated.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(), + ItemsUpdated = itemsUpdated.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(), - ItemsRemoved = itemsRemoved.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user, true)).Select(i => i.Id.ToString("N")).Distinct().ToArray(), + ItemsRemoved = itemsRemoved.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user, true)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(), - FoldersAddedTo = foldersAddedTo.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(), + FoldersAddedTo = foldersAddedTo.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(), - FoldersRemovedFrom = foldersRemovedFrom.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N")).Distinct().ToArray(), + FoldersRemovedFrom = foldersRemovedFrom.SelectMany(i => TranslatePhysicalItemToUserLibrary(i, user)).Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)).Distinct().ToArray(), CollectionFolders = GetTopParentIds(newAndRemoved, allUserRootChildren).ToArray() }; @@ -422,7 +422,7 @@ namespace Emby.Server.Implementations.EntryPoints var collectionFolders = _libraryManager.GetCollectionFolders(item, allUserRootChildren); foreach (var folder in allUserRootChildren) { - list.Add(folder.Id.ToString("N")); + list.Add(folder.Id.ToString("N", CultureInfo.InvariantCulture)); } } diff --git a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs index 091dd6a45b..141e729584 100644 --- a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Plugins; @@ -134,7 +135,7 @@ namespace Emby.Server.Implementations.EntryPoints /// The e. void userManager_UserDeleted(object sender, GenericEventArgs e) { - SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N")); + SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N", CultureInfo.InvariantCulture)); } void _userManager_UserPolicyUpdated(object sender, GenericEventArgs e) diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index a5badaceec..bae3422ff6 100644 --- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -8,7 +9,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; @@ -125,12 +125,12 @@ namespace Emby.Server.Implementations.EntryPoints .Select(i => { var dto = _userDataManager.GetUserDataDto(i, user); - dto.ItemId = i.Id.ToString("N"); + dto.ItemId = i.Id.ToString("N", CultureInfo.InvariantCulture); return dto; }) .ToArray(); - var userIdString = userId.ToString("N"); + var userIdString = userId.ToString("N", CultureInfo.InvariantCulture); return new UserDataChangeInfo { diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 331b5e29d7..9ca33d7db7 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -195,7 +195,7 @@ namespace Emby.Server.Implementations.HttpClientManager } var url = options.Url; - var urlHash = url.ToLowerInvariant().GetMD5().ToString("N"); + var urlHash = url.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture); var responseCachePath = Path.Combine(_appPaths.CachePath, "httpclient", urlHash); diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 8517abed6c..ae8371a32f 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Diagnostics; using System.IO; using System.Linq; @@ -555,7 +556,7 @@ namespace Emby.Server.Implementations.IO throw new ArgumentNullException(nameof(file2)); } - var temp1 = Path.Combine(_tempPath, Guid.NewGuid().ToString("N")); + var temp1 = Path.Combine(_tempPath, Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)); // Copying over will fail against hidden files SetHidden(file1, false); diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 46f209b4b4..d8faceadb2 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -89,7 +90,7 @@ namespace Emby.Server.Implementations.Images ImageType imageType, CancellationToken cancellationToken) { - var outputPathWithoutExtension = Path.Combine(ApplicationPaths.TempDirectory, Guid.NewGuid().ToString("N")); + var outputPathWithoutExtension = Path.Combine(ApplicationPaths.TempDirectory, Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)); Directory.CreateDirectory(Path.GetDirectoryName(outputPathWithoutExtension)); string outputPath = CreateImage(item, itemsWithImages, outputPathWithoutExtension, imageType, 0); diff --git a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs index 45a33a2969..a3c879f121 100644 --- a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs +++ b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Library; @@ -26,7 +27,7 @@ namespace Emby.Server.Implementations.Library EnableStreamSharing = false; _closeFn = closeFn; ConsumerCount = 1; - UniqueId = Guid.NewGuid().ToString("N"); + UniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); } public Task Close() diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 4b5063ada6..30ff855cc2 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1187,12 +1187,12 @@ namespace Emby.Server.Implementations.Library if (libraryFolder != null && libraryFolder.HasImage(ImageType.Primary)) { - info.PrimaryImageItemId = libraryFolder.Id.ToString("N"); + info.PrimaryImageItemId = libraryFolder.Id.ToString("N", CultureInfo.InvariantCulture); } if (libraryFolder != null) { - info.ItemId = libraryFolder.Id.ToString("N"); + info.ItemId = libraryFolder.Id.ToString("N", CultureInfo.InvariantCulture); info.LibraryOptions = GetLibraryOptions(libraryFolder); if (refreshQueue != null) @@ -2135,12 +2135,12 @@ namespace Emby.Server.Implementations.Library string viewType, string sortName) { - var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N"); - var idValues = "38_namedview_" + name + user.Id.ToString("N") + (parentIdString ?? string.Empty) + (viewType ?? string.Empty); + var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N", CultureInfo.InvariantCulture); + var idValues = "38_namedview_" + name + user.Id.ToString("N", CultureInfo.InvariantCulture) + (parentIdString ?? string.Empty) + (viewType ?? string.Empty); var id = GetNewItemId(idValues, typeof(UserView)); - var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N")); + var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture)); var item = GetItemById(id) as UserView; @@ -2271,7 +2271,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(name)); } - var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N"); + var parentIdString = parentId.Equals(Guid.Empty) ? null : parentId.ToString("N", CultureInfo.InvariantCulture); var idValues = "37_namedview_" + name + (parentIdString ?? string.Empty) + (viewType ?? string.Empty); if (!string.IsNullOrEmpty(uniqueId)) { @@ -2280,7 +2280,7 @@ namespace Emby.Server.Implementations.Library var id = GetNewItemId(idValues, typeof(UserView)); - var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N")); + var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N", CultureInfo.InvariantCulture)); var item = GetItemById(id) as UserView; diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs index c3082a78a7..33e6f24341 100644 --- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs +++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs @@ -40,7 +40,7 @@ namespace Emby.Server.Implementations.Library var now = DateTime.UtcNow; MediaInfo mediaInfo = null; - var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N") + ".json"); + var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N", CultureInfo.InvariantCulture) + ".json"); if (!string.IsNullOrEmpty(cacheKey)) { diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 24ab8e7619..d83e1fc021 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -269,7 +269,7 @@ namespace Emby.Server.Implementations.Library private static void SetKeyProperties(IMediaSourceProvider provider, MediaSourceInfo mediaSource) { - var prefix = provider.GetType().FullName.GetMD5().ToString("N") + LiveStreamIdDelimeter; + var prefix = provider.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + LiveStreamIdDelimeter; if (!string.IsNullOrEmpty(mediaSource.OpenToken) && !mediaSource.OpenToken.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { @@ -626,7 +626,7 @@ namespace Emby.Server.Implementations.Library var now = DateTime.UtcNow; MediaInfo mediaInfo = null; - var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N") + ".json"); + var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N", CultureInfo.InvariantCulture) + ".json"); if (!string.IsNullOrEmpty(cacheKey)) { @@ -854,7 +854,7 @@ namespace Emby.Server.Implementations.Library var keys = key.Split(new[] { LiveStreamIdDelimeter }, 2); - var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), keys[0], StringComparison.OrdinalIgnoreCase)); + var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture), keys[0], StringComparison.OrdinalIgnoreCase)); var splitIndex = key.IndexOf(LiveStreamIdDelimeter); var keyId = key.Substring(splitIndex + 1); diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index dfa1edaffd..36adc0b9c4 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -152,7 +152,7 @@ namespace Emby.Server.Implementations.Library /// System.String. private static string GetCacheKey(long internalUserId, Guid itemId) { - return internalUserId.ToString(CultureInfo.InvariantCulture) + "-" + itemId.ToString("N"); + return internalUserId.ToString(CultureInfo.InvariantCulture) + "-" + itemId.ToString("N", CultureInfo.InvariantCulture); } public UserItemData GetUserData(User user, BaseItem item) diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index e9ce682ee1..71f16ac3e9 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using MediaBrowser.Controller.Channels; @@ -117,7 +118,7 @@ namespace Emby.Server.Implementations.Library if (!query.IncludeHidden) { - list = list.Where(i => !user.Configuration.MyMediaExcludes.Contains(i.Id.ToString("N"))).ToList(); + list = list.Where(i => !user.Configuration.MyMediaExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))).ToList(); } var sorted = _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList(); @@ -127,7 +128,7 @@ namespace Emby.Server.Implementations.Library return list .OrderBy(i => { - var index = orders.IndexOf(i.Id.ToString("N")); + var index = orders.IndexOf(i.Id.ToString("N", CultureInfo.InvariantCulture)); if (index == -1) { @@ -136,7 +137,7 @@ namespace Emby.Server.Implementations.Library { if (!view.DisplayParentId.Equals(Guid.Empty)) { - index = orders.IndexOf(view.DisplayParentId.ToString("N")); + index = orders.IndexOf(view.DisplayParentId.ToString("N", CultureInfo.InvariantCulture)); } } } @@ -269,7 +270,7 @@ namespace Emby.Server.Implementations.Library { parents = _libraryManager.GetUserRootFolder().GetChildren(user, true) .Where(i => i is Folder) - .Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N"))) + .Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))) .ToList(); } diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs index 294348660f..b584cc649d 100644 --- a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -91,7 +92,7 @@ namespace Emby.Server.Implementations.Library.Validators continue; } - _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N"), item.Name, item.GetType().Name); + _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name); _libraryManager.DeleteItem(item, new DeleteOptions { diff --git a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs index 7899cf01b3..d00c6cde11 100644 --- a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs @@ -1,7 +1,7 @@ using System; +using System.Globalization; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; @@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.Library.Validators foreach (var item in deadEntities) { - _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N"), item.Name, item.GetType().Name); + _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name); _libraryManager.DeleteItem(item, new DeleteOptions { diff --git a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs index da4645a11d..93ded9e7bd 100644 --- a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Entities; @@ -76,7 +77,7 @@ namespace Emby.Server.Implementations.Library.Validators foreach (var item in deadEntities) { - _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N"), item.Name, item.GetType().Name); + _logger.LogInformation("Deleting dead {2} {0} {1}.", item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name, item.GetType().Name); _libraryManager.DeleteItem(item, new DeleteOptions { diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 7b210d2313..d7411af502 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -681,7 +681,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - timer.Id = Guid.NewGuid().ToString("N"); + timer.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); LiveTvProgram programInfo = null; @@ -713,7 +713,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV public async Task CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken) { - info.Id = Guid.NewGuid().ToString("N"); + info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); // populate info.seriesID var program = GetProgramInfoFromCache(info.ProgramId); @@ -1059,7 +1059,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var json = _jsonSerializer.SerializeToString(mediaSource); mediaSource = _jsonSerializer.DeserializeFromString(json); - mediaSource.Id = Guid.NewGuid().ToString("N") + "_" + mediaSource.Id; + mediaSource.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture) + "_" + mediaSource.Id; //if (mediaSource.DateLiveStreamOpened.HasValue && enableStreamSharing) //{ @@ -2529,7 +2529,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var timer = new TimerInfo { ChannelId = channelId, - Id = (seriesTimer.Id + parent.ExternalId).GetMD5().ToString("N"), + Id = (seriesTimer.Id + parent.ExternalId).GetMD5().ToString("N", CultureInfo.InvariantCulture), StartDate = parent.StartDate, EndDate = parent.EndDate.Value, ProgramId = parent.ExternalId, diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 94225a0aa9..88693f22a4 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -211,7 +211,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings HasImage = program.Icon != null && !string.IsNullOrEmpty(program.Icon.Source), OfficialRating = program.Rating != null && !string.IsNullOrEmpty(program.Rating.Value) ? program.Rating.Value : null, CommunityRating = program.StarRating, - SeriesId = program.Episode == null ? null : program.Title.GetMD5().ToString("N") + SeriesId = program.Episode == null ? null : program.Title.GetMD5().ToString("N", CultureInfo.InvariantCulture) }; if (string.IsNullOrWhiteSpace(program.ProgramId)) @@ -227,7 +227,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings uniqueString = "-" + programInfo.EpisodeNumber.Value.ToString(CultureInfo.InvariantCulture); } - programInfo.ShowId = uniqueString.GetMD5().ToString("N"); + programInfo.ShowId = uniqueString.GetMD5().ToString("N", CultureInfo.InvariantCulture); // If we don't have valid episode info, assume it's a unique program, otherwise recordings might be skipped if (programInfo.IsSeries diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs index 1144c9ab17..e584664c94 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -52,7 +53,7 @@ namespace Emby.Server.Implementations.LiveTv ExternalId = info.Id, ChannelId = GetInternalChannelId(service.Name, info.ChannelId), Status = info.Status, - SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId) ? null : GetInternalSeriesTimerId(info.SeriesTimerId).ToString("N"), + SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId) ? null : GetInternalSeriesTimerId(info.SeriesTimerId).ToString("N", CultureInfo.InvariantCulture), PrePaddingSeconds = info.PrePaddingSeconds, PostPaddingSeconds = info.PostPaddingSeconds, IsPostPaddingRequired = info.IsPostPaddingRequired, @@ -69,7 +70,7 @@ namespace Emby.Server.Implementations.LiveTv if (!string.IsNullOrEmpty(info.ProgramId)) { - dto.ProgramId = GetInternalProgramId(info.ProgramId).ToString("N"); + dto.ProgramId = GetInternalProgramId(info.ProgramId).ToString("N", CultureInfo.InvariantCulture); } if (program != null) @@ -107,7 +108,7 @@ namespace Emby.Server.Implementations.LiveTv { var dto = new SeriesTimerInfoDto { - Id = GetInternalSeriesTimerId(info.Id).ToString("N"), + Id = GetInternalSeriesTimerId(info.Id).ToString("N", CultureInfo.InvariantCulture), Overview = info.Overview, EndDate = info.EndDate, Name = info.Name, @@ -139,7 +140,7 @@ namespace Emby.Server.Implementations.LiveTv if (!string.IsNullOrEmpty(info.ProgramId)) { - dto.ProgramId = GetInternalProgramId(info.ProgramId).ToString("N"); + dto.ProgramId = GetInternalProgramId(info.ProgramId).ToString("N", CultureInfo.InvariantCulture); } dto.DayPattern = info.Days == null ? null : GetDayPattern(info.Days.ToArray()); @@ -169,7 +170,7 @@ namespace Emby.Server.Implementations.LiveTv try { dto.ParentThumbImageTag = _imageProcessor.GetImageCacheTag(librarySeries, image); - dto.ParentThumbItemId = librarySeries.Id.ToString("N"); + dto.ParentThumbItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture); } catch (Exception ex) { @@ -185,7 +186,7 @@ namespace Emby.Server.Implementations.LiveTv { _imageProcessor.GetImageCacheTag(librarySeries, image) }; - dto.ParentBackdropItemId = librarySeries.Id.ToString("N"); + dto.ParentBackdropItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture); } catch (Exception ex) { @@ -213,7 +214,7 @@ namespace Emby.Server.Implementations.LiveTv try { dto.ParentPrimaryImageTag = _imageProcessor.GetImageCacheTag(program, image); - dto.ParentPrimaryImageItemId = program.Id.ToString("N"); + dto.ParentPrimaryImageItemId = program.Id.ToString("N", CultureInfo.InvariantCulture); } catch (Exception ex) { @@ -232,7 +233,7 @@ namespace Emby.Server.Implementations.LiveTv { _imageProcessor.GetImageCacheTag(program, image) }; - dto.ParentBackdropItemId = program.Id.ToString("N"); + dto.ParentBackdropItemId = program.Id.ToString("N", CultureInfo.InvariantCulture); } catch (Exception ex) { @@ -263,7 +264,7 @@ namespace Emby.Server.Implementations.LiveTv try { dto.ParentThumbImageTag = _imageProcessor.GetImageCacheTag(librarySeries, image); - dto.ParentThumbItemId = librarySeries.Id.ToString("N"); + dto.ParentThumbItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture); } catch (Exception ex) { @@ -279,7 +280,7 @@ namespace Emby.Server.Implementations.LiveTv { _imageProcessor.GetImageCacheTag(librarySeries, image) }; - dto.ParentBackdropItemId = librarySeries.Id.ToString("N"); + dto.ParentBackdropItemId = librarySeries.Id.ToString("N", CultureInfo.InvariantCulture); } catch (Exception ex) { @@ -320,7 +321,7 @@ namespace Emby.Server.Implementations.LiveTv try { dto.ParentPrimaryImageTag = _imageProcessor.GetImageCacheTag(program, image); - dto.ParentPrimaryImageItemId = program.Id.ToString("N"); + dto.ParentPrimaryImageItemId = program.Id.ToString("N", CultureInfo.InvariantCulture); } catch (Exception ex) { @@ -339,7 +340,7 @@ namespace Emby.Server.Implementations.LiveTv { _imageProcessor.GetImageCacheTag(program, image) }; - dto.ParentBackdropItemId = program.Id.ToString("N"); + dto.ParentBackdropItemId = program.Id.ToString("N", CultureInfo.InvariantCulture); } catch (Exception ex) { @@ -407,7 +408,7 @@ namespace Emby.Server.Implementations.LiveTv { var name = ServiceName + externalId + InternalVersionNumber; - return name.ToLowerInvariant().GetMD5().ToString("N"); + return name.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture); } public Guid GetInternalSeriesTimerId(string externalId) diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 9093d9740f..1e5198dd6c 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -258,7 +259,7 @@ namespace Emby.Server.Implementations.LiveTv } info.RequiresClosing = true; - var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_"; + var idPrefix = service.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_"; info.LiveStreamId = idPrefix + info.Id; @@ -820,7 +821,7 @@ namespace Emby.Server.Implementations.LiveTv if (!string.IsNullOrWhiteSpace(query.SeriesTimerId)) { var seriesTimers = await GetSeriesTimersInternal(new SeriesTimerQuery { }, cancellationToken).ConfigureAwait(false); - var seriesTimer = seriesTimers.Items.FirstOrDefault(i => string.Equals(_tvDtoService.GetInternalSeriesTimerId(i.Id).ToString("N"), query.SeriesTimerId, StringComparison.OrdinalIgnoreCase)); + var seriesTimer = seriesTimers.Items.FirstOrDefault(i => string.Equals(_tvDtoService.GetInternalSeriesTimerId(i.Id).ToString("N", CultureInfo.InvariantCulture), query.SeriesTimerId, StringComparison.OrdinalIgnoreCase)); if (seriesTimer != null) { internalQuery.ExternalSeriesId = seriesTimer.SeriesId; @@ -997,7 +998,7 @@ namespace Emby.Server.Implementations.LiveTv if (!string.IsNullOrEmpty(timer.SeriesTimerId)) { program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(timer.SeriesTimerId) - .ToString("N"); + .ToString("N", CultureInfo.InvariantCulture); foundSeriesTimer = true; } @@ -1018,7 +1019,7 @@ namespace Emby.Server.Implementations.LiveTv if (seriesTimer != null) { program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(seriesTimer.Id) - .ToString("N"); + .ToString("N", CultureInfo.InvariantCulture); } } } @@ -1472,7 +1473,7 @@ namespace Emby.Server.Implementations.LiveTv dto.SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId) ? null - : _tvDtoService.GetInternalSeriesTimerId(info.SeriesTimerId).ToString("N"); + : _tvDtoService.GetInternalSeriesTimerId(info.SeriesTimerId).ToString("N", CultureInfo.InvariantCulture); dto.TimerId = string.IsNullOrEmpty(info.Id) ? null @@ -2027,7 +2028,7 @@ namespace Emby.Server.Implementations.LiveTv info.StartDate = program.StartDate; info.Name = program.Name; info.Overview = program.Overview; - info.ProgramId = programDto.Id.ToString("N"); + info.ProgramId = programDto.Id.ToString("N", CultureInfo.InvariantCulture); info.ExternalProgramId = program.ExternalId; if (program.EndDate.HasValue) @@ -2088,7 +2089,7 @@ namespace Emby.Server.Implementations.LiveTv if (service is ISupportsNewTimerIds supportsNewTimerIds) { newTimerId = await supportsNewTimerIds.CreateSeriesTimer(info, cancellationToken).ConfigureAwait(false); - newTimerId = _tvDtoService.GetInternalSeriesTimerId(newTimerId).ToString("N"); + newTimerId = _tvDtoService.GetInternalSeriesTimerId(newTimerId).ToString("N", CultureInfo.InvariantCulture); } else { @@ -2192,7 +2193,7 @@ namespace Emby.Server.Implementations.LiveTv info.EnabledUsers = _userManager.Users .Where(IsLiveTvEnabled) - .Select(i => i.Id.ToString("N")) + .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)) .ToArray(); return info; @@ -2219,7 +2220,7 @@ namespace Emby.Server.Implementations.LiveTv { var parts = id.Split(new[] { '_' }, 2); - var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), parts[0], StringComparison.OrdinalIgnoreCase)); + var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture), parts[0], StringComparison.OrdinalIgnoreCase)); if (service == null) { @@ -2269,7 +2270,7 @@ namespace Emby.Server.Implementations.LiveTv if (index == -1 || string.IsNullOrWhiteSpace(info.Id)) { - info.Id = Guid.NewGuid().ToString("N"); + info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); list.Add(info); config.TunerHosts = list.ToArray(); } @@ -2312,7 +2313,7 @@ namespace Emby.Server.Implementations.LiveTv if (index == -1 || string.IsNullOrWhiteSpace(info.Id)) { - info.Id = Guid.NewGuid().ToString("N"); + info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); list.Add(info); config.ListingProviders = list.ToArray(); } diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index cd1731de55..52d60c004a 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -101,7 +102,7 @@ namespace Emby.Server.Implementations.LiveTv { var openKeys = new List(); openKeys.Add(item.GetType().Name); - openKeys.Add(item.Id.ToString("N")); + openKeys.Add(item.Id.ToString("N", CultureInfo.InvariantCulture)); openKeys.Add(source.Id ?? string.Empty); source.OpenToken = string.Join(StreamIdDelimeterString, openKeys.ToArray()); } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index ed524cae30..db016ec70b 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -460,7 +461,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { id = "native"; } - id += "_" + channelId.GetMD5().ToString("N") + "_" + url.GetMD5().ToString("N"); + id += "_" + channelId.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_" + url.GetMD5().ToString("N", CultureInfo.InvariantCulture); var mediaSource = new MediaSourceInfo { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index ece2cbd547..b4395e2e1d 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -42,7 +43,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts MediaSource = mediaSource; Logger = logger; EnableStreamSharing = true; - UniqueId = Guid.NewGuid().ToString("N"); + UniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); if (tuner != null) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 2d9bec53f0..6c5c80827d 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -43,7 +44,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts private string GetFullChannelIdPrefix(TunerHostInfo info) { - return ChannelIdPrefix + info.Url.GetMD5().ToString("N"); + return ChannelIdPrefix + info.Url.GetMD5().ToString("N", CultureInfo.InvariantCulture); } protected override async Task> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken) @@ -61,7 +62,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts Name = Name, SourceType = Type, Status = LiveTvTunerStatus.Available, - Id = i.Url.GetMD5().ToString("N"), + Id = i.Url.GetMD5().ToString("N", CultureInfo.InvariantCulture), Url = i.Url }) .ToList(); @@ -173,7 +174,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts ReadAtNativeFramerate = false, - Id = channel.Path.GetMD5().ToString("N"), + Id = channel.Path.GetMD5().ToString("N", CultureInfo.InvariantCulture), IsInfiniteStream = true, IsRemote = isRemote, diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 814031b126..e8cd129f5c 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -92,11 +92,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts var channel = GetChannelnfo(extInf, tunerHostId, line); if (string.IsNullOrWhiteSpace(channel.Id)) { - channel.Id = channelIdPrefix + line.GetMD5().ToString("N"); + channel.Id = channelIdPrefix + line.GetMD5().ToString("N", CultureInfo.InvariantCulture); } else { - channel.Id = channelIdPrefix + channel.Id.GetMD5().ToString("N"); + channel.Id = channelIdPrefix + channel.Id.GetMD5().ToString("N", CultureInfo.InvariantCulture); } channel.Path = line; diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 29836e0bff..40b568b40a 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -129,7 +130,7 @@ namespace Emby.Server.Implementations.Playlists { new Share { - UserId = options.UserId.Equals(Guid.Empty) ? null : options.UserId.ToString("N"), + UserId = options.UserId.Equals(Guid.Empty) ? null : options.UserId.ToString("N", CultureInfo.InvariantCulture), CanEdit = true } } @@ -144,7 +145,7 @@ namespace Emby.Server.Implementations.Playlists if (options.ItemIdList.Length > 0) { - AddToPlaylistInternal(playlist.Id.ToString("N"), options.ItemIdList, user, new DtoOptions(false) + AddToPlaylistInternal(playlist.Id.ToString("N", CultureInfo.InvariantCulture), options.ItemIdList, user, new DtoOptions(false) { EnableImages = true }); @@ -152,7 +153,7 @@ namespace Emby.Server.Implementations.Playlists return new PlaylistCreationResult { - Id = playlist.Id.ToString("N") + Id = playlist.Id.ToString("N", CultureInfo.InvariantCulture) }; } finally diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 08bb39578b..83226b07f1 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -287,7 +287,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { if (_id == null) { - _id = ScheduledTask.GetType().FullName.GetMD5().ToString("N"); + _id = ScheduledTask.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture); } return _id; diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 545e11bf9f..26a08cbe9a 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -174,7 +174,7 @@ namespace Emby.Server.Implementations.Security if (!query.UserId.Equals(Guid.Empty)) { - statement.TryBind("@UserId", query.UserId.ToString("N")); + statement.TryBind("@UserId", query.UserId.ToString("N", CultureInfo.InvariantCulture)); } if (!string.IsNullOrEmpty(query.DeviceId)) diff --git a/Emby.Server.Implementations/Serialization/JsonSerializer.cs b/Emby.Server.Implementations/Serialization/JsonSerializer.cs index 8ae7fd90cc..36196ee36c 100644 --- a/Emby.Server.Implementations/Serialization/JsonSerializer.cs +++ b/Emby.Server.Implementations/Serialization/JsonSerializer.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.IO; using System.Threading.Tasks; using MediaBrowser.Model.IO; @@ -245,7 +246,7 @@ namespace Emby.Server.Implementations.Serialization return null; } - return guid.ToString("N"); + return guid.ToString("N", CultureInfo.InvariantCulture); } /// diff --git a/Emby.Server.Implementations/Session/HttpSessionController.cs b/Emby.Server.Implementations/Session/HttpSessionController.cs index 9281f82b39..1104a7a85b 100644 --- a/Emby.Server.Implementations/Session/HttpSessionController.cs +++ b/Emby.Server.Implementations/Session/HttpSessionController.cs @@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.Session { var dict = new Dictionary(); - dict["ItemIds"] = string.Join(",", command.ItemIds.Select(i => i.ToString("N")).ToArray()); + dict["ItemIds"] = string.Join(",", command.ItemIds.Select(i => i.ToString("N", CultureInfo.InvariantCulture)).ToArray()); if (command.StartPositionTicks.HasValue) { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 53ed5fc223..7ee573da5f 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -327,7 +327,7 @@ namespace Emby.Server.Implementations.Session { if (string.IsNullOrEmpty(info.MediaSourceId)) { - info.MediaSourceId = info.ItemId.ToString("N"); + info.MediaSourceId = info.ItemId.ToString("N", CultureInfo.InvariantCulture); } if (!info.ItemId.Equals(Guid.Empty) && info.Item == null && libraryItem != null) @@ -463,7 +463,7 @@ namespace Emby.Server.Implementations.Session Client = appName, DeviceId = deviceId, ApplicationVersion = appVersion, - Id = key.GetMD5().ToString("N"), + Id = key.GetMD5().ToString("N", CultureInfo.InvariantCulture), ServerId = _appHost.SystemId }; @@ -845,7 +845,7 @@ namespace Emby.Server.Implementations.Session // Normalize if (string.IsNullOrEmpty(info.MediaSourceId)) { - info.MediaSourceId = info.ItemId.ToString("N"); + info.MediaSourceId = info.ItemId.ToString("N", CultureInfo.InvariantCulture); } if (!info.ItemId.Equals(Guid.Empty) && info.Item == null && libraryItem != null) @@ -1029,7 +1029,7 @@ namespace Emby.Server.Implementations.Session private static async Task SendMessageToSession(SessionInfo session, string name, T data, CancellationToken cancellationToken) { var controllers = session.SessionControllers.ToArray(); - var messageId = Guid.NewGuid().ToString("N"); + var messageId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); foreach (var controller in controllers) { @@ -1234,7 +1234,7 @@ namespace Emby.Server.Implementations.Session AssertCanControl(session, controllingSession); if (!controllingSession.UserId.Equals(Guid.Empty)) { - command.ControllingUserId = controllingSession.UserId.ToString("N"); + command.ControllingUserId = controllingSession.UserId.ToString("N", CultureInfo.InvariantCulture); } } @@ -1484,7 +1484,7 @@ namespace Emby.Server.Implementations.Session DeviceId = deviceId, DeviceName = deviceName, UserId = user.Id, - AccessToken = Guid.NewGuid().ToString("N"), + AccessToken = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture), UserName = user.Name }; @@ -1822,6 +1822,7 @@ namespace Emby.Server.Implementations.Session CheckDisposed(); var sessions = Sessions.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)); + return SendMessageToSessions(sessions, name, data, cancellationToken); } @@ -1831,6 +1832,7 @@ namespace Emby.Server.Implementations.Session var sessions = Sessions .Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase) || IsAdminSession(i)); + return SendMessageToSessions(sessions, name, data, cancellationToken); } diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index 630ef4893b..4c2f24e6f2 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; @@ -73,7 +74,7 @@ namespace Emby.Server.Implementations.TV { parents = _libraryManager.GetUserRootFolder().GetChildren(user, true) .Where(i => i is Folder) - .Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N"))) + .Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))) .ToArray(); } diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 10bbc9e5d8..23c7339d27 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -537,7 +538,7 @@ namespace MediaBrowser.Api.Images if (item == null) { - throw new ResourceNotFoundException(string.Format("Item {0} not found.", itemId.ToString("N"))); + throw new ResourceNotFoundException(string.Format("Item {0} not found.", itemId.ToString("N", CultureInfo.InvariantCulture))); } } diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs index d6bcf7878b..7266bf9f92 100644 --- a/MediaBrowser.Api/Library/LibraryStructureService.cs +++ b/MediaBrowser.Api/Library/LibraryStructureService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -272,7 +273,7 @@ namespace MediaBrowser.Api.Library // Changing capitalization. Handle windows case insensitivity if (string.Equals(currentPath, newPath, StringComparison.OrdinalIgnoreCase)) { - var tempPath = Path.Combine(rootFolderPath, Guid.NewGuid().ToString("N")); + var tempPath = Path.Combine(rootFolderPath, Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)); Directory.Move(currentPath, tempPath); currentPath = tempPath; } diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index 91766255fd..d601fb5007 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; @@ -11,7 +12,6 @@ using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; @@ -268,7 +268,7 @@ namespace MediaBrowser.Api.Movies EnableGroupByMetadataKey = true, DtoOptions = dtoOptions - }).GroupBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N")) + }).GroupBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)) .Select(x => x.First()) .Take(itemLimit) .ToList(); @@ -309,7 +309,7 @@ namespace MediaBrowser.Api.Movies EnableGroupByMetadataKey = true, DtoOptions = dtoOptions - }).GroupBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N")) + }).GroupBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)) .Select(x => x.First()) .Take(itemLimit) .ToList(); diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 3994016240..114d3f7a24 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -142,7 +142,7 @@ namespace MediaBrowser.Api.Playback data += "-" + (state.Request.DeviceId ?? string.Empty) + "-" + (state.Request.PlaySessionId ?? string.Empty); - var filename = data.GetMD5().ToString("N"); + var filename = data.GetMD5().ToString("N", CultureInfo.InvariantCulture); var ext = outputFileExtension.ToLowerInvariant(); var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath; @@ -215,6 +215,12 @@ namespace MediaBrowser.Api.Playback var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions(); +<<<<<<< HEAD +======= + var transcodingId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); + var commandLineArgs = GetCommandLineArguments(outputPath, encodingOptions, state, true); + +>>>>>>> Use CultureInvariant string conversion for Guids var process = new Process() { StartInfo = new ProcessStartInfo() diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index ab3994a639..da8f99a3dd 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -306,7 +307,7 @@ namespace MediaBrowser.Api.Playback { result.MediaSources = Clone(result.MediaSources); - result.PlaySessionId = Guid.NewGuid().ToString("N"); + result.PlaySessionId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); } return result; diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs index ecf07c9122..6c67d4fb15 100644 --- a/MediaBrowser.Api/SearchService.cs +++ b/MediaBrowser.Api/SearchService.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; @@ -305,7 +306,7 @@ namespace MediaBrowser.Api if (tag != null) { hint.ThumbImageTag = tag; - hint.ThumbImageItemId = itemWithImage.Id.ToString("N"); + hint.ThumbImageItemId = itemWithImage.Id.ToString("N", CultureInfo.InvariantCulture); } } } @@ -326,7 +327,7 @@ namespace MediaBrowser.Api if (tag != null) { hint.BackdropImageTag = tag; - hint.BackdropImageItemId = itemWithImage.Id.ToString("N"); + hint.BackdropImageItemId = itemWithImage.Id.ToString("N", CultureInfo.InvariantCulture); } } } diff --git a/MediaBrowser.Api/Session/SessionsService.cs b/MediaBrowser.Api/Session/SessionsService.cs index 4109b12bfa..76392e27c9 100644 --- a/MediaBrowser.Api/Session/SessionsService.cs +++ b/MediaBrowser.Api/Session/SessionsService.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -316,7 +317,7 @@ namespace MediaBrowser.Api.Session _authRepo.Create(new AuthenticationInfo { AppName = request.App, - AccessToken = Guid.NewGuid().ToString("N"), + AccessToken = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture), DateCreated = DateTime.UtcNow, DeviceId = _appHost.SystemId, DeviceName = _appHost.FriendlyName, diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs index 08aa540a54..52043d3df6 100644 --- a/MediaBrowser.Api/Subtitles/SubtitleService.cs +++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs @@ -168,7 +168,7 @@ namespace MediaBrowser.Api.Subtitles builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0"); builder.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD"); - long positionTicks = 0; + long positionTicks = 0; var accessToken = _authContext.GetAuthorizationInfo(Request).Token; @@ -206,7 +206,7 @@ namespace MediaBrowser.Api.Subtitles { var item = (Video)_libraryManager.GetItemById(request.Id); - var idString = request.Id.ToString("N"); + var idString = request.Id.ToString("N", CultureInfo.InvariantCulture); var mediaSource = _mediaSourceManager.GetStaticMediaSources(item, false, null) .First(i => string.Equals(i.Id, request.MediaSourceId ?? idString)); diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index b0900a5548..2951fa6b47 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dto; @@ -470,7 +471,7 @@ namespace MediaBrowser.Api if (!string.IsNullOrWhiteSpace(request.StartItemId)) { - episodes = episodes.SkipWhile(i => !string.Equals(i.Id.ToString("N"), request.StartItemId, StringComparison.OrdinalIgnoreCase)).ToList(); + episodes = episodes.SkipWhile(i => !string.Equals(i.Id.ToString("N", CultureInfo.InvariantCulture), request.StartItemId, StringComparison.OrdinalIgnoreCase)).ToList(); } // This must be the last filter diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index f842230ee3..c605cd3962 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -99,7 +99,7 @@ namespace MediaBrowser.Api.UserLibrary { ancestorIds = _libraryManager.GetUserRootFolder().GetChildren(user, true) .Where(i => i is Folder) - .Where(i => !excludeFolderIds.Contains(i.Id.ToString("N"))) + .Where(i => !excludeFolderIds.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))) .Select(i => i.Id) .ToArray(); } diff --git a/MediaBrowser.Api/UserLibrary/UserViewsService.cs b/MediaBrowser.Api/UserLibrary/UserViewsService.cs index 1d61c5c1e8..2fa5d8933c 100644 --- a/MediaBrowser.Api/UserLibrary/UserViewsService.cs +++ b/MediaBrowser.Api/UserLibrary/UserViewsService.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -116,7 +117,7 @@ namespace MediaBrowser.Api.UserLibrary .Select(i => new SpecialViewOption { Name = i.Name, - Id = i.Id.ToString("N") + Id = i.Id.ToString("N", CultureInfo.InvariantCulture) }) .OrderBy(i => i.Name) diff --git a/MediaBrowser.Api/VideosService.cs b/MediaBrowser.Api/VideosService.cs index 061f724380..474036f5cb 100644 --- a/MediaBrowser.Api/VideosService.cs +++ b/MediaBrowser.Api/VideosService.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using System.Threading; using MediaBrowser.Controller.Configuration; @@ -168,7 +169,7 @@ namespace MediaBrowser.Api foreach (var item in items.Where(i => i.Id != primaryVersion.Id)) { - item.SetPrimaryVersionId(primaryVersion.Id.ToString("N")); + item.SetPrimaryVersionId(primaryVersion.Id.ToString("N", CultureInfo.InvariantCulture)); item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); diff --git a/MediaBrowser.Controller/Channels/Channel.cs b/MediaBrowser.Controller/Channels/Channel.cs index adf03fb661..89159973b8 100644 --- a/MediaBrowser.Controller/Channels/Channel.cs +++ b/MediaBrowser.Controller/Channels/Channel.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using System.Threading; using MediaBrowser.Common.Progress; @@ -14,14 +15,14 @@ namespace MediaBrowser.Controller.Channels { if (user.Policy.BlockedChannels != null) { - if (user.Policy.BlockedChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) + if (user.Policy.BlockedChannels.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) { return false; } } else { - if (!user.Policy.EnableAllChannels && !user.Policy.EnabledChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) + if (!user.Policy.EnableAllChannels && !user.Policy.EnabledChannels.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) { return false; } @@ -60,7 +61,7 @@ namespace MediaBrowser.Controller.Channels public static string GetInternalMetadataPath(string basePath, Guid id) { - return System.IO.Path.Combine(basePath, "channels", id.ToString("N"), "metadata"); + return System.IO.Path.Combine(basePath, "channels", id.ToString("N", CultureInfo.InvariantCulture), "metadata"); } public override bool CanDelete() diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 10a603e42c..2ae856b020 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -503,7 +503,7 @@ namespace MediaBrowser.Controller.Entities foreach (var folder in collectionFolders) { - if (allowed.Contains(folder.Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) + if (allowed.Contains(folder.Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) { return true; } @@ -664,10 +664,10 @@ namespace MediaBrowser.Controller.Entities { if (SourceType == SourceType.Channel) { - return System.IO.Path.Combine(basePath, "channels", ChannelId.ToString("N"), Id.ToString("N")); + return System.IO.Path.Combine(basePath, "channels", ChannelId.ToString("N", CultureInfo.InvariantCulture), Id.ToString("N", CultureInfo.InvariantCulture)); } - var idString = Id.ToString("N"); + var idString = Id.ToString("N", CultureInfo.InvariantCulture); basePath = System.IO.Path.Combine(basePath, "library"); @@ -1095,7 +1095,7 @@ namespace MediaBrowser.Controller.Entities var info = new MediaSourceInfo { - Id = item.Id.ToString("N"), + Id = item.Id.ToString("N", CultureInfo.InvariantCulture), Protocol = protocol ?? MediaProtocol.File, MediaStreams = MediaSourceManager.GetMediaStreams(item.Id), Name = GetMediaSourceName(item), @@ -1113,7 +1113,7 @@ namespace MediaBrowser.Controller.Entities if (info.Protocol == MediaProtocol.File) { - info.ETag = item.DateModified.Ticks.ToString(CultureInfo.InvariantCulture).GetMD5().ToString("N"); + info.ETag = item.DateModified.Ticks.ToString(CultureInfo.InvariantCulture).GetMD5().ToString("N", CultureInfo.InvariantCulture); } var video = item as Video; @@ -1626,7 +1626,7 @@ namespace MediaBrowser.Controller.Entities public virtual string CreatePresentationUniqueKey() { - return Id.ToString("N"); + return Id.ToString("N", CultureInfo.InvariantCulture); } [IgnoreDataMember] @@ -2736,7 +2736,7 @@ namespace MediaBrowser.Controller.Entities { var list = GetEtagValues(user); - return string.Join("|", list.ToArray()).GetMD5().ToString("N"); + return string.Join("|", list.ToArray()).GetMD5().ToString("N", CultureInfo.InvariantCulture); } protected virtual List GetEtagValues(User user) diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index c056bc0b4e..d841b7ef84 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -177,7 +178,7 @@ namespace MediaBrowser.Controller.Entities { if (user.Policy.BlockedMediaFolders != null) { - if (user.Policy.BlockedMediaFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase) || + if (user.Policy.BlockedMediaFolders.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase) || // Backwards compatibility user.Policy.BlockedMediaFolders.Contains(Name, StringComparer.OrdinalIgnoreCase)) @@ -187,7 +188,7 @@ namespace MediaBrowser.Controller.Entities } else { - if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) + if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) { return false; } diff --git a/MediaBrowser.Controller/Entities/LinkedChild.cs b/MediaBrowser.Controller/Entities/LinkedChild.cs index bb2d03246a..8230604888 100644 --- a/MediaBrowser.Controller/Entities/LinkedChild.cs +++ b/MediaBrowser.Controller/Entities/LinkedChild.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; @@ -29,7 +30,7 @@ namespace MediaBrowser.Controller.Entities if (string.IsNullOrEmpty(child.Path)) { - child.LibraryItemId = item.Id.ToString("N"); + child.LibraryItemId = item.Id.ToString("N", CultureInfo.InvariantCulture); } return child; @@ -37,7 +38,7 @@ namespace MediaBrowser.Controller.Entities public LinkedChild() { - Id = Guid.NewGuid().ToString("N"); + Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); } } diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index eae834f6f0..1aacc13c9a 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -91,7 +92,7 @@ namespace MediaBrowser.Controller.Entities.TV } var folders = LibraryManager.GetCollectionFolders(this) - .Select(i => i.Id.ToString("N")) + .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)) .ToArray(); if (folders.Length == 0) diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs index 9952ba418b..968d725798 100644 --- a/MediaBrowser.Controller/Entities/User.cs +++ b/MediaBrowser.Controller/Entities/User.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -230,7 +231,7 @@ namespace MediaBrowser.Controller.Entities // TODO: Remove idPath and just use usernamePath for future releases var usernamePath = System.IO.Path.Combine(parentPath, username); - var idPath = System.IO.Path.Combine(parentPath, Id.ToString("N")); + var idPath = System.IO.Path.Combine(parentPath, Id.ToString("N", CultureInfo.InvariantCulture)); if (!Directory.Exists(usernamePath) && Directory.Exists(idPath)) { Directory.Move(idPath, usernamePath); diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index e483c8f349..454bdc4ae2 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.Movies; @@ -987,7 +988,7 @@ namespace MediaBrowser.Controller.Entities private UserView GetUserViewWithName(string name, string type, string sortName, BaseItem parent) { - return _userViewManager.GetUserSubView(parent.Id, parent.Id.ToString("N"), type, sortName); + return _userViewManager.GetUserSubView(parent.Id, parent.Id.ToString("N", CultureInfo.InvariantCulture), type, sortName); } private UserView GetUserView(string type, string localizationKey, string sortName, BaseItem parent) diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs index 55f47aae9b..351662b294 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -89,7 +89,7 @@ namespace MediaBrowser.Controller.LiveTv var info = new MediaSourceInfo { - Id = Id.ToString("N"), + Id = Id.ToString("N", CultureInfo.InvariantCulture), Protocol = PathProtocol ?? MediaProtocol.File, MediaStreams = new List(), Name = Name, @@ -111,7 +111,7 @@ namespace MediaBrowser.Controller.LiveTv protected override string GetInternalMetadataPath(string basePath) { - return System.IO.Path.Combine(basePath, "livetv", Id.ToString("N"), "metadata"); + return System.IO.Path.Combine(basePath, "livetv", Id.ToString("N", CultureInfo.InvariantCulture), "metadata"); } public override bool CanDelete() diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs index 8bde6a5da8..bdaf10d005 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; @@ -188,7 +189,7 @@ namespace MediaBrowser.Controller.LiveTv protected override string GetInternalMetadataPath(string basePath) { - return System.IO.Path.Combine(basePath, "livetv", Id.ToString("N")); + return System.IO.Path.Combine(basePath, "livetv", Id.ToString("N", CultureInfo.InvariantCulture)); } public override bool CanDelete() diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index e832607251..aff687f882 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -239,7 +240,7 @@ namespace MediaBrowser.Controller.Playlists return base.IsVisible(user); } - var userId = user.Id.ToString("N"); + var userId = user.Id.ToString("N", CultureInfo.InvariantCulture); foreach (var share in shares) { if (string.Equals(share.UserId, userId, StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.Controller/Providers/MetadataResult.cs b/MediaBrowser.Controller/Providers/MetadataResult.cs index f4b915c06f..ebff81b7f8 100644 --- a/MediaBrowser.Controller/Providers/MetadataResult.cs +++ b/MediaBrowser.Controller/Providers/MetadataResult.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using MediaBrowser.Controller.Entities; namespace MediaBrowser.Controller.Providers @@ -55,7 +56,7 @@ namespace MediaBrowser.Controller.Providers foreach (var i in UserDataList) { - if (string.Equals(userId, i.UserId.ToString("N"), StringComparison.OrdinalIgnoreCase)) + if (string.Equals(userId, i.UserId.ToString("N", CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase)) { userData = i; } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 860ea13cf6..a22eaaa514 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -934,7 +934,7 @@ namespace MediaBrowser.Providers.Manager public void OnRefreshStart(BaseItem item) { - //_logger.LogInformation("OnRefreshStart {0}", item.Id.ToString("N")); + //_logger.LogInformation("OnRefreshStart {0}", item.Id.ToString("N", CultureInfo.InvariantCulture)); var id = item.Id; lock (_activeRefreshes) @@ -947,7 +947,7 @@ namespace MediaBrowser.Providers.Manager public void OnRefreshComplete(BaseItem item) { - //_logger.LogInformation("OnRefreshComplete {0}", item.Id.ToString("N")); + //_logger.LogInformation("OnRefreshComplete {0}", item.Id.ToString("N", CultureInfo.InvariantCulture)); lock (_activeRefreshes) { _activeRefreshes.Remove(item.Id); @@ -971,7 +971,7 @@ namespace MediaBrowser.Providers.Manager public void OnRefreshProgress(BaseItem item, double progress) { - //_logger.LogInformation("OnRefreshProgress {0} {1}", item.Id.ToString("N"), progress); + //_logger.LogInformation("OnRefreshProgress {0} {1}", item.Id.ToString("N", CultureInfo.InvariantCulture), progress); var id = item.Id; lock (_activeRefreshes) @@ -985,7 +985,7 @@ namespace MediaBrowser.Providers.Manager else { // TODO: Need to hunt down the conditions for this happening - //throw new Exception(string.Format("Refresh for item {0} {1} is not in progress", item.GetType().Name, item.Id.ToString("N"))); + //throw new Exception(string.Format("Refresh for item {0} {1} is not in progress", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture))); } } } diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs index 61a8a122b9..7023ef706b 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -99,11 +100,11 @@ namespace MediaBrowser.Providers.MediaInfo if (!string.IsNullOrWhiteSpace(item.Album) && !string.IsNullOrWhiteSpace(albumArtist)) { - filename = (item.Album + "-" + albumArtist).GetMD5().ToString("N"); + filename = (item.Album + "-" + albumArtist).GetMD5().ToString("N", CultureInfo.InvariantCulture); } else { - filename = item.Id.ToString("N"); + filename = item.Id.ToString("N", CultureInfo.InvariantCulture); } filename += ".jpg"; @@ -111,7 +112,7 @@ namespace MediaBrowser.Providers.MediaInfo else { // If it's an audio book or audio podcast, allow unique image per item - filename = item.Id.ToString("N") + ".jpg"; + filename = item.Id.ToString("N", CultureInfo.InvariantCulture) + ".jpg"; } var prefix = filename.Substring(0, 1); diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 7fc6909f55..b4a4c36e54 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -1,12 +1,11 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; @@ -296,7 +295,7 @@ namespace MediaBrowser.Providers.Subtitles private string GetProviderId(string name) { - return name.ToLowerInvariant().GetMD5().ToString("N"); + return name.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture); } private ISubtitleProvider GetProvider(string id) diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 1249a60c05..e7e02a7d5b 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -27,6 +27,10 @@ + + -- cgit v1.2.3 From 52c1b45feb75c55075135005167a308269013400 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 2 Jul 2019 21:47:36 +0200 Subject: Fix build --- .../HttpClientManager/HttpClientManager.cs | 1 + Emby.Server.Implementations/Security/AuthenticationRepository.cs | 4 ++-- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- MediaBrowser.Api/Playback/BaseStreamingService.cs | 8 +------- 4 files changed, 5 insertions(+), 10 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 9ca33d7db7..a933b53f5c 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Globalization; using System.IO; using System.Linq; using System.Net; diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 26a08cbe9a..0b5ee5d039 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -97,7 +97,7 @@ namespace Emby.Server.Implementations.Security statement.TryBind("@AppName", info.AppName); statement.TryBind("@AppVersion", info.AppVersion); statement.TryBind("@DeviceName", info.DeviceName); - statement.TryBind("@UserId", (info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N"))); + statement.TryBind("@UserId", (info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N", CultureInfo.InvariantCulture))); statement.TryBind("@UserName", info.UserName); statement.TryBind("@IsActive", true); statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue()); @@ -131,7 +131,7 @@ namespace Emby.Server.Implementations.Security statement.TryBind("@AppName", info.AppName); statement.TryBind("@AppVersion", info.AppVersion); statement.TryBind("@DeviceName", info.DeviceName); - statement.TryBind("@UserId", (info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N"))); + statement.TryBind("@UserId", (info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N", CultureInfo.InvariantCulture))); statement.TryBind("@UserName", info.UserName); statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue()); statement.TryBind("@DateLastActivity", info.DateLastActivity.ToDateTimeParamValue()); diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 7ee573da5f..0347100a48 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1041,7 +1041,7 @@ namespace Emby.Server.Implementations.Session { IEnumerable GetTasks() { - var messageId = Guid.NewGuid().ToString("N"); + var messageId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); foreach (var session in sessions) { var controllers = session.SessionControllers; diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 114d3f7a24..d9f4a583c9 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -215,12 +215,6 @@ namespace MediaBrowser.Api.Playback var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions(); -<<<<<<< HEAD -======= - var transcodingId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); - var commandLineArgs = GetCommandLineArguments(outputPath, encodingOptions, state, true); - ->>>>>>> Use CultureInvariant string conversion for Guids var process = new Process() { StartInfo = new ProcessStartInfo() @@ -246,7 +240,7 @@ namespace MediaBrowser.Api.Playback var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, state.Request.PlaySessionId, state.MediaSource.LiveStreamId, - Guid.NewGuid().ToString("N"), + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture), TranscodingJobType, process, state.Request.DeviceId, -- cgit v1.2.3 From 8d3b5c851ded74b9f34eb2cd963187761a3f6f61 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 9 Jun 2019 22:08:01 +0200 Subject: Improvements to UserManager --- Emby.Notifications/NotificationManager.cs | 2 +- Emby.Server.Implementations/ApplicationHost.cs | 3 +- .../Data/SqliteUserDataRepository.cs | 4 +- .../EntryPoints/RefreshUsersMetadata.cs | 4 +- .../Library/DefaultPasswordResetProvider.cs | 44 +-- Emby.Server.Implementations/Library/UserManager.cs | 358 ++++++++++----------- .../Session/SessionManager.cs | 8 +- MediaBrowser.Api/UserService.cs | 11 +- .../Authentication/AuthenticationException.cs | 1 + MediaBrowser.Controller/Entities/User.cs | 66 +--- MediaBrowser.Controller/Library/IUserManager.cs | 10 +- 11 files changed, 237 insertions(+), 274 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Notifications/NotificationManager.cs b/Emby.Notifications/NotificationManager.cs index a767e541e1..eecbbea071 100644 --- a/Emby.Notifications/NotificationManager.cs +++ b/Emby.Notifications/NotificationManager.cs @@ -89,7 +89,7 @@ namespace Emby.Notifications return _userManager.Users.Where(i => i.Policy.IsAdministrator) .Select(i => i.Id); case SendToUserType.All: - return _userManager.Users.Select(i => i.Id); + return _userManager.UsersIds; case SendToUserType.Custom: return request.UserIds; default: diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 0b3b81f943..c390ba635d 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -770,7 +770,8 @@ namespace Emby.Server.Implementations _userRepository = GetUserRepository(); - UserManager = new UserManager(LoggerFactory, ServerConfigurationManager, _userRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager); + UserManager = new UserManager(LoggerFactory.CreateLogger(), _userRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager); + serviceCollection.AddSingleton(UserManager); LibraryManager = new LibraryManager(this, LoggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager); diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 4035bb99d1..9d4855bcf3 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.Data var userDatasTableExists = TableExists(connection, "UserDatas"); var userDataTableExists = TableExists(connection, "userdata"); - var users = userDatasTableExists ? null : userManager.Users.ToArray(); + var users = userDatasTableExists ? null : userManager.Users; connection.RunInTransaction(db => { @@ -84,7 +84,7 @@ namespace Emby.Server.Implementations.Data } } - private void ImportUserIds(IDatabaseConnection db, User[] users) + private void ImportUserIds(IDatabaseConnection db, IEnumerable users) { var userIdsWithUserData = GetAllUserIdsWithUserData(db); diff --git a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs index b7565adec4..b2328121e3 100644 --- a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs +++ b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs @@ -50,9 +50,7 @@ namespace Emby.Server.Implementations.EntryPoints public async Task Execute(CancellationToken cancellationToken, IProgress progress) { - var users = _userManager.Users.ToList(); - - foreach (var user in users) + foreach (var user in _userManager.Users) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs index c7044820c7..fa6bbcf91c 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs @@ -1,15 +1,12 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; -using System.Linq; -using System.Text; +using System.Security.Cryptography; using System.Threading.Tasks; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; @@ -17,32 +14,37 @@ namespace Emby.Server.Implementations.Library { public class DefaultPasswordResetProvider : IPasswordResetProvider { - public string Name => "Default Password Reset Provider"; + private const string BaseResetFileName = "passwordreset"; - public bool IsEnabled => true; + private readonly IJsonSerializer _jsonSerializer; + private readonly IUserManager _userManager; private readonly string _passwordResetFileBase; private readonly string _passwordResetFileBaseDir; - private readonly string _passwordResetFileBaseName = "passwordreset"; - private readonly IJsonSerializer _jsonSerializer; - private readonly IUserManager _userManager; - private readonly ICryptoProvider _crypto; - - public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager, ICryptoProvider cryptoProvider) + public DefaultPasswordResetProvider( + IServerConfigurationManager configurationManager, + IJsonSerializer jsonSerializer, + IUserManager userManager) { _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath; - _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, _passwordResetFileBaseName); + _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, BaseResetFileName); _jsonSerializer = jsonSerializer; _userManager = userManager; - _crypto = cryptoProvider; } + /// + public string Name => "Default Password Reset Provider"; + + /// + public bool IsEnabled => true; + + /// public async Task RedeemPasswordResetPin(string pin) { SerializablePasswordReset spr; - HashSet usersreset = new HashSet(); - foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*")) + List usersreset = new List(); + foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*")) { using (var str = File.OpenRead(resetfile)) { @@ -53,12 +55,15 @@ namespace Emby.Server.Implementations.Library { File.Delete(resetfile); } - else if (spr.Pin.Replace("-", "").Equals(pin.Replace("-", ""), StringComparison.InvariantCultureIgnoreCase)) + else if (string.Equals( + spr.Pin.Replace("-", string.Empty), + pin.Replace("-", string.Empty), + StringComparison.InvariantCultureIgnoreCase)) { var resetUser = _userManager.GetUserByName(spr.UserName); if (resetUser == null) { - throw new Exception($"User with a username of {spr.UserName} not found"); + throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found"); } await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); @@ -81,10 +86,11 @@ namespace Emby.Server.Implementations.Library } } + /// public async Task StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork) { string pin = string.Empty; - using (var cryptoRandom = System.Security.Cryptography.RandomNumberGenerator.Create()) + using (var cryptoRandom = RandomNumberGenerator.Create()) { byte[] bytes = new byte[4]; cryptoRandom.GetBytes(bytes); diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index c8c8a108d5..0865278831 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -11,13 +12,11 @@ using MediaBrowser.Common.Events; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Providers; @@ -40,35 +39,20 @@ namespace Emby.Server.Implementations.Library /// public class UserManager : IUserManager { - /// - /// Gets the users. - /// - /// The users. - public IEnumerable Users => _users; - - private User[] _users; - /// /// The _logger /// private readonly ILogger _logger; - /// - /// Gets or sets the configuration manager. - /// - /// The configuration manager. - private IServerConfigurationManager ConfigurationManager { get; set; } + private readonly object _policySyncLock = new object(); /// /// Gets the active user repository /// /// The user repository. - private IUserRepository UserRepository { get; set; } - public event EventHandler> UserPasswordChanged; - + private readonly IUserRepository _userRepository; private readonly IXmlSerializer _xmlSerializer; private readonly IJsonSerializer _jsonSerializer; - private readonly INetworkManager _networkManager; private readonly Func _imageProcessorFactory; @@ -76,6 +60,8 @@ namespace Emby.Server.Implementations.Library private readonly IServerApplicationHost _appHost; private readonly IFileSystem _fileSystem; + private ConcurrentDictionary _users; + private IAuthenticationProvider[] _authenticationProviders; private DefaultAuthenticationProvider _defaultAuthenticationProvider; @@ -85,8 +71,7 @@ namespace Emby.Server.Implementations.Library private DefaultPasswordResetProvider _defaultPasswordResetProvider; public UserManager( - ILoggerFactory loggerFactory, - IServerConfigurationManager configurationManager, + ILogger logger, IUserRepository userRepository, IXmlSerializer xmlSerializer, INetworkManager networkManager, @@ -96,8 +81,8 @@ namespace Emby.Server.Implementations.Library IJsonSerializer jsonSerializer, IFileSystem fileSystem) { - _logger = loggerFactory.CreateLogger(nameof(UserManager)); - UserRepository = userRepository; + _logger = logger; + _userRepository = userRepository; _xmlSerializer = xmlSerializer; _networkManager = networkManager; _imageProcessorFactory = imageProcessorFactory; @@ -105,8 +90,51 @@ namespace Emby.Server.Implementations.Library _appHost = appHost; _jsonSerializer = jsonSerializer; _fileSystem = fileSystem; - ConfigurationManager = configurationManager; - _users = Array.Empty(); + _users = null; + } + + public event EventHandler> UserPasswordChanged; + + /// + /// Occurs when [user updated]. + /// + public event EventHandler> UserUpdated; + + public event EventHandler> UserPolicyUpdated; + + public event EventHandler> UserConfigurationUpdated; + + public event EventHandler> UserLockedOut; + + public event EventHandler> UserCreated; + + /// + /// Occurs when [user deleted]. + /// + public event EventHandler> UserDeleted; + + /// + public IEnumerable Users => _users.Values; + + /// + public IEnumerable UsersIds => _users.Keys; + + /// + /// Called when [user updated]. + /// + /// The user. + private void OnUserUpdated(User user) + { + UserUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); + } + + /// + /// Called when [user deleted]. + /// + /// The user. + private void OnUserDeleted(User user) + { + UserDeleted?.Invoke(this, new GenericEventArgs { Argument = user }); } public NameIdPair[] GetAuthenticationProviders() @@ -137,7 +165,7 @@ namespace Emby.Server.Implementations.Library .ToArray(); } - public void AddParts(IEnumerable authenticationProviders,IEnumerable passwordResetProviders) + public void AddParts(IEnumerable authenticationProviders, IEnumerable passwordResetProviders) { _authenticationProviders = authenticationProviders.ToArray(); @@ -150,54 +178,21 @@ namespace Emby.Server.Implementations.Library _defaultPasswordResetProvider = passwordResetProviders.OfType().First(); } - #region UserUpdated Event /// - /// Occurs when [user updated]. - /// - public event EventHandler> UserUpdated; - public event EventHandler> UserPolicyUpdated; - public event EventHandler> UserConfigurationUpdated; - public event EventHandler> UserLockedOut; - - /// - /// Called when [user updated]. - /// - /// The user. - private void OnUserUpdated(User user) - { - UserUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); - } - #endregion - - #region UserDeleted Event - /// - /// Occurs when [user deleted]. - /// - public event EventHandler> UserDeleted; - /// - /// Called when [user deleted]. - /// - /// The user. - private void OnUserDeleted(User user) - { - UserDeleted?.Invoke(this, new GenericEventArgs { Argument = user }); - } - #endregion - - /// - /// Gets a User by Id + /// Gets a User by Id. /// /// The id. /// User. - /// + /// public User GetUserById(Guid id) { if (id == Guid.Empty) { - throw new ArgumentException(nameof(id), "Guid can't be empty"); + throw new ArgumentException("Guid can't be empty", nameof(id)); } - return Users.FirstOrDefault(u => u.Id == id); + _users.TryGetValue(id, out User user); + return user; } /// @@ -206,15 +201,13 @@ namespace Emby.Server.Implementations.Library /// The identifier. /// User. public User GetUserById(string id) - { - return GetUserById(new Guid(id)); - } + => GetUserById(new Guid(id)); public User GetUserByName(string name) { if (string.IsNullOrWhiteSpace(name)) { - throw new ArgumentNullException(nameof(name)); + throw new ArgumentException("Invalid username", nameof(name)); } return Users.FirstOrDefault(u => string.Equals(u.Name, name, StringComparison.OrdinalIgnoreCase)); @@ -222,8 +215,9 @@ namespace Emby.Server.Implementations.Library public void Initialize() { - var users = LoadUsers(); - _users = users.ToArray(); + LoadUsers(); + + var users = Users; // If there are no local users with admin rights, make them all admins if (!users.Any(i => i.Policy.IsAdministrator)) @@ -240,14 +234,12 @@ namespace Emby.Server.Implementations.Library { // This is some regex that matches only on unicode "word" characters, as well as -, _ and @ // In theory this will cut out most if not all 'control' characters which should help minimize any weirdness - // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), and periods (.) + // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), and periods (.) return Regex.IsMatch(username, @"^[\w\-'._@]*$"); } private static bool IsValidUsernameCharacter(char i) - { - return IsValidUsername(i.ToString()); - } + => IsValidUsername(i.ToString(CultureInfo.InvariantCulture)); public string MakeValidUsername(string username) { @@ -277,8 +269,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(username)); } - var user = Users - .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); + var user = Users.FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); var success = false; string updatedUsername = null; @@ -299,13 +290,12 @@ namespace Emby.Server.Implementations.Library updatedUsername = authResult.username; success = authResult.success; - if (success && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider)) + if (success + && authenticationProvider != null + && !(authenticationProvider is DefaultAuthenticationProvider)) { // We should trust the user that the authprovider says, not what was typed - if (updatedUsername != username) - { - username = updatedUsername; - } + username = updatedUsername; // Search the database for the user again; the authprovider might have created it user = Users @@ -337,10 +327,11 @@ namespace Emby.Server.Implementations.Library if (user.Policy.IsDisabled) { - throw new AuthenticationException(string.Format( - CultureInfo.InvariantCulture, - "The {0} account is currently disabled. Please consult with your administrator.", - user.Name)); + throw new AuthenticationException( + string.Format( + CultureInfo.InvariantCulture, + "The {0} account is currently disabled. Please consult with your administrator.", + user.Name)); } if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint)) @@ -386,7 +377,7 @@ namespace Emby.Server.Implementations.Library private IAuthenticationProvider GetAuthenticationProvider(User user) { - return GetAuthenticationProviders(user).First(); + return GetAuthenticationProviders(user)[0]; } private IPasswordResetProvider GetPasswordResetProvider(User user) @@ -396,7 +387,7 @@ namespace Emby.Server.Implementations.Library private IAuthenticationProvider[] GetAuthenticationProviders(User user) { - var authenticationProviderId = user == null ? null : user.Policy.AuthenticationProviderId; + var authenticationProviderId = user?.Policy.AuthenticationProviderId; var providers = _authenticationProviders.Where(i => i.IsEnabled).ToArray(); @@ -438,16 +429,10 @@ namespace Emby.Server.Implementations.Library { try { - var requiresResolvedUser = provider as IRequiresResolvedUser; - ProviderAuthenticationResult authenticationResult = null; - if (requiresResolvedUser != null) - { - authenticationResult = await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false); - } - else - { - authenticationResult = await provider.Authenticate(username, password).ConfigureAwait(false); - } + + var authenticationResult = provider is IRequiresResolvedUser requiresResolvedUser + ? await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false) + : await provider.Authenticate(username, password).ConfigureAwait(false); if (authenticationResult.Username != username) { @@ -467,7 +452,6 @@ namespace Emby.Server.Implementations.Library private async Task<(IAuthenticationProvider authenticationProvider, string username, bool success)> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint) { - string updatedUsername = null; bool success = false; IAuthenticationProvider authenticationProvider = null; @@ -487,7 +471,7 @@ namespace Emby.Server.Implementations.Library foreach (var provider in GetAuthenticationProviders(user)) { var providerAuthResult = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); - updatedUsername = providerAuthResult.username; + var updatedUsername = providerAuthResult.username; success = providerAuthResult.success; if (success) @@ -499,25 +483,32 @@ namespace Emby.Server.Implementations.Library } } - if (user != null) + if (user != null + && !success + && _networkManager.IsInLocalNetwork(remoteEndPoint) + && user.Configuration.EnableLocalPassword) { - if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword) + if (password == null) { - if (password == null) - { - // legacy - success = string.Equals(GetAuthenticationProvider(user).GetEasyPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); - } - else - { - success = string.Equals(GetAuthenticationProvider(user).GetEasyPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase); - } + // legacy + success = string.Equals(GetLocalPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + } + else + { + success = string.Equals(GetLocalPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase); } } return (authenticationProvider, username, success); } + private string GetLocalPasswordHash(User user) + { + return string.IsNullOrEmpty(user.EasyPassword) + ? null + : PasswordHash.ConvertToByteString(new PasswordHash(user.EasyPassword).Hash); + } + private void UpdateInvalidLoginAttemptCount(User user, int newValue) { if (user.Policy.InvalidLoginAttemptCount == newValue || newValue <= 0) @@ -556,17 +547,17 @@ namespace Emby.Server.Implementations.Library } /// - /// Loads the users from the repository + /// Loads the users from the repository. /// - /// IEnumerable{User}. - private List LoadUsers() + private void LoadUsers() { - var users = UserRepository.RetrieveAllUsers(); + var users = _userRepository.RetrieveAllUsers(); // There always has to be at least one user. if (users.Count != 0) { - return users; + _users = new ConcurrentDictionary( + users.Select(x => new KeyValuePair(x.Id, x))); } var defaultName = Environment.UserName; @@ -581,14 +572,15 @@ namespace Emby.Server.Implementations.Library user.DateLastSaved = DateTime.UtcNow; - UserRepository.CreateUser(user); + _userRepository.CreateUser(user); user.Policy.IsAdministrator = true; user.Policy.EnableContentDeletion = true; user.Policy.EnableRemoteControlOfOtherUsers = true; UpdateUserPolicy(user, user.Policy, false); - return new List { user }; + _users = new ConcurrentDictionary(); + _users[user.Id] = user; } public UserDto GetUserDto(User user, string remoteEndPoint = null) @@ -619,7 +611,7 @@ namespace Emby.Server.Implementations.Library Policy = user.Policy }; - if (!hasPassword && Users.Count() == 1) + if (!hasPassword && _users.Count == 1) { dto.EnableAutoLogin = true; } @@ -694,22 +686,26 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(user)); } - if (string.IsNullOrEmpty(newName)) + if (string.IsNullOrWhiteSpace(newName)) { - throw new ArgumentNullException(nameof(newName)); + throw new ArgumentException("Invalid username", nameof(newName)); } - if (Users.Any(u => u.Id != user.Id && u.Name.Equals(newName, StringComparison.OrdinalIgnoreCase))) + if (user.Name.Equals(newName, StringComparison.OrdinalIgnoreCase)) { - throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", newName)); + throw new ArgumentException("The new and old names must be different."); } - if (user.Name.Equals(newName, StringComparison.Ordinal)) + if (Users.Any( + u => u.Id != user.Id && u.Name.Equals(newName, StringComparison.OrdinalIgnoreCase))) { - throw new ArgumentException("The new and old names must be different."); + throw new ArgumentException(string.Format( + CultureInfo.InvariantCulture, + "A user with the name '{0}' already exists.", + newName)); } - await user.Rename(newName); + await user.Rename(newName).ConfigureAwait(false); OnUserUpdated(user); } @@ -727,23 +723,30 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(user)); } - if (user.Id.Equals(Guid.Empty) || !Users.Any(u => u.Id.Equals(user.Id))) + if (user.Id == Guid.Empty) { - throw new ArgumentException(string.Format("User with name '{0}' and Id {1} does not exist.", user.Name, user.Id)); + throw new ArgumentException("Id can't be empty.", nameof(user)); + } + + if (!_users.ContainsKey(user.Id)) + { + throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + "A user '{0}' with Id {1} does not exist.", + user.Name, + user.Id), + nameof(user)); } user.DateModified = DateTime.UtcNow; user.DateLastSaved = DateTime.UtcNow; - UserRepository.UpdateUser(user); + _userRepository.UpdateUser(user); OnUserUpdated(user); } - public event EventHandler> UserCreated; - - private readonly SemaphoreSlim _userListLock = new SemaphoreSlim(1, 1); - /// /// Creates the user. /// @@ -751,7 +754,7 @@ namespace Emby.Server.Implementations.Library /// User. /// name /// - public async Task CreateUser(string name) + public User CreateUser(string name) { if (string.IsNullOrWhiteSpace(name)) { @@ -768,28 +771,17 @@ namespace Emby.Server.Implementations.Library throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", name)); } - await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); - - try - { - var user = InstantiateNewUser(name); + var user = InstantiateNewUser(name); - var list = Users.ToList(); - list.Add(user); - _users = list.ToArray(); + _users[user.Id] = user; - user.DateLastSaved = DateTime.UtcNow; + user.DateLastSaved = DateTime.UtcNow; - UserRepository.CreateUser(user); + _userRepository.CreateUser(user); - EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs { Argument = user }, _logger); + EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs { Argument = user }, _logger); - return user; - } - finally - { - _userListLock.Release(); - } + return user; } /// @@ -799,57 +791,59 @@ namespace Emby.Server.Implementations.Library /// Task. /// user /// - public async Task DeleteUser(User user) + public void DeleteUser(User user) { if (user == null) { throw new ArgumentNullException(nameof(user)); } - var allUsers = Users.ToList(); - - if (allUsers.FirstOrDefault(u => u.Id == user.Id) == null) + if (!_users.ContainsKey(user.Id)) { - throw new ArgumentException(string.Format("The user cannot be deleted because there is no user with the Name {0} and Id {1}.", user.Name, user.Id)); + throw new ArgumentException(string.Format( + CultureInfo.InvariantCulture, + "The user cannot be deleted because there is no user with the Name {0} and Id {1}.", + user.Name, + user.Id)); } - if (allUsers.Count == 1) + if (_users.Count == 1) { - throw new ArgumentException(string.Format("The user '{0}' cannot be deleted because there must be at least one user in the system.", user.Name)); + throw new ArgumentException(string.Format( + CultureInfo.InvariantCulture, + "The user '{0}' cannot be deleted because there must be at least one user in the system.", + user.Name)); } - if (user.Policy.IsAdministrator && allUsers.Count(i => i.Policy.IsAdministrator) == 1) + if (user.Policy.IsAdministrator + && Users.Count(i => i.Policy.IsAdministrator) == 1) { - throw new ArgumentException(string.Format("The user '{0}' cannot be deleted because there must be at least one admin user in the system.", user.Name)); + throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + "The user '{0}' cannot be deleted because there must be at least one admin user in the system.", + user.Name), + nameof(user)); } - await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); + var configPath = GetConfigurationFilePath(user); + + _userRepository.DeleteUser(user); try { - var configPath = GetConfigurationFilePath(user); - - UserRepository.DeleteUser(user); - - try - { - _fileSystem.DeleteFile(configPath); - } - catch (IOException ex) - { - _logger.LogError(ex, "Error deleting file {path}", configPath); - } - - DeleteUserPolicy(user); - - _users = allUsers.Where(i => i.Id != user.Id).ToArray(); - - OnUserDeleted(user); + _fileSystem.DeleteFile(configPath); } - finally + catch (IOException ex) { - _userListLock.Release(); + _logger.LogError(ex, "Error deleting file {path}", configPath); } + + DeleteUserPolicy(user); + + _users.TryRemove(user.Id, out _); + + OnUserDeleted(user); } /// @@ -906,8 +900,7 @@ namespace Emby.Server.Implementations.Library Name = name, Id = Guid.NewGuid(), DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, - UsesIdForConfigurationPath = true + DateModified = DateTime.UtcNow }; } @@ -989,7 +982,6 @@ namespace Emby.Server.Implementations.Library }; } - private readonly object _policySyncLock = new object(); public void UpdateUserPolicy(Guid userId, UserPolicy userPolicy) { var user = GetUserById(userId); diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 0347100a48..61329160ae 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1375,16 +1375,14 @@ namespace Emby.Server.Implementations.Session CheckDisposed(); User user = null; - if (!request.UserId.Equals(Guid.Empty)) + if (request.UserId != Guid.Empty) { - user = _userManager.Users - .FirstOrDefault(i => i.Id == request.UserId); + user = _userManager.GetUserById(request.UserId); } if (user == null) { - user = _userManager.Users - .FirstOrDefault(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase)); + user = _userManager.GetUserByName(request.Username); } if (user != null) diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index fa70a52aaf..21a94a4e08 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -365,8 +365,8 @@ namespace MediaBrowser.Api } _sessionMananger.RevokeUserTokens(user.Id, null); - - return _userManager.DeleteUser(user); + _userManager.DeleteUser(user); + return Task.CompletedTask; } /// @@ -503,9 +503,14 @@ namespace MediaBrowser.Api } } + /// + /// Posts the specified request. + /// + /// The request. + /// System.Object. public async Task Post(CreateUserByName request) { - var newUser = await _userManager.CreateUser(request.Name).ConfigureAwait(false); + var newUser = _userManager.CreateUser(request.Name); // no need to authenticate password for new user if (request.Password != null) diff --git a/MediaBrowser.Controller/Authentication/AuthenticationException.cs b/MediaBrowser.Controller/Authentication/AuthenticationException.cs index 045cbcdaed..62eca3ea9f 100644 --- a/MediaBrowser.Controller/Authentication/AuthenticationException.cs +++ b/MediaBrowser.Controller/Authentication/AuthenticationException.cs @@ -1,4 +1,5 @@ using System; + namespace MediaBrowser.Controller.Authentication { /// diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs index 968d725798..7d245d4aab 100644 --- a/MediaBrowser.Controller/Entities/User.cs +++ b/MediaBrowser.Controller/Entities/User.cs @@ -17,13 +17,6 @@ namespace MediaBrowser.Controller.Entities public class User : BaseItem { public static IUserManager UserManager { get; set; } - public static IXmlSerializer XmlSerializer { get; set; } - - /// - /// From now on all user paths will be Id-based. - /// This is for backwards compatibility. - /// - public bool UsesIdForConfigurationPath { get; set; } /// /// Gets or sets the password. @@ -31,7 +24,6 @@ namespace MediaBrowser.Controller.Entities /// The password. public string Password { get; set; } public string EasyPassword { get; set; } - public string Salt { get; set; } // Strictly to remove IgnoreDataMember public override ItemImageInfo[] ImageInfos @@ -148,46 +140,23 @@ namespace MediaBrowser.Controller.Entities /// public Task Rename(string newName) { - if (string.IsNullOrEmpty(newName)) - { - throw new ArgumentNullException(nameof(newName)); - } - - // If only the casing is changing, leave the file system alone - if (!UsesIdForConfigurationPath && !string.Equals(newName, Name, StringComparison.OrdinalIgnoreCase)) + if (string.IsNullOrWhiteSpace(newName)) { - UsesIdForConfigurationPath = true; - - // Move configuration - var newConfigDirectory = GetConfigurationDirectoryPath(newName); - var oldConfigurationDirectory = ConfigurationDirectoryPath; - - // Exceptions will be thrown if these paths already exist - if (Directory.Exists(newConfigDirectory)) - { - Directory.Delete(newConfigDirectory, true); - } - - if (Directory.Exists(oldConfigurationDirectory)) - { - Directory.Move(oldConfigurationDirectory, newConfigDirectory); - } - else - { - Directory.CreateDirectory(newConfigDirectory); - } + throw new ArgumentException("Username can't be empty", nameof(newName)); } Name = newName; - return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem)) - { - ReplaceAllMetadata = true, - ImageRefreshMode = MetadataRefreshMode.FullRefresh, - MetadataRefreshMode = MetadataRefreshMode.FullRefresh, - ForceSave = true + return RefreshMetadata( + new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem)) + { + ReplaceAllMetadata = true, + ImageRefreshMode = MetadataRefreshMode.FullRefresh, + MetadataRefreshMode = MetadataRefreshMode.FullRefresh, + ForceSave = true - }, CancellationToken.None); + }, + CancellationToken.None); } public override void UpdateToRepository(ItemUpdateType updateReason, CancellationToken cancellationToken) @@ -216,19 +185,6 @@ namespace MediaBrowser.Controller.Entities { var parentPath = ConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath; - // Legacy - if (!UsesIdForConfigurationPath) - { - if (string.IsNullOrEmpty(username)) - { - throw new ArgumentNullException(nameof(username)); - } - - var safeFolderName = FileSystem.GetValidFilename(username); - - return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath, safeFolderName); - } - // TODO: Remove idPath and just use usernamePath for future releases var usernamePath = System.IO.Path.Combine(parentPath, username); var idPath = System.IO.Path.Combine(parentPath, Id.ToString("N", CultureInfo.InvariantCulture)); diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 7f73708931..bbedc04425 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -22,6 +22,12 @@ namespace MediaBrowser.Controller.Library /// The users. IEnumerable Users { get; } + /// + /// Gets the user ids. + /// + /// The users ids. + IEnumerable UsersIds { get; } + /// /// Occurs when [user updated]. /// @@ -92,7 +98,7 @@ namespace MediaBrowser.Controller.Library /// User. /// name /// - Task CreateUser(string name); + User CreateUser(string name); /// /// Deletes the user. @@ -101,7 +107,7 @@ namespace MediaBrowser.Controller.Library /// Task. /// user /// - Task DeleteUser(User user); + void DeleteUser(User user); /// /// Resets the password. -- cgit v1.2.3 From bbc08753878b7b8a37ed876385d2e6864349d00d Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 20 Oct 2019 16:08:40 +0200 Subject: Don't shuffle some types by default --- Emby.Dlna/ContentDirectory/ControlHandler.cs | 30 ++++++------- .../Channels/ChannelManager.cs | 10 ++--- .../Collections/CollectionImageProvider.cs | 1 - .../Data/SqliteItemRepository.cs | 28 +++++------- .../Library/LibraryManager.cs | 4 +- .../Library/MusicManager.cs | 3 +- .../Library/SearchEngine.cs | 2 +- .../Library/UserViewManager.cs | 2 +- .../LiveTv/EmbyTV/EmbyTV.cs | 8 ++-- .../LiveTv/LiveTvManager.cs | 22 +++++----- .../Playlists/PlaylistImageProvider.cs | 5 +-- .../Session/SessionManager.cs | 50 +++++++++++----------- .../UserViews/DynamicImageProvider.cs | 5 +-- MediaBrowser.Api/Movies/MoviesService.cs | 2 - MediaBrowser.Api/TvShowsService.cs | 2 +- MediaBrowser.Api/UserLibrary/ItemsService.cs | 2 +- .../Extensions/CollectionExtensions.cs | 37 +++++++++------- MediaBrowser.Controller/Entities/Folder.cs | 5 ++- .../Entities/InternalItemsQuery.cs | 2 +- MediaBrowser.Controller/Entities/TV/Series.cs | 4 +- .../Entities/UserViewBuilder.cs | 8 ++-- MediaBrowser.Controller/Playlists/Playlist.cs | 2 +- 22 files changed, 114 insertions(+), 120 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index d22fc21774..438ca39ea7 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -425,10 +425,10 @@ namespace Emby.Dlna.ContentDirectory { var folder = (Folder)item; - var sortOrders = new List(); + var sortOrders = new List<(string, SortOrder)>(); if (!folder.IsPreSorted) { - sortOrders.Add(ItemSortBy.SortName); + sortOrders.Add((ItemSortBy.SortName, sort.SortOrder)); } var mediaTypes = new List(); @@ -464,7 +464,7 @@ namespace Emby.Dlna.ContentDirectory { Limit = limit, StartIndex = startIndex, - OrderBy = sortOrders.Select(i => new ValueTuple(i, sort.SortOrder)).ToArray(), + OrderBy = sortOrders, User = user, Recursive = true, IsMissing = false, @@ -872,10 +872,10 @@ namespace Emby.Dlna.ContentDirectory query.Parent = parent; query.SetUser(user); - query.OrderBy = new ValueTuple[] + query.OrderBy = new[] { - new ValueTuple (ItemSortBy.DatePlayed, SortOrder.Descending), - new ValueTuple (ItemSortBy.SortName, SortOrder.Ascending) + (ItemSortBy.DatePlayed, SortOrder.Descending), + (ItemSortBy.SortName, SortOrder.Ascending) }; query.IsResumable = true; @@ -1121,7 +1121,7 @@ namespace Emby.Dlna.ContentDirectory private QueryResult GetMusicLatest(BaseItem parent, User user, InternalItemsQuery query) { - query.OrderBy = new ValueTuple[] { }; + query.OrderBy = Array.Empty<(string, SortOrder)>(); var items = _userViewManager.GetLatestItems(new LatestItemsQuery { @@ -1138,7 +1138,7 @@ namespace Emby.Dlna.ContentDirectory private QueryResult GetNextUp(BaseItem parent, User user, InternalItemsQuery query) { - query.OrderBy = new ValueTuple[] { }; + query.OrderBy = Array.Empty<(string, SortOrder)>(); var result = _tvSeriesManager.GetNextUp(new NextUpQuery { @@ -1153,7 +1153,7 @@ namespace Emby.Dlna.ContentDirectory private QueryResult GetTvLatest(BaseItem parent, User user, InternalItemsQuery query) { - query.OrderBy = new ValueTuple[] { }; + query.OrderBy = Array.Empty<(string, SortOrder)>(); var items = _userViewManager.GetLatestItems(new LatestItemsQuery { @@ -1170,7 +1170,7 @@ namespace Emby.Dlna.ContentDirectory private QueryResult GetMovieLatest(BaseItem parent, User user, InternalItemsQuery query) { - query.OrderBy = new ValueTuple[] { }; + query.OrderBy = Array.Empty<(string, SortOrder)>(); var items = _userViewManager.GetLatestItems(new LatestItemsQuery { @@ -1274,13 +1274,13 @@ namespace Emby.Dlna.ContentDirectory private void SetSorting(InternalItemsQuery query, SortCriteria sort, bool isPreSorted) { - var sortOrders = new List(); - if (!isPreSorted) + if (isPreSorted) { - sortOrders.Add(ItemSortBy.SortName); + query.OrderBy = Array.Empty<(string, SortOrder)>(); + } + { + query.OrderBy = new[] { (ItemSortBy.SortName, sort.SortOrder) }; } - - query.OrderBy = sortOrders.Select(i => new ValueTuple(i, sort.SortOrder)).ToArray(); } private QueryResult ApplyPaging(QueryResult result, int? startIndex, int? limit) diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 8e5f5b5617..54a027dac6 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -510,7 +510,7 @@ namespace Emby.Server.Implementations.Channels return _libraryManager.GetItemIds(new InternalItemsQuery { IncludeItemTypes = new[] { typeof(Channel).Name }, - OrderBy = new ValueTuple[] { new ValueTuple(ItemSortBy.SortName, SortOrder.Ascending) } + OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) } }).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray(); } @@ -618,16 +618,16 @@ namespace Emby.Server.Implementations.Channels { query.OrderBy = new[] { - new ValueTuple(ItemSortBy.PremiereDate, SortOrder.Descending), - new ValueTuple(ItemSortBy.ProductionYear, SortOrder.Descending), - new ValueTuple(ItemSortBy.DateCreated, SortOrder.Descending) + (ItemSortBy.PremiereDate, SortOrder.Descending), + (ItemSortBy.ProductionYear, SortOrder.Descending), + (ItemSortBy.DateCreated, SortOrder.Descending) }; } else { query.OrderBy = new[] { - new ValueTuple(ItemSortBy.DateCreated, SortOrder.Descending) + (ItemSortBy.DateCreated, SortOrder.Descending) }; } diff --git a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs index 0244c4a684..bbb3114ca7 100644 --- a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs +++ b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs @@ -76,7 +76,6 @@ namespace Emby.Server.Implementations.Collections .Where(i => i != null) .GroupBy(x => x.Id) .Select(x => x.First()) - .OrderBy(i => Guid.NewGuid()) .ToList(); } diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 31a661c5d9..b372ab55c2 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -3,12 +3,11 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Runtime.Serialization; using System.Text; using System.Text.Json; -using System.Text.Json.Serialization; using System.Threading; using Emby.Server.Implementations.Playlists; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Json; using MediaBrowser.Controller; using MediaBrowser.Controller.Channels; @@ -2832,8 +2831,8 @@ namespace Emby.Server.Implementations.Data BindSimilarParams(query, statement); BindSearchParams(query, statement); - // Running this again will bind the params - GetWhereClauses(query, statement); + // Running this again will bind the params + GetWhereClauses(query, statement); var hasEpisodeAttributes = HasEpisodeAttributes(query); var hasServiceName = HasServiceName(query); @@ -2882,14 +2881,14 @@ namespace Emby.Server.Implementations.Data private string GetOrderByText(InternalItemsQuery query) { + var orderBy = query.OrderBy; if (string.IsNullOrEmpty(query.SearchTerm)) { - int oldLen = query.OrderBy.Length; - - if (query.SimilarTo != null && oldLen == 0) + int oldLen = orderBy.Count; + if (oldLen == 0 && query.SimilarTo != null) { var arr = new (string, SortOrder)[oldLen + 2]; - query.OrderBy.CopyTo(arr, 0); + orderBy.CopyTo(arr, 0); arr[oldLen] = ("SimilarityScore", SortOrder.Descending); arr[oldLen + 1] = (ItemSortBy.Random, SortOrder.Ascending); query.OrderBy = arr; @@ -2897,16 +2896,15 @@ namespace Emby.Server.Implementations.Data } else { - query.OrderBy = new [] + query.OrderBy = new[] { ("SearchScore", SortOrder.Descending), (ItemSortBy.SortName, SortOrder.Ascending) }; } - var orderBy = query.OrderBy; - if (orderBy.Length == 0) + if (orderBy.Count == 0) { return string.Empty; } @@ -2914,14 +2912,8 @@ namespace Emby.Server.Implementations.Data return " ORDER BY " + string.Join(",", orderBy.Select(i => { var columnMap = MapOrderByField(i.Item1, query); - var columnAscending = i.Item2 == SortOrder.Ascending; - const bool enableOrderInversion = false; - if (columnMap.Item2 && enableOrderInversion) - { - columnAscending = !columnAscending; - } - var sortOrder = columnAscending ? "ASC" : "DESC"; + var sortOrder = i.Item2 == SortOrder.Ascending ? "ASC" : "DESC"; return columnMap.Item1 + " " + sortOrder; })); diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 87e951f25d..9a2a6dc5f5 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -829,7 +829,7 @@ namespace Emby.Server.Implementations.Library { Path = path, IsFolder = isFolder, - OrderBy = new[] { ItemSortBy.DateCreated }.Select(i => new ValueTuple(i, SortOrder.Descending)).ToArray(), + OrderBy = new[] { (ItemSortBy.DateCreated, SortOrder.Descending) }, Limit = 1, DtoOptions = new DtoOptions(true) }; @@ -1257,7 +1257,7 @@ namespace Emby.Server.Implementations.Library public List GetItemList(InternalItemsQuery query, bool allowExternalContent) { - if (query.Recursive && !query.ParentId.Equals(Guid.Empty)) + if (query.Recursive && query.ParentId != Guid.Empty) { var parent = GetItemById(query.ParentId); if (parent != null) diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index 10602fea76..75cb67fcc1 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -89,10 +89,9 @@ namespace Emby.Server.Implementations.Library Limit = 200, - OrderBy = new[] { new ValueTuple(ItemSortBy.Random, SortOrder.Ascending) }, + OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) }, DtoOptions = dtoOptions - }); } diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index 9c7f7dfcb1..c10b77a246 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -162,7 +162,7 @@ namespace Emby.Server.Implementations.Library Limit = query.Limit, IncludeItemsByName = string.IsNullOrEmpty(query.ParentId), ParentId = string.IsNullOrEmpty(query.ParentId) ? Guid.Empty : new Guid(query.ParentId), - OrderBy = new[] { new ValueTuple(ItemSortBy.SortName, SortOrder.Ascending) }, + OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }, Recursive = true, IsKids = query.IsKids, diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index 88e2a8fa69..1b0813280d 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -340,7 +340,7 @@ namespace Emby.Server.Implementations.Library var query = new InternalItemsQuery(user) { IncludeItemTypes = includeItemTypes, - OrderBy = new[] { new ValueTuple(ItemSortBy.DateCreated, SortOrder.Descending) }, + OrderBy = new[] { (ItemSortBy.DateCreated, SortOrder.Descending) }, IsFolder = includeItemTypes.Length == 0 ? false : (bool?)null, ExcludeItemTypes = excludeItemTypes, IsVirtualItem = false, diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index da0013f128..7b0cd40229 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1580,15 +1580,15 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return; } - var episodesToDelete = (librarySeries.GetItemList(new InternalItemsQuery + var episodesToDelete = librarySeries.GetItemList(new InternalItemsQuery { - OrderBy = new[] { new ValueTuple(ItemSortBy.DateCreated, SortOrder.Descending) }, + OrderBy = new[] { (ItemSortBy.DateCreated, SortOrder.Descending) }, IsVirtualItem = false, IsFolder = false, Recursive = true, DtoOptions = new DtoOptions(true) - })) + }) .Where(i => i.IsFileProtocol && File.Exists(i.Path)) .Skip(seriesTimer.KeepUpTo - 1) .ToList(); @@ -2258,7 +2258,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV }, MinStartDate = startDateUtc.AddMinutes(-3), MaxStartDate = startDateUtc.AddMinutes(3), - OrderBy = new[] { new ValueTuple(ItemSortBy.StartDate, SortOrder.Ascending) } + OrderBy = new[] { (ItemSortBy.StartDate, SortOrder.Ascending) } }; if (!string.IsNullOrWhiteSpace(channelId)) diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 89b92c999e..c2350684b9 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -209,16 +209,16 @@ namespace Emby.Server.Implementations.LiveTv var orderBy = internalQuery.OrderBy.ToList(); - orderBy.AddRange(query.SortBy.Select(i => new ValueTuple(i, query.SortOrder ?? SortOrder.Ascending))); + orderBy.AddRange(query.SortBy.Select(i => (i, query.SortOrder ?? SortOrder.Ascending))); if (query.EnableFavoriteSorting) { - orderBy.Insert(0, new ValueTuple(ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending)); + orderBy.Insert(0, (ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending)); } if (!internalQuery.OrderBy.Any(i => string.Equals(i.Item1, ItemSortBy.SortName, StringComparison.OrdinalIgnoreCase))) { - orderBy.Add(new ValueTuple(ItemSortBy.SortName, SortOrder.Ascending)); + orderBy.Add((ItemSortBy.SortName, SortOrder.Ascending)); } internalQuery.OrderBy = orderBy.ToArray(); @@ -772,22 +772,22 @@ namespace Emby.Server.Implementations.LiveTv var topFolder = GetInternalLiveTvFolder(cancellationToken); - if (query.OrderBy.Length == 0) + if (query.OrderBy.Count == 0) { if (query.IsAiring ?? false) { // Unless something else was specified, order by start date to take advantage of a specialized index - query.OrderBy = new ValueTuple[] + query.OrderBy = new[] { - new ValueTuple(ItemSortBy.StartDate, SortOrder.Ascending) + (ItemSortBy.StartDate, SortOrder.Ascending) }; } else { // Unless something else was specified, order by start date to take advantage of a specialized index - query.OrderBy = new ValueTuple[] + query.OrderBy = new[] { - new ValueTuple(ItemSortBy.StartDate, SortOrder.Ascending) + (ItemSortBy.StartDate, SortOrder.Ascending) }; } } @@ -871,7 +871,7 @@ namespace Emby.Server.Implementations.LiveTv IsSports = query.IsSports, IsKids = query.IsKids, EnableTotalRecordCount = query.EnableTotalRecordCount, - OrderBy = new[] { new ValueTuple(ItemSortBy.StartDate, SortOrder.Ascending) }, + OrderBy = new[] { (ItemSortBy.StartDate, SortOrder.Ascending) }, TopParentIds = new[] { topFolder.Id }, DtoOptions = options, GenreIds = query.GenreIds @@ -1393,7 +1393,7 @@ namespace Emby.Server.Implementations.LiveTv IsVirtualItem = false, Limit = limit, StartIndex = query.StartIndex, - OrderBy = new[] { new ValueTuple(ItemSortBy.DateCreated, SortOrder.Descending) }, + OrderBy = new[] { (ItemSortBy.DateCreated, SortOrder.Descending) }, EnableTotalRecordCount = query.EnableTotalRecordCount, IncludeItemTypes = includeItemTypes.ToArray(), ExcludeItemTypes = excludeItemTypes.ToArray(), @@ -1891,7 +1891,7 @@ namespace Emby.Server.Implementations.LiveTv MaxStartDate = now, MinEndDate = now, Limit = channelIds.Length, - OrderBy = new[] { new ValueTuple(ItemSortBy.StartDate, SortOrder.Ascending) }, + OrderBy = new[] { (ItemSortBy.StartDate, SortOrder.Ascending) }, TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Id }, DtoOptions = options diff --git a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs index cad66a80fd..2dfe59088d 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs @@ -62,7 +62,6 @@ namespace Emby.Server.Implementations.Playlists return null; }) .Where(i => i != null) - .OrderBy(i => Guid.NewGuid()) .GroupBy(x => x.Id) .Select(x => x.First()) .ToList(); @@ -84,7 +83,7 @@ namespace Emby.Server.Implementations.Playlists { Genres = new[] { item.Name }, IncludeItemTypes = new[] { typeof(MusicAlbum).Name, typeof(MusicVideo).Name, typeof(Audio).Name }, - OrderBy = new[] { new ValueTuple(ItemSortBy.Random, SortOrder.Ascending) }, + OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) }, Limit = 4, Recursive = true, ImageTypes = new[] { ImageType.Primary }, @@ -108,7 +107,7 @@ namespace Emby.Server.Implementations.Playlists { Genres = new[] { item.Name }, IncludeItemTypes = new[] { typeof(Series).Name, typeof(Movie).Name }, - OrderBy = new[] { new ValueTuple(ItemSortBy.Random, SortOrder.Ascending) }, + OrderBy = new[] { (ItemSortBy.Random, SortOrder.Ascending) }, Limit = 4, Recursive = true, ImageTypes = new[] { ImageType.Primary }, diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 61329160ae..68cd3c0bae 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1061,7 +1061,7 @@ namespace Emby.Server.Implementations.Session var session = GetSessionToRemoteControl(sessionId); - var user = !session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(session.UserId) : null; + var user = session.UserId == Guid.Empty ? null : _userManager.GetUserById(session.UserId); List items; @@ -1086,7 +1086,7 @@ namespace Emby.Server.Implementations.Session if (command.PlayCommand == PlayCommand.PlayShuffle) { - items = items.OrderBy(i => Guid.NewGuid()).ToList(); + items.Shuffle(); command.PlayCommand = PlayCommand.PlayNow; } @@ -1100,28 +1100,27 @@ namespace Emby.Server.Implementations.Session } } - if (user != null && command.ItemIds.Length == 1 && user.Configuration.EnableNextEpisodeAutoPlay) + if (user != null + && command.ItemIds.Length == 1 + && user.Configuration.EnableNextEpisodeAutoPlay + && _libraryManager.GetItemById(command.ItemIds[0]) is Episode episode) { - var episode = _libraryManager.GetItemById(command.ItemIds[0]) as Episode; - if (episode != null) + var series = episode.Series; + if (series != null) { - var series = episode.Series; - if (series != null) + var episodes = series.GetEpisodes( + user, + new DtoOptions(false) + { + EnableImages = false + }) + .Where(i => !i.IsVirtualItem) + .SkipWhile(i => i.Id != episode.Id) + .ToList(); + + if (episodes.Count > 0) { - var episodes = series.GetEpisodes( - user, - new DtoOptions(false) - { - EnableImages = false - }) - .Where(i => !i.IsVirtualItem) - .SkipWhile(i => i.Id != episode.Id) - .ToList(); - - if (episodes.Count > 0) - { - command.ItemIds = episodes.Select(i => i.Id).ToArray(); - } + command.ItemIds = episodes.Select(i => i.Id).ToArray(); } } } @@ -1146,7 +1145,7 @@ namespace Emby.Server.Implementations.Session if (item == null) { _logger.LogError("A non-existant item Id {0} was passed into TranslateItemForPlayback", id); - return new List(); + return Array.Empty(); } if (item is IItemByName byName) @@ -1164,7 +1163,7 @@ namespace Emby.Server.Implementations.Session } }, IsVirtualItem = false, - OrderBy = new ValueTuple[] { new ValueTuple(ItemSortBy.SortName, SortOrder.Ascending) } + OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) } }); } @@ -1185,12 +1184,11 @@ namespace Emby.Server.Implementations.Session } }, IsVirtualItem = false, - OrderBy = new ValueTuple[] { new ValueTuple(ItemSortBy.SortName, SortOrder.Ascending) } - + OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) } }); } - return new List { item }; + return new[] { item }; } private IEnumerable TranslateItemForInstantMix(Guid id, User user) diff --git a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs index f485204436..78ac95f85e 100644 --- a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs @@ -86,20 +86,17 @@ namespace Emby.Server.Implementations.UserViews { return items .Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)) - .OrderBy(i => Guid.NewGuid()) .ToList(); } return items .Where(i => i.HasImage(ImageType.Primary)) - .OrderBy(i => Guid.NewGuid()) .ToList(); } protected override bool Supports(BaseItem item) { - var view = item as UserView; - if (view != null) + if (item is UserView view) { return IsUsingCollectionStrip(view); } diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index c1c6ffc2e2..8a3fdcbcbe 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -191,12 +191,10 @@ namespace MediaBrowser.Api.Movies var mostRecentMovies = recentlyPlayedMovies.Take(6).ToList(); // Get recently played directors var recentDirectors = GetDirectors(mostRecentMovies) - .OrderBy(i => Guid.NewGuid()) .ToList(); // Get recently played actors var recentActors = GetActors(mostRecentMovies) - .OrderBy(i => Guid.NewGuid()) .ToList(); var similarToRecentlyPlayed = GetSimilarTo(user, recentlyPlayedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToRecentlyPlayed).GetEnumerator(); diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index 1340bd8ef7..caff20084d 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -482,7 +482,7 @@ namespace MediaBrowser.Api if (string.Equals(request.SortBy, ItemSortBy.Random, StringComparison.OrdinalIgnoreCase)) { - episodes = episodes.OrderBy(i => Guid.NewGuid()).ToList(); + episodes.Shuffle(); } var returnItems = episodes; diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index b4a3026480..6e7351240b 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -476,7 +476,7 @@ namespace MediaBrowser.Api.UserLibrary } // Apply default sorting if none requested - if (query.OrderBy.Length == 0) + if (query.OrderBy.Count == 0) { // Albums by artist if (query.ArtistIds.Length > 0 && query.IncludeItemTypes.Length == 1 && string.Equals(query.IncludeItemTypes[0], "MusicAlbum", StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.Common/Extensions/CollectionExtensions.cs b/MediaBrowser.Common/Extensions/CollectionExtensions.cs index 2152243985..a12b2833f1 100644 --- a/MediaBrowser.Common/Extensions/CollectionExtensions.cs +++ b/MediaBrowser.Common/Extensions/CollectionExtensions.cs @@ -1,5 +1,6 @@ #pragma warning disable CS1591 +using System; using System.Collections.Generic; namespace MediaBrowser.Common.Extensions @@ -7,6 +8,26 @@ namespace MediaBrowser.Common.Extensions // The MS CollectionExtensions are only available in netcoreapp public static class CollectionExtensions { + private static readonly Random _rng = new Random(); + + /// + /// Shuffles the items in a list. + /// + /// The list that should get shuffled. + /// The type. + public static void Shuffle(this IList list) + { + int n = list.Count; + while (n > 1) + { + n--; + int k = _rng.Next(n + 1); + T value = list[k]; + list[k] = list[n]; + list[n] = value; + } + } + public static TValue GetValueOrDefault(this IReadOnlyDictionary dictionary, TKey key) { dictionary.TryGetValue(key, out var ret); @@ -28,21 +49,5 @@ namespace MediaBrowser.Common.Extensions destination[index + i] = source[i]; } } - - /// - /// Copies all the elements of the current collection to the specified list - /// starting at the specified destination array index. The index is specified as a 32-bit integer. - /// - /// The current collection that is the source of the elements. - /// The list that is the destination of the elements copied from the current collection. - /// A 32-bit integer that represents the index in destination at which copying begins. - /// - public static void CopyTo(this IReadOnlyCollection source, IList destination, int index = 0) - { - foreach (T item in source) - { - destination[index++] = item; - } - } } } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index d61a07066c..8e9d110122 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -1163,7 +1163,10 @@ namespace MediaBrowser.Controller.Entities } //the true root should return our users root folder children - if (IsPhysicalRoot) return LibraryManager.GetUserRootFolder().GetChildren(user, includeLinkedChildren); + if (IsPhysicalRoot) + { + return LibraryManager.GetUserRootFolder().GetChildren(user, includeLinkedChildren); + } var result = new Dictionary(); diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index 78f8590696..bd96059e32 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -155,7 +155,7 @@ namespace MediaBrowser.Controller.Entities public bool EnableGroupByMetadataKey { get; set; } public bool? HasChapterImages { get; set; } - public ValueTuple[] OrderBy { get; set; } + public IReadOnlyList<(string, SortOrder)> OrderBy { get; set; } public DateTime? MinDateCreated { get; set; } public DateTime? MinDateLastSaved { get; set; } diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index a50da9b0a0..d435551023 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -226,14 +226,16 @@ namespace MediaBrowser.Controller.Entities.TV query.AncestorWithPresentationUniqueKey = null; query.SeriesPresentationUniqueKey = seriesKey; - if (query.OrderBy.Length == 0) + if (query.OrderBy.Count == 0) { query.OrderBy = new[] { ItemSortBy.SortName }.Select(i => new ValueTuple(i, SortOrder.Ascending)).ToArray(); } + if (query.IncludeItemTypes.Length == 0) { query.IncludeItemTypes = new[] { typeof(Episode).Name, typeof(Season).Name }; } + query.IsVirtualItem = false; return LibraryManager.GetItemsResult(query); } diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 454bdc4ae2..435a1e8dae 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -450,14 +450,16 @@ namespace MediaBrowser.Controller.Entities return SortAndPage(items, totalRecordLimit, query, libraryManager, true); } - public static QueryResult SortAndPage(IEnumerable items, + public static QueryResult SortAndPage( + IEnumerable items, int? totalRecordLimit, InternalItemsQuery query, - ILibraryManager libraryManager, bool enableSorting) + ILibraryManager libraryManager, + bool enableSorting) { if (enableSorting) { - if (query.OrderBy.Length > 0) + if (query.OrderBy.Count > 0) { items = libraryManager.Sort(items, query.User, query.OrderBy); } diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index aff687f882..bef7eff064 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -181,7 +181,7 @@ namespace MediaBrowser.Controller.Playlists { Recursive = true, IsFolder = false, - OrderBy = new[] { ItemSortBy.SortName }.Select(i => new ValueTuple(i, SortOrder.Ascending)).ToArray(), + OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }, MediaTypes = new[] { mediaType }, EnableTotalRecordCount = false, DtoOptions = options -- cgit v1.2.3 From fef35d0505c6485954b1eb233a5ea67e33fe1138 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 20 Oct 2019 21:12:03 +0200 Subject: Add clearer exceptions, warnings and docs --- .../Cryptography/CryptographyProvider.cs | 10 +++----- .../Library/DefaultAuthenticationProvider.cs | 5 +++- Emby.Server.Implementations/Library/UserManager.cs | 20 +++++----------- .../Library/UserViewManager.cs | 20 ++++++++-------- .../Session/SessionManager.cs | 27 +++++++++++----------- MediaBrowser.Api/UserLibrary/UserViewsService.cs | 8 ++++++- MediaBrowser.Common/Cryptography/PasswordHash.cs | 6 ++--- MediaBrowser.Controller/Entities/Folder.cs | 10 +++++++- MediaBrowser.Controller/Library/IUserManager.cs | 10 +++++--- MediaBrowser.Model/Cryptography/ICryptoProvider.cs | 4 ---- 10 files changed, 63 insertions(+), 57 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 23b77e2687..fec7d161e5 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -30,6 +30,9 @@ namespace Emby.Server.Implementations.Cryptography private bool _disposed = false; + /// + /// Initializes a new instance of the class. + /// public CryptographyProvider() { // FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto @@ -59,12 +62,6 @@ namespace Emby.Server.Implementations.Cryptography throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}"); } - public byte[] ComputeHash(string hashMethod, byte[] bytes) - => ComputeHash(hashMethod, bytes, Array.Empty()); - - public byte[] ComputeHashWithDefaultMethod(byte[] bytes) - => ComputeHash(DefaultHashMethod, bytes); - public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt) { if (hashMethod == DefaultHashMethod) @@ -90,7 +87,6 @@ namespace Emby.Server.Implementations.Cryptography } throw new CryptographicException($"Requested hash method is not supported: {hashMethod}"); - } public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt) diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index c95b00ede2..a1143471da 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -59,7 +59,10 @@ namespace Emby.Server.Implementations.Library if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id) || _cryptographyProvider.DefaultHashMethod == readyHash.Id) { - byte[] calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.Salt); + byte[] calculatedHash = _cryptographyProvider.ComputeHash( + readyHash.Id, + passwordbytes, + readyHash.Salt); if (calculatedHash.SequenceEqual(readyHash.Hash)) { diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 52b2f56ffc..3d1030d4bb 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -179,12 +179,7 @@ namespace Emby.Server.Implementations.Library _defaultPasswordResetProvider = passwordResetProviders.OfType().First(); } - /// - /// Gets a User by Id. - /// - /// The id. - /// User. - /// + /// public User GetUserById(Guid id) { if (id == Guid.Empty) @@ -196,11 +191,7 @@ namespace Emby.Server.Implementations.Library return user; } - /// - /// Gets the user by identifier. - /// - /// The identifier. - /// User. + /// public User GetUserById(string id) => GetUserById(new Guid(id)); @@ -428,7 +419,6 @@ namespace Emby.Server.Implementations.Library { try { - var authenticationResult = provider is IRequiresResolvedUser requiresResolvedUser ? await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false) : await provider.Authenticate(username, password).ConfigureAwait(false); @@ -538,6 +528,8 @@ namespace Emby.Server.Implementations.Library defaultName = "MyJellyfinUser"; } + _logger.LogWarning("No users, creating one with username {UserName}", defaultName); + var name = MakeValidUsername(defaultName); var user = InstantiateNewUser(name); @@ -601,7 +593,7 @@ namespace Emby.Server.Implementations.Library catch (Exception ex) { // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions - _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {user}", user.Name); + _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {User}", user.Name); } } @@ -625,7 +617,7 @@ namespace Emby.Server.Implementations.Library } catch (Exception ex) { - _logger.LogError(ex, "Error getting {imageType} image info for {imagePath}", image.Type, image.Path); + _logger.LogError(ex, "Error getting {ImageType} image info for {ImagePath}", image.Type, image.Path); return null; } } diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index 88e2a8fa69..474884e428 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -42,6 +42,11 @@ namespace Emby.Server.Implementations.Library { var user = _userManager.GetUserById(query.UserId); + if (user == null) + { + throw new ArgumentException("User Id does not exists.", nameof(query)); + } + var folders = _libraryManager.GetUserRootFolder() .GetChildren(user, true) .OfType() @@ -54,7 +59,7 @@ namespace Emby.Server.Implementations.Library foreach (var folder in folders) { var collectionFolder = folder as ICollectionFolder; - var folderViewType = collectionFolder == null ? null : collectionFolder.CollectionType; + var folderViewType = collectionFolder?.CollectionType; if (UserView.IsUserSpecific(folder)) { @@ -130,16 +135,11 @@ namespace Emby.Server.Implementations.Library { var index = orders.IndexOf(i.Id.ToString("N", CultureInfo.InvariantCulture)); - if (index == -1) + if (index == -1 + && i is UserView view + && view.DisplayParentId != Guid.Empty) { - var view = i as UserView; - if (view != null) - { - if (!view.DisplayParentId.Equals(Guid.Empty)) - { - index = orders.IndexOf(view.DisplayParentId.ToString("N", CultureInfo.InvariantCulture)); - } - } + index = orders.IndexOf(view.DisplayParentId.ToString("N", CultureInfo.InvariantCulture)); } return index == -1 ? int.MaxValue : index; diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 61329160ae..d1392e1623 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1388,27 +1388,28 @@ namespace Emby.Server.Implementations.Session if (user != null) { // TODO: Move this to userManager? - if (!string.IsNullOrEmpty(request.DeviceId)) + if (!string.IsNullOrEmpty(request.DeviceId) + && !_deviceManager.CanAccessDevice(user, request.DeviceId)) { - if (!_deviceManager.CanAccessDevice(user, request.DeviceId)) - { - throw new SecurityException("User is not allowed access from this device."); - } + throw new SecurityException("User is not allowed access from this device."); } } if (enforcePassword) { - var result = await _userManager.AuthenticateUser(request.Username, request.Password, request.PasswordSha1, request.RemoteEndPoint, true).ConfigureAwait(false); - - if (result == null) - { - AuthenticationFailed?.Invoke(this, new GenericEventArgs(request)); + user = await _userManager.AuthenticateUser( + request.Username, + request.Password, + request.PasswordSha1, + request.RemoteEndPoint, + true).ConfigureAwait(false); + } - throw new SecurityException("Invalid user or password entered."); - } + if (user == null) + { + AuthenticationFailed?.Invoke(this, new GenericEventArgs(request)); - user = result; + throw new SecurityException("Invalid user or password entered."); } var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName); diff --git a/MediaBrowser.Api/UserLibrary/UserViewsService.cs b/MediaBrowser.Api/UserLibrary/UserViewsService.cs index 2fa5d8933c..d62049ce9e 100644 --- a/MediaBrowser.Api/UserLibrary/UserViewsService.cs +++ b/MediaBrowser.Api/UserLibrary/UserViewsService.cs @@ -10,6 +10,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; +using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.UserLibrary { @@ -49,7 +50,12 @@ namespace MediaBrowser.Api.UserLibrary private readonly IAuthorizationContext _authContext; private readonly ILibraryManager _libraryManager; - public UserViewsService(IUserManager userManager, IUserViewManager userViewManager, IDtoService dtoService, IAuthorizationContext authContext, ILibraryManager libraryManager) + public UserViewsService( + IUserManager userManager, + IUserViewManager userViewManager, + IDtoService dtoService, + IAuthorizationContext authContext, + ILibraryManager libraryManager) { _userManager = userManager; _userViewManager = userViewManager; diff --git a/MediaBrowser.Common/Cryptography/PasswordHash.cs b/MediaBrowser.Common/Cryptography/PasswordHash.cs index 7741571db9..1cb70675c6 100644 --- a/MediaBrowser.Common/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Common/Cryptography/PasswordHash.cs @@ -61,13 +61,13 @@ namespace MediaBrowser.Common.Cryptography /// Return the hashed password. public byte[] Hash { get; } - public static PasswordHash Parse(string storageString) + public static PasswordHash Parse(string hashString) { - string[] splitted = storageString.Split('$'); + string[] splitted = hashString.Split('$'); // The string should at least contain the hash function and the hash itself if (splitted.Length < 3) { - throw new ArgumentException("String doesn't contain enough segments", nameof(storageString)); + throw new ArgumentException("String doesn't contain enough segments", nameof(hashString)); } // Start at 1, the first index shouldn't contain any data diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index d61a07066c..b697f6576f 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -1152,6 +1152,11 @@ namespace MediaBrowser.Controller.Entities public List GetChildren(User user, bool includeLinkedChildren) { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + return GetChildren(user, includeLinkedChildren, null); } @@ -1163,7 +1168,10 @@ namespace MediaBrowser.Controller.Entities } //the true root should return our users root folder children - if (IsPhysicalRoot) return LibraryManager.GetUserRootFolder().GetChildren(user, includeLinkedChildren); + if (IsPhysicalRoot) + { + return LibraryManager.GetUserRootFolder().GetChildren(user, includeLinkedChildren); + } var result = new Dictionary(); diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index bbedc04425..6163c0ce63 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -39,17 +39,21 @@ namespace MediaBrowser.Controller.Library event EventHandler> UserDeleted; event EventHandler> UserCreated; + event EventHandler> UserPolicyUpdated; + event EventHandler> UserConfigurationUpdated; + event EventHandler> UserPasswordChanged; + event EventHandler> UserLockedOut; /// - /// Gets a User by Id + /// Gets a User by Id. /// /// The id. - /// User. - /// + /// The user with the specified Id, or null id the user doesn't exist. + /// id is an empty Guid. User GetUserById(Guid id); /// diff --git a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs index ce6493232f..2d75c9b3ec 100644 --- a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs +++ b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs @@ -8,10 +8,6 @@ namespace MediaBrowser.Model.Cryptography IEnumerable GetSupportedHashMethods(); - byte[] ComputeHash(string HashMethod, byte[] bytes); - - byte[] ComputeHashWithDefaultMethod(byte[] bytes); - byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt); byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt); -- cgit v1.2.3 From b477b3874ef8d79a1e27f8bb298d38443c3ec425 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 25 Oct 2019 12:47:20 +0200 Subject: Fix some warnings --- Emby.Naming/AudioBook/AudioBookFileInfo.cs | 2 +- Emby.Naming/AudioBook/AudioBookInfo.cs | 2 +- Emby.Naming/TV/SeasonPathParser.cs | 4 +-- Emby.Naming/Video/CleanDateTimeParser.cs | 2 +- Emby.Naming/Video/VideoFileInfo.cs | 2 +- Emby.Naming/Video/VideoInfo.cs | 2 +- Emby.Naming/Video/VideoResolver.cs | 2 +- .../Activity/ActivityLogEntryPoint.cs | 4 +-- .../AppBase/BaseConfigurationManager.cs | 8 +++--- .../AppBase/ConfigurationHelper.cs | 4 +-- Emby.Server.Implementations/ApplicationHost.cs | 6 ++-- .../Channels/ChannelManager.cs | 4 +-- .../Collections/CollectionManager.cs | 2 +- .../Data/SqliteItemRepository.cs | 2 +- .../Devices/DeviceManager.cs | 1 - .../EntryPoints/ExternalPortForwarding.cs | 1 - .../EntryPoints/LibraryChangedNotifier.cs | 2 +- .../EntryPoints/ServerEventNotifier.cs | 2 +- .../HttpClientManager/HttpClientManager.cs | 2 +- .../HttpServer/HttpResultFactory.cs | 2 +- .../HttpServer/RangeRequestWriter.cs | 4 ++- .../Library/SearchEngine.cs | 2 +- .../Library/Validators/ArtistsValidator.cs | 3 +- .../Library/Validators/GenresPostScanTask.cs | 5 ++-- .../Library/Validators/MusicGenresPostScanTask.cs | 3 +- .../Library/Validators/StudiosPostScanTask.cs | 4 ++- .../LiveTv/EmbyTV/EmbyTV.cs | 2 +- .../LiveTv/Listings/SchedulesDirect.cs | 10 +++---- .../LiveTv/RefreshChannelsScheduledTask.cs | 4 +-- .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 6 ++-- .../Localization/LocalizationManager.cs | 2 -- .../Middleware/WebSocketMiddleware.cs | 4 +-- .../Networking/NetworkManager.cs | 18 ++++++++---- .../Playlists/ManualPlaylistsFolder.cs | 1 - .../ScheduledTasks/ScheduledTaskWorker.cs | 9 +++--- .../ScheduledTasks/TaskManager.cs | 32 +++++++++++----------- .../ScheduledTasks/Triggers/DailyTrigger.cs | 5 ++-- .../Services/ServicePath.cs | 16 +++++++---- .../Services/SwaggerService.cs | 4 +-- .../Session/HttpSessionController.cs | 2 +- .../SocketSharp/SharpWebSocket.cs | 2 +- .../SocketSharp/WebSocketSharpListener.cs | 6 ++-- .../SocketSharp/WebSocketSharpRequest.cs | 3 +- .../Sorting/NameComparer.cs | 4 +++ .../Sorting/OfficialRatingComparer.cs | 4 +++ .../Sorting/StudioComparer.cs | 5 ++++ .../WebSockets/WebSocketManager.cs | 10 ++++--- MediaBrowser.Api/Playback/BaseStreamingService.cs | 2 +- 48 files changed, 128 insertions(+), 100 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Naming/AudioBook/AudioBookFileInfo.cs b/Emby.Naming/AudioBook/AudioBookFileInfo.cs index 326ea05ef3..769e3d7fac 100644 --- a/Emby.Naming/AudioBook/AudioBookFileInfo.cs +++ b/Emby.Naming/AudioBook/AudioBookFileInfo.cs @@ -3,7 +3,7 @@ using System; namespace Emby.Naming.AudioBook { /// - /// Represents a single video file + /// Represents a single video file. /// public class AudioBookFileInfo : IComparable { diff --git a/Emby.Naming/AudioBook/AudioBookInfo.cs b/Emby.Naming/AudioBook/AudioBookInfo.cs index 600d3f05da..d53f53c528 100644 --- a/Emby.Naming/AudioBook/AudioBookInfo.cs +++ b/Emby.Naming/AudioBook/AudioBookInfo.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace Emby.Naming.AudioBook { /// - /// Represents a complete video, including all parts and subtitles + /// Represents a complete video, including all parts and subtitles. /// public class AudioBookInfo { diff --git a/Emby.Naming/TV/SeasonPathParser.cs b/Emby.Naming/TV/SeasonPathParser.cs index 9096ccaf5d..f34faf8e83 100644 --- a/Emby.Naming/TV/SeasonPathParser.cs +++ b/Emby.Naming/TV/SeasonPathParser.cs @@ -25,7 +25,7 @@ namespace Emby.Naming.TV } /// - /// A season folder must contain one of these somewhere in the name + /// A season folder must contain one of these somewhere in the name. /// private static readonly string[] _seasonFolderNames = { @@ -124,7 +124,7 @@ namespace Emby.Naming.TV } /// - /// Extracts the season number from the second half of the Season folder name (everything after "Season", or "Staffel") + /// Extracts the season number from the second half of the Season folder name (everything after "Season", or "Staffel"). /// /// The path. /// System.Nullable{System.Int32}. diff --git a/Emby.Naming/Video/CleanDateTimeParser.cs b/Emby.Naming/Video/CleanDateTimeParser.cs index 25fa09c488..c6b6039d4d 100644 --- a/Emby.Naming/Video/CleanDateTimeParser.cs +++ b/Emby.Naming/Video/CleanDateTimeParser.cs @@ -8,7 +8,7 @@ using Emby.Naming.Common; namespace Emby.Naming.Video { /// - /// http://kodi.wiki/view/Advancedsettings.xml#video + /// . /// public class CleanDateTimeParser { diff --git a/Emby.Naming/Video/VideoFileInfo.cs b/Emby.Naming/Video/VideoFileInfo.cs index 78f688ca8a..2f42f77845 100644 --- a/Emby.Naming/Video/VideoFileInfo.cs +++ b/Emby.Naming/Video/VideoFileInfo.cs @@ -1,7 +1,7 @@ namespace Emby.Naming.Video { /// - /// Represents a single video file + /// Represents a single video file. /// public class VideoFileInfo { diff --git a/Emby.Naming/Video/VideoInfo.cs b/Emby.Naming/Video/VideoInfo.cs index 2e456bda2b..f576b6ca28 100644 --- a/Emby.Naming/Video/VideoInfo.cs +++ b/Emby.Naming/Video/VideoInfo.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace Emby.Naming.Video { /// - /// Represents a complete video, including all parts and subtitles + /// Represents a complete video, including all parts and subtitles. /// public class VideoInfo { diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index 02a25c4b5b..91f443500f 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -41,7 +41,7 @@ namespace Emby.Naming.Video /// if set to true [is folder]. /// Whether or not the name should be parsed for info /// VideoFileInfo. - /// path + /// path is null. public VideoFileInfo Resolve(string path, bool isDirectory, bool parseName = true) { if (string.IsNullOrEmpty(path)) diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 1514402d68..efaaa116c6 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -616,8 +616,8 @@ namespace Emby.Server.Implementations.Activity /// /// Constructs a string description of a time-span value. /// - /// The value of this item - /// The name of this item (singular form) + /// The value of this item. + /// The name of this item (singular form). private static string CreateValueString(int value, string description) { return string.Format( diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs index 7ec5252d07..0b7c35f3e9 100644 --- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs +++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs @@ -16,7 +16,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.AppBase { /// - /// Class BaseConfigurationManager + /// Class BaseConfigurationManager. /// public abstract class BaseConfigurationManager : IConfigurationManager { @@ -48,7 +48,7 @@ namespace Emby.Server.Implementations.AppBase /// The application paths. /// The logger factory. /// The XML serializer. - /// The file system + /// The file system. protected BaseConfigurationManager(IApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IXmlSerializer xmlSerializer, IFileSystem fileSystem) { CommonApplicationPaths = applicationPaths; @@ -92,7 +92,7 @@ namespace Emby.Server.Implementations.AppBase protected IXmlSerializer XmlSerializer { get; private set; } /// - /// Gets or sets the application paths. + /// Gets the application paths. /// /// The application paths. public IApplicationPaths CommonApplicationPaths { get; private set; } @@ -173,7 +173,7 @@ namespace Emby.Server.Implementations.AppBase /// Replaces the configuration. /// /// The new configuration. - /// newConfiguration + /// newConfiguration is null. public virtual void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration) { if (newConfiguration == null) diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs index 90b97061f3..854d7b4cbf 100644 --- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs +++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs @@ -6,13 +6,13 @@ using MediaBrowser.Model.Serialization; namespace Emby.Server.Implementations.AppBase { /// - /// Class ConfigurationHelper + /// Class ConfigurationHelper. /// public static class ConfigurationHelper { /// /// Reads an xml configuration file from the file system - /// It will immediately re-serialize and save if new serialization data is available due to property changes + /// It will immediately re-serialize and save if new serialization data is available due to property changes. /// /// The type. /// The path. diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 120a5adc48..1bacd8aef2 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -321,7 +321,7 @@ namespace Emby.Server.Implementations private readonly IConfiguration _configuration; /// - /// Gets or sets the installation manager. + /// Gets the installation manager. /// /// The installation manager. protected IInstallationManager InstallationManager { get; private set; } @@ -500,7 +500,7 @@ namespace Emby.Server.Implementations /// /// Gets the export types. /// - /// The type + /// The type. /// IEnumerable{Type}. public IEnumerable GetExportTypes() { @@ -1022,7 +1022,7 @@ namespace Emby.Server.Implementations } /// - /// Dirty hacks + /// Dirty hacks. /// private void SetStaticProperties() { diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 22681fb499..151670074a 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -473,7 +473,7 @@ namespace Emby.Server.Implementations.Channels await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)) { ForceSave = !isNew && forceUpdate - }, cancellationToken); + }, cancellationToken).ConfigureAwait(false); return item; } @@ -636,7 +636,7 @@ namespace Emby.Server.Implementations.Channels private async Task RefreshLatestChannelItems(IChannel channel, CancellationToken cancellationToken) { - var internalChannel = await GetChannel(channel, cancellationToken); + var internalChannel = await GetChannel(channel, cancellationToken).ConfigureAwait(false); var query = new InternalItemsQuery(); query.Parent = internalChannel; diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 6d414be739..c5a77ce5b5 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -121,7 +121,7 @@ namespace Emby.Server.Implementations.Collections // This could cause it to get re-resolved as a plain folder var folderName = _fileSystem.GetValidFilename(name) + " [boxset]"; - var parentFolder = GetCollectionsFolder(true).Result; + var parentFolder = GetCollectionsFolder(true).GetAwaiter().GetResult(); if (parentFolder == null) { diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 65f8a915f9..8d509f6888 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1177,7 +1177,7 @@ namespace Emby.Server.Implementations.Data { if (id == Guid.Empty) { - throw new ArgumentException(nameof(id), "Guid can't be empty"); + throw new ArgumentException("Guid can't be empty", nameof(id)); } CheckDisposed(); diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index d1704b3736..36d4418512 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -130,7 +130,6 @@ namespace Emby.Server.Implementations.Devices var session = _authRepo.Get(new AuthenticationInfoQuery { DeviceId = id - }).Items.FirstOrDefault(); var device = session == null ? null : ToDeviceInfo(session); diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index 08041eb59f..a2619367d5 100644 --- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -70,7 +70,6 @@ namespace Emby.Server.Implementations.EntryPoints if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase)) { Stop(); - Start(); } } diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 7bef2ae581..24906220d1 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -455,7 +455,7 @@ namespace Emby.Server.Implementations.EntryPoints return new[] { item }; } - return new T[] { }; + return Array.Empty(); } /// diff --git a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs index 141e729584..3ff8d99685 100644 --- a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs @@ -156,7 +156,7 @@ namespace Emby.Server.Implementations.EntryPoints { try { - await _sessionManager.SendMessageToAdminSessions(name, data, CancellationToken.None); + await _sessionManager.SendMessageToAdminSessions(name, data, CancellationToken.None).ConfigureAwait(false); } catch (Exception) { diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 0e6083773d..810b46200d 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -325,7 +325,7 @@ namespace Emby.Server.Implementations.HttpClientManager if (options.LogErrorResponseBody) { - var msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + string msg = await response.Content.ReadAsStringAsync().ConfigureAwait(false); _logger.LogError("HTTP request failed with message: {Message}", msg); } diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 0b2924a3ba..b5cfb6b09a 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -460,7 +460,7 @@ namespace Emby.Server.Implementations.HttpServer if (string.IsNullOrEmpty(path)) { - throw new ArgumentNullException(nameof(path)); + throw new ArgumentException("Path can't be empty.", nameof(options)); } if (fileShare != FileShareMode.Read && fileShare != FileShareMode.ReadWrite) diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index e27f794ba6..320136d11a 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -48,12 +48,14 @@ namespace Emby.Server.Implementations.HttpServer public IDictionary Headers => _options; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The range header. + /// The content length. /// The source. /// Type of the content. /// if set to true [is head request]. + /// The logger instance. public RangeRequestWriter(string rangeHeader, long contentLength, Stream source, string contentType, bool isHeadRequest, ILogger logger) { if (string.IsNullOrEmpty(contentType)) diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index 9c7f7dfcb1..6783b07eb8 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.Library if (string.IsNullOrEmpty(searchTerm)) { - throw new ArgumentNullException(nameof(searchTerm)); + throw new ArgumentNullException("SearchTerm can't be empty.", nameof(searchTerm)); } searchTerm = searchTerm.Trim().RemoveDiacritics(); diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs index b584cc649d..d06cda177f 100644 --- a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs @@ -28,10 +28,11 @@ namespace Emby.Server.Implementations.Library.Validators private readonly IItemRepository _itemRepo; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The library manager. /// The logger. + /// The item repository. public ArtistsValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; diff --git a/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs index 0568073005..3bc5c2fb2a 100644 --- a/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs +++ b/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs @@ -10,17 +10,18 @@ namespace Emby.Server.Implementations.Library.Validators public class GenresPostScanTask : ILibraryPostScanTask { /// - /// The _library manager + /// The _library manager. /// private readonly ILibraryManager _libraryManager; private readonly ILogger _logger; private readonly IItemRepository _itemRepo; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The library manager. /// The logger. + /// The item repository. public GenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; diff --git a/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs index d7ab92d306..9ac4bf7618 100644 --- a/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs +++ b/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs @@ -20,10 +20,11 @@ namespace Emby.Server.Implementations.Library.Validators private readonly IItemRepository _itemRepo; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The library manager. /// The logger. + /// The item repository. public MusicGenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; diff --git a/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs index 4aa5c7e72a..2efae0fe44 100644 --- a/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs +++ b/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs @@ -21,9 +21,11 @@ namespace Emby.Server.Implementations.Library.Validators private readonly IItemRepository _itemRepo; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The library manager. + /// The logger. + /// Th item repository. public StudiosPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 687a178a68..ef5928e480 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -237,7 +237,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (requiresRefresh) { - await _libraryManager.ValidateMediaLibrary(new SimpleProgress(), CancellationToken.None); + await _libraryManager.ValidateMediaLibrary(new SimpleProgress(), CancellationToken.None).ConfigureAwait(false); } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 9a4c91d0bd..838ac97d77 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -501,7 +501,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings public async Task> GetHeadends(ListingsProviderInfo info, string country, string location, CancellationToken cancellationToken) { - var token = await GetToken(info, cancellationToken); + var token = await GetToken(info, cancellationToken).ConfigureAwait(false); var lineups = new List(); @@ -713,7 +713,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings private async Task AddLineupToAccount(ListingsProviderInfo info, CancellationToken cancellationToken) { - var token = await GetToken(info, cancellationToken); + var token = await GetToken(info, cancellationToken).ConfigureAwait(false); if (string.IsNullOrEmpty(token)) { @@ -738,7 +738,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings httpOptions.RequestHeaders["token"] = token; - using (await _httpClient.SendAsync(httpOptions, "PUT")) + using (await _httpClient.SendAsync(httpOptions, "PUT").ConfigureAwait(false)) { } } @@ -750,7 +750,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings throw new ArgumentException("Listings Id required"); } - var token = await GetToken(info, cancellationToken); + var token = await GetToken(info, cancellationToken).ConfigureAwait(false); if (string.IsNullOrEmpty(token)) { @@ -833,7 +833,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings throw new Exception("ListingsId required"); } - var token = await GetToken(info, cancellationToken); + var token = await GetToken(info, cancellationToken).ConfigureAwait(false); if (string.IsNullOrEmpty(token)) { diff --git a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs index 542951de4a..1056a33b9a 100644 --- a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs +++ b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs @@ -38,8 +38,8 @@ namespace Emby.Server.Implementations.LiveTv /// IEnumerable{BaseTaskTrigger}. public IEnumerable GetDefaultTriggers() { - return new[] { - + return new[] + { // Every so often new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks} }; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index da98f3e58b..f834566605 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -185,7 +185,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun Url = string.Format("{0}/tuners.html", GetApiUrl(info)), CancellationToken = cancellationToken, BufferContent = false - }, HttpMethod.Get)) + }, HttpMethod.Get).ConfigureAwait(false)) using (var stream = response.Content) using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8)) { @@ -259,7 +259,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun for (int i = 0; i < model.TunerCount; ++i) { var name = string.Format("Tuner {0}", i + 1); - var currentChannel = "none"; /// @todo Get current channel and map back to Station Id + var currentChannel = "none"; // @todo Get current channel and map back to Station Id var isAvailable = await manager.CheckTunerAvailability(ipInfo, i, cancellationToken).ConfigureAwait(false); var status = isAvailable ? LiveTvTunerStatus.Available : LiveTvTunerStatus.LiveTv; tuners.Add(new LiveTvTunerInfo @@ -298,7 +298,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public async Task> GetTunerInfos(TunerHostInfo info, CancellationToken cancellationToken) { // TODO Need faster way to determine UDP vs HTTP - var channels = await GetChannels(info, true, cancellationToken); + var channels = await GetChannels(info, true, cancellationToken).ConfigureAwait(false); var hdHomerunChannelInfo = channels.FirstOrDefault() as HdHomerunChannelInfo; diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 13cdc50ca4..bda43e832a 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -5,11 +5,9 @@ using System.Globalization; using System.IO; using System.Linq; using System.Reflection; -using System.Text; using System.Threading.Tasks; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs b/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs index 268bf4042d..fda32da5e8 100644 --- a/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs +++ b/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs @@ -27,12 +27,12 @@ namespace Emby.Server.Implementations.Middleware var webSocketContext = await httpContext.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); if (webSocketContext != null) { - await _webSocketManager.OnWebSocketConnected(webSocketContext); + await _webSocketManager.OnWebSocketConnected(webSocketContext).ConfigureAwait(false); } } else { - await _next.Invoke(httpContext); + await _next.Invoke(httpContext).ConfigureAwait(false); } } } diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index d948dad688..098bd518e3 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -490,23 +490,25 @@ namespace Emby.Server.Implementations.Networking IPAddress ipaddy; int port = -1; - //check if we have an IPv6 or ports + // check if we have an IPv6 or ports if (values.Length <= 2) // ipv4 or hostname { port = values.Length == 1 ? defaultport : GetPort(values[1]); - //try to use the address as IPv4, otherwise get hostname + // try to use the address as IPv4, otherwise get hostname if (!IPAddress.TryParse(values[0], out ipaddy)) + { ipaddy = await GetIPfromHost(values[0]).ConfigureAwait(false); + } } - else if (values.Length > 2) //ipv6 + else if (values.Length > 2) // ipv6 { - //could [a:b:c]:d + //ncould [a:b:c]:d if (values[0].StartsWith("[") && values[values.Length - 2].EndsWith("]")) { string ipaddressstring = string.Join(":", values.Take(values.Length - 1).ToArray()); ipaddy = IPAddress.Parse(ipaddressstring); - port = GetPort(values[values.Length - 1]); + port = GetPort(values[^1]); } else //[a:b:c] or a:b:c { @@ -516,7 +518,11 @@ namespace Emby.Server.Implementations.Networking } else { - throw new FormatException(string.Format("Invalid endpoint ipaddress '{0}'", endpointstring)); + throw new FormatException( + string.Format( + CultureInfo.InvariantCulture, + "Invalid endpoint ipaddress '{0}'", + endpointstring)); } if (port == -1) diff --git a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs index de51a37ab7..cd9f7946e9 100644 --- a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs +++ b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs @@ -48,4 +48,3 @@ namespace Emby.Server.Implementations.Playlists } } } - diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 83226b07f1..5b188d9626 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.ScheduledTasks public event EventHandler> TaskProgress; /// - /// Gets or sets the scheduled task. + /// Gets the scheduled task. /// /// The scheduled task. public IScheduledTask ScheduledTask { get; private set; } @@ -215,11 +215,12 @@ namespace Emby.Server.Implementations.ScheduledTasks public double? CurrentProgress { get; private set; } /// - /// The _triggers + /// The _triggers. /// private Tuple[] _triggers; + /// - /// Gets the triggers that define when the task will run + /// Gets the triggers that define when the task will run. /// /// The triggers. private Tuple[] InternalTriggers @@ -245,7 +246,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// - /// Gets the triggers that define when the task will run + /// Gets the triggers that define when the task will run. /// /// The triggers. /// value diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index 595c3037d7..ecf58dbc0e 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -36,19 +36,19 @@ namespace Emby.Server.Implementations.ScheduledTasks /// Gets or sets the json serializer. /// /// The json serializer. - private IJsonSerializer JsonSerializer { get; set; } + private readonly IJsonSerializer _jsonSerializer; /// /// Gets or sets the application paths. /// /// The application paths. - private IApplicationPaths ApplicationPaths { get; set; } + private readonly IApplicationPaths _applicationPaths; /// /// Gets the logger. /// /// The logger. - private ILogger Logger { get; set; } + private readonly ILogger _logger; private readonly IFileSystem _fileSystem; /// @@ -57,19 +57,19 @@ namespace Emby.Server.Implementations.ScheduledTasks /// The application paths. /// The json serializer. /// The logger factory. - /// kernel + /// The filesystem manager. public TaskManager( IApplicationPaths applicationPaths, IJsonSerializer jsonSerializer, ILoggerFactory loggerFactory, IFileSystem fileSystem) { - ApplicationPaths = applicationPaths; - JsonSerializer = jsonSerializer; - Logger = loggerFactory.CreateLogger(nameof(TaskManager)); + _applicationPaths = applicationPaths; + _jsonSerializer = jsonSerializer; + _logger = loggerFactory.CreateLogger(nameof(TaskManager)); _fileSystem = fileSystem; - ScheduledTasks = new IScheduledTaskWorker[] { }; + ScheduledTasks = Array.Empty(); } /// @@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// Task options. public void CancelIfRunningAndQueue(TaskOptions options) - where T : IScheduledTask + where T : IScheduledTask { var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T)); ((ScheduledTaskWorker)task).CancelIfRunning(); @@ -115,7 +115,7 @@ namespace Emby.Server.Implementations.ScheduledTasks if (scheduledTask == null) { - Logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", typeof(T).Name); + _logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", typeof(T).Name); } else { @@ -147,13 +147,13 @@ namespace Emby.Server.Implementations.ScheduledTasks if (scheduledTask == null) { - Logger.LogError("Unable to find scheduled task of type {0} in Execute.", typeof(T).Name); + _logger.LogError("Unable to find scheduled task of type {0} in Execute.", typeof(T).Name); } else { var type = scheduledTask.ScheduledTask.GetType(); - Logger.LogInformation("Queueing task {0}", type.Name); + _logger.LogInformation("Queueing task {0}", type.Name); lock (_taskQueue) { @@ -176,7 +176,7 @@ namespace Emby.Server.Implementations.ScheduledTasks if (scheduledTask == null) { - Logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", task.GetType().Name); + _logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", task.GetType().Name); } else { @@ -193,7 +193,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { var type = task.ScheduledTask.GetType(); - Logger.LogInformation("Queueing task {0}", type.Name); + _logger.LogInformation("Queueing task {0}", type.Name); lock (_taskQueue) { @@ -213,7 +213,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// The tasks. public void AddTasks(IEnumerable tasks) { - var list = tasks.Select(t => new ScheduledTaskWorker(t, ApplicationPaths, this, JsonSerializer, Logger, _fileSystem)); + var list = tasks.Select(t => new ScheduledTaskWorker(t, _applicationPaths, this, _jsonSerializer, _logger, _fileSystem)); ScheduledTasks = ScheduledTasks.Concat(list).ToArray(); } @@ -281,7 +281,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// private void ExecuteQueuedTasks() { - Logger.LogInformation("ExecuteQueuedTasks"); + _logger.LogInformation("ExecuteQueuedTasks"); // Execute queued tasks lock (_taskQueue) diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs index ec9466c4ab..ea278de0d9 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using System.Threading; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; @@ -7,12 +6,12 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks { /// - /// Represents a task trigger that fires everyday + /// Represents a task trigger that fires everyday. /// public class DailyTrigger : ITaskTrigger { /// - /// Get the time of day to trigger the task to run + /// Get the time of day to trigger the task to run. /// /// The time of day. public TimeSpan TimeOfDay { get; set; } diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs index 0b67669b94..27c4dcba07 100644 --- a/Emby.Server.Implementations/Services/ServicePath.cs +++ b/Emby.Server.Implementations/Services/ServicePath.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Reflection; @@ -45,8 +46,8 @@ namespace Emby.Server.Implementations.Services public int PathComponentsCount { get; set; } /// - /// The total number of segments after subparts have been exploded ('.') - /// e.g. /path/to/here.ext == 4 + /// Gets or sets the total number of segments after subparts have been exploded ('.') + /// e.g. /path/to/here.ext == 4. /// public int TotalComponentsCount { get; set; } @@ -279,7 +280,7 @@ namespace Emby.Server.Implementations.Services } /// - /// Provide for quick lookups based on hashes that can be determined from a request url + /// Provide for quick lookups based on hashes that can be determined from a request url. /// public string FirstMatchHashKey { get; private set; } @@ -436,9 +437,12 @@ namespace Emby.Server.Implementations.Services && requestComponents.Length >= this.TotalComponentsCount - this.wildcardCount; if (!isValidWildCardPath) - throw new ArgumentException(string.Format( - "Path Mismatch: Request Path '{0}' has invalid number of components compared to: '{1}'", - pathInfo, this.restPath)); + throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + "Path Mismatch: Request Path '{0}' has invalid number of components compared to: '{1}'", + pathInfo, + this.restPath)); } var requestKeyValuesMap = new Dictionary(); diff --git a/Emby.Server.Implementations/Services/SwaggerService.cs b/Emby.Server.Implementations/Services/SwaggerService.cs index d223864364..c30f32af94 100644 --- a/Emby.Server.Implementations/Services/SwaggerService.cs +++ b/Emby.Server.Implementations/Services/SwaggerService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using MediaBrowser.Controller.Net; @@ -175,7 +175,7 @@ namespace Emby.Server.Implementations.Services private SwaggerTag[] GetTags() { - return new SwaggerTag[] { }; + return Array.Empty(); } private Dictionary GetDefinitions() diff --git a/Emby.Server.Implementations/Session/HttpSessionController.cs b/Emby.Server.Implementations/Session/HttpSessionController.cs index 1104a7a85b..dfb81816c7 100644 --- a/Emby.Server.Implementations/Session/HttpSessionController.cs +++ b/Emby.Server.Implementations/Session/HttpSessionController.cs @@ -105,7 +105,7 @@ namespace Emby.Server.Implementations.Session return SendMessage(command.Command.ToString(), messageId, args, cancellationToken); } - private string[] _supportedMessages = new string[] { }; + private string[] _supportedMessages = Array.Empty(); public Task SendMessage(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken) { if (!IsSessionActive) diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs index 62b16ed8c8..67521d6c63 100644 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.SocketSharp } /// - /// Gets or sets the state. + /// Gets the state. /// /// The state. public WebSocketState State => _webSocket.State; diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index e93bff1244..ba5ba1904c 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -81,8 +81,10 @@ namespace Emby.Server.Implementations.SocketSharp if (webSocketContext.State == WebSocketState.Open) { - await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, - result.CloseStatusDescription, _disposeCancellationToken); + await webSocketContext.CloseAsync( + result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, + result.CloseStatusDescription, + _disposeCancellationToken).ConfigureAwait(false); } socket.Dispose(); diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 332ce39034..690ba0be4a 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Net; -using System.Linq; using MediaBrowser.Common.Net; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; @@ -13,7 +12,7 @@ using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest; namespace Emby.Server.Implementations.SocketSharp { - public partial class WebSocketSharpRequest : IHttpRequest + public class WebSocketSharpRequest : IHttpRequest { public const string FormUrlEncoded = "application/x-www-form-urlencoded"; public const string MultiPartFormData = "multipart/form-data"; diff --git a/Emby.Server.Implementations/Sorting/NameComparer.cs b/Emby.Server.Implementations/Sorting/NameComparer.cs index 10fa4359aa..4eb1549f58 100644 --- a/Emby.Server.Implementations/Sorting/NameComparer.cs +++ b/Emby.Server.Implementations/Sorting/NameComparer.cs @@ -19,10 +19,14 @@ namespace Emby.Server.Implementations.Sorting public int Compare(BaseItem x, BaseItem y) { if (x == null) + { throw new ArgumentNullException(nameof(x)); + } if (y == null) + { throw new ArgumentNullException(nameof(y)); + } return string.Compare(x.Name, y.Name, StringComparison.CurrentCultureIgnoreCase); } diff --git a/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs index e8fa8edc8e..7afbd9ff7d 100644 --- a/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs @@ -24,10 +24,14 @@ namespace Emby.Server.Implementations.Sorting public int Compare(BaseItem x, BaseItem y) { if (x == null) + { throw new ArgumentNullException(nameof(x)); + } if (y == null) + { throw new ArgumentNullException(nameof(y)); + } var levelX = string.IsNullOrEmpty(x.OfficialRating) ? 0 : _localization.GetRatingLevel(x.OfficialRating) ?? 0; var levelY = string.IsNullOrEmpty(y.OfficialRating) ? 0 : _localization.GetRatingLevel(y.OfficialRating) ?? 0; diff --git a/Emby.Server.Implementations/Sorting/StudioComparer.cs b/Emby.Server.Implementations/Sorting/StudioComparer.cs index 617ed55d52..c9ac765c10 100644 --- a/Emby.Server.Implementations/Sorting/StudioComparer.cs +++ b/Emby.Server.Implementations/Sorting/StudioComparer.cs @@ -17,10 +17,15 @@ namespace Emby.Server.Implementations.Sorting public int Compare(BaseItem x, BaseItem y) { if (x == null) + { throw new ArgumentNullException(nameof(x)); + } if (y == null) + { throw new ArgumentNullException(nameof(y)); + } + return AlphanumComparator.CompareValues(x.Studios.FirstOrDefault() ?? string.Empty, y.Studios.FirstOrDefault() ?? string.Empty); } diff --git a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs index 04c73ecea7..efd97e4ff1 100644 --- a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs +++ b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs @@ -39,12 +39,12 @@ namespace Emby.Server.Implementations.WebSockets do { var buffer = WebSocket.CreateServerBuffer(BufferSize); - result = await webSocket.ReceiveAsync(buffer, cancellationToken); + result = await webSocket.ReceiveAsync(buffer, cancellationToken).ConfigureAwait(false); message.AddRange(buffer.Array.Take(result.Count)); if (result.EndOfMessage) { - await ProcessMessage(message.ToArray(), taskCompletionSource); + await ProcessMessage(message.ToArray(), taskCompletionSource).ConfigureAwait(false); message.Clear(); } } while (!taskCompletionSource.Task.IsCompleted && @@ -53,8 +53,10 @@ namespace Emby.Server.Implementations.WebSockets if (webSocket.State == WebSocketState.Open) { - await webSocket.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, - result.CloseStatusDescription, cancellationToken); + await webSocket.CloseAsync( + result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, + result.CloseStatusDescription, + cancellationToken).ConfigureAwait(false); } } diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index c7104c9507..833fcc54bc 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -588,7 +588,7 @@ namespace MediaBrowser.Api.Playback /// /// Parses query parameters as StreamOptions - /// + /// /// The stream request. private void ParseStreamOptions(StreamRequest request) { -- cgit v1.2.3 From 27e3cf15588f8ab8fe19aa611d79fa2ccd8ecda8 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 24 Nov 2019 15:27:58 +0100 Subject: Move appbuilder and service collection to Jellyfin.Server --- Emby.Server.Implementations/ApplicationHost.cs | 110 +++------------------ .../HttpServer/HttpListenerHost.cs | 3 +- .../Session/SessionWebSocketListener.cs | 4 +- Jellyfin.Api/Controllers/StartupController.cs | 2 +- .../Extensions/ApiApplicationBuilderExtensions.cs | 27 ----- .../Extensions/ApiServiceCollectionExtensions.cs | 96 ------------------ .../Models/Startup/StartupConfigurationDto.cs | 23 ----- Jellyfin.Api/Models/Startup/StartupUserDto.cs | 18 ---- .../Models/StartupDtos/StartupConfigurationDto.cs | 23 +++++ Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs | 18 ++++ .../Extensions/ApiApplicationBuilderExtensions.cs | 27 +++++ .../Extensions/ApiServiceCollectionExtensions.cs | 89 +++++++++++++++++ Jellyfin.Server/Jellyfin.Server.csproj | 4 + Jellyfin.Server/Program.cs | 70 ++++++++++++- Jellyfin.Server/Startup.cs | 81 +++++++++++++++ MediaBrowser.Controller/IServerApplicationHost.cs | 5 + 16 files changed, 336 insertions(+), 264 deletions(-) delete mode 100644 Jellyfin.Api/Extensions/ApiApplicationBuilderExtensions.cs delete mode 100644 Jellyfin.Api/Extensions/ApiServiceCollectionExtensions.cs delete mode 100644 Jellyfin.Api/Models/Startup/StartupConfigurationDto.cs delete mode 100644 Jellyfin.Api/Models/Startup/StartupUserDto.cs create mode 100644 Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs create mode 100644 Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs create mode 100644 Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs create mode 100644 Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs create mode 100644 Jellyfin.Server/Startup.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index c6cdd4786b..3b9ea41219 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -47,7 +47,6 @@ using Emby.Server.Implementations.Session; using Emby.Server.Implementations.SocketSharp; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; -using Jellyfin.Api.Extensions; using MediaBrowser.Api; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; @@ -232,7 +231,7 @@ namespace Emby.Server.Implementations } } - protected IServiceProvider _serviceProvider; + public IServiceProvider ServiceProvider; /// /// Gets the server configuration manager. @@ -461,7 +460,7 @@ namespace Emby.Server.Implementations /// The type. /// System.Object. public object CreateInstance(Type type) - => ActivatorUtilities.CreateInstance(_serviceProvider, type); + => ActivatorUtilities.CreateInstance(ServiceProvider, type); /// /// Creates an instance of type and resolves all constructor dependencies @@ -469,7 +468,7 @@ namespace Emby.Server.Implementations /// /// The type. /// T. public T CreateInstance() - => ActivatorUtilities.CreateInstance(_serviceProvider); + => ActivatorUtilities.CreateInstance(ServiceProvider); /// /// Creates the instance safe. @@ -481,7 +480,7 @@ namespace Emby.Server.Implementations try { Logger.LogDebug("Creating instance of {Type}", type); - return ActivatorUtilities.CreateInstance(_serviceProvider, type); + return ActivatorUtilities.CreateInstance(ServiceProvider, type); } catch (Exception ex) { @@ -495,7 +494,7 @@ namespace Emby.Server.Implementations /// /// The type /// ``0. - public T Resolve() => _serviceProvider.GetService(); + public T Resolve() => ServiceProvider.GetService(); /// /// Gets the export types. @@ -611,93 +610,14 @@ namespace Emby.Server.Implementations await RegisterResources(serviceCollection).ConfigureAwait(false); - string contentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; - if (string.IsNullOrEmpty(contentRoot)) + ContentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; + if (string.IsNullOrEmpty(ContentRoot)) { - contentRoot = ServerConfigurationManager.ApplicationPaths.WebPath; - } - - var host = new WebHostBuilder() - .UseKestrel(options => - { - var addresses = ServerConfigurationManager - .Configuration - .LocalNetworkAddresses - .Select(NormalizeConfiguredLocalAddress) - .Where(i => i != null) - .ToList(); - if (addresses.Any()) - { - foreach (var address in addresses) - { - Logger.LogInformation("Kestrel listening on {ipaddr}", address); - options.Listen(address, HttpPort); - - if (EnableHttps && Certificate != null) - { - options.Listen(address, HttpsPort, listenOptions => listenOptions.UseHttps(Certificate)); - } - } - } - else - { - Logger.LogInformation("Kestrel listening on all interfaces"); - options.ListenAnyIP(HttpPort); - - if (EnableHttps && Certificate != null) - { - options.ListenAnyIP(HttpsPort, listenOptions => listenOptions.UseHttps(Certificate)); - } - } - }) - .UseContentRoot(contentRoot) - .ConfigureServices(services => - { - services.AddResponseCompression(); - services.AddHttpContextAccessor(); - services.AddJellyfinApi(ServerConfigurationManager.Configuration.BaseUrl.TrimStart('/')); - - services.AddJellyfinApiSwagger(); - - // configure custom legacy authentication - services.AddCustomAuthentication(); - - services.AddJellyfinApiAuthorization(); - - // Merge the external ServiceCollection into ASP.NET DI - services.TryAdd(serviceCollection); - }) - .Configure(app => - { - app.UseWebSockets(); - - app.UseResponseCompression(); - - // TODO app.UseMiddleware(); - app.Use(ExecuteWebsocketHandlerAsync); - - // TODO use when old API is removed: app.UseAuthentication(); - app.UseJellyfinApiSwagger(); - app.UseMvc(); - app.Use(ExecuteHttpHandlerAsync); - }) - .Build(); - - _serviceProvider = host.Services; - FindParts(); - - try - { - await host.StartAsync().ConfigureAwait(false); - } - catch - { - Logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in system.xml and try again."); - throw; + ContentRoot = ServerConfigurationManager.ApplicationPaths.WebPath; } } - private async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) + public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) { if (!context.WebSockets.IsWebSocketRequest) { @@ -708,7 +628,7 @@ namespace Emby.Server.Implementations await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false); } - private async Task ExecuteHttpHandlerAsync(HttpContext context, Func next) + public async Task ExecuteHttpHandlerAsync(HttpContext context, Func next) { if (context.WebSockets.IsWebSocketRequest) { @@ -1090,9 +1010,9 @@ namespace Emby.Server.Implementations /// /// Finds the parts. /// - protected void FindParts() + public void FindParts() { - InstallationManager = _serviceProvider.GetService(); + InstallationManager = ServiceProvider.GetService(); InstallationManager.PluginInstalled += PluginInstalled; if (!ServerConfigurationManager.Configuration.IsPortAuthorized) @@ -1221,7 +1141,7 @@ namespace Emby.Server.Implementations private CertificateInfo CertificateInfo { get; set; } - protected X509Certificate2 Certificate { get; private set; } + public X509Certificate2 Certificate { get; private set; } private IEnumerable GetUrlPrefixes() { @@ -1605,7 +1525,7 @@ namespace Emby.Server.Implementations return resultList; } - private IPAddress NormalizeConfiguredLocalAddress(string address) + public IPAddress NormalizeConfiguredLocalAddress(string address) { var index = address.Trim('/').IndexOf('/'); @@ -1685,6 +1605,8 @@ namespace Emby.Server.Implementations public int HttpsPort { get; private set; } + public string ContentRoot { get; private set; } + /// /// Shuts down. /// diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index dc1a56e271..6dd016f8a2 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -18,7 +18,6 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Internal; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -164,7 +163,7 @@ namespace Emby.Server.Implementations.HttpServer { OnReceive = ProcessWebSocketMessageReceived, Url = e.Url, - QueryString = e.QueryString ?? new QueryCollection() + QueryString = e.QueryString }; connection.Closed += OnConnectionClosed; diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 63ec757626..930f2d35d3 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -4,7 +4,6 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Events; using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -67,7 +66,7 @@ namespace Emby.Server.Implementations.Session { if (queryString == null) { - throw new ArgumentNullException(nameof(queryString)); + return null; } var token = queryString["api_key"]; @@ -75,6 +74,7 @@ namespace Emby.Server.Implementations.Session { return null; } + var deviceId = queryString["deviceId"]; return _sessionManager.GetSessionByAuthenticationToken(token, deviceId, remoteEndpoint); } diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index 0e7d17a27f..50f3dc83cf 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -1,6 +1,6 @@ using System.Linq; using System.Threading.Tasks; -using Jellyfin.Api.Models.Startup; +using Jellyfin.Api.Models.StartupDtos; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using Microsoft.AspNetCore.Authorization; diff --git a/Jellyfin.Api/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Api/Extensions/ApiApplicationBuilderExtensions.cs deleted file mode 100644 index f70466ebec..0000000000 --- a/Jellyfin.Api/Extensions/ApiApplicationBuilderExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.AspNetCore.Builder; - -namespace Jellyfin.Api.Extensions -{ - /// - /// Extensions for adding API specific functionality to the application pipeline. - /// - public static class ApiApplicationBuilderExtensions - { - /// - /// Adds swagger and swagger UI to the application pipeline. - /// - /// The application builder. - /// The updated application builder. - public static IApplicationBuilder UseJellyfinApiSwagger(this IApplicationBuilder applicationBuilder) - { - applicationBuilder.UseSwagger(); - - // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), - // specifying the Swagger JSON endpoint. - return applicationBuilder.UseSwaggerUI(c => - { - c.SwaggerEndpoint("/swagger/v1/swagger.json", "Jellyfin API V1"); - }); - } - } -} diff --git a/Jellyfin.Api/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Api/Extensions/ApiServiceCollectionExtensions.cs deleted file mode 100644 index 38f5f6d390..0000000000 --- a/Jellyfin.Api/Extensions/ApiServiceCollectionExtensions.cs +++ /dev/null @@ -1,96 +0,0 @@ -using Jellyfin.Api.Auth; -using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; -using Jellyfin.Api.Auth.RequiresElevationPolicy; -using Jellyfin.Api.Controllers; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Authorization; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.OpenApi.Models; - -namespace Jellyfin.Api.Extensions -{ - /// - /// API specific extensions for the service collection. - /// - public static class ApiServiceCollectionExtensions - { - /// - /// Adds jellyfin API authorization policies to the DI container. - /// - /// The service collection. - /// The updated service collection. - public static IServiceCollection AddJellyfinApiAuthorization(this IServiceCollection serviceCollection) - { - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - return serviceCollection.AddAuthorizationCore(options => - { - options.AddPolicy( - "RequiresElevation", - policy => - { - policy.AddAuthenticationSchemes("CustomAuthentication"); - policy.AddRequirements(new RequiresElevationRequirement()); - }); - options.AddPolicy( - "FirstTimeSetupOrElevated", - policy => - { - policy.AddAuthenticationSchemes("CustomAuthentication"); - policy.AddRequirements(new FirstTimeSetupOrElevatedRequirement()); - }); - }); - } - - /// - /// Adds custom legacy authentication to the service collection. - /// - /// The service collection. - /// The updated service collection. - public static AuthenticationBuilder AddCustomAuthentication(this IServiceCollection serviceCollection) - { - return serviceCollection.AddAuthentication("CustomAuthentication") - .AddScheme("CustomAuthentication", null); - } - - /// - /// Extension method for adding the jellyfin API to the service collection. - /// - /// The service collection. - /// The base url for the API. - /// The MVC builder. - public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, string baseUrl) - { - return serviceCollection.AddMvc(opts => - { - var policy = new AuthorizationPolicyBuilder() - .RequireAuthenticatedUser() - .Build(); - opts.Filters.Add(new AuthorizeFilter(policy)); - opts.EnableEndpointRouting = false; - opts.UseGeneralRoutePrefix(baseUrl); - }) - .SetCompatibilityVersion(CompatibilityVersion.Version_2_2) - - // Clear app parts to avoid other assemblies being picked up - .ConfigureApplicationPartManager(a => a.ApplicationParts.Clear()) - .AddApplicationPart(typeof(StartupController).Assembly) - .AddControllersAsServices(); - } - - /// - /// Adds Swagger to the service collection. - /// - /// The service collection. - /// The updated service collection. - public static IServiceCollection AddJellyfinApiSwagger(this IServiceCollection serviceCollection) - { - return serviceCollection.AddSwaggerGen(c => - { - c.SwaggerDoc("v1", new OpenApiInfo { Title = "Jellyfin API", Version = "v1" }); - }); - } - } -} diff --git a/Jellyfin.Api/Models/Startup/StartupConfigurationDto.cs b/Jellyfin.Api/Models/Startup/StartupConfigurationDto.cs deleted file mode 100644 index dac15e412c..0000000000 --- a/Jellyfin.Api/Models/Startup/StartupConfigurationDto.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Jellyfin.Api.Models.Startup -{ - /// - /// The startup configuration DTO. - /// - public class StartupConfigurationDto - { - /// - /// Gets or sets UI language culture. - /// - public string UICulture { get; set; } - - /// - /// Gets or sets the metadata country code. - /// - public string MetadataCountryCode { get; set; } - - /// - /// Gets or sets the preferred language for the metadata. - /// - public string PreferredMetadataLanguage { get; set; } - } -} diff --git a/Jellyfin.Api/Models/Startup/StartupUserDto.cs b/Jellyfin.Api/Models/Startup/StartupUserDto.cs deleted file mode 100644 index 7e890d76a0..0000000000 --- a/Jellyfin.Api/Models/Startup/StartupUserDto.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Jellyfin.Api.Models.Startup -{ - /// - /// The startup user DTO. - /// - public class StartupUserDto - { - /// - /// Gets or sets the username. - /// - public string Name { get; set; } - - /// - /// Gets or sets the user's password. - /// - public string Password { get; set; } - } -} diff --git a/Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs b/Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs new file mode 100644 index 0000000000..d048dad0a1 --- /dev/null +++ b/Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs @@ -0,0 +1,23 @@ +namespace Jellyfin.Api.Models.StartupDtos +{ + /// + /// The startup configuration DTO. + /// + public class StartupConfigurationDto + { + /// + /// Gets or sets UI language culture. + /// + public string UICulture { get; set; } + + /// + /// Gets or sets the metadata country code. + /// + public string MetadataCountryCode { get; set; } + + /// + /// Gets or sets the preferred language for the metadata. + /// + public string PreferredMetadataLanguage { get; set; } + } +} diff --git a/Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs b/Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs new file mode 100644 index 0000000000..3a9348037a --- /dev/null +++ b/Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs @@ -0,0 +1,18 @@ +namespace Jellyfin.Api.Models.StartupDtos +{ + /// + /// The startup user DTO. + /// + public class StartupUserDto + { + /// + /// Gets or sets the username. + /// + public string Name { get; set; } + + /// + /// Gets or sets the user's password. + /// + public string Password { get; set; } + } +} diff --git a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs new file mode 100644 index 0000000000..db06eb4552 --- /dev/null +++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Builder; + +namespace Jellyfin.Server.Extensions +{ + /// + /// Extensions for adding API specific functionality to the application pipeline. + /// + public static class ApiApplicationBuilderExtensions + { + /// + /// Adds swagger and swagger UI to the application pipeline. + /// + /// The application builder. + /// The updated application builder. + public static IApplicationBuilder UseJellyfinApiSwagger(this IApplicationBuilder applicationBuilder) + { + applicationBuilder.UseSwagger(); + + // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), + // specifying the Swagger JSON endpoint. + return applicationBuilder.UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "Jellyfin API V1"); + }); + } + } +} diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs new file mode 100644 index 0000000000..e5a8937e87 --- /dev/null +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -0,0 +1,89 @@ +using Jellyfin.Api; +using Jellyfin.Api.Auth; +using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; +using Jellyfin.Api.Auth.RequiresElevationPolicy; +using Jellyfin.Api.Controllers; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; + +namespace Jellyfin.Server.Extensions +{ + /// + /// API specific extensions for the service collection. + /// + public static class ApiServiceCollectionExtensions + { + /// + /// Adds jellyfin API authorization policies to the DI container. + /// + /// The service collection. + /// The updated service collection. + public static IServiceCollection AddJellyfinApiAuthorization(this IServiceCollection serviceCollection) + { + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + return serviceCollection.AddAuthorizationCore(options => + { + options.AddPolicy( + "RequiresElevation", + policy => + { + policy.AddAuthenticationSchemes("CustomAuthentication"); + policy.AddRequirements(new RequiresElevationRequirement()); + }); + options.AddPolicy( + "FirstTimeSetupOrElevated", + policy => + { + policy.AddAuthenticationSchemes("CustomAuthentication"); + policy.AddRequirements(new FirstTimeSetupOrElevatedRequirement()); + }); + }); + } + + /// + /// Adds custom legacy authentication to the service collection. + /// + /// The service collection. + /// The updated service collection. + public static AuthenticationBuilder AddCustomAuthentication(this IServiceCollection serviceCollection) + { + return serviceCollection.AddAuthentication("CustomAuthentication") + .AddScheme("CustomAuthentication", null); + } + + /// + /// Extension method for adding the jellyfin API to the service collection. + /// + /// The service collection. + /// The base url for the API. + /// The MVC builder. + public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, string baseUrl) + { + return serviceCollection.AddMvc(opts => + { + opts.UseGeneralRoutePrefix(baseUrl); + }) + + // Clear app parts to avoid other assemblies being picked up + .ConfigureApplicationPartManager(a => a.ApplicationParts.Clear()) + .AddApplicationPart(typeof(StartupController).Assembly) + .AddControllersAsServices(); + } + + /// + /// Adds Swagger to the service collection. + /// + /// The service collection. + /// The updated service collection. + public static IServiceCollection AddJellyfinApiSwagger(this IServiceCollection serviceCollection) + { + return serviceCollection.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo { Title = "Jellyfin API", Version = "v1" }); + }); + } + } +} diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 4238d7fe3e..dc784becfb 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -20,6 +20,10 @@ + + + + diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index e8bd0cd309..998f1125f5 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -18,8 +18,10 @@ using Jellyfin.Drawing.Skia; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.Globalization; +using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Serilog; using Serilog.Extensions.Logging; @@ -164,7 +166,24 @@ namespace Jellyfin.Server appConfig); try { - await appHost.InitAsync(new ServiceCollection()).ConfigureAwait(false); + ServiceCollection serviceCollection = new ServiceCollection(); + await appHost.InitAsync(serviceCollection).ConfigureAwait(false); + + var host = CreateWebHostBuilder(appHost, serviceCollection).Build(); + + // A bit hacky to re-use service provider since ASP.NET doesn't allow a custom service collection. + appHost.ServiceProvider = host.Services; + appHost.FindParts(); + + try + { + await host.StartAsync().ConfigureAwait(false); + } + catch + { + _logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in system.xml and try again."); + throw; + } appHost.ImageProcessor.ImageEncoder = GetImageEncoder(appPaths, appHost.LocalizationManager); @@ -196,6 +215,55 @@ namespace Jellyfin.Server } } + private static IWebHostBuilder CreateWebHostBuilder(ApplicationHost appHost, IServiceCollection serviceCollection) + { + return new WebHostBuilder() + .UseKestrel(options => + { + var addresses = appHost.ServerConfigurationManager + .Configuration + .LocalNetworkAddresses + .Select(appHost.NormalizeConfiguredLocalAddress) + .Where(i => i != null) + .ToList(); + if (addresses.Any()) + { + foreach (var address in addresses) + { + _logger.LogInformation("Kestrel listening on {ipaddr}", address); + options.Listen(address, appHost.HttpPort); + + if (appHost.EnableHttps && appHost.Certificate != null) + { + options.Listen( + address, + appHost.HttpsPort, + listenOptions => listenOptions.UseHttps(appHost.Certificate)); + } + } + } + else + { + _logger.LogInformation("Kestrel listening on all interfaces"); + options.ListenAnyIP(appHost.HttpPort); + + if (appHost.EnableHttps && appHost.Certificate != null) + { + options.ListenAnyIP( + appHost.HttpsPort, + listenOptions => listenOptions.UseHttps(appHost.Certificate)); + } + } + }) + .UseContentRoot(appHost.ContentRoot) + .ConfigureServices(services => + { + // Merge the external ServiceCollection into ASP.NET DI + services.TryAdd(serviceCollection); + }) + .UseStartup(); + } + /// /// Create the data, config and log paths from the variety of inputs(command line args, /// environment variables) or decide on what default to use. For Windows it's %AppPath% diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs new file mode 100644 index 0000000000..3ee5fb8b50 --- /dev/null +++ b/Jellyfin.Server/Startup.cs @@ -0,0 +1,81 @@ +using Jellyfin.Server.Extensions; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Jellyfin.Server +{ + /// + /// Startup configuration for the Kestrel webhost. + /// + public class Startup + { + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// + /// Initializes a new instance of the class. + /// + /// The server configuration manager. + public Startup(IServerConfigurationManager serverConfigurationManager) + { + _serverConfigurationManager = serverConfigurationManager; + } + + /// + /// Configures the service collection for the webhost. + /// + /// The service collection. + public void ConfigureServices(IServiceCollection services) + { + services.AddResponseCompression(); + services.AddHttpContextAccessor(); + services.AddJellyfinApi(_serverConfigurationManager.Configuration.BaseUrl.TrimStart('/')); + + services.AddJellyfinApiSwagger(); + + // configure custom legacy authentication + services.AddCustomAuthentication(); + + services.AddJellyfinApiAuthorization(); + } + + /// + /// Configures the app builder for the webhost. + /// + /// The application builder. + /// The webhost environment. + /// The server application host. + public void Configure( + IApplicationBuilder app, + IWebHostEnvironment env, + IServerApplicationHost serverApplicationHost) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseWebSockets(); + + app.UseResponseCompression(); + + // TODO app.UseMiddleware(); + app.Use(serverApplicationHost.ExecuteWebsocketHandlerAsync); + + // TODO use when old API is removed: app.UseAuthentication(); + app.UseJellyfinApiSwagger(); + app.UseRouting(); + app.UseAuthorization(); + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + + app.Use(serverApplicationHost.ExecuteHttpHandlerAsync); + } + } +} diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 61b2c15ae2..b3c56bdd5f 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; using MediaBrowser.Model.System; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller { @@ -87,5 +88,9 @@ namespace MediaBrowser.Controller string ExpandVirtualPath(string path); string ReverseVirtualPath(string path); + + Task ExecuteHttpHandlerAsync(HttpContext context, Func next); + + Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next); } } -- cgit v1.2.3 From 6231fc18eac67966340fddc121dc2895fd99ab5e Mon Sep 17 00:00:00 2001 From: Cromefire_ <26320625+cromefire@users.noreply.github.com> Date: Fri, 13 Dec 2019 15:27:12 +0100 Subject: Fixed invalid username handling --- Emby.Server.Implementations/Session/SessionManager.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index b87ca3a114..80079ae6c0 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1393,6 +1393,13 @@ namespace Emby.Server.Implementations.Session } } + if (user == null) + { + AuthenticationFailed?.Invoke(this, new GenericEventArgs(request)); + + throw new SecurityException("Invalid user or password entered."); + } + if (enforcePassword) { user = await _userManager.AuthenticateUser( @@ -1403,13 +1410,6 @@ namespace Emby.Server.Implementations.Session true).ConfigureAwait(false); } - if (user == null) - { - AuthenticationFailed?.Invoke(this, new GenericEventArgs(request)); - - throw new SecurityException("Invalid user or password entered."); - } - var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName); var session = LogSessionActivity( -- cgit v1.2.3 From 7e987b9642fd1826b9adb8dc6e208454c44da4dc Mon Sep 17 00:00:00 2001 From: Cromefire_ <26320625+cromefire@users.noreply.github.com> Date: Fri, 13 Dec 2019 15:30:08 +0100 Subject: Remove whitespace Co-Authored-By: Claus Vium --- Emby.Server.Implementations/Session/SessionManager.cs | 1 - 1 file changed, 1 deletion(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 80079ae6c0..367e1d4983 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1396,7 +1396,6 @@ namespace Emby.Server.Implementations.Session if (user == null) { AuthenticationFailed?.Invoke(this, new GenericEventArgs(request)); - throw new SecurityException("Invalid user or password entered."); } -- cgit v1.2.3 From 02928128b998608e0a6fd2f8ac611be5961fa78f Mon Sep 17 00:00:00 2001 From: dkanada Date: Sat, 14 Dec 2019 11:36:06 +0900 Subject: track progress for any item that supports the feature --- Emby.Server.Implementations/Library/UserDataManager.cs | 8 +++----- Emby.Server.Implementations/Session/SessionManager.cs | 8 ++------ 2 files changed, 5 insertions(+), 11 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index 48d33c26c1..071681b08f 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -226,24 +226,21 @@ namespace Emby.Server.Implementations.Library { var pctIn = decimal.Divide(positionTicks, runtimeTicks) * 100; - // Don't track in very beginning if (pctIn < _config.Configuration.MinResumePct) { + // ignore progress during the beginning positionTicks = 0; } - - // If we're at the end, assume completed else if (pctIn > _config.Configuration.MaxResumePct || positionTicks >= runtimeTicks) { + // mark as completed close to the end positionTicks = 0; data.Played = playedToCompletion = true; } - else { // Enforce MinResumeDuration var durationSeconds = TimeSpan.FromTicks(runtimeTicks).TotalSeconds; - if (durationSeconds < _config.Configuration.MinResumeDurationSeconds) { positionTicks = 0; @@ -263,6 +260,7 @@ namespace Emby.Server.Implementations.Library positionTicks = 0; data.Played = false; } + if (!item.SupportsPositionTicksResume) { positionTicks = 0; diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index b87ca3a114..34ffceebd2 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -667,12 +667,9 @@ namespace Emby.Server.Implementations.Session data.PlayCount++; data.LastPlayedDate = DateTime.UtcNow; - if (item.SupportsPlayedStatus) + if (item.SupportsPlayedStatus && !item.SupportsPositionTicksResume) { - if (!(item is Video)) - { - data.Played = true; - } + data.Played = true; } else { @@ -769,7 +766,6 @@ namespace Emby.Server.Implementations.Session { _userDataManager.SaveUserData(user, item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None); } - } private static bool UpdatePlaybackSettings(User user, PlaybackProgressInfo info, UserItemData data) -- cgit v1.2.3 From 976459d3e8a8b889cebc2cf281e38b0fbc19c9b9 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 17 Dec 2019 23:15:02 +0100 Subject: Rewrite WebSocket handling code --- Emby.Dlna/PlayTo/PlayToController.cs | 6 +- Emby.Dlna/PlayTo/PlayToManager.cs | 2 +- Emby.Server.Implementations/ApplicationHost.cs | 34 +--- .../HttpServer/HttpListenerHost.cs | 160 ++++++++------- .../HttpServer/IHttpListener.cs | 40 ---- .../HttpServer/WebSocketConnection.cs | 215 ++++++++++----------- Emby.Server.Implementations/Net/IWebSocket.cs | 48 ----- .../Net/WebSocketConnectEventArgs.cs | 31 --- .../Session/HttpSessionController.cs | 191 ------------------ .../Session/SessionManager.cs | 13 +- .../Session/SessionWebSocketListener.cs | 36 ++-- .../Session/WebSocketController.cs | 70 ++++--- .../SocketSharp/SharpWebSocket.cs | 105 ---------- .../SocketSharp/WebSocketSharpListener.cs | 138 ------------- Jellyfin.Server/Startup.cs | 2 - .../Session/SessionInfoWebSocketListener.cs | 43 +++-- MediaBrowser.Controller/IServerApplicationHost.cs | 2 - .../Net/BasePeriodicWebSocketListener.cs | 1 - MediaBrowser.Controller/Net/IHttpServer.cs | 22 +-- .../Net/IWebSocketConnection.cs | 28 +-- .../Session/ISessionController.cs | 3 +- MediaBrowser.Controller/Session/SessionInfo.cs | 80 +++----- MediaBrowser.Model/Net/WebSocketMessage.cs | 8 +- 23 files changed, 316 insertions(+), 962 deletions(-) delete mode 100644 Emby.Server.Implementations/HttpServer/IHttpListener.cs delete mode 100644 Emby.Server.Implementations/Net/IWebSocket.cs delete mode 100644 Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs delete mode 100644 Emby.Server.Implementations/Session/HttpSessionController.cs delete mode 100644 Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs delete mode 100644 Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index c58f16438b..a215c9f4bf 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using Emby.Dlna.Didl; using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; @@ -899,7 +898,8 @@ namespace Emby.Dlna.PlayTo return 0; } - public Task SendMessage(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken) + /// + public Task SendMessage(string name, Guid messageId, T data, CancellationToken cancellationToken) { if (_disposed) { @@ -915,10 +915,12 @@ namespace Emby.Dlna.PlayTo { return SendPlayCommand(data as PlayRequest, cancellationToken); } + if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase)) { return SendPlaystateCommand(data as PlaystateRequest, cancellationToken); } + if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase)) { return SendGeneralCommand(data as GeneralCommand, cancellationToken); diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index 2ca44b7ea2..943d52b0d7 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -21,7 +21,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Dlna.PlayTo { - class PlayToManager : IDisposable + public class PlayToManager : IDisposable { private readonly ILogger _logger; private readonly ISessionManager _sessionManager; diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 0bb1d832fb..e3e071af95 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -45,7 +45,6 @@ using Emby.Server.Implementations.ScheduledTasks; using Emby.Server.Implementations.Security; using Emby.Server.Implementations.Serialization; using Emby.Server.Implementations.Session; -using Emby.Server.Implementations.SocketSharp; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; using MediaBrowser.Api; @@ -105,7 +104,6 @@ using MediaBrowser.Providers.TV.TheTVDB; using MediaBrowser.WebDashboard.Api; using MediaBrowser.XbmcMetadata.Providers; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -629,32 +627,8 @@ namespace Emby.Server.Implementations } } - public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) - { - if (!context.WebSockets.IsWebSocketRequest) - { - await next().ConfigureAwait(false); - return; - } - - await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false); - } - - public async Task ExecuteHttpHandlerAsync(HttpContext context, Func next) - { - if (context.WebSockets.IsWebSocketRequest) - { - await next().ConfigureAwait(false); - return; - } - - var request = context.Request; - var response = context.Response; - var localPath = context.Request.Path.ToString(); - - var req = new WebSocketSharpRequest(request, response, request.Path, Logger); - await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false); - } + public Task ExecuteHttpHandlerAsync(HttpContext context, Func next) + => HttpServer.RequestHandler(context); /// /// Registers resources that classes will depend on @@ -777,7 +751,7 @@ namespace Emby.Server.Implementations NetworkManager, JsonSerializer, XmlSerializer, - CreateHttpListener()) + LoggerFactory) { GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading") }; @@ -1194,8 +1168,6 @@ namespace Emby.Server.Implementations }); } - protected IHttpListener CreateHttpListener() => new WebSocketSharpListener(Logger); - private CertificateInfo GetCertificateInfo(bool generateCertificate) { // Custom cert diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index b0126f7fa5..4baf96ab51 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -7,11 +7,12 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Sockets; +using System.Net.WebSockets; using System.Reflection; using System.Threading; using System.Threading.Tasks; -using Emby.Server.Implementations.Net; using Emby.Server.Implementations.Services; +using Emby.Server.Implementations.SocketSharp; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -21,9 +22,11 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; using ServiceStack.Text.Jsv; namespace Emby.Server.Implementations.HttpServer @@ -31,18 +34,17 @@ namespace Emby.Server.Implementations.HttpServer public class HttpListenerHost : IHttpServer, IDisposable { private readonly ILogger _logger; + private readonly ILoggerFactory _loggerFactory; private readonly IServerConfigurationManager _config; private readonly INetworkManager _networkManager; private readonly IServerApplicationHost _appHost; private readonly IJsonSerializer _jsonSerializer; private readonly IXmlSerializer _xmlSerializer; - private readonly IHttpListener _socketListener; private readonly Func> _funcParseFn; private readonly string _defaultRedirectPath; private readonly string _baseUrlPrefix; private readonly Dictionary ServiceOperationsMap = new Dictionary(); private IWebSocketListener[] _webSocketListeners = Array.Empty(); - private readonly List _webSocketConnections = new List(); private bool _disposed = false; public HttpListenerHost( @@ -53,7 +55,7 @@ namespace Emby.Server.Implementations.HttpServer INetworkManager networkManager, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, - IHttpListener socketListener) + ILoggerFactory loggerFactory) { _appHost = applicationHost; _logger = logger; @@ -63,8 +65,7 @@ namespace Emby.Server.Implementations.HttpServer _networkManager = networkManager; _jsonSerializer = jsonSerializer; _xmlSerializer = xmlSerializer; - _socketListener = socketListener; - _socketListener.WebSocketConnected = OnWebSocketConnected; + _loggerFactory = loggerFactory; _funcParseFn = t => s => JsvReader.GetParseFn(t)(s); @@ -155,38 +156,6 @@ namespace Emby.Server.Implementations.HttpServer return attributes; } - private void OnWebSocketConnected(WebSocketConnectEventArgs e) - { - if (_disposed) - { - return; - } - - var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger) - { - OnReceive = ProcessWebSocketMessageReceived, - Url = e.Url, - QueryString = e.QueryString - }; - - connection.Closed += OnConnectionClosed; - - lock (_webSocketConnections) - { - _webSocketConnections.Add(connection); - } - - WebSocketConnected?.Invoke(this, new GenericEventArgs(connection)); - } - - private void OnConnectionClosed(object sender, EventArgs e) - { - lock (_webSocketConnections) - { - _webSocketConnections.Remove((IWebSocketConnection)sender); - } - } - private static Exception GetActualException(Exception ex) { if (ex is AggregateException agg) @@ -274,32 +243,6 @@ namespace Emby.Server.Implementations.HttpServer return msg; } - /// - /// Shut down the Web Service - /// - public void Stop() - { - List connections; - - lock (_webSocketConnections) - { - connections = _webSocketConnections.ToList(); - _webSocketConnections.Clear(); - } - - foreach (var connection in connections) - { - try - { - connection.Dispose(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error disposing connection"); - } - } - } - public static string RemoveQueryStringByKey(string url, string key) { var uri = new Uri(url); @@ -411,31 +354,47 @@ namespace Emby.Server.Implementations.HttpServer private bool ValidateSsl(string remoteIp, string urlString) { - if (_config.Configuration.RequireHttps && _appHost.EnableHttps && !_config.Configuration.IsBehindProxy) + if (_config.Configuration.RequireHttps + && _appHost.EnableHttps + && !_config.Configuration.IsBehindProxy + && !urlString.Contains("https://", StringComparison.OrdinalIgnoreCase)) { - if (urlString.IndexOf("https://", StringComparison.OrdinalIgnoreCase) == -1) + // These are hacks, but if these ever occur on ipv6 in the local network they could be incorrectly redirected + if (urlString.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) != -1 + || urlString.IndexOf("dlna/", StringComparison.OrdinalIgnoreCase) != -1) { - // These are hacks, but if these ever occur on ipv6 in the local network they could be incorrectly redirected - if (urlString.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) != -1 - || urlString.IndexOf("dlna/", StringComparison.OrdinalIgnoreCase) != -1) - { - return true; - } + return true; + } - if (!_networkManager.IsInLocalNetwork(remoteIp)) - { - return false; - } + if (!_networkManager.IsInLocalNetwork(remoteIp)) + { + return false; } } return true; } + /// + public Task RequestHandler(HttpContext context) + { + if (context.WebSockets.IsWebSocketRequest) + { + return WebSocketRequestHandler(context); + } + + var request = context.Request; + var response = context.Response; + var localPath = context.Request.Path.ToString(); + + var req = new WebSocketSharpRequest(request, response, request.Path, _logger); + return RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted); + } + /// - /// Overridable method that can be used to implement a custom hnandler + /// Overridable method that can be used to implement a custom handler /// - public async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken) + private async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken) { var stopWatch = new Stopwatch(); stopWatch.Start(); @@ -552,6 +511,44 @@ namespace Emby.Server.Implementations.HttpServer } } + private async Task WebSocketRequestHandler(HttpContext context) + { + if (_disposed) + { + return; + } + + var url = context.Request.GetDisplayUrl(); + _logger.LogInformation("WS {Url}. UserAgent: {UserAgent}", url, context.Request.Headers[HeaderNames.UserAgent].ToString()); + + try + { + var webSocket = await context.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); + + var connection = new WebSocketConnection( + _loggerFactory.CreateLogger(), + webSocket, + context.Connection.RemoteIpAddress) + { + Url = url, + QueryString = context.Request.Query, + OnReceive = ProcessWebSocketMessageReceived + }; + + WebSocketConnected?.Invoke(this, new GenericEventArgs(connection)); + + await connection.ProcessAsync().ConfigureAwait(false); + } + catch (WebSocketException ex) + { + _logger.LogError(ex, "ProcessWebSocketRequest error"); + if (!context.Response.HasStarted) + { + context.Response.StatusCode = 500; + } + } + } + // Entry point for HttpListener public ServiceHandler GetServiceHandler(IHttpRequest httpReq) { @@ -661,11 +658,6 @@ namespace Emby.Server.Implementations.HttpServer return _jsonSerializer.DeserializeFromStreamAsync(stream, type); } - public Task ProcessWebSocketRequest(HttpContext context) - { - return _socketListener.ProcessWebSocketRequest(context); - } - private string NormalizeEmbyRoutePath(string path) { _logger.LogDebug("Normalizing /emby route"); @@ -697,7 +689,7 @@ namespace Emby.Server.Implementations.HttpServer if (disposing) { - Stop(); + // TODO: } _disposed = true; diff --git a/Emby.Server.Implementations/HttpServer/IHttpListener.cs b/Emby.Server.Implementations/HttpServer/IHttpListener.cs deleted file mode 100644 index 1c3496e5d5..0000000000 --- a/Emby.Server.Implementations/HttpServer/IHttpListener.cs +++ /dev/null @@ -1,40 +0,0 @@ -#pragma warning disable CS1591 -#pragma warning disable SA1600 - -using System; -using System.Threading; -using System.Threading.Tasks; -using Emby.Server.Implementations.Net; -using MediaBrowser.Model.Services; -using Microsoft.AspNetCore.Http; - -namespace Emby.Server.Implementations.HttpServer -{ - public interface IHttpListener : IDisposable - { - /// - /// Gets or sets the error handler. - /// - /// The error handler. - Func ErrorHandler { get; set; } - - /// - /// Gets or sets the request handler. - /// - /// The request handler. - Func RequestHandler { get; set; } - - /// - /// Gets or sets the web socket handler. - /// - /// The web socket handler. - Action WebSocketConnected { get; set; } - - /// - /// Stops this instance. - /// - Task Stop(); - - Task ProcessWebSocketRequest(HttpContext ctx); - } -} diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 2292d86a4a..b4f420e5d2 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -1,15 +1,16 @@ using System; +using System.Buffers; +using System.IO.Pipelines; +using System.Net; using System.Net.WebSockets; -using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using Emby.Server.Implementations.Net; +using MediaBrowser.Common.Json; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Net; -using MediaBrowser.Model.Serialization; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -using UtfUnknown; namespace Emby.Server.Implementations.HttpServer { @@ -24,54 +25,46 @@ namespace Emby.Server.Implementations.HttpServer private readonly ILogger _logger; /// - /// The json serializer. + /// The json serializer options. /// - private readonly IJsonSerializer _jsonSerializer; + private readonly JsonSerializerOptions _jsonOptions; /// /// The socket. /// - private readonly IWebSocket _socket; + private readonly WebSocket _socket; + + private bool _disposed = false; /// /// Initializes a new instance of the class. /// /// The socket. /// The remote end point. - /// The json serializer. /// The logger. /// socket - public WebSocketConnection(IWebSocket socket, string remoteEndPoint, IJsonSerializer jsonSerializer, ILogger logger) + public WebSocketConnection(ILogger logger, WebSocket socket, IPAddress remoteEndPoint) { if (socket == null) { throw new ArgumentNullException(nameof(socket)); } - if (string.IsNullOrEmpty(remoteEndPoint)) + if (remoteEndPoint != null) { throw new ArgumentNullException(nameof(remoteEndPoint)); } - if (jsonSerializer == null) - { - throw new ArgumentNullException(nameof(jsonSerializer)); - } - if (logger == null) { throw new ArgumentNullException(nameof(logger)); } - Id = Guid.NewGuid(); - _jsonSerializer = jsonSerializer; _socket = socket; - _socket.OnReceiveBytes = OnReceiveInternal; - RemoteEndPoint = remoteEndPoint; _logger = logger; - socket.Closed += OnSocketClosed; + _jsonOptions = JsonDefaults.GetOptions(); } /// @@ -80,7 +73,7 @@ namespace Emby.Server.Implementations.HttpServer /// /// Gets or sets the remote end point. /// - public string RemoteEndPoint { get; private set; } + public IPAddress RemoteEndPoint { get; private set; } /// /// Gets or sets the receive action. @@ -94,12 +87,6 @@ namespace Emby.Server.Implementations.HttpServer /// The last activity date. public DateTime LastActivityDate { get; private set; } - /// - /// Gets the id. - /// - /// The id. - public Guid Id { get; private set; } - /// /// Gets or sets the URL. /// @@ -118,46 +105,78 @@ namespace Emby.Server.Implementations.HttpServer /// The state. public WebSocketState State => _socket.State; - void OnSocketClosed(object sender, EventArgs e) - { - Closed?.Invoke(this, EventArgs.Empty); - } - /// - /// Called when [receive]. + /// Sends a message asynchronously. /// - /// The bytes. - private void OnReceiveInternal(byte[] bytes) + /// + /// The message. + /// The cancellation token. + /// Task. + /// message + public Task SendAsync(WebSocketMessage message, CancellationToken cancellationToken) { - LastActivityDate = DateTime.UtcNow; - - if (OnReceive == null) + if (message == null) { - return; + throw new ArgumentNullException(nameof(message)); } - var charset = CharsetDetector.DetectFromBytes(bytes).Detected?.EncodingName; - if (string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase)) + var json = JsonSerializer.SerializeToUtf8Bytes(message, _jsonOptions); + return _socket.SendAsync(json, WebSocketMessageType.Text, true, cancellationToken); + } + + /// + public async Task ProcessAsync(CancellationToken cancellationToken = default) + { + var pipe = new Pipe(); + var writer = pipe.Writer; + + ValueWebSocketReceiveResult receiveresult; + do { - OnReceiveInternal(Encoding.UTF8.GetString(bytes, 0, bytes.Length)); - } - else + // Allocate at least 512 bytes from the PipeWriter + Memory memory = writer.GetMemory(512); + + receiveresult = await _socket.ReceiveAsync(memory, cancellationToken); + int bytesRead = receiveresult.Count; + if (bytesRead == 0) + { + continue; + } + + // Tell the PipeWriter how much was read from the Socket + writer.Advance(bytesRead); + + // Make the data available to the PipeReader + FlushResult flushResult = await writer.FlushAsync(); + if (flushResult.IsCompleted) + { + // The PipeReader stopped reading + break; + } + + if (receiveresult.EndOfMessage) + { + await ProcessInternal(pipe.Reader).ConfigureAwait(false); + } + } while (_socket.State == WebSocketState.Open && receiveresult.MessageType != WebSocketMessageType.Close); + + if (_socket.State == WebSocketState.Open) { - OnReceiveInternal(Encoding.ASCII.GetString(bytes, 0, bytes.Length)); + await _socket.CloseAsync( + WebSocketCloseStatus.NormalClosure, + string.Empty, // REVIEW: human readable explanation as to why the connection is closed. + cancellationToken).ConfigureAwait(false); } + + Closed?.Invoke(this, EventArgs.Empty); + + _socket.Dispose(); } - private void OnReceiveInternal(string message) + private async Task ProcessInternal(PipeReader reader) { LastActivityDate = DateTime.UtcNow; - if (!message.StartsWith("{", StringComparison.OrdinalIgnoreCase)) - { - // This info is useful sometimes but also clogs up the log - _logger.LogDebug("Received web socket message that is not a json structure: {message}", message); - return; - } - if (OnReceive == null) { return; @@ -165,74 +184,47 @@ namespace Emby.Server.Implementations.HttpServer try { - var stub = (WebSocketMessage)_jsonSerializer.DeserializeFromString(message, typeof(WebSocketMessage)); + var result = await reader.ReadAsync().ConfigureAwait(false); + if (!result.IsCompleted) + { + return; + } + + WebSocketMessage stub; + var buffer = result.Buffer; + if (buffer.IsSingleSegment) + { + stub = JsonSerializer.Deserialize>(buffer.FirstSpan, _jsonOptions); + } + else + { + var buf = ArrayPool.Shared.Rent(Convert.ToInt32(buffer.Length)); + try + { + buffer.CopyTo(buf); + stub = JsonSerializer.Deserialize>(buf, _jsonOptions); + } + finally + { + ArrayPool.Shared.Return(buf); + } + } var info = new WebSocketMessageInfo { MessageType = stub.MessageType, - Data = stub.Data?.ToString(), + Data = stub.Data.ToString(), Connection = this }; - OnReceive(info); + await OnReceive(info).ConfigureAwait(false); } - catch (Exception ex) + catch (JsonException ex) { _logger.LogError(ex, "Error processing web socket message"); } } - /// - /// Sends a message asynchronously. - /// - /// - /// The message. - /// The cancellation token. - /// Task. - /// message - public Task SendAsync(WebSocketMessage message, CancellationToken cancellationToken) - { - if (message == null) - { - throw new ArgumentNullException(nameof(message)); - } - - var json = _jsonSerializer.SerializeToString(message); - - return SendAsync(json, cancellationToken); - } - - /// - /// Sends a message asynchronously. - /// - /// The buffer. - /// The cancellation token. - /// Task. - public Task SendAsync(byte[] buffer, CancellationToken cancellationToken) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - cancellationToken.ThrowIfCancellationRequested(); - - return _socket.SendAsync(buffer, true, cancellationToken); - } - - /// - public Task SendAsync(string text, CancellationToken cancellationToken) - { - if (string.IsNullOrEmpty(text)) - { - throw new ArgumentNullException(nameof(text)); - } - - cancellationToken.ThrowIfCancellationRequested(); - - return _socket.SendAsync(text, true, cancellationToken); - } - /// public void Dispose() { @@ -246,10 +238,17 @@ namespace Emby.Server.Implementations.HttpServer /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool dispose) { + if (_disposed) + { + return; + } + if (dispose) { _socket.Dispose(); } + + _disposed = true; } } } diff --git a/Emby.Server.Implementations/Net/IWebSocket.cs b/Emby.Server.Implementations/Net/IWebSocket.cs deleted file mode 100644 index 4d160aa66f..0000000000 --- a/Emby.Server.Implementations/Net/IWebSocket.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; - -namespace Emby.Server.Implementations.Net -{ - /// - /// Interface IWebSocket - /// - public interface IWebSocket : IDisposable - { - /// - /// Occurs when [closed]. - /// - event EventHandler Closed; - - /// - /// Gets or sets the state. - /// - /// The state. - WebSocketState State { get; } - - /// - /// Gets or sets the receive action. - /// - /// The receive action. - Action OnReceiveBytes { get; set; } - - /// - /// Sends the async. - /// - /// The bytes. - /// if set to true [end of message]. - /// The cancellation token. - /// Task. - Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken); - - /// - /// Sends the asynchronous. - /// - /// The text. - /// if set to true [end of message]. - /// The cancellation token. - /// Task. - Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken); - } -} diff --git a/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs b/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs deleted file mode 100644 index e3047d3926..0000000000 --- a/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Net.WebSockets; -using MediaBrowser.Model.Services; -using Microsoft.AspNetCore.Http; - -namespace Emby.Server.Implementations.Net -{ - public class WebSocketConnectEventArgs : EventArgs - { - /// - /// Gets or sets the URL. - /// - /// The URL. - public string Url { get; set; } - /// - /// Gets or sets the query string. - /// - /// The query string. - public IQueryCollection QueryString { get; set; } - /// - /// Gets or sets the web socket. - /// - /// The web socket. - public IWebSocket WebSocket { get; set; } - /// - /// Gets or sets the endpoint. - /// - /// The endpoint. - public string Endpoint { get; set; } - } -} diff --git a/Emby.Server.Implementations/Session/HttpSessionController.cs b/Emby.Server.Implementations/Session/HttpSessionController.cs deleted file mode 100644 index dfb81816c7..0000000000 --- a/Emby.Server.Implementations/Session/HttpSessionController.cs +++ /dev/null @@ -1,191 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Session; - -namespace Emby.Server.Implementations.Session -{ - public class HttpSessionController : ISessionController - { - private readonly IHttpClient _httpClient; - private readonly IJsonSerializer _json; - private readonly ISessionManager _sessionManager; - - public SessionInfo Session { get; private set; } - - private readonly string _postUrl; - - public HttpSessionController(IHttpClient httpClient, - IJsonSerializer json, - SessionInfo session, - string postUrl, ISessionManager sessionManager) - { - _httpClient = httpClient; - _json = json; - Session = session; - _postUrl = postUrl; - _sessionManager = sessionManager; - } - - private string PostUrl => string.Format("http://{0}{1}", Session.RemoteEndPoint, _postUrl); - - public bool IsSessionActive => (DateTime.UtcNow - Session.LastActivityDate).TotalMinutes <= 5; - - public bool SupportsMediaControl => true; - - private Task SendMessage(string name, string messageId, CancellationToken cancellationToken) - { - return SendMessage(name, messageId, new Dictionary(), cancellationToken); - } - - private Task SendMessage(string name, string messageId, Dictionary args, CancellationToken cancellationToken) - { - args["messageId"] = messageId; - var url = PostUrl + "/" + name + ToQueryString(args); - - return SendRequest(new HttpRequestOptions - { - Url = url, - CancellationToken = cancellationToken, - BufferContent = false - }); - } - - private Task SendPlayCommand(PlayRequest command, string messageId, CancellationToken cancellationToken) - { - var dict = new Dictionary(); - - dict["ItemIds"] = string.Join(",", command.ItemIds.Select(i => i.ToString("N", CultureInfo.InvariantCulture)).ToArray()); - - if (command.StartPositionTicks.HasValue) - { - dict["StartPositionTicks"] = command.StartPositionTicks.Value.ToString(CultureInfo.InvariantCulture); - } - if (command.AudioStreamIndex.HasValue) - { - dict["AudioStreamIndex"] = command.AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture); - } - if (command.SubtitleStreamIndex.HasValue) - { - dict["SubtitleStreamIndex"] = command.SubtitleStreamIndex.Value.ToString(CultureInfo.InvariantCulture); - } - if (command.StartIndex.HasValue) - { - dict["StartIndex"] = command.StartIndex.Value.ToString(CultureInfo.InvariantCulture); - } - if (!string.IsNullOrEmpty(command.MediaSourceId)) - { - dict["MediaSourceId"] = command.MediaSourceId; - } - - return SendMessage(command.PlayCommand.ToString(), messageId, dict, cancellationToken); - } - - private Task SendPlaystateCommand(PlaystateRequest command, string messageId, CancellationToken cancellationToken) - { - var args = new Dictionary(); - - if (command.Command == PlaystateCommand.Seek) - { - if (!command.SeekPositionTicks.HasValue) - { - throw new ArgumentException("SeekPositionTicks cannot be null"); - } - - args["SeekPositionTicks"] = command.SeekPositionTicks.Value.ToString(CultureInfo.InvariantCulture); - } - - return SendMessage(command.Command.ToString(), messageId, args, cancellationToken); - } - - private string[] _supportedMessages = Array.Empty(); - public Task SendMessage(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken) - { - if (!IsSessionActive) - { - return Task.CompletedTask; - } - - if (string.Equals(name, "Play", StringComparison.OrdinalIgnoreCase)) - { - return SendPlayCommand(data as PlayRequest, messageId, cancellationToken); - } - if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase)) - { - return SendPlaystateCommand(data as PlaystateRequest, messageId, cancellationToken); - } - if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase)) - { - var command = data as GeneralCommand; - return SendMessage(command.Name, messageId, command.Arguments, cancellationToken); - } - - if (!_supportedMessages.Contains(name, StringComparer.OrdinalIgnoreCase)) - { - return Task.CompletedTask; - } - - var url = PostUrl + "/" + name; - - url += "?messageId=" + messageId; - - var options = new HttpRequestOptions - { - Url = url, - CancellationToken = cancellationToken, - BufferContent = false - }; - - if (data != null) - { - if (typeof(T) == typeof(string)) - { - var str = data as string; - if (!string.IsNullOrEmpty(str)) - { - options.RequestContent = str; - options.RequestContentType = "application/json"; - } - } - else - { - options.RequestContent = _json.SerializeToString(data); - options.RequestContentType = "application/json"; - } - } - - return SendRequest(options); - } - - private async Task SendRequest(HttpRequestOptions options) - { - using (var response = await _httpClient.Post(options).ConfigureAwait(false)) - { - - } - } - - private static string ToQueryString(Dictionary nvc) - { - var array = (from item in nvc - select string.Format("{0}={1}", WebUtility.UrlEncode(item.Key), WebUtility.UrlEncode(item.Value))) - .ToArray(); - - var args = string.Join("&", array); - - if (string.IsNullOrEmpty(args)) - { - return args; - } - - return "?" + args; - } - } -} diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index b1d513dd4f..db00ceeb71 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -463,8 +463,7 @@ namespace Emby.Server.Implementations.Session Client = appName, DeviceId = deviceId, ApplicationVersion = appVersion, - Id = key.GetMD5().ToString("N", CultureInfo.InvariantCulture), - ServerId = _appHost.SystemId + Id = key.GetMD5().ToString("N", CultureInfo.InvariantCulture) }; var username = user?.Name; @@ -1024,12 +1023,12 @@ namespace Emby.Server.Implementations.Session private static async Task SendMessageToSession(SessionInfo session, string name, T data, CancellationToken cancellationToken) { - var controllers = session.SessionControllers.ToArray(); - var messageId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); + var controllers = session.SessionControllers; + var messageId = Guid.NewGuid(); foreach (var controller in controllers) { - await controller.SendMessage(name, messageId, data, controllers, cancellationToken).ConfigureAwait(false); + await controller.SendMessage(name, messageId, data, cancellationToken).ConfigureAwait(false); } } @@ -1037,13 +1036,13 @@ namespace Emby.Server.Implementations.Session { IEnumerable GetTasks() { - var messageId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); + var messageId = Guid.NewGuid(); foreach (var session in sessions) { var controllers = session.SessionControllers; foreach (var controller in controllers) { - yield return controller.SendMessage(name, messageId, data, controllers, cancellationToken); + yield return controller.SendMessage(name, messageId, data, cancellationToken); } } } diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 930f2d35d3..13b42698d3 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Events; -using MediaBrowser.Model.Serialization; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -12,7 +11,7 @@ namespace Emby.Server.Implementations.Session /// /// Class SessionWebSocketListener /// - public class SessionWebSocketListener : IWebSocketListener, IDisposable + public sealed class SessionWebSocketListener : IWebSocketListener, IDisposable { /// /// The _session manager @@ -23,35 +22,34 @@ namespace Emby.Server.Implementations.Session /// The _logger /// private readonly ILogger _logger; - - /// - /// The _dto service - /// - private readonly IJsonSerializer _json; + private readonly ILoggerFactory _loggerFactory; private readonly IHttpServer _httpServer; - /// /// Initializes a new instance of the class. /// + /// The logger. /// The session manager. /// The logger factory. - /// The json. /// The HTTP server. - public SessionWebSocketListener(ISessionManager sessionManager, ILoggerFactory loggerFactory, IJsonSerializer json, IHttpServer httpServer) + public SessionWebSocketListener( + ILogger logger, + ISessionManager sessionManager, + ILoggerFactory loggerFactory, + IHttpServer httpServer) { + _logger = logger; _sessionManager = sessionManager; - _logger = loggerFactory.CreateLogger(GetType().Name); - _json = json; + _loggerFactory = loggerFactory; _httpServer = httpServer; - httpServer.WebSocketConnected += _serverManager_WebSocketConnected; + + httpServer.WebSocketConnected += OnServerManagerWebSocketConnected; } - void _serverManager_WebSocketConnected(object sender, GenericEventArgs e) + private void OnServerManagerWebSocketConnected(object sender, GenericEventArgs e) { - var session = GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint); - + var session = GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint.ToString()); if (session != null) { EnsureController(session, e.Argument); @@ -79,9 +77,10 @@ namespace Emby.Server.Implementations.Session return _sessionManager.GetSessionByAuthenticationToken(token, deviceId, remoteEndpoint); } + /// public void Dispose() { - _httpServer.WebSocketConnected -= _serverManager_WebSocketConnected; + _httpServer.WebSocketConnected -= OnServerManagerWebSocketConnected; } /// @@ -94,7 +93,8 @@ namespace Emby.Server.Implementations.Session private void EnsureController(SessionInfo session, IWebSocketConnection connection) { - var controllerInfo = session.EnsureController(s => new WebSocketController(s, _logger, _sessionManager)); + var controllerInfo = session.EnsureController( + s => new WebSocketController(_loggerFactory.CreateLogger(), s, _sessionManager)); var controller = (WebSocketController)controllerInfo.Item1; controller.AddWebSocket(connection); diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index 0d483c55fa..c17e67da93 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -11,60 +11,64 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Session { - public class WebSocketController : ISessionController, IDisposable + public sealed class WebSocketController : ISessionController, IDisposable { - public SessionInfo Session { get; private set; } - public IReadOnlyList Sockets { get; private set; } - private readonly ILogger _logger; - private readonly ISessionManager _sessionManager; + private readonly SessionInfo _session; - public WebSocketController(SessionInfo session, ILogger logger, ISessionManager sessionManager) + private List _sockets; + private bool _disposed = false; + + public WebSocketController( + ILogger logger, + SessionInfo session, + ISessionManager sessionManager) { - Session = session; _logger = logger; + _session = session; _sessionManager = sessionManager; - Sockets = new List(); + _sockets = new List(); } private bool HasOpenSockets => GetActiveSockets().Any(); + /// public bool SupportsMediaControl => HasOpenSockets; + /// public bool IsSessionActive => HasOpenSockets; private IEnumerable GetActiveSockets() - { - return Sockets - .OrderByDescending(i => i.LastActivityDate) - .Where(i => i.State == WebSocketState.Open); - } + => _sockets.Where(i => i.State == WebSocketState.Open); + /// public void AddWebSocket(IWebSocketConnection connection) { - var sockets = Sockets.ToList(); - sockets.Add(connection); + _logger.LogDebug("Adding websocket to session {Session}", _session.Id); + _sockets.Add(connection); - Sockets = sockets; - - connection.Closed += connection_Closed; + connection.Closed += OnConnectionClosed; } - void connection_Closed(object sender, EventArgs e) + private void OnConnectionClosed(object sender, EventArgs e) { + _logger.LogDebug("Removing websocket from session {Session}", _session.Id); var connection = (IWebSocketConnection)sender; - var sockets = Sockets.ToList(); - sockets.Remove(connection); - - Sockets = sockets; - - _sessionManager.CloseIfNeeded(Session); + _sockets.Remove(connection); + _sessionManager.CloseIfNeeded(_session); + connection.Dispose(); } - public Task SendMessage(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken) + /// + public Task SendMessage( + string name, + Guid messageId, + T data, + CancellationToken cancellationToken) { var socket = GetActiveSockets() + .OrderByDescending(i => i.LastActivityDate) .FirstOrDefault(); if (socket == null) @@ -77,16 +81,24 @@ namespace Emby.Server.Implementations.Session Data = data, MessageType = name, MessageId = messageId - }, cancellationToken); } + /// public void Dispose() { - foreach (var socket in Sockets.ToList()) + if (_disposed) { - socket.Closed -= connection_Closed; + return; } + + foreach (var socket in _sockets) + { + socket.Closed -= OnConnectionClosed; + socket.Dispose(); + } + + _disposed = true; } } } diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs deleted file mode 100644 index 67521d6c63..0000000000 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Emby.Server.Implementations.Net; -using Microsoft.Extensions.Logging; - -namespace Emby.Server.Implementations.SocketSharp -{ - public class SharpWebSocket : IWebSocket - { - /// - /// The logger - /// - private readonly ILogger _logger; - - public event EventHandler Closed; - - /// - /// Gets or sets the web socket. - /// - /// The web socket. - private readonly WebSocket _webSocket; - - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - private bool _disposed; - - public SharpWebSocket(WebSocket socket, ILogger logger) - { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _webSocket = socket ?? throw new ArgumentNullException(nameof(socket)); - } - - /// - /// Gets the state. - /// - /// The state. - public WebSocketState State => _webSocket.State; - - /// - /// Sends the async. - /// - /// The bytes. - /// if set to true [end of message]. - /// The cancellation token. - /// Task. - public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken) - { - return _webSocket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Binary, endOfMessage, cancellationToken); - } - - /// - /// Sends the asynchronous. - /// - /// The text. - /// if set to true [end of message]. - /// The cancellation token. - /// Task. - public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken) - { - return _webSocket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(text)), WebSocketMessageType.Text, endOfMessage, cancellationToken); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) - { - if (_disposed) - { - return; - } - - if (dispose) - { - _cancellationTokenSource.Cancel(); - if (_webSocket.State == WebSocketState.Open) - { - _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closed by client", - CancellationToken.None); - } - Closed?.Invoke(this, EventArgs.Empty); - } - - _disposed = true; - } - - /// - /// Gets or sets the receive action. - /// - /// The receive action. - public Action OnReceiveBytes { get; set; } - } -} diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs deleted file mode 100644 index ba5ba1904c..0000000000 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; -using Emby.Server.Implementations.HttpServer; -using Emby.Server.Implementations.Net; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Services; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; -using Microsoft.Extensions.Logging; -using Microsoft.Net.Http.Headers; - -namespace Emby.Server.Implementations.SocketSharp -{ - public class WebSocketSharpListener : IHttpListener - { - private readonly ILogger _logger; - - private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); - private CancellationToken _disposeCancellationToken; - - public WebSocketSharpListener( - ILogger logger) - { - _logger = logger; - - _disposeCancellationToken = _disposeCancellationTokenSource.Token; - } - - public Func ErrorHandler { get; set; } - public Func RequestHandler { get; set; } - - public Action WebSocketConnected { get; set; } - - private static void LogRequest(ILogger logger, HttpRequest request) - { - var url = request.GetDisplayUrl(); - - logger.LogInformation("WS {Url}. UserAgent: {UserAgent}", url, request.Headers[HeaderNames.UserAgent].ToString()); - } - - public async Task ProcessWebSocketRequest(HttpContext ctx) - { - try - { - LogRequest(_logger, ctx.Request); - var endpoint = ctx.Connection.RemoteIpAddress.ToString(); - var url = ctx.Request.GetDisplayUrl(); - - var webSocketContext = await ctx.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); - var socket = new SharpWebSocket(webSocketContext, _logger); - - WebSocketConnected(new WebSocketConnectEventArgs - { - Url = url, - QueryString = ctx.Request.Query, - WebSocket = socket, - Endpoint = endpoint - }); - - WebSocketReceiveResult result; - var message = new List(); - - do - { - var buffer = WebSocket.CreateServerBuffer(4096); - result = await webSocketContext.ReceiveAsync(buffer, _disposeCancellationToken); - message.AddRange(buffer.Array.Take(result.Count)); - - if (result.EndOfMessage) - { - socket.OnReceiveBytes(message.ToArray()); - message.Clear(); - } - } while (socket.State == WebSocketState.Open && result.MessageType != WebSocketMessageType.Close); - - - if (webSocketContext.State == WebSocketState.Open) - { - await webSocketContext.CloseAsync( - result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, - result.CloseStatusDescription, - _disposeCancellationToken).ConfigureAwait(false); - } - - socket.Dispose(); - } - catch (Exception ex) - { - _logger.LogError(ex, "AcceptWebSocketAsync error"); - if (!ctx.Response.HasStarted) - { - ctx.Response.StatusCode = 500; - } - } - } - - public Task Stop() - { - _disposeCancellationTokenSource.Cancel(); - return Task.CompletedTask; - } - - /// - /// Releases the unmanaged resources and disposes of the managed resources used. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private bool _disposed; - - /// - /// Releases the unmanaged resources and disposes of the managed resources used. - /// - /// Whether or not the managed resources should be disposed. - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (disposing) - { - Stop().GetAwaiter().GetResult(); - } - - _disposed = true; - } - } -} diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs index 3ee5fb8b50..45f2b9d7c2 100644 --- a/Jellyfin.Server/Startup.cs +++ b/Jellyfin.Server/Startup.cs @@ -3,7 +3,6 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -64,7 +63,6 @@ namespace Jellyfin.Server app.UseResponseCompression(); // TODO app.UseMiddleware(); - app.Use(serverApplicationHost.ExecuteWebsocketHandlerAsync); // TODO use when old API is removed: app.UseAuthentication(); app.UseJellyfinApiSwagger(); diff --git a/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs index f1a6622fb5..cf7ddd6319 100644 --- a/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs +++ b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs @@ -31,46 +31,46 @@ namespace MediaBrowser.Api.Session { _sessionManager = sessionManager; - _sessionManager.SessionStarted += _sessionManager_SessionStarted; - _sessionManager.SessionEnded += _sessionManager_SessionEnded; - _sessionManager.PlaybackStart += _sessionManager_PlaybackStart; - _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped; - _sessionManager.PlaybackProgress += _sessionManager_PlaybackProgress; - _sessionManager.CapabilitiesChanged += _sessionManager_CapabilitiesChanged; - _sessionManager.SessionActivity += _sessionManager_SessionActivity; + _sessionManager.SessionStarted += OnSessionManagerSessionStarted; + _sessionManager.SessionEnded += OnSessionManagerSessionEnded; + _sessionManager.PlaybackStart += OnSessionManagerPlaybackStart; + _sessionManager.PlaybackStopped += OnSessionManagerPlaybackStopped; + _sessionManager.PlaybackProgress += OnSessionManagerPlaybackProgress; + _sessionManager.CapabilitiesChanged += OnSessionManagerCapabilitiesChanged; + _sessionManager.SessionActivity += OnSessionManagerSessionActivity; } - void _sessionManager_SessionActivity(object sender, SessionEventArgs e) + private void OnSessionManagerSessionActivity(object sender, SessionEventArgs e) { SendData(false); } - void _sessionManager_CapabilitiesChanged(object sender, SessionEventArgs e) + private void OnSessionManagerCapabilitiesChanged(object sender, SessionEventArgs e) { SendData(true); } - void _sessionManager_PlaybackProgress(object sender, PlaybackProgressEventArgs e) + private void OnSessionManagerPlaybackProgress(object sender, PlaybackProgressEventArgs e) { SendData(!e.IsAutomated); } - void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) + private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e) { SendData(true); } - void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e) + private void OnSessionManagerPlaybackStart(object sender, PlaybackProgressEventArgs e) { SendData(true); } - void _sessionManager_SessionEnded(object sender, SessionEventArgs e) + private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e) { SendData(true); } - void _sessionManager_SessionStarted(object sender, SessionEventArgs e) + private void OnSessionManagerSessionStarted(object sender, SessionEventArgs e) { SendData(true); } @@ -84,15 +84,16 @@ namespace MediaBrowser.Api.Session return Task.FromResult(_sessionManager.Sessions); } + /// protected override void Dispose(bool dispose) { - _sessionManager.SessionStarted -= _sessionManager_SessionStarted; - _sessionManager.SessionEnded -= _sessionManager_SessionEnded; - _sessionManager.PlaybackStart -= _sessionManager_PlaybackStart; - _sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped; - _sessionManager.PlaybackProgress -= _sessionManager_PlaybackProgress; - _sessionManager.CapabilitiesChanged -= _sessionManager_CapabilitiesChanged; - _sessionManager.SessionActivity -= _sessionManager_SessionActivity; + _sessionManager.SessionStarted -= OnSessionManagerSessionStarted; + _sessionManager.SessionEnded -= OnSessionManagerSessionEnded; + _sessionManager.PlaybackStart -= OnSessionManagerPlaybackStart; + _sessionManager.PlaybackStopped -= OnSessionManagerPlaybackStopped; + _sessionManager.PlaybackProgress -= OnSessionManagerPlaybackProgress; + _sessionManager.CapabilitiesChanged -= OnSessionManagerCapabilitiesChanged; + _sessionManager.SessionActivity -= OnSessionManagerSessionActivity; base.Dispose(dispose); } diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 25f0905eb8..0790b6cd60 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -92,7 +92,5 @@ namespace MediaBrowser.Controller string ReverseVirtualPath(string path); Task ExecuteHttpHandlerAsync(HttpContext context, Func next); - - Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next); } } diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index ee5c1a165a..9d71426d88 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -154,7 +154,6 @@ namespace MediaBrowser.Controller.Net { MessageType = Name, Data = data - }, cancellationToken).ConfigureAwait(false); state.DateLastSendUtc = DateTime.UtcNow; diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs index 46933c0465..694e3954ed 100644 --- a/MediaBrowser.Controller/Net/IHttpServer.cs +++ b/MediaBrowser.Controller/Net/IHttpServer.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Events; using MediaBrowser.Model.Services; @@ -19,11 +18,6 @@ namespace MediaBrowser.Controller.Net /// The URL prefix. string[] UrlPrefixes { get; } - /// - /// Stops this instance. - /// - void Stop(); - /// /// Occurs when [web socket connected]. /// @@ -39,23 +33,11 @@ namespace MediaBrowser.Controller.Net /// string GlobalResponse { get; set; } - /// - /// Sends the http context to the socket listener - /// - /// - /// - Task ProcessWebSocketRequest(HttpContext ctx); - /// /// The HTTP request handler /// - /// - /// - /// - /// - /// + /// /// - Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, - CancellationToken cancellationToken); + Task RequestHandler(HttpContext context); } } diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs index 566897b31f..e2a714d5b1 100644 --- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs +++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs @@ -1,9 +1,9 @@ using System; +using System.Net; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Net; -using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net @@ -15,12 +15,6 @@ namespace MediaBrowser.Controller.Net /// event EventHandler Closed; - /// - /// Gets the id. - /// - /// The id. - Guid Id { get; } - /// /// Gets the last activity date. /// @@ -32,6 +26,7 @@ namespace MediaBrowser.Controller.Net /// /// The URL. string Url { get; set; } + /// /// Gets or sets the query string. /// @@ -54,7 +49,7 @@ namespace MediaBrowser.Controller.Net /// Gets the remote end point. /// /// The remote end point. - string RemoteEndPoint { get; } + IPAddress RemoteEndPoint { get; } /// /// Sends a message asynchronously. @@ -66,21 +61,6 @@ namespace MediaBrowser.Controller.Net /// message Task SendAsync(WebSocketMessage message, CancellationToken cancellationToken); - /// - /// Sends a message asynchronously. - /// - /// The buffer. - /// The cancellation token. - /// Task. - Task SendAsync(byte[] buffer, CancellationToken cancellationToken); - - /// - /// Sends a message asynchronously. - /// - /// The text. - /// The cancellation token. - /// Task. - /// buffer - Task SendAsync(string text, CancellationToken cancellationToken); + Task ProcessAsync(CancellationToken cancellationToken = default); } } diff --git a/MediaBrowser.Controller/Session/ISessionController.cs b/MediaBrowser.Controller/Session/ISessionController.cs index a59c96ac70..04450085bf 100644 --- a/MediaBrowser.Controller/Session/ISessionController.cs +++ b/MediaBrowser.Controller/Session/ISessionController.cs @@ -1,3 +1,4 @@ +using System; using System.Threading; using System.Threading.Tasks; @@ -20,6 +21,6 @@ namespace MediaBrowser.Controller.Session /// /// Sends the message. /// - Task SendMessage(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken); + Task SendMessage(string name, Guid messageId, T data, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index acda6a4166..63ed43a833 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -10,13 +10,23 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Session { /// - /// Class SessionInfo + /// Class SessionInfo. /// - public class SessionInfo : IDisposable + public sealed class SessionInfo : IDisposable { - private ISessionManager _sessionManager; + // 1 second + private const long ProgressIncrement = 10000000; + + private readonly ISessionManager _sessionManager; private readonly ILogger _logger; + + private readonly object _progressLock = new object(); + private Timer _progressTimer; + private PlaybackProgressInfo _lastProgressInfo; + + private bool _disposed = false; + public SessionInfo(ISessionManager sessionManager, ILogger logger) { _sessionManager = sessionManager; @@ -97,8 +107,6 @@ namespace MediaBrowser.Controller.Session /// The name of the device. public string DeviceName { get; set; } - public string DeviceType { get; set; } - /// /// Gets or sets the now playing item. /// @@ -126,28 +134,6 @@ namespace MediaBrowser.Controller.Session [JsonIgnore] public ISessionController[] SessionControllers { get; set; } - /// - /// Gets or sets the application icon URL. - /// - /// The application icon URL. - public string AppIconUrl { get; set; } - - /// - /// Gets or sets the supported commands. - /// - /// The supported commands. - public string[] SupportedCommands - { - get - { - if (Capabilities == null) - { - return new string[] { }; - } - return Capabilities.SupportedCommands; - } - } - public TranscodingInfo TranscodingInfo { get; set; } /// @@ -219,6 +205,14 @@ namespace MediaBrowser.Controller.Session } } + public QueueItem[] NowPlayingQueue { get; set; } + + public bool HasCustomDeviceName { get; set; } + + public string PlaylistItemId { get; set; } + + public string UserPrimaryImageTag { get; set; } + public Tuple EnsureController(Func factory) { var controllers = SessionControllers.ToList(); @@ -267,10 +261,6 @@ namespace MediaBrowser.Controller.Session return false; } - private readonly object _progressLock = new object(); - private Timer _progressTimer; - private PlaybackProgressInfo _lastProgressInfo; - public void StartAutomaticProgress(PlaybackProgressInfo progressInfo) { if (_disposed) @@ -293,9 +283,6 @@ namespace MediaBrowser.Controller.Session } } - // 1 second - private const long ProgressIncrement = 10000000; - private async void OnProgressTimerCallback(object state) { if (_disposed) @@ -354,8 +341,7 @@ namespace MediaBrowser.Controller.Session } } - private bool _disposed = false; - + /// public void Dispose() { _disposed = true; @@ -367,30 +353,12 @@ namespace MediaBrowser.Controller.Session foreach (var controller in controllers) { - var disposable = controller as IDisposable; - - if (disposable != null) + if (controller is IDisposable disposable) { _logger.LogDebug("Disposing session controller {0}", disposable.GetType().Name); - - try - { - disposable.Dispose(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error disposing session controller"); - } + disposable.Dispose(); } } - - _sessionManager = null; } - - public QueueItem[] NowPlayingQueue { get; set; } - public bool HasCustomDeviceName { get; set; } - public string PlaylistItemId { get; set; } - public string ServerId { get; set; } - public string UserPrimaryImageTag { get; set; } } } diff --git a/MediaBrowser.Model/Net/WebSocketMessage.cs b/MediaBrowser.Model/Net/WebSocketMessage.cs index c763216f12..308032f833 100644 --- a/MediaBrowser.Model/Net/WebSocketMessage.cs +++ b/MediaBrowser.Model/Net/WebSocketMessage.cs @@ -1,3 +1,5 @@ +using System; + namespace MediaBrowser.Model.Net { /// @@ -11,13 +13,15 @@ namespace MediaBrowser.Model.Net /// /// The type of the message. public string MessageType { get; set; } - public string MessageId { get; set; } + + public Guid MessageId { get; set; } + public string ServerId { get; set; } + /// /// Gets or sets the data. /// /// The data. public T Data { get; set; } } - } -- cgit v1.2.3 From 5ca68f9623e414b85ddbda1f97895f1b90bd05e0 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 26 Dec 2019 20:57:46 +0100 Subject: Fix nullref exception and added logging --- .../HttpServer/HttpListenerHost.cs | 17 +++--- .../HttpServer/WebSocketConnection.cs | 63 ++++++++-------------- .../Session/SessionManager.cs | 3 +- .../Session/SessionWebSocketListener.cs | 2 +- .../Session/WebSocketController.cs | 5 +- .../Net/IWebSocketConnection.cs | 16 +++--- 6 files changed, 41 insertions(+), 65 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 4baf96ab51..ebae4d0b1e 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -518,30 +518,29 @@ namespace Emby.Server.Implementations.HttpServer return; } - var url = context.Request.GetDisplayUrl(); - _logger.LogInformation("WS {Url}. UserAgent: {UserAgent}", url, context.Request.Headers[HeaderNames.UserAgent].ToString()); - try { - var webSocket = await context.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); + _logger.LogInformation("WS Request from {IP}", context.Connection.RemoteIpAddress); + + WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false); var connection = new WebSocketConnection( _loggerFactory.CreateLogger(), webSocket, - context.Connection.RemoteIpAddress) + context.Connection.RemoteIpAddress, + context.Request.Query) { - Url = url, - QueryString = context.Request.Query, OnReceive = ProcessWebSocketMessageReceived }; WebSocketConnected?.Invoke(this, new GenericEventArgs(connection)); await connection.ProcessAsync().ConfigureAwait(false); + _logger.LogInformation("WS closed from {IP}", context.Connection.RemoteIpAddress); } - catch (WebSocketException ex) + catch (Exception ex) // Otherwise ASP.Net will ignore the exception { - _logger.LogError(ex, "ProcessWebSocketRequest error"); + _logger.LogError(ex, "WebSocketRequestHandler error"); if (!context.Response.HasStarted) { context.Response.StatusCode = 500; diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index b4f420e5d2..88974f9aba 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -1,4 +1,6 @@ -using System; +#nullable enable + +using System; using System.Buffers; using System.IO.Pipelines; using System.Net; @@ -39,47 +41,38 @@ namespace Emby.Server.Implementations.HttpServer /// /// Initializes a new instance of the class. /// + /// The logger. /// The socket. /// The remote end point. - /// The logger. - /// socket - public WebSocketConnection(ILogger logger, WebSocket socket, IPAddress remoteEndPoint) + /// The query. + public WebSocketConnection( + ILogger logger, + WebSocket socket, + IPAddress? remoteEndPoint, + IQueryCollection query) { - if (socket == null) - { - throw new ArgumentNullException(nameof(socket)); - } - - if (remoteEndPoint != null) - { - throw new ArgumentNullException(nameof(remoteEndPoint)); - } - - if (logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } - + _logger = logger; _socket = socket; RemoteEndPoint = remoteEndPoint; - _logger = logger; + QueryString = query; _jsonOptions = JsonDefaults.GetOptions(); + LastActivityDate = DateTime.Now; } /// - public event EventHandler Closed; + public event EventHandler? Closed; /// /// Gets or sets the remote end point. /// - public IPAddress RemoteEndPoint { get; private set; } + public IPAddress? RemoteEndPoint { get; } /// /// Gets or sets the receive action. /// /// The receive action. - public Func OnReceive { get; set; } + public Func? OnReceive { get; set; } /// /// Gets the last activity date. @@ -87,17 +80,11 @@ namespace Emby.Server.Implementations.HttpServer /// The last activity date. public DateTime LastActivityDate { get; private set; } - /// - /// Gets or sets the URL. - /// - /// The URL. - public string Url { get; set; } - /// /// Gets or sets the query string. /// /// The query string. - public IQueryCollection QueryString { get; set; } + public IQueryCollection QueryString { get; } /// /// Gets the state. @@ -115,11 +102,6 @@ namespace Emby.Server.Implementations.HttpServer /// message public Task SendAsync(WebSocketMessage message, CancellationToken cancellationToken) { - if (message == null) - { - throw new ArgumentNullException(nameof(message)); - } - var json = JsonSerializer.SerializeToUtf8Bytes(message, _jsonOptions); return _socket.SendAsync(json, WebSocketMessageType.Text, true, cancellationToken); } @@ -140,7 +122,7 @@ namespace Emby.Server.Implementations.HttpServer int bytesRead = receiveresult.Count; if (bytesRead == 0) { - continue; + break; } // Tell the PipeWriter how much was read from the Socket @@ -154,6 +136,8 @@ namespace Emby.Server.Implementations.HttpServer break; } + LastActivityDate = DateTime.UtcNow; + if (receiveresult.EndOfMessage) { await ProcessInternal(pipe.Reader).ConfigureAwait(false); @@ -162,10 +146,7 @@ namespace Emby.Server.Implementations.HttpServer if (_socket.State == WebSocketState.Open) { - await _socket.CloseAsync( - WebSocketCloseStatus.NormalClosure, - string.Empty, // REVIEW: human readable explanation as to why the connection is closed. - cancellationToken).ConfigureAwait(false); + _logger.LogWarning("Stopped reading from websocket before it was closed"); } Closed?.Invoke(this, EventArgs.Empty); @@ -175,8 +156,6 @@ namespace Emby.Server.Implementations.HttpServer private async Task ProcessInternal(PipeReader reader) { - LastActivityDate = DateTime.UtcNow; - if (OnReceive == null) { return; diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index db00ceeb71..0d5df1dadc 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1726,6 +1726,7 @@ namespace Emby.Server.Implementations.Session string.Equals(i.Client, client)); } + /// public SessionInfo GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion) { if (info == null) @@ -1733,7 +1734,7 @@ namespace Emby.Server.Implementations.Session throw new ArgumentNullException(nameof(info)); } - var user = info.UserId.Equals(Guid.Empty) + var user = info.UserId == Guid.Empty ? null : _userManager.GetUserById(info.UserId); diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 13b42698d3..d4e4ba1f2f 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -56,7 +56,7 @@ namespace Emby.Server.Implementations.Session } else { - _logger.LogWarning("Unable to determine session based on url: {0}", e.Argument.Url); + _logger.LogWarning("Unable to determine session based on query string: {0}", e.Argument.QueryString); } } diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index c17e67da93..536013c7ad 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -53,11 +53,12 @@ namespace Emby.Server.Implementations.Session private void OnConnectionClosed(object sender, EventArgs e) { - _logger.LogDebug("Removing websocket from session {Session}", _session.Id); var connection = (IWebSocketConnection)sender; + _logger.LogDebug("Removing websocket from session {Session}", _session.Id); _sockets.Remove(connection); - _sessionManager.CloseIfNeeded(_session); + connection.Closed -= OnConnectionClosed; connection.Dispose(); + _sessionManager.CloseIfNeeded(_session); } /// diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs index e2a714d5b1..d5555884df 100644 --- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs +++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; using System.Net; using System.Net.WebSockets; @@ -13,7 +15,7 @@ namespace MediaBrowser.Controller.Net /// /// Occurs when [closed]. /// - event EventHandler Closed; + event EventHandler? Closed; /// /// Gets the last activity date. @@ -21,23 +23,17 @@ namespace MediaBrowser.Controller.Net /// The last activity date. DateTime LastActivityDate { get; } - /// - /// Gets or sets the URL. - /// - /// The URL. - string Url { get; set; } - /// /// Gets or sets the query string. /// /// The query string. - IQueryCollection QueryString { get; set; } + IQueryCollection QueryString { get; } /// /// Gets or sets the receive action. /// /// The receive action. - Func OnReceive { get; set; } + Func? OnReceive { get; set; } /// /// Gets the state. @@ -49,7 +45,7 @@ namespace MediaBrowser.Controller.Net /// Gets the remote end point. /// /// The remote end point. - IPAddress RemoteEndPoint { get; } + IPAddress? RemoteEndPoint { get; } /// /// Sends a message asynchronously. -- cgit v1.2.3 From 4d311870d2f40f67da6df5641b53df637fdee88d Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 27 Dec 2019 14:42:53 +0100 Subject: Fix websocket handling --- .../HttpServer/WebSocketConnection.cs | 73 +++++++++------------- .../Session/WebSocketController.cs | 2 - .../Net/IWebSocketConnection.cs | 2 +- 3 files changed, 30 insertions(+), 47 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 88974f9aba..913a51217f 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -99,7 +99,6 @@ namespace Emby.Server.Implementations.HttpServer /// The message. /// The cancellation token. /// Task. - /// message public Task SendAsync(WebSocketMessage message, CancellationToken cancellationToken) { var json = JsonSerializer.SerializeToUtf8Bytes(message, _jsonOptions); @@ -117,7 +116,6 @@ namespace Emby.Server.Implementations.HttpServer { // Allocate at least 512 bytes from the PipeWriter Memory memory = writer.GetMemory(512); - receiveresult = await _socket.ReceiveAsync(memory, cancellationToken); int bytesRead = receiveresult.Count; if (bytesRead == 0) @@ -144,33 +142,30 @@ namespace Emby.Server.Implementations.HttpServer } } while (_socket.State == WebSocketState.Open && receiveresult.MessageType != WebSocketMessageType.Close); - if (_socket.State == WebSocketState.Open) - { - _logger.LogWarning("Stopped reading from websocket before it was closed"); - } - Closed?.Invoke(this, EventArgs.Empty); - _socket.Dispose(); + await _socket.CloseAsync( + WebSocketCloseStatus.NormalClosure, + string.Empty, + cancellationToken).ConfigureAwait(false); } private async Task ProcessInternal(PipeReader reader) { + ReadResult result = await reader.ReadAsync().ConfigureAwait(false); + ReadOnlySequence buffer = result.Buffer; + if (OnReceive == null) { + // Tell the PipeReader how much of the buffer we have consumed + reader.AdvanceTo(buffer.End); return; } + WebSocketMessage stub; try { - var result = await reader.ReadAsync().ConfigureAwait(false); - if (!result.IsCompleted) - { - return; - } - WebSocketMessage stub; - var buffer = result.Buffer; if (buffer.IsSingleSegment) { stub = JsonSerializer.Deserialize>(buffer.FirstSpan, _jsonOptions); @@ -188,46 +183,36 @@ namespace Emby.Server.Implementations.HttpServer ArrayPool.Shared.Return(buf); } } - - var info = new WebSocketMessageInfo - { - MessageType = stub.MessageType, - Data = stub.Data.ToString(), - Connection = this - }; - - await OnReceive(info).ConfigureAwait(false); } catch (JsonException ex) { + // Tell the PipeReader how much of the buffer we have consumed + reader.AdvanceTo(buffer.End); _logger.LogError(ex, "Error processing web socket message"); + return; } - } - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } + // Tell the PipeReader how much of the buffer we have consumed + reader.AdvanceTo(buffer.End); - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) - { - if (_disposed) + _logger.LogDebug("WS received message: {@Message}", stub); + + var info = new WebSocketMessageInfo { - return; - } + MessageType = stub.MessageType, + Data = stub.Data?.ToString(), // Data can be null + Connection = this + }; + + _logger.LogDebug("WS message info: {@MessageInfo}", info); - if (dispose) + await OnReceive(info).ConfigureAwait(false); + + // Stop reading if there's no more data coming + if (result.IsCompleted) { - _socket.Dispose(); + return; } - - _disposed = true; } } } diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index 536013c7ad..c3c4b716fc 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -57,7 +57,6 @@ namespace Emby.Server.Implementations.Session _logger.LogDebug("Removing websocket from session {Session}", _session.Id); _sockets.Remove(connection); connection.Closed -= OnConnectionClosed; - connection.Dispose(); _sessionManager.CloseIfNeeded(_session); } @@ -96,7 +95,6 @@ namespace Emby.Server.Implementations.Session foreach (var socket in _sockets) { socket.Closed -= OnConnectionClosed; - socket.Dispose(); } _disposed = true; diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs index d5555884df..09e43c683f 100644 --- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs +++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs @@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net { - public interface IWebSocketConnection : IDisposable + public interface IWebSocketConnection { /// /// Occurs when [closed]. -- cgit v1.2.3 From 407f54e7764a6bfd8e4ccc0df897fac7e8c658b4 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 13 Jan 2020 20:03:49 +0100 Subject: Style fixes --- .../Session/WebSocketController.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index c3c4b716fc..c7ef9b1cec 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -1,3 +1,7 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1600 +#nullable enable + using System; using System.Collections.Generic; using System.Linq; @@ -42,7 +46,6 @@ namespace Emby.Server.Implementations.Session private IEnumerable GetActiveSockets() => _sockets.Where(i => i.State == WebSocketState.Open); - /// public void AddWebSocket(IWebSocketConnection connection) { _logger.LogDebug("Adding websocket to session {Session}", _session.Id); @@ -76,12 +79,14 @@ namespace Emby.Server.Implementations.Session return Task.CompletedTask; } - return socket.SendAsync(new WebSocketMessage - { - Data = data, - MessageType = name, - MessageId = messageId - }, cancellationToken); + return socket.SendAsync( + new WebSocketMessage + { + Data = data, + MessageType = name, + MessageId = messageId + }, + cancellationToken); } /// -- cgit v1.2.3 From c601def4847cb0ada323a9da8d152c8c8d7b834b Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 17 Jan 2020 00:19:58 +0100 Subject: Fix warnings in SessionManager --- Emby.Server.Implementations/ApplicationHost.cs | 13 +- .../Session/SessionManager.cs | 274 +++++++++++---------- 2 files changed, 156 insertions(+), 131 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 0bb1d832fb..e93337c2fc 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -808,7 +808,18 @@ namespace Emby.Server.Implementations ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, ProviderManager); serviceCollection.AddSingleton(ChannelManager); - SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, this, AuthenticationRepository, DeviceManager, MediaSourceManager); + SessionManager = new SessionManager( + LoggerFactory.CreateLogger(), + UserDataManager, + LibraryManager, + UserManager, + musicManager, + DtoService, + ImageProcessor, + this, + AuthenticationRepository, + DeviceManager, + MediaSourceManager); serviceCollection.AddSingleton(SessionManager); serviceCollection.AddSingleton( diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index b1d513dd4f..bb0836614e 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -30,17 +30,17 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Session { /// - /// Class SessionManager + /// Class SessionManager. /// public class SessionManager : ISessionManager, IDisposable { /// - /// The _user data repository + /// The _user data repository. /// private readonly IUserDataManager _userDataManager; /// - /// The _logger + /// The _logger. /// private readonly ILogger _logger; @@ -57,36 +57,19 @@ namespace Emby.Server.Implementations.Session private readonly IDeviceManager _deviceManager; /// - /// The _active connections + /// The _active connections. /// private readonly ConcurrentDictionary _activeConnections = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - public event EventHandler> AuthenticationFailed; - - public event EventHandler> AuthenticationSucceeded; - - /// - /// Occurs when [playback start]. - /// - public event EventHandler PlaybackStart; - /// - /// Occurs when [playback progress]. - /// - public event EventHandler PlaybackProgress; - /// - /// Occurs when [playback stopped]. - /// - public event EventHandler PlaybackStopped; + private Timer _idleTimer; - public event EventHandler SessionStarted; - public event EventHandler CapabilitiesChanged; - public event EventHandler SessionEnded; - public event EventHandler SessionActivity; + private DtoOptions _itemInfoDtoOptions; + private bool _disposed = false; public SessionManager( + ILogger logger, IUserDataManager userDataManager, - ILoggerFactory loggerFactory, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager, @@ -97,8 +80,8 @@ namespace Emby.Server.Implementations.Session IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager) { + _logger = logger; _userDataManager = userDataManager; - _logger = loggerFactory.CreateLogger(nameof(SessionManager)); _libraryManager = libraryManager; _userManager = userManager; _musicManager = musicManager; @@ -108,9 +91,49 @@ namespace Emby.Server.Implementations.Session _authRepo = authRepo; _deviceManager = deviceManager; _mediaSourceManager = mediaSourceManager; + _deviceManager.DeviceOptionsUpdated += OnDeviceManagerDeviceOptionsUpdated; } + /// + public event EventHandler> AuthenticationFailed; + + /// + public event EventHandler> AuthenticationSucceeded; + + /// + /// Occurs when [playback start]. + /// + public event EventHandler PlaybackStart; + + /// + /// Occurs when [playback progress]. + /// + public event EventHandler PlaybackProgress; + + /// + /// Occurs when [playback stopped]. + /// + public event EventHandler PlaybackStopped; + + /// + public event EventHandler SessionStarted; + + /// + public event EventHandler CapabilitiesChanged; + + /// + public event EventHandler SessionEnded; + + /// + public event EventHandler SessionActivity; + + /// + /// Gets all connections. + /// + /// All connections. + public IEnumerable Sessions => _activeConnections.Values.OrderByDescending(c => c.LastActivityDate); + private void OnDeviceManagerDeviceOptionsUpdated(object sender, GenericEventArgs> e) { foreach (var session in Sessions) @@ -130,14 +153,17 @@ namespace Emby.Server.Implementations.Session } } - private bool _disposed = false; - + /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { if (_disposed) @@ -147,15 +173,17 @@ namespace Emby.Server.Implementations.Session if (disposing) { - // TODO: dispose stuff + _idleTimer?.Dispose(); } + _idleTimer = null; + _deviceManager.DeviceOptionsUpdated -= OnDeviceManagerDeviceOptionsUpdated; _disposed = true; } - public void CheckDisposed() + private void CheckDisposed() { if (_disposed) { @@ -163,12 +191,6 @@ namespace Emby.Server.Implementations.Session } } - /// - /// Gets all connections. - /// - /// All connections. - public IEnumerable Sessions => _activeConnections.Values.OrderByDescending(c => c.LastActivityDate); - private void OnSessionStarted(SessionInfo info) { if (!string.IsNullOrEmpty(info.DeviceId)) @@ -199,13 +221,13 @@ namespace Emby.Server.Implementations.Session new SessionEventArgs { SessionInfo = info - }, _logger); info.Dispose(); } + /// public void UpdateDeviceName(string sessionId, string deviceName) { var session = GetSession(sessionId); @@ -225,7 +247,6 @@ namespace Emby.Server.Implementations.Session /// The remote end point. /// The user. /// SessionInfo. - /// user public SessionInfo LogSessionActivity( string appName, string appVersion, @@ -263,14 +284,7 @@ namespace Emby.Server.Implementations.Session if ((activityDate - userLastActivityDate).TotalSeconds > 60) { - try - { - _userManager.UpdateUser(user); - } - catch (Exception ex) - { - _logger.LogError("Error updating user", ex); - } + _userManager.UpdateUser(user); } } @@ -287,18 +301,20 @@ namespace Emby.Server.Implementations.Session return session; } + /// public void CloseIfNeeded(SessionInfo session) { if (!session.SessionControllers.Any(i => i.IsSessionActive)) { var key = GetSessionKey(session.Client, session.DeviceId); - _activeConnections.TryRemove(key, out var removed); + _activeConnections.TryRemove(key, out _); OnSessionEnded(session); } } + /// public void ReportSessionEnded(string sessionId) { CheckDisposed(); @@ -308,7 +324,7 @@ namespace Emby.Server.Implementations.Session { var key = GetSessionKey(session.Client, session.DeviceId); - _activeConnections.TryRemove(key, out var removed); + _activeConnections.TryRemove(key, out _); OnSessionEnded(session); } @@ -339,7 +355,7 @@ namespace Emby.Server.Implementations.Session var runtimeTicks = libraryItem.RunTimeTicks; MediaSourceInfo mediaSource = null; - if (libraryItem is IHasMediaSources hasMediaSources) + if (libraryItem is IHasMediaSources) { mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false); @@ -391,7 +407,6 @@ namespace Emby.Server.Implementations.Session /// Removes the now playing item id. /// /// The session. - /// item private void RemoveNowPlayingItem(SessionInfo session) { session.NowPlayingItem = null; @@ -404,9 +419,7 @@ namespace Emby.Server.Implementations.Session } private static string GetSessionKey(string appName, string deviceId) - { - return appName + deviceId; - } + => appName + deviceId; /// /// Gets the connection. @@ -426,6 +439,7 @@ namespace Emby.Server.Implementations.Session { throw new ArgumentNullException(nameof(deviceId)); } + var key = GetSessionKey(appName, deviceId); CheckDisposed(); @@ -498,7 +512,7 @@ namespace Emby.Server.Implementations.Session { var users = new List(); - if (!session.UserId.Equals(Guid.Empty)) + if (session.UserId != Guid.Empty) { var user = _userManager.GetUserById(session.UserId); @@ -517,8 +531,6 @@ namespace Emby.Server.Implementations.Session return users; } - private Timer _idleTimer; - private void StartIdleCheckTimer() { if (_idleTimer == null) @@ -594,11 +606,11 @@ namespace Emby.Server.Implementations.Session } /// - /// Used to report that playback has started for an item + /// Used to report that playback has started for an item. /// /// The info. /// Task. - /// info + /// info is null. public async Task OnPlaybackStart(PlaybackStartInfo info) { CheckDisposed(); @@ -610,7 +622,7 @@ namespace Emby.Server.Implementations.Session var session = GetSession(info.SessionId); - var libraryItem = info.ItemId.Equals(Guid.Empty) + var libraryItem = info.ItemId == Guid.Empty ? null : GetNowPlayingItem(session, info.ItemId); @@ -648,7 +660,6 @@ namespace Emby.Server.Implementations.Session ClientName = session.Client, DeviceId = session.DeviceId, Session = session - }, _logger); @@ -679,13 +690,14 @@ namespace Emby.Server.Implementations.Session _userDataManager.SaveUserData(user, item, data, UserDataSaveReason.PlaybackStart, CancellationToken.None); } + /// public Task OnPlaybackProgress(PlaybackProgressInfo info) { return OnPlaybackProgress(info, false); } /// - /// Used to report playback progress for an item + /// Used to report playback progress for an item. /// /// Task. public async Task OnPlaybackProgress(PlaybackProgressInfo info, bool isAutomated) @@ -852,7 +864,7 @@ namespace Emby.Server.Implementations.Session { MediaSourceInfo mediaSource = null; - if (libraryItem is IHasMediaSources hasMediaSources) + if (libraryItem is IHasMediaSources) { mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false); } @@ -924,7 +936,6 @@ namespace Emby.Server.Implementations.Session ClientName = session.Client, DeviceId = session.DeviceId, Session = session - }, _logger); } @@ -962,13 +973,17 @@ namespace Emby.Server.Implementations.Session /// The session identifier. /// if set to true [throw on missing]. /// SessionInfo. - /// sessionId + /// + /// No session with an Id equal to sessionId was found + /// and throwOnMissing is true. + /// private SessionInfo GetSession(string sessionId, bool throwOnMissing = true) { var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId, StringComparison.Ordinal)); if (session == null && throwOnMissing) { - throw new ResourceNotFoundException(string.Format("Session {0} not found.", sessionId)); + throw new ResourceNotFoundException( + string.Format(CultureInfo.InvariantCulture, "Session {0} not found.", sessionId)); } return session; @@ -981,12 +996,14 @@ namespace Emby.Server.Implementations.Session if (session == null) { - throw new ResourceNotFoundException(string.Format("Session {0} not found.", sessionId)); + throw new ResourceNotFoundException( + string.Format(CultureInfo.InvariantCulture, "Session {0} not found.", sessionId)); } return session; } + /// public Task SendMessageCommand(string controllingSessionId, string sessionId, MessageCommand command, CancellationToken cancellationToken) { CheckDisposed(); @@ -1007,6 +1024,7 @@ namespace Emby.Server.Implementations.Session return SendGeneralCommand(controllingSessionId, sessionId, generalCommand, cancellationToken); } + /// public Task SendGeneralCommand(string controllingSessionId, string sessionId, GeneralCommand command, CancellationToken cancellationToken) { CheckDisposed(); @@ -1051,6 +1069,7 @@ namespace Emby.Server.Implementations.Session return Task.WhenAll(GetTasks()); } + /// public async Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken) { CheckDisposed(); @@ -1092,7 +1111,8 @@ namespace Emby.Server.Implementations.Session { if (items.Any(i => i.GetPlayAccess(user) != PlayAccess.Full)) { - throw new ArgumentException(string.Format("{0} is not allowed to play media.", user.Name)); + throw new ArgumentException( + string.Format(CultureInfo.InvariantCulture, "{0} is not allowed to play media.", user.Name)); } } @@ -1200,6 +1220,7 @@ namespace Emby.Server.Implementations.Session return _musicManager.GetInstantMixFromItem(item, user, new DtoOptions(false) { EnableImages = false }); } + /// public Task SendBrowseCommand(string controllingSessionId, string sessionId, BrowseRequest command, CancellationToken cancellationToken) { var generalCommand = new GeneralCommand @@ -1216,6 +1237,7 @@ namespace Emby.Server.Implementations.Session return SendGeneralCommand(controllingSessionId, sessionId, generalCommand, cancellationToken); } + /// public Task SendPlaystateCommand(string controllingSessionId, string sessionId, PlaystateRequest command, CancellationToken cancellationToken) { CheckDisposed(); @@ -1299,12 +1321,12 @@ namespace Emby.Server.Implementations.Session var session = GetSession(sessionId); - if (session.UserId.Equals(userId)) + if (session.UserId == userId) { throw new ArgumentException("The requested user is already the primary user of the session."); } - if (session.AdditionalUsers.All(i => !i.UserId.Equals(userId))) + if (session.AdditionalUsers.All(i => i.UserId != userId)) { var user = _userManager.GetUserById(userId); @@ -1430,19 +1452,19 @@ namespace Emby.Server.Implementations.Session private string GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName) { - var existing = _authRepo.Get(new AuthenticationInfoQuery - { - DeviceId = deviceId, - UserId = user.Id, - Limit = 1 - - }).Items.FirstOrDefault(); - - var allExistingForDevice = _authRepo.Get(new AuthenticationInfoQuery - { - DeviceId = deviceId + var existing = _authRepo.Get( + new AuthenticationInfoQuery + { + DeviceId = deviceId, + UserId = user.Id, + Limit = 1 + }).Items.FirstOrDefault(); - }).Items; + var allExistingForDevice = _authRepo.Get( + new AuthenticationInfoQuery + { + DeviceId = deviceId + }).Items; foreach (var auth in allExistingForDevice) { @@ -1461,7 +1483,7 @@ namespace Emby.Server.Implementations.Session if (existing != null) { - _logger.LogInformation("Reissuing access token: " + existing.AccessToken); + _logger.LogInformation("Reissuing access token: {Token}", existing.AccessToken); return existing.AccessToken; } @@ -1486,6 +1508,7 @@ namespace Emby.Server.Implementations.Session return newToken.AccessToken; } + /// public void Logout(string accessToken) { CheckDisposed(); @@ -1495,19 +1518,20 @@ namespace Emby.Server.Implementations.Session throw new ArgumentNullException(nameof(accessToken)); } - var existing = _authRepo.Get(new AuthenticationInfoQuery - { - Limit = 1, - AccessToken = accessToken - - }).Items.FirstOrDefault(); + var existing = _authRepo.Get( + new AuthenticationInfoQuery + { + Limit = 1, + AccessToken = accessToken + }).Items; - if (existing != null) + if (existing.Count > 0) { - Logout(existing); + Logout(existing[0]); } } + /// public void Logout(AuthenticationInfo existing) { CheckDisposed(); @@ -1533,6 +1557,7 @@ namespace Emby.Server.Implementations.Session } } + /// public void RevokeUserTokens(Guid userId, string currentAccessToken) { CheckDisposed(); @@ -1551,6 +1576,7 @@ namespace Emby.Server.Implementations.Session } } + /// public void RevokeToken(string token) { Logout(token); @@ -1607,10 +1633,8 @@ namespace Emby.Server.Implementations.Session _deviceManager.SaveCapabilities(deviceId, capabilities); } - private DtoOptions _itemInfoDtoOptions; - /// - /// Converts a BaseItem to a BaseItemInfo + /// Converts a BaseItem to a BaseItemInfo. /// private BaseItemDto GetItemInfo(BaseItem item, MediaSourceInfo mediaSource) { @@ -1685,6 +1709,7 @@ namespace Emby.Server.Implementations.Session } } + /// public void ReportNowViewingItem(string sessionId, string itemId) { if (string.IsNullOrEmpty(itemId)) @@ -1692,23 +1717,26 @@ namespace Emby.Server.Implementations.Session throw new ArgumentNullException(nameof(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); - //ReportNowViewingItem(sessionId, info); + ReportNowViewingItem(sessionId, info); } + /// public void ReportNowViewingItem(string sessionId, BaseItemDto item) { - //var session = GetSession(sessionId); + throw new NotImplementedException(); - //session.NowViewingItem = item; + // var session = GetSession(sessionId); + // session.NowViewingItem = item; } + /// public void ReportTranscodingInfo(string deviceId, TranscodingInfo info) { - var session = Sessions.FirstOrDefault(i => string.Equals(i.DeviceId, deviceId)); + var session = Sessions.FirstOrDefault(i => string.Equals(i.DeviceId, deviceId, StringComparison.Ordinal)); if (session != null) { @@ -1716,17 +1744,18 @@ namespace Emby.Server.Implementations.Session } } + /// public void ClearTranscodingInfo(string deviceId) { ReportTranscodingInfo(deviceId, null); } + /// public SessionInfo GetSession(string deviceId, string client, string version) - { - return Sessions.FirstOrDefault(i => string.Equals(i.DeviceId, deviceId) && - string.Equals(i.Client, client)); - } + => Sessions.FirstOrDefault( + i => string.Equals(i.DeviceId, deviceId, StringComparison.Ordinal) && string.Equals(i.Client, client, StringComparison.Ordinal)); + /// public SessionInfo GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion) { if (info == null) @@ -1759,23 +1788,24 @@ namespace Emby.Server.Implementations.Session return LogSessionActivity(appName, appVersion, deviceId, deviceName, remoteEndpoint, user); } + /// public SessionInfo GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint) { - var result = _authRepo.Get(new AuthenticationInfoQuery + var items = _authRepo.Get(new AuthenticationInfoQuery { - AccessToken = token - }); - - var info = result.Items.FirstOrDefault(); + AccessToken = token, + Limit = 1 + }).Items; - if (info == null) + if (items.Count == 0) { return null; } - return GetSessionByAuthenticationToken(info, deviceId, remoteEndpoint, null); + return GetSessionByAuthenticationToken(items[0], deviceId, remoteEndpoint, null); } + /// public Task SendMessageToAdminSessions(string name, T data, CancellationToken cancellationToken) { CheckDisposed(); @@ -1785,6 +1815,7 @@ namespace Emby.Server.Implementations.Session return SendMessageToUserSessions(adminUserIds, name, data, cancellationToken); } + /// public Task SendMessageToUserSessions(List userIds, string name, Func dataFn, CancellationToken cancellationToken) { CheckDisposed(); @@ -1796,11 +1827,10 @@ namespace Emby.Server.Implementations.Session return Task.CompletedTask; } - var data = dataFn(); - - return SendMessageToSessions(sessions, name, data, cancellationToken); + return SendMessageToSessions(sessions, name, dataFn(), cancellationToken); } + /// public Task SendMessageToUserSessions(List userIds, string name, T data, CancellationToken cancellationToken) { CheckDisposed(); @@ -1809,6 +1839,7 @@ namespace Emby.Server.Implementations.Session return SendMessageToSessions(sessions, name, data, cancellationToken); } + /// public Task SendMessageToUserDeviceSessions(string deviceId, string name, T data, CancellationToken cancellationToken) { CheckDisposed(); @@ -1817,22 +1848,5 @@ namespace Emby.Server.Implementations.Session return SendMessageToSessions(sessions, name, data, cancellationToken); } - - public Task SendMessageToUserDeviceAndAdminSessions(string deviceId, string name, T data, CancellationToken cancellationToken) - { - CheckDisposed(); - - var sessions = Sessions - .Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase) || IsAdminSession(i)); - - return SendMessageToSessions(sessions, name, data, cancellationToken); - } - - private bool IsAdminSession(SessionInfo s) - { - var user = _userManager.GetUserById(s.UserId); - - return user != null && user.Policy.IsAdministrator; - } } } -- cgit v1.2.3 From 70bc03c79571f70c2a6281b99554638f76f461c9 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Sat, 1 Feb 2020 12:54:48 +0100 Subject: Apply suggestions from code review Co-Authored-By: dkanada --- Emby.Server.Implementations/Session/SessionManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index bb0836614e..f5bd51c4ae 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -35,12 +35,12 @@ namespace Emby.Server.Implementations.Session public class SessionManager : ISessionManager, IDisposable { /// - /// The _user data repository. + /// The user data repository. /// private readonly IUserDataManager _userDataManager; /// - /// The _logger. + /// The logger. /// private readonly ILogger _logger; @@ -57,7 +57,7 @@ namespace Emby.Server.Implementations.Session private readonly IDeviceManager _deviceManager; /// - /// The _active connections. + /// The active connections. /// private readonly ConcurrentDictionary _activeConnections = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); -- cgit v1.2.3 From afe9ed977b9b1fe83cb0132ddf753040a4bb95e3 Mon Sep 17 00:00:00 2001 From: dkanada Date: Sat, 1 Feb 2020 22:44:27 +0900 Subject: improve scan progress and fix a few bugs --- .../Library/LibraryManager.cs | 22 ++++++++++------------ Emby.Server.Implementations/Library/UserManager.cs | 5 +++-- .../Session/SessionManager.cs | 18 +++++++++++------- MediaBrowser.Api/Session/SessionsService.cs | 4 ++-- MediaBrowser.Api/UserService.cs | 4 ++-- MediaBrowser.Controller/Session/SessionInfo.cs | 10 ++-------- 6 files changed, 30 insertions(+), 33 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 6fb6235541..9d244b74c8 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -710,10 +710,10 @@ namespace Emby.Server.Implementations.Library } /// - /// Creates the root media folder + /// Creates the root media folder. /// /// AggregateFolder. - /// Cannot create the root folder until plugins have loaded + /// Cannot create the root folder until plugins have loaded. public AggregateFolder CreateRootFolder() { var rootFolderPath = ConfigurationManager.ApplicationPaths.RootFolderPath; @@ -824,7 +824,6 @@ namespace Emby.Server.Implementations.Library { // If this returns multiple items it could be tricky figuring out which one is correct. // In most cases, the newest one will be and the others obsolete but not yet cleaned up - if (string.IsNullOrEmpty(path)) { throw new ArgumentNullException(nameof(path)); @@ -844,7 +843,7 @@ namespace Emby.Server.Implementations.Library } /// - /// Gets a Person + /// Gets the person. /// /// The name. /// Task{Person}. @@ -854,7 +853,7 @@ namespace Emby.Server.Implementations.Library } /// - /// Gets a Studio + /// Gets the studio. /// /// The name. /// Task{Studio}. @@ -879,7 +878,7 @@ namespace Emby.Server.Implementations.Library } /// - /// Gets a Genre + /// Gets the genre. /// /// The name. /// Task{Genre}. @@ -889,7 +888,7 @@ namespace Emby.Server.Implementations.Library } /// - /// Gets the genre. + /// Gets the music genre. /// /// The name. /// Task{MusicGenre}. @@ -899,7 +898,7 @@ namespace Emby.Server.Implementations.Library } /// - /// Gets a Year + /// Gets the year. /// /// The value. /// Task{Year}. @@ -1076,9 +1075,9 @@ namespace Emby.Server.Implementations.Library var innerProgress = new ActionableProgress(); - innerProgress.RegisterAction(pct => progress.Report(pct * .96)); + innerProgress.RegisterAction(pct => progress.Report(pct * pct * 0.96)); - // Now validate the entire media library + // validate the entire media library await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: true).ConfigureAwait(false); progress.Report(96); @@ -1087,7 +1086,6 @@ namespace Emby.Server.Implementations.Library innerProgress.RegisterAction(pct => progress.Report(96 + (pct * .04))); - // Run post-scan tasks await RunPostScanTasks(innerProgress, cancellationToken).ConfigureAwait(false); progress.Report(100); @@ -1138,7 +1136,7 @@ namespace Emby.Server.Implementations.Library } catch (Exception ex) { - _logger.LogError(ex, "Error running postscan task"); + _logger.LogError(ex, "Error running post-scan task"); } numComplete++; diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 656eeb1459..a5d7153d7f 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -291,10 +291,11 @@ namespace Emby.Server.Implementations.Library && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider)) { - // We should trust the user that the authprovider says, not what was typed + // trust the username returned by the authentication provider username = updatedUsername; - // Search the database for the user again; the authprovider might have created it + // search the database for the user again + // the authentication provider might have created it user = Users .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index b1d513dd4f..72e4e151e4 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -30,17 +30,17 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Session { /// - /// Class SessionManager + /// Class SessionManager. /// public class SessionManager : ISessionManager, IDisposable { /// - /// The _user data repository + /// The user data repository. /// private readonly IUserDataManager _userDataManager; /// - /// The _logger + /// The logger. /// private readonly ILogger _logger; @@ -57,7 +57,7 @@ namespace Emby.Server.Implementations.Session private readonly IDeviceManager _deviceManager; /// - /// The _active connections + /// The active connections. /// private readonly ConcurrentDictionary _activeConnections = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); @@ -70,18 +70,23 @@ namespace Emby.Server.Implementations.Session /// Occurs when [playback start]. /// public event EventHandler PlaybackStart; + /// /// Occurs when [playback progress]. /// public event EventHandler PlaybackProgress; + /// /// Occurs when [playback stopped]. /// public event EventHandler PlaybackStopped; public event EventHandler SessionStarted; + public event EventHandler CapabilitiesChanged; + public event EventHandler SessionEnded; + public event EventHandler SessionActivity; public SessionManager( @@ -924,7 +929,6 @@ namespace Emby.Server.Implementations.Session ClientName = session.Client, DeviceId = session.DeviceId, Session = session - }, _logger); } @@ -1610,7 +1614,7 @@ namespace Emby.Server.Implementations.Session private DtoOptions _itemInfoDtoOptions; /// - /// Converts a BaseItem to a BaseItemInfo + /// Converts a BaseItem to a BaseItemInfo. /// private BaseItemDto GetItemInfo(BaseItem item, MediaSourceInfo mediaSource) { @@ -1680,7 +1684,7 @@ namespace Emby.Server.Implementations.Session } catch (Exception ex) { - _logger.LogError("Error getting {0} image info", ex, type); + _logger.LogError("Error getting image information for {0}", ex, type); return null; } } diff --git a/MediaBrowser.Api/Session/SessionsService.cs b/MediaBrowser.Api/Session/SessionsService.cs index 700861c554..9aa7b2c88a 100644 --- a/MediaBrowser.Api/Session/SessionsService.cs +++ b/MediaBrowser.Api/Session/SessionsService.cs @@ -18,7 +18,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.Session { /// - /// Class GetSessions + /// Class GetSessions. /// [Route("/Sessions", "GET", Summary = "Gets a list of sessions")] [Authenticated] @@ -34,7 +34,7 @@ namespace MediaBrowser.Api.Session } /// - /// Class DisplayContent + /// Class DisplayContent. /// [Route("/Sessions/{Id}/Viewing", "POST", Summary = "Instructs a session to browse to an item or view")] [Authenticated] diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index e1b01b012c..0d63c19b5e 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -281,7 +281,6 @@ namespace MediaBrowser.Api { IsHidden = false, IsDisabled = false - }, true, true); } @@ -395,10 +394,11 @@ namespace MediaBrowser.Api throw new MethodNotAllowedException("Hashed-only passwords are not valid for this API."); } + // password should always be null return Post(new AuthenticateUserByName { Username = user.Name, - Password = null, // This should always be null + Password = null, Pw = request.Pw }); } diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index acda6a4166..6e8385cf85 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -126,12 +126,6 @@ namespace MediaBrowser.Controller.Session [JsonIgnore] public ISessionController[] SessionControllers { get; set; } - /// - /// Gets or sets the application icon URL. - /// - /// The application icon URL. - public string AppIconUrl { get; set; } - /// /// Gets or sets the supported commands. /// @@ -259,7 +253,7 @@ namespace MediaBrowser.Controller.Session foreach (var additionalUser in AdditionalUsers) { - if (userId.Equals(userId)) + if (additionalUser.UserId.Equals(userId)) { return true; } @@ -321,7 +315,7 @@ namespace MediaBrowser.Controller.Session var newPositionTicks = positionTicks + ProgressIncrement; var item = progressInfo.Item; - long? runtimeTicks = item == null ? null : item.RunTimeTicks; + long? runtimeTicks = item?.RunTimeTicks; // Don't report beyond the runtime if (runtimeTicks.HasValue && newPositionTicks >= runtimeTicks.Value) -- cgit v1.2.3 From 7df6f8d54e4d7eb8efd261b9ce5138c3efa9a364 Mon Sep 17 00:00:00 2001 From: dkanada Date: Sat, 1 Feb 2020 23:14:27 +0900 Subject: update error message Co-Authored-By: Bond-009 --- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 72e4e151e4..0bba19f2a3 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1684,7 +1684,7 @@ namespace Emby.Server.Implementations.Session } catch (Exception ex) { - _logger.LogError("Error getting image information for {0}", ex, type); + _logger.LogError(ex, "Error getting image information for {Type}", type); return null; } } -- cgit v1.2.3 From 6341d986a95be928a85ecd81c08fefdc5c3ec3b6 Mon Sep 17 00:00:00 2001 From: dkanada Date: Sun, 2 Feb 2020 00:07:46 +0900 Subject: fix a few warnings --- .../Session/SessionManager.cs | 30 +++++++++++----------- MediaBrowser.Api/Session/SessionsService.cs | 10 +++----- MediaBrowser.Api/UserLibrary/PlaystateService.cs | 4 --- MediaBrowser.Api/UserService.cs | 2 +- MediaBrowser.Controller/Session/SessionInfo.cs | 7 ----- 5 files changed, 19 insertions(+), 34 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index b1d513dd4f..ec3e875732 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -67,15 +67,15 @@ namespace Emby.Server.Implementations.Session public event EventHandler> AuthenticationSucceeded; /// - /// Occurs when [playback start]. + /// Occurs when playback has started. /// public event EventHandler PlaybackStart; /// - /// Occurs when [playback progress]. + /// Occurs when playback has progressed. /// public event EventHandler PlaybackProgress; /// - /// Occurs when [playback stopped]. + /// Occurs when playback has stopped. /// public event EventHandler PlaybackStopped; @@ -685,7 +685,7 @@ namespace Emby.Server.Implementations.Session } /// - /// Used to report playback progress for an item + /// Used to report playback progress for an item. /// /// Task. public async Task OnPlaybackProgress(PlaybackProgressInfo info, bool isAutomated) @@ -1435,14 +1435,13 @@ namespace Emby.Server.Implementations.Session DeviceId = deviceId, UserId = user.Id, Limit = 1 - }).Items.FirstOrDefault(); - var allExistingForDevice = _authRepo.Get(new AuthenticationInfoQuery - { - DeviceId = deviceId - - }).Items; + var allExistingForDevice = _authRepo.Get( + new AuthenticationInfoQuery + { + DeviceId = deviceId + }).Items; foreach (var auth in allExistingForDevice) { @@ -1499,8 +1498,7 @@ namespace Emby.Server.Implementations.Session { Limit = 1, AccessToken = accessToken - - }).Items.FirstOrDefault(); + }).Items[0]; if (existing != null) { @@ -1708,7 +1706,8 @@ namespace Emby.Server.Implementations.Session public void ReportTranscodingInfo(string deviceId, TranscodingInfo info) { - var session = Sessions.FirstOrDefault(i => string.Equals(i.DeviceId, deviceId)); + var session = Sessions.FirstOrDefault(i => + string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)); if (session != null) { @@ -1723,8 +1722,9 @@ namespace Emby.Server.Implementations.Session public SessionInfo GetSession(string deviceId, string client, string version) { - return Sessions.FirstOrDefault(i => string.Equals(i.DeviceId, deviceId) && - string.Equals(i.Client, client)); + return Sessions.FirstOrDefault(i => + string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase) + && string.Equals(i.Client, client, StringComparison.OrdinalIgnoreCase)); } public SessionInfo GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion) diff --git a/MediaBrowser.Api/Session/SessionsService.cs b/MediaBrowser.Api/Session/SessionsService.cs index 700861c554..8e53490ffc 100644 --- a/MediaBrowser.Api/Session/SessionsService.cs +++ b/MediaBrowser.Api/Session/SessionsService.cs @@ -276,7 +276,7 @@ namespace MediaBrowser.Api.Session public class SessionsService : BaseApiService { /// - /// The _session manager. + /// The session manager. /// private readonly ISessionManager _sessionManager; @@ -438,14 +438,12 @@ namespace MediaBrowser.Api.Session public Task Post(SendSystemCommand request) { var name = request.Command; - if (Enum.TryParse(name, true, out GeneralCommandType commandType)) { name = commandType.ToString(); } var currentSession = GetSession(_sessionContext); - var command = new GeneralCommand { Name = name, @@ -518,16 +516,13 @@ namespace MediaBrowser.Api.Session { request.Id = GetSession(_sessionContext).Id; } + _sessionManager.ReportCapabilities(request.Id, new ClientCapabilities { PlayableMediaTypes = SplitValue(request.PlayableMediaTypes, ','), - SupportedCommands = SplitValue(request.SupportedCommands, ','), - SupportsMediaControl = request.SupportsMediaControl, - SupportsSync = request.SupportsSync, - SupportsPersistentIdentifier = request.SupportsPersistentIdentifier }); } @@ -538,6 +533,7 @@ namespace MediaBrowser.Api.Session { request.Id = GetSession(_sessionContext).Id; } + _sessionManager.ReportCapabilities(request.Id, request); } } diff --git a/MediaBrowser.Api/UserLibrary/PlaystateService.cs b/MediaBrowser.Api/UserLibrary/PlaystateService.cs index 9d1cf5d9ee..d0faca163b 100644 --- a/MediaBrowser.Api/UserLibrary/PlaystateService.cs +++ b/MediaBrowser.Api/UserLibrary/PlaystateService.cs @@ -103,10 +103,6 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "MediaSourceId", Description = "The id of the MediaSource", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string MediaSourceId { get; set; } - /// - /// Gets or sets a value indicating whether this is likes. - /// - /// true if likes; otherwise, false. [ApiMember(Name = "CanSeek", Description = "Indicates if the client can seek", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] public bool CanSeek { get; set; } diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index e1b01b012c..3b82dd9dd8 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -240,7 +240,7 @@ namespace MediaBrowser.Api public class UserService : BaseApiService { /// - /// The _user manager + /// The user manager. /// private readonly IUserManager _userManager; private readonly ISessionManager _sessionMananger; diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index acda6a4166..964fc9b191 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using System.Text.Json.Serialization; using System.Threading; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Dto; @@ -123,7 +122,6 @@ namespace MediaBrowser.Controller.Session /// Gets or sets the session controller. /// /// The session controller. - [JsonIgnore] public ISessionController[] SessionControllers { get; set; } /// @@ -245,11 +243,6 @@ namespace MediaBrowser.Controller.Session SessionControllers = controllers.ToArray(); } - public bool ContainsUser(string userId) - { - return ContainsUser(new Guid(userId)); - } - public bool ContainsUser(Guid userId) { if (UserId.Equals(userId)) -- cgit v1.2.3 From 26dd67a441d251bef56c14d8058e7068e38e06af Mon Sep 17 00:00:00 2001 From: dkanada Date: Sun, 2 Feb 2020 00:09:18 +0900 Subject: change authentication flow --- Emby.Server.Implementations/Session/SessionManager.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index ec3e875732..659483a361 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1379,20 +1379,16 @@ namespace Emby.Server.Implementations.Session user = _userManager.GetUserByName(request.Username); } - if (user != null) + if (user == null) { - // TODO: Move this to userManager? - if (!string.IsNullOrEmpty(request.DeviceId) - && !_deviceManager.CanAccessDevice(user, request.DeviceId)) - { - throw new SecurityException("User is not allowed access from this device."); - } + AuthenticationFailed?.Invoke(this, new GenericEventArgs(request)); + throw new SecurityException("Invalid username or password entered."); } - if (user == null) + if (!string.IsNullOrEmpty(request.DeviceId) + && !_deviceManager.CanAccessDevice(user, request.DeviceId)) { - AuthenticationFailed?.Invoke(this, new GenericEventArgs(request)); - throw new SecurityException("Invalid user or password entered."); + throw new SecurityException("User is not allowed access from this device."); } if (enforcePassword) -- cgit v1.2.3 From 1bc8ca25421dd3b66a58e1ff0938ee7874d8448e Mon Sep 17 00:00:00 2001 From: dkanada Date: Sun, 2 Feb 2020 00:16:11 +0900 Subject: add session view endpoint --- Emby.Server.Implementations/Session/SessionManager.cs | 10 +++++----- MediaBrowser.Api/Session/SessionsService.cs | 18 ++++++++++++++++++ MediaBrowser.Controller/Session/SessionInfo.cs | 2 ++ 3 files changed, 25 insertions(+), 5 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 659483a361..8e52f24159 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1686,18 +1686,18 @@ namespace Emby.Server.Implementations.Session throw new ArgumentNullException(nameof(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); - //ReportNowViewingItem(sessionId, info); + ReportNowViewingItem(sessionId, info); } public void ReportNowViewingItem(string sessionId, BaseItemDto 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/Session/SessionsService.cs b/MediaBrowser.Api/Session/SessionsService.cs index 8e53490ffc..df816927a5 100644 --- a/MediaBrowser.Api/Session/SessionsService.cs +++ b/MediaBrowser.Api/Session/SessionsService.cs @@ -230,6 +230,17 @@ namespace MediaBrowser.Api.Session public string Id { get; set; } } + [Route("/Sessions/Viewing", "POST", Summary = "Reports that a session is viewing an item")] + [Authenticated] + public class ReportViewing : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string SessionId { get; set; } + + [ApiMember(Name = "ItemId", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string ItemId { get; set; } + } + [Route("/Sessions/Logout", "POST", Summary = "Reports that a session has ended")] [Authenticated] public class ReportSessionEnded : IReturnVoid @@ -536,5 +547,12 @@ namespace MediaBrowser.Api.Session _sessionManager.ReportCapabilities(request.Id, request); } + + public void Post(ReportViewing request) + { + request.SessionId = GetSession(_sessionContext).Id; + + _sessionManager.ReportNowViewingItem(request.SessionId, request.ItemId); + } } } diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 964fc9b191..7bc50eb413 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -106,6 +106,8 @@ namespace MediaBrowser.Controller.Session public BaseItem FullNowPlayingItem { get; set; } + public BaseItemDto NowViewingItem { get; set; } + /// /// Gets or sets the device id. /// -- cgit v1.2.3 From f93edb7ade68588f4e2e7e05932b3266105ea91a Mon Sep 17 00:00:00 2001 From: dkanada Date: Tue, 4 Feb 2020 01:29:18 +0900 Subject: add code suggestions --- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- MediaBrowser.Controller/Session/SessionInfo.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 8e52f24159..4aed226deb 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1494,7 +1494,7 @@ namespace Emby.Server.Implementations.Session { Limit = 1, AccessToken = accessToken - }).Items[0]; + }).Items.FirstOrDefault(); if (existing != null) { diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 7bc50eb413..cc5160d779 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Text.Json.Serialization; using System.Threading; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Dto; @@ -124,6 +125,7 @@ namespace MediaBrowser.Controller.Session /// Gets or sets the session controller. /// /// The session controller. + [JsonIgnore] public ISessionController[] SessionControllers { get; set; } /// -- cgit v1.2.3 From 3fe84c3213b8340f86aec0c5c488f7fc303b26cc Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 6 Feb 2020 13:03:02 +0100 Subject: Apply suggestions from code review Co-Authored-By: dkanada --- Emby.Server.Implementations/Session/SessionManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index f5bd51c4ae..56dd361078 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -102,17 +102,17 @@ namespace Emby.Server.Implementations.Session public event EventHandler> AuthenticationSucceeded; /// - /// Occurs when [playback start]. + /// Occurs when playback has started. /// public event EventHandler PlaybackStart; /// - /// Occurs when [playback progress]. + /// Occurs when playback has progressed. /// public event EventHandler PlaybackProgress; /// - /// Occurs when [playback stopped]. + /// Occurs when playback has stopped. /// public event EventHandler PlaybackStopped; -- cgit v1.2.3 From cefac4f886e82a642380586b3d732e729cd76dd1 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 6 Feb 2020 13:16:22 +0100 Subject: Update Emby.Server.Implementations/Session/SessionManager.cs Co-Authored-By: dkanada --- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 31e574ba65..a6a5523ded 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -161,7 +161,7 @@ namespace Emby.Server.Implementations.Session } /// - /// Releases unmanaged and - optionally - managed resources. + /// Releases unmanaged and optionally managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) -- cgit v1.2.3 From c3562664196cde2a8318209db4117631be658857 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sun, 29 Mar 2020 16:57:13 -0400 Subject: Revert #2146 ordering change --- .../Session/SessionManager.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index dfcd3843c7..de768333d8 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1401,6 +1401,16 @@ namespace Emby.Server.Implementations.Session user = _userManager.GetUserByName(request.Username); } + if (enforcePassword) + { + user = await _userManager.AuthenticateUser( + request.Username, + request.Password, + request.PasswordSha1, + request.RemoteEndPoint, + true).ConfigureAwait(false); + } + if (user == null) { AuthenticationFailed?.Invoke(this, new GenericEventArgs(request)); @@ -1413,16 +1423,6 @@ namespace Emby.Server.Implementations.Session throw new SecurityException("User is not allowed access from this device."); } - if (enforcePassword) - { - user = await _userManager.AuthenticateUser( - request.Username, - request.Password, - request.PasswordSha1, - request.RemoteEndPoint, - true).ConfigureAwait(false); - } - var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName); var session = LogSessionActivity( -- cgit v1.2.3 From 53380689ad00f00efc0c1790f1d25d08c95d7f2d Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Mon, 13 Apr 2020 13:17:46 -0400 Subject: Return correct status codes for authentication and authorization errors - Use AuthenticatonException to return 401 - Use SecurityException to return 403 - Update existing throws to throw the correct exception for the circumstance --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 5 ++++- .../HttpServer/Security/AuthService.cs | 7 ++++--- Emby.Server.Implementations/Library/UserManager.cs | 11 ++++------- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- 4 files changed, 13 insertions(+), 12 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 5ae65a4e3d..f496ff1ba1 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -14,6 +14,7 @@ using Emby.Server.Implementations.Services; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; +using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Events; @@ -230,7 +231,8 @@ namespace Emby.Server.Implementations.HttpServer switch (ex) { case ArgumentException _: return 400; - case SecurityException _: return 401; + case AuthenticationException _: return 401; + case SecurityException _: return 403; case DirectoryNotFoundException _: case FileNotFoundException _: case ResourceNotFoundException _: return 404; @@ -550,6 +552,7 @@ namespace Emby.Server.Implementations.HttpServer || ex is IOException || ex is OperationCanceledException || ex is SecurityException + || ex is AuthenticationException || ex is FileNotFoundException; await ErrorHandler(ex, httpReq, !ignoreStackTrace, urlToLog).ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 1360a5e0ce..256b24924e 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -2,6 +2,7 @@ using System; using System.Linq; +using System.Security.Authentication; using Emby.Server.Implementations.SocketSharp; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; @@ -68,7 +69,7 @@ namespace Emby.Server.Implementations.HttpServer.Security if (user == null && auth.UserId != Guid.Empty) { - throw new SecurityException("User with Id " + auth.UserId + " not found"); + throw new AuthenticationException("User with Id " + auth.UserId + " not found"); } if (user != null) @@ -212,14 +213,14 @@ namespace Emby.Server.Implementations.HttpServer.Security { if (string.IsNullOrEmpty(token)) { - throw new SecurityException("Access token is required."); + throw new AuthenticationException("Access token is required."); } var info = GetTokenInfo(request); if (info == null) { - throw new SecurityException("Access token is invalid or expired."); + throw new AuthenticationException("Access token is invalid or expired."); } //if (!string.IsNullOrEmpty(info.UserId)) diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 7b17cc913f..f92cb6ae6d 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -20,6 +20,7 @@ using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Providers; @@ -324,21 +325,17 @@ namespace Emby.Server.Implementations.Library if (user.Policy.IsDisabled) { - throw new AuthenticationException( - string.Format( - CultureInfo.InvariantCulture, - "The {0} account is currently disabled. Please consult with your administrator.", - user.Name)); + throw new SecurityException($"The {user.Name} account is currently disabled. Please consult with your administrator."); } if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint)) { - throw new AuthenticationException("Forbidden."); + throw new SecurityException("Forbidden."); } if (!user.IsParentalScheduleAllowed()) { - throw new AuthenticationException("User is not allowed access at this time."); + throw new SecurityException("User is not allowed access at this time."); } // Update LastActivityDate and LastLoginDate, then save diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index de768333d8..c93c02c480 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1414,7 +1414,7 @@ namespace Emby.Server.Implementations.Session if (user == null) { AuthenticationFailed?.Invoke(this, new GenericEventArgs(request)); - throw new SecurityException("Invalid username or password entered."); + throw new AuthenticationException("Invalid username or password entered."); } if (!string.IsNullOrEmpty(request.DeviceId) -- cgit v1.2.3 From 36f3e933a23d802d154c16fd304a82c3fe3f453d Mon Sep 17 00:00:00 2001 From: ConfusedPolarBear <33811686+ConfusedPolarBear@users.noreply.github.com> Date: Wed, 15 Apr 2020 14:28:42 -0500 Subject: Add quick connect --- CONTRIBUTORS.md | 1 + Emby.Server.Implementations/ApplicationHost.cs | 3 + .../QuickConnect/QuickConnectManager.cs | 262 +++++++++++++++++++++ .../Session/SessionManager.cs | 18 ++ .../QuickConnect/QuickConnectService.cs | 145 ++++++++++++ MediaBrowser.Api/UserService.cs | 34 +++ .../QuickConnect/IQuickConnect.cs | 91 +++++++ MediaBrowser.Controller/Session/ISessionManager.cs | 2 + .../QuickConnect/QuickConnectResult.cs | 50 ++++ .../QuickConnect/QuickConnectResultDto.cs | 53 +++++ .../QuickConnect/QuickConnectState.cs | 23 ++ 11 files changed, 682 insertions(+) create mode 100644 Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs create mode 100644 MediaBrowser.Api/QuickConnect/QuickConnectService.cs create mode 100644 MediaBrowser.Controller/QuickConnect/IQuickConnect.cs create mode 100644 MediaBrowser.Model/QuickConnect/QuickConnectResult.cs create mode 100644 MediaBrowser.Model/QuickConnect/QuickConnectResultDto.cs create mode 100644 MediaBrowser.Model/QuickConnect/QuickConnectState.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index ce956176e8..edd33a2fb3 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -15,6 +15,7 @@ - [bugfixin](https://github.com/bugfixin) - [chaosinnovator](https://github.com/chaosinnovator) - [ckcr4lyf](https://github.com/ckcr4lyf) + - [ConfusedPolarBear](https://github.com/ConfusedPolarBear) - [crankdoofus](https://github.com/crankdoofus) - [crobibero](https://github.com/crobibero) - [cromefire](https://github.com/cromefire) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 81a80ddb26..de044a4aa2 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -74,6 +74,7 @@ using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.QuickConnect; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; @@ -857,6 +858,8 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(typeof(IAttachmentExtractor), typeof(MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor)); + serviceCollection.AddSingleton(typeof(IQuickConnect), typeof(QuickConnect.QuickConnectManager)); + _displayPreferencesRepository.Initialize(); var userDataRepo = new SqliteUserDataRepository(LoggerFactory.CreateLogger(), ApplicationPaths); diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs new file mode 100644 index 0000000000..30418097ca --- /dev/null +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs @@ -0,0 +1,262 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Security.Cryptography; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.QuickConnect; +using MediaBrowser.Controller.Security; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.QuickConnect; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Services; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.QuickConnect +{ + /// + /// Quick connect implementation. + /// + public class QuickConnectManager : IQuickConnect + { + private readonly RNGCryptoServiceProvider _rng = new RNGCryptoServiceProvider(); + private Dictionary _currentRequests = new Dictionary(); + + private ILogger _logger; + private IUserManager _userManager; + private ILocalizationManager _localizationManager; + private IJsonSerializer _jsonSerializer; + private IAuthenticationRepository _authenticationRepository; + private IAuthorizationContext _authContext; + private IServerApplicationHost _appHost; + + /// + /// Initializes a new instance of the class. + /// Should only be called at server startup when a singleton is created. + /// + /// Logger. + /// User manager. + /// Localization. + /// JSON serializer. + /// Application host. + /// Authentication context. + /// Authentication repository. + public QuickConnectManager( + ILoggerFactory loggerFactory, + IUserManager userManager, + ILocalizationManager localization, + IJsonSerializer jsonSerializer, + IServerApplicationHost appHost, + IAuthorizationContext authContext, + IAuthenticationRepository authenticationRepository) + { + _logger = loggerFactory.CreateLogger(nameof(QuickConnectManager)); + _userManager = userManager; + _localizationManager = localization; + _jsonSerializer = jsonSerializer; + _appHost = appHost; + _authContext = authContext; + _authenticationRepository = authenticationRepository; + } + + /// + public int CodeLength { get; set; } = 6; + + /// + public string TokenNamePrefix { get; set; } = "QuickConnect-"; + + /// + public QuickConnectState State { get; private set; } = QuickConnectState.Unavailable; + + /// + public int RequestExpiry { get; set; } = 30; + + /// + public void AssertActive() + { + if (State != QuickConnectState.Active) + { + throw new InvalidOperationException("Quick connect is not active on this server"); + } + } + + /// + public void SetEnabled(QuickConnectState newState) + { + _logger.LogDebug("Changed quick connect state from {0} to {1}", State, newState); + + State = newState; + } + + /// + public QuickConnectResult TryConnect(string friendlyName) + { + if (State != QuickConnectState.Active) + { + _logger.LogDebug("Refusing quick connect initiation request, current state is {0}", State); + + return new QuickConnectResult() + { + Error = "Quick connect is not active on this server" + }; + } + + _logger.LogDebug("Got new quick connect request from {friendlyName}", friendlyName); + + var lookup = GenerateSecureRandom(); + var result = new QuickConnectResult() + { + Lookup = lookup, + Secret = GenerateSecureRandom(), + FriendlyName = friendlyName, + DateAdded = DateTime.Now, + Code = GenerateCode() + }; + + _currentRequests[lookup] = result; + return result; + } + + /// + public QuickConnectResult CheckRequestStatus(string secret) + { + AssertActive(); + ExpireRequests(); + + string lookup = _currentRequests.Where(x => x.Value.Secret == secret).Select(x => x.Value.Lookup).DefaultIfEmpty(string.Empty).First(); + + _logger.LogDebug("Transformed private identifier {0} into public lookup {1}", secret, lookup); + + if (!_currentRequests.ContainsKey(lookup)) + { + throw new KeyNotFoundException("Unable to find request with provided identifier"); + } + + return _currentRequests[lookup]; + } + + /// + public List GetCurrentRequests() + { + return GetCurrentRequestsInternal().Select(x => (QuickConnectResultDto)x).ToList(); + } + + /// + public List GetCurrentRequestsInternal() + { + AssertActive(); + ExpireRequests(); + return _currentRequests.Values.ToList(); + } + + /// + public string GenerateCode() + { + // TODO: output may be biased + + int min = (int)Math.Pow(10, CodeLength - 1); + int max = (int)Math.Pow(10, CodeLength); + + uint scale = uint.MaxValue; + while (scale == uint.MaxValue) + { + byte[] raw = new byte[4]; + _rng.GetBytes(raw); + scale = BitConverter.ToUInt32(raw, 0); + } + + int code = (int)(min + (max - min) * (scale / (double)uint.MaxValue)); + return code.ToString(CultureInfo.InvariantCulture); + } + + /// + public bool AuthorizeRequest(IRequest request, string lookup) + { + AssertActive(); + + var auth = _authContext.GetAuthorizationInfo(request); + + ExpireRequests(); + + if (!_currentRequests.ContainsKey(lookup)) + { + throw new KeyNotFoundException("Unable to find request"); + } + + QuickConnectResult result = _currentRequests[lookup]; + + if (result.Authenticated) + { + throw new InvalidOperationException("Request is already authorized"); + } + + result.Authentication = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); + + // Advance the time on the request so it expires sooner as the client will pick up the changes in a few seconds + result.DateAdded = result.DateAdded.Subtract(new TimeSpan(0, RequestExpiry - 1, 0)); + + _authenticationRepository.Create(new AuthenticationInfo + { + AppName = TokenNamePrefix + result.FriendlyName, + AccessToken = result.Authentication, + DateCreated = DateTime.UtcNow, + DeviceId = _appHost.SystemId, + DeviceName = _appHost.FriendlyName, + AppVersion = _appHost.ApplicationVersionString, + UserId = auth.UserId + }); + + return true; + } + + /// + public int DeleteAllDevices(Guid user) + { + var raw = _authenticationRepository.Get(new AuthenticationInfoQuery() + { + DeviceId = _appHost.SystemId, + UserId = user + }); + + var tokens = raw.Items.Where(x => x.AppName.StartsWith(TokenNamePrefix, StringComparison.CurrentCulture)); + + foreach (var token in tokens) + { + _authenticationRepository.Delete(token); + _logger.LogDebug("Deleted token {0}", token.AccessToken); + } + + return tokens.Count(); + } + + private string GenerateSecureRandom(int length = 32) + { + var bytes = new byte[length]; + _rng.GetBytes(bytes); + + return string.Join(string.Empty, bytes.Select(x => x.ToString("x2", CultureInfo.InvariantCulture))); + } + + private void ExpireRequests() + { + var delete = new List(); + var values = _currentRequests.Values.ToList(); + + for (int i = 0; i < _currentRequests.Count; i++) + { + if (DateTime.Now > values[i].DateAdded.AddMinutes(RequestExpiry)) + { + delete.Add(values[i].Lookup); + } + } + + foreach (var lookup in delete) + { + _logger.LogDebug("Removing expired request {0}", lookup); + _currentRequests.Remove(lookup); + } + } + } +} diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index de768333d8..2c8b2f29d7 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1386,6 +1386,24 @@ namespace Emby.Server.Implementations.Session return AuthenticateNewSessionInternal(request, false); } + public Task AuthenticateQuickConnect(AuthenticationRequest request, string token) + { + var result = _authRepo.Get(new AuthenticationInfoQuery() + { + AccessToken = token, + DeviceId = _appHost.SystemId, + Limit = 1 + }); + + if(result.TotalRecordCount < 1) + { + throw new SecurityException("Unknown quick connect token"); + } + + request.UserId = result.Items[0].UserId; + return AuthenticateNewSessionInternal(request, false); + } + private async Task AuthenticateNewSessionInternal(AuthenticationRequest request, bool enforcePassword) { CheckDisposed(); diff --git a/MediaBrowser.Api/QuickConnect/QuickConnectService.cs b/MediaBrowser.Api/QuickConnect/QuickConnectService.cs new file mode 100644 index 0000000000..889a788391 --- /dev/null +++ b/MediaBrowser.Api/QuickConnect/QuickConnectService.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.QuickConnect; +using MediaBrowser.Model.QuickConnect; +using MediaBrowser.Model.Services; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Api.QuickConnect +{ + [Route("/QuickConnect/Initiate", "GET", Summary = "Requests a new quick connect code")] + public class Initiate : IReturn + { + [ApiMember(Name = "FriendlyName", Description = "Device friendly name", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string FriendlyName { get; set; } + } + + [Route("/QuickConnect/Connect", "GET", Summary = "Attempts to retrieve authentication information")] + public class Connect : IReturn + { + [ApiMember(Name = "Secret", Description = "Quick connect secret", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public string Secret { get; set; } + } + + [Route("/QuickConnect/List", "GET", Summary = "Lists all quick connect requests")] + [Authenticated] + public class QuickConnectList : IReturn> + { + } + + [Route("/QuickConnect/Authorize", "POST", Summary = "Authorizes a pending quick connect request")] + [Authenticated] + public class Authorize : IReturn + { + [ApiMember(Name = "Lookup", Description = "Quick connect public lookup", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public string Lookup { get; set; } + } + + [Route("/QuickConnect/Deauthorize", "POST", Summary = "Deletes all quick connect authorization tokens for the current user")] + [Authenticated] + public class Deauthorize : IReturn + { + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public Guid UserId { get; set; } + } + + [Route("/QuickConnect/Status", "GET", Summary = "Gets the current quick connect state")] + public class QuickConnectStatus : IReturn + { + + } + + [Route("/QuickConnect/Available", "POST", Summary = "Enables or disables quick connect")] + [Authenticated(Roles = "Admin")] + public class Available : IReturn + { + [ApiMember(Name = "Status", Description = "New quick connect status", IsRequired = false, DataType = "QuickConnectState", ParameterType = "query", Verb = "GET")] + public QuickConnectState Status { get; set; } + } + + [Route("/QuickConnect/Activate", "POST", Summary = "Temporarily activates quick connect for the time period defined in the server configuration")] + [Authenticated] + public class Activate : IReturn + { + } + + public class QuickConnectService : BaseApiService + { + private IQuickConnect _quickConnect; + private IUserManager _userManager; + private IAuthorizationContext _authContext; + + public QuickConnectService( + ILogger logger, + IServerConfigurationManager serverConfigurationManager, + IHttpResultFactory httpResultFactory, + IUserManager userManager, + IAuthorizationContext authContext, + IQuickConnect quickConnect) + : base(logger, serverConfigurationManager, httpResultFactory) + { + _userManager = userManager; + _quickConnect = quickConnect; + _authContext = authContext; + } + + public object Get(Initiate request) + { + return _quickConnect.TryConnect(request.FriendlyName); + } + + public object Get(Connect request) + { + return _quickConnect.CheckRequestStatus(request.Secret); + } + + public object Get(QuickConnectList request) + { + return _quickConnect.GetCurrentRequests(); + } + + public object Get(QuickConnectStatus request) + { + return _quickConnect.State; + } + + public object Post(Deauthorize request) + { + AssertCanUpdateUser(_authContext, _userManager, request.UserId, true); + + return _quickConnect.DeleteAllDevices(request.UserId); + } + + public object Post(Authorize request) + { + bool result = _quickConnect.AuthorizeRequest(Request, request.Lookup); + + Logger.LogInformation("Result of authorizing quick connect {0}: {1}", request.Lookup[..10], result); + + return result; + } + + public object Post(Activate request) + { + if (_quickConnect.State == QuickConnectState.Available) + { + _quickConnect.SetEnabled(QuickConnectState.Active); + + string name = _authContext.GetAuthorizationInfo(Request).User.Name; + Logger.LogInformation("{name} enabled quick connect", name); + } + + return _quickConnect.State; + } + + public object Post(Available request) + { + _quickConnect.SetEnabled(request.Status); + + return _quickConnect.State; + } + } +} diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 4015143497..ebcacd2a3d 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -117,6 +117,17 @@ namespace MediaBrowser.Api public string Pw { get; set; } } + [Route("/Users/AuthenticateWithQuickConnect", "POST", Summary = "Authenticates a user")] + public class AuthenticateUserQuickConnect : IReturn + { + /// + /// Gets or sets the token. + /// + /// The token + [ApiMember(Name = "Token", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")] + public string Token { get; set; } + } + /// /// Class UpdateUserPassword /// @@ -430,6 +441,29 @@ namespace MediaBrowser.Api } } + public async Task Post(AuthenticateUserQuickConnect request) + { + var auth = _authContext.GetAuthorizationInfo(Request); + + try + { + var result = await _sessionMananger.AuthenticateQuickConnect(new AuthenticationRequest + { + App = auth.Client, + AppVersion = auth.Version, + DeviceId = auth.DeviceId, + DeviceName = auth.Device + }, request.Token).ConfigureAwait(false); + + return ToOptimizedResult(result); + } + catch (SecurityException e) + { + // rethrow adding IP address to message + throw new SecurityException($"[{Request.RemoteIp}] {e.Message}"); + } + } + /// /// Posts the specified request. /// diff --git a/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs b/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs new file mode 100644 index 0000000000..e4a790ffe7 --- /dev/null +++ b/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Model.QuickConnect; +using MediaBrowser.Model.Services; + +namespace MediaBrowser.Controller.QuickConnect +{ + /// + /// Quick connect standard interface. + /// + public interface IQuickConnect + { + /// + /// Gets or sets the length of user facing codes. + /// + public int CodeLength { get; set; } + + /// + /// Gets or sets the string to prefix internal access tokens with. + /// + public string TokenNamePrefix { get; set; } + + /// + /// Gets the current state of quick connect. + /// + public QuickConnectState State { get; } + + /// + /// Gets or sets the time (in minutes) before a pending request will expire. + /// + public int RequestExpiry { get; set; } + + /// + /// Assert that quick connect is currently active and throws an exception if it is not. + /// + void AssertActive(); + + /// + /// Changes the status of quick connect. + /// + /// New state to change to + void SetEnabled(QuickConnectState newState); + + /// + /// Initiates a new quick connect request. + /// + /// Friendly device name to display in the request UI. + /// A quick connect result with tokens to proceed or a descriptive error message otherwise. + QuickConnectResult TryConnect(string friendlyName); + + /// + /// Checks the status of an individual request. + /// + /// Unique secret identifier of the request. + /// Quick connect result. + QuickConnectResult CheckRequestStatus(string secret); + + /// + /// Returns all current quick connect requests as DTOs. Does not include sensitive information. + /// + /// List of all quick connect results. + List GetCurrentRequests(); + + /// + /// Returns all current quick connect requests (including sensitive information). + /// + /// List of all quick connect results. + List GetCurrentRequestsInternal(); + + /// + /// Authorizes a quick connect request to connect as the calling user. + /// + /// HTTP request object. + /// Public request lookup value. + /// A boolean indicating if the authorization completed successfully. + bool AuthorizeRequest(IRequest request, string lookup); + + /// + /// Deletes all quick connect access tokens for the provided user. + /// + /// Guid of the user to delete tokens for. + /// A count of the deleted tokens. + int DeleteAllDevices(Guid user); + + /// + /// Generates a short code to display to the user to uniquely identify this request. + /// + /// A short, unique alphanumeric string. + string GenerateCode(); + } +} diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 771027103b..74ffd5a181 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -246,6 +246,8 @@ namespace MediaBrowser.Controller.Session /// Task{SessionInfo}. Task AuthenticateNewSession(AuthenticationRequest request); + public Task AuthenticateQuickConnect(AuthenticationRequest request, string token); + /// /// Creates the new session. /// diff --git a/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs b/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs new file mode 100644 index 0000000000..bc3fd00466 --- /dev/null +++ b/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs @@ -0,0 +1,50 @@ +using System; + +namespace MediaBrowser.Model.QuickConnect +{ + /// + /// Stores the result of an incoming quick connect request. + /// + public class QuickConnectResult + { + /// + /// Gets a value indicating whether this request is authorized. + /// + public bool Authenticated => !string.IsNullOrEmpty(Authentication); + + /// + /// Gets or sets the secret value used to uniquely identify this request. Can be used to retrieve authentication information. + /// + public string Secret { get; set; } + + /// + /// Gets or sets the public value used to uniquely identify this request. Can only be used to authorize the request. + /// + public string Lookup { get; set; } + + /// + /// Gets or sets the user facing code used so the user can quickly differentiate this request from others. + /// + public string Code { get; set; } + + /// + /// Gets or sets the device friendly name. + /// + public string FriendlyName { get; set; } + + /// + /// Gets or sets the private access token. + /// + public string Authentication { get; set; } + + /// + /// Gets or sets an error message. + /// + public string Error { get; set; } + + /// + /// Gets or sets the DateTime that this request was created. + /// + public DateTime DateAdded { get; set; } + } +} diff --git a/MediaBrowser.Model/QuickConnect/QuickConnectResultDto.cs b/MediaBrowser.Model/QuickConnect/QuickConnectResultDto.cs new file mode 100644 index 0000000000..671b7cc943 --- /dev/null +++ b/MediaBrowser.Model/QuickConnect/QuickConnectResultDto.cs @@ -0,0 +1,53 @@ +using System; + +namespace MediaBrowser.Model.QuickConnect +{ + /// + /// Stores the non-sensitive results of an incoming quick connect request. + /// + public class QuickConnectResultDto + { + /// + /// Gets a value indicating whether this request is authorized. + /// + public bool Authenticated { get; private set; } + + /// + /// Gets the user facing code used so the user can quickly differentiate this request from others. + /// + public string Code { get; private set; } + + /// + /// Gets the public value used to uniquely identify this request. Can only be used to authorize the request. + /// + public string Lookup { get; private set; } + + /// + /// Gets the device friendly name. + /// + public string FriendlyName { get; private set; } + + /// + /// Gets the DateTime that this request was created. + /// + public DateTime DateAdded { get; private set; } + + /// + /// Cast an internal quick connect result to a DTO by removing all sensitive properties. + /// + /// QuickConnectResult object to cast + public static implicit operator QuickConnectResultDto(QuickConnectResult result) + { + QuickConnectResultDto resultDto = new QuickConnectResultDto + { + Authenticated = result.Authenticated, + Code = result.Code, + FriendlyName = result.FriendlyName, + DateAdded = result.DateAdded, + Lookup = result.Lookup + }; + + return resultDto; + } + } +} diff --git a/MediaBrowser.Model/QuickConnect/QuickConnectState.cs b/MediaBrowser.Model/QuickConnect/QuickConnectState.cs new file mode 100644 index 0000000000..9f250519b1 --- /dev/null +++ b/MediaBrowser.Model/QuickConnect/QuickConnectState.cs @@ -0,0 +1,23 @@ +namespace MediaBrowser.Model.QuickConnect +{ + /// + /// Quick connect state. + /// + public enum QuickConnectState + { + /// + /// This feature has not been opted into and is unavailable until the server administrator chooses to opt-in. + /// + Unavailable, + + /// + /// The feature is enabled for use on the server but is not currently accepting connection requests. + /// + Available, + + /// + /// The feature is actively accepting connection requests. + /// + Active + } +} -- cgit v1.2.3 From 10c2c62f07fb4088480ff580ab67c1bc10a057a4 Mon Sep 17 00:00:00 2001 From: gion Date: Wed, 1 Apr 2020 17:52:42 +0200 Subject: Implement syncplay backend --- Emby.Server.Implementations/ApplicationHost.cs | 4 + .../Session/SessionManager.cs | 17 + .../Syncplay/SyncplayController.cs | 418 +++++++++++++++++++++ .../Syncplay/SyncplayManager.cs | 248 ++++++++++++ MediaBrowser.Api/Syncplay/SyncplayService.cs | 261 +++++++++++++ MediaBrowser.Controller/Session/ISessionManager.cs | 19 + MediaBrowser.Controller/Syncplay/GroupInfo.cs | 148 ++++++++ MediaBrowser.Controller/Syncplay/GroupMember.cs | 28 ++ .../Syncplay/ISyncplayController.cs | 61 +++ .../Syncplay/ISyncplayManager.cs | 62 +++ MediaBrowser.Model/Syncplay/GroupInfoModel.cs | 38 ++ MediaBrowser.Model/Syncplay/SyncplayCommand.cs | 32 ++ MediaBrowser.Model/Syncplay/SyncplayCommandType.cs | 21 ++ MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs | 26 ++ .../Syncplay/SyncplayGroupUpdateType.cs | 41 ++ MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs | 34 ++ MediaBrowser.Model/Syncplay/SyncplayRequestType.cs | 33 ++ 17 files changed, 1491 insertions(+) create mode 100644 Emby.Server.Implementations/Syncplay/SyncplayController.cs create mode 100644 Emby.Server.Implementations/Syncplay/SyncplayManager.cs create mode 100644 MediaBrowser.Api/Syncplay/SyncplayService.cs create mode 100644 MediaBrowser.Controller/Syncplay/GroupInfo.cs create mode 100644 MediaBrowser.Controller/Syncplay/GroupMember.cs create mode 100644 MediaBrowser.Controller/Syncplay/ISyncplayController.cs create mode 100644 MediaBrowser.Controller/Syncplay/ISyncplayManager.cs create mode 100644 MediaBrowser.Model/Syncplay/GroupInfoModel.cs create mode 100644 MediaBrowser.Model/Syncplay/SyncplayCommand.cs create mode 100644 MediaBrowser.Model/Syncplay/SyncplayCommandType.cs create mode 100644 MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs create mode 100644 MediaBrowser.Model/Syncplay/SyncplayGroupUpdateType.cs create mode 100644 MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs create mode 100644 MediaBrowser.Model/Syncplay/SyncplayRequestType.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 4d906a1bf8..8419014c2c 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -47,6 +47,7 @@ using Emby.Server.Implementations.Session; using Emby.Server.Implementations.SocketSharp; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; +using Emby.Server.Implementations.Syncplay; using MediaBrowser.Api; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; @@ -80,6 +81,7 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Subtitles; using MediaBrowser.Controller.TV; +using MediaBrowser.Controller.Syncplay; using MediaBrowser.LocalMetadata.Savers; using MediaBrowser.MediaEncoding.BdInfo; using MediaBrowser.Model.Activity; @@ -643,6 +645,8 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index c93c02c480..b1519b5729 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -25,6 +25,7 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; +using MediaBrowser.Model.Syncplay; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Session @@ -1154,6 +1155,22 @@ namespace Emby.Server.Implementations.Session await SendMessageToSession(session, "Play", command, cancellationToken).ConfigureAwait(false); } + /// + public async Task SendSyncplayCommand(string sessionId, SyncplayCommand command, CancellationToken cancellationToken) + { + CheckDisposed(); + var session = GetSessionToRemoteControl(sessionId); + await SendMessageToSession(session, "SyncplayCommand", command, cancellationToken).ConfigureAwait(false); + } + + /// + public async Task SendSyncplayGroupUpdate(string sessionId, SyncplayGroupUpdate command, CancellationToken cancellationToken) + { + CheckDisposed(); + var session = GetSessionToRemoteControl(sessionId); + await SendMessageToSession(session, "SyncplayGroupUpdate", command, cancellationToken).ConfigureAwait(false); + } + private IEnumerable TranslateItemForPlayback(Guid id, User user) { var item = _libraryManager.GetItemById(id); diff --git a/Emby.Server.Implementations/Syncplay/SyncplayController.cs b/Emby.Server.Implementations/Syncplay/SyncplayController.cs new file mode 100644 index 0000000000..4a20ceba04 --- /dev/null +++ b/Emby.Server.Implementations/Syncplay/SyncplayController.cs @@ -0,0 +1,418 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.Syncplay; +using MediaBrowser.Model.Session; +using MediaBrowser.Model.Syncplay; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Syncplay +{ + /// + /// Class SyncplayController. + /// + public class SyncplayController : ISyncplayController, IDisposable + { + private enum BroadcastType + { + AllGroup = 0, + SingleUser = 1, + AllExceptUser = 2, + AllReady = 3 + } + + /// + /// The logger. + /// + private readonly ILogger _logger; + + /// + /// The session manager. + /// + private readonly ISessionManager _sessionManager; + + /// + /// The syncplay manager. + /// + private readonly ISyncplayManager _syncplayManager; + + /// + /// The group to manage. + /// + private readonly GroupInfo _group = new GroupInfo(); + + /// + public Guid GetGroupId() => _group.GroupId; + + /// + public Guid GetPlayingItemId() => _group.PlayingItem.Id; + + /// + public bool IsGroupEmpty() => _group.IsEmpty(); + + private bool _disposed = false; + + public SyncplayController( + ILogger logger, + ISessionManager sessionManager, + ISyncplayManager syncplayManager) + { + _logger = logger; + _sessionManager = sessionManager; + _syncplayManager = syncplayManager; + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and optionally managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + _disposed = true; + } + + // TODO: use this somewhere + private void CheckDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + } + + private SessionInfo[] FilterUsers(SessionInfo from, BroadcastType type) + { + if (type == BroadcastType.SingleUser) + { + return new SessionInfo[] { from }; + } + else if (type == BroadcastType.AllGroup) + { + return _group.Partecipants.Values.Select( + user => user.Session + ).ToArray(); + } + else if (type == BroadcastType.AllExceptUser) + { + return _group.Partecipants.Values.Select( + user => user.Session + ).Where( + user => !user.Id.Equals(from.Id) + ).ToArray(); + } + else if (type == BroadcastType.AllReady) + { + return _group.Partecipants.Values.Where( + user => !user.IsBuffering + ).Select( + user => user.Session + ).ToArray(); + } + else + { + return new SessionInfo[] {}; + } + } + + private Task SendGroupUpdate(SessionInfo from, BroadcastType type, SyncplayGroupUpdate message) + { + IEnumerable GetTasks() + { + SessionInfo[] users = FilterUsers(from, type); + foreach (var user in users) + { + yield return _sessionManager.SendSyncplayGroupUpdate(user.Id.ToString(), message, CancellationToken.None); + } + } + + return Task.WhenAll(GetTasks()); + } + + private Task SendCommand(SessionInfo from, BroadcastType type, SyncplayCommand message) + { + IEnumerable GetTasks() + { + SessionInfo[] users = FilterUsers(from, type); + foreach (var user in users) + { + yield return _sessionManager.SendSyncplayCommand(user.Id.ToString(), message, CancellationToken.None); + } + } + + return Task.WhenAll(GetTasks()); + } + + private SyncplayCommand NewSyncplayCommand(SyncplayCommandType type) { + var command = new SyncplayCommand(); + command.GroupId = _group.GroupId.ToString(); + command.Command = type; + command.PositionTicks = _group.PositionTicks; + command.When = _group.LastActivity.ToUniversalTime().ToString("o"); + return command; + } + + private SyncplayGroupUpdate NewSyncplayGroupUpdate(SyncplayGroupUpdateType type, T data) + { + var command = new SyncplayGroupUpdate(); + command.GroupId = _group.GroupId.ToString(); + command.Type = type; + command.Data = data; + return command; + } + + /// + public void InitGroup(SessionInfo user) + { + _group.AddUser(user); + _syncplayManager.MapUserToGroup(user, this); + + _group.PlayingItem = user.FullNowPlayingItem; + _group.IsPaused = true; + _group.PositionTicks = user.PlayState.PositionTicks ??= 0; + _group.LastActivity = DateTime.UtcNow; + + var updateUser = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); + SendGroupUpdate(user, BroadcastType.SingleUser, updateUser); + var pauseCommand = NewSyncplayCommand(SyncplayCommandType.Pause); + SendCommand(user, BroadcastType.SingleUser, pauseCommand); + } + + /// + public void UserJoin(SessionInfo user) + { + if (user.NowPlayingItem != null && user.NowPlayingItem.Id.Equals(_group.PlayingItem.Id)) + { + _group.AddUser(user); + _syncplayManager.MapUserToGroup(user, this); + + var updateUser = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupJoined, _group.PositionTicks); + SendGroupUpdate(user, BroadcastType.SingleUser, updateUser); + + var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.UserJoined, user.UserName); + SendGroupUpdate(user, BroadcastType.AllExceptUser, updateOthers); + + // Client join and play, syncing will happen client side + if (!_group.IsPaused) + { + var playCommand = NewSyncplayCommand(SyncplayCommandType.Play); + SendCommand(user, BroadcastType.SingleUser, playCommand); + } + else + { + var pauseCommand = NewSyncplayCommand(SyncplayCommandType.Pause); + SendCommand(user, BroadcastType.SingleUser, pauseCommand); + } + } + else + { + var playRequest = new PlayRequest(); + playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id }; + playRequest.StartPositionTicks = _group.PositionTicks; + var update = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.PrepareSession, playRequest); + SendGroupUpdate(user, BroadcastType.SingleUser, update); + } + } + + /// + public void UserLeave(SessionInfo user) + { + _group.RemoveUser(user); + _syncplayManager.UnmapUserFromGroup(user, this); + + var updateUser = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupLeft, _group.PositionTicks); + SendGroupUpdate(user, BroadcastType.SingleUser, updateUser); + + var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.UserLeft, user.UserName); + SendGroupUpdate(user, BroadcastType.AllExceptUser, updateOthers); + } + + /// + public void HandleRequest(SessionInfo user, SyncplayRequestInfo request) + { + if (request.Type.Equals(SyncplayRequestType.Play)) + { + if (_group.IsPaused) + { + var delay = _group.GetHighestPing() * 2; + delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; + + _group.IsPaused = false; + _group.LastActivity = DateTime.UtcNow.AddMilliseconds( + delay + ); + + var command = NewSyncplayCommand(SyncplayCommandType.Play); + SendCommand(user, BroadcastType.AllGroup, command); + } + else + { + // Client got lost + var command = NewSyncplayCommand(SyncplayCommandType.Play); + SendCommand(user, BroadcastType.SingleUser, command); + } + } + else if (request.Type.Equals(SyncplayRequestType.Pause)) + { + if (!_group.IsPaused) + { + _group.IsPaused = true; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - _group.LastActivity; + _group.LastActivity = currentTime; + _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; + + var command = NewSyncplayCommand(SyncplayCommandType.Pause); + SendCommand(user, BroadcastType.AllGroup, command); + } + else + { + var command = NewSyncplayCommand(SyncplayCommandType.Pause); + SendCommand(user, BroadcastType.SingleUser, command); + } + } + else if (request.Type.Equals(SyncplayRequestType.Seek)) + { + // Sanitize PositionTicks + var ticks = request.PositionTicks ??= 0; + ticks = ticks >= 0 ? ticks : 0; + if (_group.PlayingItem.RunTimeTicks != null) + { + var runTimeTicks = _group.PlayingItem.RunTimeTicks ??= 0; + ticks = ticks > runTimeTicks ? runTimeTicks : ticks; + } + + _group.IsPaused = true; + _group.PositionTicks = ticks; + _group.LastActivity = DateTime.UtcNow; + + var command = NewSyncplayCommand(SyncplayCommandType.Seek); + SendCommand(user, BroadcastType.AllGroup, command); + } + // TODO: client does not implement this yet + else if (request.Type.Equals(SyncplayRequestType.Buffering)) + { + if (!_group.IsPaused) + { + _group.IsPaused = true; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - _group.LastActivity; + _group.LastActivity = currentTime; + _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; + + _group.SetBuffering(user, true); + + // Send pause command to all non-buffering users + var command = NewSyncplayCommand(SyncplayCommandType.Pause); + SendCommand(user, BroadcastType.AllReady, command); + + var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupWait, user.UserName); + SendGroupUpdate(user, BroadcastType.AllExceptUser, updateOthers); + } + else + { + var command = NewSyncplayCommand(SyncplayCommandType.Pause); + SendCommand(user, BroadcastType.SingleUser, command); + } + } + // TODO: client does not implement this yet + else if (request.Type.Equals(SyncplayRequestType.BufferingComplete)) + { + if (_group.IsPaused) + { + _group.SetBuffering(user, false); + + if (_group.IsBuffering()) { + // Others are buffering, tell this client to pause when ready + var when = request.When ??= DateTime.UtcNow; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - when; + var clientPosition = TimeSpan.FromTicks(request.PositionTicks ??= 0) + elapsedTime; + var delay = _group.PositionTicks - clientPosition.Ticks; + + var command = NewSyncplayCommand(SyncplayCommandType.Pause); + command.When = currentTime.AddMilliseconds( + delay + ).ToUniversalTime().ToString("o"); + SendCommand(user, BroadcastType.SingleUser, command); + } + else + { + // Let other clients resume as soon as the buffering client catches up + var when = request.When ??= DateTime.UtcNow; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - when; + var clientPosition = TimeSpan.FromTicks(request.PositionTicks ??= 0) + elapsedTime; + var delay = _group.PositionTicks - clientPosition.Ticks; + + _group.IsPaused = false; + + if (delay > _group.GetHighestPing() * 2) + { + // Client that was buffering is recovering, notifying others to resume + _group.LastActivity = currentTime.AddMilliseconds( + delay + ); + var command = NewSyncplayCommand(SyncplayCommandType.Play); + SendCommand(user, BroadcastType.AllExceptUser, command); + } + else + { + // Client, that was buffering, resumed playback but did not update others in time + delay = _group.GetHighestPing() * 2; + delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; + + _group.LastActivity = currentTime.AddMilliseconds( + delay + ); + + var command = NewSyncplayCommand(SyncplayCommandType.Play); + SendCommand(user, BroadcastType.AllGroup, command); + } + } + } + else + { + // Make sure client has latest group state + var command = NewSyncplayCommand(SyncplayCommandType.Play); + SendCommand(user, BroadcastType.SingleUser, command); + } + } + else if (request.Type.Equals(SyncplayRequestType.KeepAlive)) + { + _group.UpdatePing(user, request.Ping ??= _group.DefaulPing); + + var keepAlive = new SyncplayGroupUpdate(); + keepAlive.GroupId = _group.GroupId.ToString(); + keepAlive.Type = SyncplayGroupUpdateType.KeepAlive; + SendGroupUpdate(user, BroadcastType.SingleUser, keepAlive); + } + } + + /// + public GroupInfoView GetInfo() + { + var info = new GroupInfoView(); + info.GroupId = GetGroupId().ToString(); + info.PlayingItemName = _group.PlayingItem.Name; + info.PlayingItemId = _group.PlayingItem.Id.ToString(); + info.PositionTicks = _group.PositionTicks; + info.Partecipants = _group.Partecipants.Values.Select(user => user.Session.UserName).ToArray(); + return info; + } + } +} diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs new file mode 100644 index 0000000000..6bfd6aa9b0 --- /dev/null +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -0,0 +1,248 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Microsoft.Extensions.Logging; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.Syncplay; +using MediaBrowser.Model.Syncplay; + +namespace Emby.Server.Implementations.Syncplay +{ + /// + /// Class SyncplayManager. + /// + public class SyncplayManager : ISyncplayManager, IDisposable + { + /// + /// The logger. + /// + private readonly ILogger _logger; + + /// + /// The session manager. + /// + private readonly ISessionManager _sessionManager; + + /// + /// The map between users and groups. + /// + private readonly ConcurrentDictionary _userToGroupMap = + new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// The groups. + /// + private readonly ConcurrentDictionary _groups = + new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + private bool _disposed = false; + + public SyncplayManager( + ILogger logger, + ISessionManager sessionManager) + { + _logger = logger; + _sessionManager = sessionManager; + + _sessionManager.SessionEnded += _sessionManager_SessionEnded; + _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped; + } + + /// + /// Gets all groups. + /// + /// All groups. + public IEnumerable Groups => _groups.Values; + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and optionally managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + _sessionManager.SessionEnded -= _sessionManager_SessionEnded; + _sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped; + + _disposed = true; + } + + private void CheckDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + } + + void _sessionManager_SessionEnded(object sender, SessionEventArgs e) + { + LeaveGroup(e.SessionInfo); + } + + void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) + { + LeaveGroup(e.Session); + } + + private bool IsUserInGroup(SessionInfo user) + { + return _userToGroupMap.ContainsKey(user.Id); + } + + private Guid? GetUserGroup(SessionInfo user) + { + ISyncplayController group; + _userToGroupMap.TryGetValue(user.Id, out group); + if (group != null) + { + return group.GetGroupId(); + } + else + { + return null; + } + } + + /// + public void NewGroup(SessionInfo user) + { + if (IsUserInGroup(user)) + { + LeaveGroup(user); + } + + var group = new SyncplayController(_logger, _sessionManager, this); + _groups[group.GetGroupId().ToString()] = group; + + group.InitGroup(user); + } + + /// + public void JoinGroup(SessionInfo user, string groupId) + { + if (IsUserInGroup(user)) + { + if (GetUserGroup(user).Equals(groupId)) return; + LeaveGroup(user); + } + + ISyncplayController group; + _groups.TryGetValue(groupId, out group); + + if (group == null) + { + _logger.LogError("Syncplaymanager JoinGroup: " + groupId + " does not exist."); + + var update = new SyncplayGroupUpdate(); + update.Type = SyncplayGroupUpdateType.NotInGroup; + _sessionManager.SendSyncplayGroupUpdate(user.Id.ToString(), update, CancellationToken.None); + return; + } + group.UserJoin(user); + } + + /// + public void LeaveGroup(SessionInfo user) + { + ISyncplayController group; + _userToGroupMap.TryGetValue(user.Id, out group); + + if (group == null) + { + _logger.LogWarning("Syncplaymanager HandleRequest: " + user.Id + " not in group."); + + var update = new SyncplayGroupUpdate(); + update.Type = SyncplayGroupUpdateType.NotInGroup; + _sessionManager.SendSyncplayGroupUpdate(user.Id.ToString(), update, CancellationToken.None); + return; + } + group.UserLeave(user); + + if (group.IsGroupEmpty()) + { + _groups.Remove(group.GetGroupId().ToString(), out _); + } + } + + /// + public List ListGroups(SessionInfo user) + { + // Filter by playing item if the user is viewing something already + if (user.NowPlayingItem != null) + { + return _groups.Values.Where( + group => group.GetPlayingItemId().Equals(user.FullNowPlayingItem.Id) + ).Select( + group => group.GetInfo() + ).ToList(); + } + // Otherwise show all available groups + else + { + return _groups.Values.Select( + group => group.GetInfo() + ).ToList(); + } + } + + /// + public void HandleRequest(SessionInfo user, SyncplayRequestInfo request) + { + ISyncplayController group; + _userToGroupMap.TryGetValue(user.Id, out group); + + if (group == null) + { + _logger.LogWarning("Syncplaymanager HandleRequest: " + user.Id + " not in group."); + + var update = new SyncplayGroupUpdate(); + update.Type = SyncplayGroupUpdateType.NotInGroup; + _sessionManager.SendSyncplayGroupUpdate(user.Id.ToString(), update, CancellationToken.None); + return; + } + group.HandleRequest(user, request); + } + + /// + public void MapUserToGroup(SessionInfo user, ISyncplayController group) + { + if (IsUserInGroup(user)) + { + throw new InvalidOperationException("User in other group already!"); + } + _userToGroupMap[user.Id] = group; + } + + /// + public void UnmapUserFromGroup(SessionInfo user, ISyncplayController group) + { + if (!IsUserInGroup(user)) + { + throw new InvalidOperationException("User not in any group!"); + } + + ISyncplayController tempGroup; + _userToGroupMap.Remove(user.Id, out tempGroup); + + if (!tempGroup.GetGroupId().Equals(group.GetGroupId())) + { + throw new InvalidOperationException("User was in wrong group!"); + } + } + } +} diff --git a/MediaBrowser.Api/Syncplay/SyncplayService.cs b/MediaBrowser.Api/Syncplay/SyncplayService.cs new file mode 100644 index 0000000000..f17cca9ee7 --- /dev/null +++ b/MediaBrowser.Api/Syncplay/SyncplayService.cs @@ -0,0 +1,261 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.Syncplay; +using MediaBrowser.Model.Services; +using MediaBrowser.Model.Syncplay; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Api.Syncplay +{ + [Route("/Syncplay/{SessionId}/NewGroup", "POST", Summary = "Create a new Syncplay group")] + [Authenticated] + public class SyncplayNewGroup : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + [Route("/Syncplay/{SessionId}/JoinGroup", "POST", Summary = "Join an existing Syncplay group")] + [Authenticated] + public class SyncplayJoinGroup : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + + /// + /// Gets or sets the Group id. + /// + /// The Group id to join. + [ApiMember(Name = "GroupId", Description = "Group Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string GroupId { get; set; } + } + + [Route("/Syncplay/{SessionId}/LeaveGroup", "POST", Summary = "Leave joined Syncplay group")] + [Authenticated] + public class SyncplayLeaveGroup : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + [Route("/Syncplay/{SessionId}/ListGroups", "POST", Summary = "List Syncplay groups playing same item")] + [Authenticated] + public class SyncplayListGroups : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + [Route("/Syncplay/{SessionId}/PlayRequest", "POST", Summary = "Request play in Syncplay group")] + [Authenticated] + public class SyncplayPlayRequest : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + [Route("/Syncplay/{SessionId}/PauseRequest", "POST", Summary = "Request pause in Syncplay group")] + [Authenticated] + public class SyncplayPauseRequest : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + [Route("/Syncplay/{SessionId}/SeekRequest", "POST", Summary = "Request seek in Syncplay group")] + [Authenticated] + public class SyncplaySeekRequest : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + + [ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")] + public long PositionTicks { get; set; } + } + + [Route("/Syncplay/{SessionId}/BufferingRequest", "POST", Summary = "Request group wait in Syncplay group while buffering")] + [Authenticated] + public class SyncplayBufferingRequest : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + + [ApiMember(Name = "When", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string When { get; set; } + + [ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")] + public long PositionTicks { get; set; } + + [ApiMember(Name = "Resume", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")] + public bool Resume { get; set; } + } + + [Route("/Syncplay/{SessionId}/KeepAlive", "POST", Summary = "Keep session alive")] + [Authenticated] + public class SyncplayKeepAlive : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + + [ApiMember(Name = "Ping", IsRequired = true, DataType = "double", ParameterType = "query", Verb = "POST")] + public double Ping { get; set; } + } + + [Route("/Syncplay/{SessionId}/GetUtcTime", "POST", Summary = "Get UtcTime")] + [Authenticated] + public class SyncplayGetUtcTime : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + /// + /// Class SyncplayService. + /// + public class SyncplayService : BaseApiService + { + /// + /// The session manager. + /// + private readonly ISessionManager _sessionManager; + + private readonly ISessionContext _sessionContext; + + /// + /// The Syncplay manager. + /// + private readonly ISyncplayManager _syncplayManager; + + public SyncplayService( + ILogger logger, + IServerConfigurationManager serverConfigurationManager, + IHttpResultFactory httpResultFactory, + ISessionManager sessionManager, + ISessionContext sessionContext, + ISyncplayManager syncplayManager) + : base(logger, serverConfigurationManager, httpResultFactory) + { + _sessionManager = sessionManager; + _sessionContext = sessionContext; + _syncplayManager = syncplayManager; + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplayNewGroup request) + { + var currentSession = GetSession(_sessionContext); + _syncplayManager.NewGroup(currentSession); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplayJoinGroup request) + { + var currentSession = GetSession(_sessionContext); + _syncplayManager.JoinGroup(currentSession, request.GroupId); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplayLeaveGroup request) + { + var currentSession = GetSession(_sessionContext); + _syncplayManager.LeaveGroup(currentSession); + } + + /// + /// Handles the specified request. + /// + /// The request. + /// The requested list of groups. + public List Post(SyncplayListGroups request) + { + var currentSession = GetSession(_sessionContext); + return _syncplayManager.ListGroups(currentSession); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplayPlayRequest request) + { + var currentSession = GetSession(_sessionContext); + var syncplayRequest = new SyncplayRequestInfo(); + syncplayRequest.Type = SyncplayRequestType.Play; + _syncplayManager.HandleRequest(currentSession, syncplayRequest); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplayPauseRequest request) + { + var currentSession = GetSession(_sessionContext); + var syncplayRequest = new SyncplayRequestInfo(); + syncplayRequest.Type = SyncplayRequestType.Pause; + _syncplayManager.HandleRequest(currentSession, syncplayRequest); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplaySeekRequest request) + { + var currentSession = GetSession(_sessionContext); + var syncplayRequest = new SyncplayRequestInfo(); + syncplayRequest.Type = SyncplayRequestType.Seek; + syncplayRequest.PositionTicks = request.PositionTicks; + _syncplayManager.HandleRequest(currentSession, syncplayRequest); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplayBufferingRequest request) + { + var currentSession = GetSession(_sessionContext); + var syncplayRequest = new SyncplayRequestInfo(); + syncplayRequest.Type = request.Resume ? SyncplayRequestType.BufferingComplete : SyncplayRequestType.Buffering; + syncplayRequest.When = DateTime.Parse(request.When); + syncplayRequest.PositionTicks = request.PositionTicks; + _syncplayManager.HandleRequest(currentSession, syncplayRequest); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncplayKeepAlive request) + { + var currentSession = GetSession(_sessionContext); + var syncplayRequest = new SyncplayRequestInfo(); + syncplayRequest.Type = SyncplayRequestType.KeepAlive; + syncplayRequest.Ping = Convert.ToInt64(request.Ping); + _syncplayManager.HandleRequest(currentSession, syncplayRequest); + } + + /// + /// Handles the specified request. + /// + /// The request. + /// The current UTC time. + public string Post(SyncplayGetUtcTime request) + { + return DateTime.UtcNow.ToUniversalTime().ToString("o"); + } + } +} diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 771027103b..4bfc0c73f3 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -9,6 +9,7 @@ using MediaBrowser.Controller.Security; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Events; using MediaBrowser.Model.Session; +using MediaBrowser.Model.Syncplay; namespace MediaBrowser.Controller.Session { @@ -140,6 +141,24 @@ namespace MediaBrowser.Controller.Session /// Task. Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken); + /// + /// Sends the SyncplayCommand. + /// + /// The session id. + /// The command. + /// The cancellation token. + /// Task. + Task SendSyncplayCommand(string sessionId, SyncplayCommand command, CancellationToken cancellationToken); + + /// + /// Sends the SyncplayGroupUpdate. + /// + /// The session id. + /// The group update. + /// The cancellation token. + /// Task. + Task SendSyncplayGroupUpdate(string sessionId, SyncplayGroupUpdate command, CancellationToken cancellationToken); + /// /// Sends the browse command. /// diff --git a/MediaBrowser.Controller/Syncplay/GroupInfo.cs b/MediaBrowser.Controller/Syncplay/GroupInfo.cs new file mode 100644 index 0000000000..d37e8563bc --- /dev/null +++ b/MediaBrowser.Controller/Syncplay/GroupInfo.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.Syncplay +{ + /// + /// Class GroupInfo. + /// + public class GroupInfo + { + /// + /// Default ping value used for users. + /// + public readonly long DefaulPing = 500; + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public readonly Guid GroupId = Guid.NewGuid(); + + /// + /// Gets or sets the playing item. + /// + /// The playing item. + public BaseItem PlayingItem { get; set; } + + /// + /// Gets or sets whether playback is paused. + /// + /// Playback is paused. + public bool IsPaused { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long PositionTicks { get; set; } + + /// + /// Gets or sets the last activity. + /// + /// The last activity. + public DateTime LastActivity { get; set; } + + /// + /// Gets the partecipants. + /// + /// The partecipants. + public readonly ConcurrentDictionary Partecipants = + new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Checks if a user is in this group. + /// + /// true if the user is in this group; false otherwise. + public bool ContainsUser(string sessionId) + { + return Partecipants.ContainsKey(sessionId); + } + + /// + /// Adds the user to the group. + /// + /// The session. + public void AddUser(SessionInfo user) + { + if (ContainsUser(user.Id.ToString())) return; + var member = new GroupMember(); + member.Session = user; + member.Ping = DefaulPing; + member.IsBuffering = false; + Partecipants[user.Id.ToString()] = member; + } + + /// + /// Removes the user from the group. + /// + /// The session. + + public void RemoveUser(SessionInfo user) + { + if (!ContainsUser(user.Id.ToString())) return; + GroupMember member; + Partecipants.Remove(user.Id.ToString(), out member); + } + + /// + /// Updates the ping of a user. + /// + /// The session. + /// The ping. + public void UpdatePing(SessionInfo user, long ping) + { + if (!ContainsUser(user.Id.ToString())) return; + Partecipants[user.Id.ToString()].Ping = ping; + } + + /// + /// Gets the highest ping in the group. + /// + /// The highest ping in the group. + public long GetHighestPing() + { + long max = Int64.MinValue; + foreach (var user in Partecipants.Values) + { + max = Math.Max(max, user.Ping); + } + return max; + } + + /// + /// Sets the user's buffering state. + /// + /// The session. + /// The state. + public void SetBuffering(SessionInfo user, bool isBuffering) + { + if (!ContainsUser(user.Id.ToString())) return; + Partecipants[user.Id.ToString()].IsBuffering = isBuffering; + } + + /// + /// Gets the group buffering state. + /// + /// true if there is a user buffering in the group; false otherwise. + public bool IsBuffering() + { + foreach (var user in Partecipants.Values) + { + if (user.IsBuffering) return true; + } + return false; + } + + /// + /// Checks if the group is empty. + /// + /// true if the group is empty; false otherwise. + public bool IsEmpty() + { + return Partecipants.Count == 0; + } + } +} diff --git a/MediaBrowser.Controller/Syncplay/GroupMember.cs b/MediaBrowser.Controller/Syncplay/GroupMember.cs new file mode 100644 index 0000000000..7630428d76 --- /dev/null +++ b/MediaBrowser.Controller/Syncplay/GroupMember.cs @@ -0,0 +1,28 @@ +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.Syncplay +{ + /// + /// Class GroupMember. + /// + public class GroupMember + { + /// + /// Gets or sets whether this member is buffering. + /// + /// true if member is buffering; false otherwise. + public bool IsBuffering { get; set; } + + /// + /// Gets or sets the session. + /// + /// The session. + public SessionInfo Session { get; set; } + + /// + /// Gets or sets the ping. + /// + /// The ping. + public long Ping { get; set; } + } +} diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs new file mode 100644 index 0000000000..c9465b27a8 --- /dev/null +++ b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs @@ -0,0 +1,61 @@ +using System; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Syncplay; + +namespace MediaBrowser.Controller.Syncplay +{ + /// + /// Interface ISyncplayController. + /// + public interface ISyncplayController + { + /// + /// Gets the group id. + /// + /// The group id. + Guid GetGroupId(); + + /// + /// Gets the playing item id. + /// + /// The playing item id. + Guid GetPlayingItemId(); + + /// + /// Checks if the group is empty. + /// + /// If the group is empty. + bool IsGroupEmpty(); + + /// + /// Initializes the group with the user's info. + /// + /// The session. + void InitGroup(SessionInfo user); + + /// + /// Adds the user to the group. + /// + /// The session. + void UserJoin(SessionInfo user); + + /// + /// Removes the user from the group. + /// + /// The session. + void UserLeave(SessionInfo user); + + /// + /// Handles the requested action by the user. + /// + /// The session. + /// The requested action. + void HandleRequest(SessionInfo user, SyncplayRequestInfo request); + + /// + /// Gets the info about the group for the clients. + /// + /// The group info for the clients. + GroupInfoView GetInfo(); + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs new file mode 100644 index 0000000000..ec91ea69d3 --- /dev/null +++ b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Syncplay; + +namespace MediaBrowser.Controller.Syncplay +{ + /// + /// Interface ISyncplayManager. + /// + public interface ISyncplayManager + { + /// + /// Creates a new group. + /// + /// The user that's creating the group. + void NewGroup(SessionInfo user); + + /// + /// Adds the user to a group. + /// + /// The session. + /// The group id. + void JoinGroup(SessionInfo user, string groupId); + + /// + /// Removes the user from a group. + /// + /// The session. + void LeaveGroup(SessionInfo user); + + /// + /// Gets list of available groups for a user. + /// + /// The user. + /// The list of available groups. + List ListGroups(SessionInfo user); + + /// + /// Handle a request by a user in a group. + /// + /// The session. + /// The request. + void HandleRequest(SessionInfo user, SyncplayRequestInfo request); + + /// + /// Maps a user to a group. + /// + /// The user. + /// The group. + /// + void MapUserToGroup(SessionInfo user, ISyncplayController group); + + /// + /// Unmaps a user from a group. + /// + /// The user. + /// The group. + /// + void UnmapUserFromGroup(SessionInfo user, ISyncplayController group); + } +} diff --git a/MediaBrowser.Model/Syncplay/GroupInfoModel.cs b/MediaBrowser.Model/Syncplay/GroupInfoModel.cs new file mode 100644 index 0000000000..599c0dbfc9 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/GroupInfoModel.cs @@ -0,0 +1,38 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class GroupInfoModel. + /// + public class GroupInfoView + { + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public string GroupId { get; set; } + + /// + /// Gets or sets the playing item id. + /// + /// The playing item id. + public string PlayingItemId { get; set; } + + /// + /// Gets or sets the playing item name. + /// + /// The playing item name. + public string PlayingItemName { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long PositionTicks { get; set; } + + /// + /// Gets or sets the partecipants. + /// + /// The partecipants. + public string[] Partecipants { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/SyncplayCommand.cs b/MediaBrowser.Model/Syncplay/SyncplayCommand.cs new file mode 100644 index 0000000000..769316e805 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/SyncplayCommand.cs @@ -0,0 +1,32 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class SyncplayCommand. + /// + public class SyncplayCommand + { + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public string GroupId { get; set; } + + /// + /// Gets or sets the UTC time when to execute the command. + /// + /// The UTC time when to execute the command. + public string When { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long? PositionTicks { get; set; } + + /// + /// Gets or sets the command. + /// + /// The command. + public SyncplayCommandType Command { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/SyncplayCommandType.cs b/MediaBrowser.Model/Syncplay/SyncplayCommandType.cs new file mode 100644 index 0000000000..87b9ad66d6 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/SyncplayCommandType.cs @@ -0,0 +1,21 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Enum SyncplayCommandType. + /// + public enum SyncplayCommandType + { + /// + /// The play command. Instructs users to start playback. + /// + Play = 0, + /// + /// The pause command. Instructs users to pause playback. + /// + Pause = 1, + /// + /// The seek command. Instructs users to seek to a specified time. + /// + Seek = 2 + } +} diff --git a/MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs b/MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs new file mode 100644 index 0000000000..c5c2f35404 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs @@ -0,0 +1,26 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class SyncplayGroupUpdate. + /// + public class SyncplayGroupUpdate + { + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public string GroupId { get; set; } + + /// + /// Gets or sets the update type. + /// + /// The update type. + public SyncplayGroupUpdateType Type { get; set; } + + /// + /// Gets or sets the data. + /// + /// The data. + public T Data { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/SyncplayGroupUpdateType.cs b/MediaBrowser.Model/Syncplay/SyncplayGroupUpdateType.cs new file mode 100644 index 0000000000..c7c5f534dc --- /dev/null +++ b/MediaBrowser.Model/Syncplay/SyncplayGroupUpdateType.cs @@ -0,0 +1,41 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Enum SyncplayGroupUpdateType + /// + public enum SyncplayGroupUpdateType + { + /// + /// The user-joined update. Tells members of a group about a new user. + /// + UserJoined = 0, + /// + /// The user-left update. Tells members of a group that a user left. + /// + UserLeft = 1, + /// + /// The group-joined update. Tells a user that the group has been joined. + /// + GroupJoined = 2, + /// + /// The group-left update. Tells a user that the group has been left. + /// + GroupLeft = 3, + /// + /// The group-wait update. Tells members of the group that a user is buffering. + /// + GroupWait = 4, + /// + /// The prepare-session update. Tells a user to load some content. + /// + PrepareSession = 5, + /// + /// The keep-alive update. An update to keep alive the socket. + /// + KeepAlive = 6, + /// + /// The not-in-group update. Tells a user that no group has been joined. + /// + NotInGroup = 7 + } +} diff --git a/MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs b/MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs new file mode 100644 index 0000000000..7dba74ae94 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs @@ -0,0 +1,34 @@ +using System; + +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class SyncplayRequestInfo. + /// + public class SyncplayRequestInfo + { + /// + /// Gets or sets the request type. + /// + /// The request type. + public SyncplayRequestType Type; + + /// + /// Gets or sets when the request has been made by the client. + /// + /// The date of the request. + public DateTime? When { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long? PositionTicks { get; set; } + + /// + /// Gets or sets the ping time. + /// + /// The ping time. + public long? Ping { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/SyncplayRequestType.cs b/MediaBrowser.Model/Syncplay/SyncplayRequestType.cs new file mode 100644 index 0000000000..44d7a0af26 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/SyncplayRequestType.cs @@ -0,0 +1,33 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Enum SyncplayRequestType + /// + public enum SyncplayRequestType + { + /// + /// A user is requesting a play command for the group. + /// + Play = 0, + /// + /// A user is requesting a pause command for the group. + /// + Pause = 1, + /// + /// A user is requesting a seek command for the group. + /// + Seek = 2, + /// + /// A user is signaling that playback is buffering. + /// + Buffering = 3, + /// + /// A user is signaling that playback resumed. + /// + BufferingComplete = 4, + /// + /// A user is reporting its ping. + /// + KeepAlive = 5 + } +} -- cgit v1.2.3 From 84d92ba9cea4fdd97a8d1580e67706dc4577871a Mon Sep 17 00:00:00 2001 From: gion Date: Wed, 15 Apr 2020 18:03:58 +0200 Subject: Check that client is playing the right item Send date when playback command is emitted Rename some classes --- .../Session/SessionManager.cs | 4 +- .../Syncplay/SyncplayController.cs | 80 ++++++++++++---------- .../Syncplay/SyncplayManager.cs | 28 ++++---- MediaBrowser.Api/Syncplay/SyncplayService.cs | 43 +++++++++--- MediaBrowser.Controller/Session/ISessionManager.cs | 4 +- .../Syncplay/ISyncplayController.cs | 5 +- .../Syncplay/ISyncplayManager.cs | 5 +- MediaBrowser.Model/Syncplay/GroupUpdate.cs | 26 +++++++ MediaBrowser.Model/Syncplay/GroupUpdateType.cs | 41 +++++++++++ MediaBrowser.Model/Syncplay/JoinGroupRequest.cs | 22 ++++++ MediaBrowser.Model/Syncplay/PlaybackRequest.cs | 34 +++++++++ MediaBrowser.Model/Syncplay/PlaybackRequestType.cs | 33 +++++++++ MediaBrowser.Model/Syncplay/SendCommand.cs | 38 ++++++++++ MediaBrowser.Model/Syncplay/SendCommandType.cs | 21 ++++++ MediaBrowser.Model/Syncplay/SyncplayCommand.cs | 32 --------- MediaBrowser.Model/Syncplay/SyncplayCommandType.cs | 21 ------ MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs | 26 ------- .../Syncplay/SyncplayGroupUpdateType.cs | 41 ----------- MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs | 34 --------- MediaBrowser.Model/Syncplay/SyncplayRequestType.cs | 33 --------- 20 files changed, 313 insertions(+), 258 deletions(-) create mode 100644 MediaBrowser.Model/Syncplay/GroupUpdate.cs create mode 100644 MediaBrowser.Model/Syncplay/GroupUpdateType.cs create mode 100644 MediaBrowser.Model/Syncplay/JoinGroupRequest.cs create mode 100644 MediaBrowser.Model/Syncplay/PlaybackRequest.cs create mode 100644 MediaBrowser.Model/Syncplay/PlaybackRequestType.cs create mode 100644 MediaBrowser.Model/Syncplay/SendCommand.cs create mode 100644 MediaBrowser.Model/Syncplay/SendCommandType.cs delete mode 100644 MediaBrowser.Model/Syncplay/SyncplayCommand.cs delete mode 100644 MediaBrowser.Model/Syncplay/SyncplayCommandType.cs delete mode 100644 MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs delete mode 100644 MediaBrowser.Model/Syncplay/SyncplayGroupUpdateType.cs delete mode 100644 MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs delete mode 100644 MediaBrowser.Model/Syncplay/SyncplayRequestType.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index b1519b5729..6a64209c1a 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1156,7 +1156,7 @@ namespace Emby.Server.Implementations.Session } /// - public async Task SendSyncplayCommand(string sessionId, SyncplayCommand command, CancellationToken cancellationToken) + public async Task SendSyncplayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken) { CheckDisposed(); var session = GetSessionToRemoteControl(sessionId); @@ -1164,7 +1164,7 @@ namespace Emby.Server.Implementations.Session } /// - public async Task SendSyncplayGroupUpdate(string sessionId, SyncplayGroupUpdate command, CancellationToken cancellationToken) + public async Task SendSyncplayGroupUpdate(string sessionId, GroupUpdate command, CancellationToken cancellationToken) { CheckDisposed(); var session = GetSessionToRemoteControl(sessionId); diff --git a/Emby.Server.Implementations/Syncplay/SyncplayController.cs b/Emby.Server.Implementations/Syncplay/SyncplayController.cs index b156e5a879..fb37b2fb6f 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayController.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayController.cs @@ -129,7 +129,7 @@ namespace Emby.Server.Implementations.Syncplay } } - private Task SendGroupUpdate(SessionInfo from, BroadcastType type, SyncplayGroupUpdate message) + private Task SendGroupUpdate(SessionInfo from, BroadcastType type, GroupUpdate message) { IEnumerable GetTasks() { @@ -143,7 +143,7 @@ namespace Emby.Server.Implementations.Syncplay return Task.WhenAll(GetTasks()); } - private Task SendCommand(SessionInfo from, BroadcastType type, SyncplayCommand message) + private Task SendCommand(SessionInfo from, BroadcastType type, SendCommand message) { IEnumerable GetTasks() { @@ -157,18 +157,20 @@ namespace Emby.Server.Implementations.Syncplay return Task.WhenAll(GetTasks()); } - private SyncplayCommand NewSyncplayCommand(SyncplayCommandType type) { - var command = new SyncplayCommand(); + private SendCommand NewSyncplayCommand(SendCommandType type) + { + var command = new SendCommand(); command.GroupId = _group.GroupId.ToString(); command.Command = type; command.PositionTicks = _group.PositionTicks; command.When = _group.LastActivity.ToUniversalTime().ToString("o"); + command.EmittedAt = DateTime.UtcNow.ToUniversalTime().ToString("o"); return command; } - private SyncplayGroupUpdate NewSyncplayGroupUpdate(SyncplayGroupUpdateType type, T data) + private GroupUpdate NewSyncplayGroupUpdate(GroupUpdateType type, T data) { - var command = new SyncplayGroupUpdate(); + var command = new GroupUpdate(); command.GroupId = _group.GroupId.ToString(); command.Type = type; command.Data = data; @@ -186,35 +188,37 @@ namespace Emby.Server.Implementations.Syncplay _group.PositionTicks = session.PlayState.PositionTicks ??= 0; _group.LastActivity = DateTime.UtcNow; - var updateSession = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); + var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); - var pauseCommand = NewSyncplayCommand(SyncplayCommandType.Pause); + var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.SingleSession, pauseCommand); } /// - public void SessionJoin(SessionInfo session) + public void SessionJoin(SessionInfo session, JoinGroupRequest request) { - if (session.NowPlayingItem != null && session.NowPlayingItem.Id.Equals(_group.PlayingItem.Id)) + if (session.NowPlayingItem != null && + session.NowPlayingItem.Id.Equals(_group.PlayingItem.Id) && + request.PlayingItemId.Equals(_group.PlayingItem.Id)) { _group.AddSession(session); _syncplayManager.MapSessionToGroup(session, this); - var updateSession = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupJoined, _group.PositionTicks); + var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); - var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.UserJoined, session.UserName); + var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserJoined, session.UserName); SendGroupUpdate(session, BroadcastType.AllExceptSession, updateOthers); // Client join and play, syncing will happen client side if (!_group.IsPaused) { - var playCommand = NewSyncplayCommand(SyncplayCommandType.Play); + var playCommand = NewSyncplayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.SingleSession, playCommand); } else { - var pauseCommand = NewSyncplayCommand(SyncplayCommandType.Pause); + var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.SingleSession, pauseCommand); } } @@ -223,7 +227,7 @@ namespace Emby.Server.Implementations.Syncplay var playRequest = new PlayRequest(); playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id }; playRequest.StartPositionTicks = _group.PositionTicks; - var update = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.PrepareSession, playRequest); + var update = NewSyncplayGroupUpdate(GroupUpdateType.PrepareSession, playRequest); SendGroupUpdate(session, BroadcastType.SingleSession, update); } } @@ -234,17 +238,17 @@ namespace Emby.Server.Implementations.Syncplay _group.RemoveSession(session); _syncplayManager.UnmapSessionFromGroup(session, this); - var updateSession = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupLeft, _group.PositionTicks); + var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupLeft, _group.PositionTicks); SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); - var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.UserLeft, session.UserName); + var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserLeft, session.UserName); SendGroupUpdate(session, BroadcastType.AllExceptSession, updateOthers); } /// - public void HandleRequest(SessionInfo session, SyncplayRequestInfo request) + public void HandleRequest(SessionInfo session, PlaybackRequest request) { - if (request.Type.Equals(SyncplayRequestType.Play)) + if (request.Type.Equals(PlaybackRequestType.Play)) { if (_group.IsPaused) { @@ -256,17 +260,17 @@ namespace Emby.Server.Implementations.Syncplay delay ); - var command = NewSyncplayCommand(SyncplayCommandType.Play); + var command = NewSyncplayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.AllGroup, command); } else { // Client got lost - var command = NewSyncplayCommand(SyncplayCommandType.Play); + var command = NewSyncplayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.SingleSession, command); } } - else if (request.Type.Equals(SyncplayRequestType.Pause)) + else if (request.Type.Equals(PlaybackRequestType.Pause)) { if (!_group.IsPaused) { @@ -276,16 +280,16 @@ namespace Emby.Server.Implementations.Syncplay _group.LastActivity = currentTime; _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; - var command = NewSyncplayCommand(SyncplayCommandType.Pause); + var command = NewSyncplayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.AllGroup, command); } else { - var command = NewSyncplayCommand(SyncplayCommandType.Pause); + var command = NewSyncplayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.SingleSession, command); } } - else if (request.Type.Equals(SyncplayRequestType.Seek)) + else if (request.Type.Equals(PlaybackRequestType.Seek)) { // Sanitize PositionTicks var ticks = request.PositionTicks ??= 0; @@ -300,11 +304,11 @@ namespace Emby.Server.Implementations.Syncplay _group.PositionTicks = ticks; _group.LastActivity = DateTime.UtcNow; - var command = NewSyncplayCommand(SyncplayCommandType.Seek); + var command = NewSyncplayCommand(SendCommandType.Seek); SendCommand(session, BroadcastType.AllGroup, command); } // TODO: client does not implement this yet - else if (request.Type.Equals(SyncplayRequestType.Buffering)) + else if (request.Type.Equals(PlaybackRequestType.Buffering)) { if (!_group.IsPaused) { @@ -317,20 +321,20 @@ namespace Emby.Server.Implementations.Syncplay _group.SetBuffering(session, true); // Send pause command to all non-buffering sessions - var command = NewSyncplayCommand(SyncplayCommandType.Pause); + var command = NewSyncplayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.AllReady, command); - var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupWait, session.UserName); + var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.GroupWait, session.UserName); SendGroupUpdate(session, BroadcastType.AllExceptSession, updateOthers); } else { - var command = NewSyncplayCommand(SyncplayCommandType.Pause); + var command = NewSyncplayCommand(SendCommandType.Pause); SendCommand(session, BroadcastType.SingleSession, command); } } // TODO: client does not implement this yet - else if (request.Type.Equals(SyncplayRequestType.BufferingComplete)) + else if (request.Type.Equals(PlaybackRequestType.BufferingComplete)) { if (_group.IsPaused) { @@ -344,7 +348,7 @@ namespace Emby.Server.Implementations.Syncplay var clientPosition = TimeSpan.FromTicks(request.PositionTicks ??= 0) + elapsedTime; var delay = _group.PositionTicks - clientPosition.Ticks; - var command = NewSyncplayCommand(SyncplayCommandType.Pause); + var command = NewSyncplayCommand(SendCommandType.Pause); command.When = currentTime.AddMilliseconds( delay ).ToUniversalTime().ToString("o"); @@ -367,7 +371,7 @@ namespace Emby.Server.Implementations.Syncplay _group.LastActivity = currentTime.AddMilliseconds( delay ); - var command = NewSyncplayCommand(SyncplayCommandType.Play); + var command = NewSyncplayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.AllExceptSession, command); } else @@ -380,7 +384,7 @@ namespace Emby.Server.Implementations.Syncplay delay ); - var command = NewSyncplayCommand(SyncplayCommandType.Play); + var command = NewSyncplayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.AllGroup, command); } } @@ -388,17 +392,17 @@ namespace Emby.Server.Implementations.Syncplay else { // Make sure client has latest group state - var command = NewSyncplayCommand(SyncplayCommandType.Play); + var command = NewSyncplayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.SingleSession, command); } } - else if (request.Type.Equals(SyncplayRequestType.KeepAlive)) + else if (request.Type.Equals(PlaybackRequestType.KeepAlive)) { _group.UpdatePing(session, request.Ping ??= _group.DefaulPing); - var keepAlive = new SyncplayGroupUpdate(); + var keepAlive = new GroupUpdate(); keepAlive.GroupId = _group.GroupId.ToString(); - keepAlive.Type = SyncplayGroupUpdateType.KeepAlive; + keepAlive.Type = GroupUpdateType.KeepAlive; SendGroupUpdate(session, BroadcastType.SingleSession, keepAlive); } } diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index 5c44326f5d..60d70e5fde 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -166,7 +166,7 @@ namespace Emby.Server.Implementations.Syncplay if (user.Policy.SyncplayAccess != SyncplayAccess.CreateAndJoinGroups) { // TODO: shall an error message be sent back to the client? - return; + throw new ArgumentException("User does not have permission to create groups"); } if (IsSessionInGroup(session)) @@ -181,14 +181,14 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void JoinGroup(SessionInfo session, string groupId) + public void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request) { var user = _userManager.GetUserById(session.UserId); if (user.Policy.SyncplayAccess == SyncplayAccess.None) { // TODO: shall an error message be sent back to the client? - return; + throw new ArgumentException("User does not have access to syncplay"); } if (IsSessionInGroup(session)) @@ -204,18 +204,18 @@ namespace Emby.Server.Implementations.Syncplay { _logger.LogError("Syncplaymanager JoinGroup: " + groupId + " does not exist."); - var update = new SyncplayGroupUpdate(); - update.Type = SyncplayGroupUpdateType.NotInGroup; + var update = new GroupUpdate(); + update.Type = GroupUpdateType.NotInGroup; _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), update, CancellationToken.None); return; } if (!HasAccessToItem(user, group.GetPlayingItemId())) { - return; + throw new ArgumentException("User does not have access to playing item"); } - group.SessionJoin(session); + group.SessionJoin(session, request); } /// @@ -230,8 +230,8 @@ namespace Emby.Server.Implementations.Syncplay { _logger.LogWarning("Syncplaymanager HandleRequest: " + session.Id + " not in group."); - var update = new SyncplayGroupUpdate(); - update.Type = SyncplayGroupUpdateType.NotInGroup; + var update = new GroupUpdate(); + update.Type = GroupUpdateType.NotInGroup; _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), update, CancellationToken.None); return; } @@ -276,14 +276,14 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void HandleRequest(SessionInfo session, SyncplayRequestInfo request) + public void HandleRequest(SessionInfo session, PlaybackRequest request) { var user = _userManager.GetUserById(session.UserId); if (user.Policy.SyncplayAccess == SyncplayAccess.None) { // TODO: same as LeaveGroup - return; + throw new ArgumentException("User does not have access to syncplay"); } ISyncplayController group; @@ -293,14 +293,14 @@ namespace Emby.Server.Implementations.Syncplay { _logger.LogWarning("Syncplaymanager HandleRequest: " + session.Id + " not in group."); - var update = new SyncplayGroupUpdate(); - update.Type = SyncplayGroupUpdateType.NotInGroup; + var update = new GroupUpdate(); + update.Type = GroupUpdateType.NotInGroup; _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), update, CancellationToken.None); return; } group.HandleRequest(session, request); } - + /// public void MapSessionToGroup(SessionInfo session, ISyncplayController group) { diff --git a/MediaBrowser.Api/Syncplay/SyncplayService.cs b/MediaBrowser.Api/Syncplay/SyncplayService.cs index f17cca9ee7..0f9d1b7339 100644 --- a/MediaBrowser.Api/Syncplay/SyncplayService.cs +++ b/MediaBrowser.Api/Syncplay/SyncplayService.cs @@ -31,6 +31,13 @@ namespace MediaBrowser.Api.Syncplay /// The Group id to join. [ApiMember(Name = "GroupId", Description = "Group Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string GroupId { get; set; } + + /// + /// Gets or sets the playing item id. + /// + /// The client's currently playing item id. + [ApiMember(Name = "PlayingItemId", Description = "Client's playing item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string PlayingItemId { get; set; } } [Route("/Syncplay/{SessionId}/LeaveGroup", "POST", Summary = "Leave joined Syncplay group")] @@ -160,7 +167,21 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayJoinGroup request) { var currentSession = GetSession(_sessionContext); - _syncplayManager.JoinGroup(currentSession, request.GroupId); + var joinRequest = new JoinGroupRequest(); + joinRequest.GroupId = Guid.Parse(request.GroupId); + try + { + joinRequest.PlayingItemId = Guid.Parse(request.PlayingItemId); + } + catch (ArgumentNullException) + { + // Do nothing + } + catch (FormatException) + { + // Do nothing + } + _syncplayManager.JoinGroup(currentSession, request.GroupId, joinRequest); } /// @@ -191,8 +212,8 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayPlayRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new SyncplayRequestInfo(); - syncplayRequest.Type = SyncplayRequestType.Play; + var syncplayRequest = new PlaybackRequest(); + syncplayRequest.Type = PlaybackRequestType.Play; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } @@ -203,8 +224,8 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayPauseRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new SyncplayRequestInfo(); - syncplayRequest.Type = SyncplayRequestType.Pause; + var syncplayRequest = new PlaybackRequest(); + syncplayRequest.Type = PlaybackRequestType.Pause; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } @@ -215,8 +236,8 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplaySeekRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new SyncplayRequestInfo(); - syncplayRequest.Type = SyncplayRequestType.Seek; + var syncplayRequest = new PlaybackRequest(); + syncplayRequest.Type = PlaybackRequestType.Seek; syncplayRequest.PositionTicks = request.PositionTicks; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } @@ -228,8 +249,8 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayBufferingRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new SyncplayRequestInfo(); - syncplayRequest.Type = request.Resume ? SyncplayRequestType.BufferingComplete : SyncplayRequestType.Buffering; + var syncplayRequest = new PlaybackRequest(); + syncplayRequest.Type = request.Resume ? PlaybackRequestType.BufferingComplete : PlaybackRequestType.Buffering; syncplayRequest.When = DateTime.Parse(request.When); syncplayRequest.PositionTicks = request.PositionTicks; _syncplayManager.HandleRequest(currentSession, syncplayRequest); @@ -242,8 +263,8 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayKeepAlive request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new SyncplayRequestInfo(); - syncplayRequest.Type = SyncplayRequestType.KeepAlive; + var syncplayRequest = new PlaybackRequest(); + syncplayRequest.Type = PlaybackRequestType.KeepAlive; syncplayRequest.Ping = Convert.ToInt64(request.Ping); _syncplayManager.HandleRequest(currentSession, syncplayRequest); } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 4bfc0c73f3..39c065b895 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -148,7 +148,7 @@ namespace MediaBrowser.Controller.Session /// The command. /// The cancellation token. /// Task. - Task SendSyncplayCommand(string sessionId, SyncplayCommand command, CancellationToken cancellationToken); + Task SendSyncplayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken); /// /// Sends the SyncplayGroupUpdate. @@ -157,7 +157,7 @@ namespace MediaBrowser.Controller.Session /// The group update. /// The cancellation token. /// Task. - Task SendSyncplayGroupUpdate(string sessionId, SyncplayGroupUpdate command, CancellationToken cancellationToken); + Task SendSyncplayGroupUpdate(string sessionId, GroupUpdate command, CancellationToken cancellationToken); /// /// Sends the browse command. diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs index d35ae31019..5b08eac0a4 100644 --- a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs +++ b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs @@ -37,7 +37,8 @@ namespace MediaBrowser.Controller.Syncplay /// Adds the session to the group. /// /// The session. - void SessionJoin(SessionInfo session); + /// The request. + void SessionJoin(SessionInfo session, JoinGroupRequest request); /// /// Removes the session from the group. @@ -50,7 +51,7 @@ namespace MediaBrowser.Controller.Syncplay /// /// The session. /// The requested action. - void HandleRequest(SessionInfo session, SyncplayRequestInfo request); + void HandleRequest(SessionInfo session, PlaybackRequest request); /// /// Gets the info about the group for the clients. diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs index 09920a19ff..d0cf8fa9c8 100644 --- a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs +++ b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs @@ -21,7 +21,8 @@ namespace MediaBrowser.Controller.Syncplay /// /// The session. /// The group id. - void JoinGroup(SessionInfo session, string groupId); + /// The request. + void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request); /// /// Removes the session from a group. @@ -41,7 +42,7 @@ namespace MediaBrowser.Controller.Syncplay /// /// The session. /// The request. - void HandleRequest(SessionInfo session, SyncplayRequestInfo request); + void HandleRequest(SessionInfo session, PlaybackRequest request); /// /// Maps a session to a group. diff --git a/MediaBrowser.Model/Syncplay/GroupUpdate.cs b/MediaBrowser.Model/Syncplay/GroupUpdate.cs new file mode 100644 index 0000000000..cc49e92a9c --- /dev/null +++ b/MediaBrowser.Model/Syncplay/GroupUpdate.cs @@ -0,0 +1,26 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class GroupUpdate. + /// + public class GroupUpdate + { + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public string GroupId { get; set; } + + /// + /// Gets or sets the update type. + /// + /// The update type. + public GroupUpdateType Type { get; set; } + + /// + /// Gets or sets the data. + /// + /// The data. + public T Data { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs new file mode 100644 index 0000000000..ceb778b36f --- /dev/null +++ b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs @@ -0,0 +1,41 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Enum GroupUpdateType + /// + public enum GroupUpdateType + { + /// + /// The user-joined update. Tells members of a group about a new user. + /// + UserJoined = 0, + /// + /// The user-left update. Tells members of a group that a user left. + /// + UserLeft = 1, + /// + /// The group-joined update. Tells a user that the group has been joined. + /// + GroupJoined = 2, + /// + /// The group-left update. Tells a user that the group has been left. + /// + GroupLeft = 3, + /// + /// The group-wait update. Tells members of the group that a user is buffering. + /// + GroupWait = 4, + /// + /// The prepare-session update. Tells a user to load some content. + /// + PrepareSession = 5, + /// + /// The keep-alive update. An update to keep alive the socket. + /// + KeepAlive = 6, + /// + /// The not-in-group update. Tells a user that no group has been joined. + /// + NotInGroup = 7 + } +} diff --git a/MediaBrowser.Model/Syncplay/JoinGroupRequest.cs b/MediaBrowser.Model/Syncplay/JoinGroupRequest.cs new file mode 100644 index 0000000000..8d8a2646ac --- /dev/null +++ b/MediaBrowser.Model/Syncplay/JoinGroupRequest.cs @@ -0,0 +1,22 @@ +using System; + +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class JoinGroupRequest. + /// + public class JoinGroupRequest + { + /// + /// Gets or sets the Group id. + /// + /// The Group id to join. + public Guid GroupId { get; set; } + + /// + /// Gets or sets the playing item id. + /// + /// The client's currently playing item id. + public Guid PlayingItemId { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/PlaybackRequest.cs b/MediaBrowser.Model/Syncplay/PlaybackRequest.cs new file mode 100644 index 0000000000..cae769db0d --- /dev/null +++ b/MediaBrowser.Model/Syncplay/PlaybackRequest.cs @@ -0,0 +1,34 @@ +using System; + +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class PlaybackRequest. + /// + public class PlaybackRequest + { + /// + /// Gets or sets the request type. + /// + /// The request type. + public PlaybackRequestType Type; + + /// + /// Gets or sets when the request has been made by the client. + /// + /// The date of the request. + public DateTime? When { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long? PositionTicks { get; set; } + + /// + /// Gets or sets the ping time. + /// + /// The ping time. + public long? Ping { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs b/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs new file mode 100644 index 0000000000..da770736c7 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs @@ -0,0 +1,33 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Enum PlaybackRequestType + /// + public enum PlaybackRequestType + { + /// + /// A user is requesting a play command for the group. + /// + Play = 0, + /// + /// A user is requesting a pause command for the group. + /// + Pause = 1, + /// + /// A user is requesting a seek command for the group. + /// + Seek = 2, + /// + /// A user is signaling that playback is buffering. + /// + Buffering = 3, + /// + /// A user is signaling that playback resumed. + /// + BufferingComplete = 4, + /// + /// A user is reporting its ping. + /// + KeepAlive = 5 + } +} diff --git a/MediaBrowser.Model/Syncplay/SendCommand.cs b/MediaBrowser.Model/Syncplay/SendCommand.cs new file mode 100644 index 0000000000..d9f3914030 --- /dev/null +++ b/MediaBrowser.Model/Syncplay/SendCommand.cs @@ -0,0 +1,38 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class SendCommand. + /// + public class SendCommand + { + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public string GroupId { get; set; } + + /// + /// Gets or sets the UTC time when to execute the command. + /// + /// The UTC time when to execute the command. + public string When { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long? PositionTicks { get; set; } + + /// + /// Gets or sets the command. + /// + /// The command. + public SendCommandType Command { get; set; } + + /// + /// Gets or sets the UTC time when this command has been emitted. + /// + /// The UTC time when this command has been emitted. + public string EmittedAt { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/SendCommandType.cs b/MediaBrowser.Model/Syncplay/SendCommandType.cs new file mode 100644 index 0000000000..02e4774d0d --- /dev/null +++ b/MediaBrowser.Model/Syncplay/SendCommandType.cs @@ -0,0 +1,21 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Enum SendCommandType. + /// + public enum SendCommandType + { + /// + /// The play command. Instructs users to start playback. + /// + Play = 0, + /// + /// The pause command. Instructs users to pause playback. + /// + Pause = 1, + /// + /// The seek command. Instructs users to seek to a specified time. + /// + Seek = 2 + } +} diff --git a/MediaBrowser.Model/Syncplay/SyncplayCommand.cs b/MediaBrowser.Model/Syncplay/SyncplayCommand.cs deleted file mode 100644 index 769316e805..0000000000 --- a/MediaBrowser.Model/Syncplay/SyncplayCommand.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Class SyncplayCommand. - /// - public class SyncplayCommand - { - /// - /// Gets or sets the group identifier. - /// - /// The group identifier. - public string GroupId { get; set; } - - /// - /// Gets or sets the UTC time when to execute the command. - /// - /// The UTC time when to execute the command. - public string When { get; set; } - - /// - /// Gets or sets the position ticks. - /// - /// The position ticks. - public long? PositionTicks { get; set; } - - /// - /// Gets or sets the command. - /// - /// The command. - public SyncplayCommandType Command { get; set; } - } -} diff --git a/MediaBrowser.Model/Syncplay/SyncplayCommandType.cs b/MediaBrowser.Model/Syncplay/SyncplayCommandType.cs deleted file mode 100644 index 87b9ad66d6..0000000000 --- a/MediaBrowser.Model/Syncplay/SyncplayCommandType.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Enum SyncplayCommandType. - /// - public enum SyncplayCommandType - { - /// - /// The play command. Instructs users to start playback. - /// - Play = 0, - /// - /// The pause command. Instructs users to pause playback. - /// - Pause = 1, - /// - /// The seek command. Instructs users to seek to a specified time. - /// - Seek = 2 - } -} diff --git a/MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs b/MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs deleted file mode 100644 index c5c2f35404..0000000000 --- a/MediaBrowser.Model/Syncplay/SyncplayGroupUpdate.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Class SyncplayGroupUpdate. - /// - public class SyncplayGroupUpdate - { - /// - /// Gets or sets the group identifier. - /// - /// The group identifier. - public string GroupId { get; set; } - - /// - /// Gets or sets the update type. - /// - /// The update type. - public SyncplayGroupUpdateType Type { get; set; } - - /// - /// Gets or sets the data. - /// - /// The data. - public T Data { get; set; } - } -} diff --git a/MediaBrowser.Model/Syncplay/SyncplayGroupUpdateType.cs b/MediaBrowser.Model/Syncplay/SyncplayGroupUpdateType.cs deleted file mode 100644 index c7c5f534dc..0000000000 --- a/MediaBrowser.Model/Syncplay/SyncplayGroupUpdateType.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Enum SyncplayGroupUpdateType - /// - public enum SyncplayGroupUpdateType - { - /// - /// The user-joined update. Tells members of a group about a new user. - /// - UserJoined = 0, - /// - /// The user-left update. Tells members of a group that a user left. - /// - UserLeft = 1, - /// - /// The group-joined update. Tells a user that the group has been joined. - /// - GroupJoined = 2, - /// - /// The group-left update. Tells a user that the group has been left. - /// - GroupLeft = 3, - /// - /// The group-wait update. Tells members of the group that a user is buffering. - /// - GroupWait = 4, - /// - /// The prepare-session update. Tells a user to load some content. - /// - PrepareSession = 5, - /// - /// The keep-alive update. An update to keep alive the socket. - /// - KeepAlive = 6, - /// - /// The not-in-group update. Tells a user that no group has been joined. - /// - NotInGroup = 7 - } -} diff --git a/MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs b/MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs deleted file mode 100644 index 7dba74ae94..0000000000 --- a/MediaBrowser.Model/Syncplay/SyncplayRequestInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; - -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Class SyncplayRequestInfo. - /// - public class SyncplayRequestInfo - { - /// - /// Gets or sets the request type. - /// - /// The request type. - public SyncplayRequestType Type; - - /// - /// Gets or sets when the request has been made by the client. - /// - /// The date of the request. - public DateTime? When { get; set; } - - /// - /// Gets or sets the position ticks. - /// - /// The position ticks. - public long? PositionTicks { get; set; } - - /// - /// Gets or sets the ping time. - /// - /// The ping time. - public long? Ping { get; set; } - } -} diff --git a/MediaBrowser.Model/Syncplay/SyncplayRequestType.cs b/MediaBrowser.Model/Syncplay/SyncplayRequestType.cs deleted file mode 100644 index 44d7a0af26..0000000000 --- a/MediaBrowser.Model/Syncplay/SyncplayRequestType.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Enum SyncplayRequestType - /// - public enum SyncplayRequestType - { - /// - /// A user is requesting a play command for the group. - /// - Play = 0, - /// - /// A user is requesting a pause command for the group. - /// - Pause = 1, - /// - /// A user is requesting a seek command for the group. - /// - Seek = 2, - /// - /// A user is signaling that playback is buffering. - /// - Buffering = 3, - /// - /// A user is signaling that playback resumed. - /// - BufferingComplete = 4, - /// - /// A user is reporting its ping. - /// - KeepAlive = 5 - } -} -- cgit v1.2.3 From aad5058d25b3c295e9ea5b4330dde219034ba8c8 Mon Sep 17 00:00:00 2001 From: gion Date: Fri, 17 Apr 2020 13:47:00 +0200 Subject: Implement KeepAlive for WebSockets --- .../HttpServer/WebSocketConnection.cs | 27 +++- .../Session/SessionWebSocketListener.cs | 156 +++++++++++++++++++++ .../Net/IWebSocketConnection.cs | 6 + 3 files changed, 183 insertions(+), 6 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 2292d86a4a..171047e65f 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -94,6 +94,9 @@ namespace Emby.Server.Implementations.HttpServer /// The last activity date. public DateTime LastActivityDate { get; private set; } + /// + public DateTime LastKeepAliveDate { get; set; } + /// /// Gets the id. /// @@ -158,11 +161,6 @@ namespace Emby.Server.Implementations.HttpServer return; } - if (OnReceive == null) - { - return; - } - try { var stub = (WebSocketMessage)_jsonSerializer.DeserializeFromString(message, typeof(WebSocketMessage)); @@ -174,7 +172,15 @@ namespace Emby.Server.Implementations.HttpServer Connection = this }; - OnReceive(info); + if (info.MessageType.Equals("KeepAlive", StringComparison.Ordinal)) + { + SendKeepAliveResponse(); + } + + if (OnReceive != null) + { + OnReceive(info); + } } catch (Exception ex) { @@ -233,6 +239,15 @@ namespace Emby.Server.Implementations.HttpServer return _socket.SendAsync(text, true, cancellationToken); } + private Task SendKeepAliveResponse() + { + LastKeepAliveDate = DateTime.UtcNow; + return SendAsync(new WebSocketMessage + { + MessageType = "KeepAlive" + }, CancellationToken.None); + } + /// public void Dispose() { diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 930f2d35d3..d8e02ef395 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -1,8 +1,13 @@ using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Net.WebSockets; +using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Events; +using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -14,6 +19,21 @@ namespace Emby.Server.Implementations.Session /// public class SessionWebSocketListener : IWebSocketListener, IDisposable { + /// + /// The timeout in seconds after which a WebSocket is considered to be lost. + /// + public readonly int WebSocketLostTimeout = 60; + + /// + /// The timer factor; controls the frequency of the timer. + /// + public readonly double TimerFactor = 0.2; + + /// + /// The ForceKeepAlive factor; controls when a ForceKeepAlive is sent. + /// + public readonly double ForceKeepAliveFactor = 0.75; + /// /// The _session manager /// @@ -31,6 +51,15 @@ namespace Emby.Server.Implementations.Session private readonly IHttpServer _httpServer; + /// + /// The KeepAlive timer. + /// + private Timer _keepAliveTimer; + + /// + /// The WebSocket watchlist. + /// + private readonly ConcurrentDictionary _webSockets = new ConcurrentDictionary(); /// /// Initializes a new instance of the class. @@ -55,6 +84,7 @@ namespace Emby.Server.Implementations.Session if (session != null) { EnsureController(session, e.Argument); + KeepAliveWebSocket(e.Argument); } else { @@ -82,6 +112,7 @@ namespace Emby.Server.Implementations.Session public void Dispose() { _httpServer.WebSocketConnected -= _serverManager_WebSocketConnected; + StopKeepAliveTimer(); } /// @@ -99,5 +130,130 @@ namespace Emby.Server.Implementations.Session var controller = (WebSocketController)controllerInfo.Item1; controller.AddWebSocket(connection); } + + /// + /// Called when a WebSocket is closed. + /// + /// The WebSocket. + /// The event arguments. + private void _webSocket_Closed(object sender, EventArgs e) + { + var webSocket = (IWebSocketConnection) sender; + webSocket.Closed -= _webSocket_Closed; + _webSockets.TryRemove(webSocket, out _); + } + + /// + /// Adds a WebSocket to the KeepAlive watchlist. + /// + /// The WebSocket to monitor. + private async void KeepAliveWebSocket(IWebSocketConnection webSocket) + { + _webSockets.TryAdd(webSocket, 0); + webSocket.Closed += _webSocket_Closed; + webSocket.LastKeepAliveDate = DateTime.UtcNow; + + // Notify WebSocket about timeout + try + { + await SendForceKeepAlive(webSocket); + } + catch (WebSocketException exception) + { + _logger.LogDebug(exception, "Error sending ForceKeepAlive message to WebSocket."); + } + + StartKeepAliveTimer(); + } + + /// + /// Starts the KeepAlive timer. + /// + private void StartKeepAliveTimer() + { + if (_keepAliveTimer == null) + { + _keepAliveTimer = new Timer( + KeepAliveSockets, + null, + TimeSpan.FromSeconds(WebSocketLostTimeout * TimerFactor), + TimeSpan.FromSeconds(WebSocketLostTimeout * TimerFactor) + ); + } + } + + /// + /// Stops the KeepAlive timer. + /// + private void StopKeepAliveTimer() + { + if (_keepAliveTimer != null) + { + _keepAliveTimer.Dispose(); + _keepAliveTimer = null; + } + + foreach (var pair in _webSockets) + { + pair.Key.Closed -= _webSocket_Closed; + } + } + + /// + /// Checks status of KeepAlive of WebSockets. + /// + /// The state. + private async void KeepAliveSockets(object state) + { + var inactive = _webSockets.Keys.Where(i => + { + var elapsed = (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds; + return (elapsed > WebSocketLostTimeout * ForceKeepAliveFactor) && (elapsed < WebSocketLostTimeout); + }); + var lost = _webSockets.Keys.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout); + + if (inactive.Any()) + { + _logger.LogDebug("Sending ForceKeepAlive message to {0} WebSockets.", inactive.Count()); + } + + foreach (var webSocket in inactive) + { + try + { + await SendForceKeepAlive(webSocket); + } + catch (WebSocketException exception) + { + _logger.LogDebug(exception, "Error sending ForceKeepAlive message to WebSocket."); + lost.Append(webSocket); + } + } + + if (lost.Any()) + { + // TODO: handle lost webSockets + _logger.LogDebug("Lost {0} WebSockets.", lost.Count()); + } + + if (!_webSockets.Any()) + { + StopKeepAliveTimer(); + } + } + + /// + /// Sends a ForceKeepAlive message to a WebSocket. + /// + /// The WebSocket. + /// Task. + private Task SendForceKeepAlive(IWebSocketConnection webSocket) + { + return webSocket.SendAsync(new WebSocketMessage + { + MessageType = "ForceKeepAlive", + Data = WebSocketLostTimeout + }, CancellationToken.None); + } } } diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs index 31eb7ccb75..fb766ab57f 100644 --- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs +++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs @@ -26,6 +26,12 @@ namespace MediaBrowser.Controller.Net /// The last activity date. DateTime LastActivityDate { get; } + /// + /// Gets or sets the date of last Keeplive received. + /// + /// The date of last Keeplive received. + public DateTime LastKeepAliveDate { get; set; } + /// /// Gets or sets the URL. /// -- cgit v1.2.3 From 083d3272d09395e2b7d73d886377017573e63686 Mon Sep 17 00:00:00 2001 From: gion Date: Tue, 21 Apr 2020 23:37:37 +0200 Subject: Refactor and other minor changes --- .../HttpServer/WebSocketConnection.cs | 5 +- .../Session/SessionWebSocketListener.cs | 42 +- .../Syncplay/SyncplayController.cs | 485 +++++++++++++-------- .../Syncplay/SyncplayManager.cs | 50 +-- MediaBrowser.Api/Syncplay/SyncplayService.cs | 52 ++- MediaBrowser.Api/Syncplay/TimeSyncService.cs | 1 - MediaBrowser.Controller/Syncplay/GroupInfo.cs | 24 +- .../Syncplay/ISyncplayManager.cs | 4 +- MediaBrowser.Model/Syncplay/GroupInfoModel.cs | 38 -- MediaBrowser.Model/Syncplay/GroupInfoView.cs | 38 ++ MediaBrowser.Model/Syncplay/PlaybackRequestType.cs | 2 +- 11 files changed, 441 insertions(+), 300 deletions(-) delete mode 100644 MediaBrowser.Model/Syncplay/GroupInfoModel.cs create mode 100644 MediaBrowser.Model/Syncplay/GroupInfoView.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 171047e65f..c819c163a7 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -176,10 +176,9 @@ namespace Emby.Server.Implementations.HttpServer { SendKeepAliveResponse(); } - - if (OnReceive != null) + else { - OnReceive(info); + OnReceive?.Invoke(info); } } catch (Exception ex) diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index d8e02ef395..b0c6d0aa08 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System; using System.Collections.Concurrent; using System.Linq; @@ -25,7 +26,7 @@ namespace Emby.Server.Implementations.Session public readonly int WebSocketLostTimeout = 60; /// - /// The timer factor; controls the frequency of the timer. + /// The keep-alive timer factor; controls how often the timer will check on the status of the WebSockets. /// public readonly double TimerFactor = 0.2; @@ -136,11 +137,10 @@ namespace Emby.Server.Implementations.Session /// /// The WebSocket. /// The event arguments. - private void _webSocket_Closed(object sender, EventArgs e) + private void OnWebSocketClosed(object sender, EventArgs e) { var webSocket = (IWebSocketConnection) sender; - webSocket.Closed -= _webSocket_Closed; - _webSockets.TryRemove(webSocket, out _); + RemoveWebSocket(webSocket); } /// @@ -149,8 +149,12 @@ namespace Emby.Server.Implementations.Session /// The WebSocket to monitor. private async void KeepAliveWebSocket(IWebSocketConnection webSocket) { - _webSockets.TryAdd(webSocket, 0); - webSocket.Closed += _webSocket_Closed; + if (!_webSockets.TryAdd(webSocket, 0)) + { + _logger.LogWarning("Multiple attempts to keep alive single WebSocket {0}", webSocket); + return; + } + webSocket.Closed += OnWebSocketClosed; webSocket.LastKeepAliveDate = DateTime.UtcNow; // Notify WebSocket about timeout @@ -160,12 +164,22 @@ namespace Emby.Server.Implementations.Session } catch (WebSocketException exception) { - _logger.LogDebug(exception, "Error sending ForceKeepAlive message to WebSocket."); + _logger.LogWarning(exception, "Error sending ForceKeepAlive message to WebSocket."); } StartKeepAliveTimer(); } + /// + /// Removes a WebSocket from the KeepAlive watchlist. + /// + /// The WebSocket to remove. + private void RemoveWebSocket(IWebSocketConnection webSocket) + { + webSocket.Closed -= OnWebSocketClosed; + _webSockets.TryRemove(webSocket, out _); + } + /// /// Starts the KeepAlive timer. /// @@ -195,7 +209,7 @@ namespace Emby.Server.Implementations.Session foreach (var pair in _webSockets) { - pair.Key.Closed -= _webSocket_Closed; + pair.Key.Closed -= OnWebSocketClosed; } } @@ -214,7 +228,7 @@ namespace Emby.Server.Implementations.Session if (inactive.Any()) { - _logger.LogDebug("Sending ForceKeepAlive message to {0} WebSockets.", inactive.Count()); + _logger.LogDebug("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count()); } foreach (var webSocket in inactive) @@ -225,15 +239,19 @@ namespace Emby.Server.Implementations.Session } catch (WebSocketException exception) { - _logger.LogDebug(exception, "Error sending ForceKeepAlive message to WebSocket."); + _logger.LogInformation(exception, "Error sending ForceKeepAlive message to WebSocket."); lost.Append(webSocket); } } if (lost.Any()) { - // TODO: handle lost webSockets - _logger.LogDebug("Lost {0} WebSockets.", lost.Count()); + _logger.LogInformation("Lost {0} WebSockets.", lost.Count()); + foreach (var webSocket in lost) + { + // TODO: handle session relative to the lost webSocket + RemoveWebSocket(webSocket); + } } if (!_webSockets.Any()) diff --git a/Emby.Server.Implementations/Syncplay/SyncplayController.cs b/Emby.Server.Implementations/Syncplay/SyncplayController.cs index 83b4779447..02cf08cd7c 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayController.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayController.cs @@ -16,11 +16,26 @@ namespace Emby.Server.Implementations.Syncplay /// public class SyncplayController : ISyncplayController, IDisposable { + /// + /// Used to filter the sessions of a group. + /// private enum BroadcastType { + /// + /// All sessions will receive the message. + /// AllGroup = 0, - SingleSession = 1, - AllExceptSession = 2, + /// + /// Only the specified session will receive the message. + /// + CurrentSession = 1, + /// + /// All sessions, except the current one, will receive the message. + /// + AllExceptCurrentSession = 2, + /// + /// Only sessions that are not buffering will receive the message. + /// AllReady = 3 } @@ -95,40 +110,46 @@ namespace Emby.Server.Implementations.Syncplay } } + /// + /// Filters sessions of this group. + /// + /// The current session. + /// The filtering type. + /// The array of sessions matching the filter. private SessionInfo[] FilterSessions(SessionInfo from, BroadcastType type) { - if (type == BroadcastType.SingleSession) - { - return new SessionInfo[] { from }; - } - else if (type == BroadcastType.AllGroup) - { - return _group.Partecipants.Values.Select( - session => session.Session - ).ToArray(); - } - else if (type == BroadcastType.AllExceptSession) - { - return _group.Partecipants.Values.Select( - session => session.Session - ).Where( - session => !session.Id.Equals(from.Id) - ).ToArray(); - } - else if (type == BroadcastType.AllReady) + switch (type) { - return _group.Partecipants.Values.Where( - session => !session.IsBuffering - ).Select( - session => session.Session - ).ToArray(); - } - else - { - return new SessionInfo[] {}; + case BroadcastType.CurrentSession: + return new SessionInfo[] { from }; + case BroadcastType.AllGroup: + return _group.Participants.Values.Select( + session => session.Session + ).ToArray(); + case BroadcastType.AllExceptCurrentSession: + return _group.Participants.Values.Select( + session => session.Session + ).Where( + session => !session.Id.Equals(from.Id) + ).ToArray(); + case BroadcastType.AllReady: + return _group.Participants.Values.Where( + session => !session.IsBuffering + ).Select( + session => session.Session + ).ToArray(); + default: + return new SessionInfo[] { }; } } + /// + /// Sends a GroupUpdate message to the interested sessions. + /// + /// The current session. + /// The filtering type. + /// The message to send. + /// The task. private Task SendGroupUpdate(SessionInfo from, BroadcastType type, GroupUpdate message) { IEnumerable GetTasks() @@ -143,6 +164,13 @@ namespace Emby.Server.Implementations.Syncplay return Task.WhenAll(GetTasks()); } + /// + /// Sends a playback command to the interested sessions. + /// + /// The current session. + /// The filtering type. + /// The message to send. + /// The task. private Task SendCommand(SessionInfo from, BroadcastType type, SendCommand message) { IEnumerable GetTasks() @@ -157,31 +185,44 @@ namespace Emby.Server.Implementations.Syncplay return Task.WhenAll(GetTasks()); } + /// + /// Builds a new playback command with some default values. + /// + /// The command type. + /// The SendCommand. private SendCommand NewSyncplayCommand(SendCommandType type) { - var command = new SendCommand(); - command.GroupId = _group.GroupId.ToString(); - command.Command = type; - command.PositionTicks = _group.PositionTicks; - command.When = _group.LastActivity.ToUniversalTime().ToString("o"); - command.EmittedAt = DateTime.UtcNow.ToUniversalTime().ToString("o"); - return command; + return new SendCommand() + { + GroupId = _group.GroupId.ToString(), + Command = type, + PositionTicks = _group.PositionTicks, + When = _group.LastActivity.ToUniversalTime().ToString("o"), + EmittedAt = DateTime.UtcNow.ToUniversalTime().ToString("o") + }; } + /// + /// Builds a new group update message. + /// + /// The update type. + /// The data to send. + /// The GroupUpdate. private GroupUpdate NewSyncplayGroupUpdate(GroupUpdateType type, T data) { - var command = new GroupUpdate(); - command.GroupId = _group.GroupId.ToString(); - command.Type = type; - command.Data = data; - return command; + return new GroupUpdate() + { + GroupId = _group.GroupId.ToString(), + Type = type, + Data = data + }; } /// public void InitGroup(SessionInfo session) { _group.AddSession(session); - _syncplayManager.MapSessionToGroup(session, this); + _syncplayManager.AddSessionToGroup(session, this); _group.PlayingItem = session.FullNowPlayingItem; _group.IsPaused = true; @@ -189,37 +230,35 @@ namespace Emby.Server.Implementations.Syncplay _group.LastActivity = DateTime.UtcNow; var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); - SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession); var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.SingleSession, pauseCommand); + SendCommand(session, BroadcastType.CurrentSession, pauseCommand); } /// public void SessionJoin(SessionInfo session, JoinGroupRequest request) { - if (session.NowPlayingItem != null && - session.NowPlayingItem.Id.Equals(_group.PlayingItem.Id) && - request.PlayingItemId.Equals(_group.PlayingItem.Id)) + if (session.NowPlayingItem?.Id == _group.PlayingItem.Id && request.PlayingItemId == _group.PlayingItem.Id) { _group.AddSession(session); - _syncplayManager.MapSessionToGroup(session, this); + _syncplayManager.AddSessionToGroup(session, this); var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); - SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession); var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserJoined, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptSession, updateOthers); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers); // Client join and play, syncing will happen client side if (!_group.IsPaused) { var playCommand = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.SingleSession, playCommand); + SendCommand(session, BroadcastType.CurrentSession, playCommand); } else { var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.SingleSession, pauseCommand); + SendCommand(session, BroadcastType.CurrentSession, pauseCommand); } } else @@ -228,7 +267,7 @@ namespace Emby.Server.Implementations.Syncplay playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id }; playRequest.StartPositionTicks = _group.PositionTicks; var update = NewSyncplayGroupUpdate(GroupUpdateType.PrepareSession, playRequest); - SendGroupUpdate(session, BroadcastType.SingleSession, update); + SendGroupUpdate(session, BroadcastType.CurrentSession, update); } } @@ -236,182 +275,250 @@ namespace Emby.Server.Implementations.Syncplay public void SessionLeave(SessionInfo session) { _group.RemoveSession(session); - _syncplayManager.UnmapSessionFromGroup(session, this); + _syncplayManager.RemoveSessionFromGroup(session, this); var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupLeft, _group.PositionTicks); - SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession); var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserLeft, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptSession, updateOthers); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers); } /// public void HandleRequest(SessionInfo session, PlaybackRequest request) { - if (request.Type.Equals(PlaybackRequestType.Play)) + // The server's job is to mantain a consistent state to which clients refer to, + // as also to notify clients of state changes. + // The actual syncing of media playback happens client side. + // Clients are aware of the server's time and use it to sync. + switch (request.Type) { - if (_group.IsPaused) - { - var delay = _group.GetHighestPing() * 2; - delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; - - _group.IsPaused = false; - _group.LastActivity = DateTime.UtcNow.AddMilliseconds( - delay - ); + case PlaybackRequestType.Play: + HandlePlayRequest(session, request); + break; + case PlaybackRequestType.Pause: + HandlePauseRequest(session, request); + break; + case PlaybackRequestType.Seek: + HandleSeekRequest(session, request); + break; + case PlaybackRequestType.Buffering: + HandleBufferingRequest(session, request); + break; + case PlaybackRequestType.BufferingDone: + HandleBufferingDoneRequest(session, request); + break; + case PlaybackRequestType.UpdatePing: + HandlePingUpdateRequest(session, request); + break; + } + } - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllGroup, command); - } - else - { - // Client got lost - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.SingleSession, command); - } + /// + /// Handles a play action requested by a session. + /// + /// The session. + /// The play action. + private void HandlePlayRequest(SessionInfo session, PlaybackRequest request) + { + if (_group.IsPaused) + { + // Pick a suitable time that accounts for latency + var delay = _group.GetHighestPing() * 2; + delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; + + // Unpause group and set starting point in future + // Clients will start playback at LastActivity (datetime) from PositionTicks (playback position) + // The added delay does not guarantee, of course, that the command will be received in time + // Playback synchronization will mainly happen client side + _group.IsPaused = false; + _group.LastActivity = DateTime.UtcNow.AddMilliseconds( + delay + ); + + var command = NewSyncplayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.AllGroup, command); } - else if (request.Type.Equals(PlaybackRequestType.Pause)) + else { - if (!_group.IsPaused) - { - _group.IsPaused = true; - var currentTime = DateTime.UtcNow; - var elapsedTime = currentTime - _group.LastActivity; - _group.LastActivity = currentTime; - _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; + // Client got lost, sending current state + var command = NewSyncplayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.CurrentSession, command); + } + } - var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.AllGroup, command); - } - else - { - var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.SingleSession, command); - } + /// + /// Handles a pause action requested by a session. + /// + /// The session. + /// The pause action. + private void HandlePauseRequest(SessionInfo session, PlaybackRequest request) + { + if (!_group.IsPaused) + { + // Pause group and compute the media playback position + _group.IsPaused = true; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - _group.LastActivity; + _group.LastActivity = currentTime; + // Seek only if playback actually started + // (a pause request may be issued during the delay added to account for latency) + _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; + + var command = NewSyncplayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.AllGroup, command); } - else if (request.Type.Equals(PlaybackRequestType.Seek)) + else { - // Sanitize PositionTicks - var ticks = request.PositionTicks ??= 0; - ticks = ticks >= 0 ? ticks : 0; - if (_group.PlayingItem.RunTimeTicks != null) - { - var runTimeTicks = _group.PlayingItem.RunTimeTicks ??= 0; - ticks = ticks > runTimeTicks ? runTimeTicks : ticks; - } + // Client got lost, sending current state + var command = NewSyncplayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.CurrentSession, command); + } + } + + /// + /// Handles a seek action requested by a session. + /// + /// The session. + /// The seek action. + private void HandleSeekRequest(SessionInfo session, PlaybackRequest request) + { + // Sanitize PositionTicks + var ticks = request.PositionTicks ??= 0; + ticks = ticks >= 0 ? ticks : 0; + if (_group.PlayingItem.RunTimeTicks != null) + { + var runTimeTicks = _group.PlayingItem.RunTimeTicks ??= 0; + ticks = ticks > runTimeTicks ? runTimeTicks : ticks; + } + + // Pause and seek + _group.IsPaused = true; + _group.PositionTicks = ticks; + _group.LastActivity = DateTime.UtcNow; + + var command = NewSyncplayCommand(SendCommandType.Seek); + SendCommand(session, BroadcastType.AllGroup, command); + } + /// + /// Handles a buffering action requested by a session. + /// + /// The session. + /// The buffering action. + private void HandleBufferingRequest(SessionInfo session, PlaybackRequest request) + { + if (!_group.IsPaused) + { + // Pause group and compute the media playback position _group.IsPaused = true; - _group.PositionTicks = ticks; - _group.LastActivity = DateTime.UtcNow; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - _group.LastActivity; + _group.LastActivity = currentTime; + _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; - var command = NewSyncplayCommand(SendCommandType.Seek); - SendCommand(session, BroadcastType.AllGroup, command); + _group.SetBuffering(session, true); + + // Send pause command to all non-buffering sessions + var command = NewSyncplayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.AllReady, command); + + var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.GroupWait, session.UserName); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers); } - // TODO: client does not implement this yet - else if (request.Type.Equals(PlaybackRequestType.Buffering)) + else { - if (!_group.IsPaused) - { - _group.IsPaused = true; - var currentTime = DateTime.UtcNow; - var elapsedTime = currentTime - _group.LastActivity; - _group.LastActivity = currentTime; - _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; + // Client got lost, sending current state + var command = NewSyncplayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.CurrentSession, command); + } + } - _group.SetBuffering(session, true); + /// + /// Handles a buffering-done action requested by a session. + /// + /// The session. + /// The buffering-done action. + private void HandleBufferingDoneRequest(SessionInfo session, PlaybackRequest request) + { + if (_group.IsPaused) + { + _group.SetBuffering(session, false); - // Send pause command to all non-buffering sessions - var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.AllReady, command); + var when = request.When ??= DateTime.UtcNow; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - when; + var clientPosition = TimeSpan.FromTicks(request.PositionTicks ??= 0) + elapsedTime; + var delay = _group.PositionTicks - clientPosition.Ticks; - var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.GroupWait, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptSession, updateOthers); - } - else + if (_group.IsBuffering()) { + // Others are buffering, tell this client to pause when ready var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.SingleSession, command); + command.When = currentTime.AddMilliseconds( + delay + ).ToUniversalTime().ToString("o"); + SendCommand(session, BroadcastType.CurrentSession, command); } - } - // TODO: client does not implement this yet - else if (request.Type.Equals(PlaybackRequestType.BufferingComplete)) - { - if (_group.IsPaused) + else { - _group.SetBuffering(session, false); - - if (_group.IsBuffering()) { - // Others are buffering, tell this client to pause when ready - var when = request.When ??= DateTime.UtcNow; - var currentTime = DateTime.UtcNow; - var elapsedTime = currentTime - when; - var clientPosition = TimeSpan.FromTicks(request.PositionTicks ??= 0) + elapsedTime; - var delay = _group.PositionTicks - clientPosition.Ticks; - - var command = NewSyncplayCommand(SendCommandType.Pause); - command.When = currentTime.AddMilliseconds( + // Let other clients resume as soon as the buffering client catches up + _group.IsPaused = false; + + if (delay > _group.GetHighestPing() * 2) + { + // Client that was buffering is recovering, notifying others to resume + _group.LastActivity = currentTime.AddMilliseconds( delay - ).ToUniversalTime().ToString("o"); - SendCommand(session, BroadcastType.SingleSession, command); + ); + var command = NewSyncplayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.AllExceptCurrentSession, command); } else { - // Let other clients resume as soon as the buffering client catches up - var when = request.When ??= DateTime.UtcNow; - var currentTime = DateTime.UtcNow; - var elapsedTime = currentTime - when; - var clientPosition = TimeSpan.FromTicks(request.PositionTicks ??= 0) + elapsedTime; - var delay = _group.PositionTicks - clientPosition.Ticks; - - _group.IsPaused = false; - - if (delay > _group.GetHighestPing() * 2) - { - // Client that was buffering is recovering, notifying others to resume - _group.LastActivity = currentTime.AddMilliseconds( - delay - ); - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllExceptSession, command); - } - else - { - // Client, that was buffering, resumed playback but did not update others in time - delay = _group.GetHighestPing() * 2; - delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; - - _group.LastActivity = currentTime.AddMilliseconds( - delay - ); - - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllGroup, command); - } - } - } - else - { - // Make sure client has latest group state - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.SingleSession, command); + // Client, that was buffering, resumed playback but did not update others in time + delay = _group.GetHighestPing() * 2; + delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; + + _group.LastActivity = currentTime.AddMilliseconds( + delay + ); + + var command = NewSyncplayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.AllGroup, command); + } } } - else if (request.Type.Equals(PlaybackRequestType.UpdatePing)) + else { - _group.UpdatePing(session, request.Ping ??= _group.DefaulPing); + // Group was not waiting, make sure client has latest state + var command = NewSyncplayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.CurrentSession, command); } } + /// + /// Updates ping of a session. + /// + /// The session. + /// The update. + private void HandlePingUpdateRequest(SessionInfo session, PlaybackRequest request) + { + // Collected pings are used to account for network latency when unpausing playback + _group.UpdatePing(session, request.Ping ??= _group.DefaulPing); + } + /// public GroupInfoView GetInfo() { - var info = new GroupInfoView(); - info.GroupId = GetGroupId().ToString(); - info.PlayingItemName = _group.PlayingItem.Name; - info.PlayingItemId = _group.PlayingItem.Id.ToString(); - info.PositionTicks = _group.PositionTicks; - info.Partecipants = _group.Partecipants.Values.Select(session => session.Session.UserName).ToArray(); - return info; + return new GroupInfoView() + { + GroupId = GetGroupId().ToString(), + PlayingItemName = _group.PlayingItem.Name, + PlayingItemId = _group.PlayingItem.Id.ToString(), + PositionTicks = _group.PositionTicks, + Participants = _group.Participants.Values.Select(session => session.Session.UserName).ToArray() + }; } } } diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index 60d70e5fde..e7df8925e8 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -49,7 +49,7 @@ namespace Emby.Server.Implementations.Syncplay /// The groups. /// private readonly ConcurrentDictionary _groups = - new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private bool _disposed = false; @@ -64,8 +64,8 @@ namespace Emby.Server.Implementations.Syncplay _sessionManager = sessionManager; _libraryManager = libraryManager; - _sessionManager.SessionEnded += _sessionManager_SessionEnded; - _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped; + _sessionManager.SessionEnded += OnSessionManagerSessionEnded; + _sessionManager.PlaybackStopped += OnSessionManagerPlaybackStopped; } /// @@ -92,8 +92,8 @@ namespace Emby.Server.Implementations.Syncplay return; } - _sessionManager.SessionEnded -= _sessionManager_SessionEnded; - _sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped; + _sessionManager.SessionEnded -= OnSessionManagerSessionEnded; + _sessionManager.PlaybackStopped -= OnSessionManagerPlaybackStopped; _disposed = true; } @@ -106,14 +106,14 @@ namespace Emby.Server.Implementations.Syncplay } } - void _sessionManager_SessionEnded(object sender, SessionEventArgs e) + private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e) { var session = e.SessionInfo; if (!IsSessionInGroup(session)) return; LeaveGroup(session); } - void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) + private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e) { var session = e.Session; if (!IsSessionInGroup(session)) return; @@ -130,13 +130,13 @@ namespace Emby.Server.Implementations.Syncplay var item = _libraryManager.GetItemById(itemId); var hasParentalRatingAccess = user.Policy.MaxParentalRating.HasValue ? item.InheritedParentalRatingValue <= user.Policy.MaxParentalRating : true; - if (!user.Policy.EnableAllFolders) + if (!user.Policy.EnableAllFolders && hasParentalRatingAccess) { var collections = _libraryManager.GetCollectionFolders(item).Select( folder => folder.Id.ToString("N", CultureInfo.InvariantCulture) ); var intersect = collections.Intersect(user.Policy.EnabledFolders); - return intersect.Count() > 0 && hasParentalRatingAccess; + return intersect.Count() > 0; } else { @@ -165,7 +165,7 @@ namespace Emby.Server.Implementations.Syncplay if (user.Policy.SyncplayAccess != SyncplayAccess.CreateAndJoinGroups) { - // TODO: shall an error message be sent back to the client? + // TODO: report the error to the client throw new ArgumentException("User does not have permission to create groups"); } @@ -187,22 +187,16 @@ namespace Emby.Server.Implementations.Syncplay if (user.Policy.SyncplayAccess == SyncplayAccess.None) { - // TODO: shall an error message be sent back to the client? + // TODO: report the error to the client throw new ArgumentException("User does not have access to syncplay"); } - if (IsSessionInGroup(session)) - { - if (GetSessionGroup(session).Equals(groupId)) return; - LeaveGroup(session); - } - ISyncplayController group; _groups.TryGetValue(groupId, out group); if (group == null) { - _logger.LogError("Syncplaymanager JoinGroup: " + groupId + " does not exist."); + _logger.LogWarning("Syncplaymanager JoinGroup: {0} does not exist.", groupId); var update = new GroupUpdate(); update.Type = GroupUpdateType.NotInGroup; @@ -215,20 +209,26 @@ namespace Emby.Server.Implementations.Syncplay throw new ArgumentException("User does not have access to playing item"); } + if (IsSessionInGroup(session)) + { + if (GetSessionGroup(session).Equals(groupId)) return; + LeaveGroup(session); + } + group.SessionJoin(session, request); } /// public void LeaveGroup(SessionInfo session) { - // TODO: what happens to users that are in a group and get their permissions revoked? + // TODO: determine what happens to users that are in a group and get their permissions revoked ISyncplayController group; _sessionToGroupMap.TryGetValue(session.Id, out group); if (group == null) { - _logger.LogWarning("Syncplaymanager HandleRequest: " + session.Id + " not in group."); + _logger.LogWarning("Syncplaymanager LeaveGroup: {0} does not belong to any group.", session.Id); var update = new GroupUpdate(); update.Type = GroupUpdateType.NotInGroup; @@ -257,9 +257,7 @@ namespace Emby.Server.Implementations.Syncplay if (session.NowPlayingItem != null) { return _groups.Values.Where( - group => HasAccessToItem(user, group.GetPlayingItemId()) - ).Where( - group => group.GetPlayingItemId().Equals(session.FullNowPlayingItem.Id) + group => group.GetPlayingItemId().Equals(session.FullNowPlayingItem.Id) && HasAccessToItem(user, group.GetPlayingItemId()) ).Select( group => group.GetInfo() ).ToList(); @@ -291,7 +289,7 @@ namespace Emby.Server.Implementations.Syncplay if (group == null) { - _logger.LogWarning("Syncplaymanager HandleRequest: " + session.Id + " not in group."); + _logger.LogWarning("Syncplaymanager HandleRequest: {0} not in a group.", session.Id); var update = new GroupUpdate(); update.Type = GroupUpdateType.NotInGroup; @@ -302,7 +300,7 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void MapSessionToGroup(SessionInfo session, ISyncplayController group) + public void AddSessionToGroup(SessionInfo session, ISyncplayController group) { if (IsSessionInGroup(session)) { @@ -312,7 +310,7 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void UnmapSessionFromGroup(SessionInfo session, ISyncplayController group) + public void RemoveSessionFromGroup(SessionInfo session, ISyncplayController group) { if (!IsSessionInGroup(session)) { diff --git a/MediaBrowser.Api/Syncplay/SyncplayService.cs b/MediaBrowser.Api/Syncplay/SyncplayService.cs index af220ed81d..2eaf9ce834 100644 --- a/MediaBrowser.Api/Syncplay/SyncplayService.cs +++ b/MediaBrowser.Api/Syncplay/SyncplayService.cs @@ -90,12 +90,20 @@ namespace MediaBrowser.Api.Syncplay [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string SessionId { get; set; } + /// + /// Gets or sets the date used to pin PositionTicks in time. + /// + /// The date related to PositionTicks. [ApiMember(Name = "When", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string When { get; set; } [ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")] public long PositionTicks { get; set; } + /// + /// Gets or sets whether this is a buffering or a buffering-done request. + /// + /// true if buffering is complete; false otherwise. [ApiMember(Name = "Resume", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")] public bool Resume { get; set; } } @@ -162,8 +170,10 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayJoinGroup request) { var currentSession = GetSession(_sessionContext); - var joinRequest = new JoinGroupRequest(); - joinRequest.GroupId = Guid.Parse(request.GroupId); + var joinRequest = new JoinGroupRequest() + { + GroupId = Guid.Parse(request.GroupId) + }; try { joinRequest.PlayingItemId = Guid.Parse(request.PlayingItemId); @@ -207,8 +217,10 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayPlayRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest(); - syncplayRequest.Type = PlaybackRequestType.Play; + var syncplayRequest = new PlaybackRequest() + { + Type = PlaybackRequestType.Play + }; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } @@ -219,8 +231,10 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayPauseRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest(); - syncplayRequest.Type = PlaybackRequestType.Pause; + var syncplayRequest = new PlaybackRequest() + { + Type = PlaybackRequestType.Pause + }; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } @@ -231,9 +245,11 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplaySeekRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest(); - syncplayRequest.Type = PlaybackRequestType.Seek; - syncplayRequest.PositionTicks = request.PositionTicks; + var syncplayRequest = new PlaybackRequest() + { + Type = PlaybackRequestType.Seek, + PositionTicks = request.PositionTicks + }; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } @@ -244,10 +260,12 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayBufferingRequest request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest(); - syncplayRequest.Type = request.Resume ? PlaybackRequestType.BufferingComplete : PlaybackRequestType.Buffering; - syncplayRequest.When = DateTime.Parse(request.When); - syncplayRequest.PositionTicks = request.PositionTicks; + var syncplayRequest = new PlaybackRequest() + { + Type = request.Resume ? PlaybackRequestType.BufferingDone : PlaybackRequestType.Buffering, + When = DateTime.Parse(request.When), + PositionTicks = request.PositionTicks + }; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } @@ -258,9 +276,11 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayUpdatePing request) { var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest(); - syncplayRequest.Type = PlaybackRequestType.UpdatePing; - syncplayRequest.Ping = Convert.ToInt64(request.Ping); + var syncplayRequest = new PlaybackRequest() + { + Type = PlaybackRequestType.UpdatePing, + Ping = Convert.ToInt64(request.Ping) + }; _syncplayManager.HandleRequest(currentSession, syncplayRequest); } } diff --git a/MediaBrowser.Api/Syncplay/TimeSyncService.cs b/MediaBrowser.Api/Syncplay/TimeSyncService.cs index a69e0e293a..8974130157 100644 --- a/MediaBrowser.Api/Syncplay/TimeSyncService.cs +++ b/MediaBrowser.Api/Syncplay/TimeSyncService.cs @@ -54,7 +54,6 @@ namespace MediaBrowser.Api.Syncplay var response = new UtcTimeResponse(); response.RequestReceptionTime = requestReceptionTime; - var currentSession = GetSession(_sessionContext); // Important to keep the following two lines at the end var responseTransmissionTime = DateTime.UtcNow.ToUniversalTime().ToString("o"); diff --git a/MediaBrowser.Controller/Syncplay/GroupInfo.cs b/MediaBrowser.Controller/Syncplay/GroupInfo.cs index 42e85ef864..8e886a2cb2 100644 --- a/MediaBrowser.Controller/Syncplay/GroupInfo.cs +++ b/MediaBrowser.Controller/Syncplay/GroupInfo.cs @@ -46,11 +46,11 @@ namespace MediaBrowser.Controller.Syncplay public DateTime LastActivity { get; set; } /// - /// Gets the partecipants. + /// Gets the participants. /// - /// The partecipants. - public readonly ConcurrentDictionary Partecipants = - new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + /// The participants, or members of the group. + public readonly ConcurrentDictionary Participants = + new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); /// /// Checks if a session is in this group. @@ -58,7 +58,7 @@ namespace MediaBrowser.Controller.Syncplay /// true if the session is in this group; false otherwise. public bool ContainsSession(string sessionId) { - return Partecipants.ContainsKey(sessionId); + return Participants.ContainsKey(sessionId); } /// @@ -72,7 +72,7 @@ namespace MediaBrowser.Controller.Syncplay member.Session = session; member.Ping = DefaulPing; member.IsBuffering = false; - Partecipants[session.Id.ToString()] = member; + Participants[session.Id.ToString()] = member; } /// @@ -84,7 +84,7 @@ namespace MediaBrowser.Controller.Syncplay { if (!ContainsSession(session.Id.ToString())) return; GroupMember member; - Partecipants.Remove(session.Id.ToString(), out member); + Participants.Remove(session.Id.ToString(), out member); } /// @@ -95,7 +95,7 @@ namespace MediaBrowser.Controller.Syncplay public void UpdatePing(SessionInfo session, long ping) { if (!ContainsSession(session.Id.ToString())) return; - Partecipants[session.Id.ToString()].Ping = ping; + Participants[session.Id.ToString()].Ping = ping; } /// @@ -105,7 +105,7 @@ namespace MediaBrowser.Controller.Syncplay public long GetHighestPing() { long max = Int64.MinValue; - foreach (var session in Partecipants.Values) + foreach (var session in Participants.Values) { max = Math.Max(max, session.Ping); } @@ -120,7 +120,7 @@ namespace MediaBrowser.Controller.Syncplay public void SetBuffering(SessionInfo session, bool isBuffering) { if (!ContainsSession(session.Id.ToString())) return; - Partecipants[session.Id.ToString()].IsBuffering = isBuffering; + Participants[session.Id.ToString()].IsBuffering = isBuffering; } /// @@ -129,7 +129,7 @@ namespace MediaBrowser.Controller.Syncplay /// true if there is a session buffering in the group; false otherwise. public bool IsBuffering() { - foreach (var session in Partecipants.Values) + foreach (var session in Participants.Values) { if (session.IsBuffering) return true; } @@ -142,7 +142,7 @@ namespace MediaBrowser.Controller.Syncplay /// true if the group is empty; false otherwise. public bool IsEmpty() { - return Partecipants.Count == 0; + return Participants.Count == 0; } } } diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs index d0cf8fa9c8..433d6d8bc1 100644 --- a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs +++ b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs @@ -50,7 +50,7 @@ namespace MediaBrowser.Controller.Syncplay /// The session. /// The group. /// - void MapSessionToGroup(SessionInfo session, ISyncplayController group); + void AddSessionToGroup(SessionInfo session, ISyncplayController group); /// /// Unmaps a session from a group. @@ -58,6 +58,6 @@ namespace MediaBrowser.Controller.Syncplay /// The session. /// The group. /// - void UnmapSessionFromGroup(SessionInfo session, ISyncplayController group); + void RemoveSessionFromGroup(SessionInfo session, ISyncplayController group); } } diff --git a/MediaBrowser.Model/Syncplay/GroupInfoModel.cs b/MediaBrowser.Model/Syncplay/GroupInfoModel.cs deleted file mode 100644 index 599c0dbfc9..0000000000 --- a/MediaBrowser.Model/Syncplay/GroupInfoModel.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Class GroupInfoModel. - /// - public class GroupInfoView - { - /// - /// Gets or sets the group identifier. - /// - /// The group identifier. - public string GroupId { get; set; } - - /// - /// Gets or sets the playing item id. - /// - /// The playing item id. - public string PlayingItemId { get; set; } - - /// - /// Gets or sets the playing item name. - /// - /// The playing item name. - public string PlayingItemName { get; set; } - - /// - /// Gets or sets the position ticks. - /// - /// The position ticks. - public long PositionTicks { get; set; } - - /// - /// Gets or sets the partecipants. - /// - /// The partecipants. - public string[] Partecipants { get; set; } - } -} diff --git a/MediaBrowser.Model/Syncplay/GroupInfoView.cs b/MediaBrowser.Model/Syncplay/GroupInfoView.cs new file mode 100644 index 0000000000..50ad70630f --- /dev/null +++ b/MediaBrowser.Model/Syncplay/GroupInfoView.cs @@ -0,0 +1,38 @@ +namespace MediaBrowser.Model.Syncplay +{ + /// + /// Class GroupInfoView. + /// + public class GroupInfoView + { + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public string GroupId { get; set; } + + /// + /// Gets or sets the playing item id. + /// + /// The playing item id. + public string PlayingItemId { get; set; } + + /// + /// Gets or sets the playing item name. + /// + /// The playing item name. + public string PlayingItemName { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long PositionTicks { get; set; } + + /// + /// Gets or sets the participants. + /// + /// The participants. + public string[] Participants { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs b/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs index 3d99b2718e..b3d49d09ef 100644 --- a/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs +++ b/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.Model.Syncplay /// /// A user is signaling that playback resumed. /// - BufferingComplete = 4, + BufferingDone = 4, /// /// A user is reporting its ping. /// -- cgit v1.2.3 From 0b974d09ca08f70d9cd61d4871698956026b7b3b Mon Sep 17 00:00:00 2001 From: gion Date: Tue, 28 Apr 2020 14:12:06 +0200 Subject: Synchronize access to data --- .../Session/SessionWebSocketListener.cs | 186 ++++++++++++++------- .../Syncplay/SyncplayManager.cs | 147 +++++++++------- MediaBrowser.Api/Syncplay/TimeSyncService.cs | 8 +- 3 files changed, 205 insertions(+), 136 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index b0c6d0aa08..7a316b070c 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -1,6 +1,5 @@ -using System.Collections.Generic; using System; -using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; using System.Net.WebSockets; using System.Threading; @@ -26,9 +25,9 @@ namespace Emby.Server.Implementations.Session public readonly int WebSocketLostTimeout = 60; /// - /// The keep-alive timer factor; controls how often the timer will check on the status of the WebSockets. + /// The keep-alive interval factor; controls how often the watcher will check on the status of the WebSockets. /// - public readonly double TimerFactor = 0.2; + public readonly double IntervalFactor = 0.2; /// /// The ForceKeepAlive factor; controls when a ForceKeepAlive is sent. @@ -53,14 +52,24 @@ namespace Emby.Server.Implementations.Session private readonly IHttpServer _httpServer; /// - /// The KeepAlive timer. + /// The KeepAlive cancellation token. + /// + private CancellationTokenSource _keepAliveCancellationToken; + + /// + /// Lock used for accesing the KeepAlive cancellation token. /// - private Timer _keepAliveTimer; + private readonly object _keepAliveLock = new object(); /// /// The WebSocket watchlist. /// - private readonly ConcurrentDictionary _webSockets = new ConcurrentDictionary(); + private readonly HashSet _webSockets = new HashSet(); + + /// + /// Lock used for accesing the WebSockets watchlist. + /// + private readonly object _webSocketsLock = new object(); /// /// Initializes a new instance of the class. @@ -113,7 +122,7 @@ namespace Emby.Server.Implementations.Session public void Dispose() { _httpServer.WebSocketConnected -= _serverManager_WebSocketConnected; - StopKeepAliveTimer(); + StopKeepAlive(); } /// @@ -140,6 +149,7 @@ namespace Emby.Server.Implementations.Session private void OnWebSocketClosed(object sender, EventArgs e) { var webSocket = (IWebSocketConnection) sender; + _logger.LogDebug("WebSockets {0} closed.", webSocket); RemoveWebSocket(webSocket); } @@ -147,15 +157,20 @@ namespace Emby.Server.Implementations.Session /// Adds a WebSocket to the KeepAlive watchlist. /// /// The WebSocket to monitor. - private async void KeepAliveWebSocket(IWebSocketConnection webSocket) + private async Task KeepAliveWebSocket(IWebSocketConnection webSocket) { - if (!_webSockets.TryAdd(webSocket, 0)) + lock (_webSocketsLock) { - _logger.LogWarning("Multiple attempts to keep alive single WebSocket {0}", webSocket); - return; + if (!_webSockets.Add(webSocket)) + { + _logger.LogWarning("Multiple attempts to keep alive single WebSocket {0}", webSocket); + return; + } + webSocket.Closed += OnWebSocketClosed; + webSocket.LastKeepAliveDate = DateTime.UtcNow; + + StartKeepAlive(); } - webSocket.Closed += OnWebSocketClosed; - webSocket.LastKeepAliveDate = DateTime.UtcNow; // Notify WebSocket about timeout try @@ -164,10 +179,8 @@ namespace Emby.Server.Implementations.Session } catch (WebSocketException exception) { - _logger.LogWarning(exception, "Error sending ForceKeepAlive message to WebSocket."); + _logger.LogWarning(exception, "Error sending ForceKeepAlive message to WebSocket {0}.", webSocket); } - - StartKeepAliveTimer(); } /// @@ -176,87 +189,130 @@ namespace Emby.Server.Implementations.Session /// The WebSocket to remove. private void RemoveWebSocket(IWebSocketConnection webSocket) { - webSocket.Closed -= OnWebSocketClosed; - _webSockets.TryRemove(webSocket, out _); + lock (_webSocketsLock) + { + if (!_webSockets.Remove(webSocket)) + { + _logger.LogWarning("WebSocket {0} not on watchlist.", webSocket); + } + else + { + webSocket.Closed -= OnWebSocketClosed; + } + } } /// - /// Starts the KeepAlive timer. + /// Starts the KeepAlive watcher. /// - private void StartKeepAliveTimer() + private void StartKeepAlive() { - if (_keepAliveTimer == null) + lock (_keepAliveLock) { - _keepAliveTimer = new Timer( - KeepAliveSockets, - null, - TimeSpan.FromSeconds(WebSocketLostTimeout * TimerFactor), - TimeSpan.FromSeconds(WebSocketLostTimeout * TimerFactor) - ); + if (_keepAliveCancellationToken == null) + { + _keepAliveCancellationToken = new CancellationTokenSource(); + // Start KeepAlive watcher + KeepAliveSockets( + TimeSpan.FromSeconds(WebSocketLostTimeout * IntervalFactor), + _keepAliveCancellationToken.Token); + } } } /// - /// Stops the KeepAlive timer. + /// Stops the KeepAlive watcher. /// - private void StopKeepAliveTimer() + private void StopKeepAlive() { - if (_keepAliveTimer != null) + lock (_keepAliveLock) { - _keepAliveTimer.Dispose(); - _keepAliveTimer = null; + if (_keepAliveCancellationToken != null) + { + _keepAliveCancellationToken.Cancel(); + _keepAliveCancellationToken = null; + } } - foreach (var pair in _webSockets) + lock (_webSocketsLock) { - pair.Key.Closed -= OnWebSocketClosed; + foreach (var webSocket in _webSockets) + { + webSocket.Closed -= OnWebSocketClosed; + } + _webSockets.Clear(); } } /// - /// Checks status of KeepAlive of WebSockets. + /// Checks status of KeepAlive of WebSockets once every the specified interval time. /// - /// The state. - private async void KeepAliveSockets(object state) + /// The interval. + /// The cancellation token. + private async Task KeepAliveSockets(TimeSpan interval, CancellationToken cancellationToken) { - var inactive = _webSockets.Keys.Where(i => + while (true) { - var elapsed = (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds; - return (elapsed > WebSocketLostTimeout * ForceKeepAliveFactor) && (elapsed < WebSocketLostTimeout); - }); - var lost = _webSockets.Keys.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout); + _logger.LogDebug("Watching {0} WebSockets.", _webSockets.Count()); - if (inactive.Any()) - { - _logger.LogDebug("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count()); - } + IEnumerable inactive; + IEnumerable lost; + lock (_webSocketsLock) + { + inactive = _webSockets.Where(i => + { + var elapsed = (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds; + return (elapsed > WebSocketLostTimeout * ForceKeepAliveFactor) && (elapsed < WebSocketLostTimeout); + }); + lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout); + } - foreach (var webSocket in inactive) - { - try + if (inactive.Any()) { - await SendForceKeepAlive(webSocket); + _logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count()); } - catch (WebSocketException exception) + + foreach (var webSocket in inactive) { - _logger.LogInformation(exception, "Error sending ForceKeepAlive message to WebSocket."); - lost.Append(webSocket); + try + { + await SendForceKeepAlive(webSocket); + } + catch (WebSocketException exception) + { + _logger.LogInformation(exception, "Error sending ForceKeepAlive message to WebSocket."); + lost = lost.Append(webSocket); + } } - } - if (lost.Any()) - { - _logger.LogInformation("Lost {0} WebSockets.", lost.Count()); - foreach (var webSocket in lost) + lock (_webSocketsLock) { - // TODO: handle session relative to the lost webSocket - RemoveWebSocket(webSocket); + if (lost.Any()) + { + _logger.LogInformation("Lost {0} WebSockets.", lost.Count()); + foreach (var webSocket in lost.ToList()) + { + // TODO: handle session relative to the lost webSocket + RemoveWebSocket(webSocket); + } + } + + if (!_webSockets.Any()) + { + StopKeepAlive(); + } } - } - if (!_webSockets.Any()) - { - StopKeepAliveTimer(); + // Wait for next interval + Task task = Task.Delay(interval, cancellationToken); + try + { + await task; + } + catch (TaskCanceledException) + { + return; + } } } diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index 5aefd1fd98..eb61da7f32 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -42,14 +41,19 @@ namespace Emby.Server.Implementations.Syncplay /// /// The map between sessions and groups. /// - private readonly ConcurrentDictionary _sessionToGroupMap = - new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _sessionToGroupMap = + new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// The groups. /// - private readonly ConcurrentDictionary _groups = - new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _groups = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Lock used for accesing any group. + /// + private readonly object _groupsLock = new object(); private bool _disposed = false; @@ -175,15 +179,18 @@ namespace Emby.Server.Implementations.Syncplay return; } - if (IsSessionInGroup(session)) + lock (_groupsLock) { - LeaveGroup(session); - } + if (IsSessionInGroup(session)) + { + LeaveGroup(session); + } - var group = new SyncplayController(_logger, _sessionManager, this); - _groups[group.GetGroupId().ToString()] = group; + var group = new SyncplayController(_logger, _sessionManager, this); + _groups[group.GetGroupId().ToString()] = group; - group.InitGroup(session); + group.InitGroup(session); + } } /// @@ -203,67 +210,73 @@ namespace Emby.Server.Implementations.Syncplay return; } - ISyncplayController group; - _groups.TryGetValue(groupId, out group); - - if (group == null) + lock (_groupsLock) { - _logger.LogWarning("Syncplaymanager JoinGroup: {0} tried to join group {0} that does not exist.", session.Id, groupId); + ISyncplayController group; + _groups.TryGetValue(groupId, out group); - var error = new GroupUpdate() + if (group == null) { - Type = GroupUpdateType.GroupNotJoined - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; - } + _logger.LogWarning("Syncplaymanager JoinGroup: {0} tried to join group {0} that does not exist.", session.Id, groupId); - if (!HasAccessToItem(user, group.GetPlayingItemId())) - { - _logger.LogWarning("Syncplaymanager JoinGroup: {0} does not have access to {1}.", session.Id, group.GetPlayingItemId()); + var error = new GroupUpdate() + { + Type = GroupUpdateType.GroupNotJoined + }; + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } - var error = new GroupUpdate() + if (!HasAccessToItem(user, group.GetPlayingItemId())) { - GroupId = group.GetGroupId().ToString(), - Type = GroupUpdateType.LibraryAccessDenied - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; - } + _logger.LogWarning("Syncplaymanager JoinGroup: {0} does not have access to {1}.", session.Id, group.GetPlayingItemId()); + + var error = new GroupUpdate() + { + GroupId = group.GetGroupId().ToString(), + Type = GroupUpdateType.LibraryAccessDenied + }; + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } + + if (IsSessionInGroup(session)) + { + if (GetSessionGroup(session).Equals(groupId)) return; + LeaveGroup(session); + } - if (IsSessionInGroup(session)) - { - if (GetSessionGroup(session).Equals(groupId)) return; - LeaveGroup(session); + group.SessionJoin(session, request); } - - group.SessionJoin(session, request); } /// public void LeaveGroup(SessionInfo session) { // TODO: determine what happens to users that are in a group and get their permissions revoked - - ISyncplayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); - - if (group == null) + lock (_groupsLock) { - _logger.LogWarning("Syncplaymanager LeaveGroup: {0} does not belong to any group.", session.Id); + ISyncplayController group; + _sessionToGroupMap.TryGetValue(session.Id, out group); - var error = new GroupUpdate() + if (group == null) { - Type = GroupUpdateType.NotInGroup - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; - } - group.SessionLeave(session); + _logger.LogWarning("Syncplaymanager LeaveGroup: {0} does not belong to any group.", session.Id); - if (group.IsGroupEmpty()) - { - _groups.Remove(group.GetGroupId().ToString(), out _); + var error = new GroupUpdate() + { + Type = GroupUpdateType.NotInGroup + }; + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } + + group.SessionLeave(session); + + if (group.IsGroupEmpty()) + { + _groups.Remove(group.GetGroupId().ToString(), out _); + } } } @@ -314,21 +327,25 @@ namespace Emby.Server.Implementations.Syncplay return; } - ISyncplayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); - - if (group == null) + lock (_groupsLock) { - _logger.LogWarning("Syncplaymanager HandleRequest: {0} does not belong to any group.", session.Id); + ISyncplayController group; + _sessionToGroupMap.TryGetValue(session.Id, out group); - var error = new GroupUpdate() + if (group == null) { - Type = GroupUpdateType.NotInGroup - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; + _logger.LogWarning("Syncplaymanager HandleRequest: {0} does not belong to any group.", session.Id); + + var error = new GroupUpdate() + { + Type = GroupUpdateType.NotInGroup + }; + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } + + group.HandleRequest(session, request); } - group.HandleRequest(session, request); } /// diff --git a/MediaBrowser.Api/Syncplay/TimeSyncService.cs b/MediaBrowser.Api/Syncplay/TimeSyncService.cs index 8974130157..930968d9fc 100644 --- a/MediaBrowser.Api/Syncplay/TimeSyncService.cs +++ b/MediaBrowser.Api/Syncplay/TimeSyncService.cs @@ -9,7 +9,6 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.Syncplay { [Route("/GetUtcTime", "GET", Summary = "Get UtcTime")] - [Authenticated] public class GetUtcTime : IReturnVoid { // Nothing @@ -33,13 +32,10 @@ namespace MediaBrowser.Api.Syncplay public TimeSyncService( ILogger logger, IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - ISessionManager sessionManager, - ISessionContext sessionContext) + IHttpResultFactory httpResultFactory) : base(logger, serverConfigurationManager, httpResultFactory) { - _sessionManager = sessionManager; - _sessionContext = sessionContext; + // Do nothing } /// -- cgit v1.2.3 From 3623aafcb60dec4f4f5055046717d895b7597b60 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 2 May 2020 01:30:04 +0200 Subject: Make SonarCloud happy --- Emby.Server.Implementations/ApplicationHost.cs | 5 +---- .../HttpServer/HttpListenerHost.cs | 25 +--------------------- .../HttpServer/WebSocketConnection.cs | 8 ------- .../Session/WebSocketController.cs | 2 +- MediaBrowser.Controller/Net/IHttpServer.cs | 5 ++--- 5 files changed, 5 insertions(+), 40 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 6279ce5d0b..11fed24f7a 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -93,7 +93,6 @@ using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; using MediaBrowser.Model.Tasks; -using MediaBrowser.Model.Updates; using MediaBrowser.Providers.Chapters; using MediaBrowser.Providers.Manager; using MediaBrowser.Providers.Plugins.TheTvdb; @@ -101,12 +100,10 @@ using MediaBrowser.Providers.Subtitles; using MediaBrowser.WebDashboard.Api; using MediaBrowser.XbmcMetadata.Providers; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; using Prometheus.DotNetRuntime; +using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace Emby.Server.Implementations { diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index e75140d6c0..7358c1a81d 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -28,12 +28,11 @@ using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Net.Http.Headers; using ServiceStack.Text.Jsv; namespace Emby.Server.Implementations.HttpServer { - public class HttpListenerHost : IHttpServer, IDisposable + public class HttpListenerHost : IHttpServer { /// /// The key for a setting that specifies the default redirect path @@ -699,28 +698,6 @@ namespace Emby.Server.Implementations.HttpServer return _baseUrlPrefix + NormalizeUrlPath(path); } - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (disposing) - { - // TODO: - } - - _disposed = true; - } - /// /// Processes the web socket message received. /// diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 1af748ebc2..095725c504 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -36,8 +36,6 @@ namespace Emby.Server.Implementations.HttpServer /// private readonly WebSocket _socket; - private bool _disposed = false; - /// /// Initializes a new instance of the class. /// @@ -221,12 +219,6 @@ namespace Emby.Server.Implementations.HttpServer }; await OnReceive(info).ConfigureAwait(false); - - // Stop reading if there's no more data coming - if (result.IsCompleted) - { - return; - } } } } diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index c7ef9b1cec..a0274acd20 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Session private readonly ISessionManager _sessionManager; private readonly SessionInfo _session; - private List _sockets; + private readonly List _sockets; private bool _disposed = false; public WebSocketController( diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs index 666ac1cfef..f1c4417613 100644 --- a/MediaBrowser.Controller/Net/IHttpServer.cs +++ b/MediaBrowser.Controller/Net/IHttpServer.cs @@ -2,15 +2,14 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using MediaBrowser.Model.Events; -using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net { /// - /// Interface IHttpServer + /// Interface IHttpServer. /// - public interface IHttpServer : IDisposable + public interface IHttpServer { /// /// Gets the URL prefix. -- cgit v1.2.3 From 6e22e9222b68ad117550c02a8cbce2d65878f50b Mon Sep 17 00:00:00 2001 From: gion Date: Mon, 4 May 2020 19:46:02 +0200 Subject: Fix code issues --- .../HttpServer/WebSocketConnection.cs | 4 +- .../Session/SessionWebSocketListener.cs | 127 +++++++++-------- .../Syncplay/SyncplayController.cs | 158 ++++++++++++--------- .../Syncplay/SyncplayManager.cs | 61 ++++---- MediaBrowser.Api/Syncplay/SyncplayService.cs | 85 +++++++---- MediaBrowser.Api/Syncplay/TimeSyncService.cs | 13 +- MediaBrowser.Controller/Syncplay/GroupInfo.cs | 8 +- .../Syncplay/ISyncplayController.cs | 13 +- .../Syncplay/ISyncplayManager.cs | 16 ++- MediaBrowser.Model/Syncplay/GroupUpdateType.cs | 6 +- MediaBrowser.Model/Syncplay/PlaybackRequest.cs | 2 +- 11 files changed, 281 insertions(+), 212 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index c819c163a7..4c33ff71b0 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -238,10 +238,10 @@ namespace Emby.Server.Implementations.HttpServer return _socket.SendAsync(text, true, cancellationToken); } - private Task SendKeepAliveResponse() + private void SendKeepAliveResponse() { LastKeepAliveDate = DateTime.UtcNow; - return SendAsync(new WebSocketMessage + SendAsync(new WebSocketMessage { MessageType = "KeepAlive" }, CancellationToken.None); diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 7a316b070c..d1ee22ea86 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -84,10 +84,10 @@ namespace Emby.Server.Implementations.Session _logger = loggerFactory.CreateLogger(GetType().Name); _json = json; _httpServer = httpServer; - httpServer.WebSocketConnected += _serverManager_WebSocketConnected; + httpServer.WebSocketConnected += OnServerManagerWebSocketConnected; } - void _serverManager_WebSocketConnected(object sender, GenericEventArgs e) + void OnServerManagerWebSocketConnected(object sender, GenericEventArgs e) { var session = GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint); @@ -121,7 +121,7 @@ namespace Emby.Server.Implementations.Session public void Dispose() { - _httpServer.WebSocketConnected -= _serverManager_WebSocketConnected; + _httpServer.WebSocketConnected -= OnServerManagerWebSocketConnected; StopKeepAlive(); } @@ -149,7 +149,7 @@ namespace Emby.Server.Implementations.Session private void OnWebSocketClosed(object sender, EventArgs e) { var webSocket = (IWebSocketConnection) sender; - _logger.LogDebug("WebSockets {0} closed.", webSocket); + _logger.LogDebug("WebSocket {0} is closed.", webSocket); RemoveWebSocket(webSocket); } @@ -157,7 +157,7 @@ namespace Emby.Server.Implementations.Session /// Adds a WebSocket to the KeepAlive watchlist. /// /// The WebSocket to monitor. - private async Task KeepAliveWebSocket(IWebSocketConnection webSocket) + private void KeepAliveWebSocket(IWebSocketConnection webSocket) { lock (_webSocketsLock) { @@ -175,11 +175,11 @@ namespace Emby.Server.Implementations.Session // Notify WebSocket about timeout try { - await SendForceKeepAlive(webSocket); + SendForceKeepAlive(webSocket).Wait(); } catch (WebSocketException exception) { - _logger.LogWarning(exception, "Error sending ForceKeepAlive message to WebSocket {0}.", webSocket); + _logger.LogWarning(exception, "Cannot send ForceKeepAlive message to WebSocket {0}.", webSocket); } } @@ -213,7 +213,8 @@ namespace Emby.Server.Implementations.Session { _keepAliveCancellationToken = new CancellationTokenSource(); // Start KeepAlive watcher - KeepAliveSockets( + var task = RepeatAsyncCallbackEvery( + KeepAliveSockets, TimeSpan.FromSeconds(WebSocketLostTimeout * IntervalFactor), _keepAliveCancellationToken.Token); } @@ -245,73 +246,58 @@ namespace Emby.Server.Implementations.Session } /// - /// Checks status of KeepAlive of WebSockets once every the specified interval time. + /// Checks status of KeepAlive of WebSockets. /// - /// The interval. - /// The cancellation token. - private async Task KeepAliveSockets(TimeSpan interval, CancellationToken cancellationToken) + private async Task KeepAliveSockets() { - while (true) + IEnumerable inactive; + IEnumerable lost; + + lock (_webSocketsLock) { - _logger.LogDebug("Watching {0} WebSockets.", _webSockets.Count()); + _logger.LogDebug("Watching {0} WebSockets.", _webSockets.Count); - IEnumerable inactive; - IEnumerable lost; - lock (_webSocketsLock) + inactive = _webSockets.Where(i => { - inactive = _webSockets.Where(i => - { - var elapsed = (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds; - return (elapsed > WebSocketLostTimeout * ForceKeepAliveFactor) && (elapsed < WebSocketLostTimeout); - }); - lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout); - } + var elapsed = (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds; + return (elapsed > WebSocketLostTimeout * ForceKeepAliveFactor) && (elapsed < WebSocketLostTimeout); + }); + lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout); + } - if (inactive.Any()) + if (inactive.Any()) + { + _logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count()); + } + + foreach (var webSocket in inactive) + { + try { - _logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count()); + await SendForceKeepAlive(webSocket); } - - foreach (var webSocket in inactive) + catch (WebSocketException exception) { - try - { - await SendForceKeepAlive(webSocket); - } - catch (WebSocketException exception) - { - _logger.LogInformation(exception, "Error sending ForceKeepAlive message to WebSocket."); - lost = lost.Append(webSocket); - } + _logger.LogInformation(exception, "Error sending ForceKeepAlive message to WebSocket."); + lost = lost.Append(webSocket); } + } - lock (_webSocketsLock) + lock (_webSocketsLock) + { + if (lost.Any()) { - if (lost.Any()) - { - _logger.LogInformation("Lost {0} WebSockets.", lost.Count()); - foreach (var webSocket in lost.ToList()) - { - // TODO: handle session relative to the lost webSocket - RemoveWebSocket(webSocket); - } - } - - if (!_webSockets.Any()) + _logger.LogInformation("Lost {0} WebSockets.", lost.Count()); + foreach (var webSocket in lost.ToList()) { - StopKeepAlive(); + // TODO: handle session relative to the lost webSocket + RemoveWebSocket(webSocket); } } - // Wait for next interval - Task task = Task.Delay(interval, cancellationToken); - try + if (!_webSockets.Any()) { - await task; - } - catch (TaskCanceledException) - { - return; + StopKeepAlive(); } } } @@ -329,5 +315,30 @@ namespace Emby.Server.Implementations.Session Data = WebSocketLostTimeout }, CancellationToken.None); } + + /// + /// Runs a given async callback once every specified interval time, until cancelled. + /// + /// The async callback. + /// The interval time. + /// The cancellation token. + /// Task. + private async Task RepeatAsyncCallbackEvery(Func callback, TimeSpan interval, CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + await callback(); + Task task = Task.Delay(interval, cancellationToken); + + try + { + await task; + } + catch (TaskCanceledException) + { + return; + } + } + } } } diff --git a/Emby.Server.Implementations/Syncplay/SyncplayController.cs b/Emby.Server.Implementations/Syncplay/SyncplayController.cs index 02cf08cd7c..8cc3d1fac3 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayController.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayController.cs @@ -7,13 +7,15 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Syncplay; using MediaBrowser.Model.Session; using MediaBrowser.Model.Syncplay; -using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Syncplay { /// /// Class SyncplayController. /// + /// + /// Class is not thread-safe, external locking is required when accessing methods. + /// public class SyncplayController : ISyncplayController, IDisposable { /// @@ -39,11 +41,6 @@ namespace Emby.Server.Implementations.Syncplay AllReady = 3 } - /// - /// The logger. - /// - private readonly ILogger _logger; - /// /// The session manager. /// @@ -71,11 +68,9 @@ namespace Emby.Server.Implementations.Syncplay private bool _disposed = false; public SyncplayController( - ILogger logger, ISessionManager sessionManager, ISyncplayManager syncplayManager) { - _logger = logger; _sessionManager = sessionManager; _syncplayManager = syncplayManager; } @@ -110,6 +105,16 @@ namespace Emby.Server.Implementations.Syncplay } } + /// + /// Converts DateTime to UTC string. + /// + /// The date to convert. + /// The UTC string. + private string DateToUTCString(DateTime date) + { + return date.ToUniversalTime().ToString("o"); + } + /// /// Filters sessions of this group. /// @@ -149,15 +154,16 @@ namespace Emby.Server.Implementations.Syncplay /// The current session. /// The filtering type. /// The message to send. + /// The cancellation token. /// The task. - private Task SendGroupUpdate(SessionInfo from, BroadcastType type, GroupUpdate message) + private Task SendGroupUpdate(SessionInfo from, BroadcastType type, GroupUpdate message, CancellationToken cancellationToken) { IEnumerable GetTasks() { SessionInfo[] sessions = FilterSessions(from, type); foreach (var session in sessions) { - yield return _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), message, CancellationToken.None); + yield return _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), message, cancellationToken); } } @@ -170,15 +176,16 @@ namespace Emby.Server.Implementations.Syncplay /// The current session. /// The filtering type. /// The message to send. + /// The cancellation token. /// The task. - private Task SendCommand(SessionInfo from, BroadcastType type, SendCommand message) + private Task SendCommand(SessionInfo from, BroadcastType type, SendCommand message, CancellationToken cancellationToken) { IEnumerable GetTasks() { SessionInfo[] sessions = FilterSessions(from, type); foreach (var session in sessions) { - yield return _sessionManager.SendSyncplayCommand(session.Id.ToString(), message, CancellationToken.None); + yield return _sessionManager.SendSyncplayCommand(session.Id.ToString(), message, cancellationToken); } } @@ -197,8 +204,8 @@ namespace Emby.Server.Implementations.Syncplay GroupId = _group.GroupId.ToString(), Command = type, PositionTicks = _group.PositionTicks, - When = _group.LastActivity.ToUniversalTime().ToString("o"), - EmittedAt = DateTime.UtcNow.ToUniversalTime().ToString("o") + When = DateToUTCString(_group.LastActivity), + EmittedAt = DateToUTCString(DateTime.UtcNow) }; } @@ -219,46 +226,46 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void InitGroup(SessionInfo session) + public void InitGroup(SessionInfo session, CancellationToken cancellationToken) { _group.AddSession(session); _syncplayManager.AddSessionToGroup(session, this); _group.PlayingItem = session.FullNowPlayingItem; _group.IsPaused = true; - _group.PositionTicks = session.PlayState.PositionTicks ??= 0; + _group.PositionTicks = session.PlayState.PositionTicks ?? 0; _group.LastActivity = DateTime.UtcNow; - var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); - SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession); + var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow)); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.CurrentSession, pauseCommand); + SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken); } /// - public void SessionJoin(SessionInfo session, JoinGroupRequest request) + public void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken) { if (session.NowPlayingItem?.Id == _group.PlayingItem.Id && request.PlayingItemId == _group.PlayingItem.Id) { _group.AddSession(session); _syncplayManager.AddSessionToGroup(session, this); - var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); - SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession); + var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow)); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserJoined, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); // Client join and play, syncing will happen client side if (!_group.IsPaused) { var playCommand = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.CurrentSession, playCommand); + SendCommand(session, BroadcastType.CurrentSession, playCommand, cancellationToken); } else { var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.CurrentSession, pauseCommand); + SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken); } } else @@ -267,25 +274,25 @@ namespace Emby.Server.Implementations.Syncplay playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id }; playRequest.StartPositionTicks = _group.PositionTicks; var update = NewSyncplayGroupUpdate(GroupUpdateType.PrepareSession, playRequest); - SendGroupUpdate(session, BroadcastType.CurrentSession, update); + SendGroupUpdate(session, BroadcastType.CurrentSession, update, cancellationToken); } } /// - public void SessionLeave(SessionInfo session) + public void SessionLeave(SessionInfo session, CancellationToken cancellationToken) { _group.RemoveSession(session); _syncplayManager.RemoveSessionFromGroup(session, this); var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupLeft, _group.PositionTicks); - SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserLeft, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); } /// - public void HandleRequest(SessionInfo session, PlaybackRequest request) + public void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) { // The server's job is to mantain a consistent state to which clients refer to, // as also to notify clients of state changes. @@ -294,19 +301,19 @@ namespace Emby.Server.Implementations.Syncplay switch (request.Type) { case PlaybackRequestType.Play: - HandlePlayRequest(session, request); + HandlePlayRequest(session, request, cancellationToken); break; case PlaybackRequestType.Pause: - HandlePauseRequest(session, request); + HandlePauseRequest(session, request, cancellationToken); break; case PlaybackRequestType.Seek: - HandleSeekRequest(session, request); + HandleSeekRequest(session, request, cancellationToken); break; case PlaybackRequestType.Buffering: - HandleBufferingRequest(session, request); + HandleBufferingRequest(session, request, cancellationToken); break; case PlaybackRequestType.BufferingDone: - HandleBufferingDoneRequest(session, request); + HandleBufferingDoneRequest(session, request, cancellationToken); break; case PlaybackRequestType.UpdatePing: HandlePingUpdateRequest(session, request); @@ -319,7 +326,8 @@ namespace Emby.Server.Implementations.Syncplay /// /// The session. /// The play action. - private void HandlePlayRequest(SessionInfo session, PlaybackRequest request) + /// The cancellation token. + private void HandlePlayRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) { if (_group.IsPaused) { @@ -337,13 +345,13 @@ namespace Emby.Server.Implementations.Syncplay ); var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllGroup, command); + SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); } else { // Client got lost, sending current state var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.CurrentSession, command); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); } } @@ -352,7 +360,8 @@ namespace Emby.Server.Implementations.Syncplay /// /// The session. /// The pause action. - private void HandlePauseRequest(SessionInfo session, PlaybackRequest request) + /// The cancellation token. + private void HandlePauseRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) { if (!_group.IsPaused) { @@ -366,13 +375,13 @@ namespace Emby.Server.Implementations.Syncplay _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.AllGroup, command); + SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); } else { // Client got lost, sending current state var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.CurrentSession, command); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); } } @@ -381,16 +390,11 @@ namespace Emby.Server.Implementations.Syncplay /// /// The session. /// The seek action. - private void HandleSeekRequest(SessionInfo session, PlaybackRequest request) + /// The cancellation token. + private void HandleSeekRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) { // Sanitize PositionTicks - var ticks = request.PositionTicks ??= 0; - ticks = ticks >= 0 ? ticks : 0; - if (_group.PlayingItem.RunTimeTicks != null) - { - var runTimeTicks = _group.PlayingItem.RunTimeTicks ??= 0; - ticks = ticks > runTimeTicks ? runTimeTicks : ticks; - } + var ticks = SanitizePositionTicks(request.PositionTicks); // Pause and seek _group.IsPaused = true; @@ -398,7 +402,7 @@ namespace Emby.Server.Implementations.Syncplay _group.LastActivity = DateTime.UtcNow; var command = NewSyncplayCommand(SendCommandType.Seek); - SendCommand(session, BroadcastType.AllGroup, command); + SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); } /// @@ -406,7 +410,8 @@ namespace Emby.Server.Implementations.Syncplay /// /// The session. /// The buffering action. - private void HandleBufferingRequest(SessionInfo session, PlaybackRequest request) + /// The cancellation token. + private void HandleBufferingRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) { if (!_group.IsPaused) { @@ -421,16 +426,16 @@ namespace Emby.Server.Implementations.Syncplay // Send pause command to all non-buffering sessions var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.AllReady, command); + SendCommand(session, BroadcastType.AllReady, command, cancellationToken); var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.GroupWait, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); } else { // Client got lost, sending current state var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.CurrentSession, command); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); } } @@ -439,26 +444,28 @@ namespace Emby.Server.Implementations.Syncplay /// /// The session. /// The buffering-done action. - private void HandleBufferingDoneRequest(SessionInfo session, PlaybackRequest request) + /// The cancellation token. + private void HandleBufferingDoneRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) { if (_group.IsPaused) { _group.SetBuffering(session, false); - var when = request.When ??= DateTime.UtcNow; + var requestTicks = SanitizePositionTicks(request.PositionTicks); + + var when = request.When ?? DateTime.UtcNow; var currentTime = DateTime.UtcNow; var elapsedTime = currentTime - when; - var clientPosition = TimeSpan.FromTicks(request.PositionTicks ??= 0) + elapsedTime; + var clientPosition = TimeSpan.FromTicks(requestTicks) + elapsedTime; var delay = _group.PositionTicks - clientPosition.Ticks; if (_group.IsBuffering()) { - // Others are buffering, tell this client to pause when ready + // Others are still buffering, tell this client to pause when ready var command = NewSyncplayCommand(SendCommandType.Pause); - command.When = currentTime.AddMilliseconds( - delay - ).ToUniversalTime().ToString("o"); - SendCommand(session, BroadcastType.CurrentSession, command); + var pauseAtTime = currentTime.AddMilliseconds(delay); + command.When = DateToUTCString(pauseAtTime); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); } else { @@ -472,7 +479,7 @@ namespace Emby.Server.Implementations.Syncplay delay ); var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllExceptCurrentSession, command); + SendCommand(session, BroadcastType.AllExceptCurrentSession, command, cancellationToken); } else { @@ -485,7 +492,7 @@ namespace Emby.Server.Implementations.Syncplay ); var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllGroup, command); + SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); } } } @@ -493,8 +500,25 @@ namespace Emby.Server.Implementations.Syncplay { // Group was not waiting, make sure client has latest state var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.CurrentSession, command); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); + } + } + + /// + /// Sanitizes the PositionTicks, considers the current playing item when available. + /// + /// The PositionTicks. + /// The sanitized PositionTicks. + private long SanitizePositionTicks(long? positionTicks) + { + var ticks = positionTicks ?? 0; + ticks = ticks >= 0 ? ticks : 0; + if (_group.PlayingItem != null) + { + var runTimeTicks = _group.PlayingItem.RunTimeTicks ?? 0; + ticks = ticks > runTimeTicks ? runTimeTicks : ticks; } + return ticks; } /// @@ -505,7 +529,7 @@ namespace Emby.Server.Implementations.Syncplay private void HandlePingUpdateRequest(SessionInfo session, PlaybackRequest request) { // Collected pings are used to account for network latency when unpausing playback - _group.UpdatePing(session, request.Ping ??= _group.DefaulPing); + _group.UpdatePing(session, request.Ping ?? _group.DefaulPing); } /// @@ -517,7 +541,7 @@ namespace Emby.Server.Implementations.Syncplay PlayingItemName = _group.PlayingItem.Name, PlayingItemId = _group.PlayingItem.Id.ToString(), PositionTicks = _group.PositionTicks, - Participants = _group.Participants.Values.Select(session => session.Session.UserName).ToArray() + Participants = _group.Participants.Values.Select(session => session.Session.UserName).Distinct().ToArray() }; } } diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index eb61da7f32..7074e2225e 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -114,14 +114,14 @@ namespace Emby.Server.Implementations.Syncplay { var session = e.SessionInfo; if (!IsSessionInGroup(session)) return; - LeaveGroup(session); + LeaveGroup(session, CancellationToken.None); } private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e) { var session = e.Session; if (!IsSessionInGroup(session)) return; - LeaveGroup(session); + LeaveGroup(session, CancellationToken.None); } private bool IsSessionInGroup(SessionInfo session) @@ -132,7 +132,13 @@ namespace Emby.Server.Implementations.Syncplay private bool HasAccessToItem(User user, Guid itemId) { var item = _libraryManager.GetItemById(itemId); - var hasParentalRatingAccess = user.Policy.MaxParentalRating.HasValue ? item.InheritedParentalRatingValue <= user.Policy.MaxParentalRating : true; + + // Check ParentalRating access + var hasParentalRatingAccess = true; + if (user.Policy.MaxParentalRating.HasValue) + { + hasParentalRatingAccess = item.InheritedParentalRatingValue <= user.Policy.MaxParentalRating; + } if (!user.Policy.EnableAllFolders && hasParentalRatingAccess) { @@ -140,7 +146,7 @@ namespace Emby.Server.Implementations.Syncplay folder => folder.Id.ToString("N", CultureInfo.InvariantCulture) ); var intersect = collections.Intersect(user.Policy.EnabledFolders); - return intersect.Count() > 0; + return intersect.Any(); } else { @@ -163,13 +169,13 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void NewGroup(SessionInfo session) + public void NewGroup(SessionInfo session, CancellationToken cancellationToken) { var user = _userManager.GetUserById(session.UserId); if (user.Policy.SyncplayAccess != SyncplayAccess.CreateAndJoinGroups) { - _logger.LogWarning("Syncplaymanager NewGroup: {0} does not have permission to create groups.", session.Id); + _logger.LogWarning("NewGroup: {0} does not have permission to create groups.", session.Id); var error = new GroupUpdate() { @@ -183,24 +189,24 @@ namespace Emby.Server.Implementations.Syncplay { if (IsSessionInGroup(session)) { - LeaveGroup(session); + LeaveGroup(session, cancellationToken); } - var group = new SyncplayController(_logger, _sessionManager, this); + var group = new SyncplayController(_sessionManager, this); _groups[group.GetGroupId().ToString()] = group; - group.InitGroup(session); + group.InitGroup(session, cancellationToken); } } /// - public void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request) + public void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request, CancellationToken cancellationToken) { var user = _userManager.GetUserById(session.UserId); if (user.Policy.SyncplayAccess == SyncplayAccess.None) { - _logger.LogWarning("Syncplaymanager JoinGroup: {0} does not have access to Syncplay.", session.Id); + _logger.LogWarning("JoinGroup: {0} does not have access to Syncplay.", session.Id); var error = new GroupUpdate() { @@ -217,11 +223,11 @@ namespace Emby.Server.Implementations.Syncplay if (group == null) { - _logger.LogWarning("Syncplaymanager JoinGroup: {0} tried to join group {0} that does not exist.", session.Id, groupId); + _logger.LogWarning("JoinGroup: {0} tried to join group {0} that does not exist.", session.Id, groupId); var error = new GroupUpdate() { - Type = GroupUpdateType.GroupNotJoined + Type = GroupUpdateType.GroupDoesNotExist }; _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); return; @@ -229,7 +235,7 @@ namespace Emby.Server.Implementations.Syncplay if (!HasAccessToItem(user, group.GetPlayingItemId())) { - _logger.LogWarning("Syncplaymanager JoinGroup: {0} does not have access to {1}.", session.Id, group.GetPlayingItemId()); + _logger.LogWarning("JoinGroup: {0} does not have access to {1}.", session.Id, group.GetPlayingItemId()); var error = new GroupUpdate() { @@ -243,15 +249,15 @@ namespace Emby.Server.Implementations.Syncplay if (IsSessionInGroup(session)) { if (GetSessionGroup(session).Equals(groupId)) return; - LeaveGroup(session); + LeaveGroup(session, cancellationToken); } - group.SessionJoin(session, request); + group.SessionJoin(session, request, cancellationToken); } } /// - public void LeaveGroup(SessionInfo session) + public void LeaveGroup(SessionInfo session, CancellationToken cancellationToken) { // TODO: determine what happens to users that are in a group and get their permissions revoked lock (_groupsLock) @@ -261,7 +267,7 @@ namespace Emby.Server.Implementations.Syncplay if (group == null) { - _logger.LogWarning("Syncplaymanager LeaveGroup: {0} does not belong to any group.", session.Id); + _logger.LogWarning("LeaveGroup: {0} does not belong to any group.", session.Id); var error = new GroupUpdate() { @@ -271,17 +277,18 @@ namespace Emby.Server.Implementations.Syncplay return; } - group.SessionLeave(session); + group.SessionLeave(session, cancellationToken); if (group.IsGroupEmpty()) { + _logger.LogInformation("LeaveGroup: removing empty group {0}.", group.GetGroupId()); _groups.Remove(group.GetGroupId().ToString(), out _); } } } /// - public List ListGroups(SessionInfo session) + public List ListGroups(SessionInfo session, Guid filterItemId) { var user = _userManager.GetUserById(session.UserId); @@ -290,11 +297,11 @@ namespace Emby.Server.Implementations.Syncplay return new List(); } - // Filter by playing item if the user is viewing something already - if (session.NowPlayingItem != null) + // Filter by item if requested + if (!filterItemId.Equals(Guid.Empty)) { return _groups.Values.Where( - group => group.GetPlayingItemId().Equals(session.FullNowPlayingItem.Id) && HasAccessToItem(user, group.GetPlayingItemId()) + group => group.GetPlayingItemId().Equals(filterItemId) && HasAccessToItem(user, group.GetPlayingItemId()) ).Select( group => group.GetInfo() ).ToList(); @@ -311,13 +318,13 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void HandleRequest(SessionInfo session, PlaybackRequest request) + public void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) { var user = _userManager.GetUserById(session.UserId); if (user.Policy.SyncplayAccess == SyncplayAccess.None) { - _logger.LogWarning("Syncplaymanager HandleRequest: {0} does not have access to Syncplay.", session.Id); + _logger.LogWarning("HandleRequest: {0} does not have access to Syncplay.", session.Id); var error = new GroupUpdate() { @@ -334,7 +341,7 @@ namespace Emby.Server.Implementations.Syncplay if (group == null) { - _logger.LogWarning("Syncplaymanager HandleRequest: {0} does not belong to any group.", session.Id); + _logger.LogWarning("HandleRequest: {0} does not belong to any group.", session.Id); var error = new GroupUpdate() { @@ -344,7 +351,7 @@ namespace Emby.Server.Implementations.Syncplay return; } - group.HandleRequest(session, request); + group.HandleRequest(session, request, cancellationToken); } } diff --git a/MediaBrowser.Api/Syncplay/SyncplayService.cs b/MediaBrowser.Api/Syncplay/SyncplayService.cs index 2eaf9ce834..4b6e16762a 100644 --- a/MediaBrowser.Api/Syncplay/SyncplayService.cs +++ b/MediaBrowser.Api/Syncplay/SyncplayService.cs @@ -1,3 +1,4 @@ +using System.Threading; using System; using System.Collections.Generic; using MediaBrowser.Controller.Configuration; @@ -48,12 +49,19 @@ namespace MediaBrowser.Api.Syncplay public string SessionId { get; set; } } - [Route("/Syncplay/{SessionId}/ListGroups", "POST", Summary = "List Syncplay groups playing same item")] + [Route("/Syncplay/{SessionId}/ListGroups", "POST", Summary = "List Syncplay groups")] [Authenticated] public class SyncplayListGroups : IReturnVoid { [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string SessionId { get; set; } + + /// + /// Gets or sets the filter item id. + /// + /// The filter item id. + [ApiMember(Name = "FilterItemId", Description = "Filter by item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string FilterItemId { get; set; } } [Route("/Syncplay/{SessionId}/PlayRequest", "POST", Summary = "Request play in Syncplay group")] @@ -104,8 +112,8 @@ namespace MediaBrowser.Api.Syncplay /// Gets or sets whether this is a buffering or a buffering-done request. /// /// true if buffering is complete; false otherwise. - [ApiMember(Name = "Resume", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool Resume { get; set; } + [ApiMember(Name = "BufferingDone", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")] + public bool BufferingDone { get; set; } } [Route("/Syncplay/{SessionId}/UpdatePing", "POST", Summary = "Update session ping")] @@ -124,11 +132,6 @@ namespace MediaBrowser.Api.Syncplay /// public class SyncplayService : BaseApiService { - /// - /// The session manager. - /// - private readonly ISessionManager _sessionManager; - /// /// The session context. /// @@ -143,12 +146,10 @@ namespace MediaBrowser.Api.Syncplay ILogger logger, IServerConfigurationManager serverConfigurationManager, IHttpResultFactory httpResultFactory, - ISessionManager sessionManager, ISessionContext sessionContext, ISyncplayManager syncplayManager) : base(logger, serverConfigurationManager, httpResultFactory) { - _sessionManager = sessionManager; _sessionContext = sessionContext; _syncplayManager = syncplayManager; } @@ -160,7 +161,7 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayNewGroup request) { var currentSession = GetSession(_sessionContext); - _syncplayManager.NewGroup(currentSession); + _syncplayManager.NewGroup(currentSession, CancellationToken.None); } /// @@ -174,19 +175,27 @@ namespace MediaBrowser.Api.Syncplay { GroupId = Guid.Parse(request.GroupId) }; - try - { - joinRequest.PlayingItemId = Guid.Parse(request.PlayingItemId); - } - catch (ArgumentNullException) - { - // Do nothing - } - catch (FormatException) + + // Both null and empty strings mean that client isn't playing anything + if (!String.IsNullOrEmpty(request.PlayingItemId)) { - // Do nothing + try + { + joinRequest.PlayingItemId = Guid.Parse(request.PlayingItemId); + } + catch (ArgumentNullException) + { + // Should never happen, but just in case + Logger.LogError("JoinGroup: null value for PlayingItemId. Ignoring request."); + return; + } + catch (FormatException) + { + Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId); + return; + } } - _syncplayManager.JoinGroup(currentSession, request.GroupId, joinRequest); + _syncplayManager.JoinGroup(currentSession, request.GroupId, joinRequest, CancellationToken.None); } /// @@ -196,7 +205,7 @@ namespace MediaBrowser.Api.Syncplay public void Post(SyncplayLeaveGroup request) { var currentSession = GetSession(_sessionContext); - _syncplayManager.LeaveGroup(currentSession); + _syncplayManager.LeaveGroup(currentSession, CancellationToken.None); } /// @@ -207,7 +216,23 @@ namespace MediaBrowser.Api.Syncplay public List Post(SyncplayListGroups request) { var currentSession = GetSession(_sessionContext); - return _syncplayManager.ListGroups(currentSession); + var filterItemId = Guid.Empty; + if (!String.IsNullOrEmpty(request.FilterItemId)) + { + try + { + filterItemId = Guid.Parse(request.FilterItemId); + } + catch (ArgumentNullException) + { + Logger.LogWarning("ListGroups: null value for FilterItemId. Ignoring filter."); + } + catch (FormatException) + { + Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); + } + } + return _syncplayManager.ListGroups(currentSession, filterItemId); } /// @@ -221,7 +246,7 @@ namespace MediaBrowser.Api.Syncplay { Type = PlaybackRequestType.Play }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest); + _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); } /// @@ -235,7 +260,7 @@ namespace MediaBrowser.Api.Syncplay { Type = PlaybackRequestType.Pause }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest); + _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); } /// @@ -250,7 +275,7 @@ namespace MediaBrowser.Api.Syncplay Type = PlaybackRequestType.Seek, PositionTicks = request.PositionTicks }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest); + _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); } /// @@ -262,11 +287,11 @@ namespace MediaBrowser.Api.Syncplay var currentSession = GetSession(_sessionContext); var syncplayRequest = new PlaybackRequest() { - Type = request.Resume ? PlaybackRequestType.BufferingDone : PlaybackRequestType.Buffering, + Type = request.BufferingDone ? PlaybackRequestType.BufferingDone : PlaybackRequestType.Buffering, When = DateTime.Parse(request.When), PositionTicks = request.PositionTicks }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest); + _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); } /// @@ -281,7 +306,7 @@ namespace MediaBrowser.Api.Syncplay Type = PlaybackRequestType.UpdatePing, Ping = Convert.ToInt64(request.Ping) }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest); + _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); } } } diff --git a/MediaBrowser.Api/Syncplay/TimeSyncService.cs b/MediaBrowser.Api/Syncplay/TimeSyncService.cs index 930968d9fc..9a26ffd996 100644 --- a/MediaBrowser.Api/Syncplay/TimeSyncService.cs +++ b/MediaBrowser.Api/Syncplay/TimeSyncService.cs @@ -1,7 +1,6 @@ using System; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Session; using MediaBrowser.Model.Services; using MediaBrowser.Model.Syncplay; using Microsoft.Extensions.Logging; @@ -19,16 +18,6 @@ namespace MediaBrowser.Api.Syncplay /// public class TimeSyncService : BaseApiService { - /// - /// The session manager. - /// - private readonly ISessionManager _sessionManager; - - /// - /// The session context. - /// - private readonly ISessionContext _sessionContext; - public TimeSyncService( ILogger logger, IServerConfigurationManager serverConfigurationManager, @@ -55,7 +44,7 @@ namespace MediaBrowser.Api.Syncplay var responseTransmissionTime = DateTime.UtcNow.ToUniversalTime().ToString("o"); response.ResponseTransmissionTime = responseTransmissionTime; - // Implementing NTP on such a high level results in this useless + // Implementing NTP on such a high level results in this useless // information being sent. On the other hand it enables future additions. return response; } diff --git a/MediaBrowser.Controller/Syncplay/GroupInfo.cs b/MediaBrowser.Controller/Syncplay/GroupInfo.cs index 8e886a2cb2..c01fead836 100644 --- a/MediaBrowser.Controller/Syncplay/GroupInfo.cs +++ b/MediaBrowser.Controller/Syncplay/GroupInfo.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Session; @@ -9,6 +8,9 @@ namespace MediaBrowser.Controller.Syncplay /// /// Class GroupInfo. /// + /// + /// Class is not thread-safe, external locking is required when accessing methods. + /// public class GroupInfo { /// @@ -49,8 +51,8 @@ namespace MediaBrowser.Controller.Syncplay /// Gets the participants. /// /// The participants, or members of the group. - public readonly ConcurrentDictionary Participants = - new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + public readonly Dictionary Participants = + new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// Checks if a session is in this group. diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs index 5b08eac0a4..34eae40624 100644 --- a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs +++ b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Syncplay; @@ -31,27 +32,31 @@ namespace MediaBrowser.Controller.Syncplay /// Initializes the group with the session's info. /// /// The session. - void InitGroup(SessionInfo session); + /// The cancellation token. + void InitGroup(SessionInfo session, CancellationToken cancellationToken); /// /// Adds the session to the group. /// /// The session. /// The request. - void SessionJoin(SessionInfo session, JoinGroupRequest request); + /// The cancellation token. + void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken); /// /// Removes the session from the group. /// /// The session. - void SessionLeave(SessionInfo session); + /// The cancellation token. + void SessionLeave(SessionInfo session, CancellationToken cancellationToken); /// /// Handles the requested action by the session. /// /// The session. /// The requested action. - void HandleRequest(SessionInfo session, PlaybackRequest request); + /// The cancellation token. + void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken); /// /// Gets the info about the group for the clients. diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs index 433d6d8bc1..fbc208d27d 100644 --- a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs +++ b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Syncplay; @@ -14,7 +15,8 @@ namespace MediaBrowser.Controller.Syncplay /// Creates a new group. /// /// The session that's creating the group. - void NewGroup(SessionInfo session); + /// The cancellation token. + void NewGroup(SessionInfo session, CancellationToken cancellationToken); /// /// Adds the session to a group. @@ -22,27 +24,31 @@ namespace MediaBrowser.Controller.Syncplay /// The session. /// The group id. /// The request. - void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request); + /// The cancellation token. + void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request, CancellationToken cancellationToken); /// /// Removes the session from a group. /// /// The session. - void LeaveGroup(SessionInfo session); + /// The cancellation token. + void LeaveGroup(SessionInfo session, CancellationToken cancellationToken); /// /// Gets list of available groups for a session. /// /// The session. + /// The item id to filter by. /// The list of available groups. - List ListGroups(SessionInfo session); + List ListGroups(SessionInfo session, Guid filterItemId); /// /// Handle a request by a session in a group. /// /// The session. /// The request. - void HandleRequest(SessionInfo session, PlaybackRequest request); + /// The cancellation token. + void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken); /// /// Maps a session to a group. diff --git a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs index 20e76932d4..9f40f9577f 100644 --- a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs +++ b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs @@ -30,13 +30,13 @@ namespace MediaBrowser.Model.Syncplay /// PrepareSession, /// - /// The not-in-group error. Tells a user that it doesn't belong to a group. + /// The not-in-group error. Tells a user that they don't belong to a group. /// NotInGroup, /// - /// The group-not-joined error. Sent when a request to join a group fails. + /// The group-does-not-exist error. Sent when trying to join a non-existing group. /// - GroupNotJoined, + GroupDoesNotExist, /// /// The create-group-denied error. Sent when a user tries to create a group without required permissions. /// diff --git a/MediaBrowser.Model/Syncplay/PlaybackRequest.cs b/MediaBrowser.Model/Syncplay/PlaybackRequest.cs index cae769db0d..ba97641f63 100644 --- a/MediaBrowser.Model/Syncplay/PlaybackRequest.cs +++ b/MediaBrowser.Model/Syncplay/PlaybackRequest.cs @@ -11,7 +11,7 @@ namespace MediaBrowser.Model.Syncplay /// Gets or sets the request type. /// /// The request type. - public PlaybackRequestType Type; + public PlaybackRequestType Type { get; set; } /// /// Gets or sets when the request has been made by the client. -- cgit v1.2.3 From 8a6ec2fb713cb77e91d2fceea8b4fce8e7d17395 Mon Sep 17 00:00:00 2001 From: gion Date: Wed, 6 May 2020 23:42:53 +0200 Subject: Rename Syncplay to SyncPlay --- Emby.Server.Implementations/ApplicationHost.cs | 6 +- .../Session/SessionManager.cs | 10 +- .../SyncPlay/SyncPlayController.cs | 548 +++++++++++++++++++++ .../SyncPlay/SyncPlayManager.cs | 385 +++++++++++++++ .../Syncplay/SyncplayController.cs | 548 --------------------- .../Syncplay/SyncplayManager.cs | 385 --------------- MediaBrowser.Api/SyncPlay/SyncPlayService.cs | 312 ++++++++++++ MediaBrowser.Api/SyncPlay/TimeSyncService.cs | 52 ++ MediaBrowser.Api/Syncplay/SyncplayService.cs | 312 ------------ MediaBrowser.Api/Syncplay/TimeSyncService.cs | 52 -- MediaBrowser.Controller/Session/ISessionManager.cs | 10 +- MediaBrowser.Controller/SyncPlay/GroupInfo.cs | 150 ++++++ MediaBrowser.Controller/SyncPlay/GroupMember.cs | 28 ++ .../SyncPlay/ISyncPlayController.cs | 67 +++ .../SyncPlay/ISyncPlayManager.cs | 69 +++ MediaBrowser.Controller/Syncplay/GroupInfo.cs | 150 ------ MediaBrowser.Controller/Syncplay/GroupMember.cs | 28 -- .../Syncplay/ISyncplayController.cs | 67 --- .../Syncplay/ISyncplayManager.cs | 69 --- MediaBrowser.Model/Configuration/SyncplayAccess.cs | 6 +- MediaBrowser.Model/SyncPlay/GroupInfoView.cs | 38 ++ MediaBrowser.Model/SyncPlay/GroupUpdate.cs | 26 + MediaBrowser.Model/SyncPlay/GroupUpdateType.cs | 53 ++ MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs | 22 + MediaBrowser.Model/SyncPlay/PlaybackRequest.cs | 34 ++ MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs | 33 ++ MediaBrowser.Model/SyncPlay/SendCommand.cs | 38 ++ MediaBrowser.Model/SyncPlay/SendCommandType.cs | 21 + MediaBrowser.Model/SyncPlay/UtcTimeResponse.cs | 20 + MediaBrowser.Model/Syncplay/GroupInfoView.cs | 38 -- MediaBrowser.Model/Syncplay/GroupUpdate.cs | 26 - MediaBrowser.Model/Syncplay/GroupUpdateType.cs | 53 -- MediaBrowser.Model/Syncplay/JoinGroupRequest.cs | 22 - MediaBrowser.Model/Syncplay/PlaybackRequest.cs | 34 -- MediaBrowser.Model/Syncplay/PlaybackRequestType.cs | 33 -- MediaBrowser.Model/Syncplay/SendCommand.cs | 38 -- MediaBrowser.Model/Syncplay/SendCommandType.cs | 21 - MediaBrowser.Model/Syncplay/UtcTimeResponse.cs | 20 - MediaBrowser.Model/Users/UserPolicy.cs | 8 +- 39 files changed, 1916 insertions(+), 1916 deletions(-) create mode 100644 Emby.Server.Implementations/SyncPlay/SyncPlayController.cs create mode 100644 Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs delete mode 100644 Emby.Server.Implementations/Syncplay/SyncplayController.cs delete mode 100644 Emby.Server.Implementations/Syncplay/SyncplayManager.cs create mode 100644 MediaBrowser.Api/SyncPlay/SyncPlayService.cs create mode 100644 MediaBrowser.Api/SyncPlay/TimeSyncService.cs delete mode 100644 MediaBrowser.Api/Syncplay/SyncplayService.cs delete mode 100644 MediaBrowser.Api/Syncplay/TimeSyncService.cs create mode 100644 MediaBrowser.Controller/SyncPlay/GroupInfo.cs create mode 100644 MediaBrowser.Controller/SyncPlay/GroupMember.cs create mode 100644 MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs create mode 100644 MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs delete mode 100644 MediaBrowser.Controller/Syncplay/GroupInfo.cs delete mode 100644 MediaBrowser.Controller/Syncplay/GroupMember.cs delete mode 100644 MediaBrowser.Controller/Syncplay/ISyncplayController.cs delete mode 100644 MediaBrowser.Controller/Syncplay/ISyncplayManager.cs create mode 100644 MediaBrowser.Model/SyncPlay/GroupInfoView.cs create mode 100644 MediaBrowser.Model/SyncPlay/GroupUpdate.cs create mode 100644 MediaBrowser.Model/SyncPlay/GroupUpdateType.cs create mode 100644 MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs create mode 100644 MediaBrowser.Model/SyncPlay/PlaybackRequest.cs create mode 100644 MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs create mode 100644 MediaBrowser.Model/SyncPlay/SendCommand.cs create mode 100644 MediaBrowser.Model/SyncPlay/SendCommandType.cs create mode 100644 MediaBrowser.Model/SyncPlay/UtcTimeResponse.cs delete mode 100644 MediaBrowser.Model/Syncplay/GroupInfoView.cs delete mode 100644 MediaBrowser.Model/Syncplay/GroupUpdate.cs delete mode 100644 MediaBrowser.Model/Syncplay/GroupUpdateType.cs delete mode 100644 MediaBrowser.Model/Syncplay/JoinGroupRequest.cs delete mode 100644 MediaBrowser.Model/Syncplay/PlaybackRequest.cs delete mode 100644 MediaBrowser.Model/Syncplay/PlaybackRequestType.cs delete mode 100644 MediaBrowser.Model/Syncplay/SendCommand.cs delete mode 100644 MediaBrowser.Model/Syncplay/SendCommandType.cs delete mode 100644 MediaBrowser.Model/Syncplay/UtcTimeResponse.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 8419014c2c..730323c224 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -47,7 +47,7 @@ using Emby.Server.Implementations.Session; using Emby.Server.Implementations.SocketSharp; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; -using Emby.Server.Implementations.Syncplay; +using Emby.Server.Implementations.SyncPlay; using MediaBrowser.Api; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; @@ -81,7 +81,7 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Subtitles; using MediaBrowser.Controller.TV; -using MediaBrowser.Controller.Syncplay; +using MediaBrowser.Controller.SyncPlay; using MediaBrowser.LocalMetadata.Savers; using MediaBrowser.MediaEncoding.BdInfo; using MediaBrowser.Model.Activity; @@ -645,7 +645,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 6a64209c1a..aab745de4f 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -25,7 +25,7 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; -using MediaBrowser.Model.Syncplay; +using MediaBrowser.Model.SyncPlay; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Session @@ -1156,19 +1156,19 @@ namespace Emby.Server.Implementations.Session } /// - public async Task SendSyncplayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken) + public async Task SendSyncPlayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken) { CheckDisposed(); var session = GetSessionToRemoteControl(sessionId); - await SendMessageToSession(session, "SyncplayCommand", command, cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, "SyncPlayCommand", command, cancellationToken).ConfigureAwait(false); } /// - public async Task SendSyncplayGroupUpdate(string sessionId, GroupUpdate command, CancellationToken cancellationToken) + public async Task SendSyncPlayGroupUpdate(string sessionId, GroupUpdate command, CancellationToken cancellationToken) { CheckDisposed(); var session = GetSessionToRemoteControl(sessionId); - await SendMessageToSession(session, "SyncplayGroupUpdate", command, cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, "SyncPlayGroupUpdate", command, cancellationToken).ConfigureAwait(false); } private IEnumerable TranslateItemForPlayback(Guid id, User user) diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs new file mode 100644 index 0000000000..9c9758de1c --- /dev/null +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs @@ -0,0 +1,548 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.SyncPlay; +using MediaBrowser.Model.Session; +using MediaBrowser.Model.SyncPlay; + +namespace Emby.Server.Implementations.SyncPlay +{ + /// + /// Class SyncPlayController. + /// + /// + /// Class is not thread-safe, external locking is required when accessing methods. + /// + public class SyncPlayController : ISyncPlayController, IDisposable + { + /// + /// Used to filter the sessions of a group. + /// + private enum BroadcastType + { + /// + /// All sessions will receive the message. + /// + AllGroup = 0, + /// + /// Only the specified session will receive the message. + /// + CurrentSession = 1, + /// + /// All sessions, except the current one, will receive the message. + /// + AllExceptCurrentSession = 2, + /// + /// Only sessions that are not buffering will receive the message. + /// + AllReady = 3 + } + + /// + /// The session manager. + /// + private readonly ISessionManager _sessionManager; + + /// + /// The SyncPlay manager. + /// + private readonly ISyncPlayManager _syncPlayManager; + + /// + /// The group to manage. + /// + private readonly GroupInfo _group = new GroupInfo(); + + /// + public Guid GetGroupId() => _group.GroupId; + + /// + public Guid GetPlayingItemId() => _group.PlayingItem.Id; + + /// + public bool IsGroupEmpty() => _group.IsEmpty(); + + private bool _disposed = false; + + public SyncPlayController( + ISessionManager sessionManager, + ISyncPlayManager syncPlayManager) + { + _sessionManager = sessionManager; + _syncPlayManager = syncPlayManager; + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and optionally managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + _disposed = true; + } + + // TODO: use this somewhere + private void CheckDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + } + + /// + /// Converts DateTime to UTC string. + /// + /// The date to convert. + /// The UTC string. + private string DateToUTCString(DateTime date) + { + return date.ToUniversalTime().ToString("o"); + } + + /// + /// Filters sessions of this group. + /// + /// The current session. + /// The filtering type. + /// The array of sessions matching the filter. + private SessionInfo[] FilterSessions(SessionInfo from, BroadcastType type) + { + switch (type) + { + case BroadcastType.CurrentSession: + return new SessionInfo[] { from }; + case BroadcastType.AllGroup: + return _group.Participants.Values.Select( + session => session.Session + ).ToArray(); + case BroadcastType.AllExceptCurrentSession: + return _group.Participants.Values.Select( + session => session.Session + ).Where( + session => !session.Id.Equals(from.Id) + ).ToArray(); + case BroadcastType.AllReady: + return _group.Participants.Values.Where( + session => !session.IsBuffering + ).Select( + session => session.Session + ).ToArray(); + default: + return new SessionInfo[] { }; + } + } + + /// + /// Sends a GroupUpdate message to the interested sessions. + /// + /// The current session. + /// The filtering type. + /// The message to send. + /// The cancellation token. + /// The task. + private Task SendGroupUpdate(SessionInfo from, BroadcastType type, GroupUpdate message, CancellationToken cancellationToken) + { + IEnumerable GetTasks() + { + SessionInfo[] sessions = FilterSessions(from, type); + foreach (var session in sessions) + { + yield return _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), message, cancellationToken); + } + } + + return Task.WhenAll(GetTasks()); + } + + /// + /// Sends a playback command to the interested sessions. + /// + /// The current session. + /// The filtering type. + /// The message to send. + /// The cancellation token. + /// The task. + private Task SendCommand(SessionInfo from, BroadcastType type, SendCommand message, CancellationToken cancellationToken) + { + IEnumerable GetTasks() + { + SessionInfo[] sessions = FilterSessions(from, type); + foreach (var session in sessions) + { + yield return _sessionManager.SendSyncPlayCommand(session.Id.ToString(), message, cancellationToken); + } + } + + return Task.WhenAll(GetTasks()); + } + + /// + /// Builds a new playback command with some default values. + /// + /// The command type. + /// The SendCommand. + private SendCommand NewSyncPlayCommand(SendCommandType type) + { + return new SendCommand() + { + GroupId = _group.GroupId.ToString(), + Command = type, + PositionTicks = _group.PositionTicks, + When = DateToUTCString(_group.LastActivity), + EmittedAt = DateToUTCString(DateTime.UtcNow) + }; + } + + /// + /// Builds a new group update message. + /// + /// The update type. + /// The data to send. + /// The GroupUpdate. + private GroupUpdate NewSyncPlayGroupUpdate(GroupUpdateType type, T data) + { + return new GroupUpdate() + { + GroupId = _group.GroupId.ToString(), + Type = type, + Data = data + }; + } + + /// + public void InitGroup(SessionInfo session, CancellationToken cancellationToken) + { + _group.AddSession(session); + _syncPlayManager.AddSessionToGroup(session, this); + + _group.PlayingItem = session.FullNowPlayingItem; + _group.IsPaused = true; + _group.PositionTicks = session.PlayState.PositionTicks ?? 0; + _group.LastActivity = DateTime.UtcNow; + + var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow)); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); + var pauseCommand = NewSyncPlayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken); + } + + /// + public void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken) + { + if (session.NowPlayingItem?.Id == _group.PlayingItem.Id && request.PlayingItemId == _group.PlayingItem.Id) + { + _group.AddSession(session); + _syncPlayManager.AddSessionToGroup(session, this); + + var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow)); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); + + var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserJoined, session.UserName); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); + + // Client join and play, syncing will happen client side + if (!_group.IsPaused) + { + var playCommand = NewSyncPlayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.CurrentSession, playCommand, cancellationToken); + } + else + { + var pauseCommand = NewSyncPlayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken); + } + } + else + { + var playRequest = new PlayRequest(); + playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id }; + playRequest.StartPositionTicks = _group.PositionTicks; + var update = NewSyncPlayGroupUpdate(GroupUpdateType.PrepareSession, playRequest); + SendGroupUpdate(session, BroadcastType.CurrentSession, update, cancellationToken); + } + } + + /// + public void SessionLeave(SessionInfo session, CancellationToken cancellationToken) + { + _group.RemoveSession(session); + _syncPlayManager.RemoveSessionFromGroup(session, this); + + var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupLeft, _group.PositionTicks); + SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); + + var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserLeft, session.UserName); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); + } + + /// + public void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) + { + // The server's job is to mantain a consistent state to which clients refer to, + // as also to notify clients of state changes. + // The actual syncing of media playback happens client side. + // Clients are aware of the server's time and use it to sync. + switch (request.Type) + { + case PlaybackRequestType.Play: + HandlePlayRequest(session, request, cancellationToken); + break; + case PlaybackRequestType.Pause: + HandlePauseRequest(session, request, cancellationToken); + break; + case PlaybackRequestType.Seek: + HandleSeekRequest(session, request, cancellationToken); + break; + case PlaybackRequestType.Buffering: + HandleBufferingRequest(session, request, cancellationToken); + break; + case PlaybackRequestType.BufferingDone: + HandleBufferingDoneRequest(session, request, cancellationToken); + break; + case PlaybackRequestType.UpdatePing: + HandlePingUpdateRequest(session, request); + break; + } + } + + /// + /// Handles a play action requested by a session. + /// + /// The session. + /// The play action. + /// The cancellation token. + private void HandlePlayRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) + { + if (_group.IsPaused) + { + // Pick a suitable time that accounts for latency + var delay = _group.GetHighestPing() * 2; + delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; + + // Unpause group and set starting point in future + // Clients will start playback at LastActivity (datetime) from PositionTicks (playback position) + // The added delay does not guarantee, of course, that the command will be received in time + // Playback synchronization will mainly happen client side + _group.IsPaused = false; + _group.LastActivity = DateTime.UtcNow.AddMilliseconds( + delay + ); + + var command = NewSyncPlayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); + } + else + { + // Client got lost, sending current state + var command = NewSyncPlayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); + } + } + + /// + /// Handles a pause action requested by a session. + /// + /// The session. + /// The pause action. + /// The cancellation token. + private void HandlePauseRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) + { + if (!_group.IsPaused) + { + // Pause group and compute the media playback position + _group.IsPaused = true; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - _group.LastActivity; + _group.LastActivity = currentTime; + // Seek only if playback actually started + // (a pause request may be issued during the delay added to account for latency) + _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; + + var command = NewSyncPlayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); + } + else + { + // Client got lost, sending current state + var command = NewSyncPlayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); + } + } + + /// + /// Handles a seek action requested by a session. + /// + /// The session. + /// The seek action. + /// The cancellation token. + private void HandleSeekRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) + { + // Sanitize PositionTicks + var ticks = SanitizePositionTicks(request.PositionTicks); + + // Pause and seek + _group.IsPaused = true; + _group.PositionTicks = ticks; + _group.LastActivity = DateTime.UtcNow; + + var command = NewSyncPlayCommand(SendCommandType.Seek); + SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); + } + + /// + /// Handles a buffering action requested by a session. + /// + /// The session. + /// The buffering action. + /// The cancellation token. + private void HandleBufferingRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) + { + if (!_group.IsPaused) + { + // Pause group and compute the media playback position + _group.IsPaused = true; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - _group.LastActivity; + _group.LastActivity = currentTime; + _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; + + _group.SetBuffering(session, true); + + // Send pause command to all non-buffering sessions + var command = NewSyncPlayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.AllReady, command, cancellationToken); + + var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.GroupWait, session.UserName); + SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); + } + else + { + // Client got lost, sending current state + var command = NewSyncPlayCommand(SendCommandType.Pause); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); + } + } + + /// + /// Handles a buffering-done action requested by a session. + /// + /// The session. + /// The buffering-done action. + /// The cancellation token. + private void HandleBufferingDoneRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) + { + if (_group.IsPaused) + { + _group.SetBuffering(session, false); + + var requestTicks = SanitizePositionTicks(request.PositionTicks); + + var when = request.When ?? DateTime.UtcNow; + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - when; + var clientPosition = TimeSpan.FromTicks(requestTicks) + elapsedTime; + var delay = _group.PositionTicks - clientPosition.Ticks; + + if (_group.IsBuffering()) + { + // Others are still buffering, tell this client to pause when ready + var command = NewSyncPlayCommand(SendCommandType.Pause); + var pauseAtTime = currentTime.AddMilliseconds(delay); + command.When = DateToUTCString(pauseAtTime); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); + } + else + { + // Let other clients resume as soon as the buffering client catches up + _group.IsPaused = false; + + if (delay > _group.GetHighestPing() * 2) + { + // Client that was buffering is recovering, notifying others to resume + _group.LastActivity = currentTime.AddMilliseconds( + delay + ); + var command = NewSyncPlayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.AllExceptCurrentSession, command, cancellationToken); + } + else + { + // Client, that was buffering, resumed playback but did not update others in time + delay = _group.GetHighestPing() * 2; + delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; + + _group.LastActivity = currentTime.AddMilliseconds( + delay + ); + + var command = NewSyncPlayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); + } + } + } + else + { + // Group was not waiting, make sure client has latest state + var command = NewSyncPlayCommand(SendCommandType.Play); + SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); + } + } + + /// + /// Sanitizes the PositionTicks, considers the current playing item when available. + /// + /// The PositionTicks. + /// The sanitized PositionTicks. + private long SanitizePositionTicks(long? positionTicks) + { + var ticks = positionTicks ?? 0; + ticks = ticks >= 0 ? ticks : 0; + if (_group.PlayingItem != null) + { + var runTimeTicks = _group.PlayingItem.RunTimeTicks ?? 0; + ticks = ticks > runTimeTicks ? runTimeTicks : ticks; + } + return ticks; + } + + /// + /// Updates ping of a session. + /// + /// The session. + /// The update. + private void HandlePingUpdateRequest(SessionInfo session, PlaybackRequest request) + { + // Collected pings are used to account for network latency when unpausing playback + _group.UpdatePing(session, request.Ping ?? _group.DefaulPing); + } + + /// + public GroupInfoView GetInfo() + { + return new GroupInfoView() + { + GroupId = GetGroupId().ToString(), + PlayingItemName = _group.PlayingItem.Name, + PlayingItemId = _group.PlayingItem.Id.ToString(), + PositionTicks = _group.PositionTicks, + Participants = _group.Participants.Values.Select(session => session.Session.UserName).Distinct().ToArray() + }; + } + } +} diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs new file mode 100644 index 0000000000..d3197d97b8 --- /dev/null +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -0,0 +1,385 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; +using Microsoft.Extensions.Logging; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.SyncPlay; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.SyncPlay; + +namespace Emby.Server.Implementations.SyncPlay +{ + /// + /// Class SyncPlayManager. + /// + public class SyncPlayManager : ISyncPlayManager, IDisposable + { + /// + /// The logger. + /// + private readonly ILogger _logger; + + /// + /// The user manager. + /// + private readonly IUserManager _userManager; + + /// + /// The session manager. + /// + private readonly ISessionManager _sessionManager; + + /// + /// The library manager. + /// + private readonly ILibraryManager _libraryManager; + + /// + /// The map between sessions and groups. + /// + private readonly Dictionary _sessionToGroupMap = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// The groups. + /// + private readonly Dictionary _groups = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Lock used for accesing any group. + /// + private readonly object _groupsLock = new object(); + + private bool _disposed = false; + + public SyncPlayManager( + ILogger logger, + IUserManager userManager, + ISessionManager sessionManager, + ILibraryManager libraryManager) + { + _logger = logger; + _userManager = userManager; + _sessionManager = sessionManager; + _libraryManager = libraryManager; + + _sessionManager.SessionEnded += OnSessionManagerSessionEnded; + _sessionManager.PlaybackStopped += OnSessionManagerPlaybackStopped; + } + + /// + /// Gets all groups. + /// + /// All groups. + public IEnumerable Groups => _groups.Values; + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and optionally managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + _sessionManager.SessionEnded -= OnSessionManagerSessionEnded; + _sessionManager.PlaybackStopped -= OnSessionManagerPlaybackStopped; + + _disposed = true; + } + + private void CheckDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(GetType().Name); + } + } + + private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e) + { + var session = e.SessionInfo; + if (!IsSessionInGroup(session)) return; + LeaveGroup(session, CancellationToken.None); + } + + private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e) + { + var session = e.Session; + if (!IsSessionInGroup(session)) return; + LeaveGroup(session, CancellationToken.None); + } + + private bool IsSessionInGroup(SessionInfo session) + { + return _sessionToGroupMap.ContainsKey(session.Id); + } + + private bool HasAccessToItem(User user, Guid itemId) + { + var item = _libraryManager.GetItemById(itemId); + + // Check ParentalRating access + var hasParentalRatingAccess = true; + if (user.Policy.MaxParentalRating.HasValue) + { + hasParentalRatingAccess = item.InheritedParentalRatingValue <= user.Policy.MaxParentalRating; + } + + if (!user.Policy.EnableAllFolders && hasParentalRatingAccess) + { + var collections = _libraryManager.GetCollectionFolders(item).Select( + folder => folder.Id.ToString("N", CultureInfo.InvariantCulture) + ); + var intersect = collections.Intersect(user.Policy.EnabledFolders); + return intersect.Any(); + } + else + { + return hasParentalRatingAccess; + } + } + + private Guid? GetSessionGroup(SessionInfo session) + { + ISyncPlayController group; + _sessionToGroupMap.TryGetValue(session.Id, out group); + if (group != null) + { + return group.GetGroupId(); + } + else + { + return null; + } + } + + /// + public void NewGroup(SessionInfo session, CancellationToken cancellationToken) + { + var user = _userManager.GetUserById(session.UserId); + + if (user.Policy.SyncPlayAccess != SyncPlayAccess.CreateAndJoinGroups) + { + _logger.LogWarning("NewGroup: {0} does not have permission to create groups.", session.Id); + + var error = new GroupUpdate() + { + Type = GroupUpdateType.CreateGroupDenied + }; + _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } + + lock (_groupsLock) + { + if (IsSessionInGroup(session)) + { + LeaveGroup(session, cancellationToken); + } + + var group = new SyncPlayController(_sessionManager, this); + _groups[group.GetGroupId().ToString()] = group; + + group.InitGroup(session, cancellationToken); + } + } + + /// + public void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request, CancellationToken cancellationToken) + { + var user = _userManager.GetUserById(session.UserId); + + if (user.Policy.SyncPlayAccess == SyncPlayAccess.None) + { + _logger.LogWarning("JoinGroup: {0} does not have access to SyncPlay.", session.Id); + + var error = new GroupUpdate() + { + Type = GroupUpdateType.JoinGroupDenied + }; + _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } + + lock (_groupsLock) + { + ISyncPlayController group; + _groups.TryGetValue(groupId, out group); + + if (group == null) + { + _logger.LogWarning("JoinGroup: {0} tried to join group {0} that does not exist.", session.Id, groupId); + + var error = new GroupUpdate() + { + Type = GroupUpdateType.GroupDoesNotExist + }; + _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } + + if (!HasAccessToItem(user, group.GetPlayingItemId())) + { + _logger.LogWarning("JoinGroup: {0} does not have access to {1}.", session.Id, group.GetPlayingItemId()); + + var error = new GroupUpdate() + { + GroupId = group.GetGroupId().ToString(), + Type = GroupUpdateType.LibraryAccessDenied + }; + _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } + + if (IsSessionInGroup(session)) + { + if (GetSessionGroup(session).Equals(groupId)) return; + LeaveGroup(session, cancellationToken); + } + + group.SessionJoin(session, request, cancellationToken); + } + } + + /// + public void LeaveGroup(SessionInfo session, CancellationToken cancellationToken) + { + // TODO: determine what happens to users that are in a group and get their permissions revoked + lock (_groupsLock) + { + ISyncPlayController group; + _sessionToGroupMap.TryGetValue(session.Id, out group); + + if (group == null) + { + _logger.LogWarning("LeaveGroup: {0} does not belong to any group.", session.Id); + + var error = new GroupUpdate() + { + Type = GroupUpdateType.NotInGroup + }; + _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } + + group.SessionLeave(session, cancellationToken); + + if (group.IsGroupEmpty()) + { + _logger.LogInformation("LeaveGroup: removing empty group {0}.", group.GetGroupId()); + _groups.Remove(group.GetGroupId().ToString(), out _); + } + } + } + + /// + public List ListGroups(SessionInfo session, Guid filterItemId) + { + var user = _userManager.GetUserById(session.UserId); + + if (user.Policy.SyncPlayAccess == SyncPlayAccess.None) + { + return new List(); + } + + // Filter by item if requested + if (!filterItemId.Equals(Guid.Empty)) + { + return _groups.Values.Where( + group => group.GetPlayingItemId().Equals(filterItemId) && HasAccessToItem(user, group.GetPlayingItemId()) + ).Select( + group => group.GetInfo() + ).ToList(); + } + // Otherwise show all available groups + else + { + return _groups.Values.Where( + group => HasAccessToItem(user, group.GetPlayingItemId()) + ).Select( + group => group.GetInfo() + ).ToList(); + } + } + + /// + public void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) + { + var user = _userManager.GetUserById(session.UserId); + + if (user.Policy.SyncPlayAccess == SyncPlayAccess.None) + { + _logger.LogWarning("HandleRequest: {0} does not have access to SyncPlay.", session.Id); + + var error = new GroupUpdate() + { + Type = GroupUpdateType.JoinGroupDenied + }; + _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } + + lock (_groupsLock) + { + ISyncPlayController group; + _sessionToGroupMap.TryGetValue(session.Id, out group); + + if (group == null) + { + _logger.LogWarning("HandleRequest: {0} does not belong to any group.", session.Id); + + var error = new GroupUpdate() + { + Type = GroupUpdateType.NotInGroup + }; + _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + return; + } + + group.HandleRequest(session, request, cancellationToken); + } + } + + /// + public void AddSessionToGroup(SessionInfo session, ISyncPlayController group) + { + if (IsSessionInGroup(session)) + { + throw new InvalidOperationException("Session in other group already!"); + } + _sessionToGroupMap[session.Id] = group; + } + + /// + public void RemoveSessionFromGroup(SessionInfo session, ISyncPlayController group) + { + if (!IsSessionInGroup(session)) + { + throw new InvalidOperationException("Session not in any group!"); + } + + ISyncPlayController tempGroup; + _sessionToGroupMap.Remove(session.Id, out tempGroup); + + if (!tempGroup.GetGroupId().Equals(group.GetGroupId())) + { + throw new InvalidOperationException("Session was in wrong group!"); + } + } + } +} diff --git a/Emby.Server.Implementations/Syncplay/SyncplayController.cs b/Emby.Server.Implementations/Syncplay/SyncplayController.cs deleted file mode 100644 index 8cc3d1fac3..0000000000 --- a/Emby.Server.Implementations/Syncplay/SyncplayController.cs +++ /dev/null @@ -1,548 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Session; -using MediaBrowser.Controller.Syncplay; -using MediaBrowser.Model.Session; -using MediaBrowser.Model.Syncplay; - -namespace Emby.Server.Implementations.Syncplay -{ - /// - /// Class SyncplayController. - /// - /// - /// Class is not thread-safe, external locking is required when accessing methods. - /// - public class SyncplayController : ISyncplayController, IDisposable - { - /// - /// Used to filter the sessions of a group. - /// - private enum BroadcastType - { - /// - /// All sessions will receive the message. - /// - AllGroup = 0, - /// - /// Only the specified session will receive the message. - /// - CurrentSession = 1, - /// - /// All sessions, except the current one, will receive the message. - /// - AllExceptCurrentSession = 2, - /// - /// Only sessions that are not buffering will receive the message. - /// - AllReady = 3 - } - - /// - /// The session manager. - /// - private readonly ISessionManager _sessionManager; - - /// - /// The syncplay manager. - /// - private readonly ISyncplayManager _syncplayManager; - - /// - /// The group to manage. - /// - private readonly GroupInfo _group = new GroupInfo(); - - /// - public Guid GetGroupId() => _group.GroupId; - - /// - public Guid GetPlayingItemId() => _group.PlayingItem.Id; - - /// - public bool IsGroupEmpty() => _group.IsEmpty(); - - private bool _disposed = false; - - public SyncplayController( - ISessionManager sessionManager, - ISyncplayManager syncplayManager) - { - _sessionManager = sessionManager; - _syncplayManager = syncplayManager; - } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and optionally managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - _disposed = true; - } - - // TODO: use this somewhere - private void CheckDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } - } - - /// - /// Converts DateTime to UTC string. - /// - /// The date to convert. - /// The UTC string. - private string DateToUTCString(DateTime date) - { - return date.ToUniversalTime().ToString("o"); - } - - /// - /// Filters sessions of this group. - /// - /// The current session. - /// The filtering type. - /// The array of sessions matching the filter. - private SessionInfo[] FilterSessions(SessionInfo from, BroadcastType type) - { - switch (type) - { - case BroadcastType.CurrentSession: - return new SessionInfo[] { from }; - case BroadcastType.AllGroup: - return _group.Participants.Values.Select( - session => session.Session - ).ToArray(); - case BroadcastType.AllExceptCurrentSession: - return _group.Participants.Values.Select( - session => session.Session - ).Where( - session => !session.Id.Equals(from.Id) - ).ToArray(); - case BroadcastType.AllReady: - return _group.Participants.Values.Where( - session => !session.IsBuffering - ).Select( - session => session.Session - ).ToArray(); - default: - return new SessionInfo[] { }; - } - } - - /// - /// Sends a GroupUpdate message to the interested sessions. - /// - /// The current session. - /// The filtering type. - /// The message to send. - /// The cancellation token. - /// The task. - private Task SendGroupUpdate(SessionInfo from, BroadcastType type, GroupUpdate message, CancellationToken cancellationToken) - { - IEnumerable GetTasks() - { - SessionInfo[] sessions = FilterSessions(from, type); - foreach (var session in sessions) - { - yield return _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), message, cancellationToken); - } - } - - return Task.WhenAll(GetTasks()); - } - - /// - /// Sends a playback command to the interested sessions. - /// - /// The current session. - /// The filtering type. - /// The message to send. - /// The cancellation token. - /// The task. - private Task SendCommand(SessionInfo from, BroadcastType type, SendCommand message, CancellationToken cancellationToken) - { - IEnumerable GetTasks() - { - SessionInfo[] sessions = FilterSessions(from, type); - foreach (var session in sessions) - { - yield return _sessionManager.SendSyncplayCommand(session.Id.ToString(), message, cancellationToken); - } - } - - return Task.WhenAll(GetTasks()); - } - - /// - /// Builds a new playback command with some default values. - /// - /// The command type. - /// The SendCommand. - private SendCommand NewSyncplayCommand(SendCommandType type) - { - return new SendCommand() - { - GroupId = _group.GroupId.ToString(), - Command = type, - PositionTicks = _group.PositionTicks, - When = DateToUTCString(_group.LastActivity), - EmittedAt = DateToUTCString(DateTime.UtcNow) - }; - } - - /// - /// Builds a new group update message. - /// - /// The update type. - /// The data to send. - /// The GroupUpdate. - private GroupUpdate NewSyncplayGroupUpdate(GroupUpdateType type, T data) - { - return new GroupUpdate() - { - GroupId = _group.GroupId.ToString(), - Type = type, - Data = data - }; - } - - /// - public void InitGroup(SessionInfo session, CancellationToken cancellationToken) - { - _group.AddSession(session); - _syncplayManager.AddSessionToGroup(session, this); - - _group.PlayingItem = session.FullNowPlayingItem; - _group.IsPaused = true; - _group.PositionTicks = session.PlayState.PositionTicks ?? 0; - _group.LastActivity = DateTime.UtcNow; - - var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow)); - SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); - var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken); - } - - /// - public void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken) - { - if (session.NowPlayingItem?.Id == _group.PlayingItem.Id && request.PlayingItemId == _group.PlayingItem.Id) - { - _group.AddSession(session); - _syncplayManager.AddSessionToGroup(session, this); - - var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow)); - SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); - - var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserJoined, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); - - // Client join and play, syncing will happen client side - if (!_group.IsPaused) - { - var playCommand = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.CurrentSession, playCommand, cancellationToken); - } - else - { - var pauseCommand = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken); - } - } - else - { - var playRequest = new PlayRequest(); - playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id }; - playRequest.StartPositionTicks = _group.PositionTicks; - var update = NewSyncplayGroupUpdate(GroupUpdateType.PrepareSession, playRequest); - SendGroupUpdate(session, BroadcastType.CurrentSession, update, cancellationToken); - } - } - - /// - public void SessionLeave(SessionInfo session, CancellationToken cancellationToken) - { - _group.RemoveSession(session); - _syncplayManager.RemoveSessionFromGroup(session, this); - - var updateSession = NewSyncplayGroupUpdate(GroupUpdateType.GroupLeft, _group.PositionTicks); - SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken); - - var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.UserLeft, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); - } - - /// - public void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) - { - // The server's job is to mantain a consistent state to which clients refer to, - // as also to notify clients of state changes. - // The actual syncing of media playback happens client side. - // Clients are aware of the server's time and use it to sync. - switch (request.Type) - { - case PlaybackRequestType.Play: - HandlePlayRequest(session, request, cancellationToken); - break; - case PlaybackRequestType.Pause: - HandlePauseRequest(session, request, cancellationToken); - break; - case PlaybackRequestType.Seek: - HandleSeekRequest(session, request, cancellationToken); - break; - case PlaybackRequestType.Buffering: - HandleBufferingRequest(session, request, cancellationToken); - break; - case PlaybackRequestType.BufferingDone: - HandleBufferingDoneRequest(session, request, cancellationToken); - break; - case PlaybackRequestType.UpdatePing: - HandlePingUpdateRequest(session, request); - break; - } - } - - /// - /// Handles a play action requested by a session. - /// - /// The session. - /// The play action. - /// The cancellation token. - private void HandlePlayRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) - { - if (_group.IsPaused) - { - // Pick a suitable time that accounts for latency - var delay = _group.GetHighestPing() * 2; - delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; - - // Unpause group and set starting point in future - // Clients will start playback at LastActivity (datetime) from PositionTicks (playback position) - // The added delay does not guarantee, of course, that the command will be received in time - // Playback synchronization will mainly happen client side - _group.IsPaused = false; - _group.LastActivity = DateTime.UtcNow.AddMilliseconds( - delay - ); - - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); - } - else - { - // Client got lost, sending current state - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); - } - } - - /// - /// Handles a pause action requested by a session. - /// - /// The session. - /// The pause action. - /// The cancellation token. - private void HandlePauseRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) - { - if (!_group.IsPaused) - { - // Pause group and compute the media playback position - _group.IsPaused = true; - var currentTime = DateTime.UtcNow; - var elapsedTime = currentTime - _group.LastActivity; - _group.LastActivity = currentTime; - // Seek only if playback actually started - // (a pause request may be issued during the delay added to account for latency) - _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; - - var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); - } - else - { - // Client got lost, sending current state - var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); - } - } - - /// - /// Handles a seek action requested by a session. - /// - /// The session. - /// The seek action. - /// The cancellation token. - private void HandleSeekRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) - { - // Sanitize PositionTicks - var ticks = SanitizePositionTicks(request.PositionTicks); - - // Pause and seek - _group.IsPaused = true; - _group.PositionTicks = ticks; - _group.LastActivity = DateTime.UtcNow; - - var command = NewSyncplayCommand(SendCommandType.Seek); - SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); - } - - /// - /// Handles a buffering action requested by a session. - /// - /// The session. - /// The buffering action. - /// The cancellation token. - private void HandleBufferingRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) - { - if (!_group.IsPaused) - { - // Pause group and compute the media playback position - _group.IsPaused = true; - var currentTime = DateTime.UtcNow; - var elapsedTime = currentTime - _group.LastActivity; - _group.LastActivity = currentTime; - _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; - - _group.SetBuffering(session, true); - - // Send pause command to all non-buffering sessions - var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.AllReady, command, cancellationToken); - - var updateOthers = NewSyncplayGroupUpdate(GroupUpdateType.GroupWait, session.UserName); - SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); - } - else - { - // Client got lost, sending current state - var command = NewSyncplayCommand(SendCommandType.Pause); - SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); - } - } - - /// - /// Handles a buffering-done action requested by a session. - /// - /// The session. - /// The buffering-done action. - /// The cancellation token. - private void HandleBufferingDoneRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) - { - if (_group.IsPaused) - { - _group.SetBuffering(session, false); - - var requestTicks = SanitizePositionTicks(request.PositionTicks); - - var when = request.When ?? DateTime.UtcNow; - var currentTime = DateTime.UtcNow; - var elapsedTime = currentTime - when; - var clientPosition = TimeSpan.FromTicks(requestTicks) + elapsedTime; - var delay = _group.PositionTicks - clientPosition.Ticks; - - if (_group.IsBuffering()) - { - // Others are still buffering, tell this client to pause when ready - var command = NewSyncplayCommand(SendCommandType.Pause); - var pauseAtTime = currentTime.AddMilliseconds(delay); - command.When = DateToUTCString(pauseAtTime); - SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); - } - else - { - // Let other clients resume as soon as the buffering client catches up - _group.IsPaused = false; - - if (delay > _group.GetHighestPing() * 2) - { - // Client that was buffering is recovering, notifying others to resume - _group.LastActivity = currentTime.AddMilliseconds( - delay - ); - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllExceptCurrentSession, command, cancellationToken); - } - else - { - // Client, that was buffering, resumed playback but did not update others in time - delay = _group.GetHighestPing() * 2; - delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; - - _group.LastActivity = currentTime.AddMilliseconds( - delay - ); - - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); - } - } - } - else - { - // Group was not waiting, make sure client has latest state - var command = NewSyncplayCommand(SendCommandType.Play); - SendCommand(session, BroadcastType.CurrentSession, command, cancellationToken); - } - } - - /// - /// Sanitizes the PositionTicks, considers the current playing item when available. - /// - /// The PositionTicks. - /// The sanitized PositionTicks. - private long SanitizePositionTicks(long? positionTicks) - { - var ticks = positionTicks ?? 0; - ticks = ticks >= 0 ? ticks : 0; - if (_group.PlayingItem != null) - { - var runTimeTicks = _group.PlayingItem.RunTimeTicks ?? 0; - ticks = ticks > runTimeTicks ? runTimeTicks : ticks; - } - return ticks; - } - - /// - /// Updates ping of a session. - /// - /// The session. - /// The update. - private void HandlePingUpdateRequest(SessionInfo session, PlaybackRequest request) - { - // Collected pings are used to account for network latency when unpausing playback - _group.UpdatePing(session, request.Ping ?? _group.DefaulPing); - } - - /// - public GroupInfoView GetInfo() - { - return new GroupInfoView() - { - GroupId = GetGroupId().ToString(), - PlayingItemName = _group.PlayingItem.Name, - PlayingItemId = _group.PlayingItem.Id.ToString(), - PositionTicks = _group.PositionTicks, - Participants = _group.Participants.Values.Select(session => session.Session.UserName).Distinct().ToArray() - }; - } - } -} diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs deleted file mode 100644 index 7074e2225e..0000000000 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ /dev/null @@ -1,385 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading; -using Microsoft.Extensions.Logging; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Session; -using MediaBrowser.Controller.Syncplay; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Syncplay; - -namespace Emby.Server.Implementations.Syncplay -{ - /// - /// Class SyncplayManager. - /// - public class SyncplayManager : ISyncplayManager, IDisposable - { - /// - /// The logger. - /// - private readonly ILogger _logger; - - /// - /// The user manager. - /// - private readonly IUserManager _userManager; - - /// - /// The session manager. - /// - private readonly ISessionManager _sessionManager; - - /// - /// The library manager. - /// - private readonly ILibraryManager _libraryManager; - - /// - /// The map between sessions and groups. - /// - private readonly Dictionary _sessionToGroupMap = - new Dictionary(StringComparer.OrdinalIgnoreCase); - - /// - /// The groups. - /// - private readonly Dictionary _groups = - new Dictionary(StringComparer.OrdinalIgnoreCase); - - /// - /// Lock used for accesing any group. - /// - private readonly object _groupsLock = new object(); - - private bool _disposed = false; - - public SyncplayManager( - ILogger logger, - IUserManager userManager, - ISessionManager sessionManager, - ILibraryManager libraryManager) - { - _logger = logger; - _userManager = userManager; - _sessionManager = sessionManager; - _libraryManager = libraryManager; - - _sessionManager.SessionEnded += OnSessionManagerSessionEnded; - _sessionManager.PlaybackStopped += OnSessionManagerPlaybackStopped; - } - - /// - /// Gets all groups. - /// - /// All groups. - public IEnumerable Groups => _groups.Values; - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and optionally managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - _sessionManager.SessionEnded -= OnSessionManagerSessionEnded; - _sessionManager.PlaybackStopped -= OnSessionManagerPlaybackStopped; - - _disposed = true; - } - - private void CheckDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } - } - - private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e) - { - var session = e.SessionInfo; - if (!IsSessionInGroup(session)) return; - LeaveGroup(session, CancellationToken.None); - } - - private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e) - { - var session = e.Session; - if (!IsSessionInGroup(session)) return; - LeaveGroup(session, CancellationToken.None); - } - - private bool IsSessionInGroup(SessionInfo session) - { - return _sessionToGroupMap.ContainsKey(session.Id); - } - - private bool HasAccessToItem(User user, Guid itemId) - { - var item = _libraryManager.GetItemById(itemId); - - // Check ParentalRating access - var hasParentalRatingAccess = true; - if (user.Policy.MaxParentalRating.HasValue) - { - hasParentalRatingAccess = item.InheritedParentalRatingValue <= user.Policy.MaxParentalRating; - } - - if (!user.Policy.EnableAllFolders && hasParentalRatingAccess) - { - var collections = _libraryManager.GetCollectionFolders(item).Select( - folder => folder.Id.ToString("N", CultureInfo.InvariantCulture) - ); - var intersect = collections.Intersect(user.Policy.EnabledFolders); - return intersect.Any(); - } - else - { - return hasParentalRatingAccess; - } - } - - private Guid? GetSessionGroup(SessionInfo session) - { - ISyncplayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); - if (group != null) - { - return group.GetGroupId(); - } - else - { - return null; - } - } - - /// - public void NewGroup(SessionInfo session, CancellationToken cancellationToken) - { - var user = _userManager.GetUserById(session.UserId); - - if (user.Policy.SyncplayAccess != SyncplayAccess.CreateAndJoinGroups) - { - _logger.LogWarning("NewGroup: {0} does not have permission to create groups.", session.Id); - - var error = new GroupUpdate() - { - Type = GroupUpdateType.CreateGroupDenied - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; - } - - lock (_groupsLock) - { - if (IsSessionInGroup(session)) - { - LeaveGroup(session, cancellationToken); - } - - var group = new SyncplayController(_sessionManager, this); - _groups[group.GetGroupId().ToString()] = group; - - group.InitGroup(session, cancellationToken); - } - } - - /// - public void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request, CancellationToken cancellationToken) - { - var user = _userManager.GetUserById(session.UserId); - - if (user.Policy.SyncplayAccess == SyncplayAccess.None) - { - _logger.LogWarning("JoinGroup: {0} does not have access to Syncplay.", session.Id); - - var error = new GroupUpdate() - { - Type = GroupUpdateType.JoinGroupDenied - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; - } - - lock (_groupsLock) - { - ISyncplayController group; - _groups.TryGetValue(groupId, out group); - - if (group == null) - { - _logger.LogWarning("JoinGroup: {0} tried to join group {0} that does not exist.", session.Id, groupId); - - var error = new GroupUpdate() - { - Type = GroupUpdateType.GroupDoesNotExist - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; - } - - if (!HasAccessToItem(user, group.GetPlayingItemId())) - { - _logger.LogWarning("JoinGroup: {0} does not have access to {1}.", session.Id, group.GetPlayingItemId()); - - var error = new GroupUpdate() - { - GroupId = group.GetGroupId().ToString(), - Type = GroupUpdateType.LibraryAccessDenied - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; - } - - if (IsSessionInGroup(session)) - { - if (GetSessionGroup(session).Equals(groupId)) return; - LeaveGroup(session, cancellationToken); - } - - group.SessionJoin(session, request, cancellationToken); - } - } - - /// - public void LeaveGroup(SessionInfo session, CancellationToken cancellationToken) - { - // TODO: determine what happens to users that are in a group and get their permissions revoked - lock (_groupsLock) - { - ISyncplayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); - - if (group == null) - { - _logger.LogWarning("LeaveGroup: {0} does not belong to any group.", session.Id); - - var error = new GroupUpdate() - { - Type = GroupUpdateType.NotInGroup - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; - } - - group.SessionLeave(session, cancellationToken); - - if (group.IsGroupEmpty()) - { - _logger.LogInformation("LeaveGroup: removing empty group {0}.", group.GetGroupId()); - _groups.Remove(group.GetGroupId().ToString(), out _); - } - } - } - - /// - public List ListGroups(SessionInfo session, Guid filterItemId) - { - var user = _userManager.GetUserById(session.UserId); - - if (user.Policy.SyncplayAccess == SyncplayAccess.None) - { - return new List(); - } - - // Filter by item if requested - if (!filterItemId.Equals(Guid.Empty)) - { - return _groups.Values.Where( - group => group.GetPlayingItemId().Equals(filterItemId) && HasAccessToItem(user, group.GetPlayingItemId()) - ).Select( - group => group.GetInfo() - ).ToList(); - } - // Otherwise show all available groups - else - { - return _groups.Values.Where( - group => HasAccessToItem(user, group.GetPlayingItemId()) - ).Select( - group => group.GetInfo() - ).ToList(); - } - } - - /// - public void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken) - { - var user = _userManager.GetUserById(session.UserId); - - if (user.Policy.SyncplayAccess == SyncplayAccess.None) - { - _logger.LogWarning("HandleRequest: {0} does not have access to Syncplay.", session.Id); - - var error = new GroupUpdate() - { - Type = GroupUpdateType.JoinGroupDenied - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; - } - - lock (_groupsLock) - { - ISyncplayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); - - if (group == null) - { - _logger.LogWarning("HandleRequest: {0} does not belong to any group.", session.Id); - - var error = new GroupUpdate() - { - Type = GroupUpdateType.NotInGroup - }; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); - return; - } - - group.HandleRequest(session, request, cancellationToken); - } - } - - /// - public void AddSessionToGroup(SessionInfo session, ISyncplayController group) - { - if (IsSessionInGroup(session)) - { - throw new InvalidOperationException("Session in other group already!"); - } - _sessionToGroupMap[session.Id] = group; - } - - /// - public void RemoveSessionFromGroup(SessionInfo session, ISyncplayController group) - { - if (!IsSessionInGroup(session)) - { - throw new InvalidOperationException("Session not in any group!"); - } - - ISyncplayController tempGroup; - _sessionToGroupMap.Remove(session.Id, out tempGroup); - - if (!tempGroup.GetGroupId().Equals(group.GetGroupId())) - { - throw new InvalidOperationException("Session was in wrong group!"); - } - } - } -} diff --git a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs new file mode 100644 index 0000000000..bcdc833e40 --- /dev/null +++ b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs @@ -0,0 +1,312 @@ +using System.Threading; +using System; +using System.Collections.Generic; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.SyncPlay; +using MediaBrowser.Model.Services; +using MediaBrowser.Model.SyncPlay; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Api.SyncPlay +{ + [Route("/SyncPlay/{SessionId}/NewGroup", "POST", Summary = "Create a new SyncPlay group")] + [Authenticated] + public class SyncPlayNewGroup : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + [Route("/SyncPlay/{SessionId}/JoinGroup", "POST", Summary = "Join an existing SyncPlay group")] + [Authenticated] + public class SyncPlayJoinGroup : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + + /// + /// Gets or sets the Group id. + /// + /// The Group id to join. + [ApiMember(Name = "GroupId", Description = "Group Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string GroupId { get; set; } + + /// + /// Gets or sets the playing item id. + /// + /// The client's currently playing item id. + [ApiMember(Name = "PlayingItemId", Description = "Client's playing item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string PlayingItemId { get; set; } + } + + [Route("/SyncPlay/{SessionId}/LeaveGroup", "POST", Summary = "Leave joined SyncPlay group")] + [Authenticated] + public class SyncPlayLeaveGroup : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + [Route("/SyncPlay/{SessionId}/ListGroups", "POST", Summary = "List SyncPlay groups")] + [Authenticated] + public class SyncPlayListGroups : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + + /// + /// Gets or sets the filter item id. + /// + /// The filter item id. + [ApiMember(Name = "FilterItemId", Description = "Filter by item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string FilterItemId { get; set; } + } + + [Route("/SyncPlay/{SessionId}/PlayRequest", "POST", Summary = "Request play in SyncPlay group")] + [Authenticated] + public class SyncPlayPlayRequest : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + [Route("/SyncPlay/{SessionId}/PauseRequest", "POST", Summary = "Request pause in SyncPlay group")] + [Authenticated] + public class SyncPlayPauseRequest : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + } + + [Route("/SyncPlay/{SessionId}/SeekRequest", "POST", Summary = "Request seek in SyncPlay group")] + [Authenticated] + public class SyncPlaySeekRequest : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + + [ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")] + public long PositionTicks { get; set; } + } + + [Route("/SyncPlay/{SessionId}/BufferingRequest", "POST", Summary = "Request group wait in SyncPlay group while buffering")] + [Authenticated] + public class SyncPlayBufferingRequest : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + + /// + /// Gets or sets the date used to pin PositionTicks in time. + /// + /// The date related to PositionTicks. + [ApiMember(Name = "When", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string When { get; set; } + + [ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")] + public long PositionTicks { get; set; } + + /// + /// Gets or sets whether this is a buffering or a buffering-done request. + /// + /// true if buffering is complete; false otherwise. + [ApiMember(Name = "BufferingDone", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")] + public bool BufferingDone { get; set; } + } + + [Route("/SyncPlay/{SessionId}/UpdatePing", "POST", Summary = "Update session ping")] + [Authenticated] + public class SyncPlayUpdatePing : IReturnVoid + { + [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public string SessionId { get; set; } + + [ApiMember(Name = "Ping", IsRequired = true, DataType = "double", ParameterType = "query", Verb = "POST")] + public double Ping { get; set; } + } + + /// + /// Class SyncPlayService. + /// + public class SyncPlayService : BaseApiService + { + /// + /// The session context. + /// + private readonly ISessionContext _sessionContext; + + /// + /// The SyncPlay manager. + /// + private readonly ISyncPlayManager _syncPlayManager; + + public SyncPlayService( + ILogger logger, + IServerConfigurationManager serverConfigurationManager, + IHttpResultFactory httpResultFactory, + ISessionContext sessionContext, + ISyncPlayManager syncPlayManager) + : base(logger, serverConfigurationManager, httpResultFactory) + { + _sessionContext = sessionContext; + _syncPlayManager = syncPlayManager; + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncPlayNewGroup request) + { + var currentSession = GetSession(_sessionContext); + _syncPlayManager.NewGroup(currentSession, CancellationToken.None); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncPlayJoinGroup request) + { + var currentSession = GetSession(_sessionContext); + var joinRequest = new JoinGroupRequest() + { + GroupId = Guid.Parse(request.GroupId) + }; + + // Both null and empty strings mean that client isn't playing anything + if (!String.IsNullOrEmpty(request.PlayingItemId)) + { + try + { + joinRequest.PlayingItemId = Guid.Parse(request.PlayingItemId); + } + catch (ArgumentNullException) + { + // Should never happen, but just in case + Logger.LogError("JoinGroup: null value for PlayingItemId. Ignoring request."); + return; + } + catch (FormatException) + { + Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId); + return; + } + } + _syncPlayManager.JoinGroup(currentSession, request.GroupId, joinRequest, CancellationToken.None); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncPlayLeaveGroup request) + { + var currentSession = GetSession(_sessionContext); + _syncPlayManager.LeaveGroup(currentSession, CancellationToken.None); + } + + /// + /// Handles the specified request. + /// + /// The request. + /// The requested list of groups. + public List Post(SyncPlayListGroups request) + { + var currentSession = GetSession(_sessionContext); + var filterItemId = Guid.Empty; + if (!String.IsNullOrEmpty(request.FilterItemId)) + { + try + { + filterItemId = Guid.Parse(request.FilterItemId); + } + catch (ArgumentNullException) + { + Logger.LogWarning("ListGroups: null value for FilterItemId. Ignoring filter."); + } + catch (FormatException) + { + Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); + } + } + return _syncPlayManager.ListGroups(currentSession, filterItemId); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncPlayPlayRequest request) + { + var currentSession = GetSession(_sessionContext); + var syncPlayRequest = new PlaybackRequest() + { + Type = PlaybackRequestType.Play + }; + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncPlayPauseRequest request) + { + var currentSession = GetSession(_sessionContext); + var syncPlayRequest = new PlaybackRequest() + { + Type = PlaybackRequestType.Pause + }; + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncPlaySeekRequest request) + { + var currentSession = GetSession(_sessionContext); + var syncPlayRequest = new PlaybackRequest() + { + Type = PlaybackRequestType.Seek, + PositionTicks = request.PositionTicks + }; + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncPlayBufferingRequest request) + { + var currentSession = GetSession(_sessionContext); + var syncPlayRequest = new PlaybackRequest() + { + Type = request.BufferingDone ? PlaybackRequestType.BufferingDone : PlaybackRequestType.Buffering, + When = DateTime.Parse(request.When), + PositionTicks = request.PositionTicks + }; + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + } + + /// + /// Handles the specified request. + /// + /// The request. + public void Post(SyncPlayUpdatePing request) + { + var currentSession = GetSession(_sessionContext); + var syncPlayRequest = new PlaybackRequest() + { + Type = PlaybackRequestType.UpdatePing, + Ping = Convert.ToInt64(request.Ping) + }; + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + } + } +} diff --git a/MediaBrowser.Api/SyncPlay/TimeSyncService.cs b/MediaBrowser.Api/SyncPlay/TimeSyncService.cs new file mode 100644 index 0000000000..4a9307e62f --- /dev/null +++ b/MediaBrowser.Api/SyncPlay/TimeSyncService.cs @@ -0,0 +1,52 @@ +using System; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Services; +using MediaBrowser.Model.SyncPlay; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Api.SyncPlay +{ + [Route("/GetUtcTime", "GET", Summary = "Get UtcTime")] + public class GetUtcTime : IReturnVoid + { + // Nothing + } + + /// + /// Class TimeSyncService. + /// + public class TimeSyncService : BaseApiService + { + public TimeSyncService( + ILogger logger, + IServerConfigurationManager serverConfigurationManager, + IHttpResultFactory httpResultFactory) + : base(logger, serverConfigurationManager, httpResultFactory) + { + // Do nothing + } + + /// + /// Handles the specified request. + /// + /// The request. + /// The current UTC time response. + public UtcTimeResponse Get(GetUtcTime request) + { + // Important to keep the following line at the beginning + var requestReceptionTime = DateTime.UtcNow.ToUniversalTime().ToString("o"); + + var response = new UtcTimeResponse(); + response.RequestReceptionTime = requestReceptionTime; + + // Important to keep the following two lines at the end + var responseTransmissionTime = DateTime.UtcNow.ToUniversalTime().ToString("o"); + response.ResponseTransmissionTime = responseTransmissionTime; + + // Implementing NTP on such a high level results in this useless + // information being sent. On the other hand it enables future additions. + return response; + } + } +} diff --git a/MediaBrowser.Api/Syncplay/SyncplayService.cs b/MediaBrowser.Api/Syncplay/SyncplayService.cs deleted file mode 100644 index 4b6e16762a..0000000000 --- a/MediaBrowser.Api/Syncplay/SyncplayService.cs +++ /dev/null @@ -1,312 +0,0 @@ -using System.Threading; -using System; -using System.Collections.Generic; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Session; -using MediaBrowser.Controller.Syncplay; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.Syncplay; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Syncplay -{ - [Route("/Syncplay/{SessionId}/NewGroup", "POST", Summary = "Create a new Syncplay group")] - [Authenticated] - public class SyncplayNewGroup : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - } - - [Route("/Syncplay/{SessionId}/JoinGroup", "POST", Summary = "Join an existing Syncplay group")] - [Authenticated] - public class SyncplayJoinGroup : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - - /// - /// Gets or sets the Group id. - /// - /// The Group id to join. - [ApiMember(Name = "GroupId", Description = "Group Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string GroupId { get; set; } - - /// - /// Gets or sets the playing item id. - /// - /// The client's currently playing item id. - [ApiMember(Name = "PlayingItemId", Description = "Client's playing item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string PlayingItemId { get; set; } - } - - [Route("/Syncplay/{SessionId}/LeaveGroup", "POST", Summary = "Leave joined Syncplay group")] - [Authenticated] - public class SyncplayLeaveGroup : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - } - - [Route("/Syncplay/{SessionId}/ListGroups", "POST", Summary = "List Syncplay groups")] - [Authenticated] - public class SyncplayListGroups : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - - /// - /// Gets or sets the filter item id. - /// - /// The filter item id. - [ApiMember(Name = "FilterItemId", Description = "Filter by item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string FilterItemId { get; set; } - } - - [Route("/Syncplay/{SessionId}/PlayRequest", "POST", Summary = "Request play in Syncplay group")] - [Authenticated] - public class SyncplayPlayRequest : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - } - - [Route("/Syncplay/{SessionId}/PauseRequest", "POST", Summary = "Request pause in Syncplay group")] - [Authenticated] - public class SyncplayPauseRequest : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - } - - [Route("/Syncplay/{SessionId}/SeekRequest", "POST", Summary = "Request seek in Syncplay group")] - [Authenticated] - public class SyncplaySeekRequest : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - - [ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")] - public long PositionTicks { get; set; } - } - - [Route("/Syncplay/{SessionId}/BufferingRequest", "POST", Summary = "Request group wait in Syncplay group while buffering")] - [Authenticated] - public class SyncplayBufferingRequest : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - - /// - /// Gets or sets the date used to pin PositionTicks in time. - /// - /// The date related to PositionTicks. - [ApiMember(Name = "When", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string When { get; set; } - - [ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")] - public long PositionTicks { get; set; } - - /// - /// Gets or sets whether this is a buffering or a buffering-done request. - /// - /// true if buffering is complete; false otherwise. - [ApiMember(Name = "BufferingDone", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool BufferingDone { get; set; } - } - - [Route("/Syncplay/{SessionId}/UpdatePing", "POST", Summary = "Update session ping")] - [Authenticated] - public class SyncplayUpdatePing : IReturnVoid - { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - - [ApiMember(Name = "Ping", IsRequired = true, DataType = "double", ParameterType = "query", Verb = "POST")] - public double Ping { get; set; } - } - - /// - /// Class SyncplayService. - /// - public class SyncplayService : BaseApiService - { - /// - /// The session context. - /// - private readonly ISessionContext _sessionContext; - - /// - /// The Syncplay manager. - /// - private readonly ISyncplayManager _syncplayManager; - - public SyncplayService( - ILogger logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - ISessionContext sessionContext, - ISyncplayManager syncplayManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _sessionContext = sessionContext; - _syncplayManager = syncplayManager; - } - - /// - /// Handles the specified request. - /// - /// The request. - public void Post(SyncplayNewGroup request) - { - var currentSession = GetSession(_sessionContext); - _syncplayManager.NewGroup(currentSession, CancellationToken.None); - } - - /// - /// Handles the specified request. - /// - /// The request. - public void Post(SyncplayJoinGroup request) - { - var currentSession = GetSession(_sessionContext); - var joinRequest = new JoinGroupRequest() - { - GroupId = Guid.Parse(request.GroupId) - }; - - // Both null and empty strings mean that client isn't playing anything - if (!String.IsNullOrEmpty(request.PlayingItemId)) - { - try - { - joinRequest.PlayingItemId = Guid.Parse(request.PlayingItemId); - } - catch (ArgumentNullException) - { - // Should never happen, but just in case - Logger.LogError("JoinGroup: null value for PlayingItemId. Ignoring request."); - return; - } - catch (FormatException) - { - Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId); - return; - } - } - _syncplayManager.JoinGroup(currentSession, request.GroupId, joinRequest, CancellationToken.None); - } - - /// - /// Handles the specified request. - /// - /// The request. - public void Post(SyncplayLeaveGroup request) - { - var currentSession = GetSession(_sessionContext); - _syncplayManager.LeaveGroup(currentSession, CancellationToken.None); - } - - /// - /// Handles the specified request. - /// - /// The request. - /// The requested list of groups. - public List Post(SyncplayListGroups request) - { - var currentSession = GetSession(_sessionContext); - var filterItemId = Guid.Empty; - if (!String.IsNullOrEmpty(request.FilterItemId)) - { - try - { - filterItemId = Guid.Parse(request.FilterItemId); - } - catch (ArgumentNullException) - { - Logger.LogWarning("ListGroups: null value for FilterItemId. Ignoring filter."); - } - catch (FormatException) - { - Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); - } - } - return _syncplayManager.ListGroups(currentSession, filterItemId); - } - - /// - /// Handles the specified request. - /// - /// The request. - public void Post(SyncplayPlayRequest request) - { - var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest() - { - Type = PlaybackRequestType.Play - }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); - } - - /// - /// Handles the specified request. - /// - /// The request. - public void Post(SyncplayPauseRequest request) - { - var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest() - { - Type = PlaybackRequestType.Pause - }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); - } - - /// - /// Handles the specified request. - /// - /// The request. - public void Post(SyncplaySeekRequest request) - { - var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest() - { - Type = PlaybackRequestType.Seek, - PositionTicks = request.PositionTicks - }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); - } - - /// - /// Handles the specified request. - /// - /// The request. - public void Post(SyncplayBufferingRequest request) - { - var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest() - { - Type = request.BufferingDone ? PlaybackRequestType.BufferingDone : PlaybackRequestType.Buffering, - When = DateTime.Parse(request.When), - PositionTicks = request.PositionTicks - }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); - } - - /// - /// Handles the specified request. - /// - /// The request. - public void Post(SyncplayUpdatePing request) - { - var currentSession = GetSession(_sessionContext); - var syncplayRequest = new PlaybackRequest() - { - Type = PlaybackRequestType.UpdatePing, - Ping = Convert.ToInt64(request.Ping) - }; - _syncplayManager.HandleRequest(currentSession, syncplayRequest, CancellationToken.None); - } - } -} diff --git a/MediaBrowser.Api/Syncplay/TimeSyncService.cs b/MediaBrowser.Api/Syncplay/TimeSyncService.cs deleted file mode 100644 index 9a26ffd996..0000000000 --- a/MediaBrowser.Api/Syncplay/TimeSyncService.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.Syncplay; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Syncplay -{ - [Route("/GetUtcTime", "GET", Summary = "Get UtcTime")] - public class GetUtcTime : IReturnVoid - { - // Nothing - } - - /// - /// Class TimeSyncService. - /// - public class TimeSyncService : BaseApiService - { - public TimeSyncService( - ILogger logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory) - : base(logger, serverConfigurationManager, httpResultFactory) - { - // Do nothing - } - - /// - /// Handles the specified request. - /// - /// The request. - /// The current UTC time response. - public UtcTimeResponse Get(GetUtcTime request) - { - // Important to keep the following line at the beginning - var requestReceptionTime = DateTime.UtcNow.ToUniversalTime().ToString("o"); - - var response = new UtcTimeResponse(); - response.RequestReceptionTime = requestReceptionTime; - - // Important to keep the following two lines at the end - var responseTransmissionTime = DateTime.UtcNow.ToUniversalTime().ToString("o"); - response.ResponseTransmissionTime = responseTransmissionTime; - - // Implementing NTP on such a high level results in this useless - // information being sent. On the other hand it enables future additions. - return response; - } - } -} diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 39c065b895..4c2f834cb3 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -9,7 +9,7 @@ using MediaBrowser.Controller.Security; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Events; using MediaBrowser.Model.Session; -using MediaBrowser.Model.Syncplay; +using MediaBrowser.Model.SyncPlay; namespace MediaBrowser.Controller.Session { @@ -142,22 +142,22 @@ namespace MediaBrowser.Controller.Session Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken); /// - /// Sends the SyncplayCommand. + /// Sends the SyncPlayCommand. /// /// The session id. /// The command. /// The cancellation token. /// Task. - Task SendSyncplayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken); + Task SendSyncPlayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken); /// - /// Sends the SyncplayGroupUpdate. + /// Sends the SyncPlayGroupUpdate. /// /// The session id. /// The group update. /// The cancellation token. /// Task. - Task SendSyncplayGroupUpdate(string sessionId, GroupUpdate command, CancellationToken cancellationToken); + Task SendSyncPlayGroupUpdate(string sessionId, GroupUpdate command, CancellationToken cancellationToken); /// /// Sends the browse command. diff --git a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs new file mode 100644 index 0000000000..087748de08 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Class GroupInfo. + /// + /// + /// Class is not thread-safe, external locking is required when accessing methods. + /// + public class GroupInfo + { + /// + /// Default ping value used for sessions. + /// + public readonly long DefaulPing = 500; + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public readonly Guid GroupId = Guid.NewGuid(); + + /// + /// Gets or sets the playing item. + /// + /// The playing item. + public BaseItem PlayingItem { get; set; } + + /// + /// Gets or sets whether playback is paused. + /// + /// Playback is paused. + public bool IsPaused { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long PositionTicks { get; set; } + + /// + /// Gets or sets the last activity. + /// + /// The last activity. + public DateTime LastActivity { get; set; } + + /// + /// Gets the participants. + /// + /// The participants, or members of the group. + public readonly Dictionary Participants = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Checks if a session is in this group. + /// + /// true if the session is in this group; false otherwise. + public bool ContainsSession(string sessionId) + { + return Participants.ContainsKey(sessionId); + } + + /// + /// Adds the session to the group. + /// + /// The session. + public void AddSession(SessionInfo session) + { + if (ContainsSession(session.Id.ToString())) return; + var member = new GroupMember(); + member.Session = session; + member.Ping = DefaulPing; + member.IsBuffering = false; + Participants[session.Id.ToString()] = member; + } + + /// + /// Removes the session from the group. + /// + /// The session. + + public void RemoveSession(SessionInfo session) + { + if (!ContainsSession(session.Id.ToString())) return; + GroupMember member; + Participants.Remove(session.Id.ToString(), out member); + } + + /// + /// Updates the ping of a session. + /// + /// The session. + /// The ping. + public void UpdatePing(SessionInfo session, long ping) + { + if (!ContainsSession(session.Id.ToString())) return; + Participants[session.Id.ToString()].Ping = ping; + } + + /// + /// Gets the highest ping in the group. + /// + /// The highest ping in the group. + public long GetHighestPing() + { + long max = Int64.MinValue; + foreach (var session in Participants.Values) + { + max = Math.Max(max, session.Ping); + } + return max; + } + + /// + /// Sets the session's buffering state. + /// + /// The session. + /// The state. + public void SetBuffering(SessionInfo session, bool isBuffering) + { + if (!ContainsSession(session.Id.ToString())) return; + Participants[session.Id.ToString()].IsBuffering = isBuffering; + } + + /// + /// Gets the group buffering state. + /// + /// true if there is a session buffering in the group; false otherwise. + public bool IsBuffering() + { + foreach (var session in Participants.Values) + { + if (session.IsBuffering) return true; + } + return false; + } + + /// + /// Checks if the group is empty. + /// + /// true if the group is empty; false otherwise. + public bool IsEmpty() + { + return Participants.Count == 0; + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/GroupMember.cs b/MediaBrowser.Controller/SyncPlay/GroupMember.cs new file mode 100644 index 0000000000..a3975c334c --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/GroupMember.cs @@ -0,0 +1,28 @@ +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Class GroupMember. + /// + public class GroupMember + { + /// + /// Gets or sets whether this member is buffering. + /// + /// true if member is buffering; false otherwise. + public bool IsBuffering { get; set; } + + /// + /// Gets or sets the session. + /// + /// The session. + public SessionInfo Session { get; set; } + + /// + /// Gets or sets the ping. + /// + /// The ping. + public long Ping { get; set; } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs new file mode 100644 index 0000000000..de1fcd2591 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs @@ -0,0 +1,67 @@ +using System; +using System.Threading; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.SyncPlay; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Interface ISyncPlayController. + /// + public interface ISyncPlayController + { + /// + /// Gets the group id. + /// + /// The group id. + Guid GetGroupId(); + + /// + /// Gets the playing item id. + /// + /// The playing item id. + Guid GetPlayingItemId(); + + /// + /// Checks if the group is empty. + /// + /// If the group is empty. + bool IsGroupEmpty(); + + /// + /// Initializes the group with the session's info. + /// + /// The session. + /// The cancellation token. + void InitGroup(SessionInfo session, CancellationToken cancellationToken); + + /// + /// Adds the session to the group. + /// + /// The session. + /// The request. + /// The cancellation token. + void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken); + + /// + /// Removes the session from the group. + /// + /// The session. + /// The cancellation token. + void SessionLeave(SessionInfo session, CancellationToken cancellationToken); + + /// + /// Handles the requested action by the session. + /// + /// The session. + /// The requested action. + /// The cancellation token. + void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken); + + /// + /// Gets the info about the group for the clients. + /// + /// The group info for the clients. + GroupInfoView GetInfo(); + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs new file mode 100644 index 0000000000..6c962ec854 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.SyncPlay; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// + /// Interface ISyncPlayManager. + /// + public interface ISyncPlayManager + { + /// + /// Creates a new group. + /// + /// The session that's creating the group. + /// The cancellation token. + void NewGroup(SessionInfo session, CancellationToken cancellationToken); + + /// + /// Adds the session to a group. + /// + /// The session. + /// The group id. + /// The request. + /// The cancellation token. + void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request, CancellationToken cancellationToken); + + /// + /// Removes the session from a group. + /// + /// The session. + /// The cancellation token. + void LeaveGroup(SessionInfo session, CancellationToken cancellationToken); + + /// + /// Gets list of available groups for a session. + /// + /// The session. + /// The item id to filter by. + /// The list of available groups. + List ListGroups(SessionInfo session, Guid filterItemId); + + /// + /// Handle a request by a session in a group. + /// + /// The session. + /// The request. + /// The cancellation token. + void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken); + + /// + /// Maps a session to a group. + /// + /// The session. + /// The group. + /// + void AddSessionToGroup(SessionInfo session, ISyncPlayController group); + + /// + /// Unmaps a session from a group. + /// + /// The session. + /// The group. + /// + void RemoveSessionFromGroup(SessionInfo session, ISyncPlayController group); + } +} diff --git a/MediaBrowser.Controller/Syncplay/GroupInfo.cs b/MediaBrowser.Controller/Syncplay/GroupInfo.cs deleted file mode 100644 index c01fead836..0000000000 --- a/MediaBrowser.Controller/Syncplay/GroupInfo.cs +++ /dev/null @@ -1,150 +0,0 @@ -using System; -using System.Collections.Generic; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Session; - -namespace MediaBrowser.Controller.Syncplay -{ - /// - /// Class GroupInfo. - /// - /// - /// Class is not thread-safe, external locking is required when accessing methods. - /// - public class GroupInfo - { - /// - /// Default ping value used for sessions. - /// - public readonly long DefaulPing = 500; - /// - /// Gets or sets the group identifier. - /// - /// The group identifier. - public readonly Guid GroupId = Guid.NewGuid(); - - /// - /// Gets or sets the playing item. - /// - /// The playing item. - public BaseItem PlayingItem { get; set; } - - /// - /// Gets or sets whether playback is paused. - /// - /// Playback is paused. - public bool IsPaused { get; set; } - - /// - /// Gets or sets the position ticks. - /// - /// The position ticks. - public long PositionTicks { get; set; } - - /// - /// Gets or sets the last activity. - /// - /// The last activity. - public DateTime LastActivity { get; set; } - - /// - /// Gets the participants. - /// - /// The participants, or members of the group. - public readonly Dictionary Participants = - new Dictionary(StringComparer.OrdinalIgnoreCase); - - /// - /// Checks if a session is in this group. - /// - /// true if the session is in this group; false otherwise. - public bool ContainsSession(string sessionId) - { - return Participants.ContainsKey(sessionId); - } - - /// - /// Adds the session to the group. - /// - /// The session. - public void AddSession(SessionInfo session) - { - if (ContainsSession(session.Id.ToString())) return; - var member = new GroupMember(); - member.Session = session; - member.Ping = DefaulPing; - member.IsBuffering = false; - Participants[session.Id.ToString()] = member; - } - - /// - /// Removes the session from the group. - /// - /// The session. - - public void RemoveSession(SessionInfo session) - { - if (!ContainsSession(session.Id.ToString())) return; - GroupMember member; - Participants.Remove(session.Id.ToString(), out member); - } - - /// - /// Updates the ping of a session. - /// - /// The session. - /// The ping. - public void UpdatePing(SessionInfo session, long ping) - { - if (!ContainsSession(session.Id.ToString())) return; - Participants[session.Id.ToString()].Ping = ping; - } - - /// - /// Gets the highest ping in the group. - /// - /// The highest ping in the group. - public long GetHighestPing() - { - long max = Int64.MinValue; - foreach (var session in Participants.Values) - { - max = Math.Max(max, session.Ping); - } - return max; - } - - /// - /// Sets the session's buffering state. - /// - /// The session. - /// The state. - public void SetBuffering(SessionInfo session, bool isBuffering) - { - if (!ContainsSession(session.Id.ToString())) return; - Participants[session.Id.ToString()].IsBuffering = isBuffering; - } - - /// - /// Gets the group buffering state. - /// - /// true if there is a session buffering in the group; false otherwise. - public bool IsBuffering() - { - foreach (var session in Participants.Values) - { - if (session.IsBuffering) return true; - } - return false; - } - - /// - /// Checks if the group is empty. - /// - /// true if the group is empty; false otherwise. - public bool IsEmpty() - { - return Participants.Count == 0; - } - } -} diff --git a/MediaBrowser.Controller/Syncplay/GroupMember.cs b/MediaBrowser.Controller/Syncplay/GroupMember.cs deleted file mode 100644 index 7630428d76..0000000000 --- a/MediaBrowser.Controller/Syncplay/GroupMember.cs +++ /dev/null @@ -1,28 +0,0 @@ -using MediaBrowser.Controller.Session; - -namespace MediaBrowser.Controller.Syncplay -{ - /// - /// Class GroupMember. - /// - public class GroupMember - { - /// - /// Gets or sets whether this member is buffering. - /// - /// true if member is buffering; false otherwise. - public bool IsBuffering { get; set; } - - /// - /// Gets or sets the session. - /// - /// The session. - public SessionInfo Session { get; set; } - - /// - /// Gets or sets the ping. - /// - /// The ping. - public long Ping { get; set; } - } -} diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs deleted file mode 100644 index 34eae40624..0000000000 --- a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Threading; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Syncplay; - -namespace MediaBrowser.Controller.Syncplay -{ - /// - /// Interface ISyncplayController. - /// - public interface ISyncplayController - { - /// - /// Gets the group id. - /// - /// The group id. - Guid GetGroupId(); - - /// - /// Gets the playing item id. - /// - /// The playing item id. - Guid GetPlayingItemId(); - - /// - /// Checks if the group is empty. - /// - /// If the group is empty. - bool IsGroupEmpty(); - - /// - /// Initializes the group with the session's info. - /// - /// The session. - /// The cancellation token. - void InitGroup(SessionInfo session, CancellationToken cancellationToken); - - /// - /// Adds the session to the group. - /// - /// The session. - /// The request. - /// The cancellation token. - void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken); - - /// - /// Removes the session from the group. - /// - /// The session. - /// The cancellation token. - void SessionLeave(SessionInfo session, CancellationToken cancellationToken); - - /// - /// Handles the requested action by the session. - /// - /// The session. - /// The requested action. - /// The cancellation token. - void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken); - - /// - /// Gets the info about the group for the clients. - /// - /// The group info for the clients. - GroupInfoView GetInfo(); - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs deleted file mode 100644 index fbc208d27d..0000000000 --- a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Syncplay; - -namespace MediaBrowser.Controller.Syncplay -{ - /// - /// Interface ISyncplayManager. - /// - public interface ISyncplayManager - { - /// - /// Creates a new group. - /// - /// The session that's creating the group. - /// The cancellation token. - void NewGroup(SessionInfo session, CancellationToken cancellationToken); - - /// - /// Adds the session to a group. - /// - /// The session. - /// The group id. - /// The request. - /// The cancellation token. - void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request, CancellationToken cancellationToken); - - /// - /// Removes the session from a group. - /// - /// The session. - /// The cancellation token. - void LeaveGroup(SessionInfo session, CancellationToken cancellationToken); - - /// - /// Gets list of available groups for a session. - /// - /// The session. - /// The item id to filter by. - /// The list of available groups. - List ListGroups(SessionInfo session, Guid filterItemId); - - /// - /// Handle a request by a session in a group. - /// - /// The session. - /// The request. - /// The cancellation token. - void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken); - - /// - /// Maps a session to a group. - /// - /// The session. - /// The group. - /// - void AddSessionToGroup(SessionInfo session, ISyncplayController group); - - /// - /// Unmaps a session from a group. - /// - /// The session. - /// The group. - /// - void RemoveSessionFromGroup(SessionInfo session, ISyncplayController group); - } -} diff --git a/MediaBrowser.Model/Configuration/SyncplayAccess.cs b/MediaBrowser.Model/Configuration/SyncplayAccess.cs index cddf68c42d..d891a8167a 100644 --- a/MediaBrowser.Model/Configuration/SyncplayAccess.cs +++ b/MediaBrowser.Model/Configuration/SyncplayAccess.cs @@ -1,9 +1,9 @@ namespace MediaBrowser.Model.Configuration { /// - /// Enum SyncplayAccess. + /// Enum SyncPlayAccess. /// - public enum SyncplayAccess + public enum SyncPlayAccess { /// /// User can create groups and join them. @@ -16,7 +16,7 @@ namespace MediaBrowser.Model.Configuration JoinGroups, /// - /// Syncplay is disabled for the user. + /// SyncPlay is disabled for the user. /// None } diff --git a/MediaBrowser.Model/SyncPlay/GroupInfoView.cs b/MediaBrowser.Model/SyncPlay/GroupInfoView.cs new file mode 100644 index 0000000000..7b833506ba --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/GroupInfoView.cs @@ -0,0 +1,38 @@ +namespace MediaBrowser.Model.SyncPlay +{ + /// + /// Class GroupInfoView. + /// + public class GroupInfoView + { + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public string GroupId { get; set; } + + /// + /// Gets or sets the playing item id. + /// + /// The playing item id. + public string PlayingItemId { get; set; } + + /// + /// Gets or sets the playing item name. + /// + /// The playing item name. + public string PlayingItemName { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long PositionTicks { get; set; } + + /// + /// Gets or sets the participants. + /// + /// The participants. + public string[] Participants { get; set; } + } +} diff --git a/MediaBrowser.Model/SyncPlay/GroupUpdate.cs b/MediaBrowser.Model/SyncPlay/GroupUpdate.cs new file mode 100644 index 0000000000..895702f3dd --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/GroupUpdate.cs @@ -0,0 +1,26 @@ +namespace MediaBrowser.Model.SyncPlay +{ + /// + /// Class GroupUpdate. + /// + public class GroupUpdate + { + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public string GroupId { get; set; } + + /// + /// Gets or sets the update type. + /// + /// The update type. + public GroupUpdateType Type { get; set; } + + /// + /// Gets or sets the data. + /// + /// The data. + public T Data { get; set; } + } +} diff --git a/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs b/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs new file mode 100644 index 0000000000..89d2457872 --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs @@ -0,0 +1,53 @@ +namespace MediaBrowser.Model.SyncPlay +{ + /// + /// Enum GroupUpdateType. + /// + public enum GroupUpdateType + { + /// + /// The user-joined update. Tells members of a group about a new user. + /// + UserJoined, + /// + /// The user-left update. Tells members of a group that a user left. + /// + UserLeft, + /// + /// The group-joined update. Tells a user that the group has been joined. + /// + GroupJoined, + /// + /// The group-left update. Tells a user that the group has been left. + /// + GroupLeft, + /// + /// The group-wait update. Tells members of the group that a user is buffering. + /// + GroupWait, + /// + /// The prepare-session update. Tells a user to load some content. + /// + PrepareSession, + /// + /// The not-in-group error. Tells a user that they don't belong to a group. + /// + NotInGroup, + /// + /// The group-does-not-exist error. Sent when trying to join a non-existing group. + /// + GroupDoesNotExist, + /// + /// The create-group-denied error. Sent when a user tries to create a group without required permissions. + /// + CreateGroupDenied, + /// + /// The join-group-denied error. Sent when a user tries to join a group without required permissions. + /// + JoinGroupDenied, + /// + /// The library-access-denied error. Sent when a user tries to join a group without required access to the library. + /// + LibraryAccessDenied + } +} diff --git a/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs b/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs new file mode 100644 index 0000000000..d67b6bd555 --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs @@ -0,0 +1,22 @@ +using System; + +namespace MediaBrowser.Model.SyncPlay +{ + /// + /// Class JoinGroupRequest. + /// + public class JoinGroupRequest + { + /// + /// Gets or sets the Group id. + /// + /// The Group id to join. + public Guid GroupId { get; set; } + + /// + /// Gets or sets the playing item id. + /// + /// The client's currently playing item id. + public Guid PlayingItemId { get; set; } + } +} diff --git a/MediaBrowser.Model/SyncPlay/PlaybackRequest.cs b/MediaBrowser.Model/SyncPlay/PlaybackRequest.cs new file mode 100644 index 0000000000..9de23194e3 --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/PlaybackRequest.cs @@ -0,0 +1,34 @@ +using System; + +namespace MediaBrowser.Model.SyncPlay +{ + /// + /// Class PlaybackRequest. + /// + public class PlaybackRequest + { + /// + /// Gets or sets the request type. + /// + /// The request type. + public PlaybackRequestType Type { get; set; } + + /// + /// Gets or sets when the request has been made by the client. + /// + /// The date of the request. + public DateTime? When { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long? PositionTicks { get; set; } + + /// + /// Gets or sets the ping time. + /// + /// The ping time. + public long? Ping { get; set; } + } +} diff --git a/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs b/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs new file mode 100644 index 0000000000..f1e175fdec --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs @@ -0,0 +1,33 @@ +namespace MediaBrowser.Model.SyncPlay +{ + /// + /// Enum PlaybackRequestType + /// + public enum PlaybackRequestType + { + /// + /// A user is requesting a play command for the group. + /// + Play = 0, + /// + /// A user is requesting a pause command for the group. + /// + Pause = 1, + /// + /// A user is requesting a seek command for the group. + /// + Seek = 2, + /// + /// A user is signaling that playback is buffering. + /// + Buffering = 3, + /// + /// A user is signaling that playback resumed. + /// + BufferingDone = 4, + /// + /// A user is reporting its ping. + /// + UpdatePing = 5 + } +} diff --git a/MediaBrowser.Model/SyncPlay/SendCommand.cs b/MediaBrowser.Model/SyncPlay/SendCommand.cs new file mode 100644 index 0000000000..0f06e381f1 --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/SendCommand.cs @@ -0,0 +1,38 @@ +namespace MediaBrowser.Model.SyncPlay +{ + /// + /// Class SendCommand. + /// + public class SendCommand + { + /// + /// Gets or sets the group identifier. + /// + /// The group identifier. + public string GroupId { get; set; } + + /// + /// Gets or sets the UTC time when to execute the command. + /// + /// The UTC time when to execute the command. + public string When { get; set; } + + /// + /// Gets or sets the position ticks. + /// + /// The position ticks. + public long? PositionTicks { get; set; } + + /// + /// Gets or sets the command. + /// + /// The command. + public SendCommandType Command { get; set; } + + /// + /// Gets or sets the UTC time when this command has been emitted. + /// + /// The UTC time when this command has been emitted. + public string EmittedAt { get; set; } + } +} diff --git a/MediaBrowser.Model/SyncPlay/SendCommandType.cs b/MediaBrowser.Model/SyncPlay/SendCommandType.cs new file mode 100644 index 0000000000..1137198715 --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/SendCommandType.cs @@ -0,0 +1,21 @@ +namespace MediaBrowser.Model.SyncPlay +{ + /// + /// Enum SendCommandType. + /// + public enum SendCommandType + { + /// + /// The play command. Instructs users to start playback. + /// + Play = 0, + /// + /// The pause command. Instructs users to pause playback. + /// + Pause = 1, + /// + /// The seek command. Instructs users to seek to a specified time. + /// + Seek = 2 + } +} diff --git a/MediaBrowser.Model/SyncPlay/UtcTimeResponse.cs b/MediaBrowser.Model/SyncPlay/UtcTimeResponse.cs new file mode 100644 index 0000000000..0a60361544 --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/UtcTimeResponse.cs @@ -0,0 +1,20 @@ +namespace MediaBrowser.Model.SyncPlay +{ + /// + /// Class UtcTimeResponse. + /// + public class UtcTimeResponse + { + /// + /// Gets or sets the UTC time when request has been received. + /// + /// The UTC time when request has been received. + public string RequestReceptionTime { get; set; } + + /// + /// Gets or sets the UTC time when response has been sent. + /// + /// The UTC time when response has been sent. + public string ResponseTransmissionTime { get; set; } + } +} diff --git a/MediaBrowser.Model/Syncplay/GroupInfoView.cs b/MediaBrowser.Model/Syncplay/GroupInfoView.cs deleted file mode 100644 index 50ad70630f..0000000000 --- a/MediaBrowser.Model/Syncplay/GroupInfoView.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Class GroupInfoView. - /// - public class GroupInfoView - { - /// - /// Gets or sets the group identifier. - /// - /// The group identifier. - public string GroupId { get; set; } - - /// - /// Gets or sets the playing item id. - /// - /// The playing item id. - public string PlayingItemId { get; set; } - - /// - /// Gets or sets the playing item name. - /// - /// The playing item name. - public string PlayingItemName { get; set; } - - /// - /// Gets or sets the position ticks. - /// - /// The position ticks. - public long PositionTicks { get; set; } - - /// - /// Gets or sets the participants. - /// - /// The participants. - public string[] Participants { get; set; } - } -} diff --git a/MediaBrowser.Model/Syncplay/GroupUpdate.cs b/MediaBrowser.Model/Syncplay/GroupUpdate.cs deleted file mode 100644 index cc49e92a9c..0000000000 --- a/MediaBrowser.Model/Syncplay/GroupUpdate.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Class GroupUpdate. - /// - public class GroupUpdate - { - /// - /// Gets or sets the group identifier. - /// - /// The group identifier. - public string GroupId { get; set; } - - /// - /// Gets or sets the update type. - /// - /// The update type. - public GroupUpdateType Type { get; set; } - - /// - /// Gets or sets the data. - /// - /// The data. - public T Data { get; set; } - } -} diff --git a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs deleted file mode 100644 index 9f40f9577f..0000000000 --- a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Enum GroupUpdateType. - /// - public enum GroupUpdateType - { - /// - /// The user-joined update. Tells members of a group about a new user. - /// - UserJoined, - /// - /// The user-left update. Tells members of a group that a user left. - /// - UserLeft, - /// - /// The group-joined update. Tells a user that the group has been joined. - /// - GroupJoined, - /// - /// The group-left update. Tells a user that the group has been left. - /// - GroupLeft, - /// - /// The group-wait update. Tells members of the group that a user is buffering. - /// - GroupWait, - /// - /// The prepare-session update. Tells a user to load some content. - /// - PrepareSession, - /// - /// The not-in-group error. Tells a user that they don't belong to a group. - /// - NotInGroup, - /// - /// The group-does-not-exist error. Sent when trying to join a non-existing group. - /// - GroupDoesNotExist, - /// - /// The create-group-denied error. Sent when a user tries to create a group without required permissions. - /// - CreateGroupDenied, - /// - /// The join-group-denied error. Sent when a user tries to join a group without required permissions. - /// - JoinGroupDenied, - /// - /// The library-access-denied error. Sent when a user tries to join a group without required access to the library. - /// - LibraryAccessDenied - } -} diff --git a/MediaBrowser.Model/Syncplay/JoinGroupRequest.cs b/MediaBrowser.Model/Syncplay/JoinGroupRequest.cs deleted file mode 100644 index 8d8a2646ac..0000000000 --- a/MediaBrowser.Model/Syncplay/JoinGroupRequest.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; - -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Class JoinGroupRequest. - /// - public class JoinGroupRequest - { - /// - /// Gets or sets the Group id. - /// - /// The Group id to join. - public Guid GroupId { get; set; } - - /// - /// Gets or sets the playing item id. - /// - /// The client's currently playing item id. - public Guid PlayingItemId { get; set; } - } -} diff --git a/MediaBrowser.Model/Syncplay/PlaybackRequest.cs b/MediaBrowser.Model/Syncplay/PlaybackRequest.cs deleted file mode 100644 index ba97641f63..0000000000 --- a/MediaBrowser.Model/Syncplay/PlaybackRequest.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; - -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Class PlaybackRequest. - /// - public class PlaybackRequest - { - /// - /// Gets or sets the request type. - /// - /// The request type. - public PlaybackRequestType Type { get; set; } - - /// - /// Gets or sets when the request has been made by the client. - /// - /// The date of the request. - public DateTime? When { get; set; } - - /// - /// Gets or sets the position ticks. - /// - /// The position ticks. - public long? PositionTicks { get; set; } - - /// - /// Gets or sets the ping time. - /// - /// The ping time. - public long? Ping { get; set; } - } -} diff --git a/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs b/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs deleted file mode 100644 index b3d49d09ef..0000000000 --- a/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Enum PlaybackRequestType - /// - public enum PlaybackRequestType - { - /// - /// A user is requesting a play command for the group. - /// - Play = 0, - /// - /// A user is requesting a pause command for the group. - /// - Pause = 1, - /// - /// A user is requesting a seek command for the group. - /// - Seek = 2, - /// - /// A user is signaling that playback is buffering. - /// - Buffering = 3, - /// - /// A user is signaling that playback resumed. - /// - BufferingDone = 4, - /// - /// A user is reporting its ping. - /// - UpdatePing = 5 - } -} diff --git a/MediaBrowser.Model/Syncplay/SendCommand.cs b/MediaBrowser.Model/Syncplay/SendCommand.cs deleted file mode 100644 index d9f3914030..0000000000 --- a/MediaBrowser.Model/Syncplay/SendCommand.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Class SendCommand. - /// - public class SendCommand - { - /// - /// Gets or sets the group identifier. - /// - /// The group identifier. - public string GroupId { get; set; } - - /// - /// Gets or sets the UTC time when to execute the command. - /// - /// The UTC time when to execute the command. - public string When { get; set; } - - /// - /// Gets or sets the position ticks. - /// - /// The position ticks. - public long? PositionTicks { get; set; } - - /// - /// Gets or sets the command. - /// - /// The command. - public SendCommandType Command { get; set; } - - /// - /// Gets or sets the UTC time when this command has been emitted. - /// - /// The UTC time when this command has been emitted. - public string EmittedAt { get; set; } - } -} diff --git a/MediaBrowser.Model/Syncplay/SendCommandType.cs b/MediaBrowser.Model/Syncplay/SendCommandType.cs deleted file mode 100644 index 02e4774d0d..0000000000 --- a/MediaBrowser.Model/Syncplay/SendCommandType.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Enum SendCommandType. - /// - public enum SendCommandType - { - /// - /// The play command. Instructs users to start playback. - /// - Play = 0, - /// - /// The pause command. Instructs users to pause playback. - /// - Pause = 1, - /// - /// The seek command. Instructs users to seek to a specified time. - /// - Seek = 2 - } -} diff --git a/MediaBrowser.Model/Syncplay/UtcTimeResponse.cs b/MediaBrowser.Model/Syncplay/UtcTimeResponse.cs deleted file mode 100644 index f7887dc332..0000000000 --- a/MediaBrowser.Model/Syncplay/UtcTimeResponse.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace MediaBrowser.Model.Syncplay -{ - /// - /// Class UtcTimeResponse. - /// - public class UtcTimeResponse - { - /// - /// Gets or sets the UTC time when request has been received. - /// - /// The UTC time when request has been received. - public string RequestReceptionTime { get; set; } - - /// - /// Gets or sets the UTC time when response has been sent. - /// - /// The UTC time when response has been sent. - public string ResponseTransmissionTime { get; set; } - } -} diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index cf576c3582..3e027e8312 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -81,10 +81,10 @@ namespace MediaBrowser.Model.Users public string PasswordResetProviderId { get; set; } /// - /// Gets or sets a value indicating what Syncplay features the user can access. + /// Gets or sets a value indicating what SyncPlay features the user can access. /// - /// Access level to Syncplay features. - public SyncplayAccess SyncplayAccess { get; set; } + /// Access level to SyncPlay features. + public SyncPlayAccess SyncPlayAccess { get; set; } public UserPolicy() { @@ -131,7 +131,7 @@ namespace MediaBrowser.Model.Users EnableContentDownloading = true; EnablePublicSharing = true; EnableRemoteAccess = true; - SyncplayAccess = SyncplayAccess.CreateAndJoinGroups; + SyncPlayAccess = SyncPlayAccess.CreateAndJoinGroups; } } } -- cgit v1.2.3 From 5c8cbd4087261f13d003d7d4eab082cbf335b4d4 Mon Sep 17 00:00:00 2001 From: gion Date: Sat, 9 May 2020 14:34:07 +0200 Subject: Fix code issues --- .../Session/SessionWebSocketListener.cs | 21 +++++----- .../SyncPlay/SyncPlayController.cs | 4 +- .../SyncPlay/SyncPlayManager.cs | 28 +++++++++---- MediaBrowser.Api/SyncPlay/SyncPlayService.cs | 47 +++++++++++----------- .../Net/IWebSocketConnection.cs | 2 +- MediaBrowser.Controller/SyncPlay/GroupInfo.cs | 32 +++++++++++---- .../SyncPlay/ISyncPlayManager.cs | 2 +- MediaBrowser.Model/SyncPlay/GroupInfoView.cs | 4 +- 8 files changed, 86 insertions(+), 54 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index d1ee22ea86..3704445abe 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -22,17 +22,17 @@ namespace Emby.Server.Implementations.Session /// /// The timeout in seconds after which a WebSocket is considered to be lost. /// - public readonly int WebSocketLostTimeout = 60; + public const int WebSocketLostTimeout = 60; /// /// The keep-alive interval factor; controls how often the watcher will check on the status of the WebSockets. /// - public readonly double IntervalFactor = 0.2; + public const float IntervalFactor = 0.2f; /// /// The ForceKeepAlive factor; controls when a ForceKeepAlive is sent. /// - public readonly double ForceKeepAliveFactor = 0.75; + public const float ForceKeepAliveFactor = 0.75f; /// /// The _session manager @@ -213,7 +213,7 @@ namespace Emby.Server.Implementations.Session { _keepAliveCancellationToken = new CancellationTokenSource(); // Start KeepAlive watcher - var task = RepeatAsyncCallbackEvery( + _ = RepeatAsyncCallbackEvery( KeepAliveSockets, TimeSpan.FromSeconds(WebSocketLostTimeout * IntervalFactor), _keepAliveCancellationToken.Token); @@ -241,6 +241,7 @@ namespace Emby.Server.Implementations.Session { webSocket.Closed -= OnWebSocketClosed; } + _webSockets.Clear(); } } @@ -250,8 +251,8 @@ namespace Emby.Server.Implementations.Session /// private async Task KeepAliveSockets() { - IEnumerable inactive; - IEnumerable lost; + List inactive; + List lost; lock (_webSocketsLock) { @@ -261,8 +262,8 @@ namespace Emby.Server.Implementations.Session { var elapsed = (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds; return (elapsed > WebSocketLostTimeout * ForceKeepAliveFactor) && (elapsed < WebSocketLostTimeout); - }); - lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout); + }).ToList(); + lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout).ToList(); } if (inactive.Any()) @@ -279,7 +280,7 @@ namespace Emby.Server.Implementations.Session catch (WebSocketException exception) { _logger.LogInformation(exception, "Error sending ForceKeepAlive message to WebSocket."); - lost = lost.Append(webSocket); + lost.Add(webSocket); } } @@ -288,7 +289,7 @@ namespace Emby.Server.Implementations.Session if (lost.Any()) { _logger.LogInformation("Lost {0} WebSockets.", lost.Count()); - foreach (var webSocket in lost.ToList()) + foreach (var webSocket in lost) { // TODO: handle session relative to the lost webSocket RemoveWebSocket(webSocket); diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs index 9c9758de1c..c7bd242a7c 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs @@ -144,7 +144,7 @@ namespace Emby.Server.Implementations.SyncPlay session => session.Session ).ToArray(); default: - return new SessionInfo[] { }; + return Array.Empty(); } } @@ -541,7 +541,7 @@ namespace Emby.Server.Implementations.SyncPlay PlayingItemName = _group.PlayingItem.Name, PlayingItemId = _group.PlayingItem.Id.ToString(), PositionTicks = _group.PositionTicks, - Participants = _group.Participants.Values.Select(session => session.Session.UserName).Distinct().ToArray() + Participants = _group.Participants.Values.Select(session => session.Session.UserName).Distinct().ToList().AsReadOnly() }; } } diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index d3197d97b8..93cec1304c 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -47,8 +47,8 @@ namespace Emby.Server.Implementations.SyncPlay /// /// The groups. /// - private readonly Dictionary _groups = - new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _groups = + new Dictionary(); /// /// Lock used for accesing any group. @@ -113,14 +113,22 @@ namespace Emby.Server.Implementations.SyncPlay private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e) { var session = e.SessionInfo; - if (!IsSessionInGroup(session)) return; + if (!IsSessionInGroup(session)) + { + return; + } + LeaveGroup(session, CancellationToken.None); } private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e) { var session = e.Session; - if (!IsSessionInGroup(session)) return; + if (!IsSessionInGroup(session)) + { + return; + } + LeaveGroup(session, CancellationToken.None); } @@ -193,14 +201,14 @@ namespace Emby.Server.Implementations.SyncPlay } var group = new SyncPlayController(_sessionManager, this); - _groups[group.GetGroupId().ToString()] = group; + _groups[group.GetGroupId()] = group; group.InitGroup(session, cancellationToken); } } /// - public void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request, CancellationToken cancellationToken) + public void JoinGroup(SessionInfo session, Guid groupId, JoinGroupRequest request, CancellationToken cancellationToken) { var user = _userManager.GetUserById(session.UserId); @@ -248,7 +256,11 @@ namespace Emby.Server.Implementations.SyncPlay if (IsSessionInGroup(session)) { - if (GetSessionGroup(session).Equals(groupId)) return; + if (GetSessionGroup(session).Equals(groupId)) + { + return; + } + LeaveGroup(session, cancellationToken); } @@ -282,7 +294,7 @@ namespace Emby.Server.Implementations.SyncPlay if (group.IsGroupEmpty()) { _logger.LogInformation("LeaveGroup: removing empty group {0}.", group.GetGroupId()); - _groups.Remove(group.GetGroupId().ToString(), out _); + _groups.Remove(group.GetGroupId(), out _); } } } diff --git a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs index bcdc833e40..9137faf9f4 100644 --- a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs +++ b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs @@ -171,31 +171,35 @@ namespace MediaBrowser.Api.SyncPlay public void Post(SyncPlayJoinGroup request) { var currentSession = GetSession(_sessionContext); - var joinRequest = new JoinGroupRequest() + + Guid groupId; + Guid playingItemId = Guid.Empty; + + var valid = Guid.TryParse(request.GroupId, out groupId); + if (!valid) { - GroupId = Guid.Parse(request.GroupId) - }; + Logger.LogError("JoinGroup: {0} is not a valid format for GroupId. Ignoring request.", request.GroupId); + return; + } // Both null and empty strings mean that client isn't playing anything if (!String.IsNullOrEmpty(request.PlayingItemId)) { - try - { - joinRequest.PlayingItemId = Guid.Parse(request.PlayingItemId); - } - catch (ArgumentNullException) - { - // Should never happen, but just in case - Logger.LogError("JoinGroup: null value for PlayingItemId. Ignoring request."); - return; - } - catch (FormatException) + valid = Guid.TryParse(request.PlayingItemId, out playingItemId); + if (!valid) { Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId); return; } } - _syncPlayManager.JoinGroup(currentSession, request.GroupId, joinRequest, CancellationToken.None); + + var joinRequest = new JoinGroupRequest() + { + GroupId = groupId, + PlayingItemId = playingItemId + }; + + _syncPlayManager.JoinGroup(currentSession, groupId, joinRequest, CancellationToken.None); } /// @@ -217,21 +221,16 @@ namespace MediaBrowser.Api.SyncPlay { var currentSession = GetSession(_sessionContext); var filterItemId = Guid.Empty; + if (!String.IsNullOrEmpty(request.FilterItemId)) { - try - { - filterItemId = Guid.Parse(request.FilterItemId); - } - catch (ArgumentNullException) - { - Logger.LogWarning("ListGroups: null value for FilterItemId. Ignoring filter."); - } - catch (FormatException) + var valid = Guid.TryParse(request.FilterItemId, out filterItemId); + if (!valid) { Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); } } + return _syncPlayManager.ListGroups(currentSession, filterItemId); } diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs index fb766ab57f..b371a59e97 100644 --- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs +++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs @@ -30,7 +30,7 @@ namespace MediaBrowser.Controller.Net /// Gets or sets the date of last Keeplive received. /// /// The date of last Keeplive received. - public DateTime LastKeepAliveDate { get; set; } + DateTime LastKeepAliveDate { get; set; } /// /// Gets or sets the URL. diff --git a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs index 087748de08..bda49bd1b0 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs @@ -69,7 +69,11 @@ namespace MediaBrowser.Controller.SyncPlay /// The session. public void AddSession(SessionInfo session) { - if (ContainsSession(session.Id.ToString())) return; + if (ContainsSession(session.Id.ToString())) + { + return; + } + var member = new GroupMember(); member.Session = session; member.Ping = DefaulPing; @@ -84,9 +88,12 @@ namespace MediaBrowser.Controller.SyncPlay public void RemoveSession(SessionInfo session) { - if (!ContainsSession(session.Id.ToString())) return; - GroupMember member; - Participants.Remove(session.Id.ToString(), out member); + if (!ContainsSession(session.Id.ToString())) + { + return; + } + + Participants.Remove(session.Id.ToString(), out _); } /// @@ -96,7 +103,11 @@ namespace MediaBrowser.Controller.SyncPlay /// The ping. public void UpdatePing(SessionInfo session, long ping) { - if (!ContainsSession(session.Id.ToString())) return; + if (!ContainsSession(session.Id.ToString())) + { + return; + } + Participants[session.Id.ToString()].Ping = ping; } @@ -121,7 +132,11 @@ namespace MediaBrowser.Controller.SyncPlay /// The state. public void SetBuffering(SessionInfo session, bool isBuffering) { - if (!ContainsSession(session.Id.ToString())) return; + if (!ContainsSession(session.Id.ToString())) + { + return; + } + Participants[session.Id.ToString()].IsBuffering = isBuffering; } @@ -133,7 +148,10 @@ namespace MediaBrowser.Controller.SyncPlay { foreach (var session in Participants.Values) { - if (session.IsBuffering) return true; + if (session.IsBuffering) + { + return true; + } } return false; } diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs index 6c962ec854..006fb687b8 100644 --- a/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Controller.SyncPlay /// The group id. /// The request. /// The cancellation token. - void JoinGroup(SessionInfo session, string groupId, JoinGroupRequest request, CancellationToken cancellationToken); + void JoinGroup(SessionInfo session, Guid groupId, JoinGroupRequest request, CancellationToken cancellationToken); /// /// Removes the session from a group. diff --git a/MediaBrowser.Model/SyncPlay/GroupInfoView.cs b/MediaBrowser.Model/SyncPlay/GroupInfoView.cs index 7b833506ba..f28ecf16df 100644 --- a/MediaBrowser.Model/SyncPlay/GroupInfoView.cs +++ b/MediaBrowser.Model/SyncPlay/GroupInfoView.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + namespace MediaBrowser.Model.SyncPlay { /// @@ -33,6 +35,6 @@ namespace MediaBrowser.Model.SyncPlay /// Gets or sets the participants. /// /// The participants. - public string[] Participants { get; set; } + public IReadOnlyList Participants { get; set; } } } -- cgit v1.2.3 From 9ad839c7766bd5d6121a10b2c306d6fef9666c52 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 12 May 2020 22:10:35 -0400 Subject: Initial migration code --- Emby.Dlna/ContentDirectory/ContentDirectory.cs | 13 +- Emby.Dlna/ContentDirectory/ControlHandler.cs | 89 +- Emby.Dlna/Didl/DidlBuilder.cs | 11 +- Emby.Dlna/PlayTo/PlayToController.cs | 8 +- Emby.Drawing/ImageProcessor.cs | 9 + Emby.Notifications/Api/NotificationsService.cs | 6 +- Emby.Notifications/NotificationManager.cs | 13 +- .../Activity/ActivityLogEntryPoint.cs | 42 +- Emby.Server.Implementations/ApplicationHost.cs | 15 +- .../Channels/ChannelManager.cs | 7 +- .../Collections/CollectionManager.cs | 4 +- .../Data/SqliteDisplayPreferencesRepository.cs | 225 ---- .../Data/SqliteItemRepository.cs | 4 +- .../Data/SqliteUserDataRepository.cs | 379 ------- .../Data/SqliteUserRepository.cs | 240 ----- .../Devices/DeviceManager.cs | 26 +- Emby.Server.Implementations/Dto/DtoService.cs | 23 +- .../EntryPoints/LibraryChangedNotifier.cs | 2 +- .../EntryPoints/RecordingNotifier.cs | 3 +- .../EntryPoints/RefreshUsersMetadata.cs | 77 -- .../EntryPoints/ServerEventNotifier.cs | 29 +- .../HttpServer/Security/AuthService.cs | 24 +- .../HttpServer/Security/AuthorizationContext.cs | 4 +- .../HttpServer/Security/SessionContext.cs | 4 +- .../Library/DefaultAuthenticationProvider.cs | 177 ---- .../Library/DefaultPasswordResetProvider.cs | 140 --- .../Library/InvalidAuthProvider.cs | 54 - .../Library/LibraryManager.cs | 22 +- .../Library/MediaSourceManager.cs | 52 +- .../Library/MediaStreamSelector.cs | 1 + .../Library/MusicManager.cs | 32 +- .../Library/SearchEngine.cs | 4 +- .../Library/UserDataManager.cs | 10 +- Emby.Server.Implementations/Library/UserManager.cs | 1107 -------------------- .../Library/UserViewManager.cs | 29 +- .../LiveTv/LiveTvManager.cs | 40 +- .../Playlists/ManualPlaylistsFolder.cs | 6 +- .../Playlists/PlaylistManager.cs | 4 +- .../Session/SessionManager.cs | 91 +- .../Sorting/DateLastMediaAddedComparer.cs | 2 +- .../Sorting/DatePlayedComparer.cs | 2 +- .../Sorting/IsFavoriteOrLikeComparer.cs | 2 +- .../Sorting/IsPlayedComparer.cs | 2 +- .../Sorting/IsUnplayedComparer.cs | 2 +- .../Sorting/PlayCountComparer.cs | 2 +- Emby.Server.Implementations/TV/TVSeriesManager.cs | 13 +- Jellyfin.Api/Auth/CustomAuthenticationHandler.cs | 5 +- Jellyfin.Api/Controllers/StartupController.cs | 4 +- Jellyfin.Data/DayOfWeekHelper.cs | 70 ++ Jellyfin.Data/Entities/AccessSchedule.cs | 68 ++ Jellyfin.Data/Entities/ImageInfo.cs | 25 + Jellyfin.Data/Entities/User.cs | 284 +++-- Jellyfin.Data/Enums/DynamicDayOfWeek.cs | 18 + Jellyfin.Data/Enums/PermissionKind.cs | 11 +- Jellyfin.Data/Enums/PreferenceKind.cs | 13 +- Jellyfin.Data/Enums/SubtitlePlaybackMode.cs | 13 + Jellyfin.Data/Enums/UnratedItem.cs | 17 + Jellyfin.Server.Implementations/JellyfinDb.cs | 2 +- .../User/DefaultAuthenticationProvider.cs | 185 ++++ .../User/DefaultPasswordResetProvider.cs | 126 +++ .../User/DeviceAccessEntryPoint.cs | 65 ++ .../User/InvalidAuthProvider.cs | 47 + .../User/UserManager.cs | 749 +++++++++++++ .../Migrations/Routines/MigrateUserDb.cs | 8 + MediaBrowser.Api/BaseApiService.cs | 5 +- MediaBrowser.Api/FilterService.cs | 2 +- MediaBrowser.Api/Images/ImageService.cs | 147 ++- MediaBrowser.Api/Library/LibraryService.cs | 8 +- MediaBrowser.Api/LiveTv/LiveTvService.cs | 3 +- MediaBrowser.Api/Movies/MoviesService.cs | 28 +- MediaBrowser.Api/Music/InstantMixService.cs | 2 +- MediaBrowser.Api/Playback/BaseStreamingService.cs | 3 +- MediaBrowser.Api/Playback/MediaInfoService.cs | 36 +- MediaBrowser.Api/Sessions/SessionService.cs | 5 +- MediaBrowser.Api/SuggestionsService.cs | 2 +- MediaBrowser.Api/TvShowsService.cs | 8 +- .../UserLibrary/BaseItemsByNameService.cs | 4 +- MediaBrowser.Api/UserLibrary/ItemsService.cs | 20 +- MediaBrowser.Api/UserLibrary/PlaystateService.cs | 2 +- MediaBrowser.Api/UserLibrary/UserLibraryService.cs | 2 +- MediaBrowser.Api/UserService.cs | 36 +- .../Authentication/IAuthenticationProvider.cs | 2 +- .../Authentication/IPasswordResetProvider.cs | 2 +- MediaBrowser.Controller/Channels/Channel.cs | 13 +- .../Collections/ICollectionManager.cs | 2 +- MediaBrowser.Controller/Devices/IDeviceManager.cs | 2 +- MediaBrowser.Controller/Drawing/IImageProcessor.cs | 3 + .../Drawing/ImageProcessorExtensions.cs | 1 + MediaBrowser.Controller/Dto/IDtoService.cs | 8 +- MediaBrowser.Controller/Entities/Audio/Audio.cs | 1 + .../Entities/Audio/MusicAlbum.cs | 8 +- .../Entities/Audio/MusicArtist.cs | 8 +- MediaBrowser.Controller/Entities/AudioBook.cs | 1 + MediaBrowser.Controller/Entities/BaseItem.cs | 69 +- MediaBrowser.Controller/Entities/Book.cs | 1 + .../Entities/DayOfWeekHelper.cs | 71 -- MediaBrowser.Controller/Entities/Folder.cs | 55 +- .../Entities/InternalItemsQuery.cs | 18 +- MediaBrowser.Controller/Entities/Movies/BoxSet.cs | 19 +- MediaBrowser.Controller/Entities/Movies/Movie.cs | 1 + MediaBrowser.Controller/Entities/MusicVideo.cs | 1 + MediaBrowser.Controller/Entities/TV/Episode.cs | 1 + MediaBrowser.Controller/Entities/TV/Season.cs | 17 +- MediaBrowser.Controller/Entities/TV/Series.cs | 43 +- MediaBrowser.Controller/Entities/Trailer.cs | 1 + MediaBrowser.Controller/Entities/User.cs | 262 ----- MediaBrowser.Controller/Entities/UserRootFolder.cs | 4 +- MediaBrowser.Controller/Entities/UserView.cs | 8 +- .../Entities/UserViewBuilder.cs | 86 +- MediaBrowser.Controller/Library/IIntroProvider.cs | 2 +- MediaBrowser.Controller/Library/ILibraryManager.cs | 12 +- .../Library/IMediaSourceManager.cs | 6 +- MediaBrowser.Controller/Library/IMusicManager.cs | 6 +- .../Library/IUserDataManager.cs | 8 +- MediaBrowser.Controller/Library/IUserManager.cs | 125 +-- .../Library/PlaybackProgressEventArgs.cs | 4 +- MediaBrowser.Controller/LiveTv/ILiveTvManager.cs | 12 +- MediaBrowser.Controller/LiveTv/LiveTvChannel.cs | 1 + MediaBrowser.Controller/LiveTv/LiveTvProgram.cs | 1 + .../MediaEncoding/EncodingHelper.cs | 7 +- .../MediaEncoding/EncodingJobInfo.cs | 17 +- MediaBrowser.Controller/Net/AuthorizationInfo.cs | 10 +- MediaBrowser.Controller/Net/IAuthService.cs | 2 +- MediaBrowser.Controller/Net/ISessionContext.cs | 2 +- .../Notifications/INotificationService.cs | 2 +- .../Notifications/UserNotification.cs | 2 +- .../Persistence/IUserRepository.cs | 27 - MediaBrowser.Controller/Playlists/Playlist.cs | 26 +- .../Providers/IProviderManager.cs | 3 + MediaBrowser.Controller/Session/ISessionManager.cs | 2 +- .../Sorting/IUserBaseItemComparer.cs | 2 +- MediaBrowser.Model/Configuration/AccessSchedule.cs | 2 + .../Configuration/DynamicDayOfWeek.cs | 18 - .../Configuration/SubtitlePlaybackMode.cs | 13 - MediaBrowser.Model/Configuration/UnratedItem.cs | 17 - .../Configuration/UserConfiguration.cs | 1 + .../Notifications/NotificationOptions.cs | 5 +- MediaBrowser.Model/Users/UserPolicy.cs | 7 +- MediaBrowser.Providers/Manager/ImageSaver.cs | 27 +- MediaBrowser.Providers/Manager/ProviderManager.cs | 14 +- .../Users/UserMetadataService.cs | 30 - .../Auth/CustomAuthenticationHandlerTests.cs | 13 +- 142 files changed, 2531 insertions(+), 3677 deletions(-) delete mode 100644 Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs delete mode 100644 Emby.Server.Implementations/Data/SqliteUserDataRepository.cs delete mode 100644 Emby.Server.Implementations/Data/SqliteUserRepository.cs delete mode 100644 Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs delete mode 100644 Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs delete mode 100644 Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs delete mode 100644 Emby.Server.Implementations/Library/InvalidAuthProvider.cs delete mode 100644 Emby.Server.Implementations/Library/UserManager.cs create mode 100644 Jellyfin.Data/DayOfWeekHelper.cs create mode 100644 Jellyfin.Data/Entities/AccessSchedule.cs create mode 100644 Jellyfin.Data/Entities/ImageInfo.cs create mode 100644 Jellyfin.Data/Enums/DynamicDayOfWeek.cs create mode 100644 Jellyfin.Data/Enums/SubtitlePlaybackMode.cs create mode 100644 Jellyfin.Data/Enums/UnratedItem.cs create mode 100644 Jellyfin.Server.Implementations/User/DefaultAuthenticationProvider.cs create mode 100644 Jellyfin.Server.Implementations/User/DefaultPasswordResetProvider.cs create mode 100644 Jellyfin.Server.Implementations/User/DeviceAccessEntryPoint.cs create mode 100644 Jellyfin.Server.Implementations/User/InvalidAuthProvider.cs create mode 100644 Jellyfin.Server.Implementations/User/UserManager.cs create mode 100644 Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs delete mode 100644 MediaBrowser.Controller/Entities/DayOfWeekHelper.cs delete mode 100644 MediaBrowser.Controller/Entities/User.cs delete mode 100644 MediaBrowser.Controller/Persistence/IUserRepository.cs delete mode 100644 MediaBrowser.Model/Configuration/DynamicDayOfWeek.cs delete mode 100644 MediaBrowser.Model/Configuration/SubtitlePlaybackMode.cs delete mode 100644 MediaBrowser.Model/Configuration/UnratedItem.cs delete mode 100644 MediaBrowser.Providers/Users/UserMetadataService.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Dlna/ContentDirectory/ContentDirectory.cs b/Emby.Dlna/ContentDirectory/ContentDirectory.cs index 64cd308a2a..ea577a9057 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectory.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectory.cs @@ -1,8 +1,10 @@ #pragma warning disable CS1591 using System; +using System.Linq; using System.Threading.Tasks; using Emby.Dlna.Service; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; @@ -104,7 +106,7 @@ namespace Emby.Dlna.ContentDirectory .ProcessControlRequestAsync(request); } - private User GetUser(DeviceProfile profile) + private Jellyfin.Data.Entities.User GetUser(DeviceProfile profile) { if (!string.IsNullOrEmpty(profile.UserId)) { @@ -130,18 +132,13 @@ namespace Emby.Dlna.ContentDirectory foreach (var user in _userManager.Users) { - if (user.Policy.IsAdministrator) + if (user.HasPermission(PermissionKind.IsAdministrator)) { return user; } } - foreach (var user in _userManager.Users) - { - return user; - } - - return null; + return _userManager.Users.FirstOrDefault(); } } } diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 28888f031a..32bda2fe1d 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -36,7 +36,7 @@ namespace Emby.Dlna.ContentDirectory private readonly ILibraryManager _libraryManager; private readonly IUserDataManager _userDataManager; private readonly IServerConfigurationManager _config; - private readonly User _user; + private readonly Jellyfin.Data.Entities.User _user; private readonly IUserViewManager _userViewManager; private readonly ITVSeriesManager _tvSeriesManager; @@ -59,7 +59,7 @@ namespace Emby.Dlna.ContentDirectory string accessToken, IImageProcessor imageProcessor, IUserDataManager userDataManager, - User user, + Jellyfin.Data.Entities.User user, int systemUpdateId, IServerConfigurationManager config, ILocalizationManager localization, @@ -432,7 +432,7 @@ namespace Emby.Dlna.ContentDirectory xmlWriter.WriteElementString("UpdateID", _systemUpdateId.ToString(CultureInfo.InvariantCulture)); } - private QueryResult GetChildrenSorted(BaseItem item, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetChildrenSorted(BaseItem item, Jellyfin.Data.Entities.User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit) { var folder = (Folder)item; @@ -489,7 +489,7 @@ namespace Emby.Dlna.ContentDirectory return new DtoOptions(true); } - private QueryResult GetUserItems(BaseItem item, StubType? stubType, User user, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetUserItems(BaseItem item, StubType? stubType, Jellyfin.Data.Entities.User user, SortCriteria sort, int? startIndex, int? limit) { if (item is MusicGenre) { @@ -558,7 +558,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(queryResult); } - private QueryResult GetLiveTvChannels(User user, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetLiveTvChannels(Jellyfin.Data.Entities.User user, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) { @@ -574,7 +574,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMusicFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetMusicFolders(BaseItem item, Jellyfin.Data.Entities.User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) { @@ -692,7 +692,7 @@ namespace Emby.Dlna.ContentDirectory }; } - private QueryResult GetMovieFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetMovieFolders(BaseItem item, Jellyfin.Data.Entities.User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) { @@ -731,7 +731,7 @@ namespace Emby.Dlna.ContentDirectory return GetGenres(item, user, query); } - var array = new ServerItem[] + var array = new[] { new ServerItem(item) { @@ -766,7 +766,7 @@ namespace Emby.Dlna.ContentDirectory }; } - private QueryResult GetFolders(User user, int? startIndex, int? limit) + private QueryResult GetFolders(Jellyfin.Data.Entities.User user, int? startIndex, int? limit) { var folders = _libraryManager.GetUserRootFolder().GetChildren(user, true) .OrderBy(i => i.SortName) @@ -783,7 +783,7 @@ namespace Emby.Dlna.ContentDirectory }, startIndex, limit); } - private QueryResult GetTvFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetTvFolders(BaseItem item, Jellyfin.Data.Entities.User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) { @@ -871,7 +871,7 @@ namespace Emby.Dlna.ContentDirectory }; } - private QueryResult GetMovieContinueWatching(BaseItem parent, User user, InternalItemsQuery query) + private QueryResult GetMovieContinueWatching(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -891,7 +891,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetSeries(BaseItem parent, User user, InternalItemsQuery query) + private QueryResult GetSeries(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -904,7 +904,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMovieMovies(BaseItem parent, User user, InternalItemsQuery query) + private QueryResult GetMovieMovies(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -917,7 +917,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMovieCollections(User user, InternalItemsQuery query) + private QueryResult GetMovieCollections(Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.Recursive = true; //query.Parent = parent; @@ -930,7 +930,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMusicAlbums(BaseItem parent, User user, InternalItemsQuery query) + private QueryResult GetMusicAlbums(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -943,7 +943,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMusicSongs(BaseItem parent, User user, InternalItemsQuery query) + private QueryResult GetMusicSongs(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -956,7 +956,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetFavoriteSongs(BaseItem parent, User user, InternalItemsQuery query) + private QueryResult GetFavoriteSongs(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -969,7 +969,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetFavoriteSeries(BaseItem parent, User user, InternalItemsQuery query) + private QueryResult GetFavoriteSeries(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -982,7 +982,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetFavoriteEpisodes(BaseItem parent, User user, InternalItemsQuery query) + private QueryResult GetFavoriteEpisodes(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -995,7 +995,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMovieFavorites(BaseItem parent, User user, InternalItemsQuery query) + private QueryResult GetMovieFavorites(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -1008,7 +1008,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetFavoriteAlbums(BaseItem parent, User user, InternalItemsQuery query) + private QueryResult GetFavoriteAlbums(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -1021,7 +1021,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetGenres(BaseItem parent, User user, InternalItemsQuery query) + private QueryResult GetGenres(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { var genresResult = _libraryManager.GetGenres(new InternalItemsQuery(user) { @@ -1039,7 +1039,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMusicGenres(BaseItem parent, User user, InternalItemsQuery query) + private QueryResult GetMusicGenres(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { var genresResult = _libraryManager.GetMusicGenres(new InternalItemsQuery(user) { @@ -1057,7 +1057,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMusicAlbumArtists(BaseItem parent, User user, InternalItemsQuery query) + private QueryResult GetMusicAlbumArtists(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { var artists = _libraryManager.GetAlbumArtists(new InternalItemsQuery(user) { @@ -1075,7 +1075,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMusicArtists(BaseItem parent, User user, InternalItemsQuery query) + private QueryResult GetMusicArtists(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { var artists = _libraryManager.GetArtists(new InternalItemsQuery(user) { @@ -1093,7 +1093,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetFavoriteArtists(BaseItem parent, User user, InternalItemsQuery query) + private QueryResult GetFavoriteArtists(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { var artists = _libraryManager.GetArtists(new InternalItemsQuery(user) { @@ -1112,10 +1112,10 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMusicPlaylists(User user, InternalItemsQuery query) + private QueryResult GetMusicPlaylists(Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.Parent = null; - query.IncludeItemTypes = new[] { typeof(Playlist).Name }; + query.IncludeItemTypes = new[] { nameof(Playlist) }; query.SetUser(user); query.Recursive = true; @@ -1124,7 +1124,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMusicLatest(BaseItem parent, User user, InternalItemsQuery query) + private QueryResult GetMusicLatest(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.OrderBy = Array.Empty<(string, SortOrder)>(); @@ -1132,10 +1132,9 @@ namespace Emby.Dlna.ContentDirectory { UserId = user.Id, Limit = 50, - IncludeItemTypes = new[] { typeof(Audio).Name }, - ParentId = parent == null ? Guid.Empty : parent.Id, + IncludeItemTypes = new[] { nameof(Audio) }, + ParentId = parent?.Id ?? Guid.Empty, GroupItems = true - }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); return ToResult(items); @@ -1150,13 +1149,12 @@ namespace Emby.Dlna.ContentDirectory Limit = query.Limit, StartIndex = query.StartIndex, UserId = query.User.Id - }, new[] { parent }, query.DtoOptions); return ToResult(result); } - private QueryResult GetTvLatest(BaseItem parent, User user, InternalItemsQuery query) + private QueryResult GetTvLatest(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.OrderBy = Array.Empty<(string, SortOrder)>(); @@ -1167,30 +1165,29 @@ namespace Emby.Dlna.ContentDirectory IncludeItemTypes = new[] { typeof(Episode).Name }, ParentId = parent == null ? Guid.Empty : parent.Id, GroupItems = false - }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); return ToResult(items); } - private QueryResult GetMovieLatest(BaseItem parent, User user, InternalItemsQuery query) + private QueryResult GetMovieLatest(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.OrderBy = Array.Empty<(string, SortOrder)>(); - var items = _userViewManager.GetLatestItems(new LatestItemsQuery + var items = _userViewManager.GetLatestItems( + new LatestItemsQuery { UserId = user.Id, Limit = 50, - IncludeItemTypes = new[] { typeof(Movie).Name }, - ParentId = parent == null ? Guid.Empty : parent.Id, + IncludeItemTypes = new[] { nameof(Movie) }, + ParentId = parent?.Id ?? Guid.Empty, GroupItems = true - }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); return ToResult(items); } - private QueryResult GetMusicArtistItems(BaseItem item, Guid parentId, User user, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetMusicArtistItems(BaseItem item, Guid parentId, Jellyfin.Data.Entities.User user, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) { @@ -1210,14 +1207,18 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetGenreItems(BaseItem item, Guid parentId, User user, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetGenreItems(BaseItem item, Guid parentId, Jellyfin.Data.Entities.User user, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) { Recursive = true, ParentId = parentId, GenreIds = new[] { item.Id }, - IncludeItemTypes = new[] { typeof(Movie).Name, typeof(Series).Name }, + IncludeItemTypes = new[] + { + nameof(Movie), + nameof(Series) + }, Limit = limit, StartIndex = startIndex, DtoOptions = GetDtoOptions() @@ -1230,7 +1231,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMusicGenreItems(BaseItem item, Guid parentId, User user, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetMusicGenreItems(BaseItem item, Guid parentId, Jellyfin.Data.Entities.User user, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) { diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index f7d840c623..24932ced9a 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -6,7 +6,6 @@ using System.IO; using System.Linq; using System.Text; using System.Xml; -using Emby.Dlna.Configuration; using Emby.Dlna.ContentDirectory; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Drawing; @@ -39,7 +38,7 @@ namespace Emby.Dlna.Didl private readonly IImageProcessor _imageProcessor; private readonly string _serverAddress; private readonly string _accessToken; - private readonly User _user; + private readonly Jellyfin.Data.Entities.User _user; private readonly IUserDataManager _userDataManager; private readonly ILocalizationManager _localization; private readonly IMediaSourceManager _mediaSourceManager; @@ -49,7 +48,7 @@ namespace Emby.Dlna.Didl public DidlBuilder( DeviceProfile profile, - User user, + Jellyfin.Data.Entities.User user, IImageProcessor imageProcessor, string serverAddress, string accessToken, @@ -78,7 +77,7 @@ namespace Emby.Dlna.Didl return url + "&dlnaheaders=true"; } - public string GetItemDidl(BaseItem item, User user, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo) + public string GetItemDidl(BaseItem item, Jellyfin.Data.Entities.User user, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo) { var settings = new XmlWriterSettings { @@ -132,7 +131,7 @@ namespace Emby.Dlna.Didl public void WriteItemElement( XmlWriter writer, BaseItem item, - User user, + Jellyfin.Data.Entities.User user, BaseItem context, StubType? contextStubType, string deviceId, @@ -663,7 +662,7 @@ namespace Emby.Dlna.Didl writer.WriteFullEndElement(); } - private void AddSamsungBookmarkInfo(BaseItem item, User user, XmlWriter writer, StreamInfo streamInfo) + private void AddSamsungBookmarkInfo(BaseItem item, Jellyfin.Data.Entities.User user, XmlWriter writer, StreamInfo streamInfo) { if (!item.SupportsPositionTicksResume || item is Folder) { diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 43e9830540..ff37407a2f 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -441,7 +441,13 @@ namespace Emby.Dlna.PlayTo } } - private PlaylistItem CreatePlaylistItem(BaseItem item, User user, long startPostionTicks, string mediaSourceId, int? audioStreamIndex, int? subtitleStreamIndex) + private PlaylistItem CreatePlaylistItem( + BaseItem item, + Jellyfin.Data.Entities.User user, + long startPostionTicks, + string mediaSourceId, + int? audioStreamIndex, + int? subtitleStreamIndex) { var deviceInfo = _device.Properties; diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 0b3bbe29ef..b9172d2a86 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; using MediaBrowser.Controller.Drawing; @@ -14,6 +15,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using Microsoft.Extensions.Logging; +using Photo = MediaBrowser.Controller.Entities.Photo; namespace Emby.Drawing { @@ -328,6 +330,13 @@ namespace Emby.Drawing }); } + /// + public string GetImageCacheTag(User user) + { + return (user.ProfileImage.Path + user.ProfileImage.LastModified.Ticks).GetMD5() + .ToString("N", CultureInfo.InvariantCulture); + } + private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified) { var inputFormat = Path.GetExtension(originalImagePath) diff --git a/Emby.Notifications/Api/NotificationsService.cs b/Emby.Notifications/Api/NotificationsService.cs index 788750796d..221db54230 100644 --- a/Emby.Notifications/Api/NotificationsService.cs +++ b/Emby.Notifications/Api/NotificationsService.cs @@ -8,6 +8,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Notifications; @@ -164,7 +165,10 @@ namespace Emby.Notifications.Api Level = request.Level, Name = request.Name, Url = request.Url, - UserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id).ToArray() + UserIds = _userManager.Users + .Where(p => p.Permissions.Select(x => x.Kind).Contains(PermissionKind.IsAdministrator)) + .Select(p => p.Id) + .ToArray() }; return _notificationManager.SendNotification(notification, CancellationToken.None); diff --git a/Emby.Notifications/NotificationManager.cs b/Emby.Notifications/NotificationManager.cs index 639a5e1aad..9a9bc44153 100644 --- a/Emby.Notifications/NotificationManager.cs +++ b/Emby.Notifications/NotificationManager.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; @@ -81,7 +82,7 @@ namespace Emby.Notifications private Task SendNotification( NotificationRequest request, INotificationService service, - IEnumerable users, + IEnumerable users, string title, string description, CancellationToken cancellationToken) @@ -101,7 +102,7 @@ namespace Emby.Notifications switch (request.SendToUserMode.Value) { case SendToUserType.Admins: - return _userManager.Users.Where(i => i.Policy.IsAdministrator) + return _userManager.Users.Where(i => i.HasPermission(PermissionKind.IsAdministrator)) .Select(i => i.Id); case SendToUserType.All: return _userManager.UsersIds; @@ -117,7 +118,7 @@ namespace Emby.Notifications var config = GetConfiguration(); return _userManager.Users - .Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N", CultureInfo.InvariantCulture), i.Policy)) + .Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N", CultureInfo.InvariantCulture), i)) .Select(i => i.Id); } @@ -129,7 +130,7 @@ namespace Emby.Notifications INotificationService service, string title, string description, - User user, + Jellyfin.Data.Entities.User user, CancellationToken cancellationToken) { var notification = new UserNotification @@ -142,7 +143,7 @@ namespace Emby.Notifications User = user }; - _logger.LogDebug("Sending notification via {0} to user {1}", service.Name, user.Name); + _logger.LogDebug("Sending notification via {0} to user {1}", service.Name, user.Username); try { @@ -154,7 +155,7 @@ namespace Emby.Notifications } } - private bool IsEnabledForUser(INotificationService service, User user) + private bool IsEnabledForUser(INotificationService service, Jellyfin.Data.Entities.User user) { try { diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 54894fd65b..3d38590782 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -93,11 +93,10 @@ namespace Emby.Server.Implementations.Activity _subManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure; - _userManager.UserCreated += OnUserCreated; - _userManager.UserPasswordChanged += OnUserPasswordChanged; - _userManager.UserDeleted += OnUserDeleted; - _userManager.UserPolicyUpdated += OnUserPolicyUpdated; - _userManager.UserLockedOut += OnUserLockedOut; + _userManager.OnUserCreated += OnUserCreated; + _userManager.OnUserPasswordChanged += OnUserPasswordChanged; + _userManager.OnUserDeleted += OnUserDeleted; + _userManager.OnUserLockedOut += OnUserLockedOut; _deviceManager.CameraImageUploaded += OnCameraImageUploaded; @@ -118,13 +117,13 @@ namespace Emby.Server.Implementations.Activity .ConfigureAwait(false); } - private async void OnUserLockedOut(object sender, GenericEventArgs e) + private async void OnUserLockedOut(object sender, GenericEventArgs e) { await CreateLogEntry(new ActivityLog( string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserLockedOutWithName"), - e.Argument.Name), + e.Argument.Username), NotificationType.UserLockedOut.ToString(), e.Argument.Id, DateTime.UtcNow, @@ -177,7 +176,7 @@ namespace Emby.Server.Implementations.Activity string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), - user.Name, + user.Username, GetItemName(item), e.DeviceName), GetPlaybackStoppedNotificationType(item.MediaType), @@ -214,7 +213,7 @@ namespace Emby.Server.Implementations.Activity string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserStartedPlayingItemWithValues"), - user.Name, + user.Username, GetItemName(item), e.DeviceName), GetPlaybackNotificationType(item.MediaType), @@ -338,13 +337,13 @@ namespace Emby.Server.Implementations.Activity }).ConfigureAwait(false); } - private async void OnUserPolicyUpdated(object sender, GenericEventArgs e) + private async void OnUserPolicyUpdated(object sender, GenericEventArgs e) { await CreateLogEntry(new ActivityLog( string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserPolicyUpdatedWithName"), - e.Argument.Name), + e.Argument.Username), "UserPolicyUpdated", e.Argument.Id, DateTime.UtcNow, @@ -352,13 +351,13 @@ namespace Emby.Server.Implementations.Activity .ConfigureAwait(false); } - private async void OnUserDeleted(object sender, GenericEventArgs e) + private async void OnUserDeleted(object sender, GenericEventArgs e) { await CreateLogEntry(new ActivityLog( string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserDeletedWithName"), - e.Argument.Name), + e.Argument.Username), "UserDeleted", Guid.Empty, DateTime.UtcNow, @@ -366,26 +365,26 @@ namespace Emby.Server.Implementations.Activity .ConfigureAwait(false); } - private async void OnUserPasswordChanged(object sender, GenericEventArgs e) + private async void OnUserPasswordChanged(object sender, GenericEventArgs e) { await CreateLogEntry(new ActivityLog( string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserPasswordChangedWithName"), - e.Argument.Name), + e.Argument.Username), "UserPasswordChanged", e.Argument.Id, DateTime.UtcNow, LogLevel.Trace)).ConfigureAwait(false); } - private async void OnUserCreated(object sender, GenericEventArgs e) + private async void OnUserCreated(object sender, GenericEventArgs e) { await CreateLogEntry(new ActivityLog( string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserCreatedWithName"), - e.Argument.Name), + e.Argument.Username), "UserCreated", e.Argument.Id, DateTime.UtcNow, @@ -562,11 +561,10 @@ namespace Emby.Server.Implementations.Activity _subManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure; - _userManager.UserCreated -= OnUserCreated; - _userManager.UserPasswordChanged -= OnUserPasswordChanged; - _userManager.UserDeleted -= OnUserDeleted; - _userManager.UserPolicyUpdated -= OnUserPolicyUpdated; - _userManager.UserLockedOut -= OnUserLockedOut; + _userManager.OnUserCreated -= OnUserCreated; + _userManager.OnUserPasswordChanged -= OnUserPasswordChanged; + _userManager.OnUserDeleted -= OnUserDeleted; + _userManager.OnUserLockedOut -= OnUserLockedOut; _deviceManager.CameraImageUploaded -= OnCameraImageUploaded; } diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ddd9c79533..52b48d0815 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -48,6 +48,7 @@ using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations.Activity; +using Jellyfin.Server.Implementations.User; using MediaBrowser.Api; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; @@ -595,17 +596,12 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - // TODO: Refactor to eliminate the circular dependency here so that Lazy isn't required serviceCollection.AddTransient(provider => new Lazy(provider.GetRequiredService)); serviceCollection.AddSingleton(); @@ -700,17 +696,11 @@ namespace Emby.Server.Implementations _httpServer = Resolve(); _httpClient = Resolve(); - ((SqliteDisplayPreferencesRepository)Resolve()).Initialize(); ((AuthenticationRepository)Resolve()).Initialize(); - ((SqliteUserRepository)Resolve()).Initialize(); SetStaticProperties(); - var userManager = (UserManager)Resolve(); - userManager.Initialize(); - - var userDataRepo = (SqliteUserDataRepository)Resolve(); - ((SqliteItemRepository)Resolve()).Initialize(userDataRepo, userManager); + ((SqliteItemRepository)Resolve()).Initialize(); FindParts(); } @@ -793,7 +783,6 @@ namespace Emby.Server.Implementations BaseItem.ProviderManager = Resolve(); BaseItem.LocalizationManager = Resolve(); BaseItem.ItemRepository = Resolve(); - User.UserManager = Resolve(); BaseItem.FileSystem = _fileSystemManager; BaseItem.UserDataManager = Resolve(); BaseItem.ChannelManager = Resolve(); diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 138832fb86..cb320dcb10 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -46,7 +46,7 @@ namespace Emby.Server.Implementations.Channels new ConcurrentDictionary>>(); private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1); - + /// /// Initializes a new instance of the class. /// @@ -791,8 +791,9 @@ namespace Emby.Server.Implementations.Channels return result; } - private async Task GetChannelItems(IChannel channel, - User user, + private async Task GetChannelItems( + IChannel channel, + Jellyfin.Data.Entities.User user, string externalFolderId, ChannelItemSortField? sortField, bool sortDescending, diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 7c518d4831..61963b633e 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -121,7 +121,7 @@ namespace Emby.Server.Implementations.Collections return EnsureLibraryFolder(GetCollectionsFolderPath(), createIfNeeded); } - private IEnumerable GetCollections(User user) + private IEnumerable GetCollections(Jellyfin.Data.Entities.User user) { var folder = GetCollectionsFolder(false).Result; @@ -325,7 +325,7 @@ namespace Emby.Server.Implementations.Collections } /// - public IEnumerable CollapseItemsWithinBoxSets(IEnumerable items, User user) + public IEnumerable CollapseItemsWithinBoxSets(IEnumerable items, Jellyfin.Data.Entities.User user) { var results = new Dictionary(); diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs deleted file mode 100644 index d474f1c6ba..0000000000 --- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs +++ /dev/null @@ -1,225 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Text.Json; -using System.Threading; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Json; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; - -namespace Emby.Server.Implementations.Data -{ - /// - /// Class SQLiteDisplayPreferencesRepository. - /// - public class SqliteDisplayPreferencesRepository : BaseSqliteRepository, IDisplayPreferencesRepository - { - private readonly IFileSystem _fileSystem; - - private readonly JsonSerializerOptions _jsonOptions; - - public SqliteDisplayPreferencesRepository(ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem) - : base(logger) - { - _fileSystem = fileSystem; - - _jsonOptions = JsonDefaults.GetOptions(); - - DbFilePath = Path.Combine(appPaths.DataPath, "displaypreferences.db"); - } - - /// - /// Gets the name of the repository. - /// - /// The name. - public string Name => "SQLite"; - - public void Initialize() - { - try - { - InitializeInternal(); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error loading database file. Will reset and retry."); - - _fileSystem.DeleteFile(DbFilePath); - - InitializeInternal(); - } - } - - /// - /// Opens the connection to the database - /// - /// Task. - private void InitializeInternal() - { - string[] queries = - { - "create table if not exists userdisplaypreferences (id GUID NOT NULL, userId GUID NOT NULL, client text NOT NULL, data BLOB NOT NULL)", - "create unique index if not exists userdisplaypreferencesindex on userdisplaypreferences (id, userId, client)" - }; - - using (var connection = GetConnection()) - { - connection.RunQueries(queries); - } - } - - /// - /// Save the display preferences associated with an item in the repo - /// - /// The display preferences. - /// The user id. - /// The client. - /// The cancellation token. - /// item - public void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, CancellationToken cancellationToken) - { - if (displayPreferences == null) - { - throw new ArgumentNullException(nameof(displayPreferences)); - } - - if (string.IsNullOrEmpty(displayPreferences.Id)) - { - throw new ArgumentException("Display preferences has an invalid Id", nameof(displayPreferences)); - } - - cancellationToken.ThrowIfCancellationRequested(); - - using (var connection = GetConnection()) - { - connection.RunInTransaction( - db => SaveDisplayPreferences(displayPreferences, userId, client, db), - TransactionMode); - } - } - - private void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, IDatabaseConnection connection) - { - var serialized = JsonSerializer.SerializeToUtf8Bytes(displayPreferences, _jsonOptions); - - using (var statement = connection.PrepareStatement("replace into userdisplaypreferences (id, userid, client, data) values (@id, @userId, @client, @data)")) - { - statement.TryBind("@id", new Guid(displayPreferences.Id).ToByteArray()); - statement.TryBind("@userId", userId.ToByteArray()); - statement.TryBind("@client", client); - statement.TryBind("@data", serialized); - - statement.MoveNext(); - } - } - - /// - /// Save all display preferences associated with a user in the repo - /// - /// The display preferences. - /// The user id. - /// The cancellation token. - /// item - public void SaveAllDisplayPreferences(IEnumerable displayPreferences, Guid userId, CancellationToken cancellationToken) - { - if (displayPreferences == null) - { - throw new ArgumentNullException(nameof(displayPreferences)); - } - - cancellationToken.ThrowIfCancellationRequested(); - - using (var connection = GetConnection()) - { - connection.RunInTransaction( - db => - { - foreach (var displayPreference in displayPreferences) - { - SaveDisplayPreferences(displayPreference, userId, displayPreference.Client, db); - } - }, - TransactionMode); - } - } - - /// - /// Gets the display preferences. - /// - /// The display preferences id. - /// The user id. - /// The client. - /// Task{DisplayPreferences}. - /// item - public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, Guid userId, string client) - { - if (string.IsNullOrEmpty(displayPreferencesId)) - { - throw new ArgumentNullException(nameof(displayPreferencesId)); - } - - var guidId = displayPreferencesId.GetMD5(); - - using (var connection = GetConnection(true)) - { - using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where id = @id and userId=@userId and client=@client")) - { - statement.TryBind("@id", guidId.ToByteArray()); - statement.TryBind("@userId", userId.ToByteArray()); - statement.TryBind("@client", client); - - foreach (var row in statement.ExecuteQuery()) - { - return Get(row); - } - } - } - - return new DisplayPreferences - { - Id = guidId.ToString("N", CultureInfo.InvariantCulture) - }; - } - - /// - /// Gets all display preferences for the given user. - /// - /// The user id. - /// Task{DisplayPreferences}. - /// item - public IEnumerable GetAllDisplayPreferences(Guid userId) - { - var list = new List(); - - using (var connection = GetConnection(true)) - using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where userId=@userId")) - { - statement.TryBind("@userId", userId.ToByteArray()); - - foreach (var row in statement.ExecuteQuery()) - { - list.Add(Get(row)); - } - } - - return list; - } - - private DisplayPreferences Get(IReadOnlyList row) - => JsonSerializer.Deserialize(row[0].ToBlob(), _jsonOptions); - - public void SaveDisplayPreferences(DisplayPreferences displayPreferences, string userId, string client, CancellationToken cancellationToken) - => SaveDisplayPreferences(displayPreferences, new Guid(userId), client, cancellationToken); - - public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, string userId, string client) - => GetDisplayPreferences(displayPreferencesId, new Guid(userId), client); - } -} diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index ca5cd6fdd5..74b8ffc18d 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Data /// /// Opens the connection to the database /// - public void Initialize(SqliteUserDataRepository userDataRepo, IUserManager userManager) + public void Initialize() { const string CreateMediaStreamsTableCommand = "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, ColorPrimaries TEXT NULL, ColorSpace TEXT NULL, ColorTransfer TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))"; @@ -324,8 +324,6 @@ namespace Emby.Server.Implementations.Data connection.RunQueries(postQueries); } - - userDataRepo.Initialize(userManager, WriteLock, WriteConnection); } private static readonly string[] _retriveItemColumns = diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs deleted file mode 100644 index 22955850ab..0000000000 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ /dev/null @@ -1,379 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; - -namespace Emby.Server.Implementations.Data -{ - public class SqliteUserDataRepository : BaseSqliteRepository, IUserDataRepository - { - public SqliteUserDataRepository( - ILogger logger, - IApplicationPaths appPaths) - : base(logger) - { - DbFilePath = Path.Combine(appPaths.DataPath, "library.db"); - } - - /// - public string Name => "SQLite"; - - /// - /// Opens the connection to the database. - /// - public void Initialize(IUserManager userManager, SemaphoreSlim dbLock, SQLiteDatabaseConnection dbConnection) - { - WriteLock.Dispose(); - WriteLock = dbLock; - WriteConnection?.Dispose(); - WriteConnection = dbConnection; - - using (var connection = GetConnection()) - { - var userDatasTableExists = TableExists(connection, "UserDatas"); - var userDataTableExists = TableExists(connection, "userdata"); - - var users = userDatasTableExists ? null : userManager.Users; - - connection.RunInTransaction(db => - { - db.ExecuteAll(string.Join(";", new[] { - - "create table if not exists UserDatas (key nvarchar not null, userId INT not null, rating float null, played bit not null, playCount int not null, isFavorite bit not null, playbackPositionTicks bigint not null, lastPlayedDate datetime null, AudioStreamIndex INT, SubtitleStreamIndex INT)", - - "drop index if exists idx_userdata", - "drop index if exists idx_userdata1", - "drop index if exists idx_userdata2", - "drop index if exists userdataindex1", - "drop index if exists userdataindex", - "drop index if exists userdataindex3", - "drop index if exists userdataindex4", - "create unique index if not exists UserDatasIndex1 on UserDatas (key, userId)", - "create index if not exists UserDatasIndex2 on UserDatas (key, userId, played)", - "create index if not exists UserDatasIndex3 on UserDatas (key, userId, playbackPositionTicks)", - "create index if not exists UserDatasIndex4 on UserDatas (key, userId, isFavorite)" - })); - - if (userDataTableExists) - { - var existingColumnNames = GetColumnNames(db, "userdata"); - - AddColumn(db, "userdata", "InternalUserId", "int", existingColumnNames); - AddColumn(db, "userdata", "AudioStreamIndex", "int", existingColumnNames); - AddColumn(db, "userdata", "SubtitleStreamIndex", "int", existingColumnNames); - - if (!userDatasTableExists) - { - ImportUserIds(db, users); - - db.ExecuteAll("INSERT INTO UserDatas (key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex) SELECT key, InternalUserId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex from userdata where InternalUserId not null"); - } - } - }, TransactionMode); - } - } - - private void ImportUserIds(IDatabaseConnection db, IEnumerable users) - { - var userIdsWithUserData = GetAllUserIdsWithUserData(db); - - using (var statement = db.PrepareStatement("update userdata set InternalUserId=@InternalUserId where UserId=@UserId")) - { - foreach (var user in users) - { - if (!userIdsWithUserData.Contains(user.Id)) - { - continue; - } - - statement.TryBind("@UserId", user.Id.ToByteArray()); - statement.TryBind("@InternalUserId", user.InternalId); - - statement.MoveNext(); - statement.Reset(); - } - } - } - - private List GetAllUserIdsWithUserData(IDatabaseConnection db) - { - var list = new List(); - - using (var statement = PrepareStatement(db, "select DISTINCT UserId from UserData where UserId not null")) - { - foreach (var row in statement.ExecuteQuery()) - { - try - { - list.Add(row[0].ReadGuidFromBlob()); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error while getting user"); - } - } - } - - return list; - } - - /// - /// Saves the user data. - /// - public void SaveUserData(long internalUserId, string key, UserItemData userData, CancellationToken cancellationToken) - { - if (userData == null) - { - throw new ArgumentNullException(nameof(userData)); - } - if (internalUserId <= 0) - { - throw new ArgumentNullException(nameof(internalUserId)); - } - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentNullException(nameof(key)); - } - - PersistUserData(internalUserId, key, userData, cancellationToken); - } - - public void SaveAllUserData(long internalUserId, UserItemData[] userData, CancellationToken cancellationToken) - { - if (userData == null) - { - throw new ArgumentNullException(nameof(userData)); - } - if (internalUserId <= 0) - { - throw new ArgumentNullException(nameof(internalUserId)); - } - - PersistAllUserData(internalUserId, userData, cancellationToken); - } - - /// - /// Persists the user data. - /// - /// The user id. - /// The key. - /// The user data. - /// The cancellation token. - /// Task. - public void PersistUserData(long internalUserId, string key, UserItemData userData, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - using (var connection = GetConnection()) - { - connection.RunInTransaction(db => - { - SaveUserData(db, internalUserId, key, userData); - }, TransactionMode); - } - } - - private static void SaveUserData(IDatabaseConnection db, long internalUserId, string key, UserItemData userData) - { - using (var statement = db.PrepareStatement("replace into UserDatas (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate,@AudioStreamIndex,@SubtitleStreamIndex)")) - { - statement.TryBind("@userId", internalUserId); - statement.TryBind("@key", key); - - if (userData.Rating.HasValue) - { - statement.TryBind("@rating", userData.Rating.Value); - } - else - { - statement.TryBindNull("@rating"); - } - - statement.TryBind("@played", userData.Played); - statement.TryBind("@playCount", userData.PlayCount); - statement.TryBind("@isFavorite", userData.IsFavorite); - statement.TryBind("@playbackPositionTicks", userData.PlaybackPositionTicks); - - if (userData.LastPlayedDate.HasValue) - { - statement.TryBind("@lastPlayedDate", userData.LastPlayedDate.Value.ToDateTimeParamValue()); - } - else - { - statement.TryBindNull("@lastPlayedDate"); - } - - if (userData.AudioStreamIndex.HasValue) - { - statement.TryBind("@AudioStreamIndex", userData.AudioStreamIndex.Value); - } - else - { - statement.TryBindNull("@AudioStreamIndex"); - } - - if (userData.SubtitleStreamIndex.HasValue) - { - statement.TryBind("@SubtitleStreamIndex", userData.SubtitleStreamIndex.Value); - } - else - { - statement.TryBindNull("@SubtitleStreamIndex"); - } - - statement.MoveNext(); - } - } - - /// - /// Persist all user data for the specified user - /// - private void PersistAllUserData(long internalUserId, UserItemData[] userDataList, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - using (var connection = GetConnection()) - { - connection.RunInTransaction(db => - { - foreach (var userItemData in userDataList) - { - SaveUserData(db, internalUserId, userItemData.Key, userItemData); - } - }, TransactionMode); - } - } - - /// - /// Gets the user data. - /// - /// The user id. - /// The key. - /// Task{UserItemData}. - /// - /// userId - /// or - /// key - /// - public UserItemData GetUserData(long internalUserId, string key) - { - if (internalUserId <= 0) - { - throw new ArgumentNullException(nameof(internalUserId)); - } - - if (string.IsNullOrEmpty(key)) - { - throw new ArgumentNullException(nameof(key)); - } - - using (var connection = GetConnection(true)) - { - using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where key =@Key and userId=@UserId")) - { - statement.TryBind("@UserId", internalUserId); - statement.TryBind("@Key", key); - - foreach (var row in statement.ExecuteQuery()) - { - return ReadRow(row); - } - } - - return null; - } - } - - public UserItemData GetUserData(long internalUserId, List keys) - { - if (keys == null) - { - throw new ArgumentNullException(nameof(keys)); - } - - if (keys.Count == 0) - { - return null; - } - - return GetUserData(internalUserId, keys[0]); - } - - /// - /// Return all user-data associated with the given user - /// - /// - /// - public List GetAllUserData(long internalUserId) - { - if (internalUserId <= 0) - { - throw new ArgumentNullException(nameof(internalUserId)); - } - - var list = new List(); - - using (var connection = GetConnection()) - { - using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where userId=@UserId")) - { - statement.TryBind("@UserId", internalUserId); - - foreach (var row in statement.ExecuteQuery()) - { - list.Add(ReadRow(row)); - } - } - } - - return list; - } - - /// - /// Read a row from the specified reader into the provided userData object - /// - /// - private UserItemData ReadRow(IReadOnlyList reader) - { - var userData = new UserItemData(); - - userData.Key = reader[0].ToString(); - //userData.UserId = reader[1].ReadGuidFromBlob(); - - if (reader[2].SQLiteType != SQLiteType.Null) - { - userData.Rating = reader[2].ToDouble(); - } - - userData.Played = reader[3].ToBool(); - userData.PlayCount = reader[4].ToInt(); - userData.IsFavorite = reader[5].ToBool(); - userData.PlaybackPositionTicks = reader[6].ToInt64(); - - if (reader[7].SQLiteType != SQLiteType.Null) - { - userData.LastPlayedDate = reader[7].TryReadDateTime(); - } - - if (reader[8].SQLiteType != SQLiteType.Null) - { - userData.AudioStreamIndex = reader[8].ToInt(); - } - - if (reader[9].SQLiteType != SQLiteType.Null) - { - userData.SubtitleStreamIndex = reader[9].ToInt(); - } - - return userData; - } - } -} diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs deleted file mode 100644 index 0c3f26974f..0000000000 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ /dev/null @@ -1,240 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.IO; -using System.Text.Json; -using MediaBrowser.Common.Json; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Persistence; -using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; - -namespace Emby.Server.Implementations.Data -{ - /// - /// Class SQLiteUserRepository - /// - public class SqliteUserRepository : BaseSqliteRepository, IUserRepository - { - private readonly JsonSerializerOptions _jsonOptions; - - public SqliteUserRepository( - ILogger logger, - IServerApplicationPaths appPaths) - : base(logger) - { - _jsonOptions = JsonDefaults.GetOptions(); - - DbFilePath = Path.Combine(appPaths.DataPath, "users.db"); - } - - /// - /// Gets the name of the repository - /// - /// The name. - public string Name => "SQLite"; - - /// - /// Opens the connection to the database. - /// - public void Initialize() - { - using (var connection = GetConnection()) - { - var localUsersTableExists = TableExists(connection, "LocalUsersv2"); - - connection.RunQueries(new[] { - "create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)", - "drop index if exists idx_users" - }); - - if (!localUsersTableExists && TableExists(connection, "Users")) - { - TryMigrateToLocalUsersTable(connection); - } - - RemoveEmptyPasswordHashes(connection); - } - } - - private void TryMigrateToLocalUsersTable(ManagedConnection connection) - { - try - { - connection.RunQueries(new[] - { - "INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users" - }); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error migrating users database"); - } - } - - private void RemoveEmptyPasswordHashes(ManagedConnection connection) - { - foreach (var user in RetrieveAllUsers(connection)) - { - // If the user password is the sha1 hash of the empty string, remove it - if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal) - && !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)) - { - continue; - } - - user.Password = null; - var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions); - - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) - { - statement.TryBind("@InternalId", user.InternalId); - statement.TryBind("@data", serialized); - statement.MoveNext(); - } - }, TransactionMode); - } - } - - /// - /// Save a user in the repo - /// - public void CreateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions); - - using (var connection = GetConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)")) - { - statement.TryBind("@guid", user.Id.ToByteArray()); - statement.TryBind("@data", serialized); - - statement.MoveNext(); - } - - var createdUser = GetUser(user.Id, connection); - - if (createdUser == null) - { - throw new ApplicationException("created user should never be null"); - } - - user.InternalId = createdUser.InternalId; - - }, TransactionMode); - } - } - - public void UpdateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions); - - using (var connection = GetConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) - { - statement.TryBind("@InternalId", user.InternalId); - statement.TryBind("@data", serialized); - statement.MoveNext(); - } - - }, TransactionMode); - } - } - - private User GetUser(Guid guid, ManagedConnection connection) - { - using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid")) - { - statement.TryBind("@guid", guid); - - foreach (var row in statement.ExecuteQuery()) - { - return GetUser(row); - } - } - - return null; - } - - private User GetUser(IReadOnlyList row) - { - var id = row[0].ToInt64(); - var guid = row[1].ReadGuidFromBlob(); - - var user = JsonSerializer.Deserialize(row[2].ToBlob(), _jsonOptions); - user.InternalId = id; - user.Id = guid; - return user; - } - - /// - /// Retrieve all users from the database - /// - /// IEnumerable{User}. - public List RetrieveAllUsers() - { - using (var connection = GetConnection(true)) - { - return new List(RetrieveAllUsers(connection)); - } - } - - /// - /// Retrieve all users from the database - /// - /// IEnumerable{User}. - private IEnumerable RetrieveAllUsers(ManagedConnection connection) - { - foreach (var row in connection.Query("select id,guid,data from LocalUsersv2")) - { - yield return GetUser(row); - } - } - - /// - /// Deletes the user. - /// - /// The user. - /// Task. - /// user - public void DeleteUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - using (var connection = GetConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id")) - { - statement.TryBind("@id", user.InternalId); - statement.MoveNext(); - } - }, TransactionMode); - } - } - } -} diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index 579cb895e4..d6bce93f84 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; @@ -360,7 +361,7 @@ namespace Emby.Server.Implementations.Devices private string DefaultCameraUploadsPath => Path.Combine(_config.CommonApplicationPaths.DataPath, "camerauploads"); - public bool CanAccessDevice(User user, string deviceId) + public bool CanAccessDevice(Jellyfin.Data.Entities.User user, string deviceId) { if (user == null) { @@ -371,7 +372,13 @@ namespace Emby.Server.Implementations.Devices throw new ArgumentNullException(nameof(deviceId)); } - if (!CanAccessDevice(user.Policy, deviceId)) + if (user.HasPermission(PermissionKind.EnableAllDevices) + || user.HasPermission(PermissionKind.IsAdministrator)) + { + return true; + } + + if (!user.GetPreference(PreferenceKind.EnabledDevices).Contains(deviceId, StringComparer.OrdinalIgnoreCase)) { var capabilities = GetCapabilities(deviceId); @@ -383,21 +390,6 @@ namespace Emby.Server.Implementations.Devices return true; } - - private static bool CanAccessDevice(UserPolicy policy, string id) - { - if (policy.EnableAllDevices) - { - return true; - } - - if (policy.IsAdministrator) - { - return true; - } - - return policy.EnabledDevices.Contains(id, StringComparer.OrdinalIgnoreCase); - } } public class DeviceManagerEntryPoint : IServerEntryPoint diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index c4b65d2654..da40bca4cc 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Common; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Drawing; @@ -74,7 +75,7 @@ namespace Emby.Server.Implementations.Dto /// The owner. /// Task{DtoBaseItem}. /// item - public BaseItemDto GetBaseItemDto(BaseItem item, ItemFields[] fields, User user = null, BaseItem owner = null) + public BaseItemDto GetBaseItemDto(BaseItem item, ItemFields[] fields, Jellyfin.Data.Entities.User user = null, BaseItem owner = null) { var options = new DtoOptions { @@ -85,7 +86,7 @@ namespace Emby.Server.Implementations.Dto } /// - public IReadOnlyList GetBaseItemDtos(IReadOnlyList items, DtoOptions options, User user = null, BaseItem owner = null) + public IReadOnlyList GetBaseItemDtos(IReadOnlyList items, DtoOptions options, Jellyfin.Data.Entities.User user = null, BaseItem owner = null) { var returnItems = new BaseItemDto[items.Count]; var programTuples = new List<(BaseItem, BaseItemDto)>(); @@ -138,7 +139,7 @@ namespace Emby.Server.Implementations.Dto return returnItems; } - public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null) + public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, Jellyfin.Data.Entities.User user = null, BaseItem owner = null) { var dto = GetBaseItemDtoInternal(item, options, user, owner); if (item is LiveTvChannel tvChannel) @@ -172,7 +173,7 @@ namespace Emby.Server.Implementations.Dto return dto; } - private static IList GetTaggedItems(IItemByName byName, User user, DtoOptions options) + private static IList GetTaggedItems(IItemByName byName, Jellyfin.Data.Entities.User user, DtoOptions options) { return byName.GetTaggedItems( new InternalItemsQuery(user) @@ -182,7 +183,7 @@ namespace Emby.Server.Implementations.Dto }); } - private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null) + private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, Jellyfin.Data.Entities.User user = null, BaseItem owner = null) { var dto = new BaseItemDto { @@ -315,7 +316,7 @@ namespace Emby.Server.Implementations.Dto } } - public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List taggedItems, User user = null) + public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List taggedItems, Jellyfin.Data.Entities.User user = null) { var dto = GetBaseItemDtoInternal(item, options, user); @@ -327,7 +328,7 @@ namespace Emby.Server.Implementations.Dto return dto; } - private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IList taggedItems, User user = null) + private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IList taggedItems, Jellyfin.Data.Entities.User user = null) { if (item is MusicArtist) { @@ -363,7 +364,7 @@ namespace Emby.Server.Implementations.Dto /// /// Attaches the user specific info. /// - private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, DtoOptions options) + private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, Jellyfin.Data.Entities.User user, DtoOptions options) { if (item.IsFolder) { @@ -384,7 +385,7 @@ namespace Emby.Server.Implementations.Dto if (options.ContainsField(ItemFields.ChildCount)) { - dto.ChildCount = dto.ChildCount ?? GetChildCount(folder, user); + dto.ChildCount ??= GetChildCount(folder, user); } } @@ -414,7 +415,7 @@ namespace Emby.Server.Implementations.Dto if (options.ContainsField(ItemFields.BasicSyncInfo)) { - var userCanSync = user != null && user.Policy.EnableContentDownloading; + var userCanSync = user != null && user.HasPermission(PermissionKind.EnableContentDownloading); if (userCanSync && item.SupportsExternalTransfer) { dto.SupportsSync = true; @@ -422,7 +423,7 @@ namespace Emby.Server.Implementations.Dto } } - private static int GetChildCount(Folder folder, User user) + private static int GetChildCount(Folder folder, Jellyfin.Data.Entities.User user) { // Right now this is too slow to calculate for top level folders on a per-user basis // Just return something so that apps that are expecting a value won't think the folders are empty diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 8e32364071..7ece52cad4 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -446,7 +446,7 @@ namespace Emby.Server.Implementations.EntryPoints /// The user. /// if set to true [include if not found]. /// IEnumerable{``0}. - private IEnumerable TranslatePhysicalItemToUserLibrary(T item, User user, bool includeIfNotFound = false) + private IEnumerable TranslatePhysicalItemToUserLibrary(T item, Jellyfin.Data.Entities.User user, bool includeIfNotFound = false) where T : BaseItem { // If the physical root changed, return the user root diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index 41c0c5115c..75dde55980 100644 --- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Plugins; @@ -64,7 +65,7 @@ namespace Emby.Server.Implementations.EntryPoints private async void SendMessage(string name, TimerEventInfo info) { - var users = _userManager.Users.Where(i => i.Policy.EnableLiveTvAccess).Select(i => i.Id).ToList(); + var users = _userManager.Users.Where(i => i.HasPermission(PermissionKind.EnableLiveTvAccess)).Select(i => i.Id).ToList(); try { diff --git a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs deleted file mode 100644 index 54f4b67e66..0000000000 --- a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Tasks; - -namespace Emby.Server.Implementations.EntryPoints -{ - /// - /// Class RefreshUsersMetadata. - /// - public class RefreshUsersMetadata : IScheduledTask, IConfigurableScheduledTask - { - /// - /// The user manager. - /// - private readonly IUserManager _userManager; - private readonly IFileSystem _fileSystem; - - /// - /// Initializes a new instance of the class. - /// - public RefreshUsersMetadata(IUserManager userManager, IFileSystem fileSystem) - { - _userManager = userManager; - _fileSystem = fileSystem; - } - - /// - public string Name => "Refresh Users"; - - /// - public string Key => "RefreshUsers"; - - /// - public string Description => "Refresh user infos"; - - /// - public string Category => "Library"; - - /// - public bool IsHidden => true; - - /// - public bool IsEnabled => true; - - /// - public bool IsLogged => true; - - /// - public async Task Execute(CancellationToken cancellationToken, IProgress progress) - { - foreach (var user in _userManager.Users) - { - cancellationToken.ThrowIfCancellationRequested(); - - await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false); - } - } - - /// - public IEnumerable GetDefaultTriggers() - { - return new[] - { - new TaskTriggerInfo - { - IntervalTicks = TimeSpan.FromDays(1).Ticks, - Type = TaskTriggerInfo.TriggerInterval - } - }; - } - } -} diff --git a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs index e1dbb663bc..436d723f05 100644 --- a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs @@ -3,10 +3,10 @@ using System.Collections.Generic; using System.Globalization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; @@ -67,10 +67,8 @@ namespace Emby.Server.Implementations.EntryPoints /// public Task RunAsync() { - _userManager.UserDeleted += OnUserDeleted; - _userManager.UserUpdated += OnUserUpdated; - _userManager.UserPolicyUpdated += OnUserPolicyUpdated; - _userManager.UserConfigurationUpdated += OnUserConfigurationUpdated; + _userManager.OnUserDeleted += OnUserDeleted; + _userManager.OnUserUpdated += OnUserUpdated; _appHost.HasPendingRestartChanged += OnHasPendingRestartChanged; @@ -152,20 +150,6 @@ namespace Emby.Server.Implementations.EntryPoints SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N", CultureInfo.InvariantCulture)); } - private void OnUserPolicyUpdated(object sender, GenericEventArgs e) - { - var dto = _userManager.GetUserDto(e.Argument); - - SendMessageToUserSession(e.Argument, "UserPolicyUpdated", dto); - } - - private void OnUserConfigurationUpdated(object sender, GenericEventArgs e) - { - var dto = _userManager.GetUserDto(e.Argument); - - SendMessageToUserSession(e.Argument, "UserConfigurationUpdated", dto); - } - private async void SendMessageToAdminSessions(string name, T data) { try @@ -209,10 +193,9 @@ namespace Emby.Server.Implementations.EntryPoints { if (dispose) { - _userManager.UserDeleted -= OnUserDeleted; - _userManager.UserUpdated -= OnUserUpdated; - _userManager.UserPolicyUpdated -= OnUserPolicyUpdated; - _userManager.UserConfigurationUpdated -= OnUserConfigurationUpdated; + _userManager.OnUserDeleted -= OnUserDeleted; + _userManager.OnUserUpdated -= OnUserUpdated; + _installationManager.PluginUninstalled -= OnPluginUninstalled; _installationManager.PackageInstalling -= OnPackageInstalling; diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 256b24924e..ad7b76d4fd 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using System.Security.Authentication; using Emby.Server.Implementations.SocketSharp; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -43,14 +44,14 @@ namespace Emby.Server.Implementations.HttpServer.Security ValidateUser(request, authAttribtues); } - public User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes) + public Jellyfin.Data.Entities.User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes) { var req = new WebSocketSharpRequest(request, null, request.Path, _logger); var user = ValidateUser(req, authAttributes); return user; } - private User ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues) + private Jellyfin.Data.Entities.User ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues) { // This code is executed before the service var auth = _authorizationContext.GetAuthorizationInfo(request); @@ -90,7 +91,8 @@ namespace Emby.Server.Implementations.HttpServer.Security !string.IsNullOrEmpty(auth.Client) && !string.IsNullOrEmpty(auth.Device)) { - _sessionManager.LogSessionActivity(auth.Client, + _sessionManager.LogSessionActivity( + auth.Client, auth.Version, auth.DeviceId, auth.Device, @@ -102,22 +104,22 @@ namespace Emby.Server.Implementations.HttpServer.Security } private void ValidateUserAccess( - User user, + Jellyfin.Data.Entities.User user, IRequest request, IAuthenticationAttributes authAttribtues, AuthorizationInfo auth) { - if (user.Policy.IsDisabled) + if (user.HasPermission(PermissionKind.IsDisabled)) { throw new SecurityException("User account has been disabled."); } - if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(request.RemoteIp)) + if (!user.HasPermission(PermissionKind.EnableRemoteAccess) && !_networkManager.IsInLocalNetwork(request.RemoteIp)) { throw new SecurityException("User account has been disabled."); } - if (!user.Policy.IsAdministrator + if (!user.HasPermission(PermissionKind.IsAdministrator) && !authAttribtues.EscapeParentalControl && !user.IsParentalScheduleAllowed()) { @@ -176,11 +178,11 @@ namespace Emby.Server.Implementations.HttpServer.Security return false; } - private static void ValidateRoles(string[] roles, User user) + private static void ValidateRoles(string[] roles, Jellyfin.Data.Entities.User user) { if (roles.Contains("admin", StringComparer.OrdinalIgnoreCase)) { - if (user == null || !user.Policy.IsAdministrator) + if (user == null || !user.HasPermission(PermissionKind.IsAdministrator)) { throw new SecurityException("User does not have admin access."); } @@ -188,7 +190,7 @@ namespace Emby.Server.Implementations.HttpServer.Security if (roles.Contains("delete", StringComparer.OrdinalIgnoreCase)) { - if (user == null || !user.Policy.EnableContentDeletion) + if (user == null || !user.HasPermission(PermissionKind.EnableContentDeletion)) { throw new SecurityException("User does not have delete access."); } @@ -196,7 +198,7 @@ namespace Emby.Server.Implementations.HttpServer.Security if (roles.Contains("download", StringComparer.OrdinalIgnoreCase)) { - if (user == null || !user.Policy.EnableContentDownloading) + if (user == null || !user.HasPermission(PermissionKind.EnableContentDownloading)) { throw new SecurityException("User does not have download access."); } diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 129faeaab0..9558cb4c66 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -149,9 +149,9 @@ namespace Emby.Server.Implementations.HttpServer.Security { info.User = _userManager.GetUserById(tokenInfo.UserId); - if (info.User != null && !string.Equals(info.User.Name, tokenInfo.UserName, StringComparison.OrdinalIgnoreCase)) + if (info.User != null && !string.Equals(info.User.Username, tokenInfo.UserName, StringComparison.OrdinalIgnoreCase)) { - tokenInfo.UserName = info.User.Name; + tokenInfo.UserName = info.User.Username; updateToken = true; } } diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index 166952c646..3f8a64f990 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -42,14 +42,14 @@ namespace Emby.Server.Implementations.HttpServer.Security return GetSession((IRequest)requestContext); } - public User GetUser(IRequest requestContext) + public Jellyfin.Data.Entities.User GetUser(IRequest requestContext) { var session = GetSession(requestContext); return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId); } - public User GetUser(object requestContext) + public Jellyfin.Data.Entities.User GetUser(object requestContext) { return GetUser((IRequest)requestContext); } diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs deleted file mode 100644 index 52c8facc3e..0000000000 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ /dev/null @@ -1,177 +0,0 @@ -using System; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Common; -using MediaBrowser.Common.Cryptography; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Cryptography; - -namespace Emby.Server.Implementations.Library -{ - /// - /// The default authentication provider. - /// - public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser - { - private readonly ICryptoProvider _cryptographyProvider; - - /// - /// Initializes a new instance of the class. - /// - /// The cryptography provider. - public DefaultAuthenticationProvider(ICryptoProvider cryptographyProvider) - { - _cryptographyProvider = cryptographyProvider; - } - - /// - public string Name => "Default"; - - /// - public bool IsEnabled => true; - - /// - // This is dumb and an artifact of the backwards way auth providers were designed. - // This version of authenticate was never meant to be called, but needs to be here for interface compat - // Only the providers that don't provide local user support use this - public Task Authenticate(string username, string password) - { - throw new NotImplementedException(); - } - - /// - // This is the version that we need to use for local users. Because reasons. - public Task Authenticate(string username, string password, User resolvedUser) - { - if (resolvedUser == null) - { - throw new AuthenticationException($"Specified user does not exist."); - } - - bool success = false; - - // As long as jellyfin supports passwordless users, we need this little block here to accommodate - if (!HasPassword(resolvedUser) && string.IsNullOrEmpty(password)) - { - return Task.FromResult(new ProviderAuthenticationResult - { - Username = username - }); - } - - byte[] passwordbytes = Encoding.UTF8.GetBytes(password); - - PasswordHash readyHash = PasswordHash.Parse(resolvedUser.Password); - if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id) - || _cryptographyProvider.DefaultHashMethod == readyHash.Id) - { - byte[] calculatedHash = _cryptographyProvider.ComputeHash( - readyHash.Id, - passwordbytes, - readyHash.Salt.ToArray()); - - if (readyHash.Hash.SequenceEqual(calculatedHash)) - { - success = true; - } - } - else - { - throw new AuthenticationException($"Requested crypto method not available in provider: {readyHash.Id}"); - } - - if (!success) - { - throw new AuthenticationException("Invalid username or password"); - } - - return Task.FromResult(new ProviderAuthenticationResult - { - Username = username - }); - } - - /// - public bool HasPassword(User user) - => !string.IsNullOrEmpty(user.Password); - - /// - public Task ChangePassword(User user, string newPassword) - { - if (string.IsNullOrEmpty(newPassword)) - { - user.Password = null; - return Task.CompletedTask; - } - - PasswordHash newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword); - user.Password = newPasswordHash.ToString(); - - return Task.CompletedTask; - } - - /// - public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) - { - if (newPassword != null) - { - newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword).ToString(); - } - - if (string.IsNullOrWhiteSpace(newPasswordHash)) - { - throw new ArgumentNullException(nameof(newPasswordHash)); - } - - user.EasyPassword = newPasswordHash; - } - - /// - public string GetEasyPasswordHash(User user) - { - return string.IsNullOrEmpty(user.EasyPassword) - ? null - : Hex.Encode(PasswordHash.Parse(user.EasyPassword).Hash); - } - - /// - /// Gets the hashed string. - /// - public string GetHashedString(User user, string str) - { - if (string.IsNullOrEmpty(user.Password)) - { - return _cryptographyProvider.CreatePasswordHash(str).ToString(); - } - - // TODO: make use of iterations parameter? - PasswordHash passwordHash = PasswordHash.Parse(user.Password); - var salt = passwordHash.Salt.ToArray(); - return new PasswordHash( - passwordHash.Id, - _cryptographyProvider.ComputeHash( - passwordHash.Id, - Encoding.UTF8.GetBytes(str), - salt), - salt, - passwordHash.Parameters.ToDictionary(x => x.Key, y => y.Value)).ToString(); - } - - public ReadOnlySpan GetHashed(User user, string str) - { - if (string.IsNullOrEmpty(user.Password)) - { - return _cryptographyProvider.CreatePasswordHash(str).Hash; - } - - // TODO: make use of iterations parameter? - PasswordHash passwordHash = PasswordHash.Parse(user.Password); - return _cryptographyProvider.ComputeHash( - passwordHash.Id, - Encoding.UTF8.GetBytes(str), - passwordHash.Salt.ToArray()); - } - } -} diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs deleted file mode 100644 index 6c6fbd86f3..0000000000 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ /dev/null @@ -1,140 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Security.Cryptography; -using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Users; - -namespace Emby.Server.Implementations.Library -{ - /// - /// The default password reset provider. - /// - public class DefaultPasswordResetProvider : IPasswordResetProvider - { - private const string BaseResetFileName = "passwordreset"; - - private readonly IJsonSerializer _jsonSerializer; - private readonly IUserManager _userManager; - - private readonly string _passwordResetFileBase; - private readonly string _passwordResetFileBaseDir; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration manager. - /// The JSON serializer. - /// The user manager. - public DefaultPasswordResetProvider( - IServerConfigurationManager configurationManager, - IJsonSerializer jsonSerializer, - IUserManager userManager) - { - _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath; - _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, BaseResetFileName); - _jsonSerializer = jsonSerializer; - _userManager = userManager; - } - - /// - public string Name => "Default Password Reset Provider"; - - /// - public bool IsEnabled => true; - - /// - public async Task RedeemPasswordResetPin(string pin) - { - SerializablePasswordReset spr; - List usersreset = new List(); - foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*")) - { - using (var str = File.OpenRead(resetfile)) - { - spr = await _jsonSerializer.DeserializeFromStreamAsync(str).ConfigureAwait(false); - } - - if (spr.ExpirationDate < DateTime.Now) - { - File.Delete(resetfile); - } - else if (string.Equals( - spr.Pin.Replace("-", string.Empty, StringComparison.Ordinal), - pin.Replace("-", string.Empty, StringComparison.Ordinal), - StringComparison.InvariantCultureIgnoreCase)) - { - var resetUser = _userManager.GetUserByName(spr.UserName); - if (resetUser == null) - { - throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found"); - } - - await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); - usersreset.Add(resetUser.Name); - File.Delete(resetfile); - } - } - - if (usersreset.Count < 1) - { - throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}"); - } - else - { - return new PinRedeemResult - { - Success = true, - UsersReset = usersreset.ToArray() - }; - } - } - - /// - public async Task StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork) - { - string pin = string.Empty; - using (var cryptoRandom = RandomNumberGenerator.Create()) - { - byte[] bytes = new byte[4]; - cryptoRandom.GetBytes(bytes); - pin = BitConverter.ToString(bytes); - } - - DateTime expireTime = DateTime.Now.AddMinutes(30); - string filePath = _passwordResetFileBase + user.InternalId + ".json"; - SerializablePasswordReset spr = new SerializablePasswordReset - { - ExpirationDate = expireTime, - Pin = pin, - PinFile = filePath, - UserName = user.Name - }; - - using (FileStream fileStream = File.OpenWrite(filePath)) - { - _jsonSerializer.SerializeToStream(spr, fileStream); - await fileStream.FlushAsync().ConfigureAwait(false); - } - - return new ForgotPasswordResult - { - Action = ForgotPasswordAction.PinCode, - PinExpirationDate = expireTime, - PinFile = filePath - }; - } - - private class SerializablePasswordReset : PasswordPinCreationResult - { - public string Pin { get; set; } - - public string UserName { get; set; } - } - } -} diff --git a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs b/Emby.Server.Implementations/Library/InvalidAuthProvider.cs deleted file mode 100644 index dc61aacd7b..0000000000 --- a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Threading.Tasks; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Entities; - -namespace Emby.Server.Implementations.Library -{ - /// - /// An invalid authentication provider. - /// - public class InvalidAuthProvider : IAuthenticationProvider - { - /// - public string Name => "InvalidOrMissingAuthenticationProvider"; - - /// - public bool IsEnabled => true; - - /// - public Task Authenticate(string username, string password) - { - throw new AuthenticationException("User Account cannot login with this provider. The Normal provider for this user cannot be found"); - } - - /// - public bool HasPassword(User user) - { - return true; - } - - /// - public Task ChangePassword(User user, string newPassword) - { - return Task.CompletedTask; - } - - /// - public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) - { - // Nothing here - } - - /// - public string GetPasswordHash(User user) - { - return string.Empty; - } - - /// - public string GetEasyPasswordHash(User user) - { - return string.Empty; - } - } -} diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 0b86b2db7e..e7cd7512c3 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -17,6 +17,7 @@ using Emby.Server.Implementations.Library.Resolvers; using Emby.Server.Implementations.Library.Validators; using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.ScheduledTasks; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; @@ -1470,7 +1471,7 @@ namespace Emby.Server.Implementations.Library query.Parent = null; } - private void AddUserToQuery(InternalItemsQuery query, User user, bool allowExternalContent = true) + private void AddUserToQuery(InternalItemsQuery query, Jellyfin.Data.Entities.User user, bool allowExternalContent = true) { if (query.AncestorIds.Length == 0 && query.ParentId.Equals(Guid.Empty) && @@ -1491,7 +1492,7 @@ namespace Emby.Server.Implementations.Library } } - private IEnumerable GetTopParentIdsForQuery(BaseItem item, User user) + private IEnumerable GetTopParentIdsForQuery(BaseItem item, Jellyfin.Data.Entities.User user) { if (item is UserView view) { @@ -1524,7 +1525,8 @@ namespace Emby.Server.Implementations.Library } // Handle grouping - if (user != null && !string.IsNullOrEmpty(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType) && user.Configuration.GroupedFolders.Length > 0) + if (user != null && !string.IsNullOrEmpty(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType) + && user.GetPreference(PreferenceKind.GroupedFolders).Length > 0) { return GetUserRootFolder() .GetChildren(user, true) @@ -1557,7 +1559,7 @@ namespace Emby.Server.Implementations.Library /// The item. /// The user. /// IEnumerable{System.String}. - public async Task> GetIntros(BaseItem item, User user) + public async Task> GetIntros(BaseItem item, Jellyfin.Data.Entities.User user) { var tasks = IntroProviders .OrderBy(i => i.GetType().Name.Contains("Default", StringComparison.OrdinalIgnoreCase) ? 1 : 0) @@ -1579,7 +1581,7 @@ namespace Emby.Server.Implementations.Library /// The item. /// The user. /// Task<IEnumerable<IntroInfo>>. - private async Task> GetIntros(IIntroProvider provider, BaseItem item, User user) + private async Task> GetIntros(IIntroProvider provider, BaseItem item, Jellyfin.Data.Entities.User user) { try { @@ -1680,7 +1682,7 @@ namespace Emby.Server.Implementations.Library /// The sort by. /// The sort order. /// IEnumerable{BaseItem}. - public IEnumerable Sort(IEnumerable items, User user, IEnumerable sortBy, SortOrder sortOrder) + public IEnumerable Sort(IEnumerable items, Jellyfin.Data.Entities.User user, IEnumerable sortBy, SortOrder sortOrder) { var isFirst = true; @@ -1703,7 +1705,7 @@ namespace Emby.Server.Implementations.Library return orderedItems ?? items; } - public IEnumerable Sort(IEnumerable items, User user, IEnumerable> orderByList) + public IEnumerable Sort(IEnumerable items, Jellyfin.Data.Entities.User user, IEnumerable> orderByList) { var isFirst = true; @@ -1740,7 +1742,7 @@ namespace Emby.Server.Implementations.Library /// The name. /// The user. /// IBaseItemComparer. - private IBaseItemComparer GetComparer(string name, User user) + private IBaseItemComparer GetComparer(string name, Jellyfin.Data.Entities.User user) { var comparer = Comparers.FirstOrDefault(c => string.Equals(name, c.Name, StringComparison.OrdinalIgnoreCase)); @@ -2072,7 +2074,7 @@ namespace Emby.Server.Implementations.Library private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24); public UserView GetNamedView( - User user, + Jellyfin.Data.Entities.User user, string name, string viewType, string sortName) @@ -2125,7 +2127,7 @@ namespace Emby.Server.Implementations.Library } public UserView GetNamedView( - User user, + Jellyfin.Data.Entities.User user, string name, Guid parentId, string viewType, diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 01fe98f3af..25af690586 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Entities; @@ -14,7 +15,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -145,7 +145,7 @@ namespace Emby.Server.Implementations.Library }); } - public async Task> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken) + public async Task> GetPlaybackMediaSources(BaseItem item, Jellyfin.Data.Entities.User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken) { var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user); @@ -190,10 +190,7 @@ namespace Emby.Server.Implementations.Library { if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) { - if (!user.Policy.EnableAudioPlaybackTranscoding) - { - source.SupportsTranscoding = false; - } + source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding); } } } @@ -312,7 +309,7 @@ namespace Emby.Server.Implementations.Library return sources.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); } - public List GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, User user = null) + public List GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, Jellyfin.Data.Entities.User user = null) { if (item == null) { @@ -350,9 +347,11 @@ namespace Emby.Server.Implementations.Library return new string[] { language }; } - private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection) + private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, Jellyfin.Data.Entities.User user, bool allowRememberingSelection) { - if (userData.SubtitleStreamIndex.HasValue && user.Configuration.RememberSubtitleSelections && user.Configuration.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection) + if (userData.SubtitleStreamIndex.HasValue + && user.RememberSubtitleSelections + && user.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection) { var index = userData.SubtitleStreamIndex.Value; // Make sure the saved index is still valid @@ -363,26 +362,27 @@ namespace Emby.Server.Implementations.Library } } - var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference) - ? Array.Empty() : NormalizeLanguage(user.Configuration.SubtitleLanguagePreference); + + var preferredSubs = string.IsNullOrEmpty(user.SubtitleLanguagePreference) + ? Array.Empty() : NormalizeLanguage(user.SubtitleLanguagePreference); var defaultAudioIndex = source.DefaultAudioStreamIndex; var audioLangage = defaultAudioIndex == null ? null : source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault(); - source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex(source.MediaStreams, + source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex( + source.MediaStreams, preferredSubs, - user.Configuration.SubtitleMode, + user.SubtitleMode, audioLangage); - MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, - user.Configuration.SubtitleMode, audioLangage); + MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLangage); } - private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection) + private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, Jellyfin.Data.Entities.User user, bool allowRememberingSelection) { - if (userData.AudioStreamIndex.HasValue && user.Configuration.RememberAudioSelections && allowRememberingSelection) + if (userData.AudioStreamIndex.HasValue && user.RememberAudioSelections && allowRememberingSelection) { var index = userData.AudioStreamIndex.Value; // Make sure the saved index is still valid @@ -393,14 +393,14 @@ namespace Emby.Server.Implementations.Library } } - var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference) + var preferredAudio = string.IsNullOrEmpty(user.AudioLanguagePreference) ? Array.Empty() - : NormalizeLanguage(user.Configuration.AudioLanguagePreference); + : NormalizeLanguage(user.AudioLanguagePreference); - source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.Configuration.PlayDefaultAudioTrack); + source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.PlayDefaultAudioTrack); } - public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user) + public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, Jellyfin.Data.Entities.User user) { // Item would only be null if the app didn't supply ItemId as part of the live stream open request var mediaType = item == null ? MediaType.Video : item.MediaType; @@ -560,17 +560,14 @@ namespace Emby.Server.Implementations.Library { videoStream.BitRate = 30000000; } - else if (width >= 1900) { videoStream.BitRate = 20000000; } - else if (width >= 1200) { videoStream.BitRate = 8000000; } - else if (width >= 700) { videoStream.BitRate = 2000000; @@ -674,13 +671,14 @@ namespace Emby.Server.Implementations.Library mediaSource.AnalyzeDurationMs = 3000; } - mediaInfo = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest + mediaInfo = await _mediaEncoder.GetMediaInfo( + new MediaInfoRequest { MediaSource = mediaSource, MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video, ExtractChapters = false - - }, cancellationToken).ConfigureAwait(false); + }, + cancellationToken).ConfigureAwait(false); if (cacheFilePath != null) { diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs index e27145a1d2..a177138b7c 100644 --- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Jellyfin.Data.Enums; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index 1ec5783716..ad8c70f5eb 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.Library _libraryManager = libraryManager; } - public List GetInstantMixFromSong(Audio item, User user, DtoOptions dtoOptions) + public List GetInstantMixFromSong(Audio item, Jellyfin.Data.Entities.User user, DtoOptions dtoOptions) { var list = new List /// The user object. /// The item. - private void OnPlaybackStart(User user, BaseItem item) + private void OnPlaybackStart(Jellyfin.Data.Entities.User user, BaseItem item) { var data = _userDataManager.GetUserData(user, item); @@ -754,7 +764,7 @@ namespace Emby.Server.Implementations.Session StartIdleCheckTimer(); } - private void OnPlaybackProgress(User user, BaseItem item, PlaybackProgressInfo info) + private void OnPlaybackProgress(Jellyfin.Data.Entities.User user, BaseItem item, PlaybackProgressInfo info) { var data = _userDataManager.GetUserData(user, item); @@ -780,11 +790,11 @@ namespace Emby.Server.Implementations.Session } } - private static bool UpdatePlaybackSettings(User user, PlaybackProgressInfo info, UserItemData data) + private static bool UpdatePlaybackSettings(Jellyfin.Data.Entities.User user, PlaybackProgressInfo info, UserItemData data) { var changed = false; - if (user.Configuration.RememberAudioSelections) + if (user.RememberAudioSelections) { if (data.AudioStreamIndex != info.AudioStreamIndex) { @@ -801,7 +811,7 @@ namespace Emby.Server.Implementations.Session } } - if (user.Configuration.RememberSubtitleSelections) + if (user.RememberSubtitleSelections) { if (data.SubtitleStreamIndex != info.SubtitleStreamIndex) { @@ -940,7 +950,7 @@ namespace Emby.Server.Implementations.Session _logger); } - private bool OnPlaybackStopped(User user, BaseItem item, long? positionTicks, bool playbackFailed) + private bool OnPlaybackStopped(Jellyfin.Data.Entities.User user, BaseItem item, long? positionTicks, bool playbackFailed) { bool playedToCompletion = false; @@ -1112,13 +1122,13 @@ namespace Emby.Server.Implementations.Session if (items.Any(i => i.GetPlayAccess(user) != PlayAccess.Full)) { throw new ArgumentException( - string.Format(CultureInfo.InvariantCulture, "{0} is not allowed to play media.", user.Name)); + string.Format(CultureInfo.InvariantCulture, "{0} is not allowed to play media.", user.Username)); } } if (user != null && command.ItemIds.Length == 1 - && user.Configuration.EnableNextEpisodeAutoPlay + && user.EnableNextEpisodeAutoPlay && _libraryManager.GetItemById(command.ItemIds[0]) is Episode episode) { var series = episode.Series; @@ -1154,7 +1164,7 @@ namespace Emby.Server.Implementations.Session await SendMessageToSession(session, "Play", command, cancellationToken).ConfigureAwait(false); } - private IEnumerable TranslateItemForPlayback(Guid id, User user) + private IEnumerable TranslateItemForPlayback(Guid id, Jellyfin.Data.Entities.User user) { var item = _libraryManager.GetItemById(id); @@ -1173,7 +1183,7 @@ namespace Emby.Server.Implementations.Session DtoOptions = new DtoOptions(false) { EnableImages = false, - Fields = new ItemFields[] + Fields = new[] { ItemFields.SortName } @@ -1207,7 +1217,7 @@ namespace Emby.Server.Implementations.Session return new[] { item }; } - private IEnumerable TranslateItemForInstantMix(Guid id, User user) + private IEnumerable TranslateItemForInstantMix(Guid id, Jellyfin.Data.Entities.User user) { var item = _libraryManager.GetItemById(id); @@ -1335,7 +1345,7 @@ namespace Emby.Server.Implementations.Session list.Add(new SessionUserInfo { UserId = userId, - UserName = user.Name + UserName = user.Username }); session.AdditionalUsers = list.ToArray(); @@ -1390,7 +1400,7 @@ namespace Emby.Server.Implementations.Session { CheckDisposed(); - User user = null; + Jellyfin.Data.Entities.User user = null; if (request.UserId != Guid.Empty) { user = _userManager.GetUserById(request.UserId); @@ -1446,7 +1456,7 @@ namespace Emby.Server.Implementations.Session return returnResult; } - private string GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName) + private string GetAuthorizationToken(Jellyfin.Data.Entities.User user, string deviceId, string app, string appVersion, string deviceName) { var existing = _authRepo.Get( new AuthenticationInfoQuery @@ -1495,7 +1505,7 @@ namespace Emby.Server.Implementations.Session DeviceName = deviceName, UserId = user.Id, AccessToken = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture), - UserName = user.Name + UserName = user.Username }; _logger.LogInformation("Creating new access token for user {0}", user.Id); @@ -1692,15 +1702,15 @@ namespace Emby.Server.Implementations.Session return info; } - private string GetImageCacheTag(BaseItem item, ImageType type) + private string GetImageCacheTag(Jellyfin.Data.Entities.User user) { try { - return _imageProcessor.GetImageCacheTag(item, type); + return _imageProcessor.GetImageCacheTag(user); } - catch (Exception ex) + catch (Exception e) { - _logger.LogError(ex, "Error getting image information for {Type}", type); + _logger.LogError(e, "Error getting image information for profile image"); return null; } } @@ -1809,7 +1819,10 @@ namespace Emby.Server.Implementations.Session { CheckDisposed(); - var adminUserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id).ToList(); + var adminUserIds = _userManager.Users + .Where(i => i.HasPermission(PermissionKind.IsAdministrator)) + .Select(i => i.Id) + .ToList(); return SendMessageToUserSessions(adminUserIds, name, data, cancellationToken); } diff --git a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs index 623675157d..3cc2943713 100644 --- a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs @@ -12,7 +12,7 @@ namespace Emby.Server.Implementations.Sorting /// Gets or sets the user. /// /// The user. - public User User { get; set; } + public Jellyfin.Data.Entities.User User { get; set; } /// /// Gets or sets the user manager. diff --git a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs index 73f59f8cd6..57a1a00d9e 100644 --- a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs @@ -15,7 +15,7 @@ namespace Emby.Server.Implementations.Sorting /// Gets or sets the user. /// /// The user. - public User User { get; set; } + public Jellyfin.Data.Entities.User User { get; set; } /// /// Gets or sets the user manager. diff --git a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs index 66de05a6a2..c9feca7e35 100644 --- a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs @@ -11,7 +11,7 @@ namespace Emby.Server.Implementations.Sorting /// Gets or sets the user. /// /// The user. - public User User { get; set; } + public Jellyfin.Data.Entities.User User { get; set; } /// /// Compares the specified x. diff --git a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs index da3f3dd25b..6f383e65f4 100644 --- a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs @@ -11,7 +11,7 @@ namespace Emby.Server.Implementations.Sorting /// Gets or sets the user. /// /// The user. - public User User { get; set; } + public Jellyfin.Data.Entities.User User { get; set; } /// /// Compares the specified x. diff --git a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs index d99d0eff21..4845fdc0d7 100644 --- a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs @@ -11,7 +11,7 @@ namespace Emby.Server.Implementations.Sorting /// Gets or sets the user. /// /// The user. - public User User { get; set; } + public Jellyfin.Data.Entities.User User { get; set; } /// /// Compares the specified x. diff --git a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs index eb74ce1bd0..99846db612 100644 --- a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs +++ b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs @@ -14,7 +14,7 @@ namespace Emby.Server.Implementations.Sorting /// Gets or sets the user. /// /// The user. - public User User { get; set; } + public Jellyfin.Data.Entities.User User { get; set; } /// /// Compares the specified x. diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index 4c2f24e6f2..905a1ea993 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -74,7 +75,8 @@ namespace Emby.Server.Implementations.TV { parents = _libraryManager.GetUserRootFolder().GetChildren(user, true) .Where(i => i is Folder) - .Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))) + .Where(i => !user.GetPreference(PreferenceKind.LatestItemExcludes) + .Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))) .ToArray(); } @@ -137,7 +139,7 @@ namespace Emby.Server.Implementations.TV return GetResult(episodes, request); } - public IEnumerable GetNextUpEpisodes(NextUpQuery request, User user, IEnumerable seriesKeys, DtoOptions dtoOptions) + public IEnumerable GetNextUpEpisodes(NextUpQuery request, Jellyfin.Data.Entities.User user, IEnumerable seriesKeys, DtoOptions dtoOptions) { // Avoid implicitly captured closure var currentUser = user; @@ -186,13 +188,13 @@ namespace Emby.Server.Implementations.TV /// Gets the next up. /// /// Task{Episode}. - private Tuple> GetNextUp(string seriesKey, User user, DtoOptions dtoOptions) + private Tuple> GetNextUp(string seriesKey, Jellyfin.Data.Entities.User user, DtoOptions dtoOptions) { var lastWatchedEpisode = _libraryManager.GetItemList(new InternalItemsQuery(user) { AncestorWithPresentationUniqueKey = null, SeriesPresentationUniqueKey = seriesKey, - IncludeItemTypes = new[] { typeof(Episode).Name }, + IncludeItemTypes = new[] { nameof(Episode) }, OrderBy = new[] { new ValueTuple(ItemSortBy.SortName, SortOrder.Descending) }, IsPlayed = true, Limit = 1, @@ -205,7 +207,6 @@ namespace Emby.Server.Implementations.TV }, EnableImages = false } - }).FirstOrDefault(); Func getEpisode = () => @@ -220,7 +221,7 @@ namespace Emby.Server.Implementations.TV IsPlayed = false, IsVirtualItem = false, ParentIndexNumberNotEquals = 0, - MinSortName = lastWatchedEpisode == null ? null : lastWatchedEpisode.SortName, + MinSortName = lastWatchedEpisode?.SortName, DtoOptions = dtoOptions }).Cast().FirstOrDefault(); diff --git a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs index 26f7d9d2dd..4929e8897d 100644 --- a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs +++ b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs @@ -2,6 +2,7 @@ using System.Security.Claims; using System.Text.Encodings.Web; using System.Threading.Tasks; using Jellyfin.Api.Constants; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Net; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Logging; @@ -48,10 +49,10 @@ namespace Jellyfin.Api.Auth var claims = new[] { - new Claim(ClaimTypes.Name, user.Name), + new Claim(ClaimTypes.Name, user.Username), new Claim( ClaimTypes.Role, - value: user.Policy.IsAdministrator ? UserRoles.Administrator : UserRoles.User) + value: user.HasPermission(PermissionKind.IsAdministrator) ? UserRoles.Administrator : UserRoles.User) }; var identity = new ClaimsIdentity(claims, Scheme.Name); var principal = new ClaimsPrincipal(identity); diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index afc9b8f3da..f965d83f31 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -98,7 +98,7 @@ namespace Jellyfin.Api.Controllers var user = _userManager.Users.First(); return new StartupUserDto { - Name = user.Name, + Name = user.Username, Password = user.Password }; } @@ -113,7 +113,7 @@ namespace Jellyfin.Api.Controllers { var user = _userManager.Users.First(); - user.Name = startupUserDto.Name; + user.Username = startupUserDto.Name; _userManager.UpdateUser(user); diff --git a/Jellyfin.Data/DayOfWeekHelper.cs b/Jellyfin.Data/DayOfWeekHelper.cs new file mode 100644 index 0000000000..33410b732d --- /dev/null +++ b/Jellyfin.Data/DayOfWeekHelper.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using Jellyfin.Data.Enums; + +namespace Jellyfin.Data +{ + public static class DayOfWeekHelper + { + public static List GetDaysOfWeek(DynamicDayOfWeek day) + { + return GetDaysOfWeek(new List { day }); + } + + public static List GetDaysOfWeek(List days) + { + var list = new List(); + + if (days.Contains(DynamicDayOfWeek.Sunday) || + days.Contains(DynamicDayOfWeek.Weekend) || + days.Contains(DynamicDayOfWeek.Everyday)) + { + list.Add(DayOfWeek.Sunday); + } + + if (days.Contains(DynamicDayOfWeek.Saturday) || + days.Contains(DynamicDayOfWeek.Weekend) || + days.Contains(DynamicDayOfWeek.Everyday)) + { + list.Add(DayOfWeek.Saturday); + } + + if (days.Contains(DynamicDayOfWeek.Monday) || + days.Contains(DynamicDayOfWeek.Weekday) || + days.Contains(DynamicDayOfWeek.Everyday)) + { + list.Add(DayOfWeek.Monday); + } + + if (days.Contains(DynamicDayOfWeek.Tuesday) || + days.Contains(DynamicDayOfWeek.Weekday) || + days.Contains(DynamicDayOfWeek.Everyday)) + { + list.Add(DayOfWeek.Tuesday); + } + + if (days.Contains(DynamicDayOfWeek.Wednesday) || + days.Contains(DynamicDayOfWeek.Weekday) || + days.Contains(DynamicDayOfWeek.Everyday)) + { + list.Add(DayOfWeek.Wednesday); + } + + if (days.Contains(DynamicDayOfWeek.Thursday) || + days.Contains(DynamicDayOfWeek.Weekday) || + days.Contains(DynamicDayOfWeek.Everyday)) + { + list.Add(DayOfWeek.Thursday); + } + + if (days.Contains(DynamicDayOfWeek.Friday) || + days.Contains(DynamicDayOfWeek.Weekday) || + days.Contains(DynamicDayOfWeek.Everyday)) + { + list.Add(DayOfWeek.Friday); + } + + return list; + } + } +} diff --git a/Jellyfin.Data/Entities/AccessSchedule.cs b/Jellyfin.Data/Entities/AccessSchedule.cs new file mode 100644 index 0000000000..7966cdb50d --- /dev/null +++ b/Jellyfin.Data/Entities/AccessSchedule.cs @@ -0,0 +1,68 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Jellyfin.Data.Enums; + +namespace Jellyfin.Data.Entities +{ + public class AccessSchedule + { + /// + /// Initializes a new instance of the class. + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected AccessSchedule() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The day of the week. + /// The start hour. + /// The end hour. + public AccessSchedule(DynamicDayOfWeek dayOfWeek, double startHour, double endHour) + { + DayOfWeek = dayOfWeek; + StartHour = startHour; + EndHour = endHour; + } + + /// + /// Factory method + /// + /// The day of the week. + /// The start hour. + /// The end hour. + /// The newly created instance. + public static AccessSchedule CreateInstance(DynamicDayOfWeek dayOfWeek, double startHour, double endHour) + { + return new AccessSchedule(dayOfWeek, startHour, endHour); + } + + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; protected set; } + + /// + /// Gets or sets the day of week. + /// + /// The day of week. + [Required] + public DynamicDayOfWeek DayOfWeek { get; set; } + + /// + /// Gets or sets the start hour. + /// + /// The start hour. + [Required] + public double StartHour { get; set; } + + /// + /// Gets or sets the end hour. + /// + /// The end hour. + [Required] + public double EndHour { get; set; } + } +} diff --git a/Jellyfin.Data/Entities/ImageInfo.cs b/Jellyfin.Data/Entities/ImageInfo.cs new file mode 100644 index 0000000000..336c13b36a --- /dev/null +++ b/Jellyfin.Data/Entities/ImageInfo.cs @@ -0,0 +1,25 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Jellyfin.Data.Entities +{ + public class ImageInfo + { + public ImageInfo(string path) + { + Path = path; + LastModified = DateTime.UtcNow; + } + + [Key] + [Required] + + public int Id { get; protected set; } + + [Required] + public string Path { get; set; } + + [Required] + public DateTime LastModified { get; set; } + } +} diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index 715969dbf0..1aaa8a180e 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -1,18 +1,20 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Globalization; using System.Linq; -using System.Runtime.CompilerServices; +using Jellyfin.Data.Enums; namespace Jellyfin.Data.Entities { [Table("User")] - public partial class User + public class User { - partial void Init(); + /// + /// The values being delimited here are Guids, so commas work as they do not appear in Guids. + /// + private const char Delimiter = ','; /// /// Default constructor. Protected due to required properties, but present because EF needs it. @@ -23,69 +25,86 @@ namespace Jellyfin.Data.Entities Permissions = new HashSet(); ProviderMappings = new HashSet(); Preferences = new HashSet(); - - Init(); + AccessSchedules = new HashSet(); } /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// Public constructor with required data /// - public static User CreateUserUnsafe() + /// + /// + /// + /// + /// + /// + public User( + string username, + bool mustUpdatePassword, + string authenticationProviderId, + int invalidLoginAttemptCount, + SubtitlePlaybackMode subtitleMode, + bool playDefaultAudioTrack) { - return new User(); + if (string.IsNullOrEmpty(username)) + { + throw new ArgumentNullException(nameof(username)); + } + + if (string.IsNullOrEmpty(authenticationProviderId)) + { + throw new ArgumentNullException(nameof(authenticationProviderId)); + } + + Username = username; + MustUpdatePassword = mustUpdatePassword; + AuthenticationProviderId = authenticationProviderId; + InvalidLoginAttemptCount = invalidLoginAttemptCount; + SubtitleMode = subtitleMode; + PlayDefaultAudioTrack = playDefaultAudioTrack; + + Groups = new HashSet(); + Permissions = new HashSet(); + ProviderMappings = new HashSet(); + Preferences = new HashSet(); + AccessSchedules = new HashSet(); + + // Set default values + Id = Guid.NewGuid(); + DisplayMissingEpisodes = false; + DisplayCollectionsView = false; + HidePlayedInLatest = true; + RememberAudioSelections = true; + RememberSubtitleSelections = true; + EnableNextEpisodeAutoPlay = true; + EnableAutoLogin = false; } /// - /// Public constructor with required data + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. /// - /// - /// - /// - /// - /// - /// - /// - public User(string username, bool mustupdatepassword, string audiolanguagepreference, string authenticationproviderid, int invalidloginattemptcount, string subtitlemode, bool playdefaultaudiotrack) + public static User CreateUserUnsafe() { - if (string.IsNullOrEmpty(username)) throw new ArgumentNullException(nameof(username)); - this.Username = username; - - this.MustUpdatePassword = mustupdatepassword; - - if (string.IsNullOrEmpty(audiolanguagepreference)) throw new ArgumentNullException(nameof(audiolanguagepreference)); - this.AudioLanguagePreference = audiolanguagepreference; - - if (string.IsNullOrEmpty(authenticationproviderid)) throw new ArgumentNullException(nameof(authenticationproviderid)); - this.AuthenticationProviderId = authenticationproviderid; - - this.InvalidLoginAttemptCount = invalidloginattemptcount; - - if (string.IsNullOrEmpty(subtitlemode)) throw new ArgumentNullException(nameof(subtitlemode)); - this.SubtitleMode = subtitlemode; - - this.PlayDefaultAudioTrack = playdefaultaudiotrack; - - this.Groups = new HashSet(); - this.Permissions = new HashSet(); - this.ProviderMappings = new HashSet(); - this.Preferences = new HashSet(); - - Init(); + return new User(); } /// /// Static create function (for use in LINQ queries, etc.) /// /// - /// - /// - /// - /// - /// - /// - public static User Create(string username, bool mustupdatepassword, string audiolanguagepreference, string authenticationproviderid, int invalidloginattemptcount, string subtitlemode, bool playdefaultaudiotrack) + /// + /// + /// + /// + /// + public static User Create( + string username, + bool mustUpdatePassword, + string authenticationProviderId, + int invalidLoginAttemptCount, + SubtitlePlaybackMode subtitleMode, + bool playDefaultAudioTrack) { - return new User(username, mustupdatepassword, audiolanguagepreference, authenticationproviderid, invalidloginattemptcount, subtitlemode, playdefaultaudiotrack); + return new User(username, mustUpdatePassword, authenticationProviderId, invalidLoginAttemptCount, subtitleMode, playDefaultAudioTrack); } /************************************************************************* @@ -97,8 +116,7 @@ namespace Jellyfin.Data.Entities /// [Key] [Required] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public Guid Id { get; protected set; } /// /// Required, Max length = 255 @@ -115,6 +133,13 @@ namespace Jellyfin.Data.Entities [StringLength(65535)] public string Password { get; set; } + /// + /// Max length = 65535. + /// + [MaxLength(65535)] + [StringLength(65535)] + public string EasyPassword { get; set; } + /// /// Required /// @@ -122,9 +147,8 @@ namespace Jellyfin.Data.Entities public bool MustUpdatePassword { get; set; } /// - /// Required, Max length = 255 + /// Max length = 255. /// - [Required] [MaxLength(255)] [StringLength(255)] public string AudioLanguagePreference { get; set; } @@ -137,12 +161,10 @@ namespace Jellyfin.Data.Entities [StringLength(255)] public string AuthenticationProviderId { get; set; } - /// - /// Max length = 65535 - /// - [MaxLength(65535)] - [StringLength(65535)] - public string GroupedFolders { get; set; } + [Required] + [MaxLength(255)] + [StringLength(255)] + public string PasswordResetProviderId { get; set; } /// /// Required @@ -150,36 +172,17 @@ namespace Jellyfin.Data.Entities [Required] public int InvalidLoginAttemptCount { get; set; } - /// - /// Max length = 65535 - /// - [MaxLength(65535)] - [StringLength(65535)] - public string LatestItemExcludes { get; set; } + public DateTime LastActivityDate { get; set; } - public int? LoginAttemptsBeforeLockout { get; set; } + public DateTime LastLoginDate { get; set; } - /// - /// Max length = 65535 - /// - [MaxLength(65535)] - [StringLength(65535)] - public string MyMediaExcludes { get; set; } - - /// - /// Max length = 65535 - /// - [MaxLength(65535)] - [StringLength(65535)] - public string OrderedViews { get; set; } + public int? LoginAttemptsBeforeLockout { get; set; } /// - /// Required, Max length = 255 + /// Required. /// [Required] - [MaxLength(255)] - [StringLength(255)] - public string SubtitleMode { get; set; } + public SubtitlePlaybackMode SubtitleMode { get; set; } /// /// Required @@ -192,21 +195,47 @@ namespace Jellyfin.Data.Entities /// [MaxLength(255)] [StringLength(255)] - public string SubtitleLanguagePrefernce { get; set; } + public string SubtitleLanguagePreference { get; set; } + + [Required] + public bool DisplayMissingEpisodes { get; set; } + + [Required] + public bool DisplayCollectionsView { get; set; } + + [Required] + public bool EnableLocalPassword { get; set; } + + [Required] + public bool HidePlayedInLatest { get; set; } + + [Required] + public bool RememberAudioSelections { get; set; } - public bool? DisplayMissingEpisodes { get; set; } + [Required] + public bool RememberSubtitleSelections { get; set; } - public bool? DisplayCollectionsView { get; set; } + [Required] + public bool EnableNextEpisodeAutoPlay { get; set; } + + [Required] + public bool EnableAutoLogin { get; set; } - public bool? HidePlayedInLatest { get; set; } + [Required] + public bool EnableUserPreferenceAccess { get; set; } - public bool? RememberAudioSelections { get; set; } + public int? MaxParentalAgeRating { get; set; } - public bool? RememberSubtitleSelections { get; set; } + public int? RemoteClientBitrateLimit { get; set; } - public bool? EnableNextEpisodeAutoPlay { get; set; } + /// + /// This is a temporary stopgap for until the library db is migrated. + /// This corresponds to the value of the index of this user in the library db. + /// + [Required] + public long InternalId { get; set; } - public bool? EnableUserPreferenceAccess { get; set; } + public ImageInfo ProfileImage { get; set; } /// /// Required, ConcurrenyToken @@ -224,17 +253,76 @@ namespace Jellyfin.Data.Entities * Navigation properties *************************************************************************/ [ForeignKey("Group_Groups_Id")] - public virtual ICollection Groups { get; protected set; } + public ICollection Groups { get; protected set; } [ForeignKey("Permission_Permissions_Id")] - public virtual ICollection Permissions { get; protected set; } + public ICollection Permissions { get; protected set; } [ForeignKey("ProviderMapping_ProviderMappings_Id")] - public virtual ICollection ProviderMappings { get; protected set; } + public ICollection ProviderMappings { get; protected set; } [ForeignKey("Preference_Preferences_Id")] - public virtual ICollection Preferences { get; protected set; } + public ICollection Preferences { get; protected set; } + public ICollection AccessSchedules { get; protected set; } + + public bool HasPermission(PermissionKind permission) + { + return Permissions.Select(p => p.Kind).Contains(permission); + } + + public void SetPermission(PermissionKind kind, bool value) + { + var permissionObj = Permissions.First(p => p.Kind == kind); + permissionObj.Value = value; + } + + public string[] GetPreference(PreferenceKind preference) + { + return Preferences + .Where(p => p.Kind == preference) + .Select(p => p.Value) + .First() + .Split(Delimiter); + } + + public void SetPreference(PreferenceKind preference, string[] values) + { + var pref = Preferences.First(p => p.Kind == preference); + + pref.Value = string.Join(Delimiter.ToString(CultureInfo.InvariantCulture), values); + } + + public bool IsParentalScheduleAllowed() + { + var schedules = this.AccessSchedules; + + return schedules.Count == 0 || schedules.Any(i => IsParentalScheduleAllowed(i, DateTime.Now)); + } + + public bool IsFolderGrouped(Guid id) + { + return GetPreference(PreferenceKind.GroupedFolders).Any(i => new Guid(i) == id); + } + + private bool IsParentalScheduleAllowed(AccessSchedule schedule, DateTime date) + { + if (date.Kind != DateTimeKind.Utc) + { + throw new ArgumentException("Utc date expected"); + } + + var localTime = date.ToLocalTime(); + + return DayOfWeekHelper.GetDaysOfWeek(schedule.DayOfWeek).Contains(localTime.DayOfWeek) && + IsWithinTime(schedule, localTime); + } + + private bool IsWithinTime(AccessSchedule schedule, DateTime localTime) + { + var hour = localTime.TimeOfDay.TotalHours; + + return hour >= schedule.StartHour && hour <= schedule.EndHour; + } } } - diff --git a/Jellyfin.Data/Enums/DynamicDayOfWeek.cs b/Jellyfin.Data/Enums/DynamicDayOfWeek.cs new file mode 100644 index 0000000000..a33cd9d1cd --- /dev/null +++ b/Jellyfin.Data/Enums/DynamicDayOfWeek.cs @@ -0,0 +1,18 @@ +#pragma warning disable CS1591 + +namespace Jellyfin.Data.Enums +{ + public enum DynamicDayOfWeek + { + Sunday = 0, + Monday = 1, + Tuesday = 2, + Wednesday = 3, + Thursday = 4, + Friday = 5, + Saturday = 6, + Everyday = 7, + Weekday = 8, + Weekend = 9 + } +} diff --git a/Jellyfin.Data/Enums/PermissionKind.cs b/Jellyfin.Data/Enums/PermissionKind.cs index 4447fdb773..df18261e61 100644 --- a/Jellyfin.Data/Enums/PermissionKind.cs +++ b/Jellyfin.Data/Enums/PermissionKind.cs @@ -1,14 +1,11 @@ -using System; - namespace Jellyfin.Data.Enums { - public enum PermissionKind : Int32 + public enum PermissionKind { IsAdministrator, IsHidden, IsDisabled, - BlockUnrateditems, - EnbleSharedDeviceControl, + EnableSharedDeviceControl, EnableRemoteAccess, EnableLiveTvManagement, EnableLiveTvAccess, @@ -23,6 +20,8 @@ namespace Jellyfin.Data.Enums EnableAllChannels, EnableAllFolders, EnablePublicSharing, - AccessSchedules + EnableRemoteControlOfOtherUsers, + EnablePlaybackRemuxing, + ForceRemoteSourceTranscoding } } diff --git a/Jellyfin.Data/Enums/PreferenceKind.cs b/Jellyfin.Data/Enums/PreferenceKind.cs index e66a51cae1..34e20ead6a 100644 --- a/Jellyfin.Data/Enums/PreferenceKind.cs +++ b/Jellyfin.Data/Enums/PreferenceKind.cs @@ -2,14 +2,19 @@ using System; namespace Jellyfin.Data.Enums { - public enum PreferenceKind : Int32 + public enum PreferenceKind { - MaxParentalRating, BlockedTags, - RemoteClientBitrateLimit, + BlockedChannels, + BlockedMediaFolders, EnabledDevices, EnabledChannels, EnabledFolders, - EnableContentDeletionFromFolders + EnableContentDeletionFromFolders, + LatestItemExcludes, + MyMediaExcludes, + GroupedFolders, + BlockUnratedItems, + OrderedViews } } diff --git a/Jellyfin.Data/Enums/SubtitlePlaybackMode.cs b/Jellyfin.Data/Enums/SubtitlePlaybackMode.cs new file mode 100644 index 0000000000..c8fc211593 --- /dev/null +++ b/Jellyfin.Data/Enums/SubtitlePlaybackMode.cs @@ -0,0 +1,13 @@ +#pragma warning disable CS1591 + +namespace Jellyfin.Data.Enums +{ + public enum SubtitlePlaybackMode + { + Default = 0, + Always = 1, + OnlyForced = 2, + None = 3, + Smart = 4 + } +} diff --git a/Jellyfin.Data/Enums/UnratedItem.cs b/Jellyfin.Data/Enums/UnratedItem.cs new file mode 100644 index 0000000000..5259e77394 --- /dev/null +++ b/Jellyfin.Data/Enums/UnratedItem.cs @@ -0,0 +1,17 @@ +#pragma warning disable CS1591 + +namespace Jellyfin.Data.Enums +{ + public enum UnratedItem + { + Movie, + Trailer, + Series, + Music, + Book, + LiveTvChannel, + LiveTvProgram, + ChannelContent, + Other + } +} diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 8b6b7cacc2..7e13146902 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -20,7 +20,7 @@ namespace Jellyfin.Server.Implementations public virtual DbSet Permissions { get; set; } public virtual DbSet Preferences { get; set; } public virtual DbSet ProviderMappings { get; set; } - public virtual DbSet Users { get; set; } + public virtual DbSet Users { get; set; } /*public virtual DbSet Artwork { get; set; } public virtual DbSet Books { get; set; } public virtual DbSet BookMetadata { get; set; } diff --git a/Jellyfin.Server.Implementations/User/DefaultAuthenticationProvider.cs b/Jellyfin.Server.Implementations/User/DefaultAuthenticationProvider.cs new file mode 100644 index 0000000000..024500bf81 --- /dev/null +++ b/Jellyfin.Server.Implementations/User/DefaultAuthenticationProvider.cs @@ -0,0 +1,185 @@ +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Common; +using MediaBrowser.Common.Cryptography; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Model.Cryptography; + +namespace Jellyfin.Server.Implementations.User +{ + /// + /// The default authentication provider. + /// + public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser + { + private readonly ICryptoProvider _cryptographyProvider; + + /// + /// Initializes a new instance of the class. + /// + /// The cryptography provider. + public DefaultAuthenticationProvider(ICryptoProvider cryptographyProvider) + { + _cryptographyProvider = cryptographyProvider; + } + + /// + public string Name => "Default"; + + /// + public bool IsEnabled => true; + + /// + // This is dumb and an artifact of the backwards way auth providers were designed. + // This version of authenticate was never meant to be called, but needs to be here for interface compat + // Only the providers that don't provide local user support use this + public Task Authenticate(string username, string password) + { + throw new NotImplementedException(); + } + + /// + // This is the version that we need to use for local users. Because reasons. + public Task Authenticate(string username, string password, Data.Entities.User resolvedUser) + { + if (resolvedUser == null) + { + throw new AuthenticationException("Specified user does not exist."); + } + + bool success = false; + + // As long as jellyfin supports passwordless users, we need this little block here to accommodate + if (!HasPassword(resolvedUser)) + { + return Task.FromResult(new ProviderAuthenticationResult + { + Username = username + }); + } + + byte[] passwordBytes = Encoding.UTF8.GetBytes(password); + + PasswordHash readyHash = PasswordHash.Parse(resolvedUser.Password); + if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id) + || _cryptographyProvider.DefaultHashMethod == readyHash.Id) + { + byte[] calculatedHash = _cryptographyProvider.ComputeHash( + readyHash.Id, + passwordBytes, + readyHash.Salt.ToArray()); + + if (readyHash.Hash.SequenceEqual(calculatedHash)) + { + success = true; + } + } + else + { + throw new AuthenticationException($"Requested crypto method not available in provider: {readyHash.Id}"); + } + + if (!success) + { + throw new AuthenticationException("Invalid username or password"); + } + + return Task.FromResult(new ProviderAuthenticationResult + { + Username = username + }); + } + + /// + public bool HasPassword(Data.Entities.User user) + => !string.IsNullOrEmpty(user.Password); + + /// + public Task ChangePassword(Data.Entities.User user, string newPassword) + { + if (string.IsNullOrEmpty(newPassword)) + { + user.Password = null; + return Task.CompletedTask; + } + + PasswordHash newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword); + user.Password = newPasswordHash.ToString(); + + return Task.CompletedTask; + } + + /// + public void ChangeEasyPassword(Data.Entities.User user, string newPassword, string newPasswordHash) + { + if (newPassword != null) + { + newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword).ToString(); + } + + if (string.IsNullOrWhiteSpace(newPasswordHash)) + { + throw new ArgumentNullException(nameof(newPasswordHash)); + } + + user.EasyPassword = newPasswordHash; + } + + /// + public string GetEasyPasswordHash(Data.Entities.User user) + { + return string.IsNullOrEmpty(user.EasyPassword) + ? null + : Hex.Encode(PasswordHash.Parse(user.EasyPassword).Hash); + } + + /// + /// Hashes the provided string. + /// + /// The user. + /// The string to hash. + /// The hashed string. + public string GetHashedString(Data.Entities.User user, string str) + { + if (string.IsNullOrEmpty(user.Password)) + { + return _cryptographyProvider.CreatePasswordHash(str).ToString(); + } + + // TODO: make use of iterations parameter? + PasswordHash passwordHash = PasswordHash.Parse(user.Password); + var salt = passwordHash.Salt.ToArray(); + return new PasswordHash( + passwordHash.Id, + _cryptographyProvider.ComputeHash( + passwordHash.Id, + Encoding.UTF8.GetBytes(str), + salt), + salt, + passwordHash.Parameters.ToDictionary(x => x.Key, y => y.Value)).ToString(); + } + + /// + /// Hashes the provided string. + /// + /// The user. + /// The string to hash. + /// The hashed string. + public ReadOnlySpan GetHashed(Data.Entities.User user, string str) + { + if (string.IsNullOrEmpty(user.Password)) + { + return _cryptographyProvider.CreatePasswordHash(str).Hash; + } + + // TODO: make use of iterations parameter? + PasswordHash passwordHash = PasswordHash.Parse(user.Password); + return _cryptographyProvider.ComputeHash( + passwordHash.Id, + Encoding.UTF8.GetBytes(str), + passwordHash.Salt.ToArray()); + } + } +} diff --git a/Jellyfin.Server.Implementations/User/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/User/DefaultPasswordResetProvider.cs new file mode 100644 index 0000000000..80ab3ce000 --- /dev/null +++ b/Jellyfin.Server.Implementations/User/DefaultPasswordResetProvider.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Users; + +namespace Jellyfin.Server.Implementations.User +{ + /// + /// The default password reset provider. + /// + public class DefaultPasswordResetProvider : IPasswordResetProvider + { + private const string BaseResetFileName = "passwordreset"; + + private readonly IJsonSerializer _jsonSerializer; + private readonly IUserManager _userManager; + + private readonly string _passwordResetFileBase; + private readonly string _passwordResetFileBaseDir; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration manager. + /// The JSON serializer. + /// The user manager. + public DefaultPasswordResetProvider( + IServerConfigurationManager configurationManager, + IJsonSerializer jsonSerializer, + IUserManager userManager) + { + _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath; + _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, BaseResetFileName); + _jsonSerializer = jsonSerializer; + _userManager = userManager; + } + + /// + public string Name => "Default Password Reset Provider"; + + /// + public bool IsEnabled => true; + + /// + public async Task RedeemPasswordResetPin(string pin) + { + SerializablePasswordReset spr; + List usersReset = new List(); + foreach (var resetFile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*")) + { + await using (var str = File.OpenRead(resetFile)) + { + spr = await _jsonSerializer.DeserializeFromStreamAsync(str).ConfigureAwait(false); + } + + if (spr.ExpirationDate < DateTime.Now) + { + File.Delete(resetFile); + } + else if (string.Equals( + spr.Pin.Replace("-", string.Empty, StringComparison.Ordinal), + pin.Replace("-", string.Empty, StringComparison.Ordinal), + StringComparison.InvariantCultureIgnoreCase)) + { + var resetUser = _userManager.GetUserByName(spr.UserName); + if (resetUser == null) + { + throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found"); + } + + await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); + usersReset.Add(resetUser.Username); + File.Delete(resetFile); + } + } + + if (usersReset.Count < 1) + { + throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}"); + } + + return new PinRedeemResult + { + Success = true, + UsersReset = usersReset.ToArray() + }; + } + + /// + public async Task StartForgotPasswordProcess(Jellyfin.Data.Entities.User user, bool isInNetwork) + { + string pin; + using (var cryptoRandom = RandomNumberGenerator.Create()) + { + byte[] bytes = new byte[4]; + cryptoRandom.GetBytes(bytes); + pin = BitConverter.ToString(bytes); + } + + DateTime expireTime = DateTime.Now.AddMinutes(30); + + user.EasyPassword = pin; + await _userManager.UpdateUserAsync(user).ConfigureAwait(false); + + return new ForgotPasswordResult + { + Action = ForgotPasswordAction.PinCode, + PinExpirationDate = expireTime, + }; + } + + private class SerializablePasswordReset : PasswordPinCreationResult + { + public string Pin { get; set; } + + public string UserName { get; set; } + } + } +} diff --git a/Jellyfin.Server.Implementations/User/DeviceAccessEntryPoint.cs b/Jellyfin.Server.Implementations/User/DeviceAccessEntryPoint.cs new file mode 100644 index 0000000000..d33034ab2d --- /dev/null +++ b/Jellyfin.Server.Implementations/User/DeviceAccessEntryPoint.cs @@ -0,0 +1,65 @@ +#pragma warning disable CS1591 + +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Security; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Events; + +namespace Jellyfin.Server.Implementations.User +{ + public sealed class DeviceAccessEntryPoint : IServerEntryPoint + { + private readonly IUserManager _userManager; + private readonly IAuthenticationRepository _authRepo; + private readonly IDeviceManager _deviceManager; + private readonly ISessionManager _sessionManager; + + public DeviceAccessEntryPoint(IUserManager userManager, IAuthenticationRepository authRepo, IDeviceManager deviceManager, ISessionManager sessionManager) + { + _userManager = userManager; + _authRepo = authRepo; + _deviceManager = deviceManager; + _sessionManager = sessionManager; + } + + public Task RunAsync() + { + _userManager.OnUserUpdated += OnUserUpdated; + + return Task.CompletedTask; + } + + private void OnUserUpdated(object sender, GenericEventArgs e) + { + var user = e.Argument; + if (!user.HasPermission(PermissionKind.EnableAllDevices)) + { + UpdateDeviceAccess(user); + } + } + + public void Dispose() + { + } + + private void UpdateDeviceAccess(Data.Entities.User user) + { + var existing = _authRepo.Get(new AuthenticationInfoQuery + { + UserId = user.Id + }).Items; + + foreach (var authInfo in existing) + { + if (!string.IsNullOrEmpty(authInfo.DeviceId) && !_deviceManager.CanAccessDevice(user, authInfo.DeviceId)) + { + _sessionManager.Logout(authInfo); + } + } + } + } +} diff --git a/Jellyfin.Server.Implementations/User/InvalidAuthProvider.cs b/Jellyfin.Server.Implementations/User/InvalidAuthProvider.cs new file mode 100644 index 0000000000..a11ca128ac --- /dev/null +++ b/Jellyfin.Server.Implementations/User/InvalidAuthProvider.cs @@ -0,0 +1,47 @@ +using System.Threading.Tasks; +using MediaBrowser.Controller.Authentication; + +namespace Jellyfin.Server.Implementations.User +{ + /// + /// An invalid authentication provider. + /// + public class InvalidAuthProvider : IAuthenticationProvider + { + /// + public string Name => "InvalidOrMissingAuthenticationProvider"; + + /// + public bool IsEnabled => true; + + /// + public Task Authenticate(string username, string password) + { + throw new AuthenticationException("User Account cannot login with this provider. The Normal provider for this user cannot be found"); + } + + /// + public bool HasPassword(Data.Entities.User user) + { + return true; + } + + /// + public Task ChangePassword(Data.Entities.User user, string newPassword) + { + return Task.CompletedTask; + } + + /// + public void ChangeEasyPassword(Data.Entities.User user, string newPassword, string newPasswordHash) + { + // Nothing here + } + + /// + public string GetEasyPasswordHash(Data.Entities.User user) + { + return string.Empty; + } + } +} diff --git a/Jellyfin.Server.Implementations/User/UserManager.cs b/Jellyfin.Server.Implementations/User/UserManager.cs new file mode 100644 index 0000000000..1ed11cfcbb --- /dev/null +++ b/Jellyfin.Server.Implementations/User/UserManager.cs @@ -0,0 +1,749 @@ +#pragma warning disable CS0067 +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using MediaBrowser.Common.Cryptography; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Cryptography; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Users; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Implementations.User +{ + public class UserManager : IUserManager + { + private readonly JellyfinDbProvider _dbProvider; + private readonly ICryptoProvider _cryptoProvider; + private readonly INetworkManager _networkManager; + private readonly ILogger _logger; + + private IAuthenticationProvider[] _authenticationProviders; + private DefaultAuthenticationProvider _defaultAuthenticationProvider; + private InvalidAuthProvider _invalidAuthProvider; + private IPasswordResetProvider[] _passwordResetProviders; + private DefaultPasswordResetProvider _defaultPasswordResetProvider; + + public UserManager( + JellyfinDbProvider dbProvider, + ICryptoProvider cryptoProvider, + INetworkManager networkManager, + ILogger logger) + { + _dbProvider = dbProvider; + _cryptoProvider = cryptoProvider; + _networkManager = networkManager; + _logger = logger; + } + + public event EventHandler> OnUserPasswordChanged; + + /// + public event EventHandler> OnUserUpdated; + + /// + public event EventHandler> OnUserCreated; + + /// + public event EventHandler> OnUserDeleted; + + public event EventHandler> OnUserLockedOut; + + public IEnumerable Users + { + get + { + using var dbContext = _dbProvider.CreateContext(); + return dbContext.Users; + } + } + + public IEnumerable UsersIds + { + get + { + using var dbContext = _dbProvider.CreateContext(); + return dbContext.Users.Select(u => u.Id); + } + } + + public Data.Entities.User GetUserById(Guid id) + { + if (id == Guid.Empty) + { + throw new ArgumentException("Guid can't be empty", nameof(id)); + } + + using var dbContext = _dbProvider.CreateContext(); + + return dbContext.Users.Find(id); + } + + public Data.Entities.User GetUserByName(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Invalid username", nameof(name)); + } + + using var dbContext = _dbProvider.CreateContext(); + + return dbContext.Users.FirstOrDefault(u => + string.Equals(u.Username, name, StringComparison.OrdinalIgnoreCase)); + } + + public async Task RenameUser(Data.Entities.User user, string newName) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + if (string.IsNullOrWhiteSpace(newName)) + { + throw new ArgumentException("Invalid username", nameof(newName)); + } + + if (user.Username.Equals(newName, StringComparison.Ordinal)) + { + throw new ArgumentException("The new and old names must be different."); + } + + if (Users.Any( + u => u.Id != user.Id && u.Username.Equals(newName, StringComparison.OrdinalIgnoreCase))) + { + throw new ArgumentException(string.Format( + CultureInfo.InvariantCulture, + "A user with the name '{0}' already exists.", + newName)); + } + + user.Username = newName; + await UpdateUserAsync(user).ConfigureAwait(false); + + OnUserUpdated?.Invoke(this, new GenericEventArgs(user)); + } + + public void UpdateUser(Data.Entities.User user) + { + using var dbContext = _dbProvider.CreateContext(); + dbContext.Users.Update(user); + dbContext.SaveChanges(); + } + + public async Task UpdateUserAsync(Data.Entities.User user) + { + await using var dbContext = _dbProvider.CreateContext(); + dbContext.Users.Update(user); + + await dbContext.SaveChangesAsync().ConfigureAwait(false); + } + + public Data.Entities.User CreateUser(string name) + { + using var dbContext = _dbProvider.CreateContext(); + + var newUser = CreateUserObject(name); + dbContext.Users.Add(newUser); + dbContext.SaveChanges(); + + return newUser; + } + + public void DeleteUser(Data.Entities.User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + using var dbContext = _dbProvider.CreateContext(); + + if (!dbContext.Users.Contains(user)) + { + throw new ArgumentException(string.Format( + CultureInfo.InvariantCulture, + "The user cannot be deleted because there is no user with the Name {0} and Id {1}.", + user.Username, + user.Id)); + } + + if (dbContext.Users.Count() == 1) + { + throw new InvalidOperationException(string.Format( + CultureInfo.InvariantCulture, + "The user '{0}' cannot be deleted because there must be at least one user in the system.", + user.Username)); + } + + if (user.HasPermission(PermissionKind.IsAdministrator) + && Users.Count(i => i.HasPermission(PermissionKind.IsAdministrator)) == 1) + { + throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + "The user '{0}' cannot be deleted because there must be at least one admin user in the system.", + user.Username), + nameof(user)); + } + + dbContext.Users.Remove(user); + dbContext.SaveChanges(); + } + + public Task ResetPassword(Data.Entities.User user) + { + return ChangePassword(user, string.Empty); + } + + public void ResetEasyPassword(Data.Entities.User user) + { + ChangeEasyPassword(user, string.Empty, null); + } + + public async Task ChangePassword(Data.Entities.User user, string newPassword) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false); + await UpdateUserAsync(user).ConfigureAwait(false); + + OnUserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); + } + + public void ChangeEasyPassword(Data.Entities.User user, string newPassword, string newPasswordSha1) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordSha1); + + UpdateUser(user); + + OnUserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); + } + + public UserDto GetUserDto(Data.Entities.User user, string remoteEndPoint = null) + { + return new UserDto + { + Id = user.Id, + HasPassword = user.Password == null, + EnableAutoLogin = user.EnableAutoLogin, + LastLoginDate = user.LastLoginDate, + LastActivityDate = user.LastActivityDate, + Configuration = new UserConfiguration + { + SubtitleMode = user.SubtitleMode, + HidePlayedInLatest = user.HidePlayedInLatest, + EnableLocalPassword = user.EnableLocalPassword, + PlayDefaultAudioTrack = user.PlayDefaultAudioTrack, + DisplayCollectionsView = user.DisplayCollectionsView, + DisplayMissingEpisodes = user.DisplayMissingEpisodes, + AudioLanguagePreference = user.AudioLanguagePreference, + RememberAudioSelections = user.RememberAudioSelections, + EnableNextEpisodeAutoPlay = user.EnableNextEpisodeAutoPlay, + RememberSubtitleSelections = user.RememberSubtitleSelections, + SubtitleLanguagePreference = user.SubtitleLanguagePreference, + OrderedViews = user.GetPreference(PreferenceKind.OrderedViews), + GroupedFolders = user.GetPreference(PreferenceKind.GroupedFolders), + MyMediaExcludes = user.GetPreference(PreferenceKind.MyMediaExcludes), + LatestItemsExcludes = user.GetPreference(PreferenceKind.LatestItemExcludes) + }, + Policy = new UserPolicy + { + MaxParentalRating = user.MaxParentalAgeRating, + EnableUserPreferenceAccess = user.EnableUserPreferenceAccess, + RemoteClientBitrateLimit = user.RemoteClientBitrateLimit.GetValueOrDefault(), + AuthenticatioIsnProviderId = user.AuthenticationProviderId, + PasswordResetProviderId = user.PasswordResetProviderId, + InvalidLoginAttemptCount = user.InvalidLoginAttemptCount, + LoginAttemptsBeforeLockout = user.LoginAttemptsBeforeLockout.GetValueOrDefault(), + IsAdministrator = user.HasPermission(PermissionKind.IsAdministrator), + IsHidden = user.HasPermission(PermissionKind.IsHidden), + IsDisabled = user.HasPermission(PermissionKind.IsDisabled), + EnableSharedDeviceControl = user.HasPermission(PermissionKind.EnableSharedDeviceControl), + EnableRemoteAccess = user.HasPermission(PermissionKind.EnableRemoteAccess), + EnableLiveTvManagement = user.HasPermission(PermissionKind.EnableLiveTvManagement), + EnableLiveTvAccess = user.HasPermission(PermissionKind.EnableLiveTvAccess), + EnableMediaPlayback = user.HasPermission(PermissionKind.EnableMediaPlayback), + EnableAudioPlaybackTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding), + EnableVideoPlaybackTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding), + EnableContentDeletion = user.HasPermission(PermissionKind.EnableContentDeletion), + EnableContentDownloading = user.HasPermission(PermissionKind.EnableContentDownloading), + EnableSyncTranscoding = user.HasPermission(PermissionKind.EnableSyncTranscoding), + EnableMediaConversion = user.HasPermission(PermissionKind.EnableMediaConversion), + EnableAllChannels = user.HasPermission(PermissionKind.EnableAllChannels), + EnableAllDevices = user.HasPermission(PermissionKind.EnableAllDevices), + EnableAllFolders = user.HasPermission(PermissionKind.EnableAllFolders), + EnableRemoteControlOfOtherUsers = user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers), + EnablePlaybackRemuxing = user.HasPermission(PermissionKind.EnablePlaybackRemuxing), + ForceRemoteSourceTranscoding = user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding), + EnablePublicSharing = user.HasPermission(PermissionKind.EnablePublicSharing), + AccessSchedules = user.AccessSchedules.ToArray(), + BlockedTags = user.GetPreference(PreferenceKind.BlockedTags), + EnabledChannels = user.GetPreference(PreferenceKind.EnabledChannels), + EnabledDevices = user.GetPreference(PreferenceKind.EnabledDevices), + EnabledFolders = user.GetPreference(PreferenceKind.EnabledFolders), + EnableContentDeletionFromFolders = user.GetPreference(PreferenceKind.EnableContentDeletionFromFolders) + } + }; + } + + public async Task AuthenticateUser( + string username, + string password, + string passwordSha1, + string remoteEndPoint, + bool isUserSession) + { + if (string.IsNullOrWhiteSpace(username)) + { + _logger.LogInformation("Authentication request without username has been denied (IP: {IP}).", remoteEndPoint); + throw new ArgumentNullException(nameof(username)); + } + + var user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); + bool success; + IAuthenticationProvider authenticationProvider; + + if (user != null) + { + var authResult = await AuthenticateLocalUser(username, password, user, remoteEndPoint) + .ConfigureAwait(false); + authenticationProvider = authResult.authenticationProvider; + success = authResult.success; + } + else + { + var authResult = await AuthenticateLocalUser(username, password, null, remoteEndPoint) + .ConfigureAwait(false); + authenticationProvider = authResult.authenticationProvider; + string updatedUsername = authResult.username; + success = authResult.success; + + if (success + && authenticationProvider != null + && !(authenticationProvider is DefaultAuthenticationProvider)) + { + // Trust the username returned by the authentication provider + username = updatedUsername; + + // Search the database for the user again + // the authentication provider might have created it + user = Users + .FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); + + if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy) + { + UpdatePolicy(user.Id, hasNewUserPolicy.GetNewUserPolicy()); + + await UpdateUserAsync(user).ConfigureAwait(false); + } + } + } + + if (success && user != null && authenticationProvider != null) + { + var providerId = authenticationProvider.GetType().FullName; + + if (!string.Equals(providerId, user.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase)) + { + user.AuthenticationProviderId = providerId; + await UpdateUserAsync(user).ConfigureAwait(false); + } + } + + if (user == null) + { + _logger.LogInformation( + "Authentication request for {UserName} has been denied (IP: {IP}).", + username, + remoteEndPoint); + throw new AuthenticationException("Invalid username or password entered."); + } + + if (user.HasPermission(PermissionKind.IsDisabled)) + { + _logger.LogInformation( + "Authentication request for {UserName} has been denied because this account is currently disabled (IP: {IP}).", + username, + remoteEndPoint); + throw new SecurityException( + $"The {user.Username} account is currently disabled. Please consult with your administrator."); + } + + if (!user.HasPermission(PermissionKind.EnableRemoteAccess) && + !_networkManager.IsInLocalNetwork(remoteEndPoint)) + { + _logger.LogInformation( + "Authentication request for {UserName} forbidden: remote access disabled and user not in local network (IP: {IP}).", + username, + remoteEndPoint); + throw new SecurityException("Forbidden."); + } + + if (!user.IsParentalScheduleAllowed()) + { + _logger.LogInformation( + "Authentication request for {UserName} is not allowed at this time due parental restrictions (IP: {IP}).", + username, + remoteEndPoint); + throw new SecurityException("User is not allowed access at this time."); + } + + // Update LastActivityDate and LastLoginDate, then save + if (success) + { + if (isUserSession) + { + user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; + UpdateUser(user); + } + + ResetInvalidLoginAttemptCount(user); + _logger.LogInformation("Authentication request for {UserName} has succeeded.", user.Username); + } + else + { + IncrementInvalidLoginAttemptCount(user); + _logger.LogInformation( + "Authentication request for {UserName} has been denied (IP: {IP}).", + user.Username, + remoteEndPoint); + } + + return success ? user : null; + } + + public async Task StartForgotPasswordProcess(string enteredUsername, bool isInNetwork) + { + var user = string.IsNullOrWhiteSpace(enteredUsername) ? null : GetUserByName(enteredUsername); + + var action = ForgotPasswordAction.InNetworkRequired; + + if (user != null && isInNetwork) + { + var passwordResetProvider = GetPasswordResetProvider(user); + return await passwordResetProvider.StartForgotPasswordProcess(user, isInNetwork).ConfigureAwait(false); + } + + return new ForgotPasswordResult + { + Action = action, + PinFile = string.Empty + }; + } + + public async Task RedeemPasswordResetPin(string pin) + { + foreach (var provider in _passwordResetProviders) + { + var result = await provider.RedeemPasswordResetPin(pin).ConfigureAwait(false); + + if (result.Success) + { + return result; + } + } + + return new PinRedeemResult + { + Success = false, + UsersReset = Array.Empty() + }; + } + + public void AddParts(IEnumerable authenticationProviders, IEnumerable passwordResetProviders) + { + _authenticationProviders = authenticationProviders.ToArray(); + + _defaultAuthenticationProvider = _authenticationProviders.OfType().First(); + + _invalidAuthProvider = _authenticationProviders.OfType().First(); + + _passwordResetProviders = passwordResetProviders.ToArray(); + + _defaultPasswordResetProvider = passwordResetProviders.OfType().First(); + } + + public NameIdPair[] GetAuthenticationProviders() + { + return _authenticationProviders + .Where(provider => provider.IsEnabled) + .OrderBy(i => i is DefaultAuthenticationProvider ? 0 : 1) + .ThenBy(i => i.Name) + .Select(i => new NameIdPair + { + Name = i.Name, + Id = i.GetType().FullName + }) + .ToArray(); + } + + public NameIdPair[] GetPasswordResetProviders() + { + return _passwordResetProviders + .Where(provider => provider.IsEnabled) + .OrderBy(i => i is DefaultPasswordResetProvider ? 0 : 1) + .ThenBy(i => i.Name) + .Select(i => new NameIdPair + { + Name = i.Name, + Id = i.GetType().FullName + }) + .ToArray(); + } + + public void UpdateConfiguration(Guid userId, UserConfiguration config) + { + var user = GetUserById(userId); + user.SubtitleMode = config.SubtitleMode; + user.HidePlayedInLatest = config.HidePlayedInLatest; + user.EnableLocalPassword = config.EnableLocalPassword; + user.PlayDefaultAudioTrack = config.PlayDefaultAudioTrack; + user.DisplayCollectionsView = config.DisplayCollectionsView; + user.DisplayMissingEpisodes = config.DisplayMissingEpisodes; + user.AudioLanguagePreference = config.AudioLanguagePreference; + user.RememberAudioSelections = config.RememberAudioSelections; + user.EnableNextEpisodeAutoPlay = config.EnableNextEpisodeAutoPlay; + user.RememberSubtitleSelections = config.RememberSubtitleSelections; + user.SubtitleLanguagePreference = config.SubtitleLanguagePreference; + + user.SetPreference(PreferenceKind.OrderedViews, config.OrderedViews); + user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders); + user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes); + user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes); + + UpdateUser(user); + } + + public void UpdatePolicy(Guid userId, UserPolicy policy) + { + var user = GetUserById(userId); + + user.MaxParentalAgeRating = policy.MaxParentalRating; + user.EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess; + user.RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit; + user.AuthenticationProviderId = policy.AuthenticatioIsnProviderId; + user.PasswordResetProviderId = policy.PasswordResetProviderId; + user.InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount; + user.LoginAttemptsBeforeLockout = policy.LoginAttemptsBeforeLockout == -1 + ? null + : new int?(policy.LoginAttemptsBeforeLockout); + user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator); + user.SetPermission(PermissionKind.IsHidden, policy.IsHidden); + user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled); + user.SetPermission(PermissionKind.EnableSharedDeviceControl, policy.EnableSharedDeviceControl); + user.SetPermission(PermissionKind.EnableRemoteAccess, policy.EnableRemoteAccess); + user.SetPermission(PermissionKind.EnableLiveTvManagement, policy.EnableLiveTvManagement); + user.SetPermission(PermissionKind.EnableLiveTvAccess, policy.EnableLiveTvAccess); + user.SetPermission(PermissionKind.EnableMediaPlayback, policy.EnableMediaPlayback); + user.SetPermission(PermissionKind.EnableAudioPlaybackTranscoding, policy.EnableAudioPlaybackTranscoding); + user.SetPermission(PermissionKind.EnableVideoPlaybackTranscoding, policy.EnableVideoPlaybackTranscoding); + user.SetPermission(PermissionKind.EnableContentDeletion, policy.EnableContentDeletion); + user.SetPermission(PermissionKind.EnableContentDownloading, policy.EnableContentDownloading); + user.SetPermission(PermissionKind.EnableSyncTranscoding, policy.EnableSyncTranscoding); + user.SetPermission(PermissionKind.EnableMediaConversion, policy.EnableMediaConversion); + user.SetPermission(PermissionKind.EnableAllChannels, policy.EnableAllChannels); + user.SetPermission(PermissionKind.EnableAllDevices, policy.EnableAllDevices); + user.SetPermission(PermissionKind.EnableAllFolders, policy.EnableAllFolders); + user.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, policy.EnableRemoteControlOfOtherUsers); + user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing); + user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding); + user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing); + + user.AccessSchedules.Clear(); + foreach (var policyAccessSchedule in policy.AccessSchedules) + { + user.AccessSchedules.Add(policyAccessSchedule); + } + + user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags); + user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels); + user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices); + user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders); + user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders); + } + + private Data.Entities.User CreateUserObject(string name) + { + return new Data.Entities.User( + username: name, + mustUpdatePassword: false, + authenticationProviderId: _defaultAuthenticationProvider.GetType().FullName, + invalidLoginAttemptCount: -1, + subtitleMode: SubtitlePlaybackMode.Default, + playDefaultAudioTrack: true); + } + + private IAuthenticationProvider GetAuthenticationProvider(Data.Entities.User user) + { + return GetAuthenticationProviders(user)[0]; + } + + private IPasswordResetProvider GetPasswordResetProvider(Data.Entities.User user) + { + return GetPasswordResetProviders(user)[0]; + } + + private IList GetAuthenticationProviders(Data.Entities.User user) + { + var authenticationProviderId = user?.AuthenticationProviderId; + + var providers = _authenticationProviders.Where(i => i.IsEnabled).ToList(); + + if (!string.IsNullOrEmpty(authenticationProviderId)) + { + providers = providers.Where(i => string.Equals(authenticationProviderId, i.GetType().FullName, StringComparison.OrdinalIgnoreCase)).ToList(); + } + + if (providers.Count == 0) + { + // Assign the user to the InvalidAuthProvider since no configured auth provider was valid/found + _logger.LogWarning( + "User {UserName} was found with invalid/missing Authentication Provider {AuthenticationProviderId}. Assigning user to InvalidAuthProvider until this is corrected", + user?.Username, + user?.AuthenticationProviderId); + providers = new List + { + _invalidAuthProvider + }; + } + + return providers; + } + + private IList GetPasswordResetProviders(Data.Entities.User user) + { + var passwordResetProviderId = user?.PasswordResetProviderId; + var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray(); + + if (!string.IsNullOrEmpty(passwordResetProviderId)) + { + providers = providers.Where(i => + string.Equals(passwordResetProviderId, i.GetType().FullName, StringComparison.OrdinalIgnoreCase)) + .ToArray(); + } + + if (providers.Length == 0) + { + providers = new IPasswordResetProvider[] + { + _defaultPasswordResetProvider + }; + } + + return providers; + } + + private async Task<(IAuthenticationProvider authenticationProvider, string username, bool success)> + AuthenticateLocalUser( + string username, + string password, + Jellyfin.Data.Entities.User user, + string remoteEndPoint) + { + bool success = false; + IAuthenticationProvider authenticationProvider = null; + + foreach (var provider in GetAuthenticationProviders(user)) + { + var providerAuthResult = + await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); + var updatedUsername = providerAuthResult.username; + success = providerAuthResult.success; + + if (success) + { + authenticationProvider = provider; + username = updatedUsername; + break; + } + } + + if (!success + && _networkManager.IsInLocalNetwork(remoteEndPoint) + && user?.EnableLocalPassword == true + && !string.IsNullOrEmpty(user.EasyPassword)) + { + // Check easy password + var passwordHash = PasswordHash.Parse(user.EasyPassword); + var hash = _cryptoProvider.ComputeHash( + passwordHash.Id, + Encoding.UTF8.GetBytes(password), + passwordHash.Salt.ToArray()); + success = passwordHash.Hash.SequenceEqual(hash); + } + + return (authenticationProvider, username, success); + } + + private async Task<(string username, bool success)> AuthenticateWithProvider( + IAuthenticationProvider provider, + string username, + string password, + Data.Entities.User resolvedUser) + { + try + { + var authenticationResult = provider is IRequiresResolvedUser requiresResolvedUser + ? await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false) + : await provider.Authenticate(username, password).ConfigureAwait(false); + + if (authenticationResult.Username != username) + { + _logger.LogDebug("Authentication provider provided updated username {1}", authenticationResult.Username); + username = authenticationResult.Username; + } + + return (username, true); + } + catch (AuthenticationException ex) + { + _logger.LogError(ex, "Error authenticating with provider {Provider}", provider.Name); + + return (username, false); + } + } + + private void IncrementInvalidLoginAttemptCount(Data.Entities.User user) + { + int invalidLogins = user.InvalidLoginAttemptCount; + int? maxInvalidLogins = user.LoginAttemptsBeforeLockout; + if (maxInvalidLogins.HasValue + && invalidLogins >= maxInvalidLogins) + { + user.SetPermission(PermissionKind.IsDisabled, true); + OnUserLockedOut?.Invoke(this, new GenericEventArgs(user)); + _logger.LogWarning( + "Disabling user {UserName} due to {Attempts} unsuccessful login attempts.", + user.Username, + invalidLogins); + } + + UpdateUser(user); + } + + private void ResetInvalidLoginAttemptCount(Data.Entities.User user) + { + user.InvalidLoginAttemptCount = 0; + } + } +} diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs new file mode 100644 index 0000000000..23d2299cdb --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -0,0 +1,8 @@ +#pragma warning disable CS1591 + +namespace Jellyfin.Server.Migrations.Routines +{ + public class MigrateUserDb + { + } +} diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 1a1d86362a..a5b504dfa6 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -94,8 +95,8 @@ namespace MediaBrowser.Api var authenticatedUser = auth.User; // If they're going to update the record of another user, they must be an administrator - if ((!userId.Equals(auth.UserId) && !authenticatedUser.Policy.IsAdministrator) - || (restrictUserPreferences && !authenticatedUser.Policy.EnableUserPreferenceAccess)) + if ((!userId.Equals(auth.UserId) && !authenticatedUser.HasPermission(PermissionKind.IsAdministrator)) + || (restrictUserPreferences && !authenticatedUser.EnableUserPreferenceAccess)) { throw new SecurityException("Unauthorized access."); } diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs index 5eb72cdb19..ac61cd4910 100644 --- a/MediaBrowser.Api/FilterService.cs +++ b/MediaBrowser.Api/FilterService.cs @@ -220,7 +220,7 @@ namespace MediaBrowser.Api return result; } - private InternalItemsQuery GetItemsQuery(GetQueryFiltersLegacy request, User user) + private InternalItemsQuery GetItemsQuery(GetQueryFiltersLegacy request, Jellyfin.Data.Entities.User user) { var query = new InternalItemsQuery { diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 2e9b3e6cb4..f9976122c8 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -20,6 +20,7 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; +using User = Jellyfin.Data.Entities.User; namespace MediaBrowser.Api.Images { @@ -399,14 +400,14 @@ namespace MediaBrowser.Api.Images { var item = _userManager.GetUserById(request.Id); - return GetImage(request, Guid.Empty, item, false); + return GetImage(request, item, false); } public object Head(GetUserImage request) { var item = _userManager.GetUserById(request.Id); - return GetImage(request, Guid.Empty, item, true); + return GetImage(request, item, true); } public object Get(GetItemByNameImage request) @@ -439,9 +440,9 @@ namespace MediaBrowser.Api.Images request.Type = Enum.Parse(GetPathValue(3).ToString(), true); - var item = _userManager.GetUserById(id); + var user = _userManager.GetUserById(id); - return PostImage(item, request.RequestStream, request.Type, Request.ContentType); + return PostImage(user, request.RequestStream, Request.ContentType); } /// @@ -468,9 +469,9 @@ namespace MediaBrowser.Api.Images var userId = request.Id; AssertCanUpdateUser(_authContext, _userManager, userId, true); - var item = _userManager.GetUserById(userId); + var user = _userManager.GetUserById(userId); - item.DeleteImage(request.Type, request.Index ?? 0); + user.ProfileImage = null; } /// @@ -555,18 +556,17 @@ namespace MediaBrowser.Api.Images var imageInfo = GetImageInfo(request, item); if (imageInfo == null) { - var displayText = item == null ? itemId.ToString() : item.Name; - throw new ResourceNotFoundException(string.Format("{0} does not have an image of type {1}", displayText, request.Type)); + throw new ResourceNotFoundException(string.Format("{0} does not have an image of type {1}", item.Name, request.Type)); } - bool cropwhitespace; + bool cropWhitespace; if (request.CropWhitespace.HasValue) { - cropwhitespace = request.CropWhitespace.Value; + cropWhitespace = request.CropWhitespace.Value; } else { - cropwhitespace = request.Type == ImageType.Logo || request.Type == ImageType.Art; + cropWhitespace = request.Type == ImageType.Logo || request.Type == ImageType.Art; } var outputFormats = GetOutputFormats(request); @@ -589,13 +589,94 @@ namespace MediaBrowser.Api.Images itemId, request, imageInfo, - cropwhitespace, + cropWhitespace, outputFormats, cacheDuration, responseHeaders, isHeadRequest); } + public Task GetImage(ImageRequest request, User user, bool isHeadRequest) + { + var imageInfo = GetImageInfo(request, user); + + TimeSpan? cacheDuration = null; + + if (!string.IsNullOrEmpty(request.Tag)) + { + cacheDuration = TimeSpan.FromDays(365); + } + + var responseHeaders = new Dictionary + { + {"transferMode.dlna.org", "Interactive"}, + {"realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*"} + }; + + var outputFormats = GetOutputFormats(request); + + return GetImageResult( + user, + user.Id, + request, + imageInfo, + false, + outputFormats, + cacheDuration, + responseHeaders, + isHeadRequest); + } + + private async Task GetImageResult( + User user, + Guid itemId, + ImageRequest request, + ItemImageInfo info, + bool cropWhitespace, + IReadOnlyCollection supportedFormats, + TimeSpan? cacheDuration, + IDictionary headers, + bool isHeadRequest) + { + var options = new ImageProcessingOptions + { + CropWhiteSpace = true, + Height = request.Height, + ImageIndex = request.Index ?? 0, + Image = info, + Item = null, // Hack alert + ItemId = itemId, + MaxHeight = request.MaxHeight, + MaxWidth = request.MaxWidth, + Quality = request.Quality ?? 100, + Width = request.Width, + AddPlayedIndicator = request.AddPlayedIndicator, + PercentPlayed = 0, + UnplayedCount = request.UnplayedCount, + Blur = request.Blur, + BackgroundColor = request.BackgroundColor, + ForegroundLayer = request.ForegroundLayer, + SupportedOutputFormats = supportedFormats + }; + + var imageResult = await _imageProcessor.ProcessImage(options).ConfigureAwait(false); + + headers[HeaderNames.Vary] = HeaderNames.Accept; + + return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions + { + CacheDuration = cacheDuration, + ResponseHeaders = headers, + ContentType = imageResult.Item2, + DateLastModified = imageResult.Item3, + IsHeadRequest = isHeadRequest, + Path = imageResult.Item1, + + FileShare = FileShare.Read + + }).ConfigureAwait(false); + } + private async Task GetImageResult( BaseItem item, Guid itemId, @@ -740,6 +821,28 @@ namespace MediaBrowser.Api.Images return item.GetImageInfo(request.Type, index); } + private ItemImageInfo GetImageInfo(ImageRequest request, User user) + { + var info = new ItemImageInfo + { + Path = user.ProfileImage.Path, + Type = ImageType.Primary, + DateModified = user.ProfileImage.LastModified, + }; + + if (request.Width.HasValue) + { + info.Width = request.Width.Value; + } + + if (request.Height.HasValue) + { + info.Height = request.Height.Value; + } + + return info; + } + /// /// Posts the image. /// @@ -767,5 +870,25 @@ namespace MediaBrowser.Api.Images entity.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None); } + + public async Task PostImage(User user, Stream inputStream, string mimeType) + { + using var reader = new StreamReader(inputStream); + var text = await reader.ReadToEndAsync().ConfigureAwait(false); + + var bytes = Convert.FromBase64String(text); + var memoryStream = new MemoryStream(bytes) + { + Position = 0 + }; + + // Handle image/png; charset=utf-8 + mimeType = mimeType.Split(';').FirstOrDefault(); + + await _providerManager + .SaveImage(user, memoryStream, mimeType, Path.Combine(ServerConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath, _imageProcessor.GetImageCacheTag(user))) + .ConfigureAwait(false); + await _userManager.UpdateUserAsync(user); + } } } diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 997b1c45a8..783fc6073d 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -755,12 +755,12 @@ namespace MediaBrowser.Api.Library }); } - private void LogDownload(BaseItem item, User user, AuthorizationInfo auth) + private void LogDownload(BaseItem item, Jellyfin.Data.Entities.User user, AuthorizationInfo auth) { try { _activityManager.Create(new Jellyfin.Data.Entities.ActivityLog( - string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Name, item.Name), + string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Username, item.Name), "UserDownloadingContent", auth.UserId, DateTime.UtcNow, @@ -840,7 +840,7 @@ namespace MediaBrowser.Api.Library return baseItemDtos; } - private BaseItem TranslateParentItem(BaseItem item, User user) + private BaseItem TranslateParentItem(BaseItem item, Jellyfin.Data.Entities.User user) { return item.GetParent() is AggregateFolder ? _libraryManager.GetUserRootFolder().GetChildren(user, true) @@ -882,7 +882,7 @@ namespace MediaBrowser.Api.Library return ToOptimizedResult(counts); } - private int GetCount(Type type, User user, GetItemCounts request) + private int GetCount(Type type, Jellyfin.Data.Entities.User user, GetItemCounts request) { var query = new InternalItemsQuery(user) { diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 5fe4c0cca3..279fd6ee9b 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -7,6 +7,7 @@ using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Api.UserLibrary; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; @@ -859,7 +860,7 @@ namespace MediaBrowser.Api.LiveTv throw new SecurityException("Anonymous live tv management is not allowed."); } - if (!user.Policy.EnableLiveTvManagement) + if (!user.HasPermission(PermissionKind.EnableLiveTvManagement)) { throw new SecurityException("The current user does not have permission to manage live tv."); } diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index 46da8b9099..b97a0dca18 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -148,7 +148,12 @@ namespace MediaBrowser.Api.Movies return result; } - private IEnumerable GetRecommendationCategories(User user, string parentId, int categoryLimit, int itemLimit, DtoOptions dtoOptions) + private IEnumerable GetRecommendationCategories( + Jellyfin.Data.Entities.User user, + string parentId, + int categoryLimit, + int itemLimit, + DtoOptions dtoOptions) { var categories = new List(); @@ -251,7 +256,12 @@ namespace MediaBrowser.Api.Movies return categories.OrderBy(i => i.RecommendationType); } - private IEnumerable GetWithDirector(User user, IEnumerable names, int itemLimit, DtoOptions dtoOptions, RecommendationType type) + private IEnumerable GetWithDirector( + Jellyfin.Data.Entities.User user, + IEnumerable names, + int itemLimit, + DtoOptions dtoOptions, + RecommendationType type) { var itemTypes = new List { typeof(Movie).Name }; if (ServerConfigurationManager.Configuration.EnableExternalContentInSuggestions) @@ -293,7 +303,12 @@ namespace MediaBrowser.Api.Movies } } - private IEnumerable GetWithActor(User user, IEnumerable names, int itemLimit, DtoOptions dtoOptions, RecommendationType type) + private IEnumerable GetWithActor( + Jellyfin.Data.Entities.User user, + IEnumerable names, + int itemLimit, + DtoOptions dtoOptions, + RecommendationType type) { var itemTypes = new List { typeof(Movie).Name }; if (ServerConfigurationManager.Configuration.EnableExternalContentInSuggestions) @@ -334,7 +349,12 @@ namespace MediaBrowser.Api.Movies } } - private IEnumerable GetSimilarTo(User user, List baselineItems, int itemLimit, DtoOptions dtoOptions, RecommendationType type) + private IEnumerable GetSimilarTo( + Jellyfin.Data.Entities.User user, + List baselineItems, + int itemLimit, + DtoOptions dtoOptions, + RecommendationType type) { var itemTypes = new List { typeof(Movie).Name }; if (ServerConfigurationManager.Configuration.EnableExternalContentInSuggestions) diff --git a/MediaBrowser.Api/Music/InstantMixService.cs b/MediaBrowser.Api/Music/InstantMixService.cs index cacec8d640..e00b06e38c 100644 --- a/MediaBrowser.Api/Music/InstantMixService.cs +++ b/MediaBrowser.Api/Music/InstantMixService.cs @@ -171,7 +171,7 @@ namespace MediaBrowser.Api.Music return GetResult(items, user, request, dtoOptions); } - private object GetResult(List items, User user, BaseGetSimilarItems request, DtoOptions dtoOptions) + private object GetResult(List items, Jellyfin.Data.Entities.User user, BaseGetSimilarItems request, DtoOptions dtoOptions) { var list = items; diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 928ca16128..65f6ccd4d4 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; @@ -196,7 +197,7 @@ namespace MediaBrowser.Api.Playback if (state.VideoRequest != null && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { var auth = AuthorizationContext.GetAuthorizationInfo(Request); - if (auth.User != null && !auth.User.Policy.EnableVideoPlaybackTranscoding) + if (auth.User != null && !auth.User.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)) { ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state); diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index db24eaca6e..0ef39c2d7d 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; @@ -400,21 +401,24 @@ namespace MediaBrowser.Api.Playback if (item is Audio) { - Logger.LogInformation("User policy for {0}. EnableAudioPlaybackTranscoding: {1}", user.Name, user.Policy.EnableAudioPlaybackTranscoding); + Logger.LogInformation( + "User policy for {0}. EnableAudioPlaybackTranscoding: {1}", + user.Username, + user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)); } else { Logger.LogInformation("User policy for {0}. EnablePlaybackRemuxing: {1} EnableVideoPlaybackTranscoding: {2} EnableAudioPlaybackTranscoding: {3}", - user.Name, - user.Policy.EnablePlaybackRemuxing, - user.Policy.EnableVideoPlaybackTranscoding, - user.Policy.EnableAudioPlaybackTranscoding); + user.Username, + user.HasPermission(PermissionKind.EnablePlaybackRemuxing), + user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding), + user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)); } // Beginning of Playback Determination: Attempt DirectPlay first if (mediaSource.SupportsDirectPlay) { - if (mediaSource.IsRemote && user.Policy.ForceRemoteSourceTranscoding) + if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding)) { mediaSource.SupportsDirectPlay = false; } @@ -428,14 +432,16 @@ namespace MediaBrowser.Api.Playback if (item is Audio) { - if (!user.Policy.EnableAudioPlaybackTranscoding) + if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)) { options.ForceDirectPlay = true; } } else if (item is Video) { - if (!user.Policy.EnableAudioPlaybackTranscoding && !user.Policy.EnableVideoPlaybackTranscoding && !user.Policy.EnablePlaybackRemuxing) + if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding) + && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding) + && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing)) { options.ForceDirectPlay = true; } @@ -463,7 +469,7 @@ namespace MediaBrowser.Api.Playback if (mediaSource.SupportsDirectStream) { - if (mediaSource.IsRemote && user.Policy.ForceRemoteSourceTranscoding) + if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding)) { mediaSource.SupportsDirectStream = false; } @@ -473,14 +479,16 @@ namespace MediaBrowser.Api.Playback if (item is Audio) { - if (!user.Policy.EnableAudioPlaybackTranscoding) + if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)) { options.ForceDirectStream = true; } } else if (item is Video) { - if (!user.Policy.EnableAudioPlaybackTranscoding && !user.Policy.EnableVideoPlaybackTranscoding && !user.Policy.EnablePlaybackRemuxing) + if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding) + && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding) + && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing)) { options.ForceDirectStream = true; } @@ -512,7 +520,7 @@ namespace MediaBrowser.Api.Playback ? streamBuilder.BuildAudioItem(options) : streamBuilder.BuildVideoItem(options); - if (mediaSource.IsRemote && user.Policy.ForceRemoteSourceTranscoding) + if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding)) { if (streamInfo != null) { @@ -576,10 +584,10 @@ namespace MediaBrowser.Api.Playback } } - private long? GetMaxBitrate(long? clientMaxBitrate, User user) + private long? GetMaxBitrate(long? clientMaxBitrate, Jellyfin.Data.Entities.User user) { var maxBitrate = clientMaxBitrate; - var remoteClientMaxBitrate = user?.Policy.RemoteClientBitrateLimit ?? 0; + var remoteClientMaxBitrate = user?.RemoteClientBitrateLimit ?? 0; if (remoteClientMaxBitrate <= 0) { diff --git a/MediaBrowser.Api/Sessions/SessionService.cs b/MediaBrowser.Api/Sessions/SessionService.cs index 020bb5042b..d986eea65a 100644 --- a/MediaBrowser.Api/Sessions/SessionService.cs +++ b/MediaBrowser.Api/Sessions/SessionService.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; @@ -326,12 +327,12 @@ namespace MediaBrowser.Api.Sessions var user = _userManager.GetUserById(request.ControllableByUserId); - if (!user.Policy.EnableRemoteControlOfOtherUsers) + if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers)) { result = result.Where(i => i.UserId.Equals(Guid.Empty) || i.ContainsUser(request.ControllableByUserId)); } - if (!user.Policy.EnableSharedDeviceControl) + if (!user.HasPermission(PermissionKind.EnableSharedDeviceControl)) { result = result.Where(i => !i.UserId.Equals(Guid.Empty)); } diff --git a/MediaBrowser.Api/SuggestionsService.cs b/MediaBrowser.Api/SuggestionsService.cs index 91f85db6ff..51fa1eb71f 100644 --- a/MediaBrowser.Api/SuggestionsService.cs +++ b/MediaBrowser.Api/SuggestionsService.cs @@ -78,7 +78,7 @@ namespace MediaBrowser.Api }; } - private QueryResult GetItems(GetSuggestedItems request, User user, DtoOptions dtoOptions) + private QueryResult GetItems(GetSuggestedItems request, Jellyfin.Data.Entities.User user, DtoOptions dtoOptions) { return _libraryManager.GetItemsResult(new InternalItemsQuery(user) { diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index cd8e8dfbe4..0c23d8b291 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -378,7 +378,7 @@ namespace MediaBrowser.Api { var user = _userManager.GetUserById(request.UserId); - var series = GetSeries(request.Id, user); + var series = GetSeries(request.Id); if (series == null) { @@ -404,7 +404,7 @@ namespace MediaBrowser.Api }; } - private Series GetSeries(string seriesId, User user) + private Series GetSeries(string seriesId) { if (!string.IsNullOrWhiteSpace(seriesId)) { @@ -433,7 +433,7 @@ namespace MediaBrowser.Api } else if (request.Season.HasValue) { - var series = GetSeries(request.Id, user); + var series = GetSeries(request.Id); if (series == null) { @@ -446,7 +446,7 @@ namespace MediaBrowser.Api } else { - var series = GetSeries(request.Id, user); + var series = GetSeries(request.Id); if (series == null) { diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index c4a52d5f52..75a33350ed 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -94,7 +94,7 @@ namespace MediaBrowser.Api.UserLibrary { var dtoOptions = GetDtoOptions(AuthorizationContext, request); - User user = null; + Jellyfin.Data.Entities.User user = null; BaseItem parentItem; if (!request.UserId.Equals(Guid.Empty)) @@ -246,7 +246,7 @@ namespace MediaBrowser.Api.UserLibrary { var dtoOptions = GetDtoOptions(AuthorizationContext, request); - User user = null; + Jellyfin.Data.Entities.User user = null; BaseItem parentItem; if (!request.UserId.Equals(Guid.Empty)) diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index c4d44042b1..cbceb3625c 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -86,7 +87,7 @@ namespace MediaBrowser.Api.UserLibrary var ancestorIds = Array.Empty(); - var excludeFolderIds = user.Configuration.LatestItemsExcludes; + var excludeFolderIds = user.GetPreference(PreferenceKind.LatestItemExcludes); if (parentIdGuid.Equals(Guid.Empty) && excludeFolderIds.Length > 0) { ancestorIds = _libraryManager.GetUserRootFolder().GetChildren(user, true) @@ -179,7 +180,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// Gets the items to serialize. /// - private QueryResult GetQueryResult(GetItems request, DtoOptions dtoOptions, User user) + private QueryResult GetQueryResult(GetItems request, DtoOptions dtoOptions, Jellyfin.Data.Entities.User user) { if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase) || string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase)) @@ -211,14 +212,14 @@ namespace MediaBrowser.Api.UserLibrary request.IncludeItemTypes = "Playlist"; } - bool isInEnabledFolder = user.Policy.EnabledFolders.Any(i => new Guid(i) == item.Id) + bool isInEnabledFolder = user.GetPreference(PreferenceKind.EnabledFolders).Any(i => new Guid(i) == item.Id) // Assume all folders inside an EnabledChannel are enabled - || user.Policy.EnabledChannels.Any(i => new Guid(i) == item.Id); + || user.GetPreference(PreferenceKind.EnabledChannels).Any(i => new Guid(i) == item.Id); var collectionFolders = _libraryManager.GetCollectionFolders(item); foreach (var collectionFolder in collectionFolders) { - if (user.Policy.EnabledFolders.Contains( + if (user.GetPreference(PreferenceKind.EnabledFolders).Contains( collectionFolder.Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) { @@ -226,9 +227,12 @@ namespace MediaBrowser.Api.UserLibrary } } - if (!(item is UserRootFolder) && !user.Policy.EnableAllFolders && !isInEnabledFolder && !user.Policy.EnableAllChannels) + if (!(item is UserRootFolder) + && !isInEnabledFolder + && !user.HasPermission(PermissionKind.EnableAllFolders) + && !user.HasPermission(PermissionKind.EnableAllChannels)) { - Logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Name, item.Name); + Logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Username, item.Name); return new QueryResult { Items = Array.Empty(), @@ -251,7 +255,7 @@ namespace MediaBrowser.Api.UserLibrary }; } - private InternalItemsQuery GetItemsQuery(GetItems request, DtoOptions dtoOptions, User user) + private InternalItemsQuery GetItemsQuery(GetItems request, DtoOptions dtoOptions, Jellyfin.Data.Entities.User user) { var query = new InternalItemsQuery(user) { diff --git a/MediaBrowser.Api/UserLibrary/PlaystateService.cs b/MediaBrowser.Api/UserLibrary/PlaystateService.cs index d0faca163b..fb8bda1907 100644 --- a/MediaBrowser.Api/UserLibrary/PlaystateService.cs +++ b/MediaBrowser.Api/UserLibrary/PlaystateService.cs @@ -437,7 +437,7 @@ namespace MediaBrowser.Api.UserLibrary /// if set to true [was played]. /// The date played. /// Task. - private UserItemDataDto UpdatePlayedStatus(User user, string itemId, bool wasPlayed, DateTime? datePlayed) + private UserItemDataDto UpdatePlayedStatus(Jellyfin.Data.Entities.User user, string itemId, bool wasPlayed, DateTime? datePlayed) { var item = _libraryManager.GetItemById(itemId); diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index 7fa750adba..f758528859 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -312,7 +312,7 @@ namespace MediaBrowser.Api.UserLibrary if (!request.IsPlayed.HasValue) { - if (user.Configuration.HidePlayedInLatest) + if (user.HidePlayedInLatest) { request.IsPlayed = false; } diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 78fc6c6941..7bf4f88f40 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Authentication; @@ -300,12 +301,12 @@ namespace MediaBrowser.Api if (request.IsDisabled.HasValue) { - users = users.Where(i => i.Policy.IsDisabled == request.IsDisabled.Value); + users = users.Where(i => i.HasPermission(PermissionKind.IsDisabled) == request.IsDisabled.Value); } if (request.IsHidden.HasValue) { - users = users.Where(i => i.Policy.IsHidden == request.IsHidden.Value); + users = users.Where(i => i.HasPermission(PermissionKind.IsHidden) == request.IsHidden.Value); } if (filterByDevice) @@ -322,12 +323,12 @@ namespace MediaBrowser.Api { if (!_networkManager.IsInLocalNetwork(Request.RemoteIp)) { - users = users.Where(i => i.Policy.EnableRemoteAccess); + users = users.Where(i => i.HasPermission(PermissionKind.EnableRemoteAccess)); } } var result = users - .OrderBy(u => u.Name) + .OrderBy(u => u.Username) .Select(i => _userManager.GetUserDto(i, Request.RemoteIp)) .ToArray(); @@ -397,7 +398,7 @@ namespace MediaBrowser.Api // Password should always be null return Post(new AuthenticateUserByName { - Username = user.Name, + Username = user.Username, Password = null, Pw = request.Pw }); @@ -456,7 +457,12 @@ namespace MediaBrowser.Api } else { - var success = await _userManager.AuthenticateUser(user.Name, request.CurrentPw, request.CurrentPassword, Request.RemoteIp, false).ConfigureAwait(false); + var success = await _userManager.AuthenticateUser( + user.Username, + request.CurrentPw, + request.CurrentPassword, + Request.RemoteIp, + false).ConfigureAwait(false); if (success == null) { @@ -506,10 +512,10 @@ namespace MediaBrowser.Api var user = _userManager.GetUserById(id); - if (string.Equals(user.Name, dtoUser.Name, StringComparison.Ordinal)) + if (string.Equals(user.Username, dtoUser.Name, StringComparison.Ordinal)) { - _userManager.UpdateUser(user); - _userManager.UpdateConfiguration(user, dtoUser.Configuration); + await _userManager.UpdateUserAsync(user); + _userManager.UpdateConfiguration(user.Id, dtoUser.Configuration); } else { @@ -568,24 +574,24 @@ namespace MediaBrowser.Api var user = _userManager.GetUserById(request.Id); // If removing admin access - if (!request.IsAdministrator && user.Policy.IsAdministrator) + if (!request.IsAdministrator && user.HasPermission(PermissionKind.IsAdministrator)) { - if (_userManager.Users.Count(i => i.Policy.IsAdministrator) == 1) + if (_userManager.Users.Count(i => i.HasPermission(PermissionKind.IsAdministrator)) == 1) { throw new ArgumentException("There must be at least one user in the system with administrative access."); } } // If disabling - if (request.IsDisabled && user.Policy.IsAdministrator) + if (request.IsDisabled && user.HasPermission(PermissionKind.IsAdministrator)) { throw new ArgumentException("Administrators cannot be disabled."); } // If disabling - if (request.IsDisabled && !user.Policy.IsDisabled) + if (request.IsDisabled && !user.HasPermission(PermissionKind.IsDisabled)) { - if (_userManager.Users.Count(i => !i.Policy.IsDisabled) == 1) + if (_userManager.Users.Count(i => !i.HasPermission(PermissionKind.IsDisabled)) == 1) { throw new ArgumentException("There must be at least one enabled user in the system."); } @@ -594,7 +600,7 @@ namespace MediaBrowser.Api _sessionMananger.RevokeUserTokens(user.Id, currentToken); } - _userManager.UpdateUserPolicy(request.Id, request); + _userManager.UpdatePolicy(request.Id, request); } } } diff --git a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs index f5571065f2..c0324a3841 100644 --- a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs +++ b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs @@ -1,5 +1,5 @@ using System.Threading.Tasks; -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; using MediaBrowser.Model.Users; namespace MediaBrowser.Controller.Authentication diff --git a/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs index 2639960e76..d9b814f694 100644 --- a/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs +++ b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs @@ -1,6 +1,6 @@ using System; using System.Threading.Tasks; -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; using MediaBrowser.Model.Users; namespace MediaBrowser.Controller.Authentication diff --git a/MediaBrowser.Controller/Channels/Channel.cs b/MediaBrowser.Controller/Channels/Channel.cs index cdf2ca69e6..d6a1fc84e1 100644 --- a/MediaBrowser.Controller/Channels/Channel.cs +++ b/MediaBrowser.Controller/Channels/Channel.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Linq; using System.Text.Json.Serialization; using System.Threading; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Querying; @@ -11,18 +12,20 @@ namespace MediaBrowser.Controller.Channels { public class Channel : Folder { - public override bool IsVisible(User user) + public override bool IsVisible(Jellyfin.Data.Entities.User user) { - if (user.Policy.BlockedChannels != null) + if (user.GetPreference(PreferenceKind.BlockedChannels) != null) { - if (user.Policy.BlockedChannels.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) + if (user.GetPreference(PreferenceKind.BlockedChannels).Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) { return false; } } else { - if (!user.Policy.EnableAllChannels && !user.Policy.EnabledChannels.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) + if (!user.HasPermission(PermissionKind.EnableAllChannels) + && !user.GetPreference(PreferenceKind.EnabledChannels) + .Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) { return false; } @@ -74,7 +77,7 @@ namespace MediaBrowser.Controller.Channels return false; } - internal static bool IsChannelVisible(BaseItem channelItem, User user) + internal static bool IsChannelVisible(BaseItem channelItem, Jellyfin.Data.Entities.User user) { var channel = ChannelManager.GetChannel(channelItem.ChannelId.ToString("")); diff --git a/MediaBrowser.Controller/Collections/ICollectionManager.cs b/MediaBrowser.Controller/Collections/ICollectionManager.cs index cfe8493d33..f51c73bd7d 100644 --- a/MediaBrowser.Controller/Collections/ICollectionManager.cs +++ b/MediaBrowser.Controller/Collections/ICollectionManager.cs @@ -51,6 +51,6 @@ namespace MediaBrowser.Controller.Collections /// The items. /// The user. /// IEnumerable{BaseItem}. - IEnumerable CollapseItemsWithinBoxSets(IEnumerable items, User user); + IEnumerable CollapseItemsWithinBoxSets(IEnumerable items, Jellyfin.Data.Entities.User user); } } diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs index 77d5676310..117af110a0 100644 --- a/MediaBrowser.Controller/Devices/IDeviceManager.cs +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -1,7 +1,7 @@ using System; using System.IO; using System.Threading.Tasks; -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; using MediaBrowser.Model.Devices; using MediaBrowser.Model.Events; using MediaBrowser.Model.Querying; diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index 36c746624e..bdb12402a8 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -47,8 +47,11 @@ namespace MediaBrowser.Controller.Drawing /// The image. /// Guid. string GetImageCacheTag(BaseItem item, ItemImageInfo image); + string GetImageCacheTag(BaseItem item, ChapterInfo info); + string GetImageCacheTag(Jellyfin.Data.Entities.User user); + /// /// Processes the image. /// diff --git a/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs b/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs index df9050de5e..9f505be939 100644 --- a/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs +++ b/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs @@ -1,5 +1,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Entities; +using User = Jellyfin.Data.Entities.User; namespace MediaBrowser.Controller.Drawing { diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index ba693a065c..5ac4f05c0f 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -38,7 +38,7 @@ namespace MediaBrowser.Controller.Dto /// The fields. /// The user. /// The owner. - BaseItemDto GetBaseItemDto(BaseItem item, ItemFields[] fields, User user = null, BaseItem owner = null); + BaseItemDto GetBaseItemDto(BaseItem item, ItemFields[] fields, Jellyfin.Data.Entities.User user = null, BaseItem owner = null); /// /// Gets the base item dto. @@ -48,7 +48,7 @@ namespace MediaBrowser.Controller.Dto /// The user. /// The owner. /// BaseItemDto. - BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null); + BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, Jellyfin.Data.Entities.User user = null, BaseItem owner = null); /// /// Gets the base item dtos. @@ -57,11 +57,11 @@ namespace MediaBrowser.Controller.Dto /// The options. /// The user. /// The owner. - IReadOnlyList GetBaseItemDtos(IReadOnlyList items, DtoOptions options, User user = null, BaseItem owner = null); + IReadOnlyList GetBaseItemDtos(IReadOnlyList items, DtoOptions options, Jellyfin.Data.Entities.User user = null, BaseItem owner = null); /// /// Gets the item by name dto. /// - BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List taggedItems, User user = null); + BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List taggedItems, Jellyfin.Data.Entities.User user = null); } } diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index a700d0be48..9065cb27fb 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index c216176e7f..fbadeafad6 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; @@ -77,7 +79,7 @@ namespace MediaBrowser.Controller.Entities.Audio [JsonIgnore] public IEnumerable /// The user. /// PlayAccess. - public PlayAccess GetPlayAccess(User user) + public PlayAccess GetPlayAccess(Jellyfin.Data.Entities.User user) { - if (!user.Policy.EnableMediaPlayback) + if (!user.HasPermission(PermissionKind.EnableMediaPlayback)) { return PlayAccess.None; } @@ -1760,7 +1761,7 @@ namespace MediaBrowser.Controller.Entities /// The user. /// true if [is parental allowed] [the specified user]; otherwise, false. /// user - public bool IsParentalAllowed(User user) + public bool IsParentalAllowed(Jellyfin.Data.Entities.User user) { if (user == null) { @@ -1772,7 +1773,7 @@ namespace MediaBrowser.Controller.Entities return false; } - var maxAllowedRating = user.Policy.MaxParentalRating; + var maxAllowedRating = user.MaxParentalAgeRating; if (maxAllowedRating == null) { @@ -1788,7 +1789,7 @@ namespace MediaBrowser.Controller.Entities if (string.IsNullOrEmpty(rating)) { - return !GetBlockUnratedValue(user.Policy); + return !GetBlockUnratedValue(user); } var value = LocalizationManager.GetRatingLevel(rating); @@ -1796,7 +1797,7 @@ namespace MediaBrowser.Controller.Entities // Could not determine the integer value if (!value.HasValue) { - var isAllowed = !GetBlockUnratedValue(user.Policy); + var isAllowed = !GetBlockUnratedValue(user); if (!isAllowed) { @@ -1856,10 +1857,9 @@ namespace MediaBrowser.Controller.Entities return list.Distinct(StringComparer.OrdinalIgnoreCase).ToList(); } - private bool IsVisibleViaTags(User user) + private bool IsVisibleViaTags(Jellyfin.Data.Entities.User user) { - var policy = user.Policy; - if (policy.BlockedTags.Any(i => Tags.Contains(i, StringComparer.OrdinalIgnoreCase))) + if (user.GetPreference(PreferenceKind.BlockedTags).Any(i => Tags.Contains(i, StringComparer.OrdinalIgnoreCase))) { return false; } @@ -1885,22 +1885,18 @@ namespace MediaBrowser.Controller.Entities /// /// Gets the block unrated value. /// - /// The configuration. + /// The configuration. /// true if XXXX, false otherwise. - protected virtual bool GetBlockUnratedValue(UserPolicy config) + protected virtual bool GetBlockUnratedValue(Jellyfin.Data.Entities.User user) { // Don't block plain folders that are unrated. Let the media underneath get blocked // Special folders like series and albums will override this method. - if (IsFolder) - { - return false; - } - if (this is IItemByName) + if (IsFolder || this is IItemByName) { return false; } - return config.BlockUnratedItems.Contains(GetBlockUnratedType()); + return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(GetBlockUnratedType().ToString()); } /// @@ -1910,7 +1906,7 @@ namespace MediaBrowser.Controller.Entities /// The user. /// true if the specified user is visible; otherwise, false. /// user - public virtual bool IsVisible(User user) + public virtual bool IsVisible(Jellyfin.Data.Entities.User user) { if (user == null) { @@ -1920,7 +1916,7 @@ namespace MediaBrowser.Controller.Entities return IsParentalAllowed(user); } - public virtual bool IsVisibleStandalone(User user) + public virtual bool IsVisibleStandalone(Jellyfin.Data.Entities.User user) { if (SourceType == SourceType.Channel) { @@ -1933,7 +1929,7 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public virtual bool SupportsInheritedParentImages => false; - protected bool IsVisibleStandaloneInternal(User user, bool checkFolders) + protected bool IsVisibleStandaloneInternal(Jellyfin.Data.Entities.User user, bool checkFolders) { if (!IsVisible(user)) { @@ -2130,7 +2126,8 @@ namespace MediaBrowser.Controller.Entities /// if set to true [reset position]. /// Task. /// - public virtual void MarkPlayed(User user, + public virtual void MarkPlayed( + Jellyfin.Data.Entities.User user, DateTime? datePlayed, bool resetPosition) { @@ -2167,7 +2164,7 @@ namespace MediaBrowser.Controller.Entities /// The user. /// Task. /// - public virtual void MarkUnplayed(User user) + public virtual void MarkUnplayed(Jellyfin.Data.Entities.User user) { if (user == null) { @@ -2543,21 +2540,21 @@ namespace MediaBrowser.Controller.Entities UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None); } - public virtual bool IsPlayed(User user) + public virtual bool IsPlayed(Jellyfin.Data.Entities.User user) { var userdata = UserDataManager.GetUserData(user, this); return userdata != null && userdata.Played; } - public bool IsFavoriteOrLiked(User user) + public bool IsFavoriteOrLiked(Jellyfin.Data.Entities.User user) { var userdata = UserDataManager.GetUserData(user, this); return userdata != null && (userdata.IsFavorite || (userdata.Likes ?? false)); } - public virtual bool IsUnplayed(User user) + public virtual bool IsUnplayed(Jellyfin.Data.Entities.User user) { if (user == null) { @@ -2623,7 +2620,7 @@ namespace MediaBrowser.Controller.Entities return path; } - public virtual void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user, DtoOptions fields) + public virtual void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, Jellyfin.Data.Entities.User user, DtoOptions fields) { if (RunTimeTicks.HasValue) { @@ -2736,14 +2733,14 @@ namespace MediaBrowser.Controller.Entities return RefreshMetadataForOwnedItem(video, copyTitleMetadata, newOptions, cancellationToken); } - public string GetEtag(User user) + public string GetEtag(Jellyfin.Data.Entities.User user) { var list = GetEtagValues(user); return string.Join("|", list).GetMD5().ToString("N", CultureInfo.InvariantCulture); } - protected virtual List GetEtagValues(User user) + protected virtual List GetEtagValues(Jellyfin.Data.Entities.User user) { return new List { diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs index dcad2554bd..c4a2929dcc 100644 --- a/MediaBrowser.Controller/Entities/Book.cs +++ b/MediaBrowser.Controller/Entities/Book.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; diff --git a/MediaBrowser.Controller/Entities/DayOfWeekHelper.cs b/MediaBrowser.Controller/Entities/DayOfWeekHelper.cs deleted file mode 100644 index 8a79e0783c..0000000000 --- a/MediaBrowser.Controller/Entities/DayOfWeekHelper.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.Generic; -using MediaBrowser.Model.Configuration; - -namespace MediaBrowser.Controller.Entities -{ - public static class DayOfWeekHelper - { - public static List GetDaysOfWeek(DynamicDayOfWeek day) - { - return GetDaysOfWeek(new List { day }); - } - - public static List GetDaysOfWeek(List days) - { - var list = new List(); - - if (days.Contains(DynamicDayOfWeek.Sunday) || - days.Contains(DynamicDayOfWeek.Weekend) || - days.Contains(DynamicDayOfWeek.Everyday)) - { - list.Add(DayOfWeek.Sunday); - } - - if (days.Contains(DynamicDayOfWeek.Saturday) || - days.Contains(DynamicDayOfWeek.Weekend) || - days.Contains(DynamicDayOfWeek.Everyday)) - { - list.Add(DayOfWeek.Saturday); - } - - if (days.Contains(DynamicDayOfWeek.Monday) || - days.Contains(DynamicDayOfWeek.Weekday) || - days.Contains(DynamicDayOfWeek.Everyday)) - { - list.Add(DayOfWeek.Monday); - } - - if (days.Contains(DynamicDayOfWeek.Tuesday) || - days.Contains(DynamicDayOfWeek.Weekday) || - days.Contains(DynamicDayOfWeek.Everyday)) - { - list.Add(DayOfWeek.Tuesday - ); - } - - if (days.Contains(DynamicDayOfWeek.Wednesday) || - days.Contains(DynamicDayOfWeek.Weekday) || - days.Contains(DynamicDayOfWeek.Everyday)) - { - list.Add(DayOfWeek.Wednesday); - } - - if (days.Contains(DynamicDayOfWeek.Thursday) || - days.Contains(DynamicDayOfWeek.Weekday) || - days.Contains(DynamicDayOfWeek.Everyday)) - { - list.Add(DayOfWeek.Thursday); - } - - if (days.Contains(DynamicDayOfWeek.Friday) || - days.Contains(DynamicDayOfWeek.Weekday) || - days.Contains(DynamicDayOfWeek.Everyday)) - { - list.Add(DayOfWeek.Friday); - } - - return list; - } - } -} diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index a468e0c35a..03644b0c6e 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Collections; @@ -173,23 +174,25 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public IEnumerable RecursiveChildren => GetRecursiveChildren(); - public override bool IsVisible(User user) + public override bool IsVisible(Jellyfin.Data.Entities.User user) { if (this is ICollectionFolder && !(this is BasePluginFolder)) { - if (user.Policy.BlockedMediaFolders != null) + if (user.GetPreference(PreferenceKind.BlockedMediaFolders) != null) { - if (user.Policy.BlockedMediaFolders.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase) || + if (user.GetPreference(PreferenceKind.BlockedMediaFolders).Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase) || // Backwards compatibility - user.Policy.BlockedMediaFolders.Contains(Name, StringComparer.OrdinalIgnoreCase)) + user.GetPreference(PreferenceKind.BlockedMediaFolders).Contains(Name, StringComparer.OrdinalIgnoreCase)) { return false; } } else { - if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) + if (!user.HasPermission(PermissionKind.EnableAllFolders) + && !user.GetPreference(PreferenceKind.EnabledFolders) + .Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) { return false; } @@ -583,7 +586,7 @@ namespace MediaBrowser.Controller.Entities }); } - public virtual int GetChildCount(User user) + public virtual int GetChildCount(Jellyfin.Data.Entities.User user) { if (LinkedChildren.Length > 0) { @@ -608,7 +611,7 @@ namespace MediaBrowser.Controller.Entities return result.TotalRecordCount; } - public virtual int GetRecursiveChildCount(User user) + public virtual int GetRecursiveChildCount(Jellyfin.Data.Entities.User user) { return GetItems(new InternalItemsQuery(user) { @@ -877,7 +880,7 @@ namespace MediaBrowser.Controller.Entities try { query.Parent = this; - query.ChannelIds = new Guid[] { ChannelId }; + query.ChannelIds = new[] { ChannelId }; // Don't blow up here because it could cause parent screens with other content to fail return ChannelManager.GetChannelItemsInternal(query, new SimpleProgress(), CancellationToken.None).Result; @@ -947,11 +950,13 @@ namespace MediaBrowser.Controller.Entities return UserViewBuilder.SortAndPage(items, null, query, LibraryManager, enableSorting); } - private static IEnumerable CollapseBoxSetItemsIfNeeded(IEnumerable items, + private static IEnumerable CollapseBoxSetItemsIfNeeded( + IEnumerable items, InternalItemsQuery query, BaseItem queryParent, - User user, - IServerConfigurationManager configurationManager, ICollectionManager collectionManager) + Jellyfin.Data.Entities.User user, + IServerConfigurationManager configurationManager, + ICollectionManager collectionManager) { if (items == null) { @@ -968,7 +973,7 @@ namespace MediaBrowser.Controller.Entities private static bool CollapseBoxSetItems(InternalItemsQuery query, BaseItem queryParent, - User user, + Jellyfin.Data.Entities.User user, IServerConfigurationManager configurationManager) { // Could end up stuck in a loop like this @@ -1191,7 +1196,7 @@ namespace MediaBrowser.Controller.Entities return true; } - public List GetChildren(User user, bool includeLinkedChildren) + public List GetChildren(Jellyfin.Data.Entities.User user, bool includeLinkedChildren) { if (user == null) { @@ -1201,7 +1206,7 @@ namespace MediaBrowser.Controller.Entities return GetChildren(user, includeLinkedChildren, null); } - public virtual List GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query) + public virtual List GetChildren(Jellyfin.Data.Entities.User user, bool includeLinkedChildren, InternalItemsQuery query) { if (user == null) { @@ -1221,7 +1226,7 @@ namespace MediaBrowser.Controller.Entities return result.Values.ToList(); } - protected virtual IEnumerable GetEligibleChildrenForRecursiveChildren(User user) + protected virtual IEnumerable GetEligibleChildrenForRecursiveChildren(Jellyfin.Data.Entities.User user) { return Children; } @@ -1230,7 +1235,7 @@ namespace MediaBrowser.Controller.Entities /// Adds the children to list. /// /// true if XXXX, false otherwise - private void AddChildren(User user, bool includeLinkedChildren, Dictionary result, bool recursive, InternalItemsQuery query) + private void AddChildren(Jellyfin.Data.Entities.User user, bool includeLinkedChildren, Dictionary result, bool recursive, InternalItemsQuery query) { foreach (var child in GetEligibleChildrenForRecursiveChildren(user)) { @@ -1279,12 +1284,12 @@ namespace MediaBrowser.Controller.Entities /// if set to true [include linked children]. /// IEnumerable{BaseItem}. /// - public IEnumerable GetRecursiveChildren(User user, bool includeLinkedChildren = true) + public IEnumerable GetRecursiveChildren(Jellyfin.Data.Entities.User user, bool includeLinkedChildren = true) { return GetRecursiveChildren(user, null); } - public virtual IEnumerable GetRecursiveChildren(User user, InternalItemsQuery query) + public virtual IEnumerable GetRecursiveChildren(Jellyfin.Data.Entities.User user, InternalItemsQuery query) { if (user == null) { @@ -1403,7 +1408,7 @@ namespace MediaBrowser.Controller.Entities return false; } - public List GetLinkedChildren(User user) + public List GetLinkedChildren(Jellyfin.Data.Entities.User user) { if (!FilterLinkedChildrenPerUser || user == null) { @@ -1565,7 +1570,7 @@ namespace MediaBrowser.Controller.Entities /// The date played. /// if set to true [reset position]. /// Task. - public override void MarkPlayed(User user, + public override void MarkPlayed(Jellyfin.Data.Entities.User user, DateTime? datePlayed, bool resetPosition) { @@ -1577,7 +1582,7 @@ namespace MediaBrowser.Controller.Entities EnableTotalRecordCount = false }; - if (!user.Configuration.DisplayMissingEpisodes) + if (!user.DisplayMissingEpisodes) { query.IsVirtualItem = false; } @@ -1606,7 +1611,7 @@ namespace MediaBrowser.Controller.Entities /// /// The user. /// Task. - public override void MarkUnplayed(User user) + public override void MarkUnplayed(Jellyfin.Data.Entities.User user) { var itemsResult = GetItemList(new InternalItemsQuery { @@ -1624,7 +1629,7 @@ namespace MediaBrowser.Controller.Entities } } - public override bool IsPlayed(User user) + public override bool IsPlayed(Jellyfin.Data.Entities.User user) { var itemsResult = GetItemList(new InternalItemsQuery(user) { @@ -1639,7 +1644,7 @@ namespace MediaBrowser.Controller.Entities .All(i => i.IsPlayed(user)); } - public override bool IsUnplayed(User user) + public override bool IsUnplayed(Jellyfin.Data.Entities.User user) { return !IsPlayed(user); } @@ -1684,7 +1689,7 @@ namespace MediaBrowser.Controller.Entities } } - public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user, DtoOptions fields) + public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, Jellyfin.Data.Entities.User user, DtoOptions fields) { if (!SupportsUserDataFromChildren) { diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index bd96059e32..6a2cafcba4 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; @@ -15,7 +16,7 @@ namespace MediaBrowser.Controller.Entities public int? Limit { get; set; } - public User User { get; set; } + public Jellyfin.Data.Entities.User User { get; set; } public BaseItem SimilarTo { get; set; } @@ -213,25 +214,26 @@ namespace MediaBrowser.Controller.Entities Years = Array.Empty(); } - public InternalItemsQuery(User user) + public InternalItemsQuery(Jellyfin.Data.Entities.User user) : this() { SetUser(user); } - public void SetUser(User user) + public void SetUser(Jellyfin.Data.Entities.User user) { if (user != null) { - var policy = user.Policy; - MaxParentalRating = policy.MaxParentalRating; + MaxParentalRating = user.MaxParentalAgeRating; - if (policy.MaxParentalRating.HasValue) + if (MaxParentalRating.HasValue) { - BlockUnratedItems = policy.BlockUnratedItems.Where(i => i != UnratedItem.Other).ToArray(); + BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems) + .Where(i => i != UnratedItem.Other.ToString()) + .Select(e => Enum.Parse(e, true)).ToArray(); } - ExcludeInheritedTags = policy.BlockedTags; + ExcludeInheritedTags = user.GetPreference(PreferenceKind.BlockedTags); User = user; } diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index feaf8c45ac..1c1bde3e42 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -2,11 +2,10 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Users; namespace MediaBrowser.Controller.Entities.Movies { @@ -45,9 +44,9 @@ namespace MediaBrowser.Controller.Entities.Movies /// The display order. public string DisplayOrder { get; set; } - protected override bool GetBlockUnratedValue(UserPolicy config) + protected override bool GetBlockUnratedValue(Jellyfin.Data.Entities.User user) { - return config.BlockUnratedItems.Contains(UnratedItem.Movie); + return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Movie.ToString()); } public override double GetDefaultPrimaryImageAspectRatio() @@ -101,7 +100,7 @@ namespace MediaBrowser.Controller.Entities.Movies [JsonIgnore] public override bool IsPreSorted => true; - public override bool IsAuthorizedToDelete(User user, List allCollectionFolders) + public override bool IsAuthorizedToDelete(Jellyfin.Data.Entities.User user, List allCollectionFolders) { return true; } @@ -111,7 +110,7 @@ namespace MediaBrowser.Controller.Entities.Movies return true; } - public override List GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query) + public override List GetChildren(Jellyfin.Data.Entities.User user, bool includeLinkedChildren, InternalItemsQuery query) { var children = base.GetChildren(user, includeLinkedChildren, query); @@ -131,7 +130,7 @@ namespace MediaBrowser.Controller.Entities.Movies return LibraryManager.Sort(children, user, new[] { ItemSortBy.ProductionYear, ItemSortBy.PremiereDate, ItemSortBy.SortName }, SortOrder.Ascending).ToList(); } - public override IEnumerable GetRecursiveChildren(User user, InternalItemsQuery query) + public override IEnumerable GetRecursiveChildren(Jellyfin.Data.Entities.User user, InternalItemsQuery query) { var children = base.GetRecursiveChildren(user, query); @@ -149,7 +148,7 @@ namespace MediaBrowser.Controller.Entities.Movies return GetItemLookupInfo(); } - public override bool IsVisible(User user) + public override bool IsVisible(Jellyfin.Data.Entities.User user) { if (IsLegacyBoxSet) { @@ -177,7 +176,7 @@ namespace MediaBrowser.Controller.Entities.Movies return false; } - public override bool IsVisibleStandalone(User user) + public override bool IsVisibleStandalone(Jellyfin.Data.Entities.User user) { if (IsLegacyBoxSet) { @@ -189,7 +188,7 @@ namespace MediaBrowser.Controller.Entities.Movies public Guid[] LibraryFolderIds { get; set; } - private Guid[] GetLibraryFolderIds(User user) + private Guid[] GetLibraryFolderIds(Jellyfin.Data.Entities.User user) { return LibraryManager.GetUserRootFolder().GetChildren(user, true) .Select(i => i.Id) diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index 11dc472b61..38359afccb 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; diff --git a/MediaBrowser.Controller/Entities/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs index 6032420635..1b9d4614e4 100644 --- a/MediaBrowser.Controller/Entities/MusicVideo.cs +++ b/MediaBrowser.Controller/Entities/MusicVideo.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 49229fa4be..0a89da46df 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 9c8a469e26..0d1fec62f9 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; @@ -60,7 +61,7 @@ namespace MediaBrowser.Controller.Entities.TV return list; } - public override int GetChildCount(User user) + public override int GetChildCount(Jellyfin.Data.Entities.User user) { var result = GetChildren(user, true).Count; @@ -143,17 +144,17 @@ namespace MediaBrowser.Controller.Entities.TV /// /// Gets the episodes. /// - public List GetEpisodes(User user, DtoOptions options) + public List GetEpisodes(Jellyfin.Data.Entities.User user, DtoOptions options) { return GetEpisodes(Series, user, options); } - public List GetEpisodes(Series series, User user, DtoOptions options) + public List GetEpisodes(Series series, Jellyfin.Data.Entities.User user, DtoOptions options) { return GetEpisodes(series, user, null, options); } - public List GetEpisodes(Series series, User user, IEnumerable allSeriesEpisodes, DtoOptions options) + public List GetEpisodes(Series series, Jellyfin.Data.Entities.User user, IEnumerable allSeriesEpisodes, DtoOptions options) { return series.GetSeasonEpisodes(this, user, allSeriesEpisodes, options); } @@ -163,12 +164,12 @@ namespace MediaBrowser.Controller.Entities.TV return Series.GetSeasonEpisodes(this, null, null, new DtoOptions(true)); } - public override List GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query) + public override List GetChildren(Jellyfin.Data.Entities.User user, bool includeLinkedChildren, InternalItemsQuery query) { return GetEpisodes(user, new DtoOptions(true)); } - protected override bool GetBlockUnratedValue(UserPolicy config) + protected override bool GetBlockUnratedValue(Jellyfin.Data.Entities.User config) { // Don't block. Let either the entire series rating or episode rating determine it return false; @@ -203,7 +204,7 @@ namespace MediaBrowser.Controller.Entities.TV public Guid FindSeriesId() { var series = FindParent(); - return series == null ? Guid.Empty : series.Id; + return series?.Id ?? Guid.Empty; } /// @@ -234,7 +235,7 @@ namespace MediaBrowser.Controller.Entities.TV if (!IndexNumber.HasValue && !string.IsNullOrEmpty(Path)) { - IndexNumber = IndexNumber ?? LibraryManager.GetSeasonNumberFromPath(Path); + IndexNumber ??= LibraryManager.GetSeasonNumberFromPath(Path); // If a change was made record it if (IndexNumber.HasValue) diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 2475b2b7ec..4aed5fbdcc 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -5,13 +5,12 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Users; namespace MediaBrowser.Controller.Entities.TV { @@ -111,7 +110,7 @@ namespace MediaBrowser.Controller.Entities.TV return series.GetPresentationUniqueKey(); } - public override int GetChildCount(User user) + public override int GetChildCount(Jellyfin.Data.Entities.User user) { var seriesKey = GetUniqueSeriesKey(this); @@ -119,7 +118,7 @@ namespace MediaBrowser.Controller.Entities.TV { AncestorWithPresentationUniqueKey = null, SeriesPresentationUniqueKey = seriesKey, - IncludeItemTypes = new[] { typeof(Season).Name }, + IncludeItemTypes = new[] { nameof(Season) }, IsVirtualItem = false, Limit = 0, DtoOptions = new DtoOptions(false) @@ -131,7 +130,7 @@ namespace MediaBrowser.Controller.Entities.TV return result; } - public override int GetRecursiveChildCount(User user) + public override int GetRecursiveChildCount(Jellyfin.Data.Entities.User user) { var seriesKey = GetUniqueSeriesKey(this); @@ -179,12 +178,12 @@ namespace MediaBrowser.Controller.Entities.TV return list; } - public override List GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query) + public override List GetChildren(Jellyfin.Data.Entities.User user, bool includeLinkedChildren, InternalItemsQuery query) { return GetSeasons(user, new DtoOptions(true)); } - public List GetSeasons(User user, DtoOptions options) + public List GetSeasons(Jellyfin.Data.Entities.User user, DtoOptions options) { var query = new InternalItemsQuery(user) { @@ -196,7 +195,7 @@ namespace MediaBrowser.Controller.Entities.TV return LibraryManager.GetItemList(query); } - private void SetSeasonQueryOptions(InternalItemsQuery query, User user) + private void SetSeasonQueryOptions(InternalItemsQuery query, Jellyfin.Data.Entities.User user) { var seriesKey = GetUniqueSeriesKey(this); @@ -205,14 +204,9 @@ namespace MediaBrowser.Controller.Entities.TV query.IncludeItemTypes = new[] { typeof(Season).Name }; query.OrderBy = new[] { ItemSortBy.SortName }.Select(i => new ValueTuple(i, SortOrder.Ascending)).ToArray(); - if (user != null) + if (user != null && !user.DisplayMissingEpisodes) { - var config = user.Configuration; - - if (!config.DisplayMissingEpisodes) - { - query.IsMissing = false; - } + query.IsMissing = false; } } @@ -245,7 +239,7 @@ namespace MediaBrowser.Controller.Entities.TV return LibraryManager.GetItemsResult(query); } - public IEnumerable GetEpisodes(User user, DtoOptions options) + public IEnumerable GetEpisodes(Jellyfin.Data.Entities.User user, DtoOptions options) { var seriesKey = GetUniqueSeriesKey(this); @@ -257,8 +251,8 @@ namespace MediaBrowser.Controller.Entities.TV OrderBy = new[] { ItemSortBy.SortName }.Select(i => new ValueTuple(i, SortOrder.Ascending)).ToArray(), DtoOptions = options }; - var config = user.Configuration; - if (!config.DisplayMissingEpisodes) + + if (!user.DisplayMissingEpisodes) { query.IsMissing = false; } @@ -311,7 +305,7 @@ namespace MediaBrowser.Controller.Entities.TV // Refresh episodes and other children foreach (var item in items) { - if ((item is Season)) + if (item is Season) { continue; } @@ -351,7 +345,7 @@ namespace MediaBrowser.Controller.Entities.TV await ProviderManager.RefreshSingleItem(this, refreshOptions, cancellationToken).ConfigureAwait(false); } - public List GetSeasonEpisodes(Season parentSeason, User user, DtoOptions options) + public List GetSeasonEpisodes(Season parentSeason, Jellyfin.Data.Entities.User user, DtoOptions options) { var queryFromSeries = ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons; @@ -370,8 +364,7 @@ namespace MediaBrowser.Controller.Entities.TV }; if (user != null) { - var config = user.Configuration; - if (!config.DisplayMissingEpisodes) + if (!user.DisplayMissingEpisodes) { query.IsMissing = false; } @@ -382,7 +375,7 @@ namespace MediaBrowser.Controller.Entities.TV return GetSeasonEpisodes(parentSeason, user, allItems, options); } - public List GetSeasonEpisodes(Season parentSeason, User user, IEnumerable allSeriesEpisodes, DtoOptions options) + public List GetSeasonEpisodes(Season parentSeason, Jellyfin.Data.Entities.User user, IEnumerable allSeriesEpisodes, DtoOptions options) { if (allSeriesEpisodes == null) { @@ -452,9 +445,9 @@ namespace MediaBrowser.Controller.Entities.TV } - protected override bool GetBlockUnratedValue(UserPolicy config) + protected override bool GetBlockUnratedValue(Jellyfin.Data.Entities.User user) { - return config.BlockUnratedItems.Contains(UnratedItem.Series); + return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Series.ToString()); } public override UnratedItem GetBlockUnratedType() diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs index 0b8be90cd1..c646e8ae69 100644 --- a/MediaBrowser.Controller/Entities/Trailer.cs +++ b/MediaBrowser.Controller/Entities/Trailer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs deleted file mode 100644 index 53601a6104..0000000000 --- a/MediaBrowser.Controller/Entities/User.cs +++ /dev/null @@ -1,262 +0,0 @@ -using System; -using System.Globalization; -using System.IO; -using System.Text.Json.Serialization; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Users; - -namespace MediaBrowser.Controller.Entities -{ - /// - /// Class User - /// - public class User : BaseItem - { - public static IUserManager UserManager { get; set; } - - /// - /// Gets or sets the password. - /// - /// The password. - public string Password { get; set; } - public string EasyPassword { get; set; } - - // Strictly to remove JsonIgnore - public override ItemImageInfo[] ImageInfos - { - get => base.ImageInfos; - set => base.ImageInfos = value; - } - - /// - /// Gets or sets the path. - /// - /// The path. - [JsonIgnore] - public override string Path - { - get => ConfigurationDirectoryPath; - set => base.Path = value; - } - - private string _name; - /// - /// Gets or sets the name. - /// - /// The name. - public override string Name - { - get => _name; - set - { - _name = value; - - // lazy load this again - SortName = null; - } - } - - /// - /// Returns the folder containing the item. - /// If the item is a folder, it returns the folder itself - /// - /// The containing folder path. - [JsonIgnore] - public override string ContainingFolderPath => Path; - - /// - /// Gets the root folder. - /// - /// The root folder. - [JsonIgnore] - public Folder RootFolder => LibraryManager.GetUserRootFolder(); - - /// - /// Gets or sets the last login date. - /// - /// The last login date. - public DateTime? LastLoginDate { get; set; } - /// - /// Gets or sets the last activity date. - /// - /// The last activity date. - public DateTime? LastActivityDate { get; set; } - - private volatile UserConfiguration _config; - private readonly object _configSyncLock = new object(); - [JsonIgnore] - public UserConfiguration Configuration - { - get - { - if (_config == null) - { - lock (_configSyncLock) - { - if (_config == null) - { - _config = UserManager.GetUserConfiguration(this); - } - } - } - - return _config; - } - set => _config = value; - } - - private volatile UserPolicy _policy; - private readonly object _policySyncLock = new object(); - [JsonIgnore] - public UserPolicy Policy - { - get - { - if (_policy == null) - { - lock (_policySyncLock) - { - if (_policy == null) - { - _policy = UserManager.GetUserPolicy(this); - } - } - } - - return _policy; - } - set => _policy = value; - } - - /// - /// Renames the user. - /// - /// The new name. - /// Task. - /// - public Task Rename(string newName) - { - if (string.IsNullOrWhiteSpace(newName)) - { - throw new ArgumentException("Username can't be empty", nameof(newName)); - } - - Name = newName; - - return RefreshMetadata( - new MetadataRefreshOptions(new DirectoryService(FileSystem)) - { - ReplaceAllMetadata = true, - ImageRefreshMode = MetadataRefreshMode.FullRefresh, - MetadataRefreshMode = MetadataRefreshMode.FullRefresh, - ForceSave = true - - }, - CancellationToken.None); - } - - public override void UpdateToRepository(ItemUpdateType updateReason, CancellationToken cancellationToken) - { - UserManager.UpdateUser(this); - } - - /// - /// Gets the path to the user's configuration directory - /// - /// The configuration directory path. - [JsonIgnore] - public string ConfigurationDirectoryPath => GetConfigurationDirectoryPath(Name); - - public override double GetDefaultPrimaryImageAspectRatio() - { - return 1; - } - - /// - /// Gets the configuration directory path. - /// - /// The username. - /// System.String. - private string GetConfigurationDirectoryPath(string username) - { - var parentPath = ConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath; - - // TODO: Remove idPath and just use usernamePath for future releases - var usernamePath = System.IO.Path.Combine(parentPath, username); - var idPath = System.IO.Path.Combine(parentPath, Id.ToString("N", CultureInfo.InvariantCulture)); - if (!Directory.Exists(usernamePath) && Directory.Exists(idPath)) - { - Directory.Move(idPath, usernamePath); - } - - return usernamePath; - } - - public bool IsParentalScheduleAllowed() - { - return IsParentalScheduleAllowed(DateTime.UtcNow); - } - - public bool IsParentalScheduleAllowed(DateTime date) - { - var schedules = Policy.AccessSchedules; - - if (schedules.Length == 0) - { - return true; - } - - foreach (var i in schedules) - { - if (IsParentalScheduleAllowed(i, date)) - { - return true; - } - } - return false; - } - - private bool IsParentalScheduleAllowed(AccessSchedule schedule, DateTime date) - { - if (date.Kind != DateTimeKind.Utc) - { - throw new ArgumentException("Utc date expected"); - } - - var localTime = date.ToLocalTime(); - - return DayOfWeekHelper.GetDaysOfWeek(schedule.DayOfWeek).Contains(localTime.DayOfWeek) && - IsWithinTime(schedule, localTime); - } - - private bool IsWithinTime(AccessSchedule schedule, DateTime localTime) - { - var hour = localTime.TimeOfDay.TotalHours; - - return hour >= schedule.StartHour && hour <= schedule.EndHour; - } - - public bool IsFolderGrouped(Guid id) - { - foreach (var i in Configuration.GroupedFolders) - { - if (new Guid(i) == id) - { - return true; - } - } - return false; - } - - [JsonIgnore] - public override bool SupportsPeople => false; - - public long InternalId { get; set; } - - - } -} diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index 8a68f830cc..9d211540d2 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -63,7 +63,7 @@ namespace MediaBrowser.Controller.Entities return UserViewBuilder.SortAndPage(result, null, query, LibraryManager, true); } - public override int GetChildCount(User user) + public override int GetChildCount(Jellyfin.Data.Entities.User user) { return GetChildren(user, true).Count; } @@ -74,7 +74,7 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public override bool IsPreSorted => true; - protected override IEnumerable GetEligibleChildrenForRecursiveChildren(User user) + protected override IEnumerable GetEligibleChildrenForRecursiveChildren(Jellyfin.Data.Entities.User user) { var list = base.GetEligibleChildrenForRecursiveChildren(user).ToList(); list.AddRange(LibraryManager.RootFolder.VirtualChildren); diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index 4ce9ec6f82..b44e7c1917 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -48,7 +48,7 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public override bool SupportsPlayedStatus => false; - public override int GetChildCount(User user) + public override int GetChildCount(Jellyfin.Data.Entities.User user) { return GetChildren(user, true).Count; } @@ -70,7 +70,7 @@ namespace MediaBrowser.Controller.Entities .GetUserItems(parent, this, CollectionType, query); } - public override List GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query) + public override List GetChildren(Jellyfin.Data.Entities.User user, bool includeLinkedChildren, InternalItemsQuery query) { if (query == null) { @@ -93,7 +93,7 @@ namespace MediaBrowser.Controller.Entities return true; } - public override IEnumerable GetRecursiveChildren(User user, InternalItemsQuery query) + public override IEnumerable GetRecursiveChildren(Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.SetUser(user); query.Recursive = true; @@ -103,7 +103,7 @@ namespace MediaBrowser.Controller.Entities return GetItemList(query); } - protected override IEnumerable GetEligibleChildrenForRecursiveChildren(User user) + protected override IEnumerable GetEligibleChildrenForRecursiveChildren(Jellyfin.Data.Entities.User user) { return GetChildren(user, false); } diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 435a1e8dae..0ad8e6b710 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -125,7 +125,7 @@ namespace MediaBrowser.Controller.Entities return 50; } - private QueryResult GetMovieFolders(Folder parent, User user, InternalItemsQuery query) + private QueryResult GetMovieFolders(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { if (query.Recursive) { @@ -140,19 +140,20 @@ namespace MediaBrowser.Controller.Entities return parent.QueryRecursive(query); } - var list = new List(); - - list.Add(GetUserView(SpecialFolder.MovieResume, "HeaderContinueWatching", "0", parent)); - list.Add(GetUserView(SpecialFolder.MovieLatest, "Latest", "1", parent)); - list.Add(GetUserView(SpecialFolder.MovieMovies, "Movies", "2", parent)); - list.Add(GetUserView(SpecialFolder.MovieCollections, "Collections", "3", parent)); - list.Add(GetUserView(SpecialFolder.MovieFavorites, "Favorites", "4", parent)); - list.Add(GetUserView(SpecialFolder.MovieGenres, "Genres", "5", parent)); + var list = new List + { + GetUserView(SpecialFolder.MovieResume, "HeaderContinueWatching", "0", parent), + GetUserView(SpecialFolder.MovieLatest, "Latest", "1", parent), + GetUserView(SpecialFolder.MovieMovies, "Movies", "2", parent), + GetUserView(SpecialFolder.MovieCollections, "Collections", "3", parent), + GetUserView(SpecialFolder.MovieFavorites, "Favorites", "4", parent), + GetUserView(SpecialFolder.MovieGenres, "Genres", "5", parent) + }; return GetResult(list, parent, query); } - private QueryResult GetFavoriteMovies(Folder parent, User user, InternalItemsQuery query) + private QueryResult GetFavoriteMovies(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -163,7 +164,7 @@ namespace MediaBrowser.Controller.Entities return _libraryManager.GetItemsResult(query); } - private QueryResult GetFavoriteSeries(Folder parent, User user, InternalItemsQuery query) + private QueryResult GetFavoriteSeries(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -174,7 +175,7 @@ namespace MediaBrowser.Controller.Entities return _libraryManager.GetItemsResult(query); } - private QueryResult GetFavoriteEpisodes(Folder parent, User user, InternalItemsQuery query) + private QueryResult GetFavoriteEpisodes(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -185,7 +186,7 @@ namespace MediaBrowser.Controller.Entities return _libraryManager.GetItemsResult(query); } - private QueryResult GetMovieMovies(Folder parent, User user, InternalItemsQuery query) + private QueryResult GetMovieMovies(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -196,7 +197,7 @@ namespace MediaBrowser.Controller.Entities return _libraryManager.GetItemsResult(query); } - private QueryResult GetMovieCollections(Folder parent, User user, InternalItemsQuery query) + private QueryResult GetMovieCollections(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.Parent = null; query.IncludeItemTypes = new[] { typeof(BoxSet).Name }; @@ -206,7 +207,7 @@ namespace MediaBrowser.Controller.Entities return _libraryManager.GetItemsResult(query); } - private QueryResult GetMovieLatest(Folder parent, User user, InternalItemsQuery query) + private QueryResult GetMovieLatest(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.OrderBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName }.Select(i => new ValueTuple(i, SortOrder.Descending)).ToArray(); @@ -219,7 +220,7 @@ namespace MediaBrowser.Controller.Entities return ConvertToResult(_libraryManager.GetItemList(query)); } - private QueryResult GetMovieResume(Folder parent, User user, InternalItemsQuery query) + private QueryResult GetMovieResume(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.OrderBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName }.Select(i => new ValueTuple(i, SortOrder.Descending)).ToArray(); query.IsResumable = true; @@ -242,7 +243,7 @@ namespace MediaBrowser.Controller.Entities }; } - private QueryResult GetMovieGenres(Folder parent, User user, InternalItemsQuery query) + private QueryResult GetMovieGenres(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { var genres = parent.QueryRecursive(new InternalItemsQuery(user) { @@ -272,7 +273,7 @@ namespace MediaBrowser.Controller.Entities return GetResult(genres, parent, query); } - private QueryResult GetMovieGenreItems(Folder queryParent, Folder displayParent, User user, InternalItemsQuery query) + private QueryResult GetMovieGenreItems(Folder queryParent, Folder displayParent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = queryParent; @@ -284,7 +285,7 @@ namespace MediaBrowser.Controller.Entities return _libraryManager.GetItemsResult(query); } - private QueryResult GetTvView(Folder parent, User user, InternalItemsQuery query) + private QueryResult GetTvView(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { if (query.Recursive) { @@ -293,26 +294,32 @@ namespace MediaBrowser.Controller.Entities if (query.IncludeItemTypes.Length == 0) { - query.IncludeItemTypes = new[] { typeof(Series).Name, typeof(Season).Name, typeof(Episode).Name }; + query.IncludeItemTypes = new[] + { + nameof(Series), + nameof(Season), + nameof(Episode) + }; } return parent.QueryRecursive(query); } - var list = new List(); - - list.Add(GetUserView(SpecialFolder.TvResume, "HeaderContinueWatching", "0", parent)); - list.Add(GetUserView(SpecialFolder.TvNextUp, "HeaderNextUp", "1", parent)); - list.Add(GetUserView(SpecialFolder.TvLatest, "Latest", "2", parent)); - list.Add(GetUserView(SpecialFolder.TvShowSeries, "Shows", "3", parent)); - list.Add(GetUserView(SpecialFolder.TvFavoriteSeries, "HeaderFavoriteShows", "4", parent)); - list.Add(GetUserView(SpecialFolder.TvFavoriteEpisodes, "HeaderFavoriteEpisodes", "5", parent)); - list.Add(GetUserView(SpecialFolder.TvGenres, "Genres", "6", parent)); + var list = new List + { + GetUserView(SpecialFolder.TvResume, "HeaderContinueWatching", "0", parent), + GetUserView(SpecialFolder.TvNextUp, "HeaderNextUp", "1", parent), + GetUserView(SpecialFolder.TvLatest, "Latest", "2", parent), + GetUserView(SpecialFolder.TvShowSeries, "Shows", "3", parent), + GetUserView(SpecialFolder.TvFavoriteSeries, "HeaderFavoriteShows", "4", parent), + GetUserView(SpecialFolder.TvFavoriteEpisodes, "HeaderFavoriteEpisodes", "5", parent), + GetUserView(SpecialFolder.TvGenres, "Genres", "6", parent) + }; return GetResult(list, parent, query); } - private QueryResult GetTvLatest(Folder parent, User user, InternalItemsQuery query) + private QueryResult GetTvLatest(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.OrderBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName }.Select(i => new ValueTuple(i, SortOrder.Descending)).ToArray(); @@ -341,7 +348,7 @@ namespace MediaBrowser.Controller.Entities return result; } - private QueryResult GetTvResume(Folder parent, User user, InternalItemsQuery query) + private QueryResult GetTvResume(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.OrderBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName }.Select(i => new ValueTuple(i, SortOrder.Descending)).ToArray(); query.IsResumable = true; @@ -354,7 +361,7 @@ namespace MediaBrowser.Controller.Entities return ConvertToResult(_libraryManager.GetItemList(query)); } - private QueryResult GetTvSeries(Folder parent, User user, InternalItemsQuery query) + private QueryResult GetTvSeries(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -365,7 +372,7 @@ namespace MediaBrowser.Controller.Entities return _libraryManager.GetItemsResult(query); } - private QueryResult GetTvGenres(Folder parent, User user, InternalItemsQuery query) + private QueryResult GetTvGenres(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { var genres = parent.QueryRecursive(new InternalItemsQuery(user) { @@ -395,7 +402,7 @@ namespace MediaBrowser.Controller.Entities return GetResult(genres, parent, query); } - private QueryResult GetTvGenreItems(Folder queryParent, Folder displayParent, User user, InternalItemsQuery query) + private QueryResult GetTvGenreItems(Folder queryParent, Folder displayParent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = queryParent; @@ -417,7 +424,8 @@ namespace MediaBrowser.Controller.Entities }; } - private QueryResult GetResult(IEnumerable items, + private QueryResult GetResult( + IEnumerable items, BaseItem queryParent, InternalItemsQuery query) where T : BaseItem @@ -484,7 +492,7 @@ namespace MediaBrowser.Controller.Entities }; } - public static bool Filter(BaseItem item, User user, InternalItemsQuery query, IUserDataManager userDataManager, ILibraryManager libraryManager) + public static bool Filter(BaseItem item, Jellyfin.Data.Entities.User user, InternalItemsQuery query, IUserDataManager userDataManager, ILibraryManager libraryManager) { if (query.MediaTypes.Length > 0 && !query.MediaTypes.Contains(item.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) { @@ -942,7 +950,7 @@ namespace MediaBrowser.Controller.Entities return true; } - private IEnumerable GetMediaFolders(User user) + private IEnumerable GetMediaFolders(Jellyfin.Data.Entities.User user) { if (user == null) { @@ -957,7 +965,7 @@ namespace MediaBrowser.Controller.Entities .Where(i => user.IsFolderGrouped(i.Id) && UserView.IsEligibleForGrouping(i)); } - private BaseItem[] GetMediaFolders(User user, IEnumerable viewTypes) + private BaseItem[] GetMediaFolders(Jellyfin.Data.Entities.User user, IEnumerable viewTypes) { if (user == null) { @@ -978,7 +986,7 @@ namespace MediaBrowser.Controller.Entities }).ToArray(); } - private BaseItem[] GetMediaFolders(Folder parent, User user, IEnumerable viewTypes) + private BaseItem[] GetMediaFolders(Folder parent, Jellyfin.Data.Entities.User user, IEnumerable viewTypes) { if (parent == null || parent is UserView) { diff --git a/MediaBrowser.Controller/Library/IIntroProvider.cs b/MediaBrowser.Controller/Library/IIntroProvider.cs index d9d1ca8c73..aa70016112 100644 --- a/MediaBrowser.Controller/Library/IIntroProvider.cs +++ b/MediaBrowser.Controller/Library/IIntroProvider.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Controller.Library /// The item. /// The user. /// IEnumerable{System.String}. - Task> GetIntros(BaseItem item, User user); + Task> GetIntros(BaseItem item, Jellyfin.Data.Entities.User user); /// /// Gets all intro files. diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 2e1c97f674..ada570bfd6 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -141,7 +141,7 @@ namespace MediaBrowser.Controller.Library /// The item. /// The user. /// IEnumerable{System.String}. - Task> GetIntros(BaseItem item, User user); + Task> GetIntros(BaseItem item, Jellyfin.Data.Entities.User user); /// /// Gets all intro files. @@ -172,8 +172,8 @@ namespace MediaBrowser.Controller.Library /// The sort by. /// The sort order. /// IEnumerable{BaseItem}. - IEnumerable Sort(IEnumerable items, User user, IEnumerable sortBy, SortOrder sortOrder); - IEnumerable Sort(IEnumerable items, User user, IEnumerable> orderBy); + IEnumerable Sort(IEnumerable items, Jellyfin.Data.Entities.User user, IEnumerable sortBy, SortOrder sortOrder); + IEnumerable Sort(IEnumerable items, Jellyfin.Data.Entities.User user, IEnumerable> orderBy); /// /// Gets the user root folder. @@ -284,7 +284,8 @@ namespace MediaBrowser.Controller.Library /// The parent identifier. /// Type of the view. /// Name of the sort. - UserView GetNamedView(User user, + UserView GetNamedView( + Jellyfin.Data.Entities.User user, string name, Guid parentId, string viewType, @@ -297,7 +298,8 @@ namespace MediaBrowser.Controller.Library /// The name. /// Type of the view. /// Name of the sort. - UserView GetNamedView(User user, + UserView GetNamedView( + Jellyfin.Data.Entities.User user, string name, string viewType, string sortName); diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index 0ceabd0e68..57368778a3 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -55,12 +55,12 @@ namespace MediaBrowser.Controller.Library /// /// Gets the playack media sources. /// - Task> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken); + Task> GetPlaybackMediaSources(BaseItem item, Jellyfin.Data.Entities.User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken); /// /// Gets the static media sources. /// - List GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, User user = null); + List GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, Jellyfin.Data.Entities.User user = null); /// /// Gets the static media source. @@ -100,7 +100,7 @@ namespace MediaBrowser.Controller.Library MediaProtocol GetPathProtocol(string path); - void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user); + void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, Jellyfin.Data.Entities.User user); Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, string cacheKey, bool addProbeDelay, bool isLiveStream, CancellationToken cancellationToken); diff --git a/MediaBrowser.Controller/Library/IMusicManager.cs b/MediaBrowser.Controller/Library/IMusicManager.cs index 554dd08953..0618837bcc 100644 --- a/MediaBrowser.Controller/Library/IMusicManager.cs +++ b/MediaBrowser.Controller/Library/IMusicManager.cs @@ -10,16 +10,16 @@ namespace MediaBrowser.Controller.Library /// /// Gets the instant mix from song. /// - List GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions); + List GetInstantMixFromItem(BaseItem item, Jellyfin.Data.Entities.User user, DtoOptions dtoOptions); /// /// Gets the instant mix from artist. /// - List GetInstantMixFromArtist(MusicArtist artist, User user, DtoOptions dtoOptions); + List GetInstantMixFromArtist(MusicArtist artist, Jellyfin.Data.Entities.User user, DtoOptions dtoOptions); /// /// Gets the instant mix from genre. /// - List GetInstantMixFromGenres(IEnumerable genres, User user, DtoOptions dtoOptions); + List GetInstantMixFromGenres(IEnumerable genres, Jellyfin.Data.Entities.User user, DtoOptions dtoOptions); } } diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs index eb735d31a9..15da560efd 100644 --- a/MediaBrowser.Controller/Library/IUserDataManager.cs +++ b/MediaBrowser.Controller/Library/IUserDataManager.cs @@ -27,18 +27,18 @@ namespace MediaBrowser.Controller.Library /// The reason. /// The cancellation token. void SaveUserData(Guid userId, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken); - void SaveUserData(User userId, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken); + void SaveUserData(Jellyfin.Data.Entities.User user, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken); - UserItemData GetUserData(User user, BaseItem item); + UserItemData GetUserData(Jellyfin.Data.Entities.User user, BaseItem item); UserItemData GetUserData(Guid userId, BaseItem item); /// /// Gets the user data dto. /// - UserItemDataDto GetUserDataDto(BaseItem item, User user); + UserItemDataDto GetUserDataDto(BaseItem item, Jellyfin.Data.Entities.User user); - UserItemDataDto GetUserDataDto(BaseItem item, BaseItemDto itemDto, User user, DtoOptions dto_options); + UserItemDataDto GetUserDataDto(BaseItem item, BaseItemDto itemDto, Jellyfin.Data.Entities.User user, DtoOptions dto_options); /// /// Get all user data for the given user diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index be7b4ce59d..1e385dcb9a 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -1,9 +1,8 @@ using System; using System.Collections.Generic; -using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Events; @@ -17,36 +16,41 @@ namespace MediaBrowser.Controller.Library public interface IUserManager { /// - /// Gets the users. + /// Occurs when a user is updated. /// - /// The users. - IEnumerable Users { get; } + event EventHandler> OnUserUpdated; /// - /// Gets the user ids. + /// Occurs when a user is created. /// - /// The users ids. - IEnumerable UsersIds { get; } + event EventHandler> OnUserCreated; /// - /// Occurs when [user updated]. + /// Occurs when a user is deleted. /// - event EventHandler> UserUpdated; + event EventHandler> OnUserDeleted; /// - /// Occurs when [user deleted]. + /// Occurs when a user's password is changed. /// - event EventHandler> UserDeleted; - - event EventHandler> UserCreated; + event EventHandler> OnUserPasswordChanged; - event EventHandler> UserPolicyUpdated; - - event EventHandler> UserConfigurationUpdated; + /// + /// Occurs when a user is locked out. + /// + event EventHandler> OnUserLockedOut; - event EventHandler> UserPasswordChanged; + /// + /// Gets the users. + /// + /// The users. + IEnumerable Users { get; } - event EventHandler> UserLockedOut; + /// + /// Gets the user ids. + /// + /// The users ids. + IEnumerable UsersIds { get; } /// /// Gets a user by Id. @@ -63,13 +67,6 @@ namespace MediaBrowser.Controller.Library /// User. User GetUserByName(string name); - /// - /// Refreshes metadata for each user - /// - /// The cancellation token. - /// Task. - Task RefreshUsersMetadata(CancellationToken cancellationToken); - /// /// Renames the user. /// @@ -89,19 +86,27 @@ namespace MediaBrowser.Controller.Library void UpdateUser(User user); /// - /// Creates the user. + /// Updates the user. /// - /// The name. - /// User. + /// The user. + /// If user is null. + /// If the provided user doesn't exist. + /// A task representing the update of the user. + Task UpdateUserAsync(User user); + + /// + /// Creates a user with the specified name. + /// + /// The name of the new user. + /// The created user. /// name /// User CreateUser(string name); /// - /// Deletes the user. + /// Deletes the specified user. /// - /// The user. - /// Task. + /// The user to be deleted. void DeleteUser(User user); /// @@ -111,13 +116,6 @@ namespace MediaBrowser.Controller.Library /// Task. Task ResetPassword(User user); - /// - /// Gets the offline user dto. - /// - /// The user. - /// UserDto. - UserDto GetOfflineUserDto(User user); - /// /// Resets the easy password. /// @@ -163,47 +161,28 @@ namespace MediaBrowser.Controller.Library /// true if XXXX, false otherwise. Task RedeemPasswordResetPin(string pin); - /// - /// Gets the user policy. - /// - /// The user. - /// UserPolicy. - UserPolicy GetUserPolicy(User user); - - /// - /// Gets the user configuration. - /// - /// The user. - /// UserConfiguration. - UserConfiguration GetUserConfiguration(User user); + void AddParts(IEnumerable authenticationProviders, IEnumerable passwordResetProviders); - /// - /// Updates the configuration. - /// - /// The user identifier. - /// The new configuration. - /// Task. - void UpdateConfiguration(Guid userId, UserConfiguration newConfiguration); + NameIdPair[] GetAuthenticationProviders(); - void UpdateConfiguration(User user, UserConfiguration newConfiguration); + NameIdPair[] GetPasswordResetProviders(); /// - /// Updates the user policy. + /// This method updates the user's configuration. + /// This is only included as a stopgap until the new API, using this internally is not recommended. + /// Instead, modify the user object directlu, then call . /// - /// The user identifier. - /// The user policy. - void UpdateUserPolicy(Guid userId, UserPolicy userPolicy); + /// The user's Id. + /// The request containing the new user configuration. + void UpdateConfiguration(Guid userId, UserConfiguration config); /// - /// Makes the valid username. + /// This method updates the user's policy. + /// This is only included as a stopgap until the new API, using this internally is not recommended. + /// Instead, modify the user object directlu, then call . /// - /// The username. - /// System.String. - string MakeValidUsername(string username); - - void AddParts(IEnumerable authenticationProviders, IEnumerable passwordResetProviders); - - NameIdPair[] GetAuthenticationProviders(); - NameIdPair[] GetPasswordResetProviders(); + /// The user's Id. + /// The request containing the new user policy. + void UpdatePolicy(Guid userId, UserPolicy policy); } } diff --git a/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs b/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs index b0302d04cc..83c0e3297a 100644 --- a/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs +++ b/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs @@ -11,7 +11,7 @@ namespace MediaBrowser.Controller.Library /// public class PlaybackProgressEventArgs : EventArgs { - public List Users { get; set; } + public List Users { get; set; } public long? PlaybackPositionTicks { get; set; } public BaseItem Item { get; set; } public BaseItemDto MediaInfo { get; set; } @@ -28,7 +28,7 @@ namespace MediaBrowser.Controller.Library public PlaybackProgressEventArgs() { - Users = new List(); + Users = new List(); } } } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index e02c387e42..99fd18bf9c 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -115,7 +115,7 @@ namespace MediaBrowser.Controller.LiveTv /// The cancellation token. /// The user. /// Task{ProgramInfoDto}. - Task GetProgram(string id, CancellationToken cancellationToken, User user = null); + Task GetProgram(string id, CancellationToken cancellationToken, Jellyfin.Data.Entities.User user = null); /// /// Gets the programs. @@ -202,7 +202,7 @@ namespace MediaBrowser.Controller.LiveTv /// Gets the enabled users. /// /// IEnumerable{User}. - IEnumerable GetEnabledUsers(); + IEnumerable GetEnabledUsers(); /// /// Gets the internal channels. @@ -221,7 +221,7 @@ namespace MediaBrowser.Controller.LiveTv /// The fields. /// The user. /// Task. - Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> programs, ItemFields[] fields, User user = null); + Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> programs, ItemFields[] fields, Jellyfin.Data.Entities.User user = null); /// /// Saves the tuner host. @@ -258,7 +258,7 @@ namespace MediaBrowser.Controller.LiveTv /// The items. /// The options. /// The user. - void AddChannelInfo(IReadOnlyCollection<(BaseItemDto, LiveTvChannel)> items, DtoOptions options, User user); + void AddChannelInfo(IReadOnlyCollection<(BaseItemDto, LiveTvChannel)> items, DtoOptions options, Jellyfin.Data.Entities.User user); Task> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken); Task> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken); @@ -277,9 +277,9 @@ namespace MediaBrowser.Controller.LiveTv ActiveRecordingInfo GetActiveRecordingInfo(string path); - void AddInfoToRecordingDto(BaseItem item, BaseItemDto dto, ActiveRecordingInfo activeRecordingInfo, User user = null); + void AddInfoToRecordingDto(BaseItem item, BaseItemDto dto, ActiveRecordingInfo activeRecordingInfo, Jellyfin.Data.Entities.User user = null); - List GetRecordingFolders(User user); + List GetRecordingFolders(Jellyfin.Data.Entities.User user); } public class ActiveRecordingInfo diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs index 60391bb83f..f3ff8bd044 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs index 13df85aeda..e6dc4bdda2 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 61a3306756..2559bc2489 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Text; using System.Threading; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Extensions; using MediaBrowser.Model.Configuration; @@ -1991,7 +1992,7 @@ namespace MediaBrowser.Controller.MediaEncoding filters.Add("hwupload"); } - // When the input may or may not be hardware QSV decodable + // When the input may or may not be hardware QSV decodable else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) { if (!hasTextSubs) @@ -2147,7 +2148,7 @@ namespace MediaBrowser.Controller.MediaEncoding var user = state.User; // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not - if (user != null && !user.Policy.EnableVideoPlaybackTranscoding) + if (user != null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)) { state.OutputVideoCodec = "copy"; } @@ -2163,7 +2164,7 @@ namespace MediaBrowser.Controller.MediaEncoding var user = state.User; // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not - if (user != null && !user.Policy.EnableAudioPlaybackTranscoding) + if (user != null && !user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)) { state.OutputAudioCodec = "copy"; } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index 1127a08ded..dc4361fc34 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -18,23 +18,38 @@ namespace MediaBrowser.Controller.MediaEncoding public class EncodingJobInfo { public MediaStream VideoStream { get; set; } + public VideoType VideoType { get; set; } + public Dictionary RemoteHttpHeaders { get; set; } + public string OutputVideoCodec { get; set; } + public MediaProtocol InputProtocol { get; set; } + public string MediaPath { get; set; } + public bool IsInputVideo { get; set; } + public IIsoMount IsoMount { get; set; } + public string[] PlayableStreamFileNames { get; set; } + public string OutputAudioCodec { get; set; } + public int? OutputVideoBitrate { get; set; } + public MediaStream SubtitleStream { get; set; } + public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; } + public string[] SupportedSubtitleCodecs { get; set; } public int InternalSubtitleStreamOffset { get; set; } + public MediaSourceInfo MediaSource { get; set; } - public User User { get; set; } + + public Jellyfin.Data.Entities.User User { get; set; } public long? RunTimeTicks { get; set; } diff --git a/MediaBrowser.Controller/Net/AuthorizationInfo.cs b/MediaBrowser.Controller/Net/AuthorizationInfo.cs index 3e004763df..4361e253b6 100644 --- a/MediaBrowser.Controller/Net/AuthorizationInfo.cs +++ b/MediaBrowser.Controller/Net/AuthorizationInfo.cs @@ -1,36 +1,40 @@ using System; -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; namespace MediaBrowser.Controller.Net { public class AuthorizationInfo { /// - /// Gets or sets the user identifier. + /// Gets the user identifier. /// /// The user identifier. - public Guid UserId => User == null ? Guid.Empty : User.Id; + public Guid UserId => User?.Id ?? Guid.Empty; /// /// Gets or sets the device identifier. /// /// The device identifier. public string DeviceId { get; set; } + /// /// Gets or sets the device. /// /// The device. public string Device { get; set; } + /// /// Gets or sets the client. /// /// The client. public string Client { get; set; } + /// /// Gets or sets the version. /// /// The version. public string Version { get; set; } + /// /// Gets or sets the token. /// diff --git a/MediaBrowser.Controller/Net/IAuthService.cs b/MediaBrowser.Controller/Net/IAuthService.cs index 9132404a08..61fc7e6e64 100644 --- a/MediaBrowser.Controller/Net/IAuthService.cs +++ b/MediaBrowser.Controller/Net/IAuthService.cs @@ -9,6 +9,6 @@ namespace MediaBrowser.Controller.Net public interface IAuthService { void Authenticate(IRequest request, IAuthenticationAttributes authAttribtues); - User? Authenticate(HttpRequest request, IAuthenticationAttributes authAttribtues); + Jellyfin.Data.Entities.User? Authenticate(HttpRequest request, IAuthenticationAttributes authAttribtues); } } diff --git a/MediaBrowser.Controller/Net/ISessionContext.cs b/MediaBrowser.Controller/Net/ISessionContext.cs index 5c3c19f6b3..421ac3fe24 100644 --- a/MediaBrowser.Controller/Net/ISessionContext.cs +++ b/MediaBrowser.Controller/Net/ISessionContext.cs @@ -1,4 +1,4 @@ -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Services; diff --git a/MediaBrowser.Controller/Notifications/INotificationService.cs b/MediaBrowser.Controller/Notifications/INotificationService.cs index 8c6019923f..2bc7517587 100644 --- a/MediaBrowser.Controller/Notifications/INotificationService.cs +++ b/MediaBrowser.Controller/Notifications/INotificationService.cs @@ -25,6 +25,6 @@ namespace MediaBrowser.Controller.Notifications /// /// The user. /// true if [is enabled for user] [the specified user identifier]; otherwise, false. - bool IsEnabledForUser(User user); + bool IsEnabledForUser(Jellyfin.Data.Entities.User user); } } diff --git a/MediaBrowser.Controller/Notifications/UserNotification.cs b/MediaBrowser.Controller/Notifications/UserNotification.cs index 3f46468b31..a1029589b8 100644 --- a/MediaBrowser.Controller/Notifications/UserNotification.cs +++ b/MediaBrowser.Controller/Notifications/UserNotification.cs @@ -1,5 +1,5 @@ using System; -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; using MediaBrowser.Model.Notifications; namespace MediaBrowser.Controller.Notifications diff --git a/MediaBrowser.Controller/Persistence/IUserRepository.cs b/MediaBrowser.Controller/Persistence/IUserRepository.cs deleted file mode 100644 index cd23e52234..0000000000 --- a/MediaBrowser.Controller/Persistence/IUserRepository.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; -using MediaBrowser.Controller.Entities; - -namespace MediaBrowser.Controller.Persistence -{ - /// - /// Provides an interface to implement a User repository - /// - public interface IUserRepository : IRepository - { - /// - /// Deletes the user. - /// - /// The user. - /// Task. - void DeleteUser(User user); - - /// - /// Retrieves all users. - /// - /// IEnumerable{User}. - List RetrieveAllUsers(); - - void CreateUser(User user); - void UpdateUser(User user); - } -} diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index 3b08e72b92..03bdf1eaf2 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -78,7 +78,7 @@ namespace MediaBrowser.Controller.Playlists return 1; } - public override bool IsAuthorizedToDelete(User user, List allCollectionFolders) + public override bool IsAuthorizedToDelete(Jellyfin.Data.Entities.User user, List allCollectionFolders) { return true; } @@ -99,7 +99,7 @@ namespace MediaBrowser.Controller.Playlists return Task.CompletedTask; } - public override List GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query) + public override List GetChildren(Jellyfin.Data.Entities.User user, bool includeLinkedChildren, InternalItemsQuery query) { return GetPlayableItems(user, query); } @@ -109,7 +109,7 @@ namespace MediaBrowser.Controller.Playlists return new List(); } - public override IEnumerable GetRecursiveChildren(User user, InternalItemsQuery query) + public override IEnumerable GetRecursiveChildren(Jellyfin.Data.Entities.User user, InternalItemsQuery query) { return GetPlayableItems(user, query); } @@ -119,7 +119,7 @@ namespace MediaBrowser.Controller.Playlists return GetLinkedChildrenInfos(); } - private List GetPlayableItems(User user, InternalItemsQuery query) + private List GetPlayableItems(Jellyfin.Data.Entities.User user, InternalItemsQuery query) { if (query == null) { @@ -131,7 +131,7 @@ namespace MediaBrowser.Controller.Playlists return base.GetChildren(user, true, query); } - public static List GetPlaylistItems(string playlistMediaType, IEnumerable inputItems, User user, DtoOptions options) + public static List GetPlaylistItems(string playlistMediaType, IEnumerable inputItems, Jellyfin.Data.Entities.User user, DtoOptions options) { if (user != null) { @@ -149,7 +149,7 @@ namespace MediaBrowser.Controller.Playlists return list; } - private static IEnumerable GetPlaylistItems(BaseItem item, User user, string mediaType, DtoOptions options) + private static IEnumerable GetPlaylistItems(BaseItem item, Jellyfin.Data.Entities.User user, string mediaType, DtoOptions options) { if (item is MusicGenre musicGenre) { @@ -222,7 +222,7 @@ namespace MediaBrowser.Controller.Playlists } } - public override bool IsVisible(User user) + public override bool IsVisible(Jellyfin.Data.Entities.User user) { if (!IsSharedItem) { @@ -241,18 +241,10 @@ namespace MediaBrowser.Controller.Playlists } var userId = user.Id.ToString("N", CultureInfo.InvariantCulture); - foreach (var share in shares) - { - if (string.Equals(share.UserId, userId, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; + return shares.Any(share => string.Equals(share.UserId, userId, StringComparison.OrdinalIgnoreCase)); } - public override bool IsVisibleStandalone(User user) + public override bool IsVisibleStandalone(Jellyfin.Data.Entities.User user) { if (!IsSharedItem) { diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index 254b274601..955db0278c 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -70,6 +71,8 @@ namespace MediaBrowser.Controller.Providers /// Task. Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken); + Task SaveImage(User user, Stream source, string mimeType, string path); + /// /// Adds the metadata providers. /// diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 771027103b..32e62db148 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -74,7 +74,7 @@ namespace MediaBrowser.Controller.Session /// Name of the device. /// The remote end point. /// The user. - SessionInfo LogSessionActivity(string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user); + SessionInfo LogSessionActivity(string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, Jellyfin.Data.Entities.User user); void UpdateDeviceName(string sessionId, string reportedDeviceName); diff --git a/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs b/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs index 1e2df37bfa..f079bc7ea1 100644 --- a/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs +++ b/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.Sorting /// Gets or sets the user. /// /// The user. - User User { get; set; } + Jellyfin.Data.Entities.User User { get; set; } /// /// Gets or sets the user manager. diff --git a/MediaBrowser.Model/Configuration/AccessSchedule.cs b/MediaBrowser.Model/Configuration/AccessSchedule.cs index 120c47dbca..7bd355449f 100644 --- a/MediaBrowser.Model/Configuration/AccessSchedule.cs +++ b/MediaBrowser.Model/Configuration/AccessSchedule.cs @@ -1,3 +1,5 @@ +using Jellyfin.Data.Enums; + #pragma warning disable CS1591 namespace MediaBrowser.Model.Configuration diff --git a/MediaBrowser.Model/Configuration/DynamicDayOfWeek.cs b/MediaBrowser.Model/Configuration/DynamicDayOfWeek.cs deleted file mode 100644 index 71b16cfba5..0000000000 --- a/MediaBrowser.Model/Configuration/DynamicDayOfWeek.cs +++ /dev/null @@ -1,18 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Model.Configuration -{ - public enum DynamicDayOfWeek - { - Sunday = 0, - Monday = 1, - Tuesday = 2, - Wednesday = 3, - Thursday = 4, - Friday = 5, - Saturday = 6, - Everyday = 7, - Weekday = 8, - Weekend = 9 - } -} diff --git a/MediaBrowser.Model/Configuration/SubtitlePlaybackMode.cs b/MediaBrowser.Model/Configuration/SubtitlePlaybackMode.cs deleted file mode 100644 index f0aa2b98c0..0000000000 --- a/MediaBrowser.Model/Configuration/SubtitlePlaybackMode.cs +++ /dev/null @@ -1,13 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Model.Configuration -{ - public enum SubtitlePlaybackMode - { - Default = 0, - Always = 1, - OnlyForced = 2, - None = 3, - Smart = 4 - } -} diff --git a/MediaBrowser.Model/Configuration/UnratedItem.cs b/MediaBrowser.Model/Configuration/UnratedItem.cs deleted file mode 100644 index e1d1a363db..0000000000 --- a/MediaBrowser.Model/Configuration/UnratedItem.cs +++ /dev/null @@ -1,17 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Model.Configuration -{ - public enum UnratedItem - { - Movie, - Trailer, - Series, - Music, - Book, - LiveTvChannel, - LiveTvProgram, - ChannelContent, - Other - } -} diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs index a475c9910b..79cf0a065b 100644 --- a/MediaBrowser.Model/Configuration/UserConfiguration.cs +++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System; +using Jellyfin.Data.Enums; namespace MediaBrowser.Model.Configuration { diff --git a/MediaBrowser.Model/Notifications/NotificationOptions.cs b/MediaBrowser.Model/Notifications/NotificationOptions.cs index 79a128e9be..b3c46ace2a 100644 --- a/MediaBrowser.Model/Notifications/NotificationOptions.cs +++ b/MediaBrowser.Model/Notifications/NotificationOptions.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System; +using Jellyfin.Data.Enums; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Users; @@ -109,7 +110,7 @@ namespace MediaBrowser.Model.Notifications !ListHelper.ContainsIgnoreCase(opt.DisabledMonitorUsers, userId.ToString("")); } - public bool IsEnabledToSendToUser(string type, string userId, UserPolicy userPolicy) + public bool IsEnabledToSendToUser(string type, string userId, Jellyfin.Data.Entities.User user) { NotificationOption opt = GetOptions(type); @@ -120,7 +121,7 @@ namespace MediaBrowser.Model.Notifications return true; } - if (opt.SendToUserMode == SendToUserType.Admins && userPolicy.IsAdministrator) + if (opt.SendToUserMode == SendToUserType.Admins && user.HasPermission(PermissionKind.IsAdministrator)) { return true; } diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index ae2b3fd4e9..8d94d39716 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -1,7 +1,8 @@ #pragma warning disable CS1591 using System; -using MediaBrowser.Model.Configuration; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; namespace MediaBrowser.Model.Users { @@ -33,7 +34,7 @@ namespace MediaBrowser.Model.Users public string[] BlockedTags { get; set; } public bool EnableUserPreferenceAccess { get; set; } - public AccessSchedule[] AccessSchedules { get; set; } + public Jellyfin.Data.Entities.AccessSchedule[] AccessSchedules { get; set; } public UnratedItem[] BlockUnratedItems { get; set; } public bool EnableRemoteControlOfOtherUsers { get; set; } public bool EnableSharedDeviceControl { get; set; } @@ -77,7 +78,7 @@ namespace MediaBrowser.Model.Users public string[] BlockedChannels { get; set; } public int RemoteClientBitrateLimit { get; set; } - public string AuthenticationProviderId { get; set; } + public string AuthenticatioIsnProviderId { get; set; } public string PasswordResetProviderId { get; set; } public UserPolicy() diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 3ab621ba41..6bed387807 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -5,17 +5,21 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using Microsoft.Extensions.Logging; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; +using Person = MediaBrowser.Controller.Entities.Person; +using Season = MediaBrowser.Controller.Entities.TV.Season; namespace MediaBrowser.Providers.Manager { @@ -78,11 +82,6 @@ namespace MediaBrowser.Providers.Manager var saveLocally = item.SupportsLocalMetadata && item.IsSaveLocalMetadataEnabled() && !item.ExtraType.HasValue && !(item is Audio); - if (item is User) - { - saveLocally = true; - } - if (type != ImageType.Primary && item is Episode) { saveLocally = false; @@ -136,7 +135,7 @@ namespace MediaBrowser.Providers.Manager var savedPaths = new List(); - using (source) + await using (source) { var currentPathIndex = 0; @@ -172,7 +171,6 @@ namespace MediaBrowser.Providers.Manager } catch (FileNotFoundException) { - } finally { @@ -181,6 +179,16 @@ namespace MediaBrowser.Providers.Manager } } + public async Task SaveImage(User user, Stream source, string mimeType, string path) + { + if (string.IsNullOrEmpty(mimeType)) + { + throw new ArgumentNullException(nameof(mimeType)); + } + + await SaveImageToLocation(source, path, path, CancellationToken.None).ConfigureAwait(false); + } + private async Task SaveImageToLocation(Stream source, string path, string retryPath, CancellationToken cancellationToken) { try @@ -244,7 +252,7 @@ namespace MediaBrowser.Providers.Manager _fileSystem.SetAttributes(path, false, false); - using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) + await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) { await source.CopyToAsync(fs, IODefaults.CopyToBufferSize, cancellationToken).ConfigureAwait(false); } @@ -439,7 +447,6 @@ namespace MediaBrowser.Providers.Manager { path = Path.Combine(Path.GetDirectoryName(item.Path), "metadata", filename + extension); } - else if (item.IsInMixedFolder) { path = GetSavePathForItemInMixedFolder(item, type, filename, extension); diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index cfff897672..f4e875a24f 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Net; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; @@ -16,7 +17,6 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Subtitles; @@ -28,6 +28,12 @@ using MediaBrowser.Model.Providers; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; using Priority_Queue; +using Book = MediaBrowser.Controller.Entities.Book; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using Movie = MediaBrowser.Controller.Entities.Movies.Movie; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; +using Season = MediaBrowser.Controller.Entities.TV.Season; +using Series = MediaBrowser.Controller.Entities.TV.Series; namespace MediaBrowser.Providers.Manager { @@ -182,6 +188,12 @@ namespace MediaBrowser.Providers.Manager return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken); } + public Task SaveImage(User user, Stream source, string mimeType, string path) + { + return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger) + .SaveImage(user, source, mimeType, path); + } + public async Task> GetAvailableRemoteImages(BaseItem item, RemoteImageQuery query, CancellationToken cancellationToken) { var providers = GetRemoteImageProviders(item, query.IncludeDisabledProviders); diff --git a/MediaBrowser.Providers/Users/UserMetadataService.cs b/MediaBrowser.Providers/Users/UserMetadataService.cs deleted file mode 100644 index fb6c91b6c0..0000000000 --- a/MediaBrowser.Providers/Users/UserMetadataService.cs +++ /dev/null @@ -1,30 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Providers.Manager; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Providers.Users -{ - public class UserMetadataService : MetadataService - { - public UserMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } - - /// - protected override void MergeData(MetadataResult source, MetadataResult target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) - { - ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); - } - } -} diff --git a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs index 3b3d03c8b7..0cb8d8be68 100644 --- a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs +++ b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs @@ -7,6 +7,7 @@ using AutoFixture; using AutoFixture.AutoMoq; using Jellyfin.Api.Auth; using Jellyfin.Api.Constants; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Net; using Microsoft.AspNetCore.Authentication; @@ -83,7 +84,7 @@ namespace Jellyfin.Api.Tests.Auth a => a.Authenticate( It.IsAny(), It.IsAny())) - .Returns((User?)null); + .Returns((Jellyfin.Data.Entities.User?)null); var authenticateResult = await _sut.AuthenticateAsync(); @@ -124,7 +125,7 @@ namespace Jellyfin.Api.Tests.Auth var user = SetupUser(); var authenticateResult = await _sut.AuthenticateAsync(); - Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Name, user.Name)); + Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Name, user.Username)); } [Theory] @@ -135,7 +136,7 @@ namespace Jellyfin.Api.Tests.Auth var user = SetupUser(isAdmin); var authenticateResult = await _sut.AuthenticateAsync(); - var expectedRole = user.Policy.IsAdministrator ? UserRoles.Administrator : UserRoles.User; + var expectedRole = user.HasPermission(PermissionKind.IsAdministrator) ? UserRoles.Administrator : UserRoles.User; Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Role, expectedRole)); } @@ -148,10 +149,10 @@ namespace Jellyfin.Api.Tests.Auth Assert.Equal(_scheme.Name, authenticatedResult.Ticket.AuthenticationScheme); } - private User SetupUser(bool isAdmin = false) + private Jellyfin.Data.Entities.User SetupUser(bool isAdmin = false) { - var user = _fixture.Create(); - user.Policy.IsAdministrator = isAdmin; + var user = _fixture.Create(); + user.SetPermission(PermissionKind.IsAdministrator, isAdmin); _jellyfinAuthServiceMock.Setup( a => a.Authenticate( -- cgit v1.2.3 From 8c04049a595df054f491712ed317274566f2d71b Mon Sep 17 00:00:00 2001 From: gion Date: Fri, 15 May 2020 20:06:41 +0200 Subject: Fix some code smells --- .../Session/SessionWebSocketListener.cs | 4 ++-- MediaBrowser.Api/SyncPlay/SyncPlayService.cs | 16 +++++----------- 2 files changed, 7 insertions(+), 13 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index a5293b41c2..3af18f6813 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -269,7 +269,7 @@ namespace Emby.Server.Implementations.Session if (inactive.Any()) { - _logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count()); + _logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count); } foreach (var webSocket in inactive) @@ -289,7 +289,7 @@ namespace Emby.Server.Implementations.Session { if (lost.Any()) { - _logger.LogInformation("Lost {0} WebSockets.", lost.Count()); + _logger.LogInformation("Lost {0} WebSockets.", lost.Count); foreach (var webSocket in lost) { // TODO: handle session relative to the lost webSocket diff --git a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs index 8f552ef89d..8064ea7dc1 100644 --- a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs +++ b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs @@ -182,13 +182,10 @@ namespace MediaBrowser.Api.SyncPlay } // Both null and empty strings mean that client isn't playing anything - if (!String.IsNullOrEmpty(request.PlayingItemId)) + if (!String.IsNullOrEmpty(request.PlayingItemId) && !Guid.TryParse(request.PlayingItemId, out playingItemId)) { - if (!Guid.TryParse(request.PlayingItemId, out playingItemId)) - { - Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId); - return; - } + Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId); + return; } var joinRequest = new JoinGroupRequest() @@ -220,12 +217,9 @@ namespace MediaBrowser.Api.SyncPlay var currentSession = GetSession(_sessionContext); var filterItemId = Guid.Empty; - if (!String.IsNullOrEmpty(request.FilterItemId)) + if (!String.IsNullOrEmpty(request.FilterItemId) && !Guid.TryParse(request.FilterItemId, out filterItemId)) { - if (!Guid.TryParse(request.FilterItemId, out filterItemId)) - { - Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); - } + Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); } return _syncPlayManager.ListGroups(currentSession, filterItemId); -- cgit v1.2.3 From 3eeb6576d8425c8d2917f861b466dfa36e3994df Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 15 May 2020 17:24:01 -0400 Subject: Migrate User DB to EF Core --- Emby.Server.Implementations/ApplicationHost.cs | 1 - .../Data/SqliteDisplayPreferencesRepository.cs | 225 ++++++ .../Session/SessionManager.cs | 35 +- Jellyfin.Data/Entities/AccessSchedule.cs | 18 +- Jellyfin.Data/Entities/Group.cs | 41 +- Jellyfin.Data/Entities/Permission.cs | 53 +- Jellyfin.Data/Entities/Preference.cs | 81 +-- Jellyfin.Data/Entities/User.cs | 141 ++-- Jellyfin.Data/IHasPermissions.cs | 10 + Jellyfin.Server.Implementations/JellyfinDb.cs | 10 +- .../JellyfinDbProvider.cs | 2 +- .../20200504195702_UserSchema.Designer.cs | 324 --------- .../Migrations/20200504195702_UserSchema.cs | 219 ------ .../Migrations/20200517002411_AddUsers.Designer.cs | 404 +++++++++++ .../Migrations/20200517002411_AddUsers.cs | 297 ++++++++ .../Migrations/JellyfinDbModelSnapshot.cs | 186 +++-- .../User/DefaultAuthenticationProvider.cs | 185 ----- .../User/DefaultPasswordResetProvider.cs | 126 ---- .../User/DeviceAccessEntryPoint.cs | 65 -- .../User/InvalidAuthProvider.cs | 47 -- .../User/UserManager.cs | 771 --------------------- .../Users/DefaultAuthenticationProvider.cs | 185 +++++ .../Users/DefaultPasswordResetProvider.cs | 127 ++++ .../Users/DeviceAccessEntryPoint.cs | 66 ++ .../Users/InvalidAuthProvider.cs | 47 ++ .../Users/UserManager.cs | 763 ++++++++++++++++++++ Jellyfin.Server/CoreAppHost.cs | 6 + Jellyfin.Server/Jellyfin.Server.csproj | 1 - .../Migrations/Routines/MigrateUserDb.cs | 100 ++- MediaBrowser.Model/Users/UserPolicy.cs | 8 +- 30 files changed, 2526 insertions(+), 2018 deletions(-) create mode 100644 Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs create mode 100644 Jellyfin.Data/IHasPermissions.cs delete mode 100644 Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.Designer.cs delete mode 100644 Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.Designer.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.cs delete mode 100644 Jellyfin.Server.Implementations/User/DefaultAuthenticationProvider.cs delete mode 100644 Jellyfin.Server.Implementations/User/DefaultPasswordResetProvider.cs delete mode 100644 Jellyfin.Server.Implementations/User/DeviceAccessEntryPoint.cs delete mode 100644 Jellyfin.Server.Implementations/User/InvalidAuthProvider.cs delete mode 100644 Jellyfin.Server.Implementations/User/UserManager.cs create mode 100644 Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs create mode 100644 Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs create mode 100644 Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs create mode 100644 Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs create mode 100644 Jellyfin.Server.Implementations/Users/UserManager.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index b2fe6a3ab7..56b1037639 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -562,7 +562,6 @@ namespace Emby.Server.Implementations // TODO: Refactor to eliminate the circular dependency here so that Lazy isn't required serviceCollection.AddTransient(provider => new Lazy(provider.GetRequiredService)); - serviceCollection.AddSingleton(); // TODO: Refactor to eliminate the circular dependency here so that Lazy isn't required // TODO: Add StartupOptions.FFmpegPath to IConfiguration and remove this custom activation diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs new file mode 100644 index 0000000000..63d0321b73 --- /dev/null +++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs @@ -0,0 +1,225 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text.Json; +using System.Threading; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Json; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using Microsoft.Extensions.Logging; +using SQLitePCL.pretty; + +namespace Emby.Server.Implementations.Data +{ + /// + /// Class SQLiteDisplayPreferencesRepository. + /// + public class SqliteDisplayPreferencesRepository : BaseSqliteRepository, IDisplayPreferencesRepository + { + private readonly IFileSystem _fileSystem; + + private readonly JsonSerializerOptions _jsonOptions; + + public SqliteDisplayPreferencesRepository(ILogger logger, IApplicationPaths appPaths, IFileSystem fileSystem) + : base(logger) + { + _fileSystem = fileSystem; + + _jsonOptions = JsonDefaults.GetOptions(); + + DbFilePath = Path.Combine(appPaths.DataPath, "displaypreferences.db"); + } + + /// + /// Gets the name of the repository. + /// + /// The name. + public string Name => "SQLite"; + + public void Initialize() + { + try + { + InitializeInternal(); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error loading database file. Will reset and retry."); + + _fileSystem.DeleteFile(DbFilePath); + + InitializeInternal(); + } + } + + /// + /// Opens the connection to the database + /// + /// Task. + private void InitializeInternal() + { + string[] queries = + { + "create table if not exists userdisplaypreferences (id GUID NOT NULL, userId GUID NOT NULL, client text NOT NULL, data BLOB NOT NULL)", + "create unique index if not exists userdisplaypreferencesindex on userdisplaypreferences (id, userId, client)" + }; + + using (var connection = GetConnection()) + { + connection.RunQueries(queries); + } + } + + /// + /// Save the display preferences associated with an item in the repo + /// + /// The display preferences. + /// The user id. + /// The client. + /// The cancellation token. + /// item + public void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, CancellationToken cancellationToken) + { + if (displayPreferences == null) + { + throw new ArgumentNullException(nameof(displayPreferences)); + } + + if (string.IsNullOrEmpty(displayPreferences.Id)) + { + throw new ArgumentException("Display preferences has an invalid Id", nameof(displayPreferences)); + } + + cancellationToken.ThrowIfCancellationRequested(); + + using (var connection = GetConnection()) + { + connection.RunInTransaction( + db => SaveDisplayPreferences(displayPreferences, userId, client, db), + TransactionMode); + } + } + + private void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, IDatabaseConnection connection) + { + var serialized = JsonSerializer.SerializeToUtf8Bytes(displayPreferences, _jsonOptions); + + using (var statement = connection.PrepareStatement("replace into userdisplaypreferences (id, userid, client, data) values (@id, @userId, @client, @data)")) + { + statement.TryBind("@id", new Guid(displayPreferences.Id).ToByteArray()); + statement.TryBind("@userId", userId.ToByteArray()); + statement.TryBind("@client", client); + statement.TryBind("@data", serialized); + + statement.MoveNext(); + } + } + + /// + /// Save all display preferences associated with a user in the repo + /// + /// The display preferences. + /// The user id. + /// The cancellation token. + /// item + public void SaveAllDisplayPreferences(IEnumerable displayPreferences, Guid userId, CancellationToken cancellationToken) + { + if (displayPreferences == null) + { + throw new ArgumentNullException(nameof(displayPreferences)); + } + + cancellationToken.ThrowIfCancellationRequested(); + + using (var connection = GetConnection()) + { + connection.RunInTransaction( + db => + { + foreach (var displayPreference in displayPreferences) + { + SaveDisplayPreferences(displayPreference, userId, displayPreference.Client, db); + } + }, + TransactionMode); + } + } + + /// + /// Gets the display preferences. + /// + /// The display preferences id. + /// The user id. + /// The client. + /// Task{DisplayPreferences}. + /// item + public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, Guid userId, string client) + { + if (string.IsNullOrEmpty(displayPreferencesId)) + { + throw new ArgumentNullException(nameof(displayPreferencesId)); + } + + var guidId = displayPreferencesId.GetMD5(); + + using (var connection = GetConnection(true)) + { + using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where id = @id and userId=@userId and client=@client")) + { + statement.TryBind("@id", guidId.ToByteArray()); + statement.TryBind("@userId", userId.ToByteArray()); + statement.TryBind("@client", client); + + foreach (var row in statement.ExecuteQuery()) + { + return Get(row); + } + } + } + + return new DisplayPreferences + { + Id = guidId.ToString("N", CultureInfo.InvariantCulture) + }; + } + + /// + /// Gets all display preferences for the given user. + /// + /// The user id. + /// Task{DisplayPreferences}. + /// item + public IEnumerable GetAllDisplayPreferences(Guid userId) + { + var list = new List(); + + using (var connection = GetConnection(true)) + using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where userId=@userId")) + { + statement.TryBind("@userId", userId.ToByteArray()); + + foreach (var row in statement.ExecuteQuery()) + { + list.Add(Get(row)); + } + } + + return list; + } + + private DisplayPreferences Get(IReadOnlyList row) + => JsonSerializer.Deserialize(row[0].ToBlob(), _jsonOptions); + + public void SaveDisplayPreferences(DisplayPreferences displayPreferences, string userId, string client, CancellationToken cancellationToken) + => SaveDisplayPreferences(displayPreferences, new Guid(userId), client, cancellationToken); + + public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, string userId, string client) + => GetDisplayPreferences(displayPreferencesId, new Guid(userId), client); + } +} diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 84b16f1c7a..54c26a8a50 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; @@ -14,7 +15,6 @@ using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; @@ -27,6 +27,7 @@ using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; namespace Emby.Server.Implementations.Session { @@ -254,7 +255,7 @@ namespace Emby.Server.Implementations.Session string deviceId, string deviceName, string remoteEndPoint, - Jellyfin.Data.Entities.User user) + User user) { CheckDisposed(); @@ -438,7 +439,7 @@ namespace Emby.Server.Implementations.Session string deviceId, string deviceName, string remoteEndPoint, - Jellyfin.Data.Entities.User user) + User user) { CheckDisposed(); @@ -457,7 +458,7 @@ namespace Emby.Server.Implementations.Session sessionInfo.UserId = user?.Id ?? Guid.Empty; sessionInfo.UserName = user?.Username; - sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user); + sessionInfo.UserPrimaryImageTag = user?.ProfileImage == null ? null : GetImageCacheTag(user); sessionInfo.RemoteEndPoint = remoteEndPoint; sessionInfo.Client = appName; @@ -483,7 +484,7 @@ namespace Emby.Server.Implementations.Session string deviceId, string deviceName, string remoteEndPoint, - Jellyfin.Data.Entities.User user) + User user) { var sessionInfo = new SessionInfo(this, _logger) { @@ -497,7 +498,7 @@ namespace Emby.Server.Implementations.Session sessionInfo.UserId = user?.Id ?? Guid.Empty; sessionInfo.UserName = username; - sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user); + sessionInfo.UserPrimaryImageTag = user?.ProfileImage == null ? null : GetImageCacheTag(user); sessionInfo.RemoteEndPoint = remoteEndPoint; if (string.IsNullOrEmpty(deviceName)) @@ -520,9 +521,9 @@ namespace Emby.Server.Implementations.Session return sessionInfo; } - private List GetUsers(SessionInfo session) + private List GetUsers(SessionInfo session) { - var users = new List(); + var users = new List(); if (session.UserId != Guid.Empty) { @@ -680,7 +681,7 @@ namespace Emby.Server.Implementations.Session /// /// The user object. /// The item. - private void OnPlaybackStart(Jellyfin.Data.Entities.User user, BaseItem item) + private void OnPlaybackStart(User user, BaseItem item) { var data = _userDataManager.GetUserData(user, item); @@ -763,7 +764,7 @@ namespace Emby.Server.Implementations.Session StartIdleCheckTimer(); } - private void OnPlaybackProgress(Jellyfin.Data.Entities.User user, BaseItem item, PlaybackProgressInfo info) + private void OnPlaybackProgress(User user, BaseItem item, PlaybackProgressInfo info) { var data = _userDataManager.GetUserData(user, item); @@ -789,7 +790,7 @@ namespace Emby.Server.Implementations.Session } } - private static bool UpdatePlaybackSettings(Jellyfin.Data.Entities.User user, PlaybackProgressInfo info, UserItemData data) + private static bool UpdatePlaybackSettings(User user, PlaybackProgressInfo info, UserItemData data) { var changed = false; @@ -949,7 +950,7 @@ namespace Emby.Server.Implementations.Session _logger); } - private bool OnPlaybackStopped(Jellyfin.Data.Entities.User user, BaseItem item, long? positionTicks, bool playbackFailed) + private bool OnPlaybackStopped(User user, BaseItem item, long? positionTicks, bool playbackFailed) { bool playedToCompletion = false; @@ -1163,7 +1164,7 @@ namespace Emby.Server.Implementations.Session await SendMessageToSession(session, "Play", command, cancellationToken).ConfigureAwait(false); } - private IEnumerable TranslateItemForPlayback(Guid id, Jellyfin.Data.Entities.User user) + private IEnumerable TranslateItemForPlayback(Guid id, User user) { var item = _libraryManager.GetItemById(id); @@ -1216,7 +1217,7 @@ namespace Emby.Server.Implementations.Session return new[] { item }; } - private IEnumerable TranslateItemForInstantMix(Guid id, Jellyfin.Data.Entities.User user) + private IEnumerable TranslateItemForInstantMix(Guid id, User user) { var item = _libraryManager.GetItemById(id); @@ -1399,7 +1400,7 @@ namespace Emby.Server.Implementations.Session { CheckDisposed(); - Jellyfin.Data.Entities.User user = null; + User user = null; if (request.UserId != Guid.Empty) { user = _userManager.GetUserById(request.UserId); @@ -1455,7 +1456,7 @@ namespace Emby.Server.Implementations.Session return returnResult; } - private string GetAuthorizationToken(Jellyfin.Data.Entities.User user, string deviceId, string app, string appVersion, string deviceName) + private string GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName) { var existing = _authRepo.Get( new AuthenticationInfoQuery @@ -1701,7 +1702,7 @@ namespace Emby.Server.Implementations.Session return info; } - private string GetImageCacheTag(Jellyfin.Data.Entities.User user) + private string GetImageCacheTag(User user) { try { diff --git a/Jellyfin.Data/Entities/AccessSchedule.cs b/Jellyfin.Data/Entities/AccessSchedule.cs index 7966cdb50d..711e94dd18 100644 --- a/Jellyfin.Data/Entities/AccessSchedule.cs +++ b/Jellyfin.Data/Entities/AccessSchedule.cs @@ -1,5 +1,7 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; using Jellyfin.Data.Enums; namespace Jellyfin.Data.Entities @@ -20,8 +22,9 @@ namespace Jellyfin.Data.Entities /// The day of the week. /// The start hour. /// The end hour. - public AccessSchedule(DynamicDayOfWeek dayOfWeek, double startHour, double endHour) + public AccessSchedule(DynamicDayOfWeek dayOfWeek, double startHour, double endHour, Guid userId) { + UserId = userId; DayOfWeek = dayOfWeek; StartHour = startHour; EndHour = endHour; @@ -34,15 +37,20 @@ namespace Jellyfin.Data.Entities /// The start hour. /// The end hour. /// The newly created instance. - public static AccessSchedule CreateInstance(DynamicDayOfWeek dayOfWeek, double startHour, double endHour) + public static AccessSchedule CreateInstance(DynamicDayOfWeek dayOfWeek, double startHour, double endHour, Guid userId) { - return new AccessSchedule(dayOfWeek, startHour, endHour); + return new AccessSchedule(dayOfWeek, startHour, endHour, userId); } + [JsonIgnore] [Key] [Required] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public int Id { get; set; } + + [Required] + [ForeignKey("Id")] + public Guid UserId { get; set; } /// /// Gets or sets the day of week. diff --git a/Jellyfin.Data/Entities/Group.cs b/Jellyfin.Data/Entities/Group.cs index 54f9f49057..017fb22342 100644 --- a/Jellyfin.Data/Entities/Group.cs +++ b/Jellyfin.Data/Entities/Group.cs @@ -5,7 +5,7 @@ using System.ComponentModel.DataAnnotations.Schema; namespace Jellyfin.Data.Entities { - public partial class Group + public partial class Group : IHasPermissions, ISavingChanges { partial void Init(); @@ -14,35 +14,29 @@ namespace Jellyfin.Data.Entities /// protected Group() { - GroupPermissions = new HashSet(); + Permissions = new HashSet(); ProviderMappings = new HashSet(); Preferences = new HashSet(); Init(); } - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static Group CreateGroupUnsafe() - { - return new Group(); - } - /// /// Public constructor with required data /// /// - /// - public Group(string name, User _user0) + /// + public Group(string name, User user) { - if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); - this.Name = name; + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentNullException(nameof(name)); + } - if (_user0 == null) throw new ArgumentNullException(nameof(_user0)); - _user0.Groups.Add(this); + this.Name = name; + user.Groups.Add(this); - this.GroupPermissions = new HashSet(); + this.Permissions = new HashSet(); this.ProviderMappings = new HashSet(); this.Preferences = new HashSet(); @@ -54,9 +48,9 @@ namespace Jellyfin.Data.Entities /// /// /// - public static Group Create(string name, User _user0) + public static Group Create(string name, User user) { - return new Group(name, _user0); + return new Group(name, user); } /************************************************************************* @@ -68,8 +62,7 @@ namespace Jellyfin.Data.Entities /// [Key] [Required] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public Guid Id { get; protected set; } /// /// Required, Max length = 255 @@ -96,13 +89,13 @@ namespace Jellyfin.Data.Entities *************************************************************************/ [ForeignKey("Permission_GroupPermissions_Id")] - public virtual ICollection GroupPermissions { get; protected set; } + public ICollection Permissions { get; protected set; } [ForeignKey("ProviderMapping_ProviderMappings_Id")] - public virtual ICollection ProviderMappings { get; protected set; } + public ICollection ProviderMappings { get; protected set; } [ForeignKey("Preference_Preferences_Id")] - public virtual ICollection Preferences { get; protected set; } + public ICollection Preferences { get; protected set; } } } diff --git a/Jellyfin.Data/Entities/Permission.cs b/Jellyfin.Data/Entities/Permission.cs index 0b5b52cbd0..136e7abd35 100644 --- a/Jellyfin.Data/Entities/Permission.cs +++ b/Jellyfin.Data/Entities/Permission.cs @@ -3,10 +3,11 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Runtime.CompilerServices; +using Jellyfin.Data.Enums; namespace Jellyfin.Data.Entities { - public partial class Permission + public partial class Permission : ISavingChanges { partial void Init(); @@ -18,33 +19,16 @@ namespace Jellyfin.Data.Entities Init(); } - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static Permission CreatePermissionUnsafe() - { - return new Permission(); - } - /// /// Public constructor with required data /// /// /// - /// - /// - public Permission(Enums.PermissionKind kind, bool value, User _user0, Group _group1) + /// + public Permission(PermissionKind kind, bool value) { - this.Kind = kind; - - this.Value = value; - - if (_user0 == null) throw new ArgumentNullException(nameof(_user0)); - _user0.Permissions.Add(this); - - if (_group1 == null) throw new ArgumentNullException(nameof(_group1)); - _group1.GroupPermissions.Add(this); - + Kind = kind; + Value = value; Init(); } @@ -54,11 +38,10 @@ namespace Jellyfin.Data.Entities /// /// /// - /// - /// - public static Permission Create(Enums.PermissionKind kind, bool value, User _user0, Group _group1) + /// + public static Permission Create(PermissionKind kind, bool value) { - return new Permission(kind, value, _user0, _group1); + return new Permission(kind, value); } /************************************************************************* @@ -76,31 +59,32 @@ namespace Jellyfin.Data.Entities /// /// Backing field for Kind /// - protected Enums.PermissionKind _Kind; + protected PermissionKind _Kind; /// /// When provided in a partial class, allows value of Kind to be changed before setting. /// - partial void SetKind(Enums.PermissionKind oldValue, ref Enums.PermissionKind newValue); + partial void SetKind(PermissionKind oldValue, ref PermissionKind newValue); /// /// When provided in a partial class, allows value of Kind to be changed before returning. /// - partial void GetKind(ref Enums.PermissionKind result); + partial void GetKind(ref PermissionKind result); /// /// Required /// [Required] - public Enums.PermissionKind Kind + public PermissionKind Kind { get { - Enums.PermissionKind value = _Kind; + PermissionKind value = _Kind; GetKind(ref value); - return (_Kind = value); + return _Kind = value; } + set { - Enums.PermissionKind oldValue = _Kind; + PermissionKind oldValue = _Kind; SetKind(oldValue, ref value); if (oldValue != value) { @@ -117,7 +101,7 @@ namespace Jellyfin.Data.Entities public bool Value { get; set; } /// - /// Required, ConcurrenyToken + /// Required, ConcurrencyToken. /// [ConcurrencyCheck] [Required] @@ -138,7 +122,6 @@ namespace Jellyfin.Data.Entities { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } - } } diff --git a/Jellyfin.Data/Entities/Preference.cs b/Jellyfin.Data/Entities/Preference.cs index 505f52e6b0..56a07d4401 100644 --- a/Jellyfin.Data/Entities/Preference.cs +++ b/Jellyfin.Data/Entities/Preference.cs @@ -1,63 +1,33 @@ using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Jellyfin.Data.Enums; namespace Jellyfin.Data.Entities { - public partial class Preference + /// + /// An entity representing a preference attached to a user or group. + /// + public class Preference : ISavingChanges { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Preference() - { - Init(); - } - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// Initializes a new instance of the class. + /// Public constructor with required data. /// - public static Preference CreatePreferenceUnsafe() + /// The preference kind. + /// The value. + public Preference(PreferenceKind kind, string value) { - return new Preference(); + Kind = kind; + Value = value ?? throw new ArgumentNullException(nameof(value)); } /// - /// Public constructor with required data - /// - /// - /// - /// - /// - public Preference(Enums.PreferenceKind kind, string value, User _user0, Group _group1) - { - this.Kind = kind; - - if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value)); - this.Value = value; - - if (_user0 == null) throw new ArgumentNullException(nameof(_user0)); - _user0.Preferences.Add(this); - - if (_group1 == null) throw new ArgumentNullException(nameof(_group1)); - _group1.Preferences.Add(this); - - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) + /// Initializes a new instance of the class. + /// Default constructor. Protected due to required properties, but present because EF needs it. /// - /// - /// - /// - /// - public static Preference Create(Enums.PreferenceKind kind, string value, User _user0, Group _group1) + protected Preference() { - return new Preference(kind, value, _user0, _group1); } /************************************************************************* @@ -76,7 +46,7 @@ namespace Jellyfin.Data.Entities /// Required /// [Required] - public Enums.PreferenceKind Kind { get; set; } + public PreferenceKind Kind { get; set; } /// /// Required, Max length = 65535 @@ -87,21 +57,28 @@ namespace Jellyfin.Data.Entities public string Value { get; set; } /// - /// Required, ConcurrenyToken + /// Required, ConcurrencyToken. /// [ConcurrencyCheck] [Required] public uint RowVersion { get; set; } + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The preference kind. + /// The value. + /// The new instance. + public static Preference Create(PreferenceKind kind, string value) + { + return new Preference(kind, value); + } + + /// public void OnSavingChanges() { RowVersion++; } - - /************************************************************************* - * Navigation properties - *************************************************************************/ - } } diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index 17913959ee..7252ef2303 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -9,45 +9,23 @@ using Jellyfin.Data.Enums; namespace Jellyfin.Data.Entities { - public partial class User + /// + /// An entity representing a user. + /// + public partial class User : IHasPermissions, ISavingChanges { /// /// The values being delimited here are Guids, so commas work as they do not appear in Guids. /// private const char Delimiter = ','; - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected User() - { - Groups = new HashSet(); - Permissions = new HashSet(); - ProviderMappings = new HashSet(); - Preferences = new HashSet(); - AccessSchedules = new HashSet(); - - Init(); - } - /// - /// Public constructor with required data + /// Initializes a new instance of the class. + /// Public constructor with required data. /// - /// - /// - /// - /// - /// - /// - public User( - string username, - bool mustUpdatePassword, - string authenticationProviderId, - int invalidLoginAttemptCount, - SubtitlePlaybackMode subtitleMode, - bool playDefaultAudioTrack) + /// The username for the new user. + /// The authentication provider's Id + public User(string username, string authenticationProviderId) { if (string.IsNullOrEmpty(username)) { @@ -60,11 +38,7 @@ namespace Jellyfin.Data.Entities } Username = username; - MustUpdatePassword = mustUpdatePassword; AuthenticationProviderId = authenticationProviderId; - InvalidLoginAttemptCount = invalidLoginAttemptCount; - SubtitleMode = subtitleMode; - PlayDefaultAudioTrack = playDefaultAudioTrack; Groups = new HashSet(); Permissions = new HashSet(); @@ -74,6 +48,8 @@ namespace Jellyfin.Data.Entities // Set default values Id = Guid.NewGuid(); + InvalidLoginAttemptCount = 0; + MustUpdatePassword = false; DisplayMissingEpisodes = false; DisplayCollectionsView = false; HidePlayedInLatest = true; @@ -81,36 +57,40 @@ namespace Jellyfin.Data.Entities RememberSubtitleSelections = true; EnableNextEpisodeAutoPlay = true; EnableAutoLogin = false; + PlayDefaultAudioTrack = true; + SubtitleMode = SubtitlePlaybackMode.Default; + AddDefaultPermissions(); + AddDefaultPreferences(); Init(); } /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// Initializes a new instance of the class. + /// Default constructor. Protected due to required properties, but present because EF needs it. /// - public static User CreateUserUnsafe() + protected User() { - return new User(); + Groups = new HashSet(); + Permissions = new HashSet(); + ProviderMappings = new HashSet(); + Preferences = new HashSet(); + AccessSchedules = new HashSet(); + + AddDefaultPermissions(); + AddDefaultPreferences(); + Init(); } /// /// Static create function (for use in LINQ queries, etc.) /// - /// - /// - /// - /// - /// - /// - public static User Create( - string username, - bool mustUpdatePassword, - string authenticationProviderId, - int invalidLoginAttemptCount, - SubtitlePlaybackMode subtitleMode, - bool playDefaultAudioTrack) + /// The username for the created user. + /// The Id of the user's authentication provider. + /// The created instance. + public static User Create(string username, string authenticationProviderId) { - return new User(username, mustUpdatePassword, authenticationProviderId, invalidLoginAttemptCount, subtitleMode, playDefaultAudioTrack); + return new User(username, authenticationProviderId); } /************************************************************************* @@ -131,7 +111,6 @@ namespace Jellyfin.Data.Entities [Required] [MaxLength(255)] [StringLength(255)] - [JsonPropertyName("Name")] public string Username { get; set; } /// @@ -199,6 +178,7 @@ namespace Jellyfin.Data.Entities public bool PlayDefaultAudioTrack { get; set; } /// + /// Gets or sets the subtitle language preference. /// Max length = 255 /// [MaxLength(255)] @@ -237,6 +217,7 @@ namespace Jellyfin.Data.Entities public int? RemoteClientBitrateLimit { get; set; } /// + /// Gets or sets the internal id. /// This is a temporary stopgap for until the library db is migrated. /// This corresponds to the value of the index of this user in the library db. /// @@ -246,7 +227,8 @@ namespace Jellyfin.Data.Entities public ImageInfo ProfileImage { get; set; } /// - /// Required, ConcurrenyToken + /// Gets or sets the row version. + /// Required, ConcurrenyToken. /// [ConcurrencyCheck] [Required] @@ -260,23 +242,25 @@ namespace Jellyfin.Data.Entities /************************************************************************* * Navigation properties *************************************************************************/ - [ForeignKey("Group_Groups_Id")] + [ForeignKey("Group_Groups_Guid")] public ICollection Groups { get; protected set; } - [ForeignKey("Permission_Permissions_Id")] + [ForeignKey("Permission_Permissions_Guid")] public ICollection Permissions { get; protected set; } [ForeignKey("ProviderMapping_ProviderMappings_Id")] public ICollection ProviderMappings { get; protected set; } - [ForeignKey("Preference_Preferences_Id")] + [ForeignKey("Preference_Preferences_Guid")] public ICollection Preferences { get; protected set; } public ICollection AccessSchedules { get; protected set; } + partial void Init(); + public bool HasPermission(PermissionKind permission) { - return Permissions.Select(p => p.Kind).Contains(permission); + return Permissions.First(p => p.Kind == permission).Value; } public void SetPermission(PermissionKind kind, bool value) @@ -287,11 +271,12 @@ namespace Jellyfin.Data.Entities public string[] GetPreference(PreferenceKind preference) { - return Preferences + var val = Preferences .Where(p => p.Kind == preference) .Select(p => p.Value) - .First() - .Split(Delimiter); + .First(); + + return Equals(val, string.Empty) ? Array.Empty() : val.Split(Delimiter); } public void SetPreference(PreferenceKind preference, string[] values) @@ -332,5 +317,39 @@ namespace Jellyfin.Data.Entities return hour >= schedule.StartHour && hour <= schedule.EndHour; } + + // TODO: make these user configurable? + private void AddDefaultPermissions() + { + Permissions.Add(new Permission(PermissionKind.IsAdministrator, false)); + Permissions.Add(new Permission(PermissionKind.IsDisabled, false)); + Permissions.Add(new Permission(PermissionKind.IsHidden, false)); + Permissions.Add(new Permission(PermissionKind.EnableAllChannels, false)); + Permissions.Add(new Permission(PermissionKind.EnableAllDevices, true)); + Permissions.Add(new Permission(PermissionKind.EnableAllFolders, false)); + Permissions.Add(new Permission(PermissionKind.EnableContentDeletion, false)); + Permissions.Add(new Permission(PermissionKind.EnableContentDownloading, true)); + Permissions.Add(new Permission(PermissionKind.EnableMediaConversion, true)); + Permissions.Add(new Permission(PermissionKind.EnableMediaPlayback, true)); + Permissions.Add(new Permission(PermissionKind.EnablePlaybackRemuxing, true)); + Permissions.Add(new Permission(PermissionKind.EnablePublicSharing, true)); + Permissions.Add(new Permission(PermissionKind.EnableRemoteAccess, true)); + Permissions.Add(new Permission(PermissionKind.EnableSyncTranscoding, true)); + Permissions.Add(new Permission(PermissionKind.EnableAudioPlaybackTranscoding, true)); + Permissions.Add(new Permission(PermissionKind.EnableLiveTvAccess, true)); + Permissions.Add(new Permission(PermissionKind.EnableLiveTvManagement, true)); + Permissions.Add(new Permission(PermissionKind.EnableSharedDeviceControl, true)); + Permissions.Add(new Permission(PermissionKind.EnableVideoPlaybackTranscoding, true)); + Permissions.Add(new Permission(PermissionKind.ForceRemoteSourceTranscoding, false)); + Permissions.Add(new Permission(PermissionKind.EnableRemoteControlOfOtherUsers, false)); + } + + private void AddDefaultPreferences() + { + foreach (var val in Enum.GetValues(typeof(PreferenceKind)).Cast()) + { + Preferences.Add(new Preference(val, string.Empty)); + } + } } } diff --git a/Jellyfin.Data/IHasPermissions.cs b/Jellyfin.Data/IHasPermissions.cs new file mode 100644 index 0000000000..a77e51e1e2 --- /dev/null +++ b/Jellyfin.Data/IHasPermissions.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using Jellyfin.Data.Entities; + +namespace Jellyfin.Data +{ + public interface IHasPermissions + { + ICollection Permissions { get; } + } +} diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 9ac97a131b..8eb35ec87e 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -16,6 +16,13 @@ namespace Jellyfin.Server.Implementations public partial class JellyfinDb : DbContext { public virtual DbSet ActivityLogs { get; set; } + + public virtual DbSet Groups { get; set; } + + public virtual DbSet Permissions { get; set; } + + public virtual DbSet Preferences { get; set; } + public virtual DbSet Users { get; set; } /*public virtual DbSet Artwork { get; set; } public virtual DbSet Books { get; set; } @@ -30,7 +37,6 @@ namespace Jellyfin.Server.Implementations public virtual DbSet Episodes { get; set; } public virtual DbSet EpisodeMetadata { get; set; } public virtual DbSet Genres { get; set; } - public virtual DbSet Groups { get; set; } public virtual DbSet Libraries { get; set; } public virtual DbSet LibraryItems { get; set; } public virtual DbSet LibraryRoot { get; set; } @@ -43,12 +49,10 @@ namespace Jellyfin.Server.Implementations public virtual DbSet MovieMetadata { get; set; } public virtual DbSet MusicAlbums { get; set; } public virtual DbSet MusicAlbumMetadata { get; set; } - public virtual DbSet Permissions { get; set; } public virtual DbSet People { get; set; } public virtual DbSet PersonRoles { get; set; } public virtual DbSet Photo { get; set; } public virtual DbSet PhotoMetadata { get; set; } - public virtual DbSet Preferences { get; set; } public virtual DbSet ProviderMappings { get; set; } public virtual DbSet Ratings { get; set; } diff --git a/Jellyfin.Server.Implementations/JellyfinDbProvider.cs b/Jellyfin.Server.Implementations/JellyfinDbProvider.cs index eab531d386..8f5c199001 100644 --- a/Jellyfin.Server.Implementations/JellyfinDbProvider.cs +++ b/Jellyfin.Server.Implementations/JellyfinDbProvider.cs @@ -18,7 +18,7 @@ namespace Jellyfin.Server.Implementations public JellyfinDbProvider(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; - serviceProvider.GetService().Database.Migrate(); + serviceProvider.GetRequiredService().Database.Migrate(); } /// diff --git a/Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.Designer.cs deleted file mode 100644 index 8313c6a3b7..0000000000 --- a/Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.Designer.cs +++ /dev/null @@ -1,324 +0,0 @@ -#pragma warning disable CS1591 -#pragma warning disable SA1601 - -// -using System; -using Jellyfin.Server.Implementations; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -namespace Jellyfin.Server.Implementations.Migrations -{ - [DbContext(typeof(JellyfinDb))] - [Migration("20200504195702_UserSchema")] - partial class UserSchema - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("jellyfin") - .HasAnnotation("ProductVersion", "3.1.3"); - - modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("DateCreated") - .HasColumnType("TEXT"); - - b.Property("ItemId") - .HasColumnType("TEXT") - .HasMaxLength(256); - - b.Property("LogSeverity") - .HasColumnType("INTEGER"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(512); - - b.Property("Overview") - .HasColumnType("TEXT") - .HasMaxLength(512); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("ShortOverview") - .HasColumnType("TEXT") - .HasMaxLength(512); - - b.Property("Type") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(256); - - b.Property("UserId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("ActivityLog"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Group", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Group_Groups_Id") - .HasColumnType("INTEGER"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Group_Groups_Id"); - - b.ToTable("Group"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Kind") - .HasColumnType("INTEGER"); - - b.Property("Permission_GroupPermissions_Id") - .HasColumnType("INTEGER"); - - b.Property("Permission_Permissions_Id") - .HasColumnType("INTEGER"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("Value") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Permission_GroupPermissions_Id"); - - b.HasIndex("Permission_Permissions_Id"); - - b.ToTable("Permission"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Kind") - .HasColumnType("INTEGER"); - - b.Property("Preference_Preferences_Id") - .HasColumnType("INTEGER"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("Value") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.HasKey("Id"); - - b.HasIndex("Preference_Preferences_Id"); - - b.ToTable("Preference"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.ProviderMapping", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ProviderData") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("ProviderMapping_ProviderMappings_Id") - .HasColumnType("INTEGER"); - - b.Property("ProviderName") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.Property("ProviderSecrets") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("ProviderMapping_ProviderMappings_Id"); - - b.ToTable("ProviderMapping"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AudioLanguagePreference") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.Property("AuthenticationProviderId") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.Property("DisplayCollectionsView") - .HasColumnType("INTEGER"); - - b.Property("DisplayMissingEpisodes") - .HasColumnType("INTEGER"); - - b.Property("EnableNextEpisodeAutoPlay") - .HasColumnType("INTEGER"); - - b.Property("EnableUserPreferenceAccess") - .HasColumnType("INTEGER"); - - b.Property("GroupedFolders") - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("HidePlayedInLatest") - .HasColumnType("INTEGER"); - - b.Property("InvalidLoginAttemptCount") - .HasColumnType("INTEGER"); - - b.Property("LatestItemExcludes") - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("LoginAttemptsBeforeLockout") - .HasColumnType("INTEGER"); - - b.Property("MustUpdatePassword") - .HasColumnType("INTEGER"); - - b.Property("MyMediaExcludes") - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("OrderedViews") - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("Password") - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("PlayDefaultAudioTrack") - .HasColumnType("INTEGER"); - - b.Property("RememberAudioSelections") - .HasColumnType("INTEGER"); - - b.Property("RememberSubtitleSelections") - .HasColumnType("INTEGER"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("SubtitleLanguagePrefernce") - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.Property("SubtitleMode") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.Property("Username") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.HasKey("Id"); - - b.ToTable("User"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Group", b => - { - b.HasOne("Jellyfin.Data.Entities.User", null) - .WithMany("Groups") - .HasForeignKey("Group_Groups_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => - { - b.HasOne("Jellyfin.Data.Entities.Group", null) - .WithMany("GroupPermissions") - .HasForeignKey("Permission_GroupPermissions_Id"); - - b.HasOne("Jellyfin.Data.Entities.User", null) - .WithMany("Permissions") - .HasForeignKey("Permission_Permissions_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => - { - b.HasOne("Jellyfin.Data.Entities.Group", null) - .WithMany("Preferences") - .HasForeignKey("Preference_Preferences_Id"); - - b.HasOne("Jellyfin.Data.Entities.User", null) - .WithMany("Preferences") - .HasForeignKey("Preference_Preferences_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.ProviderMapping", b => - { - b.HasOne("Jellyfin.Data.Entities.Group", null) - .WithMany("ProviderMappings") - .HasForeignKey("ProviderMapping_ProviderMappings_Id"); - - b.HasOne("Jellyfin.Data.Entities.User", null) - .WithMany("ProviderMappings") - .HasForeignKey("ProviderMapping_ProviderMappings_Id"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.cs b/Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.cs deleted file mode 100644 index f24ccccbf6..0000000000 --- a/Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.cs +++ /dev/null @@ -1,219 +0,0 @@ -#pragma warning disable CS1591 -#pragma warning disable SA1601 - -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Jellyfin.Server.Implementations.Migrations -{ - public partial class UserSchema : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "User", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Username = table.Column(maxLength: 255, nullable: false), - Password = table.Column(maxLength: 65535, nullable: true), - MustUpdatePassword = table.Column(nullable: false), - AudioLanguagePreference = table.Column(maxLength: 255, nullable: false), - AuthenticationProviderId = table.Column(maxLength: 255, nullable: false), - GroupedFolders = table.Column(maxLength: 65535, nullable: true), - InvalidLoginAttemptCount = table.Column(nullable: false), - LatestItemExcludes = table.Column(maxLength: 65535, nullable: true), - LoginAttemptsBeforeLockout = table.Column(nullable: true), - MyMediaExcludes = table.Column(maxLength: 65535, nullable: true), - OrderedViews = table.Column(maxLength: 65535, nullable: true), - SubtitleMode = table.Column(maxLength: 255, nullable: false), - PlayDefaultAudioTrack = table.Column(nullable: false), - SubtitleLanguagePrefernce = table.Column(maxLength: 255, nullable: true), - DisplayMissingEpisodes = table.Column(nullable: true), - DisplayCollectionsView = table.Column(nullable: true), - HidePlayedInLatest = table.Column(nullable: true), - RememberAudioSelections = table.Column(nullable: true), - RememberSubtitleSelections = table.Column(nullable: true), - EnableNextEpisodeAutoPlay = table.Column(nullable: true), - EnableUserPreferenceAccess = table.Column(nullable: true), - RowVersion = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_User", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Group", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Name = table.Column(maxLength: 255, nullable: false), - RowVersion = table.Column(nullable: false), - Group_Groups_Id = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Group", x => x.Id); - table.ForeignKey( - name: "FK_Group_User_Group_Groups_Id", - column: x => x.Group_Groups_Id, - principalSchema: "jellyfin", - principalTable: "User", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "Permission", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Kind = table.Column(nullable: false), - Value = table.Column(nullable: false), - RowVersion = table.Column(nullable: false), - Permission_GroupPermissions_Id = table.Column(nullable: true), - Permission_Permissions_Id = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Permission", x => x.Id); - table.ForeignKey( - name: "FK_Permission_Group_Permission_GroupPermissions_Id", - column: x => x.Permission_GroupPermissions_Id, - principalSchema: "jellyfin", - principalTable: "Group", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Permission_User_Permission_Permissions_Id", - column: x => x.Permission_Permissions_Id, - principalSchema: "jellyfin", - principalTable: "User", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "Preference", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Kind = table.Column(nullable: false), - Value = table.Column(maxLength: 65535, nullable: false), - RowVersion = table.Column(nullable: false), - Preference_Preferences_Id = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Preference", x => x.Id); - table.ForeignKey( - name: "FK_Preference_Group_Preference_Preferences_Id", - column: x => x.Preference_Preferences_Id, - principalSchema: "jellyfin", - principalTable: "Group", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Preference_User_Preference_Preferences_Id", - column: x => x.Preference_Preferences_Id, - principalSchema: "jellyfin", - principalTable: "User", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "ProviderMapping", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - ProviderName = table.Column(maxLength: 255, nullable: false), - ProviderSecrets = table.Column(maxLength: 65535, nullable: false), - ProviderData = table.Column(maxLength: 65535, nullable: false), - RowVersion = table.Column(nullable: false), - ProviderMapping_ProviderMappings_Id = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_ProviderMapping", x => x.Id); - table.ForeignKey( - name: "FK_ProviderMapping_Group_ProviderMapping_ProviderMappings_Id", - column: x => x.ProviderMapping_ProviderMappings_Id, - principalSchema: "jellyfin", - principalTable: "Group", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_ProviderMapping_User_ProviderMapping_ProviderMappings_Id", - column: x => x.ProviderMapping_ProviderMappings_Id, - principalSchema: "jellyfin", - principalTable: "User", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_Group_Group_Groups_Id", - schema: "jellyfin", - table: "Group", - column: "Group_Groups_Id"); - - migrationBuilder.CreateIndex( - name: "IX_Permission_Permission_GroupPermissions_Id", - schema: "jellyfin", - table: "Permission", - column: "Permission_GroupPermissions_Id"); - - migrationBuilder.CreateIndex( - name: "IX_Permission_Permission_Permissions_Id", - schema: "jellyfin", - table: "Permission", - column: "Permission_Permissions_Id"); - - migrationBuilder.CreateIndex( - name: "IX_Preference_Preference_Preferences_Id", - schema: "jellyfin", - table: "Preference", - column: "Preference_Preferences_Id"); - - migrationBuilder.CreateIndex( - name: "IX_ProviderMapping_ProviderMapping_ProviderMappings_Id", - schema: "jellyfin", - table: "ProviderMapping", - column: "ProviderMapping_ProviderMappings_Id"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Permission", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "Preference", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "ProviderMapping", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "Group", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "User", - schema: "jellyfin"); - } - } -} diff --git a/Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.Designer.cs new file mode 100644 index 0000000000..36c58c8ca2 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.Designer.cs @@ -0,0 +1,404 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1601 + +// +using System; +using Jellyfin.Server.Implementations; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Jellyfin.Server.Implementations.Migrations +{ + [DbContext(typeof(JellyfinDb))] + [Migration("20200517002411_AddUsers")] + partial class AddUsers + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("jellyfin") + .HasAnnotation("ProductVersion", "3.1.3"); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("EndHour") + .HasColumnType("REAL"); + + b.Property("StartHour") + .HasColumnType("REAL"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AccessSchedule"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateCreated") + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property("LogSeverity") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(512); + + b.Property("Overview") + .HasColumnType("TEXT") + .HasMaxLength(512); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("ShortOverview") + .HasColumnType("TEXT") + .HasMaxLength(512); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ActivityLogs"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Group_Groups_Guid") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Group_Groups_Guid"); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ImageInfo"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Permission_GroupPermissions_Id") + .HasColumnType("TEXT"); + + b.Property("Permission_Permissions_Guid") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Permission_GroupPermissions_Id"); + + b.HasIndex("Permission_Permissions_Guid"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Preference_Preferences_Guid") + .HasColumnType("TEXT"); + + b.Property("Preference_Preferences_Id") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.HasKey("Id"); + + b.HasIndex("Preference_Preferences_Guid"); + + b.HasIndex("Preference_Preferences_Id"); + + b.ToTable("Preferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ProviderMapping", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ProviderData") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("ProviderMapping_ProviderMappings_Id") + .HasColumnType("TEXT"); + + b.Property("ProviderName") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("ProviderSecrets") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ProviderMapping_ProviderMappings_Id"); + + b.ToTable("ProviderMapping"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AudioLanguagePreference") + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("AuthenticationProviderId") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("DisplayCollectionsView") + .HasColumnType("INTEGER"); + + b.Property("DisplayMissingEpisodes") + .HasColumnType("INTEGER"); + + b.Property("EasyPassword") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("EnableAutoLogin") + .HasColumnType("INTEGER"); + + b.Property("EnableLocalPassword") + .HasColumnType("INTEGER"); + + b.Property("EnableNextEpisodeAutoPlay") + .HasColumnType("INTEGER"); + + b.Property("EnableUserPreferenceAccess") + .HasColumnType("INTEGER"); + + b.Property("HidePlayedInLatest") + .HasColumnType("INTEGER"); + + b.Property("InternalId") + .HasColumnType("INTEGER"); + + b.Property("InvalidLoginAttemptCount") + .HasColumnType("INTEGER"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.Property("LastLoginDate") + .HasColumnType("TEXT"); + + b.Property("LoginAttemptsBeforeLockout") + .HasColumnType("INTEGER"); + + b.Property("MaxParentalAgeRating") + .HasColumnType("INTEGER"); + + b.Property("MustUpdatePassword") + .HasColumnType("INTEGER"); + + b.Property("Password") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("PasswordResetProviderId") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("PlayDefaultAudioTrack") + .HasColumnType("INTEGER"); + + b.Property("ProfileImageId") + .HasColumnType("INTEGER"); + + b.Property("RememberAudioSelections") + .HasColumnType("INTEGER"); + + b.Property("RememberSubtitleSelections") + .HasColumnType("INTEGER"); + + b.Property("RemoteClientBitrateLimit") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SubtitleLanguagePreference") + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("SubtitleMode") + .HasColumnType("INTEGER"); + + b.Property("Username") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.HasKey("Id"); + + b.HasIndex("ProfileImageId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("AccessSchedules") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Group", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Groups") + .HasForeignKey("Group_Groups_Guid"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.HasOne("Jellyfin.Data.Entities.Group", null) + .WithMany("Permissions") + .HasForeignKey("Permission_GroupPermissions_Id"); + + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Permissions") + .HasForeignKey("Permission_Permissions_Guid"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Preferences") + .HasForeignKey("Preference_Preferences_Guid"); + + b.HasOne("Jellyfin.Data.Entities.Group", null) + .WithMany("Preferences") + .HasForeignKey("Preference_Preferences_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ProviderMapping", b => + { + b.HasOne("Jellyfin.Data.Entities.Group", null) + .WithMany("ProviderMappings") + .HasForeignKey("ProviderMapping_ProviderMappings_Id"); + + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("ProviderMappings") + .HasForeignKey("ProviderMapping_ProviderMappings_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.User", b => + { + b.HasOne("Jellyfin.Data.Entities.ImageInfo", "ProfileImage") + .WithMany() + .HasForeignKey("ProfileImageId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.cs b/Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.cs new file mode 100644 index 0000000000..55c6f371c0 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.cs @@ -0,0 +1,297 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1601 + +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Jellyfin.Server.Implementations.Migrations +{ + public partial class AddUsers : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ImageInfo", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Path = table.Column(nullable: false), + LastModified = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ImageInfo", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Users", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false), + Username = table.Column(maxLength: 255, nullable: false), + Password = table.Column(maxLength: 65535, nullable: true), + EasyPassword = table.Column(maxLength: 65535, nullable: true), + MustUpdatePassword = table.Column(nullable: false), + AudioLanguagePreference = table.Column(maxLength: 255, nullable: true), + AuthenticationProviderId = table.Column(maxLength: 255, nullable: false), + PasswordResetProviderId = table.Column(maxLength: 255, nullable: false), + InvalidLoginAttemptCount = table.Column(nullable: false), + LastActivityDate = table.Column(nullable: false), + LastLoginDate = table.Column(nullable: false), + LoginAttemptsBeforeLockout = table.Column(nullable: true), + SubtitleMode = table.Column(nullable: false), + PlayDefaultAudioTrack = table.Column(nullable: false), + SubtitleLanguagePreference = table.Column(maxLength: 255, nullable: true), + DisplayMissingEpisodes = table.Column(nullable: false), + DisplayCollectionsView = table.Column(nullable: false), + EnableLocalPassword = table.Column(nullable: false), + HidePlayedInLatest = table.Column(nullable: false), + RememberAudioSelections = table.Column(nullable: false), + RememberSubtitleSelections = table.Column(nullable: false), + EnableNextEpisodeAutoPlay = table.Column(nullable: false), + EnableAutoLogin = table.Column(nullable: false), + EnableUserPreferenceAccess = table.Column(nullable: false), + MaxParentalAgeRating = table.Column(nullable: true), + RemoteClientBitrateLimit = table.Column(nullable: true), + InternalId = table.Column(nullable: false), + ProfileImageId = table.Column(nullable: true), + RowVersion = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + table.ForeignKey( + name: "FK_Users_ImageInfo_ProfileImageId", + column: x => x.ProfileImageId, + principalSchema: "jellyfin", + principalTable: "ImageInfo", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "AccessSchedule", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(nullable: false), + DayOfWeek = table.Column(nullable: false), + StartHour = table.Column(nullable: false), + EndHour = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AccessSchedule", x => x.Id); + table.ForeignKey( + name: "FK_AccessSchedule_Users_UserId", + column: x => x.UserId, + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Groups", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false), + Name = table.Column(maxLength: 255, nullable: false), + RowVersion = table.Column(nullable: false), + Group_Groups_Guid = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Groups", x => x.Id); + table.ForeignKey( + name: "FK_Groups_Users_Group_Groups_Guid", + column: x => x.Group_Groups_Guid, + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Permissions", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Kind = table.Column(nullable: false), + Value = table.Column(nullable: false), + RowVersion = table.Column(nullable: false), + Permission_GroupPermissions_Id = table.Column(nullable: true), + Permission_Permissions_Guid = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Permissions", x => x.Id); + table.ForeignKey( + name: "FK_Permissions_Groups_Permission_GroupPermissions_Id", + column: x => x.Permission_GroupPermissions_Id, + principalSchema: "jellyfin", + principalTable: "Groups", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Permissions_Users_Permission_Permissions_Guid", + column: x => x.Permission_Permissions_Guid, + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Preferences", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Kind = table.Column(nullable: false), + Value = table.Column(maxLength: 65535, nullable: false), + RowVersion = table.Column(nullable: false), + Preference_Preferences_Guid = table.Column(nullable: true), + Preference_Preferences_Id = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Preferences", x => x.Id); + table.ForeignKey( + name: "FK_Preferences_Users_Preference_Preferences_Guid", + column: x => x.Preference_Preferences_Guid, + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Preferences_Groups_Preference_Preferences_Id", + column: x => x.Preference_Preferences_Id, + principalSchema: "jellyfin", + principalTable: "Groups", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "ProviderMapping", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ProviderName = table.Column(maxLength: 255, nullable: false), + ProviderSecrets = table.Column(maxLength: 65535, nullable: false), + ProviderData = table.Column(maxLength: 65535, nullable: false), + RowVersion = table.Column(nullable: false), + ProviderMapping_ProviderMappings_Id = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ProviderMapping", x => x.Id); + table.ForeignKey( + name: "FK_ProviderMapping_Groups_ProviderMapping_ProviderMappings_Id", + column: x => x.ProviderMapping_ProviderMappings_Id, + principalSchema: "jellyfin", + principalTable: "Groups", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_ProviderMapping_Users_ProviderMapping_ProviderMappings_Id", + column: x => x.ProviderMapping_ProviderMappings_Id, + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_AccessSchedule_UserId", + schema: "jellyfin", + table: "AccessSchedule", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Groups_Group_Groups_Guid", + schema: "jellyfin", + table: "Groups", + column: "Group_Groups_Guid"); + + migrationBuilder.CreateIndex( + name: "IX_Permissions_Permission_GroupPermissions_Id", + schema: "jellyfin", + table: "Permissions", + column: "Permission_GroupPermissions_Id"); + + migrationBuilder.CreateIndex( + name: "IX_Permissions_Permission_Permissions_Guid", + schema: "jellyfin", + table: "Permissions", + column: "Permission_Permissions_Guid"); + + migrationBuilder.CreateIndex( + name: "IX_Preferences_Preference_Preferences_Guid", + schema: "jellyfin", + table: "Preferences", + column: "Preference_Preferences_Guid"); + + migrationBuilder.CreateIndex( + name: "IX_Preferences_Preference_Preferences_Id", + schema: "jellyfin", + table: "Preferences", + column: "Preference_Preferences_Id"); + + migrationBuilder.CreateIndex( + name: "IX_ProviderMapping_ProviderMapping_ProviderMappings_Id", + schema: "jellyfin", + table: "ProviderMapping", + column: "ProviderMapping_ProviderMappings_Id"); + + migrationBuilder.CreateIndex( + name: "IX_Users_ProfileImageId", + schema: "jellyfin", + table: "Users", + column: "ProfileImageId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AccessSchedule", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Permissions", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Preferences", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "ProviderMapping", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Groups", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Users", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "ImageInfo", + schema: "jellyfin"); + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs index 0fb0ba8033..46714e8659 100644 --- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs +++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs @@ -1,7 +1,9 @@ // using System; +using Jellyfin.Server.Implementations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Jellyfin.Server.Implementations.Migrations { @@ -15,6 +17,31 @@ namespace Jellyfin.Server.Implementations.Migrations .HasDefaultSchema("jellyfin") .HasAnnotation("ProductVersion", "3.1.3"); + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("EndHour") + .HasColumnType("REAL"); + + b.Property("StartHour") + .HasColumnType("REAL"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AccessSchedule"); + }); + modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b => { b.Property("Id") @@ -63,12 +90,12 @@ namespace Jellyfin.Server.Implementations.Migrations modelBuilder.Entity("Jellyfin.Data.Entities.Group", b => { - b.Property("Id") + b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); + .HasColumnType("TEXT"); - b.Property("Group_Groups_Id") - .HasColumnType("INTEGER"); + b.Property("Group_Groups_Guid") + .HasColumnType("TEXT"); b.Property("Name") .IsRequired() @@ -81,9 +108,27 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasKey("Id"); - b.HasIndex("Group_Groups_Id"); + b.HasIndex("Group_Groups_Guid"); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); - b.ToTable("Group"); + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ImageInfo"); }); modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => @@ -95,11 +140,11 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("Kind") .HasColumnType("INTEGER"); - b.Property("Permission_GroupPermissions_Id") - .HasColumnType("INTEGER"); + b.Property("Permission_GroupPermissions_Id") + .HasColumnType("TEXT"); - b.Property("Permission_Permissions_Id") - .HasColumnType("INTEGER"); + b.Property("Permission_Permissions_Guid") + .HasColumnType("TEXT"); b.Property("RowVersion") .IsConcurrencyToken() @@ -112,9 +157,9 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasIndex("Permission_GroupPermissions_Id"); - b.HasIndex("Permission_Permissions_Id"); + b.HasIndex("Permission_Permissions_Guid"); - b.ToTable("Permission"); + b.ToTable("Permissions"); }); modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => @@ -126,8 +171,11 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("Kind") .HasColumnType("INTEGER"); - b.Property("Preference_Preferences_Id") - .HasColumnType("INTEGER"); + b.Property("Preference_Preferences_Guid") + .HasColumnType("TEXT"); + + b.Property("Preference_Preferences_Id") + .HasColumnType("TEXT"); b.Property("RowVersion") .IsConcurrencyToken() @@ -140,9 +188,11 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasKey("Id"); + b.HasIndex("Preference_Preferences_Guid"); + b.HasIndex("Preference_Preferences_Id"); - b.ToTable("Preference"); + b.ToTable("Preferences"); }); modelBuilder.Entity("Jellyfin.Data.Entities.ProviderMapping", b => @@ -156,8 +206,8 @@ namespace Jellyfin.Server.Implementations.Migrations .HasColumnType("TEXT") .HasMaxLength(65535); - b.Property("ProviderMapping_ProviderMappings_Id") - .HasColumnType("INTEGER"); + b.Property("ProviderMapping_ProviderMappings_Id") + .HasColumnType("TEXT"); b.Property("ProviderName") .IsRequired() @@ -182,12 +232,11 @@ namespace Jellyfin.Server.Implementations.Migrations modelBuilder.Entity("Jellyfin.Data.Entities.User", b => { - b.Property("Id") + b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); + .HasColumnType("TEXT"); b.Property("AudioLanguagePreference") - .IsRequired() .HasColumnType("TEXT") .HasMaxLength(255); @@ -196,71 +245,86 @@ namespace Jellyfin.Server.Implementations.Migrations .HasColumnType("TEXT") .HasMaxLength(255); - b.Property("DisplayCollectionsView") + b.Property("DisplayCollectionsView") .HasColumnType("INTEGER"); - b.Property("DisplayMissingEpisodes") + b.Property("DisplayMissingEpisodes") .HasColumnType("INTEGER"); - b.Property("EnableNextEpisodeAutoPlay") + b.Property("EasyPassword") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("EnableAutoLogin") .HasColumnType("INTEGER"); - b.Property("EnableUserPreferenceAccess") + b.Property("EnableLocalPassword") .HasColumnType("INTEGER"); - b.Property("GroupedFolders") - .HasColumnType("TEXT") - .HasMaxLength(65535); + b.Property("EnableNextEpisodeAutoPlay") + .HasColumnType("INTEGER"); - b.Property("HidePlayedInLatest") + b.Property("EnableUserPreferenceAccess") + .HasColumnType("INTEGER"); + + b.Property("HidePlayedInLatest") + .HasColumnType("INTEGER"); + + b.Property("InternalId") .HasColumnType("INTEGER"); b.Property("InvalidLoginAttemptCount") .HasColumnType("INTEGER"); - b.Property("LatestItemExcludes") - .HasColumnType("TEXT") - .HasMaxLength(65535); + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.Property("LastLoginDate") + .HasColumnType("TEXT"); b.Property("LoginAttemptsBeforeLockout") .HasColumnType("INTEGER"); - b.Property("MustUpdatePassword") + b.Property("MaxParentalAgeRating") .HasColumnType("INTEGER"); - b.Property("MyMediaExcludes") - .HasColumnType("TEXT") - .HasMaxLength(65535); + b.Property("MustUpdatePassword") + .HasColumnType("INTEGER"); - b.Property("OrderedViews") + b.Property("Password") .HasColumnType("TEXT") .HasMaxLength(65535); - b.Property("Password") + b.Property("PasswordResetProviderId") + .IsRequired() .HasColumnType("TEXT") - .HasMaxLength(65535); + .HasMaxLength(255); b.Property("PlayDefaultAudioTrack") .HasColumnType("INTEGER"); - b.Property("RememberAudioSelections") + b.Property("ProfileImageId") + .HasColumnType("INTEGER"); + + b.Property("RememberAudioSelections") .HasColumnType("INTEGER"); - b.Property("RememberSubtitleSelections") + b.Property("RememberSubtitleSelections") + .HasColumnType("INTEGER"); + + b.Property("RemoteClientBitrateLimit") .HasColumnType("INTEGER"); b.Property("RowVersion") .IsConcurrencyToken() .HasColumnType("INTEGER"); - b.Property("SubtitleLanguagePrefernce") + b.Property("SubtitleLanguagePreference") .HasColumnType("TEXT") .HasMaxLength(255); - b.Property("SubtitleMode") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(255); + b.Property("SubtitleMode") + .HasColumnType("INTEGER"); b.Property("Username") .IsRequired() @@ -269,34 +333,45 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasKey("Id"); - b.ToTable("User"); + b.HasIndex("ProfileImageId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("AccessSchedules") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("Jellyfin.Data.Entities.Group", b => { b.HasOne("Jellyfin.Data.Entities.User", null) .WithMany("Groups") - .HasForeignKey("Group_Groups_Id"); + .HasForeignKey("Group_Groups_Guid"); }); modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => { b.HasOne("Jellyfin.Data.Entities.Group", null) - .WithMany("GroupPermissions") + .WithMany("Permissions") .HasForeignKey("Permission_GroupPermissions_Id"); b.HasOne("Jellyfin.Data.Entities.User", null) .WithMany("Permissions") - .HasForeignKey("Permission_Permissions_Id"); + .HasForeignKey("Permission_Permissions_Guid"); }); modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => { - b.HasOne("Jellyfin.Data.Entities.Group", null) + b.HasOne("Jellyfin.Data.Entities.User", null) .WithMany("Preferences") - .HasForeignKey("Preference_Preferences_Id"); + .HasForeignKey("Preference_Preferences_Guid"); - b.HasOne("Jellyfin.Data.Entities.User", null) + b.HasOne("Jellyfin.Data.Entities.Group", null) .WithMany("Preferences") .HasForeignKey("Preference_Preferences_Id"); }); @@ -311,6 +386,13 @@ namespace Jellyfin.Server.Implementations.Migrations .WithMany("ProviderMappings") .HasForeignKey("ProviderMapping_ProviderMappings_Id"); }); + + modelBuilder.Entity("Jellyfin.Data.Entities.User", b => + { + b.HasOne("Jellyfin.Data.Entities.ImageInfo", "ProfileImage") + .WithMany() + .HasForeignKey("ProfileImageId"); + }); #pragma warning restore 612, 618 } } diff --git a/Jellyfin.Server.Implementations/User/DefaultAuthenticationProvider.cs b/Jellyfin.Server.Implementations/User/DefaultAuthenticationProvider.cs deleted file mode 100644 index 024500bf81..0000000000 --- a/Jellyfin.Server.Implementations/User/DefaultAuthenticationProvider.cs +++ /dev/null @@ -1,185 +0,0 @@ -using System; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Common; -using MediaBrowser.Common.Cryptography; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Model.Cryptography; - -namespace Jellyfin.Server.Implementations.User -{ - /// - /// The default authentication provider. - /// - public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser - { - private readonly ICryptoProvider _cryptographyProvider; - - /// - /// Initializes a new instance of the class. - /// - /// The cryptography provider. - public DefaultAuthenticationProvider(ICryptoProvider cryptographyProvider) - { - _cryptographyProvider = cryptographyProvider; - } - - /// - public string Name => "Default"; - - /// - public bool IsEnabled => true; - - /// - // This is dumb and an artifact of the backwards way auth providers were designed. - // This version of authenticate was never meant to be called, but needs to be here for interface compat - // Only the providers that don't provide local user support use this - public Task Authenticate(string username, string password) - { - throw new NotImplementedException(); - } - - /// - // This is the version that we need to use for local users. Because reasons. - public Task Authenticate(string username, string password, Data.Entities.User resolvedUser) - { - if (resolvedUser == null) - { - throw new AuthenticationException("Specified user does not exist."); - } - - bool success = false; - - // As long as jellyfin supports passwordless users, we need this little block here to accommodate - if (!HasPassword(resolvedUser)) - { - return Task.FromResult(new ProviderAuthenticationResult - { - Username = username - }); - } - - byte[] passwordBytes = Encoding.UTF8.GetBytes(password); - - PasswordHash readyHash = PasswordHash.Parse(resolvedUser.Password); - if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id) - || _cryptographyProvider.DefaultHashMethod == readyHash.Id) - { - byte[] calculatedHash = _cryptographyProvider.ComputeHash( - readyHash.Id, - passwordBytes, - readyHash.Salt.ToArray()); - - if (readyHash.Hash.SequenceEqual(calculatedHash)) - { - success = true; - } - } - else - { - throw new AuthenticationException($"Requested crypto method not available in provider: {readyHash.Id}"); - } - - if (!success) - { - throw new AuthenticationException("Invalid username or password"); - } - - return Task.FromResult(new ProviderAuthenticationResult - { - Username = username - }); - } - - /// - public bool HasPassword(Data.Entities.User user) - => !string.IsNullOrEmpty(user.Password); - - /// - public Task ChangePassword(Data.Entities.User user, string newPassword) - { - if (string.IsNullOrEmpty(newPassword)) - { - user.Password = null; - return Task.CompletedTask; - } - - PasswordHash newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword); - user.Password = newPasswordHash.ToString(); - - return Task.CompletedTask; - } - - /// - public void ChangeEasyPassword(Data.Entities.User user, string newPassword, string newPasswordHash) - { - if (newPassword != null) - { - newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword).ToString(); - } - - if (string.IsNullOrWhiteSpace(newPasswordHash)) - { - throw new ArgumentNullException(nameof(newPasswordHash)); - } - - user.EasyPassword = newPasswordHash; - } - - /// - public string GetEasyPasswordHash(Data.Entities.User user) - { - return string.IsNullOrEmpty(user.EasyPassword) - ? null - : Hex.Encode(PasswordHash.Parse(user.EasyPassword).Hash); - } - - /// - /// Hashes the provided string. - /// - /// The user. - /// The string to hash. - /// The hashed string. - public string GetHashedString(Data.Entities.User user, string str) - { - if (string.IsNullOrEmpty(user.Password)) - { - return _cryptographyProvider.CreatePasswordHash(str).ToString(); - } - - // TODO: make use of iterations parameter? - PasswordHash passwordHash = PasswordHash.Parse(user.Password); - var salt = passwordHash.Salt.ToArray(); - return new PasswordHash( - passwordHash.Id, - _cryptographyProvider.ComputeHash( - passwordHash.Id, - Encoding.UTF8.GetBytes(str), - salt), - salt, - passwordHash.Parameters.ToDictionary(x => x.Key, y => y.Value)).ToString(); - } - - /// - /// Hashes the provided string. - /// - /// The user. - /// The string to hash. - /// The hashed string. - public ReadOnlySpan GetHashed(Data.Entities.User user, string str) - { - if (string.IsNullOrEmpty(user.Password)) - { - return _cryptographyProvider.CreatePasswordHash(str).Hash; - } - - // TODO: make use of iterations parameter? - PasswordHash passwordHash = PasswordHash.Parse(user.Password); - return _cryptographyProvider.ComputeHash( - passwordHash.Id, - Encoding.UTF8.GetBytes(str), - passwordHash.Salt.ToArray()); - } - } -} diff --git a/Jellyfin.Server.Implementations/User/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/User/DefaultPasswordResetProvider.cs deleted file mode 100644 index 80ab3ce000..0000000000 --- a/Jellyfin.Server.Implementations/User/DefaultPasswordResetProvider.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Security.Cryptography; -using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Users; - -namespace Jellyfin.Server.Implementations.User -{ - /// - /// The default password reset provider. - /// - public class DefaultPasswordResetProvider : IPasswordResetProvider - { - private const string BaseResetFileName = "passwordreset"; - - private readonly IJsonSerializer _jsonSerializer; - private readonly IUserManager _userManager; - - private readonly string _passwordResetFileBase; - private readonly string _passwordResetFileBaseDir; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration manager. - /// The JSON serializer. - /// The user manager. - public DefaultPasswordResetProvider( - IServerConfigurationManager configurationManager, - IJsonSerializer jsonSerializer, - IUserManager userManager) - { - _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath; - _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, BaseResetFileName); - _jsonSerializer = jsonSerializer; - _userManager = userManager; - } - - /// - public string Name => "Default Password Reset Provider"; - - /// - public bool IsEnabled => true; - - /// - public async Task RedeemPasswordResetPin(string pin) - { - SerializablePasswordReset spr; - List usersReset = new List(); - foreach (var resetFile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*")) - { - await using (var str = File.OpenRead(resetFile)) - { - spr = await _jsonSerializer.DeserializeFromStreamAsync(str).ConfigureAwait(false); - } - - if (spr.ExpirationDate < DateTime.Now) - { - File.Delete(resetFile); - } - else if (string.Equals( - spr.Pin.Replace("-", string.Empty, StringComparison.Ordinal), - pin.Replace("-", string.Empty, StringComparison.Ordinal), - StringComparison.InvariantCultureIgnoreCase)) - { - var resetUser = _userManager.GetUserByName(spr.UserName); - if (resetUser == null) - { - throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found"); - } - - await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); - usersReset.Add(resetUser.Username); - File.Delete(resetFile); - } - } - - if (usersReset.Count < 1) - { - throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}"); - } - - return new PinRedeemResult - { - Success = true, - UsersReset = usersReset.ToArray() - }; - } - - /// - public async Task StartForgotPasswordProcess(Jellyfin.Data.Entities.User user, bool isInNetwork) - { - string pin; - using (var cryptoRandom = RandomNumberGenerator.Create()) - { - byte[] bytes = new byte[4]; - cryptoRandom.GetBytes(bytes); - pin = BitConverter.ToString(bytes); - } - - DateTime expireTime = DateTime.Now.AddMinutes(30); - - user.EasyPassword = pin; - await _userManager.UpdateUserAsync(user).ConfigureAwait(false); - - return new ForgotPasswordResult - { - Action = ForgotPasswordAction.PinCode, - PinExpirationDate = expireTime, - }; - } - - private class SerializablePasswordReset : PasswordPinCreationResult - { - public string Pin { get; set; } - - public string UserName { get; set; } - } - } -} diff --git a/Jellyfin.Server.Implementations/User/DeviceAccessEntryPoint.cs b/Jellyfin.Server.Implementations/User/DeviceAccessEntryPoint.cs deleted file mode 100644 index d33034ab2d..0000000000 --- a/Jellyfin.Server.Implementations/User/DeviceAccessEntryPoint.cs +++ /dev/null @@ -1,65 +0,0 @@ -#pragma warning disable CS1591 - -using System.Threading.Tasks; -using Jellyfin.Data.Enums; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Plugins; -using MediaBrowser.Controller.Security; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Events; - -namespace Jellyfin.Server.Implementations.User -{ - public sealed class DeviceAccessEntryPoint : IServerEntryPoint - { - private readonly IUserManager _userManager; - private readonly IAuthenticationRepository _authRepo; - private readonly IDeviceManager _deviceManager; - private readonly ISessionManager _sessionManager; - - public DeviceAccessEntryPoint(IUserManager userManager, IAuthenticationRepository authRepo, IDeviceManager deviceManager, ISessionManager sessionManager) - { - _userManager = userManager; - _authRepo = authRepo; - _deviceManager = deviceManager; - _sessionManager = sessionManager; - } - - public Task RunAsync() - { - _userManager.OnUserUpdated += OnUserUpdated; - - return Task.CompletedTask; - } - - private void OnUserUpdated(object sender, GenericEventArgs e) - { - var user = e.Argument; - if (!user.HasPermission(PermissionKind.EnableAllDevices)) - { - UpdateDeviceAccess(user); - } - } - - public void Dispose() - { - } - - private void UpdateDeviceAccess(Data.Entities.User user) - { - var existing = _authRepo.Get(new AuthenticationInfoQuery - { - UserId = user.Id - }).Items; - - foreach (var authInfo in existing) - { - if (!string.IsNullOrEmpty(authInfo.DeviceId) && !_deviceManager.CanAccessDevice(user, authInfo.DeviceId)) - { - _sessionManager.Logout(authInfo); - } - } - } - } -} diff --git a/Jellyfin.Server.Implementations/User/InvalidAuthProvider.cs b/Jellyfin.Server.Implementations/User/InvalidAuthProvider.cs deleted file mode 100644 index a11ca128ac..0000000000 --- a/Jellyfin.Server.Implementations/User/InvalidAuthProvider.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Threading.Tasks; -using MediaBrowser.Controller.Authentication; - -namespace Jellyfin.Server.Implementations.User -{ - /// - /// An invalid authentication provider. - /// - public class InvalidAuthProvider : IAuthenticationProvider - { - /// - public string Name => "InvalidOrMissingAuthenticationProvider"; - - /// - public bool IsEnabled => true; - - /// - public Task Authenticate(string username, string password) - { - throw new AuthenticationException("User Account cannot login with this provider. The Normal provider for this user cannot be found"); - } - - /// - public bool HasPassword(Data.Entities.User user) - { - return true; - } - - /// - public Task ChangePassword(Data.Entities.User user, string newPassword) - { - return Task.CompletedTask; - } - - /// - public void ChangeEasyPassword(Data.Entities.User user, string newPassword, string newPasswordHash) - { - // Nothing here - } - - /// - public string GetEasyPasswordHash(Data.Entities.User user) - { - return string.Empty; - } - } -} diff --git a/Jellyfin.Server.Implementations/User/UserManager.cs b/Jellyfin.Server.Implementations/User/UserManager.cs deleted file mode 100644 index 73905ff70b..0000000000 --- a/Jellyfin.Server.Implementations/User/UserManager.cs +++ /dev/null @@ -1,771 +0,0 @@ -#pragma warning disable CS0067 -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Jellyfin.Data.Enums; -using MediaBrowser.Common.Cryptography; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.Users; -using Microsoft.Extensions.Logging; - -namespace Jellyfin.Server.Implementations.User -{ - public class UserManager : IUserManager - { - private readonly JellyfinDbProvider _dbProvider; - private readonly ICryptoProvider _cryptoProvider; - private readonly INetworkManager _networkManager; - private readonly ILogger _logger; - - private IAuthenticationProvider[] _authenticationProviders; - private DefaultAuthenticationProvider _defaultAuthenticationProvider; - private InvalidAuthProvider _invalidAuthProvider; - private IPasswordResetProvider[] _passwordResetProviders; - private DefaultPasswordResetProvider _defaultPasswordResetProvider; - - public UserManager( - JellyfinDbProvider dbProvider, - ICryptoProvider cryptoProvider, - INetworkManager networkManager, - ILogger logger) - { - _dbProvider = dbProvider; - _cryptoProvider = cryptoProvider; - _networkManager = networkManager; - _logger = logger; - } - - public event EventHandler> OnUserPasswordChanged; - - /// - public event EventHandler> OnUserUpdated; - - /// - public event EventHandler> OnUserCreated; - - /// - public event EventHandler> OnUserDeleted; - - public event EventHandler> OnUserLockedOut; - - public IEnumerable Users - { - get - { - using var dbContext = _dbProvider.CreateContext(); - return dbContext.Users; - } - } - - public IEnumerable UsersIds - { - get - { - using var dbContext = _dbProvider.CreateContext(); - return dbContext.Users.Select(u => u.Id); - } - } - - public Data.Entities.User GetUserById(Guid id) - { - if (id == Guid.Empty) - { - throw new ArgumentException("Guid can't be empty", nameof(id)); - } - - using var dbContext = _dbProvider.CreateContext(); - - return dbContext.Users.Find(id); - } - - public Data.Entities.User GetUserByName(string name) - { - if (string.IsNullOrWhiteSpace(name)) - { - throw new ArgumentException("Invalid username", nameof(name)); - } - - using var dbContext = _dbProvider.CreateContext(); - - return dbContext.Users.FirstOrDefault(u => - string.Equals(u.Username, name, StringComparison.OrdinalIgnoreCase)); - } - - public async Task RenameUser(Data.Entities.User user, string newName) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (string.IsNullOrWhiteSpace(newName)) - { - throw new ArgumentException("Invalid username", nameof(newName)); - } - - if (user.Username.Equals(newName, StringComparison.Ordinal)) - { - throw new ArgumentException("The new and old names must be different."); - } - - if (Users.Any( - u => u.Id != user.Id && u.Username.Equals(newName, StringComparison.OrdinalIgnoreCase))) - { - throw new ArgumentException(string.Format( - CultureInfo.InvariantCulture, - "A user with the name '{0}' already exists.", - newName)); - } - - user.Username = newName; - await UpdateUserAsync(user).ConfigureAwait(false); - - OnUserUpdated?.Invoke(this, new GenericEventArgs(user)); - } - - public void UpdateUser(Data.Entities.User user) - { - using var dbContext = _dbProvider.CreateContext(); - dbContext.Users.Update(user); - dbContext.SaveChanges(); - } - - public async Task UpdateUserAsync(Data.Entities.User user) - { - await using var dbContext = _dbProvider.CreateContext(); - dbContext.Users.Update(user); - - await dbContext.SaveChangesAsync().ConfigureAwait(false); - } - - public Data.Entities.User CreateUser(string name) - { - using var dbContext = _dbProvider.CreateContext(); - - var newUser = CreateUserObject(name); - dbContext.Users.Add(newUser); - dbContext.SaveChanges(); - - return newUser; - } - - public void DeleteUser(Data.Entities.User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - using var dbContext = _dbProvider.CreateContext(); - - if (!dbContext.Users.Contains(user)) - { - throw new ArgumentException(string.Format( - CultureInfo.InvariantCulture, - "The user cannot be deleted because there is no user with the Name {0} and Id {1}.", - user.Username, - user.Id)); - } - - if (dbContext.Users.Count() == 1) - { - throw new InvalidOperationException(string.Format( - CultureInfo.InvariantCulture, - "The user '{0}' cannot be deleted because there must be at least one user in the system.", - user.Username)); - } - - if (user.HasPermission(PermissionKind.IsAdministrator) - && Users.Count(i => i.HasPermission(PermissionKind.IsAdministrator)) == 1) - { - throw new ArgumentException( - string.Format( - CultureInfo.InvariantCulture, - "The user '{0}' cannot be deleted because there must be at least one admin user in the system.", - user.Username), - nameof(user)); - } - - dbContext.Users.Remove(user); - dbContext.SaveChanges(); - } - - public Task ResetPassword(Data.Entities.User user) - { - return ChangePassword(user, string.Empty); - } - - public void ResetEasyPassword(Data.Entities.User user) - { - ChangeEasyPassword(user, string.Empty, null); - } - - public async Task ChangePassword(Data.Entities.User user, string newPassword) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false); - await UpdateUserAsync(user).ConfigureAwait(false); - - OnUserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); - } - - public void ChangeEasyPassword(Data.Entities.User user, string newPassword, string newPasswordSha1) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordSha1); - - UpdateUser(user); - - OnUserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); - } - - public UserDto GetUserDto(Data.Entities.User user, string remoteEndPoint = null) - { - return new UserDto - { - Id = user.Id, - HasPassword = user.Password == null, - EnableAutoLogin = user.EnableAutoLogin, - LastLoginDate = user.LastLoginDate, - LastActivityDate = user.LastActivityDate, - Configuration = new UserConfiguration - { - SubtitleMode = user.SubtitleMode, - HidePlayedInLatest = user.HidePlayedInLatest, - EnableLocalPassword = user.EnableLocalPassword, - PlayDefaultAudioTrack = user.PlayDefaultAudioTrack, - DisplayCollectionsView = user.DisplayCollectionsView, - DisplayMissingEpisodes = user.DisplayMissingEpisodes, - AudioLanguagePreference = user.AudioLanguagePreference, - RememberAudioSelections = user.RememberAudioSelections, - EnableNextEpisodeAutoPlay = user.EnableNextEpisodeAutoPlay, - RememberSubtitleSelections = user.RememberSubtitleSelections, - SubtitleLanguagePreference = user.SubtitleLanguagePreference, - OrderedViews = user.GetPreference(PreferenceKind.OrderedViews), - GroupedFolders = user.GetPreference(PreferenceKind.GroupedFolders), - MyMediaExcludes = user.GetPreference(PreferenceKind.MyMediaExcludes), - LatestItemsExcludes = user.GetPreference(PreferenceKind.LatestItemExcludes) - }, - Policy = new UserPolicy - { - MaxParentalRating = user.MaxParentalAgeRating, - EnableUserPreferenceAccess = user.EnableUserPreferenceAccess, - RemoteClientBitrateLimit = user.RemoteClientBitrateLimit.GetValueOrDefault(), - AuthenticatioIsnProviderId = user.AuthenticationProviderId, - PasswordResetProviderId = user.PasswordResetProviderId, - InvalidLoginAttemptCount = user.InvalidLoginAttemptCount, - LoginAttemptsBeforeLockout = user.LoginAttemptsBeforeLockout.GetValueOrDefault(), - IsAdministrator = user.HasPermission(PermissionKind.IsAdministrator), - IsHidden = user.HasPermission(PermissionKind.IsHidden), - IsDisabled = user.HasPermission(PermissionKind.IsDisabled), - EnableSharedDeviceControl = user.HasPermission(PermissionKind.EnableSharedDeviceControl), - EnableRemoteAccess = user.HasPermission(PermissionKind.EnableRemoteAccess), - EnableLiveTvManagement = user.HasPermission(PermissionKind.EnableLiveTvManagement), - EnableLiveTvAccess = user.HasPermission(PermissionKind.EnableLiveTvAccess), - EnableMediaPlayback = user.HasPermission(PermissionKind.EnableMediaPlayback), - EnableAudioPlaybackTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding), - EnableVideoPlaybackTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding), - EnableContentDeletion = user.HasPermission(PermissionKind.EnableContentDeletion), - EnableContentDownloading = user.HasPermission(PermissionKind.EnableContentDownloading), - EnableSyncTranscoding = user.HasPermission(PermissionKind.EnableSyncTranscoding), - EnableMediaConversion = user.HasPermission(PermissionKind.EnableMediaConversion), - EnableAllChannels = user.HasPermission(PermissionKind.EnableAllChannels), - EnableAllDevices = user.HasPermission(PermissionKind.EnableAllDevices), - EnableAllFolders = user.HasPermission(PermissionKind.EnableAllFolders), - EnableRemoteControlOfOtherUsers = user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers), - EnablePlaybackRemuxing = user.HasPermission(PermissionKind.EnablePlaybackRemuxing), - ForceRemoteSourceTranscoding = user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding), - EnablePublicSharing = user.HasPermission(PermissionKind.EnablePublicSharing), - AccessSchedules = user.AccessSchedules.ToArray(), - BlockedTags = user.GetPreference(PreferenceKind.BlockedTags), - EnabledChannels = user.GetPreference(PreferenceKind.EnabledChannels), - EnabledDevices = user.GetPreference(PreferenceKind.EnabledDevices), - EnabledFolders = user.GetPreference(PreferenceKind.EnabledFolders), - EnableContentDeletionFromFolders = user.GetPreference(PreferenceKind.EnableContentDeletionFromFolders) - } - }; - } - - public PublicUserDto GetPublicUserDto(Data.Entities.User user, string remoteEndPoint = null) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user); - bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetAuthenticationProvider(user).GetEasyPasswordHash(user)); - - bool hasPassword = user.EnableLocalPassword && - !string.IsNullOrEmpty(remoteEndPoint) && - _networkManager.IsInLocalNetwork(remoteEndPoint) ? hasConfiguredEasyPassword : hasConfiguredPassword; - - return new PublicUserDto - { - Name = user.Username, - HasPassword = hasPassword, - HasConfiguredPassword = hasConfiguredPassword - }; - } - - public async Task AuthenticateUser( - string username, - string password, - string passwordSha1, - string remoteEndPoint, - bool isUserSession) - { - if (string.IsNullOrWhiteSpace(username)) - { - _logger.LogInformation("Authentication request without username has been denied (IP: {IP}).", remoteEndPoint); - throw new ArgumentNullException(nameof(username)); - } - - var user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); - bool success; - IAuthenticationProvider authenticationProvider; - - if (user != null) - { - var authResult = await AuthenticateLocalUser(username, password, user, remoteEndPoint) - .ConfigureAwait(false); - authenticationProvider = authResult.authenticationProvider; - success = authResult.success; - } - else - { - var authResult = await AuthenticateLocalUser(username, password, null, remoteEndPoint) - .ConfigureAwait(false); - authenticationProvider = authResult.authenticationProvider; - string updatedUsername = authResult.username; - success = authResult.success; - - if (success - && authenticationProvider != null - && !(authenticationProvider is DefaultAuthenticationProvider)) - { - // Trust the username returned by the authentication provider - username = updatedUsername; - - // Search the database for the user again - // the authentication provider might have created it - user = Users - .FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); - - if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy) - { - UpdatePolicy(user.Id, hasNewUserPolicy.GetNewUserPolicy()); - - await UpdateUserAsync(user).ConfigureAwait(false); - } - } - } - - if (success && user != null && authenticationProvider != null) - { - var providerId = authenticationProvider.GetType().FullName; - - if (!string.Equals(providerId, user.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase)) - { - user.AuthenticationProviderId = providerId; - await UpdateUserAsync(user).ConfigureAwait(false); - } - } - - if (user == null) - { - _logger.LogInformation( - "Authentication request for {UserName} has been denied (IP: {IP}).", - username, - remoteEndPoint); - throw new AuthenticationException("Invalid username or password entered."); - } - - if (user.HasPermission(PermissionKind.IsDisabled)) - { - _logger.LogInformation( - "Authentication request for {UserName} has been denied because this account is currently disabled (IP: {IP}).", - username, - remoteEndPoint); - throw new SecurityException( - $"The {user.Username} account is currently disabled. Please consult with your administrator."); - } - - if (!user.HasPermission(PermissionKind.EnableRemoteAccess) && - !_networkManager.IsInLocalNetwork(remoteEndPoint)) - { - _logger.LogInformation( - "Authentication request for {UserName} forbidden: remote access disabled and user not in local network (IP: {IP}).", - username, - remoteEndPoint); - throw new SecurityException("Forbidden."); - } - - if (!user.IsParentalScheduleAllowed()) - { - _logger.LogInformation( - "Authentication request for {UserName} is not allowed at this time due parental restrictions (IP: {IP}).", - username, - remoteEndPoint); - throw new SecurityException("User is not allowed access at this time."); - } - - // Update LastActivityDate and LastLoginDate, then save - if (success) - { - if (isUserSession) - { - user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; - UpdateUser(user); - } - - ResetInvalidLoginAttemptCount(user); - _logger.LogInformation("Authentication request for {UserName} has succeeded.", user.Username); - } - else - { - IncrementInvalidLoginAttemptCount(user); - _logger.LogInformation( - "Authentication request for {UserName} has been denied (IP: {IP}).", - user.Username, - remoteEndPoint); - } - - return success ? user : null; - } - - public async Task StartForgotPasswordProcess(string enteredUsername, bool isInNetwork) - { - var user = string.IsNullOrWhiteSpace(enteredUsername) ? null : GetUserByName(enteredUsername); - - var action = ForgotPasswordAction.InNetworkRequired; - - if (user != null && isInNetwork) - { - var passwordResetProvider = GetPasswordResetProvider(user); - return await passwordResetProvider.StartForgotPasswordProcess(user, isInNetwork).ConfigureAwait(false); - } - - return new ForgotPasswordResult - { - Action = action, - PinFile = string.Empty - }; - } - - public async Task RedeemPasswordResetPin(string pin) - { - foreach (var provider in _passwordResetProviders) - { - var result = await provider.RedeemPasswordResetPin(pin).ConfigureAwait(false); - - if (result.Success) - { - return result; - } - } - - return new PinRedeemResult - { - Success = false, - UsersReset = Array.Empty() - }; - } - - public void AddParts(IEnumerable authenticationProviders, IEnumerable passwordResetProviders) - { - _authenticationProviders = authenticationProviders.ToArray(); - - _defaultAuthenticationProvider = _authenticationProviders.OfType().First(); - - _invalidAuthProvider = _authenticationProviders.OfType().First(); - - _passwordResetProviders = passwordResetProviders.ToArray(); - - _defaultPasswordResetProvider = passwordResetProviders.OfType().First(); - } - - public NameIdPair[] GetAuthenticationProviders() - { - return _authenticationProviders - .Where(provider => provider.IsEnabled) - .OrderBy(i => i is DefaultAuthenticationProvider ? 0 : 1) - .ThenBy(i => i.Name) - .Select(i => new NameIdPair - { - Name = i.Name, - Id = i.GetType().FullName - }) - .ToArray(); - } - - public NameIdPair[] GetPasswordResetProviders() - { - return _passwordResetProviders - .Where(provider => provider.IsEnabled) - .OrderBy(i => i is DefaultPasswordResetProvider ? 0 : 1) - .ThenBy(i => i.Name) - .Select(i => new NameIdPair - { - Name = i.Name, - Id = i.GetType().FullName - }) - .ToArray(); - } - - public void UpdateConfiguration(Guid userId, UserConfiguration config) - { - var user = GetUserById(userId); - user.SubtitleMode = config.SubtitleMode; - user.HidePlayedInLatest = config.HidePlayedInLatest; - user.EnableLocalPassword = config.EnableLocalPassword; - user.PlayDefaultAudioTrack = config.PlayDefaultAudioTrack; - user.DisplayCollectionsView = config.DisplayCollectionsView; - user.DisplayMissingEpisodes = config.DisplayMissingEpisodes; - user.AudioLanguagePreference = config.AudioLanguagePreference; - user.RememberAudioSelections = config.RememberAudioSelections; - user.EnableNextEpisodeAutoPlay = config.EnableNextEpisodeAutoPlay; - user.RememberSubtitleSelections = config.RememberSubtitleSelections; - user.SubtitleLanguagePreference = config.SubtitleLanguagePreference; - - user.SetPreference(PreferenceKind.OrderedViews, config.OrderedViews); - user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders); - user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes); - user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes); - - UpdateUser(user); - } - - public void UpdatePolicy(Guid userId, UserPolicy policy) - { - var user = GetUserById(userId); - - user.MaxParentalAgeRating = policy.MaxParentalRating; - user.EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess; - user.RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit; - user.AuthenticationProviderId = policy.AuthenticatioIsnProviderId; - user.PasswordResetProviderId = policy.PasswordResetProviderId; - user.InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount; - user.LoginAttemptsBeforeLockout = policy.LoginAttemptsBeforeLockout == -1 - ? null - : new int?(policy.LoginAttemptsBeforeLockout); - user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator); - user.SetPermission(PermissionKind.IsHidden, policy.IsHidden); - user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled); - user.SetPermission(PermissionKind.EnableSharedDeviceControl, policy.EnableSharedDeviceControl); - user.SetPermission(PermissionKind.EnableRemoteAccess, policy.EnableRemoteAccess); - user.SetPermission(PermissionKind.EnableLiveTvManagement, policy.EnableLiveTvManagement); - user.SetPermission(PermissionKind.EnableLiveTvAccess, policy.EnableLiveTvAccess); - user.SetPermission(PermissionKind.EnableMediaPlayback, policy.EnableMediaPlayback); - user.SetPermission(PermissionKind.EnableAudioPlaybackTranscoding, policy.EnableAudioPlaybackTranscoding); - user.SetPermission(PermissionKind.EnableVideoPlaybackTranscoding, policy.EnableVideoPlaybackTranscoding); - user.SetPermission(PermissionKind.EnableContentDeletion, policy.EnableContentDeletion); - user.SetPermission(PermissionKind.EnableContentDownloading, policy.EnableContentDownloading); - user.SetPermission(PermissionKind.EnableSyncTranscoding, policy.EnableSyncTranscoding); - user.SetPermission(PermissionKind.EnableMediaConversion, policy.EnableMediaConversion); - user.SetPermission(PermissionKind.EnableAllChannels, policy.EnableAllChannels); - user.SetPermission(PermissionKind.EnableAllDevices, policy.EnableAllDevices); - user.SetPermission(PermissionKind.EnableAllFolders, policy.EnableAllFolders); - user.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, policy.EnableRemoteControlOfOtherUsers); - user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing); - user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding); - user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing); - - user.AccessSchedules.Clear(); - foreach (var policyAccessSchedule in policy.AccessSchedules) - { - user.AccessSchedules.Add(policyAccessSchedule); - } - - user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags); - user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels); - user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices); - user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders); - user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders); - } - - private Data.Entities.User CreateUserObject(string name) - { - return new Data.Entities.User( - username: name, - mustUpdatePassword: false, - authenticationProviderId: _defaultAuthenticationProvider.GetType().FullName, - invalidLoginAttemptCount: -1, - subtitleMode: SubtitlePlaybackMode.Default, - playDefaultAudioTrack: true); - } - - private IAuthenticationProvider GetAuthenticationProvider(Data.Entities.User user) - { - return GetAuthenticationProviders(user)[0]; - } - - private IPasswordResetProvider GetPasswordResetProvider(Data.Entities.User user) - { - return GetPasswordResetProviders(user)[0]; - } - - private IList GetAuthenticationProviders(Data.Entities.User user) - { - var authenticationProviderId = user?.AuthenticationProviderId; - - var providers = _authenticationProviders.Where(i => i.IsEnabled).ToList(); - - if (!string.IsNullOrEmpty(authenticationProviderId)) - { - providers = providers.Where(i => string.Equals(authenticationProviderId, i.GetType().FullName, StringComparison.OrdinalIgnoreCase)).ToList(); - } - - if (providers.Count == 0) - { - // Assign the user to the InvalidAuthProvider since no configured auth provider was valid/found - _logger.LogWarning( - "User {UserName} was found with invalid/missing Authentication Provider {AuthenticationProviderId}. Assigning user to InvalidAuthProvider until this is corrected", - user?.Username, - user?.AuthenticationProviderId); - providers = new List - { - _invalidAuthProvider - }; - } - - return providers; - } - - private IList GetPasswordResetProviders(Data.Entities.User user) - { - var passwordResetProviderId = user?.PasswordResetProviderId; - var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray(); - - if (!string.IsNullOrEmpty(passwordResetProviderId)) - { - providers = providers.Where(i => - string.Equals(passwordResetProviderId, i.GetType().FullName, StringComparison.OrdinalIgnoreCase)) - .ToArray(); - } - - if (providers.Length == 0) - { - providers = new IPasswordResetProvider[] - { - _defaultPasswordResetProvider - }; - } - - return providers; - } - - private async Task<(IAuthenticationProvider authenticationProvider, string username, bool success)> - AuthenticateLocalUser( - string username, - string password, - Jellyfin.Data.Entities.User user, - string remoteEndPoint) - { - bool success = false; - IAuthenticationProvider authenticationProvider = null; - - foreach (var provider in GetAuthenticationProviders(user)) - { - var providerAuthResult = - await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); - var updatedUsername = providerAuthResult.username; - success = providerAuthResult.success; - - if (success) - { - authenticationProvider = provider; - username = updatedUsername; - break; - } - } - - if (!success - && _networkManager.IsInLocalNetwork(remoteEndPoint) - && user?.EnableLocalPassword == true - && !string.IsNullOrEmpty(user.EasyPassword)) - { - // Check easy password - var passwordHash = PasswordHash.Parse(user.EasyPassword); - var hash = _cryptoProvider.ComputeHash( - passwordHash.Id, - Encoding.UTF8.GetBytes(password), - passwordHash.Salt.ToArray()); - success = passwordHash.Hash.SequenceEqual(hash); - } - - return (authenticationProvider, username, success); - } - - private async Task<(string username, bool success)> AuthenticateWithProvider( - IAuthenticationProvider provider, - string username, - string password, - Data.Entities.User resolvedUser) - { - try - { - var authenticationResult = provider is IRequiresResolvedUser requiresResolvedUser - ? await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false) - : await provider.Authenticate(username, password).ConfigureAwait(false); - - if (authenticationResult.Username != username) - { - _logger.LogDebug("Authentication provider provided updated username {1}", authenticationResult.Username); - username = authenticationResult.Username; - } - - return (username, true); - } - catch (AuthenticationException ex) - { - _logger.LogError(ex, "Error authenticating with provider {Provider}", provider.Name); - - return (username, false); - } - } - - private void IncrementInvalidLoginAttemptCount(Data.Entities.User user) - { - int invalidLogins = user.InvalidLoginAttemptCount; - int? maxInvalidLogins = user.LoginAttemptsBeforeLockout; - if (maxInvalidLogins.HasValue - && invalidLogins >= maxInvalidLogins) - { - user.SetPermission(PermissionKind.IsDisabled, true); - OnUserLockedOut?.Invoke(this, new GenericEventArgs(user)); - _logger.LogWarning( - "Disabling user {UserName} due to {Attempts} unsuccessful login attempts.", - user.Username, - invalidLogins); - } - - UpdateUser(user); - } - - private void ResetInvalidLoginAttemptCount(Data.Entities.User user) - { - user.InvalidLoginAttemptCount = 0; - } - } -} diff --git a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs new file mode 100644 index 0000000000..38494727c3 --- /dev/null +++ b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs @@ -0,0 +1,185 @@ +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Common; +using MediaBrowser.Common.Cryptography; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Model.Cryptography; + +namespace Jellyfin.Server.Implementations.Users +{ + /// + /// The default authentication provider. + /// + public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser + { + private readonly ICryptoProvider _cryptographyProvider; + + /// + /// Initializes a new instance of the class. + /// + /// The cryptography provider. + public DefaultAuthenticationProvider(ICryptoProvider cryptographyProvider) + { + _cryptographyProvider = cryptographyProvider; + } + + /// + public string Name => "Default"; + + /// + public bool IsEnabled => true; + + /// + // This is dumb and an artifact of the backwards way auth providers were designed. + // This version of authenticate was never meant to be called, but needs to be here for interface compat + // Only the providers that don't provide local user support use this + public Task Authenticate(string username, string password) + { + throw new NotImplementedException(); + } + + /// + // This is the version that we need to use for local users. Because reasons. + public Task Authenticate(string username, string password, Data.Entities.User resolvedUser) + { + if (resolvedUser == null) + { + throw new AuthenticationException("Specified user does not exist."); + } + + bool success = false; + + // As long as jellyfin supports passwordless users, we need this little block here to accommodate + if (!HasPassword(resolvedUser)) + { + return Task.FromResult(new ProviderAuthenticationResult + { + Username = username + }); + } + + byte[] passwordBytes = Encoding.UTF8.GetBytes(password); + + PasswordHash readyHash = PasswordHash.Parse(resolvedUser.Password); + if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id) + || _cryptographyProvider.DefaultHashMethod == readyHash.Id) + { + byte[] calculatedHash = _cryptographyProvider.ComputeHash( + readyHash.Id, + passwordBytes, + readyHash.Salt.ToArray()); + + if (readyHash.Hash.SequenceEqual(calculatedHash)) + { + success = true; + } + } + else + { + throw new AuthenticationException($"Requested crypto method not available in provider: {readyHash.Id}"); + } + + if (!success) + { + throw new AuthenticationException("Invalid username or password"); + } + + return Task.FromResult(new ProviderAuthenticationResult + { + Username = username + }); + } + + /// + public bool HasPassword(Data.Entities.User user) + => !string.IsNullOrEmpty(user.Password); + + /// + public Task ChangePassword(Data.Entities.User user, string newPassword) + { + if (string.IsNullOrEmpty(newPassword)) + { + user.Password = null; + return Task.CompletedTask; + } + + PasswordHash newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword); + user.Password = newPasswordHash.ToString(); + + return Task.CompletedTask; + } + + /// + public void ChangeEasyPassword(Data.Entities.User user, string newPassword, string newPasswordHash) + { + if (newPassword != null) + { + newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword).ToString(); + } + + if (string.IsNullOrWhiteSpace(newPasswordHash)) + { + throw new ArgumentNullException(nameof(newPasswordHash)); + } + + user.EasyPassword = newPasswordHash; + } + + /// + public string GetEasyPasswordHash(Data.Entities.User user) + { + return string.IsNullOrEmpty(user.EasyPassword) + ? null + : Hex.Encode(PasswordHash.Parse(user.EasyPassword).Hash); + } + + /// + /// Hashes the provided string. + /// + /// The user. + /// The string to hash. + /// The hashed string. + public string GetHashedString(Data.Entities.User user, string str) + { + if (string.IsNullOrEmpty(user.Password)) + { + return _cryptographyProvider.CreatePasswordHash(str).ToString(); + } + + // TODO: make use of iterations parameter? + PasswordHash passwordHash = PasswordHash.Parse(user.Password); + var salt = passwordHash.Salt.ToArray(); + return new PasswordHash( + passwordHash.Id, + _cryptographyProvider.ComputeHash( + passwordHash.Id, + Encoding.UTF8.GetBytes(str), + salt), + salt, + passwordHash.Parameters.ToDictionary(x => x.Key, y => y.Value)).ToString(); + } + + /// + /// Hashes the provided string. + /// + /// The user. + /// The string to hash. + /// The hashed string. + public ReadOnlySpan GetHashed(Data.Entities.User user, string str) + { + if (string.IsNullOrEmpty(user.Password)) + { + return _cryptographyProvider.CreatePasswordHash(str).Hash; + } + + // TODO: make use of iterations parameter? + PasswordHash passwordHash = PasswordHash.Parse(user.Password); + return _cryptographyProvider.ComputeHash( + passwordHash.Id, + Encoding.UTF8.GetBytes(str), + passwordHash.Salt.ToArray()); + } + } +} diff --git a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs new file mode 100644 index 0000000000..60b48ec761 --- /dev/null +++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Users; + +namespace Jellyfin.Server.Implementations.Users +{ + /// + /// The default password reset provider. + /// + public class DefaultPasswordResetProvider : IPasswordResetProvider + { + private const string BaseResetFileName = "passwordreset"; + + private readonly IJsonSerializer _jsonSerializer; + private readonly IUserManager _userManager; + + private readonly string _passwordResetFileBase; + private readonly string _passwordResetFileBaseDir; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration manager. + /// The JSON serializer. + /// The user manager. + public DefaultPasswordResetProvider( + IServerConfigurationManager configurationManager, + IJsonSerializer jsonSerializer, + IUserManager userManager) + { + _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath; + _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, BaseResetFileName); + _jsonSerializer = jsonSerializer; + _userManager = userManager; + } + + /// + public string Name => "Default Password Reset Provider"; + + /// + public bool IsEnabled => true; + + /// + public async Task RedeemPasswordResetPin(string pin) + { + SerializablePasswordReset spr; + List usersReset = new List(); + foreach (var resetFile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*")) + { + await using (var str = File.OpenRead(resetFile)) + { + spr = await _jsonSerializer.DeserializeFromStreamAsync(str).ConfigureAwait(false); + } + + if (spr.ExpirationDate < DateTime.Now) + { + File.Delete(resetFile); + } + else if (string.Equals( + spr.Pin.Replace("-", string.Empty, StringComparison.Ordinal), + pin.Replace("-", string.Empty, StringComparison.Ordinal), + StringComparison.InvariantCultureIgnoreCase)) + { + var resetUser = _userManager.GetUserByName(spr.UserName); + if (resetUser == null) + { + throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found"); + } + + await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); + usersReset.Add(resetUser.Username); + File.Delete(resetFile); + } + } + + if (usersReset.Count < 1) + { + throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}"); + } + + return new PinRedeemResult + { + Success = true, + UsersReset = usersReset.ToArray() + }; + } + + /// + public async Task StartForgotPasswordProcess(User user, bool isInNetwork) + { + string pin; + using (var cryptoRandom = RandomNumberGenerator.Create()) + { + byte[] bytes = new byte[4]; + cryptoRandom.GetBytes(bytes); + pin = BitConverter.ToString(bytes); + } + + DateTime expireTime = DateTime.Now.AddMinutes(30); + + user.EasyPassword = pin; + await _userManager.UpdateUserAsync(user).ConfigureAwait(false); + + return new ForgotPasswordResult + { + Action = ForgotPasswordAction.PinCode, + PinExpirationDate = expireTime, + }; + } + + private class SerializablePasswordReset : PasswordPinCreationResult + { + public string Pin { get; set; } + + public string UserName { get; set; } + } + } +} diff --git a/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs b/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs new file mode 100644 index 0000000000..d94a27b9db --- /dev/null +++ b/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs @@ -0,0 +1,66 @@ +#pragma warning disable CS1591 + +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Security; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Events; + +namespace Jellyfin.Server.Implementations.Users +{ + public sealed class DeviceAccessEntryPoint : IServerEntryPoint + { + private readonly IUserManager _userManager; + private readonly IAuthenticationRepository _authRepo; + private readonly IDeviceManager _deviceManager; + private readonly ISessionManager _sessionManager; + + public DeviceAccessEntryPoint(IUserManager userManager, IAuthenticationRepository authRepo, IDeviceManager deviceManager, ISessionManager sessionManager) + { + _userManager = userManager; + _authRepo = authRepo; + _deviceManager = deviceManager; + _sessionManager = sessionManager; + } + + public Task RunAsync() + { + _userManager.OnUserUpdated += OnUserUpdated; + + return Task.CompletedTask; + } + + public void Dispose() + { + } + + private void OnUserUpdated(object sender, GenericEventArgs e) + { + var user = e.Argument; + if (!user.HasPermission(PermissionKind.EnableAllDevices)) + { + UpdateDeviceAccess(user); + } + } + + private void UpdateDeviceAccess(User user) + { + var existing = _authRepo.Get(new AuthenticationInfoQuery + { + UserId = user.Id + }).Items; + + foreach (var authInfo in existing) + { + if (!string.IsNullOrEmpty(authInfo.DeviceId) && !_deviceManager.CanAccessDevice(user, authInfo.DeviceId)) + { + _sessionManager.Logout(authInfo); + } + } + } + } +} diff --git a/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs b/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs new file mode 100644 index 0000000000..e430808bfd --- /dev/null +++ b/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs @@ -0,0 +1,47 @@ +using System.Threading.Tasks; +using MediaBrowser.Controller.Authentication; + +namespace Jellyfin.Server.Implementations.Users +{ + /// + /// An invalid authentication provider. + /// + public class InvalidAuthProvider : IAuthenticationProvider + { + /// + public string Name => "InvalidOrMissingAuthenticationProvider"; + + /// + public bool IsEnabled => true; + + /// + public Task Authenticate(string username, string password) + { + throw new AuthenticationException("User Account cannot login with this provider. The Normal provider for this user cannot be found"); + } + + /// + public bool HasPassword(Data.Entities.User user) + { + return true; + } + + /// + public Task ChangePassword(Data.Entities.User user, string newPassword) + { + return Task.CompletedTask; + } + + /// + public void ChangeEasyPassword(Data.Entities.User user, string newPassword, string newPasswordHash) + { + // Nothing here + } + + /// + public string GetEasyPasswordHash(Data.Entities.User user) + { + return string.Empty; + } + } +} diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs new file mode 100644 index 0000000000..ddc05055b6 --- /dev/null +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -0,0 +1,763 @@ +#pragma warning disable CA1307 +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; +using MediaBrowser.Common.Cryptography; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Cryptography; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Users; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Implementations.Users +{ + public class UserManager : IUserManager + { + private readonly JellyfinDbProvider _dbProvider; + private readonly ICryptoProvider _cryptoProvider; + private readonly INetworkManager _networkManager; + private readonly ILogger _logger; + + private IAuthenticationProvider[] _authenticationProviders; + private DefaultAuthenticationProvider _defaultAuthenticationProvider; + private InvalidAuthProvider _invalidAuthProvider; + private IPasswordResetProvider[] _passwordResetProviders; + private DefaultPasswordResetProvider _defaultPasswordResetProvider; + + public UserManager( + JellyfinDbProvider dbProvider, + ICryptoProvider cryptoProvider, + INetworkManager networkManager, + ILogger logger) + { + _dbProvider = dbProvider; + _cryptoProvider = cryptoProvider; + _networkManager = networkManager; + _logger = logger; + } + + public event EventHandler> OnUserPasswordChanged; + + /// + public event EventHandler> OnUserUpdated; + + /// + public event EventHandler> OnUserCreated; + + /// + public event EventHandler> OnUserDeleted; + + public event EventHandler> OnUserLockedOut; + + public IEnumerable Users + { + get + { + var dbContext = _dbProvider.CreateContext(); + return dbContext.Users; + } + } + + public IEnumerable UsersIds + { + get + { + var dbContext = _dbProvider.CreateContext(); + return dbContext.Users.Select(u => u.Id); + } + } + + public User GetUserById(Guid id) + { + if (id == Guid.Empty) + { + throw new ArgumentException("Guid can't be empty", nameof(id)); + } + + var dbContext = _dbProvider.CreateContext(); + + return dbContext.Users.Find(id); + } + + public User GetUserByName(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Invalid username", nameof(name)); + } + + var dbContext = _dbProvider.CreateContext(); + + // This can't use an overload with StringComparer because that would cause the query to + // have to be evaluated client-side. + return dbContext.Users.FirstOrDefault(u => string.Equals(u.Username, name)); + } + + public async Task RenameUser(User user, string newName) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + if (string.IsNullOrWhiteSpace(newName)) + { + throw new ArgumentException("Invalid username", nameof(newName)); + } + + if (user.Username.Equals(newName, StringComparison.Ordinal)) + { + throw new ArgumentException("The new and old names must be different."); + } + + if (Users.Any( + u => u.Id != user.Id && u.Username.Equals(newName, StringComparison.OrdinalIgnoreCase))) + { + throw new ArgumentException(string.Format( + CultureInfo.InvariantCulture, + "A user with the name '{0}' already exists.", + newName)); + } + + user.Username = newName; + await UpdateUserAsync(user).ConfigureAwait(false); + + OnUserUpdated?.Invoke(this, new GenericEventArgs(user)); + } + + public void UpdateUser(User user) + { + var dbContext = _dbProvider.CreateContext(); + dbContext.Users.Update(user); + dbContext.SaveChanges(); + } + + public async Task UpdateUserAsync(User user) + { + var dbContext = _dbProvider.CreateContext(); + dbContext.Users.Update(user); + + await dbContext.SaveChangesAsync().ConfigureAwait(false); + } + + public User CreateUser(string name) + { + if (!IsValidUsername(name)) + { + throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)"); + } + + var dbContext = _dbProvider.CreateContext(); + + var newUser = new User(name, _defaultAuthenticationProvider.GetType().FullName); + dbContext.Users.Add(newUser); + dbContext.SaveChanges(); + + OnUserCreated?.Invoke(this, new GenericEventArgs(newUser)); + + return newUser; + } + + public void DeleteUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var dbContext = _dbProvider.CreateContext(); + + if (!dbContext.Users.Contains(user)) + { + throw new ArgumentException(string.Format( + CultureInfo.InvariantCulture, + "The user cannot be deleted because there is no user with the Name {0} and Id {1}.", + user.Username, + user.Id)); + } + + if (dbContext.Users.Count() == 1) + { + throw new InvalidOperationException(string.Format( + CultureInfo.InvariantCulture, + "The user '{0}' cannot be deleted because there must be at least one user in the system.", + user.Username)); + } + + if (user.HasPermission(PermissionKind.IsAdministrator) + && Users.Count(i => i.HasPermission(PermissionKind.IsAdministrator)) == 1) + { + throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + "The user '{0}' cannot be deleted because there must be at least one admin user in the system.", + user.Username), + nameof(user)); + } + + dbContext.Users.Remove(user); + dbContext.SaveChanges(); + OnUserDeleted?.Invoke(this, new GenericEventArgs(user)); + } + + public Task ResetPassword(User user) + { + return ChangePassword(user, string.Empty); + } + + public void ResetEasyPassword(User user) + { + ChangeEasyPassword(user, string.Empty, null); + } + + public async Task ChangePassword(User user, string newPassword) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false); + await UpdateUserAsync(user).ConfigureAwait(false); + + OnUserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); + } + + public void ChangeEasyPassword(User user, string newPassword, string newPasswordSha1) + { + GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordSha1); + UpdateUser(user); + + OnUserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); + } + + public UserDto GetUserDto(User user, string remoteEndPoint = null) + { + return new UserDto + { + Id = user.Id, + HasPassword = user.Password == null, + EnableAutoLogin = user.EnableAutoLogin, + LastLoginDate = user.LastLoginDate, + LastActivityDate = user.LastActivityDate, + Configuration = new UserConfiguration + { + SubtitleMode = user.SubtitleMode, + HidePlayedInLatest = user.HidePlayedInLatest, + EnableLocalPassword = user.EnableLocalPassword, + PlayDefaultAudioTrack = user.PlayDefaultAudioTrack, + DisplayCollectionsView = user.DisplayCollectionsView, + DisplayMissingEpisodes = user.DisplayMissingEpisodes, + AudioLanguagePreference = user.AudioLanguagePreference, + RememberAudioSelections = user.RememberAudioSelections, + EnableNextEpisodeAutoPlay = user.EnableNextEpisodeAutoPlay, + RememberSubtitleSelections = user.RememberSubtitleSelections, + SubtitleLanguagePreference = user.SubtitleLanguagePreference, + OrderedViews = user.GetPreference(PreferenceKind.OrderedViews), + GroupedFolders = user.GetPreference(PreferenceKind.GroupedFolders), + MyMediaExcludes = user.GetPreference(PreferenceKind.MyMediaExcludes), + LatestItemsExcludes = user.GetPreference(PreferenceKind.LatestItemExcludes) + }, + Policy = new UserPolicy + { + MaxParentalRating = user.MaxParentalAgeRating, + EnableUserPreferenceAccess = user.EnableUserPreferenceAccess, + RemoteClientBitrateLimit = user.RemoteClientBitrateLimit.GetValueOrDefault(), + AuthenticationProviderId = user.AuthenticationProviderId, + PasswordResetProviderId = user.PasswordResetProviderId, + InvalidLoginAttemptCount = user.InvalidLoginAttemptCount, + LoginAttemptsBeforeLockout = user.LoginAttemptsBeforeLockout.GetValueOrDefault(), + IsAdministrator = user.HasPermission(PermissionKind.IsAdministrator), + IsHidden = user.HasPermission(PermissionKind.IsHidden), + IsDisabled = user.HasPermission(PermissionKind.IsDisabled), + EnableSharedDeviceControl = user.HasPermission(PermissionKind.EnableSharedDeviceControl), + EnableRemoteAccess = user.HasPermission(PermissionKind.EnableRemoteAccess), + EnableLiveTvManagement = user.HasPermission(PermissionKind.EnableLiveTvManagement), + EnableLiveTvAccess = user.HasPermission(PermissionKind.EnableLiveTvAccess), + EnableMediaPlayback = user.HasPermission(PermissionKind.EnableMediaPlayback), + EnableAudioPlaybackTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding), + EnableVideoPlaybackTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding), + EnableContentDeletion = user.HasPermission(PermissionKind.EnableContentDeletion), + EnableContentDownloading = user.HasPermission(PermissionKind.EnableContentDownloading), + EnableSyncTranscoding = user.HasPermission(PermissionKind.EnableSyncTranscoding), + EnableMediaConversion = user.HasPermission(PermissionKind.EnableMediaConversion), + EnableAllChannels = user.HasPermission(PermissionKind.EnableAllChannels), + EnableAllDevices = user.HasPermission(PermissionKind.EnableAllDevices), + EnableAllFolders = user.HasPermission(PermissionKind.EnableAllFolders), + EnableRemoteControlOfOtherUsers = user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers), + EnablePlaybackRemuxing = user.HasPermission(PermissionKind.EnablePlaybackRemuxing), + ForceRemoteSourceTranscoding = user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding), + EnablePublicSharing = user.HasPermission(PermissionKind.EnablePublicSharing), + AccessSchedules = user.AccessSchedules.ToArray(), + BlockedTags = user.GetPreference(PreferenceKind.BlockedTags), + EnabledChannels = user.GetPreference(PreferenceKind.EnabledChannels), + EnabledDevices = user.GetPreference(PreferenceKind.EnabledDevices), + EnabledFolders = user.GetPreference(PreferenceKind.EnabledFolders), + EnableContentDeletionFromFolders = user.GetPreference(PreferenceKind.EnableContentDeletionFromFolders) + } + }; + } + + public PublicUserDto GetPublicUserDto(User user, string remoteEndPoint = null) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user); + bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetAuthenticationProvider(user).GetEasyPasswordHash(user)); + + bool hasPassword = user.EnableLocalPassword && + !string.IsNullOrEmpty(remoteEndPoint) && + _networkManager.IsInLocalNetwork(remoteEndPoint) ? hasConfiguredEasyPassword : hasConfiguredPassword; + + return new PublicUserDto + { + Name = user.Username, + HasPassword = hasPassword, + HasConfiguredPassword = hasConfiguredPassword + }; + } + + public async Task AuthenticateUser( + string username, + string password, + string passwordSha1, + string remoteEndPoint, + bool isUserSession) + { + if (string.IsNullOrWhiteSpace(username)) + { + _logger.LogInformation("Authentication request without username has been denied (IP: {IP}).", remoteEndPoint); + throw new ArgumentNullException(nameof(username)); + } + + var user = Users.ToList().FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); + bool success; + IAuthenticationProvider authenticationProvider; + + if (user != null) + { + var authResult = await AuthenticateLocalUser(username, password, user, remoteEndPoint) + .ConfigureAwait(false); + authenticationProvider = authResult.authenticationProvider; + success = authResult.success; + } + else + { + var authResult = await AuthenticateLocalUser(username, password, null, remoteEndPoint) + .ConfigureAwait(false); + authenticationProvider = authResult.authenticationProvider; + string updatedUsername = authResult.username; + success = authResult.success; + + if (success + && authenticationProvider != null + && !(authenticationProvider is DefaultAuthenticationProvider)) + { + // Trust the username returned by the authentication provider + username = updatedUsername; + + // Search the database for the user again + // the authentication provider might have created it + user = Users + .ToList().FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); + + if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy) + { + UpdatePolicy(user.Id, hasNewUserPolicy.GetNewUserPolicy()); + + await UpdateUserAsync(user).ConfigureAwait(false); + } + } + } + + if (success && user != null && authenticationProvider != null) + { + var providerId = authenticationProvider.GetType().FullName; + + if (!string.Equals(providerId, user.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase)) + { + user.AuthenticationProviderId = providerId; + await UpdateUserAsync(user).ConfigureAwait(false); + } + } + + if (user == null) + { + _logger.LogInformation( + "Authentication request for {UserName} has been denied (IP: {IP}).", + username, + remoteEndPoint); + throw new AuthenticationException("Invalid username or password entered."); + } + + if (user.HasPermission(PermissionKind.IsDisabled)) + { + _logger.LogInformation( + "Authentication request for {UserName} has been denied because this account is currently disabled (IP: {IP}).", + username, + remoteEndPoint); + throw new SecurityException( + $"The {user.Username} account is currently disabled. Please consult with your administrator."); + } + + if (!user.HasPermission(PermissionKind.EnableRemoteAccess) && + !_networkManager.IsInLocalNetwork(remoteEndPoint)) + { + _logger.LogInformation( + "Authentication request for {UserName} forbidden: remote access disabled and user not in local network (IP: {IP}).", + username, + remoteEndPoint); + throw new SecurityException("Forbidden."); + } + + if (!user.IsParentalScheduleAllowed()) + { + _logger.LogInformation( + "Authentication request for {UserName} is not allowed at this time due parental restrictions (IP: {IP}).", + username, + remoteEndPoint); + throw new SecurityException("User is not allowed access at this time."); + } + + // Update LastActivityDate and LastLoginDate, then save + if (success) + { + if (isUserSession) + { + user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; + await UpdateUserAsync(user).ConfigureAwait(false); + } + + user.InvalidLoginAttemptCount = 0; + _logger.LogInformation("Authentication request for {UserName} has succeeded.", user.Username); + } + else + { + IncrementInvalidLoginAttemptCount(user); + _logger.LogInformation( + "Authentication request for {UserName} has been denied (IP: {IP}).", + user.Username, + remoteEndPoint); + } + + return success ? user : null; + } + + public async Task StartForgotPasswordProcess(string enteredUsername, bool isInNetwork) + { + var user = string.IsNullOrWhiteSpace(enteredUsername) ? null : GetUserByName(enteredUsername); + + var action = ForgotPasswordAction.InNetworkRequired; + + if (user != null && isInNetwork) + { + var passwordResetProvider = GetPasswordResetProvider(user); + return await passwordResetProvider.StartForgotPasswordProcess(user, isInNetwork).ConfigureAwait(false); + } + + return new ForgotPasswordResult + { + Action = action, + PinFile = string.Empty + }; + } + + public async Task RedeemPasswordResetPin(string pin) + { + foreach (var provider in _passwordResetProviders) + { + var result = await provider.RedeemPasswordResetPin(pin).ConfigureAwait(false); + + if (result.Success) + { + return result; + } + } + + return new PinRedeemResult + { + Success = false, + UsersReset = Array.Empty() + }; + } + + public void AddParts(IEnumerable authenticationProviders, IEnumerable passwordResetProviders) + { + _authenticationProviders = authenticationProviders.ToArray(); + _passwordResetProviders = passwordResetProviders.ToArray(); + + _invalidAuthProvider = _authenticationProviders.OfType().First(); + _defaultAuthenticationProvider = _authenticationProviders.OfType().First(); + _defaultPasswordResetProvider = _passwordResetProviders.OfType().First(); + } + + public NameIdPair[] GetAuthenticationProviders() + { + return _authenticationProviders + .Where(provider => provider.IsEnabled) + .OrderBy(i => i is DefaultAuthenticationProvider ? 0 : 1) + .ThenBy(i => i.Name) + .Select(i => new NameIdPair + { + Name = i.Name, + Id = i.GetType().FullName + }) + .ToArray(); + } + + public NameIdPair[] GetPasswordResetProviders() + { + return _passwordResetProviders + .Where(provider => provider.IsEnabled) + .OrderBy(i => i is DefaultPasswordResetProvider ? 0 : 1) + .ThenBy(i => i.Name) + .Select(i => new NameIdPair + { + Name = i.Name, + Id = i.GetType().FullName + }) + .ToArray(); + } + + public void UpdateConfiguration(Guid userId, UserConfiguration config) + { + var user = GetUserById(userId); + user.SubtitleMode = config.SubtitleMode; + user.HidePlayedInLatest = config.HidePlayedInLatest; + user.EnableLocalPassword = config.EnableLocalPassword; + user.PlayDefaultAudioTrack = config.PlayDefaultAudioTrack; + user.DisplayCollectionsView = config.DisplayCollectionsView; + user.DisplayMissingEpisodes = config.DisplayMissingEpisodes; + user.AudioLanguagePreference = config.AudioLanguagePreference; + user.RememberAudioSelections = config.RememberAudioSelections; + user.EnableNextEpisodeAutoPlay = config.EnableNextEpisodeAutoPlay; + user.RememberSubtitleSelections = config.RememberSubtitleSelections; + user.SubtitleLanguagePreference = config.SubtitleLanguagePreference; + + user.SetPreference(PreferenceKind.OrderedViews, config.OrderedViews); + user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders); + user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes); + user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes); + + UpdateUser(user); + } + + public void UpdatePolicy(Guid userId, UserPolicy policy) + { + var user = GetUserById(userId); + + user.MaxParentalAgeRating = policy.MaxParentalRating; + user.EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess; + user.RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit; + user.AuthenticationProviderId = policy.AuthenticationProviderId; + user.PasswordResetProviderId = policy.PasswordResetProviderId; + user.InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount; + user.LoginAttemptsBeforeLockout = policy.LoginAttemptsBeforeLockout == -1 + ? null + : new int?(policy.LoginAttemptsBeforeLockout); + user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator); + user.SetPermission(PermissionKind.IsHidden, policy.IsHidden); + user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled); + user.SetPermission(PermissionKind.EnableSharedDeviceControl, policy.EnableSharedDeviceControl); + user.SetPermission(PermissionKind.EnableRemoteAccess, policy.EnableRemoteAccess); + user.SetPermission(PermissionKind.EnableLiveTvManagement, policy.EnableLiveTvManagement); + user.SetPermission(PermissionKind.EnableLiveTvAccess, policy.EnableLiveTvAccess); + user.SetPermission(PermissionKind.EnableMediaPlayback, policy.EnableMediaPlayback); + user.SetPermission(PermissionKind.EnableAudioPlaybackTranscoding, policy.EnableAudioPlaybackTranscoding); + user.SetPermission(PermissionKind.EnableVideoPlaybackTranscoding, policy.EnableVideoPlaybackTranscoding); + user.SetPermission(PermissionKind.EnableContentDeletion, policy.EnableContentDeletion); + user.SetPermission(PermissionKind.EnableContentDownloading, policy.EnableContentDownloading); + user.SetPermission(PermissionKind.EnableSyncTranscoding, policy.EnableSyncTranscoding); + user.SetPermission(PermissionKind.EnableMediaConversion, policy.EnableMediaConversion); + user.SetPermission(PermissionKind.EnableAllChannels, policy.EnableAllChannels); + user.SetPermission(PermissionKind.EnableAllDevices, policy.EnableAllDevices); + user.SetPermission(PermissionKind.EnableAllFolders, policy.EnableAllFolders); + user.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, policy.EnableRemoteControlOfOtherUsers); + user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing); + user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding); + user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing); + + user.AccessSchedules.Clear(); + foreach (var policyAccessSchedule in policy.AccessSchedules) + { + user.AccessSchedules.Add(policyAccessSchedule); + } + + user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags); + user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels); + user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices); + user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders); + user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders); + } + + private bool IsValidUsername(string name) + { + // This is some regex that matches only on unicode "word" characters, as well as -, _ and @ + // In theory this will cut out most if not all 'control' characters which should help minimize any weirdness + // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), and periods (.) + return Regex.IsMatch(name, @"^[\w\-'._@]*$"); + } + + private IAuthenticationProvider GetAuthenticationProvider(User user) + { + return GetAuthenticationProviders(user)[0]; + } + + private IPasswordResetProvider GetPasswordResetProvider(User user) + { + return GetPasswordResetProviders(user)[0]; + } + + private IList GetAuthenticationProviders(User user) + { + var authenticationProviderId = user?.AuthenticationProviderId; + + var providers = _authenticationProviders.Where(i => i.IsEnabled).ToList(); + + if (!string.IsNullOrEmpty(authenticationProviderId)) + { + providers = providers.Where(i => string.Equals(authenticationProviderId, i.GetType().FullName, StringComparison.OrdinalIgnoreCase)).ToList(); + } + + if (providers.Count == 0) + { + // Assign the user to the InvalidAuthProvider since no configured auth provider was valid/found + _logger.LogWarning( + "User {Username} was found with invalid/missing Authentication Provider {AuthenticationProviderId}. Assigning user to InvalidAuthProvider until this is corrected", + user?.Username, + user?.AuthenticationProviderId); + providers = new List + { + _invalidAuthProvider + }; + } + + return providers; + } + + private IList GetPasswordResetProviders(User user) + { + var passwordResetProviderId = user?.PasswordResetProviderId; + var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray(); + + if (!string.IsNullOrEmpty(passwordResetProviderId)) + { + providers = providers.Where(i => + string.Equals(passwordResetProviderId, i.GetType().FullName, StringComparison.OrdinalIgnoreCase)) + .ToArray(); + } + + if (providers.Length == 0) + { + providers = new IPasswordResetProvider[] + { + _defaultPasswordResetProvider + }; + } + + return providers; + } + + private async Task<(IAuthenticationProvider authenticationProvider, string username, bool success)> AuthenticateLocalUser( + string username, + string password, + User user, + string remoteEndPoint) + { + bool success = false; + IAuthenticationProvider authenticationProvider = null; + + foreach (var provider in GetAuthenticationProviders(user)) + { + var providerAuthResult = + await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); + var updatedUsername = providerAuthResult.username; + success = providerAuthResult.success; + + if (success) + { + authenticationProvider = provider; + username = updatedUsername; + break; + } + } + + if (!success + && _networkManager.IsInLocalNetwork(remoteEndPoint) + && user?.EnableLocalPassword == true + && !string.IsNullOrEmpty(user.EasyPassword)) + { + // Check easy password + var passwordHash = PasswordHash.Parse(user.EasyPassword); + var hash = _cryptoProvider.ComputeHash( + passwordHash.Id, + Encoding.UTF8.GetBytes(password), + passwordHash.Salt.ToArray()); + success = passwordHash.Hash.SequenceEqual(hash); + } + + return (authenticationProvider, username, success); + } + + private async Task<(string username, bool success)> AuthenticateWithProvider( + IAuthenticationProvider provider, + string username, + string password, + User resolvedUser) + { + try + { + var authenticationResult = provider is IRequiresResolvedUser requiresResolvedUser + ? await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false) + : await provider.Authenticate(username, password).ConfigureAwait(false); + + if (authenticationResult.Username != username) + { + _logger.LogDebug("Authentication provider provided updated username {1}", authenticationResult.Username); + username = authenticationResult.Username; + } + + return (username, true); + } + catch (AuthenticationException ex) + { + _logger.LogError(ex, "Error authenticating with provider {Provider}", provider.Name); + + return (username, false); + } + } + + private void IncrementInvalidLoginAttemptCount(User user) + { + int invalidLogins = user.InvalidLoginAttemptCount; + int? maxInvalidLogins = user.LoginAttemptsBeforeLockout; + if (maxInvalidLogins.HasValue && invalidLogins >= maxInvalidLogins) + { + user.SetPermission(PermissionKind.IsDisabled, true); + OnUserLockedOut?.Invoke(this, new GenericEventArgs(user)); + _logger.LogWarning( + "Disabling user {Username} due to {Attempts} unsuccessful login attempts.", + user.Username, + invalidLogins); + } + + UpdateUser(user); + } + } +} diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 331a32c737..b4c5fd4ea0 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -7,8 +7,10 @@ using Emby.Server.Implementations; using Jellyfin.Drawing.Skia; using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations.Activity; +using Jellyfin.Server.Implementations.Users; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Library; using MediaBrowser.Model.Activity; using MediaBrowser.Model.IO; using Microsoft.EntityFrameworkCore; @@ -69,6 +71,7 @@ namespace Jellyfin.Server serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); base.RegisterServices(serviceCollection); } @@ -80,6 +83,9 @@ namespace Jellyfin.Server protected override IEnumerable GetAssembliesWithPartsInternal() { yield return typeof(CoreAppHost).Assembly; + yield return typeof(DefaultAuthenticationProvider).Assembly; + yield return typeof(DefaultPasswordResetProvider).Assembly; + yield return typeof(InvalidAuthProvider).Assembly; } /// diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index ea16c55730..9eec6ed4eb 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -41,7 +41,6 @@ - diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index b19d7f7fca..0271098f48 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -1,33 +1,45 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; +using System; using System.IO; using Emby.Server.Implementations.Data; using Emby.Server.Implementations.Serialization; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using Jellyfin.Server.Implementations; +using Jellyfin.Server.Implementations.Users; using MediaBrowser.Controller; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Users; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using SQLitePCL.pretty; +using JsonSerializer = System.Text.Json.JsonSerializer; namespace Jellyfin.Server.Migrations.Routines { + /// + /// The migration routine for migrating the user database to EF Core. + /// public class MigrateUserDb : IMigrationRoutine { - private readonly ILogger _logger; + private const string DbFilename = "users.db"; + private readonly ILogger _logger; private readonly IServerApplicationPaths _paths; - private readonly JellyfinDbProvider _provider; - private readonly MyXmlSerializer _xmlSerializer; - public MigrateUserDb(ILogger logger, IServerApplicationPaths paths, JellyfinDbProvider provider, MyXmlSerializer xmlSerializer) + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The server application paths. + /// The database provider. + /// The xml serializer. + public MigrateUserDb( + ILogger logger, + IServerApplicationPaths paths, + JellyfinDbProvider provider, + MyXmlSerializer xmlSerializer) { _logger = logger; _paths = paths; @@ -35,18 +47,21 @@ namespace Jellyfin.Server.Migrations.Routines _xmlSerializer = xmlSerializer; } + /// public Guid Id => Guid.Parse("5C4B82A2-F053-4009-BD05-B6FCAD82F14C"); - public string Name => "MigrateUserDb"; + /// + public string Name => "MigrateUserDatabase"; + /// public void Perform() { var dataPath = _paths.DataPath; _logger.LogInformation("Migrating the user database may take a while, do not stop Jellyfin."); - using (var connection = SQLite3.Open(Path.Combine(dataPath, "users.db"), ConnectionFlags.ReadOnly, null)) + using (var connection = SQLite3.Open(Path.Combine(dataPath, DbFilename), ConnectionFlags.ReadOnly, null)) { - using var dbContext = _provider.CreateContext(); + var dbContext = _provider.CreateContext(); var queryResult = connection.Query("SELECT * FROM LocalUsersv2"); @@ -55,26 +70,30 @@ namespace Jellyfin.Server.Migrations.Routines foreach (var entry in queryResult) { - var json = JsonConvert.DeserializeObject>(entry[2].ToString()); - var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, json["Name"]); - - var config = (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), Path.Combine(userDataDir, "config.xml")); - var policy = (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), Path.Combine(userDataDir, "policy.xml")); - - var user = new User( - json["Name"], - false, - policy.AuthenticatioIsnProviderId, - policy.InvalidLoginAttemptCount, - config.SubtitleMode, - config.PlayDefaultAudioTrack) + UserMockup mockup = JsonSerializer.Deserialize(entry[2].ToBlob()); + var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, mockup.Name); + + var config = File.Exists(Path.Combine(userDataDir, "config.xml")) + ? (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), Path.Combine(userDataDir, "config.xml")) + : new UserConfiguration(); + var policy = File.Exists(Path.Combine(userDataDir, "policy.xml")) + ? (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), Path.Combine(userDataDir, "policy.xml")) + : new UserPolicy(); + policy.AuthenticationProviderId = policy.AuthenticationProviderId?.Replace( + "Emby.Server.Implementations.Library", + "Jellyfin.Server.Implementations.Users", + StringComparison.Ordinal) + ?? typeof(DefaultAuthenticationProvider).FullName; + + policy.PasswordResetProviderId ??= typeof(DefaultPasswordResetProvider).FullName; + + var user = new User(mockup.Name, policy.AuthenticationProviderId) { Id = entry[1].ReadGuidFromBlob(), InternalId = entry[0].ToInt64(), MaxParentalAgeRating = policy.MaxParentalRating, EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess, RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit, - AuthenticationProviderId = policy.AuthenticatioIsnProviderId, PasswordResetProviderId = policy.PasswordResetProviderId, InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount, LoginAttemptsBeforeLockout = policy.LoginAttemptsBeforeLockout == -1 ? null : new int?(policy.LoginAttemptsBeforeLockout), @@ -89,6 +108,10 @@ namespace Jellyfin.Server.Migrations.Routines EnableNextEpisodeAutoPlay = config.EnableNextEpisodeAutoPlay, RememberSubtitleSelections = config.RememberSubtitleSelections, SubtitleLanguagePreference = config.SubtitleLanguagePreference, + Password = mockup.Password, + EasyPassword = mockup.EasyPassword, + LastLoginDate = mockup.LastLoginDate ?? DateTime.MinValue, + LastActivityDate = mockup.LastActivityDate ?? DateTime.MinValue }; user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator); @@ -112,6 +135,7 @@ namespace Jellyfin.Server.Migrations.Routines user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing); user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding); user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing); + foreach (var policyAccessSchedule in policy.AccessSchedules) { user.AccessSchedules.Add(policyAccessSchedule); @@ -126,6 +150,8 @@ namespace Jellyfin.Server.Migrations.Routines user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders); user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes); user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes); + + dbContext.Users.Add(user); } dbContext.SaveChanges(); @@ -133,12 +159,32 @@ namespace Jellyfin.Server.Migrations.Routines try { - File.Move(Path.Combine(dataPath, "users.db"), Path.Combine(dataPath, "users.db" + ".old")); + File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old")); + + var journalPath = Path.Combine(dataPath, DbFilename + "-journal"); + if (File.Exists(journalPath)) + { + File.Move(journalPath, Path.Combine(dataPath, DbFilename + ".old-journal")); + } } catch (IOException e) { _logger.LogError(e, "Error renaming legacy user database to 'users.db.old'"); } } + +#nullable disable + internal class UserMockup + { + public string Password { get; set; } + + public string EasyPassword { get; set; } + + public DateTime? LastLoginDate { get; set; } + + public DateTime? LastActivityDate { get; set; } + + public string Name { get; set; } + } } } diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index 8d94d39716..bc9724027d 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -1,6 +1,8 @@ #pragma warning disable CS1591 using System; +using System.Text.Json.Serialization; +using System.Xml.Serialization; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; @@ -34,7 +36,7 @@ namespace MediaBrowser.Model.Users public string[] BlockedTags { get; set; } public bool EnableUserPreferenceAccess { get; set; } - public Jellyfin.Data.Entities.AccessSchedule[] AccessSchedules { get; set; } + public AccessSchedule[] AccessSchedules { get; set; } public UnratedItem[] BlockUnratedItems { get; set; } public bool EnableRemoteControlOfOtherUsers { get; set; } public bool EnableSharedDeviceControl { get; set; } @@ -78,7 +80,9 @@ namespace MediaBrowser.Model.Users public string[] BlockedChannels { get; set; } public int RemoteClientBitrateLimit { get; set; } - public string AuthenticatioIsnProviderId { get; set; } + + [XmlElement(ElementName = "AuthenticationProviderId")] + public string AuthenticationProviderId { get; set; } public string PasswordResetProviderId { get; set; } public UserPolicy() -- cgit v1.2.3 From e42bfc92f3e072a3d51dddce06bc90587e06791c Mon Sep 17 00:00:00 2001 From: gion Date: Tue, 26 May 2020 11:37:52 +0200 Subject: Fix code issues --- .../HttpServer/WebSocketConnection.cs | 6 ++-- .../Session/SessionWebSocketListener.cs | 10 +++--- .../SyncPlay/SyncPlayController.cs | 37 ++-------------------- .../SyncPlay/SyncPlayManager.cs | 1 + MediaBrowser.Api/SyncPlay/SyncPlayService.cs | 4 +-- MediaBrowser.Controller/SyncPlay/GroupInfo.cs | 9 +++--- 6 files changed, 19 insertions(+), 48 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 1f5a7d1773..0680c5ffe7 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -223,7 +223,7 @@ namespace Emby.Server.Implementations.HttpServer if (info.MessageType.Equals("KeepAlive", StringComparison.Ordinal)) { - SendKeepAliveResponse(); + await SendKeepAliveResponse(); } else { @@ -231,10 +231,10 @@ namespace Emby.Server.Implementations.HttpServer } } - private void SendKeepAliveResponse() + private Task SendKeepAliveResponse() { LastKeepAliveDate = DateTime.UtcNow; - SendAsync(new WebSocketMessage + return SendAsync(new WebSocketMessage { MessageType = "KeepAlive" }, CancellationToken.None); diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 3af18f6813..e7b4b0ec35 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -87,13 +87,13 @@ namespace Emby.Server.Implementations.Session httpServer.WebSocketConnected += OnServerManagerWebSocketConnected; } - private void OnServerManagerWebSocketConnected(object sender, GenericEventArgs e) + private async void OnServerManagerWebSocketConnected(object sender, GenericEventArgs e) { var session = GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint.ToString()); if (session != null) { EnsureController(session, e.Argument); - KeepAliveWebSocket(e.Argument); + await KeepAliveWebSocket(e.Argument); } else { @@ -149,7 +149,7 @@ namespace Emby.Server.Implementations.Session /// The event arguments. private void OnWebSocketClosed(object sender, EventArgs e) { - var webSocket = (IWebSocketConnection) sender; + var webSocket = (IWebSocketConnection)sender; _logger.LogDebug("WebSocket {0} is closed.", webSocket); RemoveWebSocket(webSocket); } @@ -158,7 +158,7 @@ namespace Emby.Server.Implementations.Session /// Adds a WebSocket to the KeepAlive watchlist. /// /// The WebSocket to monitor. - private void KeepAliveWebSocket(IWebSocketConnection webSocket) + private async Task KeepAliveWebSocket(IWebSocketConnection webSocket) { lock (_webSocketsLock) { @@ -176,7 +176,7 @@ namespace Emby.Server.Implementations.Session // Notify WebSocket about timeout try { - SendForceKeepAlive(webSocket).Wait(); + await SendForceKeepAlive(webSocket); } catch (WebSocketException exception) { diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs index c7bd242a7c..d430d4d162 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.SyncPlay /// /// Class is not thread-safe, external locking is required when accessing methods. /// - public class SyncPlayController : ISyncPlayController, IDisposable + public class SyncPlayController : ISyncPlayController { /// /// Used to filter the sessions of a group. @@ -65,8 +65,6 @@ namespace Emby.Server.Implementations.SyncPlay /// public bool IsGroupEmpty() => _group.IsEmpty(); - private bool _disposed = false; - public SyncPlayController( ISessionManager sessionManager, ISyncPlayManager syncPlayManager) @@ -75,36 +73,6 @@ namespace Emby.Server.Implementations.SyncPlay _syncPlayManager = syncPlayManager; } - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and optionally managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - _disposed = true; - } - - // TODO: use this somewhere - private void CheckDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } - } - /// /// Converts DateTime to UTC string. /// @@ -518,6 +486,7 @@ namespace Emby.Server.Implementations.SyncPlay var runTimeTicks = _group.PlayingItem.RunTimeTicks ?? 0; ticks = ticks > runTimeTicks ? runTimeTicks : ticks; } + return ticks; } @@ -541,7 +510,7 @@ namespace Emby.Server.Implementations.SyncPlay PlayingItemName = _group.PlayingItem.Name, PlayingItemId = _group.PlayingItem.Id.ToString(), PositionTicks = _group.PositionTicks, - Participants = _group.Participants.Values.Select(session => session.Session.UserName).Distinct().ToList().AsReadOnly() + Participants = _group.Participants.Values.Select(session => session.Session.UserName).Distinct().ToList() }; } } diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index 93cec1304c..1f76dd4e36 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -374,6 +374,7 @@ namespace Emby.Server.Implementations.SyncPlay { throw new InvalidOperationException("Session in other group already!"); } + _sessionToGroupMap[session.Id] = group; } diff --git a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs index 8064ea7dc1..1e14ea552c 100644 --- a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs +++ b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs @@ -182,7 +182,7 @@ namespace MediaBrowser.Api.SyncPlay } // Both null and empty strings mean that client isn't playing anything - if (!String.IsNullOrEmpty(request.PlayingItemId) && !Guid.TryParse(request.PlayingItemId, out playingItemId)) + if (!string.IsNullOrEmpty(request.PlayingItemId) && !Guid.TryParse(request.PlayingItemId, out playingItemId)) { Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId); return; @@ -217,7 +217,7 @@ namespace MediaBrowser.Api.SyncPlay var currentSession = GetSession(_sessionContext); var filterItemId = Guid.Empty; - if (!String.IsNullOrEmpty(request.FilterItemId) && !Guid.TryParse(request.FilterItemId, out filterItemId)) + if (!string.IsNullOrEmpty(request.FilterItemId) && !Guid.TryParse(request.FilterItemId, out filterItemId)) { Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); } diff --git a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs index bda49bd1b0..28a3ac505f 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs @@ -16,12 +16,13 @@ namespace MediaBrowser.Controller.SyncPlay /// /// Default ping value used for sessions. /// - public readonly long DefaulPing = 500; + public long DefaulPing { get; } = 500; + /// /// Gets or sets the group identifier. /// /// The group identifier. - public readonly Guid GroupId = Guid.NewGuid(); + public Guid GroupId { get; } = Guid.NewGuid(); /// /// Gets or sets the playing item. @@ -51,7 +52,7 @@ namespace MediaBrowser.Controller.SyncPlay /// Gets the participants. /// /// The participants, or members of the group. - public readonly Dictionary Participants = + public Dictionary Participants { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); /// @@ -85,7 +86,6 @@ namespace MediaBrowser.Controller.SyncPlay /// Removes the session from the group. /// /// The session. - public void RemoveSession(SessionInfo session) { if (!ContainsSession(session.Id.ToString())) @@ -153,6 +153,7 @@ namespace MediaBrowser.Controller.SyncPlay return true; } } + return false; } -- cgit v1.2.3 From 8ca78f33e9769f823fe079e90b62b561646709d7 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 28 May 2020 14:21:26 -0400 Subject: Fix bug when migrating user db with users that have never logged in. --- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- .../SyncPlay/SyncPlayManager.cs | 18 ++++++------------ Jellyfin.Data/Entities/User.cs | 4 ++-- Jellyfin.Server.Implementations/Users/UserManager.cs | 2 +- Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs | 4 ++-- 5 files changed, 12 insertions(+), 18 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 70b3bda6ef..94423c2874 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -282,7 +282,7 @@ namespace Emby.Server.Implementations.Session if (user != null) { - var userLastActivityDate = user.LastActivityDate; + var userLastActivityDate = user.LastActivityDate ?? DateTime.MinValue; user.LastActivityDate = activityDate; if ((activityDate - userLastActivityDate).TotalSeconds > 60) diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index f44b32c365..8885266d3b 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -134,11 +134,8 @@ namespace Emby.Server.Implementations.SyncPlay var item = _libraryManager.GetItemById(itemId); // Check ParentalRating access - var hasParentalRatingAccess = true; - if (user.MaxParentalAgeRating.HasValue) - { - hasParentalRatingAccess = item.InheritedParentalRatingValue <= user.MaxParentalAgeRating.Value; - } + var hasParentalRatingAccess = !user.MaxParentalAgeRating.HasValue + || item.InheritedParentalRatingValue <= user.MaxParentalAgeRating; if (!user.HasPermission(PermissionKind.EnableAllFolders) && hasParentalRatingAccess) { @@ -255,8 +252,7 @@ namespace Emby.Server.Implementations.SyncPlay // TODO: determine what happens to users that are in a group and get their permissions revoked lock (_groupsLock) { - ISyncPlayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); + _sessionToGroupMap.TryGetValue(session.Id, out var group); if (group == null) { @@ -329,8 +325,7 @@ namespace Emby.Server.Implementations.SyncPlay lock (_groupsLock) { - ISyncPlayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); + _sessionToGroupMap.TryGetValue(session.Id, out var group); if (group == null) { @@ -340,7 +335,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.NotInGroup }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -367,8 +362,7 @@ namespace Emby.Server.Implementations.SyncPlay throw new InvalidOperationException("Session not in any group!"); } - ISyncPlayController tempGroup; - _sessionToGroupMap.Remove(session.Id, out tempGroup); + _sessionToGroupMap.Remove(session.Id, out var tempGroup); if (!tempGroup.GetGroupId().Equals(group.GetGroupId())) { diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index cef2edfa97..1098cdb2f5 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -181,12 +181,12 @@ namespace Jellyfin.Data.Entities /// /// Gets or sets the last activity date. /// - public DateTime LastActivityDate { get; set; } + public DateTime? LastActivityDate { get; set; } /// /// Gets or sets the last login date. /// - public DateTime LastLoginDate { get; set; } + public DateTime? LastLoginDate { get; set; } /// /// Gets or sets the number of login attempts the user can make before they are locked out. diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 60d78afd02..3d473f5f21 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -218,7 +218,7 @@ namespace Jellyfin.Server.Implementations.Users var dbContext = _dbProvider.CreateContext(); - if (!dbContext.Users.Contains(user)) + if (dbContext.Users.Find(user.Id) == null) { throw new ArgumentException(string.Format( CultureInfo.InvariantCulture, diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index 53c93f64b5..2be10c7087 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -196,9 +196,9 @@ namespace Jellyfin.Server.Migrations.Routines public string EasyPassword { get; set; } - public DateTime LastLoginDate { get; set; } + public DateTime? LastLoginDate { get; set; } - public DateTime LastActivityDate { get; set; } + public DateTime? LastActivityDate { get; set; } public string Name { get; set; } -- cgit v1.2.3 From 4748105dce13c0fe0b4d8fcbf44f26033d314b26 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 29 May 2020 11:28:19 +0200 Subject: Enable TreatWarningsAsErrors for Jellyfin.Server.Implementations in Release mode --- .../Channels/ChannelManager.cs | 4 +- .../Data/SqliteItemRepository.cs | 5 +- .../Emby.Server.Implementations.csproj | 1 + .../EntryPoints/UdpServerEntryPoint.cs | 3 +- .../HttpServer/HttpResultFactory.cs | 1 + Emby.Server.Implementations/IStartupOptions.cs | 2 + .../Library/DefaultAuthenticationProvider.cs | 38 ---- .../Library/LibraryManager.cs | 8 +- .../LiveTv/LiveTvManager.cs | 2 - .../LiveTv/RefreshChannelsScheduledTask.cs | 2 + .../MediaEncoder/EncodingManager.cs | 2 + Emby.Server.Implementations/Net/SocketFactory.cs | 2 + Emby.Server.Implementations/Net/UdpSocket.cs | 2 + .../Networking/NetworkManager.cs | 2 + .../Playlists/ManualPlaylistsFolder.cs | 2 + .../Playlists/PlaylistImageProvider.cs | 6 +- .../Playlists/PlaylistManager.cs | 2 + Emby.Server.Implementations/ResourceFileManager.cs | 2 + .../ScheduledTasks/ScheduledTaskWorker.cs | 11 +- .../ScheduledTasks/TaskManager.cs | 4 +- .../ScheduledTasks/Tasks/ChapterImagesTask.cs | 7 + .../ScheduledTasks/Tasks/DeleteCacheFileTask.cs | 7 + .../ScheduledTasks/Tasks/DeleteLogFileTask.cs | 9 + .../Tasks/DeleteTranscodeFileTask.cs | 7 + .../ScheduledTasks/Tasks/PeopleValidationTask.cs | 10 +- .../ScheduledTasks/Tasks/PluginUpdateTask.cs | 2 + .../Tasks/RefreshMediaLibraryTask.cs | 17 +- .../ScheduledTasks/Triggers/DailyTrigger.cs | 7 +- .../ScheduledTasks/Triggers/IntervalTrigger.cs | 2 + .../ScheduledTasks/Triggers/StartupTrigger.cs | 6 +- .../ScheduledTasks/Triggers/WeeklyTrigger.cs | 2 + .../Security/AuthenticationRepository.cs | 2 + .../Serialization/JsonSerializer.cs | 5 + Emby.Server.Implementations/Services/HttpResult.cs | 2 + .../Services/RequestHelper.cs | 7 +- .../Services/ResponseHelper.cs | 5 +- .../Services/ServiceController.cs | 2 + .../Services/ServiceExec.cs | 2 + .../Services/ServiceHandler.cs | 2 + .../Services/ServiceMethod.cs | 2 + .../Services/ServicePath.cs | 2 + .../Services/StringMapTypeDeserializer.cs | 2 + .../Services/SwaggerService.cs | 2 + .../Services/UrlExtensions.cs | 2 + .../Session/SessionManager.cs | 2 + .../SocketSharp/HttpFile.cs | 18 -- .../SocketSharp/HttpPostedFile.cs | 198 --------------------- .../SocketSharp/WebSocketSharpRequest.cs | 2 + .../Sorting/AiredEpisodeOrderComparer.cs | 2 + .../Sorting/CommunityRatingComparer.cs | 18 +- .../Sorting/DateLastMediaAddedComparer.cs | 18 +- .../Sorting/IsFavoriteOrLikeComparer.cs | 38 ++-- .../Sorting/IsFolderComparer.cs | 14 +- .../Sorting/IsPlayedComparer.cs | 38 ++-- .../Sorting/IsUnplayedComparer.cs | 38 ++-- .../Sorting/OfficialRatingComparer.cs | 14 +- .../Sorting/SeriesSortNameComparer.cs | 14 +- .../Sorting/StartDateComparer.cs | 19 +- .../Sorting/StudioComparer.cs | 2 + .../SyncPlay/SyncPlayController.cs | 5 + .../SyncPlay/SyncPlayManager.cs | 7 + Emby.Server.Implementations/TV/TVSeriesManager.cs | 7 +- .../Updates/InstallationManager.cs | 4 +- .../UserViews/CollectionFolderImageProvider.cs | 2 + .../UserViews/DynamicImageProvider.cs | 6 +- .../UserViews/FolderImageProvider.cs | 6 +- Jellyfin.Server/Migrations/IMigrationRoutine.cs | 1 - .../Routines/CreateUserLoggingConfigFile.cs | 1 - Jellyfin.Server/Program.cs | 4 +- 69 files changed, 291 insertions(+), 401 deletions(-) delete mode 100644 Emby.Server.Implementations/SocketSharp/HttpFile.cs delete mode 100644 Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 138832fb86..04fe0bacb4 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -46,14 +46,14 @@ namespace Emby.Server.Implementations.Channels new ConcurrentDictionary>>(); private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1); - + /// /// Initializes a new instance of the class. /// /// The user manager. /// The dto service. /// The library manager. - /// The logger factory. + /// The logger. /// The server configuration manager. /// The filesystem. /// The user data manager. diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index ca5cd6fdd5..58702541e0 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; @@ -33,7 +35,7 @@ using SQLitePCL.pretty; namespace Emby.Server.Implementations.Data { /// - /// Class SQLiteItemRepository + /// Class SQLiteItemRepository. /// public class SqliteItemRepository : BaseSqliteRepository, IItemRepository { @@ -1971,6 +1973,7 @@ namespace Emby.Server.Implementations.Data /// Gets the chapter. /// /// The reader. + /// The item. /// ChapterInfo. private ChapterInfo GetChapter(IReadOnlyList reader, BaseItem item) { diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index b69a126b3a..279ec30986 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -54,6 +54,7 @@ netstandard2.1 false true + true diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index 6929c81f92..5bc1a81aa6 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -47,10 +47,11 @@ namespace Emby.Server.Implementations.EntryPoints } /// - public async Task RunAsync() + public Task RunAsync() { _udpServer = new UdpServer(_logger, _appHost, _config); _udpServer.Start(PortNumber, _cancellationTokenSource.Token); + return Task.CompletedTask; } /// diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 2e9ecc4ae6..dd7f753cc3 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -56,6 +56,7 @@ namespace Emby.Server.Implementations.HttpServer /// /// Gets the result. /// + /// The request context. /// The content. /// Type of the content. /// The response headers. diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index acae702f30..0b9f805389 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; namespace Emby.Server.Implementations diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 52c8facc3e..02f1506076 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -135,43 +135,5 @@ namespace Emby.Server.Implementations.Library ? null : Hex.Encode(PasswordHash.Parse(user.EasyPassword).Hash); } - - /// - /// Gets the hashed string. - /// - public string GetHashedString(User user, string str) - { - if (string.IsNullOrEmpty(user.Password)) - { - return _cryptographyProvider.CreatePasswordHash(str).ToString(); - } - - // TODO: make use of iterations parameter? - PasswordHash passwordHash = PasswordHash.Parse(user.Password); - var salt = passwordHash.Salt.ToArray(); - return new PasswordHash( - passwordHash.Id, - _cryptographyProvider.ComputeHash( - passwordHash.Id, - Encoding.UTF8.GetBytes(str), - salt), - salt, - passwordHash.Parameters.ToDictionary(x => x.Key, y => y.Value)).ToString(); - } - - public ReadOnlySpan GetHashed(User user, string str) - { - if (string.IsNullOrEmpty(user.Password)) - { - return _cryptographyProvider.CreatePasswordHash(str).Hash; - } - - // TODO: make use of iterations parameter? - PasswordHash passwordHash = PasswordHash.Parse(user.Password); - return _cryptographyProvider.ComputeHash( - passwordHash.Id, - Encoding.UTF8.GetBytes(str), - passwordHash.Salt.ToArray()); - } } } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 0b86b2db7e..67a72d3137 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -50,7 +50,7 @@ using VideoResolver = Emby.Naming.Video.VideoResolver; namespace Emby.Server.Implementations.Library { /// - /// Class LibraryManager + /// Class LibraryManager. /// public class LibraryManager : ILibraryManager { @@ -135,6 +135,12 @@ namespace Emby.Server.Implementations.Library /// The user manager. /// The configuration manager. /// The user data repository. + /// The library monitor. + /// The file system. + /// The provider manager. + /// The userview manager. + /// The media encoder. + /// The item repository. public LibraryManager( IServerApplicationHost appHost, ILogger logger, diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 1b10f2d27c..3e48425a24 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -10,10 +10,8 @@ using Emby.Server.Implementations.Library; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; -using MediaBrowser.Controller; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; diff --git a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs index 1056a33b9a..8e7d60a15a 100644 --- a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs +++ b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index 677d68b4c9..7b7575707a 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs index e42ff8496e..f347540c77 100644 --- a/Emby.Server.Implementations/Net/SocketFactory.cs +++ b/Emby.Server.Implementations/Net/SocketFactory.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Net; using System.Net.Sockets; diff --git a/Emby.Server.Implementations/Net/UdpSocket.cs b/Emby.Server.Implementations/Net/UdpSocket.cs index 211ca67841..848f82d855 100644 --- a/Emby.Server.Implementations/Net/UdpSocket.cs +++ b/Emby.Server.Implementations/Net/UdpSocket.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Net; using System.Net.Sockets; diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index b3e88b6675..d1a28e7a19 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; diff --git a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs index cd9f7946e9..889760586c 100644 --- a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs +++ b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; diff --git a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs index bb56d9771b..f8a2d97417 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System.Collections.Generic; using System.Linq; using Emby.Server.Implementations.Images; @@ -32,9 +34,7 @@ namespace Emby.Server.Implementations.Playlists { var subItem = i.Item2; - var episode = subItem as Episode; - - if (episode != null) + if (subItem is Episode episode) { var series = episode.Series; if (series != null && series.HasImage(ImageType.Primary)) diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 9b1510ac97..d4d1c1ff7b 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; diff --git a/Emby.Server.Implementations/ResourceFileManager.cs b/Emby.Server.Implementations/ResourceFileManager.cs index 6eda2b5032..d192be921f 100644 --- a/Emby.Server.Implementations/ResourceFileManager.cs +++ b/Emby.Server.Implementations/ResourceFileManager.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.IO; using MediaBrowser.Controller; diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 5b188d9626..dc3e9a6070 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Globalization; using System.IO; @@ -51,7 +53,6 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// The task manager. private ITaskManager TaskManager { get; set; } - private readonly IFileSystem _fileSystem; /// /// Initializes a new instance of the class. @@ -72,24 +73,28 @@ namespace Emby.Server.Implementations.ScheduledTasks /// or /// logger /// - public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, IJsonSerializer jsonSerializer, ILogger logger, IFileSystem fileSystem) + public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, IJsonSerializer jsonSerializer, ILogger logger) { if (scheduledTask == null) { throw new ArgumentNullException(nameof(scheduledTask)); } + if (applicationPaths == null) { throw new ArgumentNullException(nameof(applicationPaths)); } + if (taskManager == null) { throw new ArgumentNullException(nameof(taskManager)); } + if (jsonSerializer == null) { throw new ArgumentNullException(nameof(jsonSerializer)); } + if (logger == null) { throw new ArgumentNullException(nameof(logger)); @@ -100,7 +105,6 @@ namespace Emby.Server.Implementations.ScheduledTasks TaskManager = taskManager; JsonSerializer = jsonSerializer; Logger = logger; - _fileSystem = fileSystem; InitTriggerEvents(); } @@ -576,6 +580,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// The start time. /// The end time. /// The status. + /// The exception. private void OnTaskCompleted(DateTime startTime, DateTime endTime, TaskCompletionStatus status, Exception ex) { var elapsedTime = endTime - startTime; diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index 6ffa581a9c..9076802393 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -199,7 +201,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// The tasks. public void AddTasks(IEnumerable tasks) { - var list = tasks.Select(t => new ScheduledTaskWorker(t, _applicationPaths, this, _jsonSerializer, _logger, _fileSystem)); + var list = tasks.Select(t => new ScheduledTaskWorker(t, _applicationPaths, this, _jsonSerializer, _logger)); ScheduledTasks = ScheduledTasks.Concat(list).ToArray(); } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index ea6a70615b..fae0499148 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -169,18 +169,25 @@ namespace Emby.Server.Implementations.ScheduledTasks } } + /// public string Name => _localization.GetLocalizedString("TaskRefreshChapterImages"); + /// public string Description => _localization.GetLocalizedString("TaskRefreshChapterImagesDescription"); + /// public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); + /// public string Key => "RefreshChapterImages"; + /// public bool IsHidden => false; + /// public bool IsEnabled => true; + /// public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs index 9df7c538b1..a6c13eaefe 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs @@ -165,18 +165,25 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks } } + /// public string Name => _localization.GetLocalizedString("TaskCleanCache"); + /// public string Description => _localization.GetLocalizedString("TaskCleanCacheDescription"); + /// public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + /// public string Key => "DeleteCacheFiles"; + /// public bool IsHidden => false; + /// public bool IsEnabled => true; + /// public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs index 3140aa4893..402b39a263 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs @@ -28,6 +28,8 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// Initializes a new instance of the class. /// /// The configuration manager. + /// The file system. + /// The localization manager. public DeleteLogFileTask(IConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization) { ConfigurationManager = configurationManager; @@ -82,18 +84,25 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks return Task.CompletedTask; } + /// public string Name => _localization.GetLocalizedString("TaskCleanLogs"); + /// public string Description => string.Format(_localization.GetLocalizedString("TaskCleanLogsDescription"), ConfigurationManager.CommonConfiguration.LogFileRetentionDays); + /// public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + /// public string Key => "CleanLogFiles"; + /// public bool IsHidden => false; + /// public bool IsEnabled => true; + /// public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs index 1d133dcda8..0d36b82c09 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs @@ -132,18 +132,25 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks } } + /// public string Name => _localization.GetLocalizedString("TaskCleanTranscode"); + /// public string Description => _localization.GetLocalizedString("TaskCleanTranscodeDescription"); + /// public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + /// public string Key => "DeleteTranscodeFiles"; + /// public bool IsHidden => false; + /// public bool IsEnabled => false; + /// public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs index 63f867bf6c..c384cf4bbe 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs @@ -1,8 +1,9 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Controller; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Globalization; @@ -18,19 +19,16 @@ namespace Emby.Server.Implementations.ScheduledTasks /// The library manager. /// private readonly ILibraryManager _libraryManager; - - private readonly IServerApplicationHost _appHost; private readonly ILocalizationManager _localization; /// /// Initializes a new instance of the class. /// /// The library manager. - /// The server application host - public PeopleValidationTask(ILibraryManager libraryManager, IServerApplicationHost appHost, ILocalizationManager localization) + /// The localization manager. + public PeopleValidationTask(ILibraryManager libraryManager, ILocalizationManager localization) { _libraryManager = libraryManager; - _appHost = appHost; _localization = localization; } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs index 6a1afced79..9d9d77538e 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.IO; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs index 74cb01444e..e470adcf48 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs @@ -1,9 +1,10 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Library; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Globalization; @@ -16,20 +17,19 @@ namespace Emby.Server.Implementations.ScheduledTasks public class RefreshMediaLibraryTask : IScheduledTask { /// - /// The _library manager + /// The _library manager. /// private readonly ILibraryManager _libraryManager; - private readonly IServerConfigurationManager _config; private readonly ILocalizationManager _localization; /// /// Initializes a new instance of the class. /// /// The library manager. - public RefreshMediaLibraryTask(ILibraryManager libraryManager, IServerConfigurationManager config, ILocalizationManager localization) + /// The localization manager. + public RefreshMediaLibraryTask(ILibraryManager libraryManager, ILocalizationManager localization) { _libraryManager = libraryManager; - _config = config; _localization = localization; } @@ -61,18 +61,25 @@ namespace Emby.Server.Implementations.ScheduledTasks return ((LibraryManager)_libraryManager).ValidateMediaLibraryInternal(progress, cancellationToken); } + /// public string Name => _localization.GetLocalizedString("TaskRefreshLibrary"); + /// public string Description => _localization.GetLocalizedString("TaskRefreshLibraryDescription"); + /// public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); + /// public string Key => "RefreshLibrary"; + /// public bool IsHidden => false; + /// public bool IsEnabled => true; + /// public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs index ea278de0d9..c7819d4c0f 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs @@ -31,6 +31,8 @@ namespace Emby.Server.Implementations.ScheduledTasks /// Stars waiting for the trigger action /// /// The last result. + /// The logger. + /// The name of the task. /// if set to true [is application startup]. public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup) { @@ -77,10 +79,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// private void OnTriggered() { - if (Triggered != null) - { - Triggered(this, EventArgs.Empty); - } + Triggered?.Invoke(this, EventArgs.Empty); } } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs index 3a34da3af2..74cd4ef1ea 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs @@ -34,6 +34,8 @@ namespace Emby.Server.Implementations.ScheduledTasks /// Stars waiting for the trigger action /// /// The last result. + /// The logger. + /// The name of the task. /// if set to true [is application startup]. public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup) { diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs index 08ff4f55f7..e171a9e9f5 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Threading.Tasks; using MediaBrowser.Model.Tasks; @@ -6,7 +8,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks { /// - /// Class StartupTaskTrigger + /// Class StartupTaskTrigger. /// public class StartupTrigger : ITaskTrigger { @@ -26,6 +28,8 @@ namespace Emby.Server.Implementations.ScheduledTasks /// Stars waiting for the trigger action /// /// The last result. + /// The logger. + /// The name of the task. /// if set to true [is application startup]. public async void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup) { diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs index 2a6a7b13cd..ad0b57af6e 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs @@ -37,6 +37,8 @@ namespace Emby.Server.Implementations.ScheduledTasks /// Stars waiting for the trigger action /// /// The last result. + /// The logger. + /// The name of the task. /// if set to true [is application startup]. public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup) { diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 4e4029f06f..750890ec8f 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; diff --git a/Emby.Server.Implementations/Serialization/JsonSerializer.cs b/Emby.Server.Implementations/Serialization/JsonSerializer.cs index bcc814daf2..5ec3a735a6 100644 --- a/Emby.Server.Implementations/Serialization/JsonSerializer.cs +++ b/Emby.Server.Implementations/Serialization/JsonSerializer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Globalization; using System.IO; @@ -11,6 +13,9 @@ namespace Emby.Server.Implementations.Serialization /// public class JsonSerializer : IJsonSerializer { + /// + /// Initializes a new instance of the class. + /// public JsonSerializer() { ServiceStack.Text.JsConfig.DateHandler = ServiceStack.Text.DateHandler.ISO8601; diff --git a/Emby.Server.Implementations/Services/HttpResult.cs b/Emby.Server.Implementations/Services/HttpResult.cs index 095193828b..8ba86f756d 100644 --- a/Emby.Server.Implementations/Services/HttpResult.cs +++ b/Emby.Server.Implementations/Services/HttpResult.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System.Collections.Generic; using System.IO; using System.Net; diff --git a/Emby.Server.Implementations/Services/RequestHelper.cs b/Emby.Server.Implementations/Services/RequestHelper.cs index 2563cac999..1f9c7fc223 100644 --- a/Emby.Server.Implementations/Services/RequestHelper.cs +++ b/Emby.Server.Implementations/Services/RequestHelper.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.IO; using System.Threading.Tasks; @@ -43,10 +45,7 @@ namespace Emby.Server.Implementations.Services private static string GetContentTypeWithoutEncoding(string contentType) { - return contentType == null - ? null - : contentType.Split(';')[0].ToLowerInvariant().Trim(); + return contentType?.Split(';')[0].ToLowerInvariant().Trim(); } - } } diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs index a566b18dd0..f2b1d06f3a 100644 --- a/Emby.Server.Implementations/Services/ResponseHelper.cs +++ b/Emby.Server.Implementations/Services/ResponseHelper.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Globalization; using System.IO; @@ -43,8 +45,7 @@ namespace Emby.Server.Implementations.Services response.StatusCode = httpResult.Status; } - var responseOptions = result as IHasHeaders; - if (responseOptions != null) + if (result is IHasHeaders responseOptions) { foreach (var responseHeaders in responseOptions.Headers) { diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs index e24a95dbb3..ad6015c1cd 100644 --- a/Emby.Server.Implementations/Services/ServiceController.cs +++ b/Emby.Server.Implementations/Services/ServiceController.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/Emby.Server.Implementations/Services/ServiceExec.cs b/Emby.Server.Implementations/Services/ServiceExec.cs index 9f5f97028c..606f2a2405 100644 --- a/Emby.Server.Implementations/Services/ServiceExec.cs +++ b/Emby.Server.Implementations/Services/ServiceExec.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Linq; diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs index 934560de3c..7f44357e1b 100644 --- a/Emby.Server.Implementations/Services/ServiceHandler.cs +++ b/Emby.Server.Implementations/Services/ServiceHandler.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Reflection; diff --git a/Emby.Server.Implementations/Services/ServiceMethod.cs b/Emby.Server.Implementations/Services/ServiceMethod.cs index 5018bf4a2e..59ee5908fd 100644 --- a/Emby.Server.Implementations/Services/ServiceMethod.cs +++ b/Emby.Server.Implementations/Services/ServiceMethod.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; namespace Emby.Server.Implementations.Services diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs index 27c4dcba07..278379a921 100644 --- a/Emby.Server.Implementations/Services/ServicePath.cs +++ b/Emby.Server.Implementations/Services/ServicePath.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs index 56e23d5492..ab22fe019a 100644 --- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs +++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Reflection; diff --git a/Emby.Server.Implementations/Services/SwaggerService.cs b/Emby.Server.Implementations/Services/SwaggerService.cs index 5177251c3e..16142a70df 100644 --- a/Emby.Server.Implementations/Services/SwaggerService.cs +++ b/Emby.Server.Implementations/Services/SwaggerService.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Linq; diff --git a/Emby.Server.Implementations/Services/UrlExtensions.cs b/Emby.Server.Implementations/Services/UrlExtensions.cs index 483c63ade7..e3b6aa1975 100644 --- a/Emby.Server.Implementations/Services/UrlExtensions.cs +++ b/Emby.Server.Implementations/Services/UrlExtensions.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using MediaBrowser.Common.Extensions; diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 2b09a93efc..5c480e8424 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Concurrent; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/SocketSharp/HttpFile.cs b/Emby.Server.Implementations/SocketSharp/HttpFile.cs deleted file mode 100644 index 120ac50d9c..0000000000 --- a/Emby.Server.Implementations/SocketSharp/HttpFile.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.IO; -using MediaBrowser.Model.Services; - -namespace Emby.Server.Implementations.SocketSharp -{ - public class HttpFile : IHttpFile - { - public string Name { get; set; } - - public string FileName { get; set; } - - public long ContentLength { get; set; } - - public string ContentType { get; set; } - - public Stream InputStream { get; set; } - } -} diff --git a/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs b/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs deleted file mode 100644 index 7479d81045..0000000000 --- a/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs +++ /dev/null @@ -1,198 +0,0 @@ -using System; -using System.IO; - -public sealed class HttpPostedFile : IDisposable -{ - private string _name; - private string _contentType; - private Stream _stream; - private bool _disposed = false; - - internal HttpPostedFile(string name, string content_type, Stream base_stream, long offset, long length) - { - _name = name; - _contentType = content_type; - _stream = new ReadSubStream(base_stream, offset, length); - } - - public string ContentType => _contentType; - - public int ContentLength => (int)_stream.Length; - - public string FileName => _name; - - public Stream InputStream => _stream; - - /// - /// Releases the unmanaged resources and disposes of the managed resources used. - /// - public void Dispose() - { - if (_disposed) - { - return; - } - - _stream.Dispose(); - _stream = null; - - _name = null; - _contentType = null; - - _disposed = true; - } - - private class ReadSubStream : Stream - { - private Stream _stream; - private long _offset; - private long _end; - private long _position; - - public ReadSubStream(Stream s, long offset, long length) - { - _stream = s; - _offset = offset; - _end = offset + length; - _position = offset; - } - - public override bool CanRead => true; - - public override bool CanSeek => true; - - public override bool CanWrite => false; - - public override long Length => _end - _offset; - - public override long Position - { - get => _position - _offset; - set - { - if (value > Length) - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - - _position = Seek(value, SeekOrigin.Begin); - } - } - - public override void Flush() - { - } - - public override int Read(byte[] buffer, int dest_offset, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (dest_offset < 0) - { - throw new ArgumentOutOfRangeException(nameof(dest_offset), "< 0"); - } - - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), "< 0"); - } - - int len = buffer.Length; - if (dest_offset > len) - { - throw new ArgumentException("destination offset is beyond array size", nameof(dest_offset)); - } - - // reordered to avoid possible integer overflow - if (dest_offset > len - count) - { - throw new ArgumentException("Reading would overrun buffer", nameof(count)); - } - - if (count > _end - _position) - { - count = (int)(_end - _position); - } - - if (count <= 0) - { - return 0; - } - - _stream.Position = _position; - int result = _stream.Read(buffer, dest_offset, count); - if (result > 0) - { - _position += result; - } - else - { - _position = _end; - } - - return result; - } - - public override int ReadByte() - { - if (_position >= _end) - { - return -1; - } - - _stream.Position = _position; - int result = _stream.ReadByte(); - if (result < 0) - { - _position = _end; - } - else - { - _position++; - } - - return result; - } - - public override long Seek(long d, SeekOrigin origin) - { - long real; - switch (origin) - { - case SeekOrigin.Begin: - real = _offset + d; - break; - case SeekOrigin.End: - real = _end + d; - break; - case SeekOrigin.Current: - real = _position + d; - break; - default: - throw new ArgumentException("Unknown SeekOrigin value", nameof(origin)); - } - - long virt = real - _offset; - if (virt < 0 || virt > Length) - { - throw new ArgumentException("Invalid position", nameof(d)); - } - - _position = _stream.Seek(real, SeekOrigin.Begin); - return _position; - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } - } -} diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index ee5131c1ff..146c84d7cb 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.IO; diff --git a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs index 16507466f9..67e31f7f63 100644 --- a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs +++ b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; diff --git a/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs b/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs index 87d3ae2d6d..980954ba03 100644 --- a/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Sorting; @@ -7,6 +9,12 @@ namespace Emby.Server.Implementations.Sorting { public class CommunityRatingComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.CommunityRating; + /// /// Compares the specified x. /// @@ -16,18 +24,16 @@ namespace Emby.Server.Implementations.Sorting public int Compare(BaseItem x, BaseItem y) { if (x == null) + { throw new ArgumentNullException(nameof(x)); + } if (y == null) + { throw new ArgumentNullException(nameof(y)); + } return (x.CommunityRating ?? 0).CompareTo(y.CommunityRating ?? 0); } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.CommunityRating; } } diff --git a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs index 623675157d..5c1503ed2b 100644 --- a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -26,6 +28,12 @@ namespace Emby.Server.Implementations.Sorting /// The user data repository. public IUserDataManager UserDataRepository { get; set; } + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.DateLastContentAdded; + /// /// Compares the specified x. /// @@ -44,9 +52,7 @@ namespace Emby.Server.Implementations.Sorting /// DateTime. private static DateTime GetDate(BaseItem x) { - var folder = x as Folder; - - if (folder != null) + if (x is Folder folder) { if (folder.DateLastMediaAdded.HasValue) { @@ -56,11 +62,5 @@ namespace Emby.Server.Implementations.Sorting return DateTime.MinValue; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.DateLastContentAdded; } } diff --git a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs index 66de05a6a2..aba14c6ca0 100644 --- a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; @@ -13,6 +15,24 @@ namespace Emby.Server.Implementations.Sorting /// The user. public User User { get; set; } + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.IsFavoriteOrLiked; + + /// + /// Gets or sets the user data repository. + /// + /// The user data repository. + public IUserDataManager UserDataRepository { get; set; } + + /// + /// Gets or sets the user manager. + /// + /// The user manager. + public IUserManager UserManager { get; set; } + /// /// Compares the specified x. /// @@ -33,23 +53,5 @@ namespace Emby.Server.Implementations.Sorting { return x.IsFavoriteOrLiked(User) ? 0 : 1; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.IsFavoriteOrLiked; - - /// - /// Gets or sets the user data repository. - /// - /// The user data repository. - public IUserDataManager UserDataRepository { get; set; } - - /// - /// Gets or sets the user manager. - /// - /// The user manager. - public IUserManager UserManager { get; set; } } } diff --git a/Emby.Server.Implementations/Sorting/IsFolderComparer.cs b/Emby.Server.Implementations/Sorting/IsFolderComparer.cs index dfaa144cdc..a35192eff8 100644 --- a/Emby.Server.Implementations/Sorting/IsFolderComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsFolderComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Querying; @@ -6,6 +8,12 @@ namespace Emby.Server.Implementations.Sorting { public class IsFolderComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.IsFolder; + /// /// Compares the specified x. /// @@ -26,11 +34,5 @@ namespace Emby.Server.Implementations.Sorting { return x.IsFolder ? 0 : 1; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.IsFolder; } } diff --git a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs index da3f3dd25b..39d9bc68ea 100644 --- a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; @@ -13,6 +15,24 @@ namespace Emby.Server.Implementations.Sorting /// The user. public User User { get; set; } + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.IsUnplayed; + + /// + /// Gets or sets the user data repository. + /// + /// The user data repository. + public IUserDataManager UserDataRepository { get; set; } + + /// + /// Gets or sets the user manager. + /// + /// The user manager. + public IUserManager UserManager { get; set; } + /// /// Compares the specified x. /// @@ -33,23 +53,5 @@ namespace Emby.Server.Implementations.Sorting { return x.IsPlayed(User) ? 0 : 1; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.IsUnplayed; - - /// - /// Gets or sets the user data repository. - /// - /// The user data repository. - public IUserDataManager UserDataRepository { get; set; } - - /// - /// Gets or sets the user manager. - /// - /// The user manager. - public IUserManager UserManager { get; set; } } } diff --git a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs index d99d0eff21..478df4035f 100644 --- a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; @@ -13,6 +15,24 @@ namespace Emby.Server.Implementations.Sorting /// The user. public User User { get; set; } + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.IsUnplayed; + + /// + /// Gets or sets the user data repository. + /// + /// The user data repository. + public IUserDataManager UserDataRepository { get; set; } + + /// + /// Gets or sets the user manager. + /// + /// The user manager. + public IUserManager UserManager { get; set; } + /// /// Compares the specified x. /// @@ -33,23 +53,5 @@ namespace Emby.Server.Implementations.Sorting { return x.IsUnplayed(User) ? 0 : 1; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.IsUnplayed; - - /// - /// Gets or sets the user data repository. - /// - /// The user data repository. - public IUserDataManager UserDataRepository { get; set; } - - /// - /// Gets or sets the user manager. - /// - /// The user manager. - public IUserManager UserManager { get; set; } } } diff --git a/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs index 7afbd9ff7d..76bb798b5f 100644 --- a/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Sorting; @@ -15,6 +17,12 @@ namespace Emby.Server.Implementations.Sorting _localization = localization; } + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.OfficialRating; + /// /// Compares the specified x. /// @@ -38,11 +46,5 @@ namespace Emby.Server.Implementations.Sorting return levelX.CompareTo(levelY); } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.OfficialRating; } } diff --git a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs index 504b6d2838..b9205ee076 100644 --- a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Sorting; @@ -7,6 +9,12 @@ namespace Emby.Server.Implementations.Sorting { public class SeriesSortNameComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.SeriesSortName; + /// /// Compares the specified x. /// @@ -18,12 +26,6 @@ namespace Emby.Server.Implementations.Sorting return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); } - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.SeriesSortName; - private static string GetValue(BaseItem item) { var hasSeries = item as IHasSeries; diff --git a/Emby.Server.Implementations/Sorting/StartDateComparer.cs b/Emby.Server.Implementations/Sorting/StartDateComparer.cs index aa040fa15e..558a3d3513 100644 --- a/Emby.Server.Implementations/Sorting/StartDateComparer.cs +++ b/Emby.Server.Implementations/Sorting/StartDateComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.LiveTv; @@ -8,6 +10,12 @@ namespace Emby.Server.Implementations.Sorting { public class StartDateComparer : IBaseItemComparer { + /// + /// Gets the name. + /// + /// The name. + public string Name => ItemSortBy.StartDate; + /// /// Compares the specified x. /// @@ -26,19 +34,12 @@ namespace Emby.Server.Implementations.Sorting /// DateTime. private static DateTime GetDate(BaseItem x) { - var hasStartDate = x as LiveTvProgram; - - if (hasStartDate != null) + if (x is LiveTvProgram hasStartDate) { return hasStartDate.StartDate; } + return DateTime.MinValue; } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.StartDate; } } diff --git a/Emby.Server.Implementations/Sorting/StudioComparer.cs b/Emby.Server.Implementations/Sorting/StudioComparer.cs index c9ac765c10..5766dc542b 100644 --- a/Emby.Server.Implementations/Sorting/StudioComparer.cs +++ b/Emby.Server.Implementations/Sorting/StudioComparer.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Linq; using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs index d430d4d162..d0812a13f6 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs @@ -65,6 +65,11 @@ namespace Emby.Server.Implementations.SyncPlay /// public bool IsGroupEmpty() => _group.IsEmpty(); + /// + /// Initializes a new instance of the class. + /// + /// The session manager. + /// The SyncPlay manager. public SyncPlayController( ISessionManager sessionManager, ISyncPlayManager syncPlayManager) diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index 1f76dd4e36..129262e53c 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -57,6 +57,13 @@ namespace Emby.Server.Implementations.SyncPlay private bool _disposed = false; + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The user manager. + /// The session manager. + /// The library manager. public SyncPlayManager( ILogger logger, IUserManager userManager, diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index 4c2f24e6f2..383615f748 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -1,8 +1,9 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Globalization; using System.Linq; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; @@ -18,14 +19,12 @@ namespace Emby.Server.Implementations.TV private readonly IUserManager _userManager; private readonly IUserDataManager _userDataManager; private readonly ILibraryManager _libraryManager; - private readonly IServerConfigurationManager _config; - public TVSeriesManager(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager, IServerConfigurationManager config) + public TVSeriesManager(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager) { _userManager = userManager; _userDataManager = userDataManager; _libraryManager = libraryManager; - _config = config; } public QueryResult GetNextUp(NextUpQuery request, DtoOptions dtoOptions) diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 0b2309889f..4f6a84ef7f 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -1,11 +1,11 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Net; using System.Net.Http; -using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Security.Cryptography; using System.Threading; diff --git a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs index a3f3f6cb4d..7b7d66ca6c 100644 --- a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.IO; diff --git a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs index 78ac95f85e..e7888595fc 100644 --- a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.IO; @@ -19,13 +21,11 @@ namespace Emby.Server.Implementations.UserViews public class DynamicImageProvider : BaseDynamicImageProvider { private readonly IUserManager _userManager; - private readonly ILibraryManager _libraryManager; - public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager, ILibraryManager libraryManager) + public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor) { _userManager = userManager; - _libraryManager = libraryManager; } protected override IReadOnlyList GetItemsWithImages(BaseItem item) diff --git a/Emby.Server.Implementations/UserViews/FolderImageProvider.cs b/Emby.Server.Implementations/UserViews/FolderImageProvider.cs index 4655cd928a..58a023638f 100644 --- a/Emby.Server.Implementations/UserViews/FolderImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/FolderImageProvider.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System.Collections.Generic; using Emby.Server.Implementations.Images; using MediaBrowser.Common.Configuration; @@ -75,14 +77,14 @@ namespace Emby.Server.Implementations.UserViews return false; } - var folder = item as Folder; - if (folder != null) + if (item is Folder folder) { if (folder.IsTopParent) { return false; } } + return true; //return item.SourceType == SourceType.Library; } diff --git a/Jellyfin.Server/Migrations/IMigrationRoutine.cs b/Jellyfin.Server/Migrations/IMigrationRoutine.cs index b79fdeac03..6b5780a262 100644 --- a/Jellyfin.Server/Migrations/IMigrationRoutine.cs +++ b/Jellyfin.Server/Migrations/IMigrationRoutine.cs @@ -1,5 +1,4 @@ using System; -using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Migrations { diff --git a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs index 89514c89b7..b15e092906 100644 --- a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs +++ b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using MediaBrowser.Common.Configuration; -using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; namespace Jellyfin.Server.Migrations.Routines diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index b9895386f5..7c693f8c39 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -40,12 +40,12 @@ namespace Jellyfin.Server /// /// The name of logging configuration file containing application defaults. /// - public static readonly string LoggingConfigFileDefault = "logging.default.json"; + public const string LoggingConfigFileDefault = "logging.default.json"; /// /// The name of the logging configuration file containing the system-specific override settings. /// - public static readonly string LoggingConfigFileSystem = "logging.json"; + public const string LoggingConfigFileSystem = "logging.json"; private static readonly CancellationTokenSource _tokenSource = new CancellationTokenSource(); private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory(); -- cgit v1.2.3 From 44957c5a9a11fcd6e4567c7d31bc39d79c709068 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 5 Jun 2020 18:15:56 -0600 Subject: Use typed logger where possible --- Emby.Dlna/ConnectionManager/ConnectionManager.cs | 2 +- Emby.Dlna/DlnaManager.cs | 4 ++-- Emby.Dlna/Main/DlnaEntryPoint.cs | 10 ++++++---- Emby.Dlna/PlayTo/PlayToManager.cs | 3 ++- Emby.Dlna/Service/BaseService.cs | 4 ++-- Emby.Drawing/ImageProcessor.cs | 2 +- Emby.Notifications/NotificationEntryPoint.cs | 2 +- Emby.Notifications/NotificationManager.cs | 2 +- Emby.Photos/PhotoProvider.cs | 2 +- .../AppBase/BaseConfigurationManager.cs | 4 ++-- Emby.Server.Implementations/ApplicationHost.cs | 2 +- Emby.Server.Implementations/Browser/BrowserLauncher.cs | 2 +- Emby.Server.Implementations/Channels/ChannelManager.cs | 2 +- .../Channels/RefreshChannelsScheduledTask.cs | 2 +- Emby.Server.Implementations/Collections/CollectionManager.cs | 6 +++--- Emby.Server.Implementations/Data/BaseSqliteRepository.cs | 4 ++-- Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs | 2 +- Emby.Server.Implementations/Devices/DeviceId.cs | 4 ++-- Emby.Server.Implementations/Dto/DtoService.cs | 2 +- .../EntryPoints/ExternalPortForwarding.cs | 2 +- .../EntryPoints/LibraryChangedNotifier.cs | 2 +- Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs | 2 +- Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs | 2 +- .../HttpClientManager/HttpClientManager.cs | 2 +- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 4 ++-- Emby.Server.Implementations/HttpServer/HttpResultFactory.cs | 4 ++-- Emby.Server.Implementations/HttpServer/Security/AuthService.cs | 2 +- Emby.Server.Implementations/HttpServer/WebSocketConnection.cs | 2 +- Emby.Server.Implementations/IO/LibraryMonitor.cs | 2 +- Emby.Server.Implementations/IO/ManagedFileSystem.cs | 2 +- Emby.Server.Implementations/Library/LibraryManager.cs | 2 +- Emby.Server.Implementations/Library/MediaSourceManager.cs | 2 +- .../Library/Resolvers/Audio/MusicAlbumResolver.cs | 2 +- .../Library/Resolvers/TV/SeasonResolver.cs | 2 +- .../Library/Resolvers/TV/SeriesResolver.cs | 4 ++-- Emby.Server.Implementations/Library/SearchEngine.cs | 2 +- Emby.Server.Implementations/Library/UserDataManager.cs | 2 +- Emby.Server.Implementations/Library/UserManager.cs | 2 +- .../Library/Validators/ArtistsPostScanTask.cs | 4 ++-- .../Library/Validators/ArtistsValidator.cs | 4 ++-- .../Library/Validators/GenresPostScanTask.cs | 4 ++-- .../Library/Validators/GenresValidator.cs | 4 ++-- .../Library/Validators/MusicGenresPostScanTask.cs | 4 ++-- .../Library/Validators/MusicGenresValidator.cs | 4 ++-- .../Library/Validators/StudiosPostScanTask.cs | 4 ++-- .../Library/Validators/StudiosValidator.cs | 4 ++-- Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs | 2 +- Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs | 2 +- .../LiveTv/Listings/XmlTvListingsProvider.cs | 2 +- Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs | 2 +- Emby.Server.Implementations/LiveTv/LiveTvManager.cs | 2 +- .../LiveTv/LiveTvMediaSourceProvider.cs | 2 +- Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs | 4 ++-- Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs | 1 + .../Localization/LocalizationManager.cs | 2 +- Emby.Server.Implementations/MediaEncoder/EncodingManager.cs | 2 +- Emby.Server.Implementations/Networking/NetworkManager.cs | 2 +- Emby.Server.Implementations/Playlists/PlaylistManager.cs | 2 +- Emby.Server.Implementations/ResourceFileManager.cs | 2 +- Emby.Server.Implementations/ScheduledTasks/TaskManager.cs | 2 +- .../ScheduledTasks/Tasks/ChapterImagesTask.cs | 4 ++-- .../ScheduledTasks/Tasks/DeleteCacheFileTask.cs | 2 +- .../ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs | 2 +- .../ScheduledTasks/Tasks/PluginUpdateTask.cs | 2 +- Emby.Server.Implementations/Services/ServiceController.cs | 2 +- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- .../Session/SessionWebSocketListener.cs | 2 +- Emby.Server.Implementations/Session/WebSocketController.cs | 2 +- .../SocketSharp/WebSocketSharpRequest.cs | 2 +- Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs | 2 +- Emby.Server.Implementations/Updates/InstallationManager.cs | 2 +- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 2 +- .../Migrations/Routines/DisableTranscodingThrottling.cs | 2 +- Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs | 2 +- MediaBrowser.Api/ApiEntryPoint.cs | 2 +- MediaBrowser.Api/Music/AlbumsService.cs | 10 ++++++---- MediaBrowser.Api/SimilarItemsHelper.cs | 2 +- MediaBrowser.Controller/Entities/BaseItem.cs | 3 ++- MediaBrowser.Controller/Entities/UserView.cs | 3 ++- MediaBrowser.Controller/Entities/UserViewBuilder.cs | 4 ++-- MediaBrowser.Controller/IO/FileData.cs | 3 ++- MediaBrowser.Controller/Library/Profiler.cs | 2 +- MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs | 4 ++-- MediaBrowser.Controller/Session/SessionInfo.cs | 1 + .../Images/InternalMetadataFolderImageProvider.cs | 2 +- MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs | 4 ++-- MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs | 2 +- MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs | 2 +- MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs | 4 ++-- MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs | 4 ++-- MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs | 4 ++-- MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs | 2 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 2 +- MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs | 2 +- MediaBrowser.Providers/Manager/ImageSaver.cs | 1 + MediaBrowser.Providers/Manager/MetadataService.cs | 4 ++-- MediaBrowser.Providers/Manager/ProviderManager.cs | 2 +- MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs | 2 +- MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs | 3 ++- MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs | 2 +- MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs | 2 +- MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs | 2 +- MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs | 2 +- .../Plugins/TheTvdb/TvdbEpisodeImageProvider.cs | 2 +- MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs | 2 +- .../Plugins/TheTvdb/TvdbPersonImageProvider.cs | 2 +- .../Plugins/TheTvdb/TvdbSeasonImageProvider.cs | 2 +- .../Plugins/TheTvdb/TvdbSeriesImageProvider.cs | 2 +- MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs | 2 +- MediaBrowser.Providers/Subtitles/SubtitleManager.cs | 2 +- MediaBrowser.Providers/TV/SeriesMetadataService.cs | 3 ++- MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetProvider.cs | 2 +- MediaBrowser.Providers/Tmdb/Movies/TmdbMovieProvider.cs | 2 +- MediaBrowser.Providers/Tmdb/People/TmdbPersonProvider.cs | 2 +- MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeImageProvider.cs | 4 ++-- MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProvider.cs | 4 ++-- MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProviderBase.cs | 4 ++-- MediaBrowser.Providers/Tmdb/TV/TmdbSeasonProvider.cs | 6 +++--- MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs | 2 +- MediaBrowser.WebDashboard/Api/DashboardService.cs | 2 +- MediaBrowser.XbmcMetadata/EntryPoint.cs | 2 +- MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs | 2 +- MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs | 2 +- MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs | 4 ++-- MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs | 2 +- MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs | 2 +- MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs | 2 +- MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs | 6 +++--- 128 files changed, 179 insertions(+), 166 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Dlna/ConnectionManager/ConnectionManager.cs b/Emby.Dlna/ConnectionManager/ConnectionManager.cs index e32cc11bfa..480dd3a796 100644 --- a/Emby.Dlna/ConnectionManager/ConnectionManager.cs +++ b/Emby.Dlna/ConnectionManager/ConnectionManager.cs @@ -18,7 +18,7 @@ namespace Emby.Dlna.ConnectionManager public ConnectionManager( IDlnaManager dlna, IServerConfigurationManager config, - ILogger logger, + ILogger logger, IHttpClient httpClient) : base(logger, httpClient) { diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index 10f881fe76..a85e5c35e7 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -31,7 +31,7 @@ namespace Emby.Dlna private readonly IApplicationPaths _appPaths; private readonly IXmlSerializer _xmlSerializer; private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IJsonSerializer _jsonSerializer; private readonly IServerApplicationHost _appHost; private static readonly Assembly _assembly = typeof(DlnaManager).Assembly; @@ -49,7 +49,7 @@ namespace Emby.Dlna _xmlSerializer = xmlSerializer; _fileSystem = fileSystem; _appPaths = appPaths; - _logger = loggerFactory.CreateLogger("Dlna"); + _logger = loggerFactory.CreateLogger(); _jsonSerializer = jsonSerializer; _appHost = appHost; } diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index bcab4adbaf..47b235e59f 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -33,7 +33,7 @@ namespace Emby.Dlna.Main public class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup { private readonly IServerConfigurationManager _config; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IServerApplicationHost _appHost; private PlayToManager _manager; @@ -65,7 +65,8 @@ namespace Emby.Dlna.Main public static DlnaEntryPoint Current; - public DlnaEntryPoint(IServerConfigurationManager config, + public DlnaEntryPoint( + IServerConfigurationManager config, ILoggerFactory loggerFactory, IServerApplicationHost appHost, ISessionManager sessionManager, @@ -99,7 +100,7 @@ namespace Emby.Dlna.Main _mediaEncoder = mediaEncoder; _socketFactory = socketFactory; _networkManager = networkManager; - _logger = loggerFactory.CreateLogger("Dlna"); + _logger = loggerFactory.CreateLogger(); ContentDirectory = new ContentDirectory.ContentDirectory( dlnaManager, @@ -347,7 +348,8 @@ namespace Emby.Dlna.Main try { - _manager = new PlayToManager(_logger, + _manager = new PlayToManager( + _logger, _sessionManager, _libraryManager, _userManager, diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index bbedd1485c..9b0339e5d9 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -184,7 +184,8 @@ namespace Emby.Dlna.PlayTo serverAddress = _appHost.GetLocalApiUrl(info.LocalIpAddress); } - controller = new PlayToController(sessionInfo, + controller = new PlayToController( + sessionInfo, _sessionManager, _libraryManager, _logger, diff --git a/Emby.Dlna/Service/BaseService.cs b/Emby.Dlna/Service/BaseService.cs index 3704bedcd5..4ecffa2930 100644 --- a/Emby.Dlna/Service/BaseService.cs +++ b/Emby.Dlna/Service/BaseService.cs @@ -12,12 +12,12 @@ namespace Emby.Dlna.Service protected IHttpClient HttpClient; protected ILogger Logger; - protected BaseService(ILogger logger, IHttpClient httpClient) + protected BaseService(ILogger logger, IHttpClient httpClient) { Logger = logger; HttpClient = httpClient; - EventManager = new EventManager(Logger, HttpClient); + EventManager = new EventManager(logger, HttpClient); } public EventSubscriptionResponse CancelEventSubscription(string subscriptionId) diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 47778acace..c9fc8d55ac 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -28,7 +28,7 @@ namespace Emby.Drawing private static readonly HashSet _transparentImageTypes = new HashSet(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" }; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IServerApplicationPaths _appPaths; private readonly IImageEncoder _imageEncoder; diff --git a/Emby.Notifications/NotificationEntryPoint.cs b/Emby.Notifications/NotificationEntryPoint.cs index 869b7407e2..b923fd26ce 100644 --- a/Emby.Notifications/NotificationEntryPoint.cs +++ b/Emby.Notifications/NotificationEntryPoint.cs @@ -25,7 +25,7 @@ namespace Emby.Notifications /// public class NotificationEntryPoint : IServerEntryPoint { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IActivityManager _activityManager; private readonly ILocalizationManager _localization; private readonly INotificationManager _notificationManager; diff --git a/Emby.Notifications/NotificationManager.cs b/Emby.Notifications/NotificationManager.cs index 639a5e1aad..2792a8f0b5 100644 --- a/Emby.Notifications/NotificationManager.cs +++ b/Emby.Notifications/NotificationManager.cs @@ -21,7 +21,7 @@ namespace Emby.Notifications /// public class NotificationManager : INotificationManager { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IUserManager _userManager; private readonly IServerConfigurationManager _config; diff --git a/Emby.Photos/PhotoProvider.cs b/Emby.Photos/PhotoProvider.cs index 987cb7fb2a..58baa1e50d 100644 --- a/Emby.Photos/PhotoProvider.cs +++ b/Emby.Photos/PhotoProvider.cs @@ -22,7 +22,7 @@ namespace Emby.Photos /// public class PhotoProvider : ICustomMetadataProvider, IForcedProvider, IHasItemChangeMonitor { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IImageProcessor _imageProcessor; // These are causing taglib to hang diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs index 080cfbbd1a..d4a8268b97 100644 --- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs +++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs @@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.AppBase CommonApplicationPaths = applicationPaths; XmlSerializer = xmlSerializer; _fileSystem = fileSystem; - Logger = loggerFactory.CreateLogger(GetType().Name); + Logger = loggerFactory.CreateLogger(); UpdateCachePath(); } @@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.AppBase /// Gets the logger. /// /// The logger. - protected ILogger Logger { get; private set; } + protected ILogger Logger { get; private set; } /// /// Gets the XML serializer. diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index be4e05a648..184c6f399b 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -173,7 +173,7 @@ namespace Emby.Server.Implementations /// /// Gets the logger. /// - protected ILogger Logger { get; } + protected ILogger Logger { get; } private IPlugin[] _plugins; diff --git a/Emby.Server.Implementations/Browser/BrowserLauncher.cs b/Emby.Server.Implementations/Browser/BrowserLauncher.cs index 7f7c6a0be4..7a0294e07b 100644 --- a/Emby.Server.Implementations/Browser/BrowserLauncher.cs +++ b/Emby.Server.Implementations/Browser/BrowserLauncher.cs @@ -41,7 +41,7 @@ namespace Emby.Server.Implementations.Browser } catch (Exception ex) { - var logger = appHost.Resolve(); + var logger = appHost.Resolve>(); logger?.LogError(ex, "Failed to open browser window with URL {URL}", relativeUrl); } } diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 04fe0bacb4..f9eea66b40 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Channels private readonly IUserDataManager _userDataManager; private readonly IDtoService _dtoService; private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; diff --git a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs index 54b621e250..e5dde48d8e 100644 --- a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs +++ b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Channels public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask { private readonly IChannelManager _channelManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly ILocalizationManager _localization; diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 7c518d4831..cba3975cf3 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Collections private readonly ILibraryManager _libraryManager; private readonly IFileSystem _fileSystem; private readonly ILibraryMonitor _iLibraryMonitor; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IProviderManager _providerManager; private readonly ILocalizationManager _localizationManager; private readonly IApplicationPaths _appPaths; @@ -56,7 +56,7 @@ namespace Emby.Server.Implementations.Collections _libraryManager = libraryManager; _fileSystem = fileSystem; _iLibraryMonitor = iLibraryMonitor; - _logger = loggerFactory.CreateLogger(nameof(CollectionManager)); + _logger = loggerFactory.CreateLogger(); _providerManager = providerManager; _localizationManager = localizationManager; _appPaths = appPaths; @@ -370,7 +370,7 @@ namespace Emby.Server.Implementations.Collections { private readonly CollectionManager _collectionManager; private readonly IServerConfigurationManager _config; - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// Initializes a new instance of the class. diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 0654132f41..f816fd54f1 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Data /// Initializes a new instance of the class. /// /// The logger. - protected BaseSqliteRepository(ILogger logger) + protected BaseSqliteRepository(ILogger logger) { Logger = logger; } @@ -32,7 +32,7 @@ namespace Emby.Server.Implementations.Data /// Gets the logger. /// /// The logger. - protected ILogger Logger { get; } + protected ILogger Logger { get; } /// /// Gets the default connection flags. diff --git a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs index 37c678a5d1..6c9bcff0f2 100644 --- a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs +++ b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs @@ -12,7 +12,7 @@ namespace Emby.Server.Implementations.Data public class CleanDatabaseScheduledTask : ILibraryPostScanTask { private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; + private readonly ILogger _logger; public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger logger) { diff --git a/Emby.Server.Implementations/Devices/DeviceId.cs b/Emby.Server.Implementations/Devices/DeviceId.cs index f0d43e665b..fa6ac95fd3 100644 --- a/Emby.Server.Implementations/Devices/DeviceId.cs +++ b/Emby.Server.Implementations/Devices/DeviceId.cs @@ -12,7 +12,7 @@ namespace Emby.Server.Implementations.Devices public class DeviceId { private readonly IApplicationPaths _appPaths; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly object _syncLock = new object(); @@ -90,7 +90,7 @@ namespace Emby.Server.Implementations.Devices public DeviceId(IApplicationPaths appPaths, ILoggerFactory loggerFactory) { _appPaths = appPaths; - _logger = loggerFactory.CreateLogger("SystemId"); + _logger = loggerFactory.CreateLogger(); } public string Value => _id ?? (_id = GetDeviceId()); diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 38c4f940d7..f9841082c1 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Dto { public class DtoService : IDtoService { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly IUserDataManager _userDataRepository; private readonly IItemRepository _itemRepo; diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index 878cee23c4..9fce49425e 100644 --- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.EntryPoints public class ExternalPortForwarding : IServerEntryPoint { private readonly IServerApplicationHost _appHost; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IServerConfigurationManager _config; private readonly IDeviceDiscovery _deviceDiscovery; diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 9bc2b62ec8..b9992ae737 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.EntryPoints private readonly ISessionManager _sessionManager; private readonly IUserManager _userManager; - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// The library changed sync lock. diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index 997571a91b..b64439802f 100644 --- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.EntryPoints private readonly ILiveTvManager _liveTvManager; private readonly ISessionManager _sessionManager; private readonly IUserManager _userManager; - private readonly ILogger _logger; + private readonly ILogger _logger; public RecordingNotifier( ISessionManager sessionManager, diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index 5bc1a81aa6..ecdce89cea 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.EntryPoints /// /// The logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IServerApplicationHost _appHost; private readonly IConfiguration _config; diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index d66bb7638b..87977494a1 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.HttpClientManager /// public class HttpClientManager : IHttpClient { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IApplicationPaths _appPaths; private readonly IFileSystem _fileSystem; private readonly IApplicationHost _appHost; diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 7de4f168c1..17994d199b 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -41,7 +41,7 @@ namespace Emby.Server.Implementations.HttpServer /// public const string DefaultRedirectKey = "HttpListenerHost:DefaultRedirectPath"; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; private readonly IServerConfigurationManager _config; private readonly INetworkManager _networkManager; @@ -397,7 +397,7 @@ namespace Emby.Server.Implementations.HttpServer var response = context.Response; var localPath = context.Request.Path.ToString(); - var req = new WebSocketSharpRequest(request, response, request.Path, _logger); + var req = new WebSocketSharpRequest(request, response, request.Path); return RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted); } diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 9d59695830..cc47977908 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -37,7 +37,7 @@ namespace Emby.Server.Implementations.HttpServer /// /// The logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; private readonly IStreamHelper _streamHelper; @@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.HttpServer _fileSystem = fileSystem; _jsonSerializer = jsonSerializer; _streamHelper = streamHelper; - _logger = loggerfactory.CreateLogger("HttpResultFactory"); + _logger = loggerfactory.CreateLogger(); } /// diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 256b24924e..9bb29586ed 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -45,7 +45,7 @@ namespace Emby.Server.Implementations.HttpServer.Security public User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes) { - var req = new WebSocketSharpRequest(request, null, request.Path, _logger); + var req = new WebSocketSharpRequest(request, null, request.Path); var user = ValidateUser(req, authAttributes); return user; } diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 0680c5ffe7..c64d573392 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.HttpServer /// /// The logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// The json serializer options. diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index eb5e190aab..49bca7dac8 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -18,7 +18,7 @@ namespace Emby.Server.Implementations.IO { public class LibraryMonitor : ILibraryMonitor { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly IServerConfigurationManager _configurationManager; private readonly IFileSystem _fileSystem; diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 7461ec4f1d..2bcfc82b62 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -20,7 +20,7 @@ namespace Emby.Server.Implementations.IO /// public class ManagedFileSystem : IFileSystem { - protected ILogger Logger; + protected ILogger Logger; private readonly List _shortcutHandlers = new List(); private readonly string _tempPath; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 677030b828..8c6be8e047 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -56,7 +56,7 @@ namespace Emby.Server.Implementations.Library /// public class LibraryManager : ILibraryManager { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ITaskManager _taskManager; private readonly IUserManager _userManager; private readonly IUserDataManager _userDataRepository; diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index a5e5981b80..00826c7cb9 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.Library private readonly ILibraryManager _libraryManager; private readonly IJsonSerializer _jsonSerializer; private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IUserDataManager _userDataManager; private readonly IMediaEncoder _mediaEncoder; private readonly ILocalizationManager _localizationManager; diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index 6c9ba7c272..ebfe95d0a1 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -109,7 +109,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio IEnumerable list, bool allowSubfolders, IDirectoryService directoryService, - ILogger logger, + ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager) { diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs index 18145b7f17..7f8800a647 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV private readonly IServerConfigurationManager _config; private readonly ILibraryManager _libraryManager; private readonly ILocalizationManager _localization; - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// Initializes a new instance of the class. diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index dd6bd8ee87..4180dad361 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -20,7 +20,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV public class SeriesResolver : FolderResolver { private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; /// @@ -119,7 +119,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV IEnumerable fileSystemChildren, IDirectoryService directoryService, IFileSystem fileSystem, - ILogger logger, + ILogger logger, ILibraryManager libraryManager, bool isTvContentType) { diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index 59a77607d2..bde77ee8ed 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Library { public class SearchEngine : ISearchEngine { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index a9772a078d..d8694b07d9 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.Library private readonly ConcurrentDictionary _userData = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IServerConfigurationManager _config; private readonly IUserManager _userManager; private readonly IUserDataRepository _repository; diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 140155d0e1..531e00666e 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -46,7 +46,7 @@ namespace Emby.Server.Implementations.Library private readonly object _policySyncLock = new object(); private readonly object _configSyncLock = new object(); - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IUserRepository _userRepository; private readonly IXmlSerializer _xmlSerializer; private readonly IJsonSerializer _jsonSerializer; diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs index 2af8ff5cbf..d51f9aaa79 100644 --- a/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs +++ b/Emby.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Validators /// The _library manager. /// private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IItemRepository _itemRepo; /// @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Library.Validators /// The item repository. public ArtistsPostScanTask( ILibraryManager libraryManager, - ILogger logger, + ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs index 1497f4a3a7..8a6bd5e786 100644 --- a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs @@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.Library.Validators /// /// The logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IItemRepository _itemRepo; /// @@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.Library.Validators /// The library manager. /// The logger. /// The item repository. - public ArtistsValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) + public ArtistsValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; diff --git a/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs index 251785dfd8..d21d2887b0 100644 --- a/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs +++ b/Emby.Server.Implementations/Library/Validators/GenresPostScanTask.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Validators /// The _library manager. /// private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IItemRepository _itemRepo; /// @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Library.Validators /// The item repository. public GenresPostScanTask( ILibraryManager libraryManager, - ILogger logger, + ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; diff --git a/Emby.Server.Implementations/Library/Validators/GenresValidator.cs b/Emby.Server.Implementations/Library/Validators/GenresValidator.cs index b0cd5f87a7..e59c62e239 100644 --- a/Emby.Server.Implementations/Library/Validators/GenresValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/GenresValidator.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Library.Validators /// /// The logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// Initializes a new instance of the class. @@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Library.Validators /// The library manager. /// The logger. /// The item repository. - public GenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) + public GenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; diff --git a/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs index 9d8690116f..be119866b1 100644 --- a/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs +++ b/Emby.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Validators /// The library manager. /// private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IItemRepository _itemRepo; /// @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Library.Validators /// The item repository. public MusicGenresPostScanTask( ILibraryManager libraryManager, - ILogger logger, + ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; diff --git a/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs b/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs index 5ee4ca72ea..1ecf4c87c9 100644 --- a/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/MusicGenresValidator.cs @@ -20,7 +20,7 @@ namespace Emby.Server.Implementations.Library.Validators /// /// The logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IItemRepository _itemRepo; /// @@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Library.Validators /// The library manager. /// The logger. /// The item repository. - public MusicGenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) + public MusicGenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; diff --git a/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs index 2f8f906b97..c682b156b8 100644 --- a/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs +++ b/Emby.Server.Implementations/Library/Validators/StudiosPostScanTask.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Library.Validators /// private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IItemRepository _itemRepo; /// @@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.Library.Validators /// The item repository. public StudiosPostScanTask( ILibraryManager libraryManager, - ILogger logger, + ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; diff --git a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs index 15e7a0dbb8..7a6cd11df2 100644 --- a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs @@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.Library.Validators /// /// The logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// Initializes a new instance of the class. @@ -32,7 +32,7 @@ namespace Emby.Server.Implementations.Library.Validators /// The library manager. /// The logger. /// The item repository. - public StudiosValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) + public StudiosValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) { _libraryManager = libraryManager; _logger = logger; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 5a5dc33292..f9b71939b0 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -46,7 +46,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private const int TunerDiscoveryDurationMs = 3000; private readonly IServerApplicationHost _appHost; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IHttpClient _httpClient; private readonly IServerConfigurationManager _config; private readonly IJsonSerializer _jsonSerializer; diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 89b81fd968..02f18060d0 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings { public class SchedulesDirect : IListingsProvider { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IJsonSerializer _jsonSerializer; private readonly IHttpClient _httpClient; private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1); diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 07f8539c5e..077b5c7e50 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings { private readonly IServerConfigurationManager _config; private readonly IHttpClient _httpClient; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IZipClient _zipClient; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs index a59c1090e5..49ad73af3f 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.LiveTv private const string ServiceName = "Emby"; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IImageProcessor _imageProcessor; private readonly IDtoService _dtoService; private readonly IApplicationHost _appHost; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index dbd0e6f2eb..a88e0f31fd 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.LiveTv private const string EtagKey = "ProgramEtag"; private readonly IServerConfigurationManager _config; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IItemRepository _itemRepo; private readonly IUserManager _userManager; private readonly IDtoService _dtoService; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index 7f63991d0c..f3fc413527 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.LiveTv private const string StreamIdDelimeterString = "_"; private readonly ILiveTvManager _liveTvManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IMediaSourceManager _mediaSourceManager; private readonly IServerApplicationHost _appHost; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index 80ee1ee33a..ba3594efd3 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -22,14 +22,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public abstract class BaseTunerHost { protected readonly IServerConfigurationManager Config; - protected readonly ILogger Logger; + protected readonly ILogger Logger; protected IJsonSerializer JsonSerializer; protected readonly IFileSystem FileSystem; private readonly ConcurrentDictionary _channelCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - protected BaseTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem) + protected BaseTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem) { Config = config; Logger = logger; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index 4e4f1d7f60..c3336ca928 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Emby.Server.Implementations.Library; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index e2a634e1a4..62a23118fb 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Localization private readonly IServerConfigurationManager _configurationManager; private readonly IJsonSerializer _jsonSerializer; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly Dictionary> _allParentalRatings = new Dictionary>(StringComparer.OrdinalIgnoreCase); diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index 7b7575707a..438bbe24a5 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.MediaEncoder { private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IMediaEncoder _encoder; private readonly IChapterManager _chapterManager; private readonly ILibraryManager _libraryManager; diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index d1a28e7a19..45864bb42a 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -15,7 +15,7 @@ namespace Emby.Server.Implementations.Networking { public class NetworkManager : INetworkManager { - private readonly ILogger _logger; + private readonly ILogger _logger; private IPAddress[] _localIpAddresses; private readonly object _localIpAddressSyncLock = new object(); diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index c51eb05863..8d022d6c4e 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Playlists private readonly ILibraryManager _libraryManager; private readonly IFileSystem _fileSystem; private readonly ILibraryMonitor _iLibraryMonitor; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IUserManager _userManager; private readonly IProviderManager _providerManager; private readonly IConfiguration _appConfig; diff --git a/Emby.Server.Implementations/ResourceFileManager.cs b/Emby.Server.Implementations/ResourceFileManager.cs index d192be921f..22fc62293a 100644 --- a/Emby.Server.Implementations/ResourceFileManager.cs +++ b/Emby.Server.Implementations/ResourceFileManager.cs @@ -12,7 +12,7 @@ namespace Emby.Server.Implementations public class ResourceFileManager : IResourceFileManager { private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; + private readonly ILogger _logger; public ResourceFileManager(ILogger logger, IFileSystem fileSystem) { diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index efefa55066..94220ac5d1 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.ScheduledTasks private readonly IJsonSerializer _jsonSerializer; private readonly IApplicationPaths _applicationPaths; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IFileSystem _fileSystem; /// diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index fae0499148..9028222dd1 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// The _logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// The _library manager. @@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.ScheduledTasks IFileSystem fileSystem, ILocalizationManager localization) { - _logger = loggerFactory.CreateLogger(GetType().Name); + _logger = loggerFactory.CreateLogger(); _libraryManager = libraryManager; _itemRepo = itemRepo; _appPaths = appPaths; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs index a6c13eaefe..966b549b23 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs @@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// The application paths. private IApplicationPaths ApplicationPaths { get; set; } - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly ILocalizationManager _localization; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs index 0d36b82c09..53cf9a0a5f 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// public class DeleteTranscodeFileTask : IScheduledTask, IConfigurableScheduledTask { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IConfigurationManager _configurationManager; private readonly IFileSystem _fileSystem; private readonly ILocalizationManager _localization; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs index acab3aeea9..7388086fb0 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs @@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// The _logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IInstallationManager _installationManager; private readonly ILocalizationManager _localization; diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs index ad6015c1cd..e688278b5d 100644 --- a/Emby.Server.Implementations/Services/ServiceController.cs +++ b/Emby.Server.Implementations/Services/ServiceController.cs @@ -15,7 +15,7 @@ namespace Emby.Server.Implementations.Services public class ServiceController { - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// Initializes a new instance of the class. diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 5c480e8424..506e6739f0 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -45,7 +45,7 @@ namespace Emby.Server.Implementations.Session /// /// The logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index e7b4b0ec35..ef32c692c1 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -41,7 +41,7 @@ namespace Emby.Server.Implementations.Session /// /// The _logger /// - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; private readonly IHttpServer _httpServer; diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index a0274acd20..94604ca1e0 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Session { public sealed class WebSocketController : ISessionController, IDisposable { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ISessionManager _sessionManager; private readonly SessionInfo _session; diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index aa76901a4e..ae1a8d0b7f 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.SocketSharp private Dictionary _items; private string _responseContentType; - public WebSocketSharpRequest(HttpRequest httpRequest, HttpResponse httpResponse, string operationName, ILogger logger) + public WebSocketSharpRequest(HttpRequest httpRequest, HttpResponse httpResponse, string operationName) { this.OperationName = operationName; this.Request = httpRequest; diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index 129262e53c..1bfc9a9a52 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.SyncPlay /// /// The logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// The user manager. diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 178f32c313..80326fddf2 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -39,7 +39,7 @@ namespace Emby.Server.Implementations.Updates /// /// The logger. /// - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IApplicationPaths _appPaths; private readonly IHttpClient _httpClient; private readonly IJsonSerializer _jsonSerializer; diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index ccee5c5b91..ba9a5809f2 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -21,7 +21,7 @@ namespace Jellyfin.Drawing.Skia private static readonly HashSet _transparentImageTypes = new HashSet(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" }; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IApplicationPaths _appPaths; /// diff --git a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs index b2e957d5bb..c18aa16293 100644 --- a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs +++ b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs @@ -10,7 +10,7 @@ namespace Jellyfin.Server.Migrations.Routines /// internal class DisableTranscodingThrottling : IMigrationRoutine { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IConfigurationManager _configManager; public DisableTranscodingThrottling(ILogger logger, IConfigurationManager configManager) diff --git a/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs b/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs index e955363881..2ebef02414 100644 --- a/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs +++ b/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs @@ -14,7 +14,7 @@ namespace Jellyfin.Server.Migrations.Routines internal class RemoveDuplicateExtras : IMigrationRoutine { private const string DbFilename = "library.db"; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IServerApplicationPaths _paths; public RemoveDuplicateExtras(ILogger logger, IServerApplicationPaths paths) diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index c7485a2e96..9e651fb197 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -31,7 +31,7 @@ namespace MediaBrowser.Api /// /// The logger. /// - private ILogger _logger; + private ILogger _logger; /// /// The configuration manager. diff --git a/MediaBrowser.Api/Music/AlbumsService.cs b/MediaBrowser.Api/Music/AlbumsService.cs index 58c95d053e..f257d10141 100644 --- a/MediaBrowser.Api/Music/AlbumsService.cs +++ b/MediaBrowser.Api/Music/AlbumsService.cs @@ -67,12 +67,13 @@ namespace MediaBrowser.Api.Music { var dtoOptions = GetDtoOptions(_authContext, request); - var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager, + var result = SimilarItemsHelper.GetSimilarItemsResult( + dtoOptions, + _userManager, _itemRepo, _libraryManager, _userDataRepository, _dtoService, - Logger, request, new[] { typeof(MusicArtist) }, SimilarItemsHelper.GetSimiliarityScore); @@ -88,12 +89,13 @@ namespace MediaBrowser.Api.Music { var dtoOptions = GetDtoOptions(_authContext, request); - var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager, + var result = SimilarItemsHelper.GetSimilarItemsResult( + dtoOptions, + _userManager, _itemRepo, _libraryManager, _userDataRepository, _dtoService, - Logger, request, new[] { typeof(MusicAlbum) }, GetAlbumSimilarityScore); diff --git a/MediaBrowser.Api/SimilarItemsHelper.cs b/MediaBrowser.Api/SimilarItemsHelper.cs index 44bb24ef2d..dcd22280a6 100644 --- a/MediaBrowser.Api/SimilarItemsHelper.cs +++ b/MediaBrowser.Api/SimilarItemsHelper.cs @@ -69,7 +69,7 @@ namespace MediaBrowser.Api /// public static class SimilarItemsHelper { - internal static QueryResult GetSimilarItemsResult(DtoOptions dtoOptions, IUserManager userManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, ILogger logger, BaseGetSimilarItemsFromItem request, Type[] includeTypes, Func, List, BaseItem, int> getSimilarityScore) + internal static QueryResult GetSimilarItemsResult(DtoOptions dtoOptions, IUserManager userManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, BaseGetSimilarItemsFromItem request, Type[] includeTypes, Func, List, BaseItem, int> getSimilarityScore) { var user = !request.UserId.Equals(Guid.Empty) ? userManager.GetUserById(request.UserId) : null; diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index f4b71d8bf7..2c789e386b 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -557,7 +557,8 @@ namespace MediaBrowser.Controller.Entities /// /// The logger /// - public static ILogger Logger { get; set; } + public static ILoggerFactory LoggerFactory { get; set; } + public static ILogger Logger { get; set; } public static ILibraryManager LibraryManager { get; set; } public static IServerConfigurationManager ConfigurationManager { get; set; } public static IProviderManager ProviderManager { get; set; } diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index 4ce9ec6f82..86cff632c5 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -5,6 +5,7 @@ using System.Text.Json.Serialization; using System.Threading.Tasks; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Querying; +using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Entities { @@ -66,7 +67,7 @@ namespace MediaBrowser.Controller.Entities parent = LibraryManager.GetItemById(ParentId) as Folder ?? parent; } - return new UserViewBuilder(UserViewManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, ConfigurationManager) + return new UserViewBuilder(UserViewManager, LibraryManager, LoggerFactory.CreateLogger(), UserDataManager, TVSeriesManager, ConfigurationManager) .GetUserItems(parent, this, CollectionType, query); } diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 435a1e8dae..3d91a0d0eb 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.Controller.Entities { private readonly IUserViewManager _userViewManager; private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IUserDataManager _userDataManager; private readonly ITVSeriesManager _tvSeriesManager; private readonly IServerConfigurationManager _config; @@ -25,7 +25,7 @@ namespace MediaBrowser.Controller.Entities public UserViewBuilder( IUserViewManager userViewManager, ILibraryManager libraryManager, - ILogger logger, + ILogger logger, IUserDataManager userDataManager, ITVSeriesManager tvSeriesManager, IServerConfigurationManager config) diff --git a/MediaBrowser.Controller/IO/FileData.cs b/MediaBrowser.Controller/IO/FileData.cs index 4bbb60283e..666a3f76b4 100644 --- a/MediaBrowser.Controller/IO/FileData.cs +++ b/MediaBrowser.Controller/IO/FileData.cs @@ -35,7 +35,8 @@ namespace MediaBrowser.Controller.IO /// if set to true [resolve shortcuts]. /// Dictionary{System.StringFileSystemInfo}. /// path - public static FileSystemMetadata[] GetFilteredFileSystemEntries(IDirectoryService directoryService, + public static FileSystemMetadata[] GetFilteredFileSystemEntries( + IDirectoryService directoryService, string path, IFileSystem fileSystem, IServerApplicationHost appHost, diff --git a/MediaBrowser.Controller/Library/Profiler.cs b/MediaBrowser.Controller/Library/Profiler.cs index 46a97d181f..0febef3d3e 100644 --- a/MediaBrowser.Controller/Library/Profiler.cs +++ b/MediaBrowser.Controller/Library/Profiler.cs @@ -21,7 +21,7 @@ namespace MediaBrowser.Controller.Library /// /// The _logger /// - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// Initializes a new instance of the class. diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index 5be656bdbe..7dca793c64 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -40,9 +40,9 @@ namespace MediaBrowser.Controller.Net /// /// The logger /// - protected ILogger Logger; + protected ILogger> Logger; - protected BasePeriodicWebSocketListener(ILogger logger) + protected BasePeriodicWebSocketListener(ILogger> logger) { if (logger == null) { diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 2ba7c9fec0..ecc9108728 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; diff --git a/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs b/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs index 795933ce98..5137e4c681 100644 --- a/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs @@ -13,7 +13,7 @@ namespace MediaBrowser.LocalMetadata.Images { private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; + private readonly ILogger _logger; public InternalMetadataFolderImageProvider( IServerConfigurationManager config, diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index d4b98182f9..9621cfa4e8 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -23,7 +23,7 @@ namespace MediaBrowser.LocalMetadata.Parsers /// /// The logger /// - protected ILogger Logger { get; private set; } + protected ILogger> Logger { get; private set; } protected IProviderManager ProviderManager { get; private set; } private Dictionary _validProviderIds; @@ -32,7 +32,7 @@ namespace MediaBrowser.LocalMetadata.Parsers /// Initializes a new instance of the class. /// /// The logger. - public BaseItemXmlParser(ILogger logger, IProviderManager providerManager) + public BaseItemXmlParser(ILogger> logger, IProviderManager providerManager) { Logger = logger; ProviderManager = providerManager; diff --git a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs index 127334625d..ca11a079d2 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs @@ -85,7 +85,7 @@ namespace MediaBrowser.LocalMetadata.Parsers item.Item.LinkedChildren = list.ToArray(); } - public BoxSetXmlParser(ILogger logger, IProviderManager providerManager) + public BoxSetXmlParser(ILogger logger, IProviderManager providerManager) : base(logger, providerManager) { } diff --git a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs index 5608a0be90..54710cd825 100644 --- a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs @@ -93,7 +93,7 @@ namespace MediaBrowser.LocalMetadata.Parsers item.LinkedChildren = list.ToArray(); } - public PlaylistXmlParser(ILogger logger, IProviderManager providerManager) + public PlaylistXmlParser(ILogger logger, IProviderManager providerManager) : base(logger, providerManager) { } diff --git a/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs index b2e3bc9e2b..2d115a591e 100644 --- a/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs @@ -13,10 +13,10 @@ namespace MediaBrowser.LocalMetadata.Providers /// public class BoxSetXmlProvider : BaseXmlProvider { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IProviderManager _providerManager; - public BoxSetXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager) + public BoxSetXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager) : base(fileSystem) { _logger = logger; diff --git a/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs index df8107bade..d4e2bc8e51 100644 --- a/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs @@ -10,12 +10,12 @@ namespace MediaBrowser.LocalMetadata.Providers { public class PlaylistXmlProvider : BaseXmlProvider { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IProviderManager _providerManager; public PlaylistXmlProvider( IFileSystem fileSystem, - ILogger logger, + ILogger logger, IProviderManager providerManager) : base(fileSystem) { diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index ba1d850e3c..071902393f 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs @@ -21,7 +21,7 @@ namespace MediaBrowser.LocalMetadata.Savers { private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - public BaseXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger) + public BaseXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger) { FileSystem = fileSystem; ConfigurationManager = configurationManager; @@ -36,7 +36,7 @@ namespace MediaBrowser.LocalMetadata.Savers protected ILibraryManager LibraryManager { get; private set; } protected IUserManager UserManager { get; private set; } protected IUserDataManager UserDataManager { get; private set; } - protected ILogger Logger { get; private set; } + protected ILogger Logger { get; private set; } public string Name => XmlProviderUtils.Name; diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index 3f177a9fa8..a82f2108fb 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -21,7 +21,7 @@ namespace MediaBrowser.MediaEncoding.Attachments { public class AttachmentExtractor : IAttachmentExtractor, IDisposable { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IApplicationPaths _appPaths; private readonly IFileSystem _fileSystem; private readonly IMediaEncoder _mediaEncoder; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 1377502dd9..26bd202ba6 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -34,7 +34,7 @@ namespace MediaBrowser.MediaEncoding.Encoder /// internal const int DefaultImageExtractionTimeout = 5000; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IServerConfigurationManager _configurationManager; private readonly IFileSystem _fileSystem; private readonly ILocalizationManager _localization; diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index ba171295ea..f08af60451 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles public class SubtitleEncoder : ISubtitleEncoder { private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IApplicationPaths _appPaths; private readonly IFileSystem _fileSystem; private readonly IMediaEncoder _mediaEncoder; diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 3ab621ba41..f21ac2d65c 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -15,6 +15,7 @@ using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; +using MediaBrowser.Providers.Tmdb.Models.General; using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Manager diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index c49aa407aa..2997ba064d 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -20,12 +20,12 @@ namespace MediaBrowser.Providers.Manager where TIdType : ItemLookupInfo, new() { protected readonly IServerConfigurationManager ServerConfigurationManager; - protected readonly ILogger Logger; + protected readonly ILogger> Logger; protected readonly IProviderManager ProviderManager; protected readonly IFileSystem FileSystem; protected readonly ILibraryManager LibraryManager; - protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, ILibraryManager libraryManager) + protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger> logger, IProviderManager providerManager, IFileSystem fileSystem, ILibraryManager libraryManager) { ServerConfigurationManager = serverConfigurationManager; Logger = logger; diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 400ec8d29e..13a4436e15 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.Manager /// public class ProviderManager : IProviderManager, IDisposable { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IHttpClient _httpClient; private readonly ILibraryMonitor _libraryMonitor; private readonly IFileSystem _fileSystem; diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index 6982568eb2..81103575da 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -37,7 +37,7 @@ namespace MediaBrowser.Providers.MediaInfo IPreRefreshProvider, IHasItemChangeMonitor { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IIsoManager _isoManager; private readonly IMediaEncoder _mediaEncoder; private readonly IItemRepository _itemRepo; diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs index 77c0e9b4ef..f35e82bb5b 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs @@ -25,7 +25,8 @@ namespace MediaBrowser.Providers.MediaInfo _subtitleManager = subtitleManager; } - public async Task> DownloadSubtitles(Video video, + public async Task> DownloadSubtitles( + Video video, List mediaStreams, bool skipIfEmbeddedSubtitlesPresent, bool skipIfAudioTrackMatches, diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index 2615f2dbbb..fb87030ffc 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.Providers.MediaInfo private readonly IServerConfigurationManager _config; private readonly ISubtitleManager _subtitleManager; private readonly IMediaSourceManager _mediaSourceManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IJsonSerializer _json; private readonly ILocalizationManager _localization; diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index f405700407..08e503a71b 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.Providers.MediaInfo public class VideoImageProvider : IDynamicImageProvider, IHasOrder { private readonly IMediaEncoder _mediaEncoder; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IFileSystem _fileSystem; public VideoImageProvider(IMediaEncoder mediaEncoder, ILogger logger, IFileSystem fileSystem) diff --git a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs index ae837c591a..9fc86b213e 100644 --- a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs +++ b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs @@ -20,7 +20,7 @@ namespace MediaBrowser.Providers.Playlists IPreRefreshProvider, IHasItemChangeMonitor { - private ILogger _logger; + private readonly ILogger _logger; private IFileSystem _fileSystem; public PlaylistItemsProvider(IFileSystem fileSystem, ILogger logger) diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs index 31cdaf616a..9d0d149696 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs @@ -42,7 +42,7 @@ namespace MediaBrowser.Providers.Music private readonly IHttpClient _httpClient; private readonly IApplicationHost _appHost; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly string _musicBrainzBaseUrl; diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs index 6118a9c53e..7a6fdc5f40 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeImageProvider.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb public class TvdbEpisodeImageProvider : IRemoteImageProvider { private readonly IHttpClient _httpClient; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly TvdbClientManager _tvdbClientManager; public TvdbEpisodeImageProvider(IHttpClient httpClient, ILogger logger, TvdbClientManager tvdbClientManager) diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs index 08c2a74d2c..1f8b82ecd6 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs @@ -21,7 +21,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb public class TvdbEpisodeProvider : IRemoteMetadataProvider, IHasOrder { private readonly IHttpClient _httpClient; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly TvdbClientManager _tvdbClientManager; public TvdbEpisodeProvider(IHttpClient httpClient, ILogger logger, TvdbClientManager tvdbClientManager) diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs index c1cdc90e90..961cb59707 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb public class TvdbPersonImageProvider : IRemoteImageProvider, IHasOrder { private readonly IHttpClient _httpClient; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly TvdbClientManager _tvdbClientManager; diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs index a5d183df77..cd22514b4a 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb public class TvdbSeasonImageProvider : IRemoteImageProvider, IHasOrder { private readonly IHttpClient _httpClient; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly TvdbClientManager _tvdbClientManager; public TvdbSeasonImageProvider(IHttpClient httpClient, ILogger logger, TvdbClientManager tvdbClientManager) diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs index 1bad607565..3fd98b8284 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb public class TvdbSeriesImageProvider : IRemoteImageProvider, IHasOrder { private readonly IHttpClient _httpClient; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly TvdbClientManager _tvdbClientManager; public TvdbSeriesImageProvider(IHttpClient httpClient, ILogger logger, TvdbClientManager tvdbClientManager) diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs index f6cd249f51..35152610a2 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs @@ -23,7 +23,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb { internal static TvdbSeriesProvider Current { get; private set; } private readonly IHttpClient _httpClient; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly ILocalizationManager _localizationManager; private readonly TvdbClientManager _tvdbClientManager; diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 127d29c04e..c6ffc460c4 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Providers.Subtitles { public class SubtitleManager : ISubtitleManager { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly ILibraryMonitor _monitor; private readonly IMediaSourceManager _mediaSourceManager; diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs index 5e75a8125d..a91d5d7076 100644 --- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs @@ -42,7 +42,8 @@ namespace MediaBrowser.Providers.TV await seasonProvider.Run(item, cancellationToken).ConfigureAwait(false); // TODO why does it not register this itself omg - var provider = new MissingEpisodeProvider(Logger, + var provider = new MissingEpisodeProvider( + Logger, ServerConfigurationManager, LibraryManager, _localization, diff --git a/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetProvider.cs b/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetProvider.cs index dd3783ffbf..e17f5efdf5 100644 --- a/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetProvider.cs +++ b/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetProvider.cs @@ -29,7 +29,7 @@ namespace MediaBrowser.Providers.Tmdb.BoxSets internal static TmdbBoxSetProvider Current; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IJsonSerializer _json; private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; diff --git a/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieProvider.cs index e2fd5b9e30..3787d4003b 100644 --- a/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieProvider.cs @@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.Tmdb.Movies private readonly IHttpClient _httpClient; private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _configurationManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly IApplicationHost _appHost; diff --git a/MediaBrowser.Providers/Tmdb/People/TmdbPersonProvider.cs b/MediaBrowser.Providers/Tmdb/People/TmdbPersonProvider.cs index 5880011691..1f09badbc5 100644 --- a/MediaBrowser.Providers/Tmdb/People/TmdbPersonProvider.cs +++ b/MediaBrowser.Providers/Tmdb/People/TmdbPersonProvider.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Providers.Tmdb.People private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _configurationManager; private readonly IHttpClient _httpClient; - private readonly ILogger _logger; + private readonly ILogger _logger; public TmdbPersonProvider( IFileSystem fileSystem, diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeImageProvider.cs index 558c8149e5..b0174a587c 100644 --- a/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeImageProvider.cs @@ -24,8 +24,8 @@ namespace MediaBrowser.Providers.Tmdb.TV IRemoteImageProvider, IHasOrder { - public TmdbEpisodeImageProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILoggerFactory loggerFactory) - : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, loggerFactory) + public TmdbEpisodeImageProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILogger logger) + : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, logger) { } public IEnumerable GetSupportedImages(BaseItem item) diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProvider.cs index a17f5d17a2..330b9f42bc 100644 --- a/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProvider.cs @@ -25,8 +25,8 @@ namespace MediaBrowser.Providers.Tmdb.TV IRemoteMetadataProvider, IHasOrder { - public TmdbEpisodeProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILoggerFactory loggerFactory) - : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, loggerFactory) + public TmdbEpisodeProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILogger logger) + : base(httpClient, configurationManager, jsonSerializer, fileSystem, localization, logger) { } public async Task> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken) diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProviderBase.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProviderBase.cs index e87fe9332f..3937ebc953 100644 --- a/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProviderBase.cs +++ b/MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProviderBase.cs @@ -24,14 +24,14 @@ namespace MediaBrowser.Providers.Tmdb.TV private readonly ILocalizationManager _localization; private readonly ILogger _logger; - protected TmdbEpisodeProviderBase(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILoggerFactory loggerFactory) + protected TmdbEpisodeProviderBase(IHttpClient httpClient, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem, ILocalizationManager localization, ILogger logger) { _httpClient = httpClient; _configurationManager = configurationManager; _jsonSerializer = jsonSerializer; _fileSystem = fileSystem; _localization = localization; - _logger = loggerFactory.CreateLogger(GetType().Name); + _logger = logger; } protected ILogger Logger => _logger; diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbSeasonProvider.cs index 5ad3319717..832b64941e 100644 --- a/MediaBrowser.Providers/Tmdb/TV/TmdbSeasonProvider.cs +++ b/MediaBrowser.Providers/Tmdb/TV/TmdbSeasonProvider.cs @@ -29,18 +29,18 @@ namespace MediaBrowser.Providers.Tmdb.TV private readonly IJsonSerializer _jsonSerializer; private readonly IFileSystem _fileSystem; private readonly ILocalizationManager _localization; - private readonly ILogger _logger; + private readonly ILogger _logger; internal static TmdbSeasonProvider Current { get; private set; } - public TmdbSeasonProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization, IJsonSerializer jsonSerializer, ILoggerFactory loggerFactory) + public TmdbSeasonProvider(IHttpClient httpClient, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization, IJsonSerializer jsonSerializer, ILogger logger) { _httpClient = httpClient; _configurationManager = configurationManager; _fileSystem = fileSystem; _localization = localization; _jsonSerializer = jsonSerializer; - _logger = loggerFactory.CreateLogger(GetType().Name); + _logger = logger; Current = this; } diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs index 6e3c26c263..649c5474bf 100644 --- a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs @@ -31,7 +31,7 @@ namespace MediaBrowser.Providers.Tmdb.TV private readonly IJsonSerializer _jsonSerializer; private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _configurationManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ILocalizationManager _localization; private readonly IHttpClient _httpClient; private readonly ILibraryManager _libraryManager; diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 55fc463d0e..63cbfd9e42 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -90,7 +90,7 @@ namespace MediaBrowser.WebDashboard.Api /// Gets or sets the logger. /// /// The logger. - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// Gets or sets the HTTP result factory. diff --git a/MediaBrowser.XbmcMetadata/EntryPoint.cs b/MediaBrowser.XbmcMetadata/EntryPoint.cs index 571953b471..11b36285c9 100644 --- a/MediaBrowser.XbmcMetadata/EntryPoint.cs +++ b/MediaBrowser.XbmcMetadata/EntryPoint.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.XbmcMetadata public sealed class EntryPoint : IServerEntryPoint { private readonly IUserDataManager _userDataManager; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IProviderManager _providerManager; private readonly IConfigurationManager _config; diff --git a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs index 4b1ee4c9c9..433a936d91 100644 --- a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.XbmcMetadata.Providers /// public class AlbumNfoProvider : BaseNfoProvider { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IConfigurationManager _config; private readonly IProviderManager _providerManager; diff --git a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs index 3bbfa6e83b..d69cdc90ae 100644 --- a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.XbmcMetadata.Providers /// public class ArtistNfoProvider : BaseNfoProvider { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IConfigurationManager _config; private readonly IProviderManager _providerManager; diff --git a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs index 84c99664ae..2b1589d47b 100644 --- a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs @@ -15,12 +15,12 @@ namespace MediaBrowser.XbmcMetadata.Providers public abstract class BaseVideoNfoProvider : BaseNfoProvider where T : Video, new() { - private readonly ILogger _logger; + private readonly ILogger> _logger; private readonly IConfigurationManager _config; private readonly IProviderManager _providerManager; public BaseVideoNfoProvider( - ILogger logger, + ILogger> logger, IFileSystem fileSystem, IConfigurationManager config, IProviderManager providerManager) diff --git a/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs index b2dc2e38ef..26983b1a69 100644 --- a/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.XbmcMetadata.Providers /// public class EpisodeNfoProvider : BaseNfoProvider { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IConfigurationManager _config; private readonly IProviderManager _providerManager; diff --git a/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs index 63ddd6025d..0603fd0d13 100644 --- a/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.XbmcMetadata.Providers /// public class SeasonNfoProvider : BaseNfoProvider { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IConfigurationManager _config; private readonly IProviderManager _providerManager; diff --git a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs index d659145422..7e059e0aae 100644 --- a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.XbmcMetadata.Providers /// public class SeriesNfoProvider : BaseNfoProvider { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IConfigurationManager _config; private readonly IProviderManager _providerManager; diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index 90e8b4b99f..3602ec14c1 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs @@ -105,7 +105,7 @@ namespace MediaBrowser.XbmcMetadata.Savers ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, - ILogger logger) + ILogger logger) { Logger = logger; UserDataManager = userDataManager; @@ -125,7 +125,7 @@ namespace MediaBrowser.XbmcMetadata.Savers protected IUserDataManager UserDataManager { get; } - protected ILogger Logger { get; } + protected ILogger Logger { get; } protected ItemUpdateType MinimumUpdateType { @@ -974,7 +974,7 @@ namespace MediaBrowser.XbmcMetadata.Savers => string.Equals(person.Type, type, StringComparison.OrdinalIgnoreCase) || string.Equals(person.Role, type, StringComparison.OrdinalIgnoreCase); - private void AddCustomTags(string path, List xmlTagsUsed, XmlWriter writer, ILogger logger) + private void AddCustomTags(string path, List xmlTagsUsed, XmlWriter writer, ILogger logger) { var settings = new XmlReaderSettings() { -- cgit v1.2.3 From 7d9b5524031fe6b5c23b4282cb1f9ec850b114fe Mon Sep 17 00:00:00 2001 From: ConfusedPolarBear <33811686+ConfusedPolarBear@users.noreply.github.com> Date: Tue, 9 Jun 2020 13:28:40 -0500 Subject: Apply suggestions from code review Co-authored-by: Cody Robibero --- Emby.Server.Implementations/ApplicationHost.cs | 1 - .../QuickConnect/ConfigurationExtension.cs | 17 +++++------ .../QuickConnect/QuickConnectConfiguration.cs | 11 ++----- .../QuickConnect/QuickConnectManager.cs | 35 +++++++++++++++------- .../Session/SessionManager.cs | 2 +- 5 files changed, 36 insertions(+), 30 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 0a349bb331..51e63ecfc2 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -646,7 +646,6 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); } - /// /// Create services registered with the service container that need to be initialized at application startup. /// diff --git a/Emby.Server.Implementations/QuickConnect/ConfigurationExtension.cs b/Emby.Server.Implementations/QuickConnect/ConfigurationExtension.cs index 349010039b..596ded8caa 100644 --- a/Emby.Server.Implementations/QuickConnect/ConfigurationExtension.cs +++ b/Emby.Server.Implementations/QuickConnect/ConfigurationExtension.cs @@ -1,18 +1,17 @@ -using System.Collections.Generic; using MediaBrowser.Common.Configuration; namespace Emby.Server.Implementations.QuickConnect { /// - /// Configuration extension to support persistent quick connect configuration + /// Configuration extension to support persistent quick connect configuration. /// public static class ConfigurationExtension { /// - /// Return the current quick connect configuration + /// Return the current quick connect configuration. /// - /// Configuration manager - /// + /// Configuration manager. + /// Current quick connect configuration. public static QuickConnectConfiguration GetQuickConnectConfiguration(this IConfigurationManager manager) { return manager.GetConfiguration("quickconnect"); @@ -20,17 +19,17 @@ namespace Emby.Server.Implementations.QuickConnect } /// - /// Configuration factory for quick connect + /// Configuration factory for quick connect. /// public class QuickConnectConfigurationFactory : IConfigurationFactory { /// - /// Returns the current quick connect configuration + /// Returns the current quick connect configuration. /// - /// + /// Current quick connect configuration. public IEnumerable GetConfigurations() { - return new ConfigurationStore[] + return new[] { new ConfigurationStore { diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectConfiguration.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectConfiguration.cs index e1881f2783..2302ddbc3f 100644 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectConfiguration.cs +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectConfiguration.cs @@ -3,19 +3,12 @@ using MediaBrowser.Model.QuickConnect; namespace Emby.Server.Implementations.QuickConnect { /// - /// Persistent quick connect configuration + /// Persistent quick connect configuration. /// public class QuickConnectConfiguration { /// - /// Quick connect configuration object - /// - public QuickConnectConfiguration() - { - } - - /// - /// Persistent quick connect availability state + /// Gets or sets persistent quick connect availability state. /// public QuickConnectState State { get; set; } } diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs index 929e021a3d..62b775fa60 100644 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs @@ -27,15 +27,11 @@ namespace Emby.Server.Implementations.QuickConnect private readonly RNGCryptoServiceProvider _rng = new RNGCryptoServiceProvider(); private Dictionary _currentRequests = new Dictionary(); - private IServerConfigurationManager _config; - private ILogger _logger; - private IUserManager _userManager; - private ILocalizationManager _localizationManager; - private IJsonSerializer _jsonSerializer; - private IAuthenticationRepository _authenticationRepository; - private IAuthorizationContext _authContext; - private IServerApplicationHost _appHost; - private ITaskManager _taskManager; + private readonly IServerConfigurationManager _config; + private readonly ILogger _logger; + private readonly IAuthenticationRepository _authenticationRepository; + private readonly IAuthorizationContext _authContext; + private readonly IServerApplicationHost _appHost; /// /// Initializes a new instance of the class. @@ -207,7 +203,7 @@ namespace Emby.Server.Implementations.QuickConnect scale = BitConverter.ToUInt32(raw, 0); } - int code = (int)(min + (max - min) * (scale / (double)uint.MaxValue)); + int code = (int)(min + ((max - min) * (scale / (double)uint.MaxValue))); return code.ToString(CultureInfo.InvariantCulture); } @@ -272,7 +268,26 @@ namespace Emby.Server.Implementations.QuickConnect return tokens.Count(); } + /// + /// Dispose. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + /// + /// Dispose. + /// + /// Dispose unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _rng?.Dispose(); + } + } private string GenerateSecureRandom(int length = 32) { var bytes = new byte[length]; diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index d7054e0b11..188b366aae 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1413,7 +1413,7 @@ namespace Emby.Server.Implementations.Session Limit = 1 }); - if(result.TotalRecordCount < 1) + if (result.TotalRecordCount < 1) { throw new SecurityException("Unknown quick connect token"); } -- cgit v1.2.3 From d0e2027b05e000b64f9cd26c6a10b8ef1f95e481 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 11 Jun 2020 19:45:31 -0400 Subject: (Hopefully) fix concurrency exceptions. --- Emby.Server.Implementations/Session/SessionManager.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 900eba18e0..b0a4cf83b5 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -29,6 +29,7 @@ using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; using MediaBrowser.Model.SyncPlay; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Episode = MediaBrowser.Controller.Entities.TV.Episode; @@ -285,11 +286,18 @@ namespace Emby.Server.Implementations.Session if (user != null) { var userLastActivityDate = user.LastActivityDate ?? DateTime.MinValue; - user.LastActivityDate = activityDate; if ((activityDate - userLastActivityDate).TotalSeconds > 60) { - _userManager.UpdateUser(user); + try + { + user.LastActivityDate = activityDate; + _userManager.UpdateUser(user); + } + catch (DbUpdateConcurrencyException e) + { + _logger.LogWarning(e, "Error updating user's last activity date."); + } } } -- cgit v1.2.3 From 9018f8d8be10bc4812f7d1bd230a1516eca61eea Mon Sep 17 00:00:00 2001 From: telans Date: Tue, 16 Jun 2020 10:37:52 +1200 Subject: Add full stop at end of comments (SA1629) --- Emby.Dlna/Didl/DidlBuilder.cs | 2 +- Emby.Dlna/DlnaManager.cs | 2 +- Emby.Dlna/PlayTo/Device.cs | 2 +- Emby.Naming/Common/MediaType.cs | 6 +- Emby.Server.Implementations/ApplicationHost.cs | 2 +- .../Data/BaseSqliteRepository.cs | 4 +- .../Data/SqliteDisplayPreferencesRepository.cs | 6 +- .../Data/SqliteItemRepository.cs | 10 +-- .../Data/SqliteUserDataRepository.cs | 6 +- Emby.Server.Implementations/Dto/DtoService.cs | 8 +-- .../HttpClientManager/HttpClientManager.cs | 2 +- .../HttpServer/FileWriter.cs | 4 +- .../HttpServer/HttpListenerHost.cs | 2 +- .../HttpServer/HttpResultFactory.cs | 2 +- .../HttpServer/RangeRequestWriter.cs | 8 +-- .../Library/CoreResolutionIgnoreRule.cs | 2 +- .../Library/IgnorePatterns.cs | 6 +- .../Library/LibraryManager.cs | 14 ++-- .../Library/ResolverHelper.cs | 2 +- .../Library/Resolvers/ItemResolver.cs | 2 +- .../Library/UserDataManager.cs | 4 +- .../LiveTv/RefreshChannelsScheduledTask.cs | 2 +- .../Networking/NetworkManager.cs | 2 +- .../ScheduledTasks/ScheduledTaskWorker.cs | 18 +++--- .../ScheduledTasks/TaskManager.cs | 8 +-- .../ScheduledTasks/Tasks/DeleteCacheFileTask.cs | 8 +-- .../Tasks/DeleteTranscodeFileTask.cs | 2 +- .../ScheduledTasks/Triggers/DailyTrigger.cs | 4 +- .../ScheduledTasks/Triggers/IntervalTrigger.cs | 6 +- .../ScheduledTasks/Triggers/StartupTrigger.cs | 4 +- .../ScheduledTasks/Triggers/WeeklyTrigger.cs | 8 +-- .../Services/ServiceHandler.cs | 2 +- .../Services/UrlExtensions.cs | 2 +- .../Session/SessionManager.cs | 2 +- .../Session/SessionWebSocketListener.cs | 6 +- .../Sorting/AlbumArtistComparer.cs | 2 +- .../Sorting/AlbumComparer.cs | 2 +- .../Sorting/CriticRatingComparer.cs | 2 +- .../Sorting/DateCreatedComparer.cs | 2 +- .../Sorting/DatePlayedComparer.cs | 2 +- .../Sorting/NameComparer.cs | 2 +- .../Sorting/PlayCountComparer.cs | 2 +- .../Sorting/PremiereDateComparer.cs | 2 +- .../Sorting/ProductionYearComparer.cs | 2 +- .../Sorting/RandomComparer.cs | 2 +- .../Sorting/RuntimeComparer.cs | 2 +- .../Sorting/SortNameComparer.cs | 2 +- Emby.Server.Implementations/Udp/UdpServer.cs | 2 +- Jellyfin.Data/Entities/Artwork.cs | 14 ++-- Jellyfin.Data/Entities/Book.cs | 2 +- Jellyfin.Data/Entities/BookMetadata.cs | 4 +- Jellyfin.Data/Entities/Chapter.cs | 20 +++--- Jellyfin.Data/Entities/Collection.cs | 10 +-- Jellyfin.Data/Entities/CollectionItem.cs | 10 +-- Jellyfin.Data/Entities/Company.cs | 8 +-- Jellyfin.Data/Entities/CompanyMetadata.cs | 10 +-- Jellyfin.Data/Entities/CustomItem.cs | 2 +- Jellyfin.Data/Entities/CustomItemMetadata.cs | 2 +- Jellyfin.Data/Entities/Episode.cs | 4 +- Jellyfin.Data/Entities/EpisodeMetadata.cs | 8 +-- Jellyfin.Data/Entities/Genre.cs | 10 +-- Jellyfin.Data/Entities/Library.cs | 10 +-- Jellyfin.Data/Entities/LibraryItem.cs | 16 ++--- Jellyfin.Data/Entities/LibraryRoot.cs | 16 ++--- Jellyfin.Data/Entities/MediaFile.cs | 16 ++--- Jellyfin.Data/Entities/MediaFileStream.cs | 12 ++-- Jellyfin.Data/Entities/Metadata.cs | 30 ++++----- Jellyfin.Data/Entities/MetadataProvider.cs | 10 +-- Jellyfin.Data/Entities/MetadataProviderId.cs | 12 ++-- Jellyfin.Data/Entities/Movie.cs | 2 +- Jellyfin.Data/Entities/MovieMetadata.cs | 10 +-- Jellyfin.Data/Entities/MusicAlbum.cs | 2 +- Jellyfin.Data/Entities/MusicAlbumMetadata.cs | 8 +-- Jellyfin.Data/Entities/Person.cs | 24 +++---- Jellyfin.Data/Entities/PersonRole.cs | 16 ++--- Jellyfin.Data/Entities/Photo.cs | 2 +- Jellyfin.Data/Entities/PhotoMetadata.cs | 2 +- Jellyfin.Data/Entities/ProviderMapping.cs | 6 +- Jellyfin.Data/Entities/Rating.cs | 14 ++-- Jellyfin.Data/Entities/RatingSource.cs | 20 +++--- Jellyfin.Data/Entities/Release.cs | 10 +-- Jellyfin.Data/Entities/Season.cs | 4 +- Jellyfin.Data/Entities/SeasonMetadata.cs | 4 +- Jellyfin.Data/Entities/Series.cs | 10 +-- Jellyfin.Data/Entities/SeriesMetadata.cs | 10 +-- Jellyfin.Data/Entities/Track.cs | 4 +- Jellyfin.Data/Entities/TrackMetadata.cs | 2 +- Jellyfin.Data/Enums/PreferenceKind.cs | 2 +- Jellyfin.Server.Implementations/JellyfinDb.cs | 2 +- MediaBrowser.Api/ApiEntryPoint.cs | 4 +- MediaBrowser.Api/BaseApiService.cs | 2 +- MediaBrowser.Api/ChannelService.cs | 6 +- MediaBrowser.Api/ConfigurationService.cs | 8 +-- MediaBrowser.Api/DisplayPreferencesService.cs | 8 +-- MediaBrowser.Api/EnvironmentService.cs | 12 ++-- MediaBrowser.Api/IHasItemFields.cs | 2 +- MediaBrowser.Api/Images/ImageByNameService.cs | 10 +-- MediaBrowser.Api/Images/ImageRequest.cs | 12 ++-- MediaBrowser.Api/Images/ImageService.cs | 20 +++--- MediaBrowser.Api/Images/RemoteImageService.cs | 2 +- MediaBrowser.Api/Library/LibraryService.cs | 14 ++-- .../Library/LibraryStructureService.cs | 8 +-- MediaBrowser.Api/LiveTv/LiveTvService.cs | 14 ++-- MediaBrowser.Api/LocalizationService.cs | 12 ++-- MediaBrowser.Api/Movies/MoviesService.cs | 4 +- MediaBrowser.Api/Movies/TrailersService.cs | 6 +- MediaBrowser.Api/Music/AlbumsService.cs | 6 +- MediaBrowser.Api/PackageService.cs | 10 +-- MediaBrowser.Api/Playback/BaseStreamingService.cs | 2 +- MediaBrowser.Api/Playback/Hls/BaseHlsService.cs | 2 +- MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs | 6 +- MediaBrowser.Api/Playback/Hls/VideoHlsService.cs | 2 +- .../Playback/Progressive/AudioService.cs | 4 +- .../Progressive/BaseProgressiveStreamingService.cs | 2 +- .../Playback/Progressive/VideoService.cs | 4 +- .../Playback/StaticRemoteStreamWriter.cs | 6 +- MediaBrowser.Api/Playback/StreamRequest.cs | 2 +- MediaBrowser.Api/PlaylistService.cs | 4 +- MediaBrowser.Api/PluginService.cs | 20 +++--- .../ScheduledTasks/ScheduledTaskService.cs | 12 ++-- .../ScheduledTasksWebSocketListener.cs | 2 +- MediaBrowser.Api/SearchService.cs | 10 +-- .../Sessions/SessionInfoWebSocketListener.cs | 4 +- MediaBrowser.Api/Sessions/SessionService.cs | 4 +- MediaBrowser.Api/SimilarItemsHelper.cs | 8 +-- MediaBrowser.Api/System/ActivityLogService.cs | 2 +- .../System/ActivityLogWebSocketListener.cs | 4 +- MediaBrowser.Api/System/SystemService.cs | 8 +-- MediaBrowser.Api/TvShowsService.cs | 22 +++---- MediaBrowser.Api/UserLibrary/ArtistsService.cs | 4 +- .../UserLibrary/BaseItemsByNameService.cs | 6 +- MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs | 22 +++---- MediaBrowser.Api/UserLibrary/GenresService.cs | 6 +- MediaBrowser.Api/UserLibrary/ItemsService.cs | 10 +-- MediaBrowser.Api/UserLibrary/PersonsService.cs | 6 +- MediaBrowser.Api/UserLibrary/PlaystateService.cs | 10 +-- MediaBrowser.Api/UserLibrary/StudiosService.cs | 6 +- MediaBrowser.Api/UserLibrary/UserLibraryService.cs | 20 +++--- MediaBrowser.Api/UserLibrary/YearsService.cs | 6 +- MediaBrowser.Api/UserService.cs | 24 +++---- .../Channels/InternalChannelFeatures.cs | 2 +- .../Configuration/IServerConfigurationManager.cs | 2 +- MediaBrowser.Controller/Drawing/IImageProcessor.cs | 2 +- MediaBrowser.Controller/Dto/IDtoService.cs | 2 +- .../Entities/AggregateFolder.cs | 2 +- MediaBrowser.Controller/Entities/Audio/Audio.cs | 2 +- .../Entities/Audio/MusicAlbum.cs | 2 +- .../Entities/Audio/MusicArtist.cs | 6 +- .../Entities/Audio/MusicGenre.cs | 6 +- MediaBrowser.Controller/Entities/BaseItem.cs | 28 ++++---- .../Entities/CollectionFolder.cs | 4 +- MediaBrowser.Controller/Entities/Extensions.cs | 2 +- MediaBrowser.Controller/Entities/Folder.cs | 12 ++-- MediaBrowser.Controller/Entities/Genre.cs | 6 +- .../Entities/ICollectionFolder.cs | 2 +- .../Entities/IHasAspectRatio.cs | 2 +- .../Entities/IHasDisplayOrder.cs | 2 +- .../Entities/IHasScreenshots.cs | 2 +- MediaBrowser.Controller/Entities/LinkedChild.cs | 2 +- MediaBrowser.Controller/Entities/Movies/BoxSet.cs | 2 +- MediaBrowser.Controller/Entities/Movies/Movie.cs | 2 +- MediaBrowser.Controller/Entities/Person.cs | 4 +- MediaBrowser.Controller/Entities/Studio.cs | 6 +- MediaBrowser.Controller/Entities/TV/Episode.cs | 4 +- MediaBrowser.Controller/Entities/TV/Season.cs | 6 +- MediaBrowser.Controller/Entities/TV/Series.cs | 4 +- MediaBrowser.Controller/Entities/Trailer.cs | 2 +- MediaBrowser.Controller/Entities/UserItemData.cs | 6 +- MediaBrowser.Controller/Entities/Video.cs | 2 +- MediaBrowser.Controller/Entities/Year.cs | 6 +- .../Extensions/StringExtensions.cs | 2 +- MediaBrowser.Controller/IServerApplicationHost.cs | 2 +- MediaBrowser.Controller/IServerApplicationPaths.cs | 16 ++--- MediaBrowser.Controller/Library/IIntroProvider.cs | 2 +- MediaBrowser.Controller/Library/ILibraryManager.cs | 14 ++-- .../Library/ILibraryPostScanTask.cs | 2 +- MediaBrowser.Controller/Library/IMetadataSaver.cs | 2 +- MediaBrowser.Controller/Library/ISearchEngine.cs | 2 +- .../Library/IUserDataManager.cs | 6 +- MediaBrowser.Controller/Library/IUserManager.cs | 2 +- .../Library/ItemChangeEventArgs.cs | 2 +- MediaBrowser.Controller/Library/ItemResolveArgs.cs | 2 +- .../Library/PlaybackProgressEventArgs.cs | 2 +- MediaBrowser.Controller/Library/Profiler.cs | 8 +-- MediaBrowser.Controller/Library/SearchHintInfo.cs | 2 +- MediaBrowser.Controller/Library/TVUtils.cs | 2 +- .../Library/UserDataSaveEventArgs.cs | 2 +- MediaBrowser.Controller/LiveTv/ChannelInfo.cs | 6 +- MediaBrowser.Controller/LiveTv/ILiveTvManager.cs | 2 +- MediaBrowser.Controller/LiveTv/LiveTvProgram.cs | 2 +- MediaBrowser.Controller/LiveTv/ProgramInfo.cs | 6 +- MediaBrowser.Controller/LiveTv/RecordingInfo.cs | 4 +- .../MediaEncoding/EncodingHelper.cs | 14 ++-- .../MediaEncoding/EncodingJobInfo.cs | 18 +++--- .../MediaEncoding/IMediaEncoder.cs | 2 +- .../MediaEncoding/MediaEncoderHelpers.cs | 2 +- .../Net/BasePeriodicWebSocketListener.cs | 10 +-- MediaBrowser.Controller/Net/IHttpResultFactory.cs | 2 +- MediaBrowser.Controller/Net/IHttpServer.cs | 6 +- MediaBrowser.Controller/Net/IWebSocketListener.cs | 2 +- .../Net/WebSocketMessageInfo.cs | 2 +- .../Persistence/IItemRepository.cs | 8 +-- MediaBrowser.Controller/Persistence/IRepository.cs | 4 +- .../Persistence/IUserDataRepository.cs | 6 +- .../Plugins/IPluginConfigurationPage.cs | 8 +-- .../Providers/IMetadataProvider.cs | 2 +- .../Providers/MetadataRefreshMode.cs | 8 +-- .../Providers/MetadataResult.cs | 2 +- .../Providers/VideoContentType.cs | 6 +- .../Resolvers/BaseItemResolver.cs | 4 +- MediaBrowser.Controller/Resolvers/IItemResolver.cs | 2 +- .../Resolvers/ResolverPriority.cs | 10 +-- MediaBrowser.Controller/Session/ISessionManager.cs | 8 +-- .../Sorting/IBaseItemComparer.cs | 2 +- .../Sorting/IUserBaseItemComparer.cs | 2 +- .../Sync/IRemoteSyncProvider.cs | 2 +- .../Parsers/BaseItemXmlParser.cs | 10 +-- .../BdInfo/BdInfoExaminer.cs | 2 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 4 +- .../Probing/FFProbeHelpers.cs | 8 +-- .../Probing/ProbeResultNormalizer.cs | 10 +-- .../Subtitles/SubtitleEncoder.cs | 2 +- MediaBrowser.Model/Channels/ChannelFeatures.cs | 2 +- MediaBrowser.Model/Channels/ChannelQuery.cs | 4 +- .../Configuration/BaseApplicationConfiguration.cs | 2 +- .../Configuration/MetadataPluginType.cs | 2 +- .../Configuration/ServerConfiguration.cs | 6 +- .../Configuration/UserConfiguration.cs | 2 +- MediaBrowser.Model/Dlna/AudioOptions.cs | 2 +- MediaBrowser.Model/Dlna/StreamInfo.cs | 26 ++++---- MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs | 8 +-- MediaBrowser.Model/Dto/BaseItemDto.cs | 2 +- MediaBrowser.Model/Dto/ImageOptions.cs | 2 +- MediaBrowser.Model/Dto/MediaSourceInfo.cs | 2 +- MediaBrowser.Model/Entities/DisplayPreferences.cs | 2 +- MediaBrowser.Model/Entities/MediaStream.cs | 2 +- MediaBrowser.Model/Entities/MetadataProvider.cs | 12 ++-- MediaBrowser.Model/Entities/VirtualFolderInfo.cs | 2 +- MediaBrowser.Model/IO/IZipClient.cs | 2 +- MediaBrowser.Model/LiveTv/ChannelType.cs | 2 +- MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs | 8 +-- MediaBrowser.Model/LiveTv/RecordingQuery.cs | 4 +- MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs | 2 +- MediaBrowser.Model/Net/NetworkShare.cs | 10 +-- .../Notifications/NotificationOption.cs | 2 +- MediaBrowser.Model/Querying/ItemFields.cs | 74 +++++++++++----------- MediaBrowser.Model/Querying/ItemSortBy.cs | 2 +- MediaBrowser.Model/Querying/NextUpQuery.cs | 4 +- MediaBrowser.Model/Querying/QueryResult.cs | 2 +- .../Querying/UpcomingEpisodesQuery.cs | 4 +- MediaBrowser.Model/Search/SearchQuery.cs | 4 +- MediaBrowser.Model/Services/ApiMemberAttribute.cs | 2 +- MediaBrowser.Model/Services/IRequest.cs | 8 +-- .../Services/IRequiresRequestStream.cs | 2 +- MediaBrowser.Model/Session/PlayRequest.cs | 4 +- MediaBrowser.Model/Sync/SyncCategory.cs | 6 +- MediaBrowser.Model/System/SystemInfo.cs | 2 +- MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs | 2 +- MediaBrowser.Model/Tasks/ITaskManager.cs | 2 +- MediaBrowser.Providers/Manager/ImageSaver.cs | 6 +- .../Manager/ItemImageProvider.cs | 4 +- MediaBrowser.Providers/Manager/ProviderManager.cs | 2 +- .../MediaInfo/AudioImageProvider.cs | 2 +- .../MediaInfo/FFProbeAudioInfo.cs | 2 +- .../MediaInfo/FFProbeVideoInfo.cs | 2 +- .../Plugins/Tmdb/Models/Search/MovieResult.cs | 2 +- .../Plugins/Tmdb/Movies/TmdbMovieProvider.cs | 4 +- .../TV/MissingEpisodeProvider.cs | 4 +- RSSDP/DiscoveredSsdpDevice.cs | 2 +- 269 files changed, 816 insertions(+), 816 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index 6ded76f7dd..0f53bf2a45 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -705,7 +705,7 @@ namespace Emby.Dlna.Didl } /// - /// Adds fields used by both items and folders + /// Adds fields used by both items and folders. /// private void AddCommonFields(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter) { diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index e5f4839508..6b7063c5d7 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -473,7 +473,7 @@ namespace Emby.Dlna /// /// Recreates the object using serialization, to ensure it's not a subclass. - /// If it's a subclass it may not serlialize properly to xml (different root element tag name) + /// If it's a subclass it may not serlialize properly to xml (different root element tag name). /// /// /// diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index 86b72e264d..6442ac5cc3 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -232,7 +232,7 @@ namespace Emby.Dlna.PlayTo } /// - /// Sets volume on a scale of 0-100 + /// Sets volume on a scale of 0-100. /// public async Task SetVolume(int value, CancellationToken cancellationToken) { diff --git a/Emby.Naming/Common/MediaType.cs b/Emby.Naming/Common/MediaType.cs index cc18ce4cdd..148833765f 100644 --- a/Emby.Naming/Common/MediaType.cs +++ b/Emby.Naming/Common/MediaType.cs @@ -5,17 +5,17 @@ namespace Emby.Naming.Common public enum MediaType { /// - /// The audio + /// The audio. /// Audio = 0, /// - /// The photo + /// The photo. /// Photo = 1, /// - /// The video + /// The video. /// Video = 2 } diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 5772dd479d..23f0571a1d 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -956,7 +956,7 @@ namespace Emby.Server.Implementations } /// - /// Notifies that the kernel that a change has been made that requires a restart + /// Notifies that the kernel that a change has been made that requires a restart. /// public void NotifyPendingRestart() { diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index 53c9ccdbf3..8a3716380b 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -247,12 +247,12 @@ namespace Emby.Server.Implementations.Data public enum SynchronousMode { /// - /// SQLite continues without syncing as soon as it has handed data off to the operating system + /// SQLite continues without syncing as soon as it has handed data off to the operating system. /// Off = 0, /// - /// SQLite database engine will still sync at the most critical moments + /// SQLite database engine will still sync at the most critical moments. /// Normal = 1, diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs index 63d0321b73..5597155a8d 100644 --- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs @@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.Data } /// - /// Opens the connection to the database + /// Opens the connection to the database. /// /// Task. private void InitializeInternal() @@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.Data } /// - /// Save the display preferences associated with an item in the repo + /// Save the display preferences associated with an item in the repo. /// /// The display preferences. /// The user id. @@ -122,7 +122,7 @@ namespace Emby.Server.Implementations.Data } /// - /// Save all display preferences associated with a user in the repo + /// Save all display preferences associated with a user in the repo. /// /// The display preferences. /// The user id. diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index d331256611..13b581fc08 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Data protected override TempStoreMode TempStore => TempStoreMode.Memory; /// - /// Opens the connection to the database + /// Opens the connection to the database. /// public void Initialize(SqliteUserDataRepository userDataRepo, IUserManager userManager) { @@ -548,7 +548,7 @@ namespace Emby.Server.Implementations.Data } /// - /// Save a standard item in the repo + /// Save a standard item in the repo. /// /// The item. /// The cancellation token. @@ -1204,7 +1204,7 @@ namespace Emby.Server.Implementations.Data } /// - /// Internal retrieve from items or users table + /// Internal retrieve from items or users table. /// /// The id. /// BaseItem. @@ -1918,7 +1918,7 @@ namespace Emby.Server.Implementations.Data } /// - /// Gets chapters for an item + /// Gets chapters for an item. /// /// The item. /// IEnumerable{ChapterInfo}. @@ -1946,7 +1946,7 @@ namespace Emby.Server.Implementations.Data } /// - /// Gets a single chapter for an item + /// Gets a single chapter for an item. /// /// The item. /// The index. diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 7e66fa072e..663c226a2c 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -235,7 +235,7 @@ namespace Emby.Server.Implementations.Data } /// - /// Persist all user data for the specified user + /// Persist all user data for the specified user. /// private void PersistAllUserData(long internalUserId, UserItemData[] userDataList, CancellationToken cancellationToken) { @@ -309,7 +309,7 @@ namespace Emby.Server.Implementations.Data } /// - /// Return all user-data associated with the given user + /// Return all user-data associated with the given user. /// /// /// @@ -339,7 +339,7 @@ namespace Emby.Server.Implementations.Data } /// - /// Read a row from the specified reader into the provided userData object + /// Read a row from the specified reader into the provided userData object. /// /// private UserItemData ReadRow(IReadOnlyList reader) diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 41ff7e3abf..c3bb7a8144 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -74,7 +74,7 @@ namespace Emby.Server.Implementations.Dto } /// - /// Converts a BaseItem to a DTOBaseItem + /// Converts a BaseItem to a DTOBaseItem. /// /// The item. /// The fields. @@ -442,7 +442,7 @@ namespace Emby.Server.Implementations.Dto } /// - /// Gets client-side Id of a server-side BaseItem + /// Gets client-side Id of a server-side BaseItem. /// /// The item. /// System.String. @@ -537,7 +537,7 @@ namespace Emby.Server.Implementations.Dto } /// - /// Attaches People DTO's to a DTOBaseItem + /// Attaches People DTO's to a DTOBaseItem. /// /// The dto. /// The item. @@ -725,7 +725,7 @@ namespace Emby.Server.Implementations.Dto } /// - /// Sets simple property values on a DTOBaseItem + /// Sets simple property values on a DTOBaseItem. /// /// The dto. /// The item. diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 87977494a1..25adc58126 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.HttpClientManager => SendAsync(options, HttpMethod.Get); /// - /// Performs a GET request and returns the resulting stream + /// Performs a GET request and returns the resulting stream. /// /// The options. /// Task{Stream}. diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 0b61e40b05..590eee1b48 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -32,12 +32,12 @@ namespace Emby.Server.Implementations.HttpServer private readonly IFileSystem _fileSystem; /// - /// The _options + /// The _options. /// private readonly IDictionary _options = new Dictionary(); /// - /// The _requested ranges + /// The _requested ranges. /// private List> _requestedRanges; diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 1b6e4b5547..ca8cb40873 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -591,7 +591,7 @@ namespace Emby.Server.Implementations.HttpServer } /// - /// Get the default CORS headers + /// Get the default CORS headers. /// /// /// diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index d254d394fa..ad31b3e1ea 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -692,7 +692,7 @@ namespace Emby.Server.Implementations.HttpServer /// - /// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that + /// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that. /// /// The date. /// DateTime. diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index 8b9028f6bc..d838690773 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -34,17 +34,17 @@ namespace Emby.Server.Implementations.HttpServer private const int BufferSize = 81920; /// - /// The _options + /// The _options. /// private readonly Dictionary _options = new Dictionary(); /// - /// The us culture + /// The us culture. /// private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); /// - /// Additional HTTP Headers + /// Additional HTTP Headers. /// /// The headers. public IDictionary Headers => _options; @@ -110,7 +110,7 @@ namespace Emby.Server.Implementations.HttpServer } /// - /// The _requested ranges + /// The _requested ranges. /// private List> _requestedRanges; /// diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs index 218e5a0c6d..e140009ea9 100644 --- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs +++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs @@ -8,7 +8,7 @@ using MediaBrowser.Model.IO; namespace Emby.Server.Implementations.Library { /// - /// Provides the core resolver ignore rules + /// Provides the core resolver ignore rules. /// public class CoreResolutionIgnoreRule : IResolverIgnoreRule { diff --git a/Emby.Server.Implementations/Library/IgnorePatterns.cs b/Emby.Server.Implementations/Library/IgnorePatterns.cs index d12b5855b2..8c40989489 100644 --- a/Emby.Server.Implementations/Library/IgnorePatterns.cs +++ b/Emby.Server.Implementations/Library/IgnorePatterns.cs @@ -4,12 +4,12 @@ using DotNet.Globbing; namespace Emby.Server.Implementations.Library { /// - /// Glob patterns for files to ignore + /// Glob patterns for files to ignore. /// public static class IgnorePatterns { /// - /// Files matching these glob patterns will be ignored + /// Files matching these glob patterns will be ignored. /// public static readonly string[] Patterns = new string[] { @@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.Library private static readonly Glob[] _globs = Patterns.Select(p => Glob.Parse(p, _globOptions)).ToArray(); /// - /// Returns true if the supplied path should be ignored + /// Returns true if the supplied path should be ignored. /// public static bool ShouldIgnore(string path) { diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 1d4651da20..799ce28a06 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -97,13 +97,13 @@ namespace Emby.Server.Implementations.Library private IIntroProvider[] IntroProviders { get; set; } /// - /// Gets or sets the list of entity resolution ignore rules + /// Gets or sets the list of entity resolution ignore rules. /// /// The entity resolution ignore rules. private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; } /// - /// Gets or sets the list of currently registered entity resolvers + /// Gets or sets the list of currently registered entity resolvers. /// /// The entity resolvers enumerable. private IItemResolver[] EntityResolvers { get; set; } @@ -209,12 +209,12 @@ namespace Emby.Server.Implementations.Library } /// - /// The _root folder + /// The _root folder. /// private volatile AggregateFolder _rootFolder; /// - /// The _root folder sync lock + /// The _root folder sync lock. /// private readonly object _rootFolderSyncLock = new object(); @@ -627,7 +627,7 @@ namespace Emby.Server.Implementations.Library } /// - /// Determines whether a path should be ignored based on its contents - called after the contents have been read + /// Determines whether a path should be ignored based on its contents - called after the contents have been read. /// /// The args. /// true if XXXX, false otherwise @@ -909,7 +909,7 @@ namespace Emby.Server.Implementations.Library } /// - /// Gets a Genre + /// Gets a Genre. /// /// The name. /// Task{Genre}. @@ -990,7 +990,7 @@ namespace Emby.Server.Implementations.Library } /// - /// Reloads the root media folder + /// Reloads the root media folder. /// /// The progress. /// The cancellation token. diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs index 7ca15b4e55..4e4cac75bf 100644 --- a/Emby.Server.Implementations/Library/ResolverHelper.cs +++ b/Emby.Server.Implementations/Library/ResolverHelper.cs @@ -107,7 +107,7 @@ namespace Emby.Server.Implementations.Library } /// - /// Ensures DateCreated and DateModified have values + /// Ensures DateCreated and DateModified have values. /// /// The file system. /// The item. diff --git a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs index 32ccc7fdd4..9ca76095b2 100644 --- a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs @@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.Library.Resolvers public virtual ResolverPriority Priority => ResolverPriority.First; /// - /// Sets initial values on the newly resolved item + /// Sets initial values on the newly resolved item. /// /// The item. /// The args. diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index 9e17fa672c..175b3af572 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -103,7 +103,7 @@ namespace Emby.Server.Implementations.Library } /// - /// Retrieve all user data for the given user + /// Retrieve all user data for the given user. /// /// /// @@ -188,7 +188,7 @@ namespace Emby.Server.Implementations.Library } /// - /// Converts a UserItemData to a DTOUserItemData + /// Converts a UserItemData to a DTOUserItemData. /// /// The data. /// DtoUserItemData. diff --git a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs index 8e7d60a15a..f1b61f7c7d 100644 --- a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs +++ b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs @@ -35,7 +35,7 @@ namespace Emby.Server.Implementations.LiveTv } /// - /// Creates the triggers that define when the task will run + /// Creates the triggers that define when the task will run. /// /// IEnumerable{BaseTaskTrigger}. public IEnumerable GetDefaultTriggers() diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index 2909cdd0db..d12b5a9371 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -411,7 +411,7 @@ namespace Emby.Server.Implementations.Networking } /// - /// Gets a random port number that is currently available + /// Gets a random port number that is currently available. /// /// System.Int32. public int GetRandomUnusedTcpPort() diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index e58c335a85..1995d1309f 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -18,7 +18,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks { /// - /// Class ScheduledTaskWorker + /// Class ScheduledTaskWorker. /// public class ScheduledTaskWorker : IScheduledTaskWorker { @@ -111,11 +111,11 @@ namespace Emby.Server.Implementations.ScheduledTasks private bool _readFromFile = false; /// - /// The _last execution result + /// The _last execution result. /// private TaskResult _lastExecutionResult; /// - /// The _last execution result sync lock + /// The _last execution result sync lock. /// private readonly object _lastExecutionResultSyncLock = new object(); /// @@ -182,7 +182,7 @@ namespace Emby.Server.Implementations.ScheduledTasks public string Category => ScheduledTask.Category; /// - /// Gets the current cancellation token + /// Gets the current cancellation token. /// /// The current cancellation token source. private CancellationTokenSource CurrentCancellationTokenSource { get; set; } @@ -278,7 +278,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// - /// The _id + /// The _id. /// private string _id; @@ -358,7 +358,7 @@ namespace Emby.Server.Implementations.ScheduledTasks private Task _currentTask; /// - /// Executes the task + /// Executes the task. /// /// Task options. /// Task. @@ -453,7 +453,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// - /// Stops the task if it is currently executing + /// Stops the task if it is currently executing. /// /// Cannot cancel a Task unless it is in the Running state. public void Cancel() @@ -683,7 +683,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// - /// Converts a TaskTriggerInfo into a concrete BaseTaskTrigger + /// Converts a TaskTriggerInfo into a concrete BaseTaskTrigger. /// /// The info. /// BaseTaskTrigger. @@ -753,7 +753,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// - /// Disposes each trigger + /// Disposes each trigger. /// private void DisposeTriggers() { diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index 94220ac5d1..0ad4253e4b 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -15,7 +15,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks { /// - /// Class TaskManager + /// Class TaskManager. /// public class TaskManager : ITaskManager { @@ -23,13 +23,13 @@ namespace Emby.Server.Implementations.ScheduledTasks public event EventHandler TaskCompleted; /// - /// Gets the list of Scheduled Tasks + /// Gets the list of Scheduled Tasks. /// /// The scheduled tasks. public IScheduledTaskWorker[] ScheduledTasks { get; private set; } /// - /// The _task queue + /// The _task queue. /// private readonly ConcurrentQueue> _taskQueue = new ConcurrentQueue>(); @@ -81,7 +81,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// - /// Cancels if running + /// Cancels if running. /// /// public void CancelIfRunning() diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs index 966b549b23..e29fcfb5f1 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs @@ -13,7 +13,7 @@ using MediaBrowser.Model.Globalization; namespace Emby.Server.Implementations.ScheduledTasks.Tasks { /// - /// Deletes old cache files + /// Deletes old cache files. /// public class DeleteCacheFileTask : IScheduledTask, IConfigurableScheduledTask { @@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks } /// - /// Creates the triggers that define when the task will run + /// Creates the triggers that define when the task will run. /// /// IEnumerable{BaseTaskTrigger}. public IEnumerable GetDefaultTriggers() @@ -57,7 +57,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks } /// - /// Returns the task to be executed + /// Returns the task to be executed. /// /// The cancellation token. /// The progress. @@ -93,7 +93,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// - /// Deletes the cache files from directory with a last write time less than a given date + /// Deletes the cache files from directory with a last write time less than a given date. /// /// The task cancellation token. /// The directory. diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs index 53cf9a0a5f..6914081673 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs @@ -13,7 +13,7 @@ using MediaBrowser.Model.Globalization; namespace Emby.Server.Implementations.ScheduledTasks.Tasks { /// - /// Deletes all transcoding temp files + /// Deletes all transcoding temp files. /// public class DeleteTranscodeFileTask : IScheduledTask, IConfigurableScheduledTask { diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs index c7819d4c0f..eb628ec5fe 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs @@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.ScheduledTasks private Timer Timer { get; set; } /// - /// Stars waiting for the trigger action + /// Stars waiting for the trigger action. /// /// The last result. /// The logger. @@ -51,7 +51,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// - /// Stops waiting for the trigger action + /// Stops waiting for the trigger action. /// public void Stop() { diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs index 74cd4ef1ea..247a6785ad 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks { /// - /// Represents a task trigger that runs repeatedly on an interval + /// Represents a task trigger that runs repeatedly on an interval. /// public class IntervalTrigger : ITaskTrigger { @@ -31,7 +31,7 @@ namespace Emby.Server.Implementations.ScheduledTasks private DateTime _lastStartDate; /// - /// Stars waiting for the trigger action + /// Stars waiting for the trigger action. /// /// The last result. /// The logger. @@ -70,7 +70,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// - /// Stops waiting for the trigger action + /// Stops waiting for the trigger action. /// public void Stop() { diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs index e171a9e9f5..96e5d88970 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// - /// Stars waiting for the trigger action + /// Stars waiting for the trigger action. /// /// The last result. /// The logger. @@ -42,7 +42,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// - /// Stops waiting for the trigger action + /// Stops waiting for the trigger action. /// public void Stop() { diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs index ad0b57af6e..4f1bf5c19a 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs @@ -6,12 +6,12 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks { /// - /// Represents a task trigger that fires on a weekly basis + /// Represents a task trigger that fires on a weekly basis. /// public class WeeklyTrigger : ITaskTrigger { /// - /// Get the time of day to trigger the task to run + /// Get the time of day to trigger the task to run. /// /// The time of day. public TimeSpan TimeOfDay { get; set; } @@ -34,7 +34,7 @@ namespace Emby.Server.Implementations.ScheduledTasks private Timer Timer { get; set; } /// - /// Stars waiting for the trigger action + /// Stars waiting for the trigger action. /// /// The last result. /// The logger. @@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// - /// Stops waiting for the trigger action + /// Stops waiting for the trigger action. /// public void Stop() { diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs index 7f44357e1b..a42f88ea09 100644 --- a/Emby.Server.Implementations/Services/ServiceHandler.cs +++ b/Emby.Server.Implementations/Services/ServiceHandler.cs @@ -180,7 +180,7 @@ namespace Emby.Server.Implementations.Services => string.Equals(method, expected, StringComparison.OrdinalIgnoreCase); /// - /// Duplicate params have their values joined together in a comma-delimited string + /// Duplicate params have their values joined together in a comma-delimited string. /// private static Dictionary GetFlattenedRequestParams(HttpRequest request) { diff --git a/Emby.Server.Implementations/Services/UrlExtensions.cs b/Emby.Server.Implementations/Services/UrlExtensions.cs index e3b6aa1975..92e36b60e0 100644 --- a/Emby.Server.Implementations/Services/UrlExtensions.cs +++ b/Emby.Server.Implementations/Services/UrlExtensions.cs @@ -9,7 +9,7 @@ namespace Emby.Server.Implementations.Services /// Donated by Ivan Korneliuk from his post: /// http://korneliuk.blogspot.com/2012/08/servicestack-reusing-dtos.html /// - /// Modified to only allow using routes matching the supplied HTTP Verb + /// Modified to only allow using routes matching the supplied HTTP Verb. /// public static class UrlExtensions { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 07e443ef52..75fdedd107 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -843,7 +843,7 @@ namespace Emby.Server.Implementations.Session } /// - /// Used to report that playback has ended for an item + /// Used to report that playback has ended for an item. /// /// The info. /// Task. diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index ef32c692c1..a891e03f93 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Session { /// - /// Class SessionWebSocketListener + /// Class SessionWebSocketListener. /// public sealed class SessionWebSocketListener : IWebSocketListener, IDisposable { @@ -34,12 +34,12 @@ namespace Emby.Server.Implementations.Session public const float ForceKeepAliveFactor = 0.75f; /// - /// The _session manager + /// The _session manager. /// private readonly ISessionManager _sessionManager; /// - /// The _logger + /// The _logger. /// private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; diff --git a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs index 0804b01fca..7657cc74e1 100644 --- a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs +++ b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs @@ -8,7 +8,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// - /// Class AlbumArtistComparer + /// Class AlbumArtistComparer. /// public class AlbumArtistComparer : IBaseItemComparer { diff --git a/Emby.Server.Implementations/Sorting/AlbumComparer.cs b/Emby.Server.Implementations/Sorting/AlbumComparer.cs index 3831a0d2d8..7dfdd9ecff 100644 --- a/Emby.Server.Implementations/Sorting/AlbumComparer.cs +++ b/Emby.Server.Implementations/Sorting/AlbumComparer.cs @@ -7,7 +7,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// - /// Class AlbumComparer + /// Class AlbumComparer. /// public class AlbumComparer : IBaseItemComparer { diff --git a/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs b/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs index adb78dec53..fa136c36d0 100644 --- a/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs @@ -5,7 +5,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// - /// Class CriticRatingComparer + /// Class CriticRatingComparer. /// public class CriticRatingComparer : IBaseItemComparer { diff --git a/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs b/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs index 8501bd9ee8..ea981e8402 100644 --- a/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// - /// Class DateCreatedComparer + /// Class DateCreatedComparer. /// public class DateCreatedComparer : IBaseItemComparer { diff --git a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs index 5e527ea257..16bd2aff86 100644 --- a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs @@ -8,7 +8,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// - /// Class DatePlayedComparer + /// Class DatePlayedComparer. /// public class DatePlayedComparer : IUserBaseItemComparer { diff --git a/Emby.Server.Implementations/Sorting/NameComparer.cs b/Emby.Server.Implementations/Sorting/NameComparer.cs index 4eb1549f58..da020d8d8e 100644 --- a/Emby.Server.Implementations/Sorting/NameComparer.cs +++ b/Emby.Server.Implementations/Sorting/NameComparer.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// - /// Class NameComparer + /// Class NameComparer. /// public class NameComparer : IBaseItemComparer { diff --git a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs index afbaaf6ab8..5c28303229 100644 --- a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs +++ b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs @@ -7,7 +7,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// - /// Class PlayCountComparer + /// Class PlayCountComparer. /// public class PlayCountComparer : IUserBaseItemComparer { diff --git a/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs index 0c944a7a02..a24dc4030f 100644 --- a/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs +++ b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// - /// Class PremiereDateComparer + /// Class PremiereDateComparer. /// public class PremiereDateComparer : IBaseItemComparer { diff --git a/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs b/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs index 472a07eb36..e2857df0b9 100644 --- a/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs +++ b/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs @@ -5,7 +5,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// - /// Class ProductionYearComparer + /// Class ProductionYearComparer. /// public class ProductionYearComparer : IBaseItemComparer { diff --git a/Emby.Server.Implementations/Sorting/RandomComparer.cs b/Emby.Server.Implementations/Sorting/RandomComparer.cs index bde8b4534d..7739d04182 100644 --- a/Emby.Server.Implementations/Sorting/RandomComparer.cs +++ b/Emby.Server.Implementations/Sorting/RandomComparer.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// - /// Class RandomComparer + /// Class RandomComparer. /// public class RandomComparer : IBaseItemComparer { diff --git a/Emby.Server.Implementations/Sorting/RuntimeComparer.cs b/Emby.Server.Implementations/Sorting/RuntimeComparer.cs index 1d2bdde260..f165123eab 100644 --- a/Emby.Server.Implementations/Sorting/RuntimeComparer.cs +++ b/Emby.Server.Implementations/Sorting/RuntimeComparer.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// - /// Class RuntimeComparer + /// Class RuntimeComparer. /// public class RuntimeComparer : IBaseItemComparer { diff --git a/Emby.Server.Implementations/Sorting/SortNameComparer.cs b/Emby.Server.Implementations/Sorting/SortNameComparer.cs index cc0571c782..93389fc3e8 100644 --- a/Emby.Server.Implementations/Sorting/SortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SortNameComparer.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Querying; namespace Emby.Server.Implementations.Sorting { /// - /// Class SortNameComparer + /// Class SortNameComparer. /// public class SortNameComparer : IBaseItemComparer { diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index a26f714b12..bf8a436b4c 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -18,7 +18,7 @@ namespace Emby.Server.Implementations.Udp public sealed class UdpServer : IDisposable { /// - /// The _logger + /// The _logger. /// private readonly ILogger _logger; private readonly IServerApplicationHost _appHost; diff --git a/Jellyfin.Data/Entities/Artwork.cs b/Jellyfin.Data/Entities/Artwork.cs index 214fb4cb1b..891904cc3e 100644 --- a/Jellyfin.Data/Entities/Artwork.cs +++ b/Jellyfin.Data/Entities/Artwork.cs @@ -24,7 +24,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// /// @@ -64,7 +64,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Id + /// Backing field for Id. /// internal int _Id; /// @@ -77,7 +77,7 @@ namespace Jellyfin.Data.Entities partial void GetId(ref int result); /// - /// Identity, Indexed, Required + /// Identity, Indexed, Required. /// [Key] [Required] @@ -101,7 +101,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Path + /// Backing field for Path. /// protected string _Path; /// @@ -139,7 +139,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Kind + /// Backing field for Kind. /// internal Enums.ArtKind _Kind; /// @@ -152,7 +152,7 @@ namespace Jellyfin.Data.Entities partial void GetKind(ref Enums.ArtKind result); /// - /// Indexed, Required + /// Indexed, Required. /// [Required] public Enums.ArtKind Kind @@ -175,7 +175,7 @@ namespace Jellyfin.Data.Entities } /// - /// Required, ConcurrenyToken + /// Required, ConcurrenyToken. /// [ConcurrencyCheck] [Required] diff --git a/Jellyfin.Data/Entities/Book.cs b/Jellyfin.Data/Entities/Book.cs index faefc74000..c4d12496e6 100644 --- a/Jellyfin.Data/Entities/Book.cs +++ b/Jellyfin.Data/Entities/Book.cs @@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// This is whats gets displayed in the Urls and API requests. This could also be a string. public Book(Guid urlid, DateTime dateadded) diff --git a/Jellyfin.Data/Entities/BookMetadata.cs b/Jellyfin.Data/Entities/BookMetadata.cs index dd389b64a7..474f906a1c 100644 --- a/Jellyfin.Data/Entities/BookMetadata.cs +++ b/Jellyfin.Data/Entities/BookMetadata.cs @@ -27,7 +27,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// The title or name of the object /// ISO-639-3 3-character language codes @@ -64,7 +64,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for ISBN + /// Backing field for ISBN. /// protected long? _ISBN; /// diff --git a/Jellyfin.Data/Entities/Chapter.cs b/Jellyfin.Data/Entities/Chapter.cs index 9b3a5e8275..5a5cdaa8f8 100644 --- a/Jellyfin.Data/Entities/Chapter.cs +++ b/Jellyfin.Data/Entities/Chapter.cs @@ -25,7 +25,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// ISO-639-3 3-character language codes /// @@ -60,7 +60,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Id + /// Backing field for Id. /// internal int _Id; /// @@ -73,7 +73,7 @@ namespace Jellyfin.Data.Entities partial void GetId(ref int result); /// - /// Identity, Indexed, Required + /// Identity, Indexed, Required. /// [Key] [Required] @@ -98,7 +98,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Name + /// Backing field for Name. /// protected string _Name; /// @@ -135,7 +135,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Language + /// Backing field for Language. /// protected string _Language; /// @@ -149,7 +149,7 @@ namespace Jellyfin.Data.Entities /// /// Required, Min length = 3, Max length = 3 - /// ISO-639-3 3-character language codes + /// ISO-639-3 3-character language codes. /// [Required] [MinLength(3)] @@ -175,7 +175,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for TimeStart + /// Backing field for TimeStart. /// protected long _TimeStart; /// @@ -188,7 +188,7 @@ namespace Jellyfin.Data.Entities partial void GetTimeStart(ref long result); /// - /// Required + /// Required. /// [Required] public long TimeStart @@ -211,7 +211,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for TimeEnd + /// Backing field for TimeEnd. /// protected long? _TimeEnd; /// @@ -243,7 +243,7 @@ namespace Jellyfin.Data.Entities } /// - /// Required, ConcurrenyToken + /// Required, ConcurrenyToken. /// [ConcurrencyCheck] [Required] diff --git a/Jellyfin.Data/Entities/Collection.cs b/Jellyfin.Data/Entities/Collection.cs index c040cfe336..87c9487c06 100644 --- a/Jellyfin.Data/Entities/Collection.cs +++ b/Jellyfin.Data/Entities/Collection.cs @@ -9,7 +9,7 @@ namespace Jellyfin.Data.Entities partial void Init(); /// - /// Default constructor + /// Default constructor. /// public Collection() { @@ -23,7 +23,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Id + /// Backing field for Id. /// internal int _Id; /// @@ -36,7 +36,7 @@ namespace Jellyfin.Data.Entities partial void GetId(ref int result); /// - /// Identity, Indexed, Required + /// Identity, Indexed, Required. /// [Key] [Required] @@ -61,7 +61,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Name + /// Backing field for Name. /// protected string _Name; /// @@ -98,7 +98,7 @@ namespace Jellyfin.Data.Entities } /// - /// Required, ConcurrenyToken + /// Required, ConcurrenyToken. /// [ConcurrencyCheck] [Required] diff --git a/Jellyfin.Data/Entities/CollectionItem.cs b/Jellyfin.Data/Entities/CollectionItem.cs index c5e54c3a2d..fc3705fe01 100644 --- a/Jellyfin.Data/Entities/CollectionItem.cs +++ b/Jellyfin.Data/Entities/CollectionItem.cs @@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// /// @@ -67,7 +67,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Id + /// Backing field for Id. /// internal int _Id; /// @@ -80,7 +80,7 @@ namespace Jellyfin.Data.Entities partial void GetId(ref int result); /// - /// Identity, Indexed, Required + /// Identity, Indexed, Required. /// [Key] [Required] @@ -105,7 +105,7 @@ namespace Jellyfin.Data.Entities } /// - /// Required, ConcurrenyToken + /// Required, ConcurrenyToken. /// [ConcurrencyCheck] [Required] @@ -121,7 +121,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Required + /// Required. /// [ForeignKey("LibraryItem_Id")] public virtual LibraryItem LibraryItem { get; set; } diff --git a/Jellyfin.Data/Entities/Company.cs b/Jellyfin.Data/Entities/Company.cs index 7d6f3b2076..2af05b8a13 100644 --- a/Jellyfin.Data/Entities/Company.cs +++ b/Jellyfin.Data/Entities/Company.cs @@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// /// @@ -75,7 +75,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Id + /// Backing field for Id. /// internal int _Id; /// @@ -88,7 +88,7 @@ namespace Jellyfin.Data.Entities partial void GetId(ref int result); /// - /// Identity, Indexed, Required + /// Identity, Indexed, Required. /// [Key] [Required] @@ -113,7 +113,7 @@ namespace Jellyfin.Data.Entities } /// - /// Required, ConcurrenyToken + /// Required, ConcurrenyToken. /// [ConcurrencyCheck] [Required] diff --git a/Jellyfin.Data/Entities/CompanyMetadata.cs b/Jellyfin.Data/Entities/CompanyMetadata.cs index 1ad03b4f9c..64d59fbd24 100644 --- a/Jellyfin.Data/Entities/CompanyMetadata.cs +++ b/Jellyfin.Data/Entities/CompanyMetadata.cs @@ -24,7 +24,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// The title or name of the object /// ISO-639-3 3-character language codes @@ -60,7 +60,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Description + /// Backing field for Description. /// protected string _Description; /// @@ -97,7 +97,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Headquarters + /// Backing field for Headquarters. /// protected string _Headquarters; /// @@ -134,7 +134,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Country + /// Backing field for Country. /// protected string _Country; /// @@ -171,7 +171,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Homepage + /// Backing field for Homepage. /// protected string _Homepage; /// diff --git a/Jellyfin.Data/Entities/CustomItem.cs b/Jellyfin.Data/Entities/CustomItem.cs index 5f6fc3a231..4463915914 100644 --- a/Jellyfin.Data/Entities/CustomItem.cs +++ b/Jellyfin.Data/Entities/CustomItem.cs @@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// This is whats gets displayed in the Urls and API requests. This could also be a string. public CustomItem(Guid urlid, DateTime dateadded) diff --git a/Jellyfin.Data/Entities/CustomItemMetadata.cs b/Jellyfin.Data/Entities/CustomItemMetadata.cs index ee37aaaa97..b81408aa68 100644 --- a/Jellyfin.Data/Entities/CustomItemMetadata.cs +++ b/Jellyfin.Data/Entities/CustomItemMetadata.cs @@ -23,7 +23,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// The title or name of the object /// ISO-639-3 3-character language codes diff --git a/Jellyfin.Data/Entities/Episode.cs b/Jellyfin.Data/Entities/Episode.cs index 88531205fc..132af9bdc8 100644 --- a/Jellyfin.Data/Entities/Episode.cs +++ b/Jellyfin.Data/Entities/Episode.cs @@ -31,7 +31,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// This is whats gets displayed in the Urls and API requests. This could also be a string. /// @@ -66,7 +66,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for EpisodeNumber + /// Backing field for EpisodeNumber. /// protected int? _EpisodeNumber; /// diff --git a/Jellyfin.Data/Entities/EpisodeMetadata.cs b/Jellyfin.Data/Entities/EpisodeMetadata.cs index 0aa4b42703..a4e50fca2a 100644 --- a/Jellyfin.Data/Entities/EpisodeMetadata.cs +++ b/Jellyfin.Data/Entities/EpisodeMetadata.cs @@ -24,7 +24,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// The title or name of the object /// ISO-639-3 3-character language codes @@ -60,7 +60,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Outline + /// Backing field for Outline. /// protected string _Outline; /// @@ -97,7 +97,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Plot + /// Backing field for Plot. /// protected string _Plot; /// @@ -134,7 +134,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Tagline + /// Backing field for Tagline. /// protected string _Tagline; /// diff --git a/Jellyfin.Data/Entities/Genre.cs b/Jellyfin.Data/Entities/Genre.cs index ff07106712..a38c019fb5 100644 --- a/Jellyfin.Data/Entities/Genre.cs +++ b/Jellyfin.Data/Entities/Genre.cs @@ -25,7 +25,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// /// @@ -56,7 +56,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Id + /// Backing field for Id. /// internal int _Id; /// @@ -69,7 +69,7 @@ namespace Jellyfin.Data.Entities partial void GetId(ref int result); /// - /// Identity, Indexed, Required + /// Identity, Indexed, Required. /// [Key] [Required] @@ -94,7 +94,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Name + /// Backing field for Name. /// internal string _Name; /// @@ -132,7 +132,7 @@ namespace Jellyfin.Data.Entities } /// - /// Required, ConcurrenyToken + /// Required, ConcurrenyToken. /// [ConcurrencyCheck] [Required] diff --git a/Jellyfin.Data/Entities/Library.cs b/Jellyfin.Data/Entities/Library.cs index a5cc5c8da4..c5609d8582 100644 --- a/Jellyfin.Data/Entities/Library.cs +++ b/Jellyfin.Data/Entities/Library.cs @@ -25,7 +25,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// public Library(string name) @@ -51,7 +51,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Id + /// Backing field for Id. /// internal int _Id; /// @@ -64,7 +64,7 @@ namespace Jellyfin.Data.Entities partial void GetId(ref int result); /// - /// Identity, Indexed, Required + /// Identity, Indexed, Required. /// [Key] [Required] @@ -89,7 +89,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Name + /// Backing field for Name. /// protected string _Name; /// @@ -127,7 +127,7 @@ namespace Jellyfin.Data.Entities } /// - /// Required, ConcurrenyToken + /// Required, ConcurrenyToken. /// [ConcurrencyCheck] [Required] diff --git a/Jellyfin.Data/Entities/LibraryItem.cs b/Jellyfin.Data/Entities/LibraryItem.cs index c2ba7059d9..b7dad8d7c5 100644 --- a/Jellyfin.Data/Entities/LibraryItem.cs +++ b/Jellyfin.Data/Entities/LibraryItem.cs @@ -17,7 +17,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// This is whats gets displayed in the Urls and API requests. This could also be a string. protected LibraryItem(Guid urlid, DateTime dateadded) @@ -33,7 +33,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Id + /// Backing field for Id. /// internal int _Id; /// @@ -46,7 +46,7 @@ namespace Jellyfin.Data.Entities partial void GetId(ref int result); /// - /// Identity, Indexed, Required + /// Identity, Indexed, Required. /// [Key] [Required] @@ -71,7 +71,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for UrlId + /// Backing field for UrlId. /// internal Guid _UrlId; /// @@ -108,7 +108,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for DateAdded + /// Backing field for DateAdded. /// protected DateTime _DateAdded; /// @@ -121,7 +121,7 @@ namespace Jellyfin.Data.Entities partial void GetDateAdded(ref DateTime result); /// - /// Required + /// Required. /// [Required] public DateTime DateAdded @@ -144,7 +144,7 @@ namespace Jellyfin.Data.Entities } /// - /// Required, ConcurrenyToken + /// Required, ConcurrenyToken. /// [ConcurrencyCheck] [Required] @@ -160,7 +160,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Required + /// Required. /// [ForeignKey("LibraryRoot_Id")] public virtual LibraryRoot LibraryRoot { get; set; } diff --git a/Jellyfin.Data/Entities/LibraryRoot.cs b/Jellyfin.Data/Entities/LibraryRoot.cs index 7823db02a8..a84c7de4b1 100644 --- a/Jellyfin.Data/Entities/LibraryRoot.cs +++ b/Jellyfin.Data/Entities/LibraryRoot.cs @@ -25,7 +25,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// Absolute Path public LibraryRoot(string path) @@ -51,7 +51,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Id + /// Backing field for Id. /// internal int _Id; /// @@ -64,7 +64,7 @@ namespace Jellyfin.Data.Entities partial void GetId(ref int result); /// - /// Identity, Indexed, Required + /// Identity, Indexed, Required. /// [Key] [Required] @@ -89,7 +89,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Path + /// Backing field for Path. /// protected string _Path; /// @@ -103,7 +103,7 @@ namespace Jellyfin.Data.Entities /// /// Required, Max length = 65535 - /// Absolute Path + /// Absolute Path. /// [Required] [MaxLength(65535)] @@ -128,7 +128,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for NetworkPath + /// Backing field for NetworkPath. /// protected string _NetworkPath; /// @@ -166,7 +166,7 @@ namespace Jellyfin.Data.Entities } /// - /// Required, ConcurrenyToken + /// Required, ConcurrenyToken. /// [ConcurrencyCheck] [Required] @@ -182,7 +182,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Required + /// Required. /// [ForeignKey("Library_Id")] public virtual Library Library { get; set; } diff --git a/Jellyfin.Data/Entities/MediaFile.cs b/Jellyfin.Data/Entities/MediaFile.cs index 94c39a28a5..d8781bcb2a 100644 --- a/Jellyfin.Data/Entities/MediaFile.cs +++ b/Jellyfin.Data/Entities/MediaFile.cs @@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// Relative to the LibraryRoot /// @@ -64,7 +64,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Id + /// Backing field for Id. /// internal int _Id; /// @@ -77,7 +77,7 @@ namespace Jellyfin.Data.Entities partial void GetId(ref int result); /// - /// Identity, Indexed, Required + /// Identity, Indexed, Required. /// [Key] [Required] @@ -102,7 +102,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Path + /// Backing field for Path. /// protected string _Path; /// @@ -116,7 +116,7 @@ namespace Jellyfin.Data.Entities /// /// Required, Max length = 65535 - /// Relative to the LibraryRoot + /// Relative to the LibraryRoot. /// [Required] [MaxLength(65535)] @@ -141,7 +141,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Kind + /// Backing field for Kind. /// protected Enums.MediaFileKind _Kind; /// @@ -154,7 +154,7 @@ namespace Jellyfin.Data.Entities partial void GetKind(ref Enums.MediaFileKind result); /// - /// Required + /// Required. /// [Required] public Enums.MediaFileKind Kind @@ -177,7 +177,7 @@ namespace Jellyfin.Data.Entities } /// - /// Required, ConcurrenyToken + /// Required, ConcurrenyToken. /// [ConcurrencyCheck] [Required] diff --git a/Jellyfin.Data/Entities/MediaFileStream.cs b/Jellyfin.Data/Entities/MediaFileStream.cs index 723977fdf1..a7a33e255b 100644 --- a/Jellyfin.Data/Entities/MediaFileStream.cs +++ b/Jellyfin.Data/Entities/MediaFileStream.cs @@ -25,7 +25,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// /// @@ -55,7 +55,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Id + /// Backing field for Id. /// internal int _Id; /// @@ -68,7 +68,7 @@ namespace Jellyfin.Data.Entities partial void GetId(ref int result); /// - /// Identity, Indexed, Required + /// Identity, Indexed, Required. /// [Key] [Required] @@ -93,7 +93,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for StreamNumber + /// Backing field for StreamNumber. /// protected int _StreamNumber; /// @@ -106,7 +106,7 @@ namespace Jellyfin.Data.Entities partial void GetStreamNumber(ref int result); /// - /// Required + /// Required. /// [Required] public int StreamNumber @@ -129,7 +129,7 @@ namespace Jellyfin.Data.Entities } /// - /// Required, ConcurrenyToken + /// Required, ConcurrenyToken. /// [ConcurrencyCheck] [Required] diff --git a/Jellyfin.Data/Entities/Metadata.cs b/Jellyfin.Data/Entities/Metadata.cs index 6558642cf1..4282bb9f7b 100644 --- a/Jellyfin.Data/Entities/Metadata.cs +++ b/Jellyfin.Data/Entities/Metadata.cs @@ -24,7 +24,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// The title or name of the object /// ISO-639-3 3-character language codes @@ -50,7 +50,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Id + /// Backing field for Id. /// internal int _Id; /// @@ -63,7 +63,7 @@ namespace Jellyfin.Data.Entities partial void GetId(ref int result); /// - /// Identity, Indexed, Required + /// Identity, Indexed, Required. /// [Key] [Required] @@ -88,7 +88,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Title + /// Backing field for Title. /// protected string _Title; /// @@ -102,7 +102,7 @@ namespace Jellyfin.Data.Entities /// /// Required, Max length = 1024 - /// The title or name of the object + /// The title or name of the object. /// [Required] [MaxLength(1024)] @@ -127,7 +127,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for OriginalTitle + /// Backing field for OriginalTitle. /// protected string _OriginalTitle; /// @@ -164,7 +164,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for SortTitle + /// Backing field for SortTitle. /// protected string _SortTitle; /// @@ -201,7 +201,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Language + /// Backing field for Language. /// protected string _Language; /// @@ -215,7 +215,7 @@ namespace Jellyfin.Data.Entities /// /// Required, Min length = 3, Max length = 3 - /// ISO-639-3 3-character language codes + /// ISO-639-3 3-character language codes. /// [Required] [MinLength(3)] @@ -241,7 +241,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for ReleaseDate + /// Backing field for ReleaseDate. /// protected DateTimeOffset? _ReleaseDate; /// @@ -273,7 +273,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for DateAdded + /// Backing field for DateAdded. /// protected DateTime _DateAdded; /// @@ -286,7 +286,7 @@ namespace Jellyfin.Data.Entities partial void GetDateAdded(ref DateTime result); /// - /// Required + /// Required. /// [Required] public DateTime DateAdded @@ -309,7 +309,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for DateModified + /// Backing field for DateModified. /// protected DateTime _DateModified; /// @@ -322,7 +322,7 @@ namespace Jellyfin.Data.Entities partial void GetDateModified(ref DateTime result); /// - /// Required + /// Required. /// [Required] public DateTime DateModified @@ -345,7 +345,7 @@ namespace Jellyfin.Data.Entities } /// - /// Required, ConcurrenyToken + /// Required, ConcurrenyToken. /// [ConcurrencyCheck] [Required] diff --git a/Jellyfin.Data/Entities/MetadataProvider.cs b/Jellyfin.Data/Entities/MetadataProvider.cs index bf9689709b..9069d6ad65 100644 --- a/Jellyfin.Data/Entities/MetadataProvider.cs +++ b/Jellyfin.Data/Entities/MetadataProvider.cs @@ -25,7 +25,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// public MetadataProvider(string name) @@ -51,7 +51,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Id + /// Backing field for Id. /// internal int _Id; /// @@ -64,7 +64,7 @@ namespace Jellyfin.Data.Entities partial void GetId(ref int result); /// - /// Identity, Indexed, Required + /// Identity, Indexed, Required. /// [Key] [Required] @@ -89,7 +89,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Name + /// Backing field for Name. /// protected string _Name; /// @@ -127,7 +127,7 @@ namespace Jellyfin.Data.Entities } /// - /// Required, ConcurrenyToken + /// Required, ConcurrenyToken. /// [ConcurrencyCheck] [Required] diff --git a/Jellyfin.Data/Entities/MetadataProviderId.cs b/Jellyfin.Data/Entities/MetadataProviderId.cs index c49c6f42e4..5facf11885 100644 --- a/Jellyfin.Data/Entities/MetadataProviderId.cs +++ b/Jellyfin.Data/Entities/MetadataProviderId.cs @@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// /// @@ -77,7 +77,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Id + /// Backing field for Id. /// internal int _Id; /// @@ -90,7 +90,7 @@ namespace Jellyfin.Data.Entities partial void GetId(ref int result); /// - /// Identity, Indexed, Required + /// Identity, Indexed, Required. /// [Key] [Required] @@ -115,7 +115,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for ProviderId + /// Backing field for ProviderId. /// protected string _ProviderId; /// @@ -153,7 +153,7 @@ namespace Jellyfin.Data.Entities } /// - /// Required, ConcurrenyToken + /// Required, ConcurrenyToken. /// [ConcurrencyCheck] [Required] @@ -169,7 +169,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Required + /// Required. /// [ForeignKey("MetadataProvider_Id")] public virtual MetadataProvider MetadataProvider { get; set; } diff --git a/Jellyfin.Data/Entities/Movie.cs b/Jellyfin.Data/Entities/Movie.cs index ad2504b0dd..64326ca3a4 100644 --- a/Jellyfin.Data/Entities/Movie.cs +++ b/Jellyfin.Data/Entities/Movie.cs @@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// This is whats gets displayed in the Urls and API requests. This could also be a string. public Movie(Guid urlid, DateTime dateadded) diff --git a/Jellyfin.Data/Entities/MovieMetadata.cs b/Jellyfin.Data/Entities/MovieMetadata.cs index 1f8f1c2a00..f01b208bc2 100644 --- a/Jellyfin.Data/Entities/MovieMetadata.cs +++ b/Jellyfin.Data/Entities/MovieMetadata.cs @@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// The title or name of the object /// ISO-639-3 3-character language codes @@ -65,7 +65,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Outline + /// Backing field for Outline. /// protected string _Outline; /// @@ -102,7 +102,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Plot + /// Backing field for Plot. /// protected string _Plot; /// @@ -139,7 +139,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Tagline + /// Backing field for Tagline. /// protected string _Tagline; /// @@ -176,7 +176,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Country + /// Backing field for Country. /// protected string _Country; /// diff --git a/Jellyfin.Data/Entities/MusicAlbum.cs b/Jellyfin.Data/Entities/MusicAlbum.cs index e07f4357b4..9afea1fb69 100644 --- a/Jellyfin.Data/Entities/MusicAlbum.cs +++ b/Jellyfin.Data/Entities/MusicAlbum.cs @@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// This is whats gets displayed in the Urls and API requests. This could also be a string. public MusicAlbum(Guid urlid, DateTime dateadded) diff --git a/Jellyfin.Data/Entities/MusicAlbumMetadata.cs b/Jellyfin.Data/Entities/MusicAlbumMetadata.cs index 7743890a67..0ba594654c 100644 --- a/Jellyfin.Data/Entities/MusicAlbumMetadata.cs +++ b/Jellyfin.Data/Entities/MusicAlbumMetadata.cs @@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// The title or name of the object /// ISO-639-3 3-character language codes @@ -65,7 +65,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Barcode + /// Backing field for Barcode. /// protected string _Barcode; /// @@ -102,7 +102,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for LabelNumber + /// Backing field for LabelNumber. /// protected string _LabelNumber; /// @@ -139,7 +139,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Country + /// Backing field for Country. /// protected string _Country; /// diff --git a/Jellyfin.Data/Entities/Person.cs b/Jellyfin.Data/Entities/Person.cs index f714188190..8f6fb3a7d4 100644 --- a/Jellyfin.Data/Entities/Person.cs +++ b/Jellyfin.Data/Entities/Person.cs @@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// /// @@ -59,7 +59,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Id + /// Backing field for Id. /// internal int _Id; /// @@ -72,7 +72,7 @@ namespace Jellyfin.Data.Entities partial void GetId(ref int result); /// - /// Identity, Indexed, Required + /// Identity, Indexed, Required. /// [Key] [Required] @@ -97,7 +97,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for UrlId + /// Backing field for UrlId. /// protected Guid _UrlId; /// @@ -110,7 +110,7 @@ namespace Jellyfin.Data.Entities partial void GetUrlId(ref Guid result); /// - /// Required + /// Required. /// [Required] public Guid UrlId @@ -133,7 +133,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Name + /// Backing field for Name. /// protected string _Name; /// @@ -171,7 +171,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for SourceId + /// Backing field for SourceId. /// protected string _SourceId; /// @@ -208,7 +208,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for DateAdded + /// Backing field for DateAdded. /// protected DateTime _DateAdded; /// @@ -221,7 +221,7 @@ namespace Jellyfin.Data.Entities partial void GetDateAdded(ref DateTime result); /// - /// Required + /// Required. /// [Required] public DateTime DateAdded @@ -244,7 +244,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for DateModified + /// Backing field for DateModified. /// protected DateTime _DateModified; /// @@ -257,7 +257,7 @@ namespace Jellyfin.Data.Entities partial void GetDateModified(ref DateTime result); /// - /// Required + /// Required. /// [Required] public DateTime DateModified @@ -280,7 +280,7 @@ namespace Jellyfin.Data.Entities } /// - /// Required, ConcurrenyToken + /// Required, ConcurrenyToken. /// [ConcurrencyCheck] [Required] diff --git a/Jellyfin.Data/Entities/PersonRole.cs b/Jellyfin.Data/Entities/PersonRole.cs index a3d0471151..9bd4779388 100644 --- a/Jellyfin.Data/Entities/PersonRole.cs +++ b/Jellyfin.Data/Entities/PersonRole.cs @@ -31,7 +31,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// /// @@ -65,7 +65,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Id + /// Backing field for Id. /// internal int _Id; /// @@ -78,7 +78,7 @@ namespace Jellyfin.Data.Entities partial void GetId(ref int result); /// - /// Identity, Indexed, Required + /// Identity, Indexed, Required. /// [Key] [Required] @@ -103,7 +103,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Role + /// Backing field for Role. /// protected string _Role; /// @@ -140,7 +140,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Type + /// Backing field for Type. /// protected Enums.PersonRoleType _Type; /// @@ -153,7 +153,7 @@ namespace Jellyfin.Data.Entities partial void GetType(ref Enums.PersonRoleType result); /// - /// Required + /// Required. /// [Required] public Enums.PersonRoleType Type @@ -176,7 +176,7 @@ namespace Jellyfin.Data.Entities } /// - /// Required, ConcurrenyToken + /// Required, ConcurrenyToken. /// [ConcurrencyCheck] [Required] @@ -192,7 +192,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Required + /// Required. /// [ForeignKey("Person_Id")] diff --git a/Jellyfin.Data/Entities/Photo.cs b/Jellyfin.Data/Entities/Photo.cs index 226730126a..9da55fe430 100644 --- a/Jellyfin.Data/Entities/Photo.cs +++ b/Jellyfin.Data/Entities/Photo.cs @@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// This is whats gets displayed in the Urls and API requests. This could also be a string. public Photo(Guid urlid, DateTime dateadded) diff --git a/Jellyfin.Data/Entities/PhotoMetadata.cs b/Jellyfin.Data/Entities/PhotoMetadata.cs index 2bb239cdd0..5a9cf5b66a 100644 --- a/Jellyfin.Data/Entities/PhotoMetadata.cs +++ b/Jellyfin.Data/Entities/PhotoMetadata.cs @@ -24,7 +24,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// The title or name of the object /// ISO-639-3 3-character language codes diff --git a/Jellyfin.Data/Entities/ProviderMapping.cs b/Jellyfin.Data/Entities/ProviderMapping.cs index e86d9737fc..4125eabcd6 100644 --- a/Jellyfin.Data/Entities/ProviderMapping.cs +++ b/Jellyfin.Data/Entities/ProviderMapping.cs @@ -25,7 +25,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// /// @@ -65,7 +65,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Identity, Indexed, Required + /// Identity, Indexed, Required. /// [Key] [Required] @@ -97,7 +97,7 @@ namespace Jellyfin.Data.Entities public string ProviderData { get; set; } /// - /// Required, ConcurrenyToken + /// Required, ConcurrenyToken. /// [ConcurrencyCheck] [Required] diff --git a/Jellyfin.Data/Entities/Rating.cs b/Jellyfin.Data/Entities/Rating.cs index 0c8b99ca2c..34f395a228 100644 --- a/Jellyfin.Data/Entities/Rating.cs +++ b/Jellyfin.Data/Entities/Rating.cs @@ -25,7 +25,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// /// @@ -55,7 +55,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Id + /// Backing field for Id. /// internal int _Id; /// @@ -68,7 +68,7 @@ namespace Jellyfin.Data.Entities partial void GetId(ref int result); /// - /// Identity, Indexed, Required + /// Identity, Indexed, Required. /// [Key] [Required] @@ -93,7 +93,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Value + /// Backing field for Value. /// protected double _Value; /// @@ -106,7 +106,7 @@ namespace Jellyfin.Data.Entities partial void GetValue(ref double result); /// - /// Required + /// Required. /// [Required] public double Value @@ -129,7 +129,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Votes + /// Backing field for Votes. /// protected int? _Votes; /// @@ -161,7 +161,7 @@ namespace Jellyfin.Data.Entities } /// - /// Required, ConcurrenyToken + /// Required, ConcurrenyToken. /// [ConcurrencyCheck] [Required] diff --git a/Jellyfin.Data/Entities/RatingSource.cs b/Jellyfin.Data/Entities/RatingSource.cs index c829042b5c..5ea6069f98 100644 --- a/Jellyfin.Data/Entities/RatingSource.cs +++ b/Jellyfin.Data/Entities/RatingSource.cs @@ -5,7 +5,7 @@ using System.ComponentModel.DataAnnotations.Schema; namespace Jellyfin.Data.Entities { /// - /// This is the entity to store review ratings, not age ratings + /// This is the entity to store review ratings, not age ratings. /// public partial class RatingSource { @@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// /// @@ -62,7 +62,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Id + /// Backing field for Id. /// internal int _Id; /// @@ -75,7 +75,7 @@ namespace Jellyfin.Data.Entities partial void GetId(ref int result); /// - /// Identity, Indexed, Required + /// Identity, Indexed, Required. /// [Key] [Required] @@ -100,7 +100,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Name + /// Backing field for Name. /// protected string _Name; /// @@ -137,7 +137,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for MaximumValue + /// Backing field for MaximumValue. /// protected double _MaximumValue; /// @@ -150,7 +150,7 @@ namespace Jellyfin.Data.Entities partial void GetMaximumValue(ref double result); /// - /// Required + /// Required. /// [Required] public double MaximumValue @@ -173,7 +173,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for MinimumValue + /// Backing field for MinimumValue. /// protected double _MinimumValue; /// @@ -186,7 +186,7 @@ namespace Jellyfin.Data.Entities partial void GetMinimumValue(ref double result); /// - /// Required + /// Required. /// [Required] public double MinimumValue @@ -209,7 +209,7 @@ namespace Jellyfin.Data.Entities } /// - /// Required, ConcurrenyToken + /// Required, ConcurrenyToken. /// [ConcurrencyCheck] [Required] diff --git a/Jellyfin.Data/Entities/Release.cs b/Jellyfin.Data/Entities/Release.cs index 35fcbb4b76..fe0dbaa54e 100644 --- a/Jellyfin.Data/Entities/Release.cs +++ b/Jellyfin.Data/Entities/Release.cs @@ -29,7 +29,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// /// @@ -87,7 +87,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Id + /// Backing field for Id. /// internal int _Id; /// @@ -100,7 +100,7 @@ namespace Jellyfin.Data.Entities partial void GetId(ref int result); /// - /// Identity, Indexed, Required + /// Identity, Indexed, Required. /// [Key] [Required] @@ -125,7 +125,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Name + /// Backing field for Name. /// protected string _Name; /// @@ -163,7 +163,7 @@ namespace Jellyfin.Data.Entities } /// - /// Required, ConcurrenyToken + /// Required, ConcurrenyToken. /// [ConcurrencyCheck] [Required] diff --git a/Jellyfin.Data/Entities/Season.cs b/Jellyfin.Data/Entities/Season.cs index 2a861b660a..81f413fa8b 100644 --- a/Jellyfin.Data/Entities/Season.cs +++ b/Jellyfin.Data/Entities/Season.cs @@ -31,7 +31,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// This is whats gets displayed in the Urls and API requests. This could also be a string. /// @@ -66,7 +66,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for SeasonNumber + /// Backing field for SeasonNumber. /// protected int? _SeasonNumber; /// diff --git a/Jellyfin.Data/Entities/SeasonMetadata.cs b/Jellyfin.Data/Entities/SeasonMetadata.cs index 10320c6bbd..b323984f1a 100644 --- a/Jellyfin.Data/Entities/SeasonMetadata.cs +++ b/Jellyfin.Data/Entities/SeasonMetadata.cs @@ -25,7 +25,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// The title or name of the object /// ISO-639-3 3-character language codes @@ -61,7 +61,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Outline + /// Backing field for Outline. /// protected string _Outline; /// diff --git a/Jellyfin.Data/Entities/Series.cs b/Jellyfin.Data/Entities/Series.cs index cf1d6b781b..013c8385f6 100644 --- a/Jellyfin.Data/Entities/Series.cs +++ b/Jellyfin.Data/Entities/Series.cs @@ -20,7 +20,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// This is whats gets displayed in the Urls and API requests. This could also be a string. public Series(Guid urlid, DateTime dateadded) @@ -47,7 +47,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for AirsDayOfWeek + /// Backing field for AirsDayOfWeek. /// protected DayOfWeek? _AirsDayOfWeek; /// @@ -79,7 +79,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for AirsTime + /// Backing field for AirsTime. /// protected DateTimeOffset? _AirsTime; /// @@ -92,7 +92,7 @@ namespace Jellyfin.Data.Entities partial void GetAirsTime(ref DateTimeOffset? result); /// - /// The time the show airs, ignore the date portion + /// The time the show airs, ignore the date portion. /// public DateTimeOffset? AirsTime { @@ -114,7 +114,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for FirstAired + /// Backing field for FirstAired. /// protected DateTimeOffset? _FirstAired; /// diff --git a/Jellyfin.Data/Entities/SeriesMetadata.cs b/Jellyfin.Data/Entities/SeriesMetadata.cs index bb31c2e4e8..4666d79ef6 100644 --- a/Jellyfin.Data/Entities/SeriesMetadata.cs +++ b/Jellyfin.Data/Entities/SeriesMetadata.cs @@ -28,7 +28,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// The title or name of the object /// ISO-639-3 3-character language codes @@ -65,7 +65,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for Outline + /// Backing field for Outline. /// protected string _Outline; /// @@ -102,7 +102,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Plot + /// Backing field for Plot. /// protected string _Plot; /// @@ -139,7 +139,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Tagline + /// Backing field for Tagline. /// protected string _Tagline; /// @@ -176,7 +176,7 @@ namespace Jellyfin.Data.Entities } /// - /// Backing field for Country + /// Backing field for Country. /// protected string _Country; /// diff --git a/Jellyfin.Data/Entities/Track.cs b/Jellyfin.Data/Entities/Track.cs index c9e8fd1c3b..1c3562a659 100644 --- a/Jellyfin.Data/Entities/Track.cs +++ b/Jellyfin.Data/Entities/Track.cs @@ -31,7 +31,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// This is whats gets displayed in the Urls and API requests. This could also be a string. /// @@ -66,7 +66,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Backing field for TrackNumber + /// Backing field for TrackNumber. /// protected int? _TrackNumber; /// diff --git a/Jellyfin.Data/Entities/TrackMetadata.cs b/Jellyfin.Data/Entities/TrackMetadata.cs index 7b99c06837..05bb953f8e 100644 --- a/Jellyfin.Data/Entities/TrackMetadata.cs +++ b/Jellyfin.Data/Entities/TrackMetadata.cs @@ -24,7 +24,7 @@ namespace Jellyfin.Data.Entities } /// - /// Public constructor with required data + /// Public constructor with required data. /// /// The title or name of the object /// ISO-639-3 3-character language codes diff --git a/Jellyfin.Data/Enums/PreferenceKind.cs b/Jellyfin.Data/Enums/PreferenceKind.cs index de8eecc734..a54d789afb 100644 --- a/Jellyfin.Data/Enums/PreferenceKind.cs +++ b/Jellyfin.Data/Enums/PreferenceKind.cs @@ -26,7 +26,7 @@ namespace Jellyfin.Data.Enums EnabledDevices = 3, /// - /// A list of enabled channels + /// A list of enabled channels. /// EnabledChannels = 4, diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index f574ebc66b..a3fc6291fb 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -69,7 +69,7 @@ namespace Jellyfin.Server.Implementations /// /// Repository for global::Jellyfin.Data.Entities.RatingSource - This is the entity to - /// store review ratings, not age ratings + /// store review ratings, not age ratings. /// public virtual DbSet RatingSources { get; set; } public virtual DbSet Releases { get; set; } diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 9e651fb197..b041effb2e 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -43,7 +43,7 @@ namespace MediaBrowser.Api private readonly IMediaSourceManager _mediaSourceManager; /// - /// The active transcoding jobs + /// The active transcoding jobs. /// private readonly List _activeTranscodingJobs = new List(); @@ -293,7 +293,7 @@ namespace MediaBrowser.Api /// /// - /// The progressive + /// The progressive. /// /// Called when [transcode failed to start]. /// diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index a91a9b580c..63a31a7452 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -17,7 +17,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api { /// - /// Class BaseApiService + /// Class BaseApiService. /// public abstract class BaseApiService : IService, IRequiresRequest { diff --git a/MediaBrowser.Api/ChannelService.cs b/MediaBrowser.Api/ChannelService.cs index 3cab9fb66f..8c336b1c9d 100644 --- a/MediaBrowser.Api/ChannelService.cs +++ b/MediaBrowser.Api/ChannelService.cs @@ -36,7 +36,7 @@ namespace MediaBrowser.Api public int? StartIndex { get; set; } /// - /// The maximum number of items to return + /// The maximum number of items to return. /// /// The limit. [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] @@ -90,7 +90,7 @@ namespace MediaBrowser.Api public int? StartIndex { get; set; } /// - /// The maximum number of items to return + /// The maximum number of items to return. /// /// The limit. [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] @@ -149,7 +149,7 @@ namespace MediaBrowser.Api public int? StartIndex { get; set; } /// - /// The maximum number of items to return + /// The maximum number of items to return. /// /// The limit. [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs index 3ad51de8de..19369cccae 100644 --- a/MediaBrowser.Api/ConfigurationService.cs +++ b/MediaBrowser.Api/ConfigurationService.cs @@ -11,7 +11,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api { /// - /// Class GetConfiguration + /// Class GetConfiguration. /// [Route("/System/Configuration", "GET", Summary = "Gets application configuration")] [Authenticated] @@ -28,7 +28,7 @@ namespace MediaBrowser.Api } /// - /// Class UpdateConfiguration + /// Class UpdateConfiguration. /// [Route("/System/Configuration", "POST", Summary = "Updates application configuration")] [Authenticated(Roles = "Admin")] @@ -65,12 +65,12 @@ namespace MediaBrowser.Api public class ConfigurationService : BaseApiService { /// - /// The _json serializer + /// The _json serializer. /// private readonly IJsonSerializer _jsonSerializer; /// - /// The _configuration manager + /// The _configuration manager. /// private readonly IServerConfigurationManager _configurationManager; diff --git a/MediaBrowser.Api/DisplayPreferencesService.cs b/MediaBrowser.Api/DisplayPreferencesService.cs index 62c4ff43f2..c3ed40ad3c 100644 --- a/MediaBrowser.Api/DisplayPreferencesService.cs +++ b/MediaBrowser.Api/DisplayPreferencesService.cs @@ -10,7 +10,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api { /// - /// Class UpdateDisplayPreferences + /// Class UpdateDisplayPreferences. /// [Route("/DisplayPreferences/{DisplayPreferencesId}", "POST", Summary = "Updates a user's display preferences for an item")] public class UpdateDisplayPreferences : DisplayPreferences, IReturnVoid @@ -44,17 +44,17 @@ namespace MediaBrowser.Api } /// - /// Class DisplayPreferencesService + /// Class DisplayPreferencesService. /// [Authenticated] public class DisplayPreferencesService : BaseApiService { /// - /// The _display preferences manager + /// The _display preferences manager. /// private readonly IDisplayPreferencesRepository _displayPreferencesManager; /// - /// The _json serializer + /// The _json serializer. /// private readonly IJsonSerializer _jsonSerializer; diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs index fddf784651..70acd069ed 100644 --- a/MediaBrowser.Api/EnvironmentService.cs +++ b/MediaBrowser.Api/EnvironmentService.cs @@ -12,7 +12,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api { /// - /// Class GetDirectoryContents + /// Class GetDirectoryContents. /// [Route("/Environment/DirectoryContents", "GET", Summary = "Gets the contents of a given directory in the file system")] public class GetDirectoryContents : IReturn> @@ -66,7 +66,7 @@ namespace MediaBrowser.Api } /// - /// Class GetDrives + /// Class GetDrives. /// [Route("/Environment/Drives", "GET", Summary = "Gets available drives from the server's file system")] public class GetDrives : IReturn> @@ -74,7 +74,7 @@ namespace MediaBrowser.Api } /// - /// Class GetNetworkComputers + /// Class GetNetworkComputers. /// [Route("/Environment/NetworkDevices", "GET", Summary = "Gets a list of devices on the network")] public class GetNetworkDevices : IReturn> @@ -103,7 +103,7 @@ namespace MediaBrowser.Api } /// - /// Class EnvironmentService + /// Class EnvironmentService. /// [Authenticated(Roles = "Admin", AllowBeforeStartupWizard = true)] public class EnvironmentService : BaseApiService @@ -112,7 +112,7 @@ namespace MediaBrowser.Api private const string UncSeparatorString = "\\"; /// - /// The _network manager + /// The _network manager. /// private readonly INetworkManager _networkManager; private readonly IFileSystem _fileSystem; @@ -220,7 +220,7 @@ namespace MediaBrowser.Api } /// - /// Gets the list that is returned when an empty path is supplied + /// Gets the list that is returned when an empty path is supplied. /// /// IEnumerable{FileSystemEntryInfo}. private IEnumerable GetDrives() diff --git a/MediaBrowser.Api/IHasItemFields.cs b/MediaBrowser.Api/IHasItemFields.cs index 6359de77dc..ad4f1b4891 100644 --- a/MediaBrowser.Api/IHasItemFields.cs +++ b/MediaBrowser.Api/IHasItemFields.cs @@ -5,7 +5,7 @@ using MediaBrowser.Model.Querying; namespace MediaBrowser.Api { /// - /// Interface IHasItemFields + /// Interface IHasItemFields. /// public interface IHasItemFields { diff --git a/MediaBrowser.Api/Images/ImageByNameService.cs b/MediaBrowser.Api/Images/ImageByNameService.cs index 45b7d0c100..2d405ac3d8 100644 --- a/MediaBrowser.Api/Images/ImageByNameService.cs +++ b/MediaBrowser.Api/Images/ImageByNameService.cs @@ -16,7 +16,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.Images { /// - /// Class GetGeneralImage + /// Class GetGeneralImage. /// [Route("/Images/General/{Name}/{Type}", "GET", Summary = "Gets a general image by name")] public class GetGeneralImage @@ -33,7 +33,7 @@ namespace MediaBrowser.Api.Images } /// - /// Class GetRatingImage + /// Class GetRatingImage. /// [Route("/Images/Ratings/{Theme}/{Name}", "GET", Summary = "Gets a rating image by name")] public class GetRatingImage @@ -54,7 +54,7 @@ namespace MediaBrowser.Api.Images } /// - /// Class GetMediaInfoImage + /// Class GetMediaInfoImage. /// [Route("/Images/MediaInfo/{Theme}/{Name}", "GET", Summary = "Gets a media info image by name")] public class GetMediaInfoImage @@ -93,12 +93,12 @@ namespace MediaBrowser.Api.Images } /// - /// Class ImageByNameService + /// Class ImageByNameService. /// public class ImageByNameService : BaseApiService { /// - /// The _app paths + /// The _app paths. /// private readonly IServerApplicationPaths _appPaths; diff --git a/MediaBrowser.Api/Images/ImageRequest.cs b/MediaBrowser.Api/Images/ImageRequest.cs index 71ff09b63f..0f3455548d 100644 --- a/MediaBrowser.Api/Images/ImageRequest.cs +++ b/MediaBrowser.Api/Images/ImageRequest.cs @@ -4,30 +4,30 @@ using MediaBrowser.Model.Services; namespace MediaBrowser.Api.Images { /// - /// Class ImageRequest + /// Class ImageRequest. /// public class ImageRequest : DeleteImageRequest { /// - /// The max width + /// The max width. /// [ApiMember(Name = "MaxWidth", Description = "The maximum image width to return.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? MaxWidth { get; set; } /// - /// The max height + /// The max height. /// [ApiMember(Name = "MaxHeight", Description = "The maximum image height to return.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? MaxHeight { get; set; } /// - /// The width + /// The width. /// [ApiMember(Name = "Width", Description = "The fixed image width to return.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? Width { get; set; } /// - /// The height + /// The height. /// [ApiMember(Name = "Height", Description = "The fixed image height to return.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? Height { get; set; } @@ -79,7 +79,7 @@ namespace MediaBrowser.Api.Images } /// - /// Class DeleteImageRequest + /// Class DeleteImageRequest. /// public class DeleteImageRequest { diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 6f2956c5d1..8426a9a4f8 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -58,7 +58,7 @@ namespace MediaBrowser.Api.Images } /// - /// Class UpdateItemImageIndex + /// Class UpdateItemImageIndex. /// [Route("/Items/{Id}/Images/{Type}/{Index}/Index", "POST", Summary = "Updates the index for an item image")] [Authenticated(Roles = "admin")] @@ -94,7 +94,7 @@ namespace MediaBrowser.Api.Images } /// - /// Class GetPersonImage + /// Class GetPersonImage. /// [Route("/Artists/{Name}/Images/{Type}", "GET")] [Route("/Artists/{Name}/Images/{Type}/{Index}", "GET")] @@ -131,7 +131,7 @@ namespace MediaBrowser.Api.Images } /// - /// Class GetUserImage + /// Class GetUserImage. /// [Route("/Users/{Id}/Images/{Type}", "GET")] [Route("/Users/{Id}/Images/{Type}/{Index}", "GET")] @@ -148,7 +148,7 @@ namespace MediaBrowser.Api.Images } /// - /// Class DeleteItemImage + /// Class DeleteItemImage. /// [Route("/Items/{Id}/Images/{Type}", "DELETE")] [Route("/Items/{Id}/Images/{Type}/{Index}", "DELETE")] @@ -164,7 +164,7 @@ namespace MediaBrowser.Api.Images } /// - /// Class DeleteUserImage + /// Class DeleteUserImage. /// [Route("/Users/{Id}/Images/{Type}", "DELETE")] [Route("/Users/{Id}/Images/{Type}/{Index}", "DELETE")] @@ -180,7 +180,7 @@ namespace MediaBrowser.Api.Images } /// - /// Class PostUserImage + /// Class PostUserImage. /// [Route("/Users/{Id}/Images/{Type}", "POST")] [Route("/Users/{Id}/Images/{Type}/{Index}", "POST")] @@ -195,14 +195,14 @@ namespace MediaBrowser.Api.Images public string Id { get; set; } /// - /// The raw Http Request Input Stream + /// The raw Http Request Input Stream. /// /// The request stream. public Stream RequestStream { get; set; } } /// - /// Class PostItemImage + /// Class PostItemImage. /// [Route("/Items/{Id}/Images/{Type}", "POST")] [Route("/Items/{Id}/Images/{Type}/{Index}", "POST")] @@ -217,14 +217,14 @@ namespace MediaBrowser.Api.Images public string Id { get; set; } /// - /// The raw Http Request Input Stream + /// The raw Http Request Input Stream. /// /// The request stream. public Stream RequestStream { get; set; } } /// - /// Class ImageService + /// Class ImageService. /// public class ImageService : BaseApiService { diff --git a/MediaBrowser.Api/Images/RemoteImageService.cs b/MediaBrowser.Api/Images/RemoteImageService.cs index 2633a5d3c6..86464b4b9a 100644 --- a/MediaBrowser.Api/Images/RemoteImageService.cs +++ b/MediaBrowser.Api/Images/RemoteImageService.cs @@ -33,7 +33,7 @@ namespace MediaBrowser.Api.Images public int? StartIndex { get; set; } /// - /// The maximum number of items to return + /// The maximum number of items to return. /// /// The limit. [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index eb64abb4d4..1ab8206806 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -49,7 +49,7 @@ namespace MediaBrowser.Api.Library } /// - /// Class GetCriticReviews + /// Class GetCriticReviews. /// [Route("/Items/{Id}/CriticReviews", "GET", Summary = "Gets critic reviews for an item")] [Authenticated] @@ -70,7 +70,7 @@ namespace MediaBrowser.Api.Library public int? StartIndex { get; set; } /// - /// The maximum number of items to return + /// The maximum number of items to return. /// /// The limit. [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] @@ -78,7 +78,7 @@ namespace MediaBrowser.Api.Library } /// - /// Class GetThemeSongs + /// Class GetThemeSongs. /// [Route("/Items/{Id}/ThemeSongs", "GET", Summary = "Gets theme songs for an item")] [Authenticated] @@ -103,7 +103,7 @@ namespace MediaBrowser.Api.Library } /// - /// Class GetThemeVideos + /// Class GetThemeVideos. /// [Route("/Items/{Id}/ThemeVideos", "GET", Summary = "Gets theme videos for an item")] [Authenticated] @@ -128,7 +128,7 @@ namespace MediaBrowser.Api.Library } /// - /// Class GetThemeVideos + /// Class GetThemeVideos. /// [Route("/Items/{Id}/ThemeMedia", "GET", Summary = "Gets theme videos and songs for an item")] [Authenticated] @@ -205,7 +205,7 @@ namespace MediaBrowser.Api.Library } /// - /// Class GetPhyscialPaths + /// Class GetPhyscialPaths. /// [Route("/Library/PhysicalPaths", "GET", Summary = "Gets a list of physical paths from virtual folders")] [Authenticated(Roles = "Admin")] @@ -312,7 +312,7 @@ namespace MediaBrowser.Api.Library } /// - /// Class LibraryService + /// Class LibraryService. /// public class LibraryService : BaseApiService { diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs index 1e300814f6..b69550ed1a 100644 --- a/MediaBrowser.Api/Library/LibraryStructureService.cs +++ b/MediaBrowser.Api/Library/LibraryStructureService.cs @@ -19,7 +19,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.Library { /// - /// Class GetDefaultVirtualFolders + /// Class GetDefaultVirtualFolders. /// [Route("/Library/VirtualFolders", "GET")] public class GetVirtualFolders : IReturn> @@ -166,18 +166,18 @@ namespace MediaBrowser.Api.Library } /// - /// Class LibraryStructureService + /// Class LibraryStructureService. /// [Authenticated(Roles = "Admin", AllowBeforeStartupWizard = true)] public class LibraryStructureService : BaseApiService { /// - /// The _app paths + /// The _app paths. /// private readonly IServerApplicationPaths _appPaths; /// - /// The _library manager + /// The _library manager. /// private readonly ILibraryManager _libraryManager; private readonly ILibraryMonitor _libraryMonitor; diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index b00a5fec8c..8e13744c41 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -31,7 +31,7 @@ using Microsoft.Net.Http.Headers; namespace MediaBrowser.Api.LiveTv { /// - /// This is insecure right now to avoid windows phone refactoring + /// This is insecure right now to avoid windows phone refactoring. /// [Route("/LiveTv/Info", "GET", Summary = "Gets available live tv services.")] [Authenticated] @@ -72,7 +72,7 @@ namespace MediaBrowser.Api.LiveTv public bool? IsSports { get; set; } /// - /// The maximum number of items to return + /// The maximum number of items to return. /// /// The limit. [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] @@ -100,7 +100,7 @@ namespace MediaBrowser.Api.LiveTv public string EnableImageTypes { get; set; } /// - /// Fields to return within the items, in addition to basic information + /// Fields to return within the items, in addition to basic information. /// /// The fields. [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] @@ -188,7 +188,7 @@ namespace MediaBrowser.Api.LiveTv public string EnableImageTypes { get; set; } /// - /// Fields to return within the items, in addition to basic information + /// Fields to return within the items, in addition to basic information. /// /// The fields. [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] @@ -250,7 +250,7 @@ namespace MediaBrowser.Api.LiveTv public string EnableImageTypes { get; set; } /// - /// Fields to return within the items, in addition to basic information + /// Fields to return within the items, in addition to basic information. /// /// The fields. [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] @@ -410,7 +410,7 @@ namespace MediaBrowser.Api.LiveTv public Guid LibrarySeriesId { get; set; } /// - /// Fields to return within the items, in addition to basic information + /// Fields to return within the items, in addition to basic information. /// /// The fields. [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] @@ -473,7 +473,7 @@ namespace MediaBrowser.Api.LiveTv public string GenreIds { get; set; } /// - /// Fields to return within the items, in addition to basic information + /// Fields to return within the items, in addition to basic information. /// /// The fields. [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] diff --git a/MediaBrowser.Api/LocalizationService.cs b/MediaBrowser.Api/LocalizationService.cs index 6a69d26568..d6b5f51950 100644 --- a/MediaBrowser.Api/LocalizationService.cs +++ b/MediaBrowser.Api/LocalizationService.cs @@ -8,7 +8,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api { /// - /// Class GetCultures + /// Class GetCultures. /// [Route("/Localization/Cultures", "GET", Summary = "Gets known cultures")] public class GetCultures : IReturn @@ -16,7 +16,7 @@ namespace MediaBrowser.Api } /// - /// Class GetCountries + /// Class GetCountries. /// [Route("/Localization/Countries", "GET", Summary = "Gets known countries")] public class GetCountries : IReturn @@ -24,7 +24,7 @@ namespace MediaBrowser.Api } /// - /// Class ParentalRatings + /// Class ParentalRatings. /// [Route("/Localization/ParentalRatings", "GET", Summary = "Gets known parental ratings")] public class GetParentalRatings : IReturn @@ -32,7 +32,7 @@ namespace MediaBrowser.Api } /// - /// Class ParentalRatings + /// Class ParentalRatings. /// [Route("/Localization/Options", "GET", Summary = "Gets localization options")] public class GetLocalizationOptions : IReturn @@ -40,13 +40,13 @@ namespace MediaBrowser.Api } /// - /// Class CulturesService + /// Class CulturesService. /// [Authenticated(AllowBeforeStartupWizard = true)] public class LocalizationService : BaseApiService { /// - /// The _localization + /// The _localization. /// private readonly ILocalizationManager _localization; diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index 88ca0aa23c..34cccffa38 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -65,13 +65,13 @@ namespace MediaBrowser.Api.Movies } /// - /// Class MoviesService + /// Class MoviesService. /// [Authenticated] public class MoviesService : BaseApiService { /// - /// The _user manager + /// The _user manager. /// private readonly IUserManager _userManager; diff --git a/MediaBrowser.Api/Movies/TrailersService.cs b/MediaBrowser.Api/Movies/TrailersService.cs index a7758b100e..ca9f9d03b2 100644 --- a/MediaBrowser.Api/Movies/TrailersService.cs +++ b/MediaBrowser.Api/Movies/TrailersService.cs @@ -18,18 +18,18 @@ namespace MediaBrowser.Api.Movies } /// - /// Class TrailersService + /// Class TrailersService. /// [Authenticated] public class TrailersService : BaseApiService { /// - /// The _user manager + /// The _user manager. /// private readonly IUserManager _userManager; /// - /// The _library manager + /// The _library manager. /// private readonly ILibraryManager _libraryManager; diff --git a/MediaBrowser.Api/Music/AlbumsService.cs b/MediaBrowser.Api/Music/AlbumsService.cs index f257d10141..74d3cce12a 100644 --- a/MediaBrowser.Api/Music/AlbumsService.cs +++ b/MediaBrowser.Api/Music/AlbumsService.cs @@ -27,16 +27,16 @@ namespace MediaBrowser.Api.Music public class AlbumsService : BaseApiService { /// - /// The _user manager + /// The _user manager. /// private readonly IUserManager _userManager; /// - /// The _user data repository + /// The _user data repository. /// private readonly IUserDataManager _userDataRepository; /// - /// The _library manager + /// The _library manager. /// private readonly ILibraryManager _libraryManager; private readonly IItemRepository _itemRepo; diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs index 444354a992..a63d06ad5f 100644 --- a/MediaBrowser.Api/PackageService.cs +++ b/MediaBrowser.Api/PackageService.cs @@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api { /// - /// Class GetPackage + /// Class GetPackage. /// [Route("/Packages/{Name}", "GET", Summary = "Gets a package, by name or assembly guid")] [Authenticated] @@ -36,7 +36,7 @@ namespace MediaBrowser.Api } /// - /// Class GetPackages + /// Class GetPackages. /// [Route("/Packages", "GET", Summary = "Gets available packages")] [Authenticated] @@ -45,7 +45,7 @@ namespace MediaBrowser.Api } /// - /// Class InstallPackage + /// Class InstallPackage. /// [Route("/Packages/Installed/{Name}", "POST", Summary = "Installs a package")] [Authenticated(Roles = "Admin")] @@ -74,7 +74,7 @@ namespace MediaBrowser.Api } /// - /// Class CancelPackageInstallation + /// Class CancelPackageInstallation. /// [Route("/Packages/Installing/{Id}", "DELETE", Summary = "Cancels a package installation")] [Authenticated(Roles = "Admin")] @@ -89,7 +89,7 @@ namespace MediaBrowser.Api } /// - /// Class PackageService + /// Class PackageService. /// public class PackageService : BaseApiService { diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 2eb6198c00..afbafa9d33 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -28,7 +28,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.Playback { /// - /// Class BaseStreamingService + /// Class BaseStreamingService. /// public abstract class BaseStreamingService : BaseApiService { diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index c2d49a93b6..f41f006fa8 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -20,7 +20,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.Playback.Hls { /// - /// Class BaseHlsService + /// Class BaseHlsService. /// public abstract class BaseHlsService : BaseStreamingService { diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs index 87ccde2e04..8a3d00283f 100644 --- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs +++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs @@ -13,7 +13,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.Playback.Hls { /// - /// Class GetHlsAudioSegment + /// Class GetHlsAudioSegment. /// // Can't require authentication just yet due to seeing some requests come from Chrome without full query string //[Authenticated] @@ -37,7 +37,7 @@ namespace MediaBrowser.Api.Playback.Hls } /// - /// Class GetHlsVideoSegment + /// Class GetHlsVideoSegment. /// [Route("/Videos/{Id}/hls/{PlaylistId}/stream.m3u8", "GET")] [Authenticated] @@ -66,7 +66,7 @@ namespace MediaBrowser.Api.Playback.Hls } /// - /// Class GetHlsVideoSegment + /// Class GetHlsVideoSegment. /// // Can't require authentication just yet due to seeing some requests come from Chrome without full query string //[Authenticated] diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index aefb3f019b..9562f9953a 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Api.Playback.Hls } /// - /// Class VideoHlsService + /// Class VideoHlsService. /// [Authenticated] public class VideoHlsService : BaseHlsService diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs index 34c7986ca5..d51787df27 100644 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs @@ -15,7 +15,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.Playback.Progressive { /// - /// Class GetAudioStream + /// Class GetAudioStream. /// [Route("/Audio/{Id}/stream.{Container}", "GET", Summary = "Gets an audio stream")] [Route("/Audio/{Id}/stream", "GET", Summary = "Gets an audio stream")] @@ -26,7 +26,7 @@ namespace MediaBrowser.Api.Playback.Progressive } /// - /// Class AudioService + /// Class AudioService. /// // TODO: In order to autheneticate this in the future, Dlna playback will require updating //[Authenticated] diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 43cde440ce..4820cbd920 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -21,7 +21,7 @@ using Microsoft.Net.Http.Headers; namespace MediaBrowser.Api.Playback.Progressive { /// - /// Class BaseProgressiveStreamingService + /// Class BaseProgressiveStreamingService. /// public abstract class BaseProgressiveStreamingService : BaseStreamingService { diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index a35e6c201b..c3f6b905cb 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -15,7 +15,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.Playback.Progressive { /// - /// Class GetVideoStream + /// Class GetVideoStream. /// [Route("/Videos/{Id}/stream.mpegts", "GET")] [Route("/Videos/{Id}/stream.ts", "GET")] @@ -62,7 +62,7 @@ namespace MediaBrowser.Api.Playback.Progressive } /// - /// Class VideoService + /// Class VideoService. /// // TODO: In order to autheneticate this in the future, Dlna playback will require updating //[Authenticated] diff --git a/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs b/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs index 3b8b299957..7e2e337ad1 100644 --- a/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs +++ b/MediaBrowser.Api/Playback/StaticRemoteStreamWriter.cs @@ -8,17 +8,17 @@ using MediaBrowser.Model.Services; namespace MediaBrowser.Api.Playback { /// - /// Class StaticRemoteStreamWriter + /// Class StaticRemoteStreamWriter. /// public class StaticRemoteStreamWriter : IAsyncStreamWriter, IHasHeaders { /// - /// The _input stream + /// The _input stream. /// private readonly HttpResponseInfo _response; /// - /// The _options + /// The _options. /// private readonly IDictionary _options = new Dictionary(); diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs index 9ba8eda91f..cfcc980353 100644 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ b/MediaBrowser.Api/Playback/StreamRequest.cs @@ -4,7 +4,7 @@ using MediaBrowser.Model.Services; namespace MediaBrowser.Api.Playback { /// - /// Class StreamRequest + /// Class StreamRequest. /// public class StreamRequest : BaseEncodingJobOptions { diff --git a/MediaBrowser.Api/PlaylistService.cs b/MediaBrowser.Api/PlaylistService.cs index d5def03bec..5513c08922 100644 --- a/MediaBrowser.Api/PlaylistService.cs +++ b/MediaBrowser.Api/PlaylistService.cs @@ -95,14 +95,14 @@ namespace MediaBrowser.Api public int? StartIndex { get; set; } /// - /// The maximum number of items to return + /// The maximum number of items to return. /// /// The limit. [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? Limit { get; set; } /// - /// Fields to return within the items, in addition to basic information + /// Fields to return within the items, in addition to basic information. /// /// The fields. [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] diff --git a/MediaBrowser.Api/PluginService.cs b/MediaBrowser.Api/PluginService.cs index fd10757272..e9d5b2bc4c 100644 --- a/MediaBrowser.Api/PluginService.cs +++ b/MediaBrowser.Api/PluginService.cs @@ -15,7 +15,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api { /// - /// Class Plugins + /// Class Plugins. /// [Route("/Plugins", "GET", Summary = "Gets a list of currently installed plugins")] [Authenticated] @@ -25,7 +25,7 @@ namespace MediaBrowser.Api } /// - /// Class UninstallPlugin + /// Class UninstallPlugin. /// [Route("/Plugins/{Id}", "DELETE", Summary = "Uninstalls a plugin")] [Authenticated(Roles = "Admin")] @@ -40,7 +40,7 @@ namespace MediaBrowser.Api } /// - /// Class GetPluginConfiguration + /// Class GetPluginConfiguration. /// [Route("/Plugins/{Id}/Configuration", "GET", Summary = "Gets a plugin's configuration")] [Authenticated] @@ -55,7 +55,7 @@ namespace MediaBrowser.Api } /// - /// Class UpdatePluginConfiguration + /// Class UpdatePluginConfiguration. /// [Route("/Plugins/{Id}/Configuration", "POST", Summary = "Updates a plugin's configuration")] [Authenticated] @@ -69,7 +69,7 @@ namespace MediaBrowser.Api public string Id { get; set; } /// - /// The raw Http Request Input Stream + /// The raw Http Request Input Stream. /// /// The request stream. public Stream RequestStream { get; set; } @@ -86,7 +86,7 @@ namespace MediaBrowser.Api } /// - /// Class GetPluginSecurityInfo + /// Class GetPluginSecurityInfo. /// [Route("/Plugins/SecurityInfo", "GET", Summary = "Gets plugin registration information", IsHidden = true)] [Authenticated] @@ -95,7 +95,7 @@ namespace MediaBrowser.Api } /// - /// Class UpdatePluginSecurityInfo + /// Class UpdatePluginSecurityInfo. /// [Route("/Plugins/SecurityInfo", "POST", Summary = "Updates plugin registration information", IsHidden = true)] [Authenticated(Roles = "Admin")] @@ -136,17 +136,17 @@ namespace MediaBrowser.Api public bool IsMBSupporter { get; set; } } /// - /// Class PluginsService + /// Class PluginsService. /// public class PluginService : BaseApiService { /// - /// The _json serializer + /// The _json serializer. /// private readonly IJsonSerializer _jsonSerializer; /// - /// The _app host + /// The _app host. /// private readonly IApplicationHost _appHost; private readonly IInstallationManager _installationManager; diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs index e08a8482e0..86b00316ad 100644 --- a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs +++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs @@ -11,7 +11,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.ScheduledTasks { /// - /// Class GetScheduledTask + /// Class GetScheduledTask. /// [Route("/ScheduledTasks/{Id}", "GET", Summary = "Gets a scheduled task, by Id")] public class GetScheduledTask : IReturn @@ -25,7 +25,7 @@ namespace MediaBrowser.Api.ScheduledTasks } /// - /// Class GetScheduledTasks + /// Class GetScheduledTasks. /// [Route("/ScheduledTasks", "GET", Summary = "Gets scheduled tasks")] public class GetScheduledTasks : IReturn @@ -38,7 +38,7 @@ namespace MediaBrowser.Api.ScheduledTasks } /// - /// Class StartScheduledTask + /// Class StartScheduledTask. /// [Route("/ScheduledTasks/Running/{Id}", "POST", Summary = "Starts a scheduled task")] public class StartScheduledTask : IReturnVoid @@ -52,7 +52,7 @@ namespace MediaBrowser.Api.ScheduledTasks } /// - /// Class StopScheduledTask + /// Class StopScheduledTask. /// [Route("/ScheduledTasks/Running/{Id}", "DELETE", Summary = "Stops a scheduled task")] public class StopScheduledTask : IReturnVoid @@ -66,7 +66,7 @@ namespace MediaBrowser.Api.ScheduledTasks } /// - /// Class UpdateScheduledTaskTriggers + /// Class UpdateScheduledTaskTriggers. /// [Route("/ScheduledTasks/{Id}/Triggers", "POST", Summary = "Updates the triggers for a scheduled task")] public class UpdateScheduledTaskTriggers : List, IReturnVoid @@ -80,7 +80,7 @@ namespace MediaBrowser.Api.ScheduledTasks } /// - /// Class ScheduledTasksService + /// Class ScheduledTasksService. /// [Authenticated(Roles = "Admin")] public class ScheduledTaskService : BaseApiService diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs index 14b9b3618b..25dd39f2de 100644 --- a/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs +++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.ScheduledTasks { /// - /// Class ScheduledTasksWebSocketListener + /// Class ScheduledTasksWebSocketListener. /// public class ScheduledTasksWebSocketListener : BasePeriodicWebSocketListener, WebSocketListenerState> { diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs index 4a2f96ed86..64ee69300e 100644 --- a/MediaBrowser.Api/SearchService.cs +++ b/MediaBrowser.Api/SearchService.cs @@ -18,7 +18,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api { /// - /// Class GetSearchHints + /// Class GetSearchHints. /// [Route("/Search/Hints", "GET", Summary = "Gets search hints based on a search term")] public class GetSearchHints : IReturn @@ -31,7 +31,7 @@ namespace MediaBrowser.Api public int? StartIndex { get; set; } /// - /// The maximum number of items to return + /// The maximum number of items to return. /// /// The limit. [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] @@ -45,7 +45,7 @@ namespace MediaBrowser.Api public Guid UserId { get; set; } /// - /// Search characters used to find items + /// Search characters used to find items. /// /// The index by. [ApiMember(Name = "SearchTerm", Description = "The search term to filter on", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] @@ -104,13 +104,13 @@ namespace MediaBrowser.Api } /// - /// Class SearchService + /// Class SearchService. /// [Authenticated] public class SearchService : BaseApiService { /// - /// The _search engine + /// The _search engine. /// private readonly ISearchEngine _searchEngine; private readonly ILibraryManager _libraryManager; diff --git a/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs index 175984575d..2400d6defe 100644 --- a/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs +++ b/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs @@ -8,7 +8,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.Sessions { /// - /// Class SessionInfoWebSocketListener + /// Class SessionInfoWebSocketListener. /// public class SessionInfoWebSocketListener : BasePeriodicWebSocketListener, WebSocketListenerState> { @@ -19,7 +19,7 @@ namespace MediaBrowser.Api.Sessions protected override string Name => "Sessions"; /// - /// The _kernel + /// The _kernel. /// private readonly ISessionManager _sessionManager; diff --git a/MediaBrowser.Api/Sessions/SessionService.cs b/MediaBrowser.Api/Sessions/SessionService.cs index d986eea65a..50adc56980 100644 --- a/MediaBrowser.Api/Sessions/SessionService.cs +++ b/MediaBrowser.Api/Sessions/SessionService.cs @@ -46,14 +46,14 @@ namespace MediaBrowser.Api.Sessions public string Id { get; set; } /// - /// Artist, Genre, Studio, Person, or any kind of BaseItem + /// Artist, Genre, Studio, Person, or any kind of BaseItem. /// /// The type of the item. [ApiMember(Name = "ItemType", Description = "The type of item to browse to.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string ItemType { get; set; } /// - /// Artist name, genre name, item Id, etc + /// Artist name, genre name, item Id, etc. /// /// The item identifier. [ApiMember(Name = "ItemId", Description = "The Id of the item.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] diff --git a/MediaBrowser.Api/SimilarItemsHelper.cs b/MediaBrowser.Api/SimilarItemsHelper.cs index 90c324ff3f..7ed70f5d72 100644 --- a/MediaBrowser.Api/SimilarItemsHelper.cs +++ b/MediaBrowser.Api/SimilarItemsHelper.cs @@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api { /// - /// Class BaseGetSimilarItemsFromItem + /// Class BaseGetSimilarItemsFromItem. /// public class BaseGetSimilarItemsFromItem : BaseGetSimilarItems { @@ -50,14 +50,14 @@ namespace MediaBrowser.Api public Guid UserId { get; set; } /// - /// The maximum number of items to return + /// The maximum number of items to return. /// /// The limit. [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? Limit { get; set; } /// - /// Fields to return within the items, in addition to basic information + /// Fields to return within the items, in addition to basic information. /// /// The fields. [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] @@ -65,7 +65,7 @@ namespace MediaBrowser.Api } /// - /// Class SimilarItemsHelper + /// Class SimilarItemsHelper. /// public static class SimilarItemsHelper { diff --git a/MediaBrowser.Api/System/ActivityLogService.cs b/MediaBrowser.Api/System/ActivityLogService.cs index a6bacad4fc..7ca31c21ab 100644 --- a/MediaBrowser.Api/System/ActivityLogService.cs +++ b/MediaBrowser.Api/System/ActivityLogService.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Api.System public int? StartIndex { get; set; } /// - /// The maximum number of items to return + /// The maximum number of items to return. /// /// The limit. [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] diff --git a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs b/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs index 8e4860be4b..39976371a9 100644 --- a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs +++ b/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs @@ -8,7 +8,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.System { /// - /// Class SessionInfoWebSocketListener + /// Class SessionInfoWebSocketListener. /// public class ActivityLogWebSocketListener : BasePeriodicWebSocketListener { @@ -19,7 +19,7 @@ namespace MediaBrowser.Api.System protected override string Name => "ActivityLogEntry"; /// - /// The _kernel + /// The _kernel. /// private readonly IActivityManager _activityManager; diff --git a/MediaBrowser.Api/System/SystemService.cs b/MediaBrowser.Api/System/SystemService.cs index 4f10a4ad2e..e0e20d828e 100644 --- a/MediaBrowser.Api/System/SystemService.cs +++ b/MediaBrowser.Api/System/SystemService.cs @@ -18,7 +18,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.System { /// - /// Class GetSystemInfo + /// Class GetSystemInfo. /// [Route("/System/Info", "GET", Summary = "Gets information about the server")] [Authenticated(EscapeParentalControl = true, AllowBeforeStartupWizard = true)] @@ -38,7 +38,7 @@ namespace MediaBrowser.Api.System } /// - /// Class RestartApplication + /// Class RestartApplication. /// [Route("/System/Restart", "POST", Summary = "Restarts the application, if needed")] [Authenticated(Roles = "Admin", AllowLocal = true)] @@ -83,12 +83,12 @@ namespace MediaBrowser.Api.System } /// - /// Class SystemInfoService + /// Class SystemInfoService. /// public class SystemService : BaseApiService { /// - /// The _app host + /// The _app host. /// private readonly IServerApplicationHost _appHost; private readonly IApplicationPaths _appPaths; diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index 23062b67b7..6177c2e2e0 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -19,7 +19,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api { /// - /// Class GetNextUpEpisodes + /// Class GetNextUpEpisodes. /// [Route("/Shows/NextUp", "GET", Summary = "Gets a list of next up episodes")] public class GetNextUpEpisodes : IReturn>, IHasDtoOptions @@ -39,14 +39,14 @@ namespace MediaBrowser.Api public int? StartIndex { get; set; } /// - /// The maximum number of items to return + /// The maximum number of items to return. /// /// The limit. [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? Limit { get; set; } /// - /// Fields to return within the items, in addition to basic information + /// Fields to return within the items, in addition to basic information. /// /// The fields. [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] @@ -99,14 +99,14 @@ namespace MediaBrowser.Api public int? StartIndex { get; set; } /// - /// The maximum number of items to return + /// The maximum number of items to return. /// /// The limit. [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? Limit { get; set; } /// - /// Fields to return within the items, in addition to basic information + /// Fields to return within the items, in addition to basic information. /// /// The fields. [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] @@ -143,7 +143,7 @@ namespace MediaBrowser.Api public Guid UserId { get; set; } /// - /// Fields to return within the items, in addition to basic information + /// Fields to return within the items, in addition to basic information. /// /// The fields. [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] @@ -175,7 +175,7 @@ namespace MediaBrowser.Api public int? StartIndex { get; set; } /// - /// The maximum number of items to return + /// The maximum number of items to return. /// /// The limit. [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] @@ -211,7 +211,7 @@ namespace MediaBrowser.Api public Guid UserId { get; set; } /// - /// Fields to return within the items, in addition to basic information + /// Fields to return within the items, in addition to basic information. /// /// The fields. [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] @@ -243,18 +243,18 @@ namespace MediaBrowser.Api } /// - /// Class TvShowsService + /// Class TvShowsService. /// [Authenticated] public class TvShowsService : BaseApiService { /// - /// The _user manager + /// The _user manager. /// private readonly IUserManager _userManager; /// - /// The _library manager + /// The _library manager. /// private readonly ILibraryManager _libraryManager; diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs index bef91d54df..9875e02084 100644 --- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs +++ b/MediaBrowser.Api/UserLibrary/ArtistsService.cs @@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.UserLibrary { /// - /// Class GetArtists + /// Class GetArtists. /// [Route("/Artists", "GET", Summary = "Gets all artists from a given item, folder, or the entire library")] public class GetArtists : GetItemsByName @@ -45,7 +45,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class ArtistsService + /// Class ArtistsService. /// [Authenticated] public class ArtistsService : BaseItemsByNameService diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 4802849f48..a68c6fb6d3 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -15,7 +15,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.UserLibrary { /// - /// Class BaseItemsByNameService + /// Class BaseItemsByNameService. /// /// The type of the T item type. public abstract class BaseItemsByNameService : BaseApiService @@ -52,7 +52,7 @@ namespace MediaBrowser.Api.UserLibrary protected IUserManager UserManager { get; } /// - /// Gets the library manager + /// Gets the library manager. /// protected ILibraryManager LibraryManager { get; } @@ -375,7 +375,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class GetItemsByName + /// Class GetItemsByName. /// public class GetItemsByName : BaseItemsRequest, IReturn> { diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs index 7561b5c892..5a4394425d 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs @@ -111,14 +111,14 @@ namespace MediaBrowser.Api.UserLibrary public int? StartIndex { get; set; } /// - /// The maximum number of items to return + /// The maximum number of items to return. /// /// The limit. [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? Limit { get; set; } /// - /// Whether or not to perform the query recursively + /// Whether or not to perform the query recursively. /// /// true if recursive; otherwise, false. [ApiMember(Name = "Recursive", Description = "When searching within folders, this determines whether or not the search will be recursive. true/false", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] @@ -141,7 +141,7 @@ namespace MediaBrowser.Api.UserLibrary public string ParentId { get; set; } /// - /// Fields to return within the items, in addition to basic information + /// Fields to return within the items, in addition to basic information. /// /// The fields. [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] @@ -162,14 +162,14 @@ namespace MediaBrowser.Api.UserLibrary public string IncludeItemTypes { get; set; } /// - /// Filters to apply to the results + /// Filters to apply to the results. /// /// The filters. [ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] public string Filters { get; set; } /// - /// Gets or sets the Isfavorite option + /// Gets or sets the Isfavorite option. /// /// IsFavorite [ApiMember(Name = "IsFavorite", Description = "Optional filter by items that are marked as favorite, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] @@ -190,7 +190,7 @@ namespace MediaBrowser.Api.UserLibrary public string ImageTypes { get; set; } /// - /// What to sort the results by + /// What to sort the results by. /// /// The sort by. [ApiMember(Name = "SortBy", Description = "Optional. Specify one or more sort orders, comma delimeted. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] @@ -200,7 +200,7 @@ namespace MediaBrowser.Api.UserLibrary public bool? IsPlayed { get; set; } /// - /// Limit results to items containing specific genres + /// Limit results to items containing specific genres. /// /// The genres. [ApiMember(Name = "Genres", Description = "Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] @@ -215,7 +215,7 @@ namespace MediaBrowser.Api.UserLibrary public string Tags { get; set; } /// - /// Limit results to items containing specific years + /// Limit results to items containing specific years. /// /// The years. [ApiMember(Name = "Years", Description = "Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] @@ -234,7 +234,7 @@ namespace MediaBrowser.Api.UserLibrary public string EnableImageTypes { get; set; } /// - /// Limit results to items containing a specific person + /// Limit results to items containing a specific person. /// /// The person. [ApiMember(Name = "Person", Description = "Optional. If specified, results will be filtered to include only those containing the specified person.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] @@ -244,14 +244,14 @@ namespace MediaBrowser.Api.UserLibrary public string PersonIds { get; set; } /// - /// If the Person filter is used, this can also be used to restrict to a specific person type + /// If the Person filter is used, this can also be used to restrict to a specific person type. /// /// The type of the person. [ApiMember(Name = "PersonTypes", Description = "Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string PersonTypes { get; set; } /// - /// Limit results to items containing specific studios + /// Limit results to items containing specific studios. /// /// The studios. [ApiMember(Name = "Studios", Description = "Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] diff --git a/MediaBrowser.Api/UserLibrary/GenresService.cs b/MediaBrowser.Api/UserLibrary/GenresService.cs index 1fa272a5f7..7bdfbac981 100644 --- a/MediaBrowser.Api/UserLibrary/GenresService.cs +++ b/MediaBrowser.Api/UserLibrary/GenresService.cs @@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.UserLibrary { /// - /// Class GetGenres + /// Class GetGenres. /// [Route("/Genres", "GET", Summary = "Gets all genres from a given item, folder, or the entire library")] public class GetGenres : GetItemsByName @@ -22,7 +22,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class GetGenre + /// Class GetGenre. /// [Route("/Genres/{Name}", "GET", Summary = "Gets a genre, by name")] public class GetGenre : IReturn @@ -43,7 +43,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class GenresService + /// Class GenresService. /// [Authenticated] public class GenresService : BaseItemsByNameService diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 49d534c364..7efe0552c6 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -20,7 +20,7 @@ using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; namespace MediaBrowser.Api.UserLibrary { /// - /// Class GetItems + /// Class GetItems. /// [Route("/Items", "GET", Summary = "Gets items based on a query.")] [Route("/Users/{UserId}/Items", "GET", Summary = "Gets items based on a query.")] @@ -34,18 +34,18 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class ItemsService + /// Class ItemsService. /// [Authenticated] public class ItemsService : BaseApiService { /// - /// The _user manager + /// The _user manager. /// private readonly IUserManager _userManager; /// - /// The _library manager + /// The _library manager. /// private readonly ILibraryManager _libraryManager; private readonly ILocalizationManager _localization; @@ -496,7 +496,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class DateCreatedComparer + /// Class DateCreatedComparer. /// public class DateCreatedComparer : IComparer { diff --git a/MediaBrowser.Api/UserLibrary/PersonsService.cs b/MediaBrowser.Api/UserLibrary/PersonsService.cs index 3204e5219f..7924339ed3 100644 --- a/MediaBrowser.Api/UserLibrary/PersonsService.cs +++ b/MediaBrowser.Api/UserLibrary/PersonsService.cs @@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.UserLibrary { /// - /// Class GetPersons + /// Class GetPersons. /// [Route("/Persons", "GET", Summary = "Gets all persons from a given item, folder, or the entire library")] public class GetPersons : GetItemsByName @@ -22,7 +22,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class GetPerson + /// Class GetPerson. /// [Route("/Persons/{Name}", "GET", Summary = "Gets a person, by name")] public class GetPerson : IReturn @@ -43,7 +43,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class PersonsService + /// Class PersonsService. /// [Authenticated] public class PersonsService : BaseItemsByNameService diff --git a/MediaBrowser.Api/UserLibrary/PlaystateService.cs b/MediaBrowser.Api/UserLibrary/PlaystateService.cs index ab231626bb..d809cc2e79 100644 --- a/MediaBrowser.Api/UserLibrary/PlaystateService.cs +++ b/MediaBrowser.Api/UserLibrary/PlaystateService.cs @@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.UserLibrary { /// - /// Class MarkPlayedItem + /// Class MarkPlayedItem. /// [Route("/Users/{UserId}/PlayedItems/{Id}", "POST", Summary = "Marks an item as played")] public class MarkPlayedItem : IReturn @@ -38,7 +38,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class MarkUnplayedItem + /// Class MarkUnplayedItem. /// [Route("/Users/{UserId}/PlayedItems/{Id}", "DELETE", Summary = "Marks an item as unplayed")] public class MarkUnplayedItem : IReturn @@ -81,7 +81,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class OnPlaybackStart + /// Class OnPlaybackStart. /// [Route("/Users/{UserId}/PlayingItems/{Id}", "POST", Summary = "Reports that a user has begun playing an item")] public class OnPlaybackStart : IReturnVoid @@ -123,7 +123,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class OnPlaybackProgress + /// Class OnPlaybackProgress. /// [Route("/Users/{UserId}/PlayingItems/{Id}/Progress", "POST", Summary = "Reports a user's playback progress")] public class OnPlaybackProgress : IReturnVoid @@ -181,7 +181,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class OnPlaybackStopped + /// Class OnPlaybackStopped. /// [Route("/Users/{UserId}/PlayingItems/{Id}", "DELETE", Summary = "Reports that a user has stopped playing an item")] public class OnPlaybackStopped : IReturnVoid diff --git a/MediaBrowser.Api/UserLibrary/StudiosService.cs b/MediaBrowser.Api/UserLibrary/StudiosService.cs index 683ce5d09d..66350955f5 100644 --- a/MediaBrowser.Api/UserLibrary/StudiosService.cs +++ b/MediaBrowser.Api/UserLibrary/StudiosService.cs @@ -13,7 +13,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.UserLibrary { /// - /// Class GetStudios + /// Class GetStudios. /// [Route("/Studios", "GET", Summary = "Gets all studios from a given item, folder, or the entire library")] public class GetStudios : GetItemsByName @@ -21,7 +21,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class GetStudio + /// Class GetStudio. /// [Route("/Studios/{Name}", "GET", Summary = "Gets a studio, by name")] public class GetStudio : IReturn @@ -42,7 +42,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class StudiosService + /// Class StudiosService. /// [Authenticated] public class StudiosService : BaseItemsByNameService diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index f758528859..f9cbba4104 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -20,7 +20,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.UserLibrary { /// - /// Class GetItem + /// Class GetItem. /// [Route("/Users/{UserId}/Items/{Id}", "GET", Summary = "Gets an item from a user's library")] public class GetItem : IReturn @@ -41,7 +41,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class GetItem + /// Class GetItem. /// [Route("/Users/{UserId}/Items/Root", "GET", Summary = "Gets the root folder from a user's library")] public class GetRootFolder : IReturn @@ -55,7 +55,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class GetIntros + /// Class GetIntros. /// [Route("/Users/{UserId}/Items/{Id}/Intros", "GET", Summary = "Gets intros to play before the main media item plays")] public class GetIntros : IReturn> @@ -76,7 +76,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class MarkFavoriteItem + /// Class MarkFavoriteItem. /// [Route("/Users/{UserId}/FavoriteItems/{Id}", "POST", Summary = "Marks an item as a favorite")] public class MarkFavoriteItem : IReturn @@ -97,7 +97,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class UnmarkFavoriteItem + /// Class UnmarkFavoriteItem. /// [Route("/Users/{UserId}/FavoriteItems/{Id}", "DELETE", Summary = "Unmarks an item as a favorite")] public class UnmarkFavoriteItem : IReturn @@ -118,7 +118,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class ClearUserItemRating + /// Class ClearUserItemRating. /// [Route("/Users/{UserId}/Items/{Id}/Rating", "DELETE", Summary = "Deletes a user's saved personal rating for an item")] public class DeleteUserItemRating : IReturn @@ -139,7 +139,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class UpdateUserItemRating + /// Class UpdateUserItemRating. /// [Route("/Users/{UserId}/Items/{Id}/Rating", "POST", Summary = "Updates a user's rating for an item")] public class UpdateUserItemRating : IReturn @@ -167,7 +167,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class GetLocalTrailers + /// Class GetLocalTrailers. /// [Route("/Users/{UserId}/Items/{Id}/LocalTrailers", "GET", Summary = "Gets local trailers for an item")] public class GetLocalTrailers : IReturn @@ -188,7 +188,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class GetSpecialFeatures + /// Class GetSpecialFeatures. /// [Route("/Users/{UserId}/Items/{Id}/SpecialFeatures", "GET", Summary = "Gets special features for an item")] public class GetSpecialFeatures : IReturn @@ -259,7 +259,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class UserLibraryService + /// Class UserLibraryService. /// [Authenticated] public class UserLibraryService : BaseApiService diff --git a/MediaBrowser.Api/UserLibrary/YearsService.cs b/MediaBrowser.Api/UserLibrary/YearsService.cs index d023ee90ab..0523f89fa7 100644 --- a/MediaBrowser.Api/UserLibrary/YearsService.cs +++ b/MediaBrowser.Api/UserLibrary/YearsService.cs @@ -13,7 +13,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.UserLibrary { /// - /// Class GetYears + /// Class GetYears. /// [Route("/Years", "GET", Summary = "Gets all years from a given item, folder, or the entire library")] public class GetYears : GetItemsByName @@ -21,7 +21,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class GetYear + /// Class GetYear. /// [Route("/Years/{Year}", "GET", Summary = "Gets a year")] public class GetYear : IReturn @@ -42,7 +42,7 @@ namespace MediaBrowser.Api.UserLibrary } /// - /// Class YearsService + /// Class YearsService. /// [Authenticated] public class YearsService : BaseItemsByNameService diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 9cb9baf631..131def5542 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -19,7 +19,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api { /// - /// Class GetUsers + /// Class GetUsers. /// [Route("/Users", "GET", Summary = "Gets a list of users")] [Authenticated] @@ -41,7 +41,7 @@ namespace MediaBrowser.Api } /// - /// Class GetUser + /// Class GetUser. /// [Route("/Users/{Id}", "GET", Summary = "Gets a user by Id")] [Authenticated(EscapeParentalControl = true)] @@ -56,7 +56,7 @@ namespace MediaBrowser.Api } /// - /// Class DeleteUser + /// Class DeleteUser. /// [Route("/Users/{Id}", "DELETE", Summary = "Deletes a user")] [Authenticated(Roles = "Admin")] @@ -71,7 +71,7 @@ namespace MediaBrowser.Api } /// - /// Class AuthenticateUser + /// Class AuthenticateUser. /// [Route("/Users/{Id}/Authenticate", "POST", Summary = "Authenticates a user")] public class AuthenticateUser : IReturn @@ -95,7 +95,7 @@ namespace MediaBrowser.Api } /// - /// Class AuthenticateUser + /// Class AuthenticateUser. /// [Route("/Users/AuthenticateByName", "POST", Summary = "Authenticates a user")] public class AuthenticateUserByName : IReturn @@ -119,7 +119,7 @@ namespace MediaBrowser.Api } /// - /// Class UpdateUserPassword + /// Class UpdateUserPassword. /// [Route("/Users/{Id}/Password", "POST", Summary = "Updates a user's password")] [Authenticated] @@ -149,7 +149,7 @@ namespace MediaBrowser.Api } /// - /// Class UpdateUserEasyPassword + /// Class UpdateUserEasyPassword. /// [Route("/Users/{Id}/EasyPassword", "POST", Summary = "Updates a user's easy password")] [Authenticated] @@ -177,7 +177,7 @@ namespace MediaBrowser.Api } /// - /// Class UpdateUser + /// Class UpdateUser. /// [Route("/Users/{Id}", "POST", Summary = "Updates a user")] [Authenticated] @@ -186,7 +186,7 @@ namespace MediaBrowser.Api } /// - /// Class UpdateUser + /// Class UpdateUser. /// [Route("/Users/{Id}/Policy", "POST", Summary = "Updates a user policy")] [Authenticated(Roles = "admin")] @@ -197,7 +197,7 @@ namespace MediaBrowser.Api } /// - /// Class UpdateUser + /// Class UpdateUser. /// [Route("/Users/{Id}/Configuration", "POST", Summary = "Updates a user configuration")] [Authenticated] @@ -208,7 +208,7 @@ namespace MediaBrowser.Api } /// - /// Class CreateUser + /// Class CreateUser. /// [Route("/Users/New", "POST", Summary = "Creates a user")] [Authenticated(Roles = "Admin")] @@ -236,7 +236,7 @@ namespace MediaBrowser.Api } /// - /// Class UsersService + /// Class UsersService. /// public class UserService : BaseApiService { diff --git a/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs b/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs index 60455e68ae..1f4a26064c 100644 --- a/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs +++ b/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Controller.Channels public List ContentTypes { get; set; } /// - /// Represents the maximum number of records the channel allows retrieving at a time + /// Represents the maximum number of records the channel allows retrieving at a time. /// public int? MaxPageSize { get; set; } diff --git a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs index 6660743e62..a5c5e3bccf 100644 --- a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs +++ b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs @@ -4,7 +4,7 @@ using MediaBrowser.Model.Configuration; namespace MediaBrowser.Controller.Configuration { /// - /// Interface IServerConfigurationManager + /// Interface IServerConfigurationManager. /// public interface IServerConfigurationManager : IConfigurationManager { diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index f1873d5396..488692c036 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -10,7 +10,7 @@ using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Drawing { /// - /// Interface IImageProcessor + /// Interface IImageProcessor. /// public interface IImageProcessor { diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index 56e6c47c4b..0dadc283ee 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -7,7 +7,7 @@ using MediaBrowser.Model.Querying; namespace MediaBrowser.Controller.Dto { /// - /// Interface IDtoService + /// Interface IDtoService. /// public interface IDtoService { diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs index 54540e8921..67858b8446 100644 --- a/MediaBrowser.Controller/Entities/AggregateFolder.cs +++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Controller.Entities public override bool SupportsPlayedStatus => false; /// - /// The _virtual children + /// The _virtual children. /// private readonly ConcurrentBag _virtualChildren = new ConcurrentBag(); diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index a8ea2157d5..b3e241a15a 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -11,7 +11,7 @@ using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Entities.Audio { /// - /// Class Audio + /// Class Audio. /// public class Audio : BaseItem, IHasAlbumArtist, diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index f7b2f95498..e563f398b8 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -15,7 +15,7 @@ using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider; namespace MediaBrowser.Controller.Entities.Audio { /// - /// Class MusicAlbum + /// Class MusicAlbum. /// public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo, IMetadataContainer { diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 63db3cfab2..56c3c38dcd 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -15,7 +15,7 @@ using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider; namespace MediaBrowser.Controller.Entities.Audio { /// - /// Class MusicArtist + /// Class MusicArtist. /// public class MusicArtist : Folder, IItemByName, IHasMusicGenres, IHasDualAccess, IHasLookupInfo { @@ -111,7 +111,7 @@ namespace MediaBrowser.Controller.Entities.Audio /// /// Returns the folder containing the item. - /// If the item is a folder, it returns the folder itself + /// If the item is a folder, it returns the folder itself. /// /// The containing folder path. [JsonIgnore] @@ -201,7 +201,7 @@ namespace MediaBrowser.Controller.Entities.Audio } /// - /// This is called before any metadata refresh and returns true or false indicating if changes were made + /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// public override bool BeforeMetadataRefresh(bool replaceAllMetdata) { diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs index 537e9630be..49a4cbae26 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Entities.Audio { /// - /// Class MusicGenre + /// Class MusicGenre. /// public class MusicGenre : BaseItem, IItemByName { @@ -34,7 +34,7 @@ namespace MediaBrowser.Controller.Entities.Audio /// /// Returns the folder containing the item. - /// If the item is a folder, it returns the folder itself + /// If the item is a folder, it returns the folder itself. /// /// The containing folder path. [JsonIgnore] @@ -98,7 +98,7 @@ namespace MediaBrowser.Controller.Entities.Audio } /// - /// This is called before any metadata refresh and returns true or false indicating if changes were made + /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// public override bool BeforeMetadataRefresh(bool replaceAllMetdata) { diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index f2de1f2b19..ecbb83d076 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -31,12 +31,12 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Entities { /// - /// Class BaseItem + /// Class BaseItem. /// public abstract class BaseItem : IHasProviderIds, IHasLookupInfo, IEquatable { /// - /// The supported image extensions + /// The supported image extensions. /// public static readonly string[] SupportedImageExtensions = new[] { ".png", ".jpg", ".jpeg", ".tbn", ".gif" }; @@ -75,7 +75,7 @@ namespace MediaBrowser.Controller.Entities public static char SlugChar = '-'; /// - /// The trailer folder name + /// The trailer folder name. /// public const string TrailerFolderName = "trailers"; public const string ThemeSongsFolderName = "theme-music"; @@ -243,7 +243,7 @@ namespace MediaBrowser.Controller.Entities /// /// Returns the folder containing the item. - /// If the item is a folder, it returns the folder itself + /// If the item is a folder, it returns the folder itself. /// [JsonIgnore] public virtual string ContainingFolderPath @@ -267,7 +267,7 @@ namespace MediaBrowser.Controller.Entities public string ServiceName { get; set; } /// - /// If this content came from an external service, the id of the content on that service + /// If this content came from an external service, the id of the content on that service. /// [JsonIgnore] public string ExternalId { get; set; } @@ -411,7 +411,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// This is just a helper for convenience + /// This is just a helper for convenience. /// /// The primary image path. [JsonIgnore] @@ -556,7 +556,7 @@ namespace MediaBrowser.Controller.Entities public DateTime DateLastRefreshed { get; set; } /// - /// The logger + /// The logger. /// public static ILoggerFactory LoggerFactory { get; set; } public static ILogger Logger { get; set; } @@ -799,7 +799,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// Finds a parent of a given type + /// Finds a parent of a given type. /// /// /// ``0. @@ -1351,7 +1351,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// Overrides the base implementation to refresh metadata for local trailers + /// Overrides the base implementation to refresh metadata for local trailers. /// /// The options. /// The cancellation token. @@ -1753,7 +1753,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// Determines if a given user has access to this item + /// Determines if a given user has access to this item. /// /// The user. /// true if [is parental allowed] [the specified user]; otherwise, false. @@ -2059,7 +2059,7 @@ namespace MediaBrowser.Controller.Entities public virtual bool EnableRememberingTrackSelections => true; /// - /// Adds a studio to the item + /// Adds a studio to the item. /// /// The name. /// @@ -2095,7 +2095,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// Adds a genre to the item + /// Adds a genre to the item. /// /// The name. /// @@ -2190,7 +2190,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// Gets an image + /// Gets an image. /// /// The type. /// Index of the image. @@ -2506,7 +2506,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// Gets the file system path to delete when the item is to be deleted + /// Gets the file system path to delete when the item is to be deleted. /// /// public virtual IEnumerable GetDeletePaths() diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index e5adf88d12..35a6cef955 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Controller.Entities { /// /// Specialized Folder class that points to a subset of the physical folders in the system. - /// It is created from the user-specific folders within the system root + /// It is created from the user-specific folders within the system root. /// public class CollectionFolder : Folder, ICollectionFolder { @@ -140,7 +140,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// Allow different display preferences for each collection folder + /// Allow different display preferences for each collection folder. /// /// The display prefs id. [JsonIgnore] diff --git a/MediaBrowser.Controller/Entities/Extensions.cs b/MediaBrowser.Controller/Entities/Extensions.cs index d2ca11740e..3a34c668cf 100644 --- a/MediaBrowser.Controller/Entities/Extensions.cs +++ b/MediaBrowser.Controller/Entities/Extensions.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Entities { /// - /// Class Extensions + /// Class Extensions. /// public static class Extensions { diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 3a01b43795..34e5d78e7a 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -31,7 +31,7 @@ using Series = MediaBrowser.Controller.Entities.TV.Series; namespace MediaBrowser.Controller.Entities { /// - /// Class Folder + /// Class Folder. /// public class Folder : BaseItem { @@ -172,7 +172,7 @@ namespace MediaBrowser.Controller.Entities public virtual IEnumerable Children => LoadChildren(); /// - /// thread-safe access to all recursive children of this folder - without regard to user + /// thread-safe access to all recursive children of this folder - without regard to user. /// /// The recursive children. [JsonIgnore] @@ -229,7 +229,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// Validates that the children of the folder still exist + /// Validates that the children of the folder still exist. /// /// The progress. /// The cancellation token. @@ -570,7 +570,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// Get the children of this folder from the actual file system + /// Get the children of this folder from the actual file system. /// /// IEnumerable{BaseItem}. protected virtual IEnumerable GetNonCachedChildren(IDirectoryService directoryService) @@ -582,7 +582,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// Get our children from the repo - stubbed for now + /// Get our children from the repo - stubbed for now. /// /// IEnumerable{BaseItem}. protected List GetCachedChildren() @@ -1286,7 +1286,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// Gets allowed recursive children of an item + /// Gets allowed recursive children of an item. /// /// The user. /// if set to true [include linked children]. diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs index 773c7df341..852cf46846 100644 --- a/MediaBrowser.Controller/Entities/Genre.cs +++ b/MediaBrowser.Controller/Entities/Genre.cs @@ -8,7 +8,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Entities { /// - /// Class Genre + /// Class Genre. /// public class Genre : BaseItem, IItemByName { @@ -31,7 +31,7 @@ namespace MediaBrowser.Controller.Entities /// /// Returns the folder containing the item. - /// If the item is a folder, it returns the folder itself + /// If the item is a folder, it returns the folder itself. /// /// The containing folder path. [JsonIgnore] @@ -96,7 +96,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// This is called before any metadata refresh and returns true or false indicating if changes were made + /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// public override bool BeforeMetadataRefresh(bool replaceAllMetdata) { diff --git a/MediaBrowser.Controller/Entities/ICollectionFolder.cs b/MediaBrowser.Controller/Entities/ICollectionFolder.cs index 4f0760746e..245b23ff0b 100644 --- a/MediaBrowser.Controller/Entities/ICollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/ICollectionFolder.cs @@ -3,7 +3,7 @@ using System; namespace MediaBrowser.Controller.Entities { /// - /// This is just a marker interface to denote top level folders + /// This is just a marker interface to denote top level folders. /// public interface ICollectionFolder : IHasCollectionType { diff --git a/MediaBrowser.Controller/Entities/IHasAspectRatio.cs b/MediaBrowser.Controller/Entities/IHasAspectRatio.cs index 149c1e5abb..d7d0076681 100644 --- a/MediaBrowser.Controller/Entities/IHasAspectRatio.cs +++ b/MediaBrowser.Controller/Entities/IHasAspectRatio.cs @@ -1,7 +1,7 @@ namespace MediaBrowser.Controller.Entities { /// - /// Interface IHasAspectRatio + /// Interface IHasAspectRatio. /// public interface IHasAspectRatio { diff --git a/MediaBrowser.Controller/Entities/IHasDisplayOrder.cs b/MediaBrowser.Controller/Entities/IHasDisplayOrder.cs index abee75a28d..13226b2346 100644 --- a/MediaBrowser.Controller/Entities/IHasDisplayOrder.cs +++ b/MediaBrowser.Controller/Entities/IHasDisplayOrder.cs @@ -1,7 +1,7 @@ namespace MediaBrowser.Controller.Entities { /// - /// Interface IHasDisplayOrder + /// Interface IHasDisplayOrder. /// public interface IHasDisplayOrder { diff --git a/MediaBrowser.Controller/Entities/IHasScreenshots.cs b/MediaBrowser.Controller/Entities/IHasScreenshots.cs index 0975242f5b..b027a0cb13 100644 --- a/MediaBrowser.Controller/Entities/IHasScreenshots.cs +++ b/MediaBrowser.Controller/Entities/IHasScreenshots.cs @@ -1,7 +1,7 @@ namespace MediaBrowser.Controller.Entities { /// - /// Interface IHasScreenshots + /// Interface IHasScreenshots. /// public interface IHasScreenshots { diff --git a/MediaBrowser.Controller/Entities/LinkedChild.cs b/MediaBrowser.Controller/Entities/LinkedChild.cs index d88c31007a..fbce74aab4 100644 --- a/MediaBrowser.Controller/Entities/LinkedChild.cs +++ b/MediaBrowser.Controller/Entities/LinkedChild.cs @@ -16,7 +16,7 @@ namespace MediaBrowser.Controller.Entities public string Id { get; set; } /// - /// Serves as a cache + /// Serves as a cache. /// public Guid? ItemId { get; set; } diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index be71bcc3c2..70c48b6f1a 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -11,7 +11,7 @@ using MediaBrowser.Model.Querying; namespace MediaBrowser.Controller.Entities.Movies { /// - /// Class BoxSet + /// Class BoxSet. /// public class BoxSet : Folder, IHasTrailers, IHasDisplayOrder, IHasLookupInfo { diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index 26a1650252..53badac4db 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -13,7 +13,7 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Controller.Entities.Movies { /// - /// Class Movie + /// Class Movie. /// public class Movie : Video, IHasSpecialFeatures, IHasTrailers, IHasLookupInfo, ISupportsBoxSetGrouping { diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index 9e4f9d47ed..69bd7659cc 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -46,7 +46,7 @@ namespace MediaBrowser.Controller.Entities /// /// Returns the folder containing the item. - /// If the item is a folder, it returns the folder itself + /// If the item is a folder, it returns the folder itself. /// /// The containing folder path. [JsonIgnore] @@ -118,7 +118,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// This is called before any metadata refresh and returns true or false indicating if changes were made + /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// public override bool BeforeMetadataRefresh(bool replaceAllMetdata) { diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs index 068032317d..f81b6f194f 100644 --- a/MediaBrowser.Controller/Entities/Studio.cs +++ b/MediaBrowser.Controller/Entities/Studio.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Entities { /// - /// Class Studio + /// Class Studio. /// public class Studio : BaseItem, IItemByName { @@ -25,7 +25,7 @@ namespace MediaBrowser.Controller.Entities /// /// Returns the folder containing the item. - /// If the item is a folder, it returns the folder itself + /// If the item is a folder, it returns the folder itself. /// /// The containing folder path. [JsonIgnore] @@ -97,7 +97,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// This is called before any metadata refresh and returns true or false indicating if changes were made + /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// public override bool BeforeMetadataRefresh(bool replaceAllMetdata) { diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 4ec60e7cd3..82e7d49fb9 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -12,7 +12,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Entities.TV { /// - /// Class Episode + /// Class Episode. /// public class Episode : Video, IHasTrailers, IHasLookupInfo, IHasSeries { @@ -101,7 +101,7 @@ namespace MediaBrowser.Controller.Entities.TV } /// - /// This Episode's Series Instance + /// This Episode's Series Instance. /// /// The series. [JsonIgnore] diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 7dfd1a7597..9f0658776b 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -11,7 +11,7 @@ using MediaBrowser.Model.Querying; namespace MediaBrowser.Controller.Entities.TV { /// - /// Class Season + /// Class Season. /// public class Season : Folder, IHasSeries, IHasLookupInfo { @@ -68,7 +68,7 @@ namespace MediaBrowser.Controller.Entities.TV } /// - /// This Episode's Series Instance + /// This Episode's Series Instance. /// /// The series. [JsonIgnore] @@ -225,7 +225,7 @@ namespace MediaBrowser.Controller.Entities.TV } /// - /// This is called before any metadata refresh and returns true or false indicating if changes were made + /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// /// true if XXXX, false otherwise. public override bool BeforeMetadataRefresh(bool replaceAllMetdata) diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index a519089b3e..9263a156af 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -17,7 +17,7 @@ using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider; namespace MediaBrowser.Controller.Entities.TV { /// - /// Class Series + /// Class Series. /// public class Series : Folder, IHasTrailers, IHasDisplayOrder, IHasLookupInfo, IMetadataContainer { @@ -54,7 +54,7 @@ namespace MediaBrowser.Controller.Entities.TV public IReadOnlyList RemoteTrailerIds { get; set; } /// - /// airdate, dvd or absolute + /// airdate, dvd or absolute. /// public string DisplayOrder { get; set; } diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs index c327d17c9f..6b544afc68 100644 --- a/MediaBrowser.Controller/Entities/Trailer.cs +++ b/MediaBrowser.Controller/Entities/Trailer.cs @@ -9,7 +9,7 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Controller.Entities { /// - /// Class Trailer + /// Class Trailer. /// public class Trailer : Video, IHasLookupInfo { diff --git a/MediaBrowser.Controller/Entities/UserItemData.cs b/MediaBrowser.Controller/Entities/UserItemData.cs index ab425ee0f9..81f4850a53 100644 --- a/MediaBrowser.Controller/Entities/UserItemData.cs +++ b/MediaBrowser.Controller/Entities/UserItemData.cs @@ -4,7 +4,7 @@ using System.Text.Json.Serialization; namespace MediaBrowser.Controller.Entities { /// - /// Class UserItemData + /// Class UserItemData. /// public class UserItemData { @@ -21,11 +21,11 @@ namespace MediaBrowser.Controller.Entities public string Key { get; set; } /// - /// The _rating + /// The _rating. /// private double? _rating; /// - /// Gets or sets the users 0-10 rating + /// Gets or sets the users 0-10 rating. /// /// The rating. /// Rating;A 0 to 10 rating is required for UserItemData. diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 4cfa0e74d6..4efeede193 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -17,7 +17,7 @@ using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Controller.Entities { /// - /// Class Video + /// Class Video. /// public class Video : BaseItem, IHasAspectRatio, diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs index a01ef5c316..c961da6437 100644 --- a/MediaBrowser.Controller/Entities/Year.cs +++ b/MediaBrowser.Controller/Entities/Year.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Entities { /// - /// Class Year + /// Class Year. /// public class Year : BaseItem, IItemByName { @@ -21,7 +21,7 @@ namespace MediaBrowser.Controller.Entities /// /// Returns the folder containing the item. - /// If the item is a folder, it returns the folder itself + /// If the item is a folder, it returns the folder itself. /// /// The containing folder path. [JsonIgnore] @@ -107,7 +107,7 @@ namespace MediaBrowser.Controller.Entities } /// - /// This is called before any metadata refresh and returns true or false indicating if changes were made + /// This is called before any metadata refresh and returns true or false indicating if changes were made. /// public override bool BeforeMetadataRefresh(bool replaceAllMetdata) { diff --git a/MediaBrowser.Controller/Extensions/StringExtensions.cs b/MediaBrowser.Controller/Extensions/StringExtensions.cs index b1aaf6534e..e09543e145 100644 --- a/MediaBrowser.Controller/Extensions/StringExtensions.cs +++ b/MediaBrowser.Controller/Extensions/StringExtensions.cs @@ -7,7 +7,7 @@ using System.Text.RegularExpressions; namespace MediaBrowser.Controller.Extensions { /// - /// Class BaseExtensions + /// Class BaseExtensions. /// public static class StringExtensions { diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index d1d6c74b86..abdb0f695d 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller { /// - /// Interface IServerApplicationHost + /// Interface IServerApplicationHost. /// public interface IServerApplicationHost : IApplicationHost { diff --git a/MediaBrowser.Controller/IServerApplicationPaths.cs b/MediaBrowser.Controller/IServerApplicationPaths.cs index c35a22ac70..155bf9177b 100644 --- a/MediaBrowser.Controller/IServerApplicationPaths.cs +++ b/MediaBrowser.Controller/IServerApplicationPaths.cs @@ -5,7 +5,7 @@ namespace MediaBrowser.Controller public interface IServerApplicationPaths : IApplicationPaths { /// - /// Gets the path to the base root media directory + /// Gets the path to the base root media directory. /// /// The root folder path. string RootFolderPath { get; } @@ -17,13 +17,13 @@ namespace MediaBrowser.Controller string DefaultUserViewsPath { get; } /// - /// Gets the path to the People directory + /// Gets the path to the People directory. /// /// The people path. string PeoplePath { get; } /// - /// Gets the path to the Genre directory + /// Gets the path to the Genre directory. /// /// The genre path. string GenrePath { get; } @@ -35,25 +35,25 @@ namespace MediaBrowser.Controller string MusicGenrePath { get; } /// - /// Gets the path to the Studio directory + /// Gets the path to the Studio directory. /// /// The studio path. string StudioPath { get; } /// - /// Gets the path to the Year directory + /// Gets the path to the Year directory. /// /// The year path. string YearPath { get; } /// - /// Gets the path to the General IBN directory + /// Gets the path to the General IBN directory. /// /// The general path. string GeneralPath { get; } /// - /// Gets the path to the Ratings IBN directory + /// Gets the path to the Ratings IBN directory. /// /// The ratings path. string RatingsPath { get; } @@ -65,7 +65,7 @@ namespace MediaBrowser.Controller string MediaInfoImagesPath { get; } /// - /// Gets the path to the user configuration directory + /// Gets the path to the user configuration directory. /// /// The user configuration directory path. string UserConfigurationDirectoryPath { get; } diff --git a/MediaBrowser.Controller/Library/IIntroProvider.cs b/MediaBrowser.Controller/Library/IIntroProvider.cs index aa70016112..d45493d404 100644 --- a/MediaBrowser.Controller/Library/IIntroProvider.cs +++ b/MediaBrowser.Controller/Library/IIntroProvider.cs @@ -5,7 +5,7 @@ using MediaBrowser.Controller.Entities; namespace MediaBrowser.Controller.Library { /// - /// Class BaseIntroProvider + /// Class BaseIntroProvider. /// public interface IIntroProvider { diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index d7237039ef..47c080ebdc 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -21,7 +21,7 @@ using Person = MediaBrowser.Controller.Entities.Person; namespace MediaBrowser.Controller.Library { /// - /// Interface ILibraryManager + /// Interface ILibraryManager. /// public interface ILibraryManager { @@ -38,7 +38,7 @@ namespace MediaBrowser.Controller.Library bool allowIgnorePath = true); /// - /// Resolves a set of files into a list of BaseItem + /// Resolves a set of files into a list of BaseItem. /// IEnumerable ResolvePaths( IEnumerable files, @@ -54,7 +54,7 @@ namespace MediaBrowser.Controller.Library AggregateFolder RootFolder { get; } /// - /// Gets a Person + /// Gets a Person. /// /// The name. /// Task{Person}. @@ -75,14 +75,14 @@ namespace MediaBrowser.Controller.Library MusicArtist GetArtist(string name); MusicArtist GetArtist(string name, DtoOptions options); /// - /// Gets a Studio + /// Gets a Studio. /// /// The name. /// Task{Studio}. Studio GetStudio(string name); /// - /// Gets a Genre + /// Gets a Genre. /// /// The name. /// Task{Genre}. @@ -96,7 +96,7 @@ namespace MediaBrowser.Controller.Library MusicGenre GetMusicGenre(string name); /// - /// Gets a Year + /// Gets a Year. /// /// The value. /// Task{Year}. @@ -113,7 +113,7 @@ namespace MediaBrowser.Controller.Library Task ValidatePeople(CancellationToken cancellationToken, IProgress progress); /// - /// Reloads the root media folder + /// Reloads the root media folder. /// /// The progress. /// The cancellation token. diff --git a/MediaBrowser.Controller/Library/ILibraryPostScanTask.cs b/MediaBrowser.Controller/Library/ILibraryPostScanTask.cs index cba5e8fd72..4032e9d83b 100644 --- a/MediaBrowser.Controller/Library/ILibraryPostScanTask.cs +++ b/MediaBrowser.Controller/Library/ILibraryPostScanTask.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Controller.Library { /// - /// An interface for tasks that run after the media library scan + /// An interface for tasks that run after the media library scan. /// public interface ILibraryPostScanTask { diff --git a/MediaBrowser.Controller/Library/IMetadataSaver.cs b/MediaBrowser.Controller/Library/IMetadataSaver.cs index dd119984e6..027cc5b40e 100644 --- a/MediaBrowser.Controller/Library/IMetadataSaver.cs +++ b/MediaBrowser.Controller/Library/IMetadataSaver.cs @@ -4,7 +4,7 @@ using MediaBrowser.Controller.Entities; namespace MediaBrowser.Controller.Library { /// - /// Interface IMetadataSaver + /// Interface IMetadataSaver. /// public interface IMetadataSaver { diff --git a/MediaBrowser.Controller/Library/ISearchEngine.cs b/MediaBrowser.Controller/Library/ISearchEngine.cs index 8498b92aec..31dcbba5bd 100644 --- a/MediaBrowser.Controller/Library/ISearchEngine.cs +++ b/MediaBrowser.Controller/Library/ISearchEngine.cs @@ -4,7 +4,7 @@ using MediaBrowser.Model.Search; namespace MediaBrowser.Controller.Library { /// - /// Interface ILibrarySearchEngine + /// Interface ILibrarySearchEngine. /// public interface ISearchEngine { diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs index f5ccad671b..d08ad4cac0 100644 --- a/MediaBrowser.Controller/Library/IUserDataManager.cs +++ b/MediaBrowser.Controller/Library/IUserDataManager.cs @@ -42,14 +42,14 @@ namespace MediaBrowser.Controller.Library UserItemDataDto GetUserDataDto(BaseItem item, BaseItemDto itemDto, User user, DtoOptions dto_options); /// - /// Get all user data for the given user + /// Get all user data for the given user. /// /// /// List GetAllUserData(Guid userId); /// - /// Save the all provided user data for the given user + /// Save the all provided user data for the given user. /// /// /// @@ -58,7 +58,7 @@ namespace MediaBrowser.Controller.Library void SaveAllUserData(Guid userId, UserItemData[] userData, CancellationToken cancellationToken); /// - /// Updates playstate for an item and returns true or false indicating if it was played to completion + /// Updates playstate for an item and returns true or false indicating if it was played to completion. /// bool UpdatePlayState(BaseItem item, UserItemData data, long? positionTicks); } diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index b5b2e47297..fe3e4f9e68 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -11,7 +11,7 @@ using MediaBrowser.Model.Users; namespace MediaBrowser.Controller.Library { /// - /// Interface IUserManager + /// Interface IUserManager. /// public interface IUserManager { diff --git a/MediaBrowser.Controller/Library/ItemChangeEventArgs.cs b/MediaBrowser.Controller/Library/ItemChangeEventArgs.cs index c9671de479..b5c48321ba 100644 --- a/MediaBrowser.Controller/Library/ItemChangeEventArgs.cs +++ b/MediaBrowser.Controller/Library/ItemChangeEventArgs.cs @@ -3,7 +3,7 @@ using MediaBrowser.Controller.Entities; namespace MediaBrowser.Controller.Library { /// - /// Class ItemChangeEventArgs + /// Class ItemChangeEventArgs. /// public class ItemChangeEventArgs { diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs index cca85cd3b9..7c1463862b 100644 --- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs +++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Controller.Library public class ItemResolveArgs : EventArgs { /// - /// The _app paths + /// The _app paths. /// private readonly IServerApplicationPaths _appPaths; diff --git a/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs b/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs index b4e2051845..abe1294849 100644 --- a/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs +++ b/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs @@ -8,7 +8,7 @@ using MediaBrowser.Model.Dto; namespace MediaBrowser.Controller.Library { /// - /// Holds information about a playback progress event + /// Holds information about a playback progress event. /// public class PlaybackProgressEventArgs : EventArgs { diff --git a/MediaBrowser.Controller/Library/Profiler.cs b/MediaBrowser.Controller/Library/Profiler.cs index 0febef3d3e..a566de3388 100644 --- a/MediaBrowser.Controller/Library/Profiler.cs +++ b/MediaBrowser.Controller/Library/Profiler.cs @@ -5,21 +5,21 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Library { /// - /// Class Profiler + /// Class Profiler. /// public class Profiler : IDisposable { /// - /// The name + /// The name. /// readonly string _name; /// - /// The stopwatch + /// The stopwatch. /// readonly Stopwatch _stopwatch; /// - /// The _logger + /// The _logger. /// private readonly ILogger _logger; diff --git a/MediaBrowser.Controller/Library/SearchHintInfo.cs b/MediaBrowser.Controller/Library/SearchHintInfo.cs index 692431e345..897c2b7f49 100644 --- a/MediaBrowser.Controller/Library/SearchHintInfo.cs +++ b/MediaBrowser.Controller/Library/SearchHintInfo.cs @@ -3,7 +3,7 @@ using MediaBrowser.Controller.Entities; namespace MediaBrowser.Controller.Library { /// - /// Class SearchHintInfo + /// Class SearchHintInfo. /// public class SearchHintInfo { diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs index fd5fb6748f..ebdcfbdb36 100644 --- a/MediaBrowser.Controller/Library/TVUtils.cs +++ b/MediaBrowser.Controller/Library/TVUtils.cs @@ -3,7 +3,7 @@ using System; namespace MediaBrowser.Controller.Library { /// - /// Class TVUtils + /// Class TVUtils. /// public static class TVUtils { diff --git a/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs b/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs index 3e7351b8b9..fa01927843 100644 --- a/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs +++ b/MediaBrowser.Controller/Library/UserDataSaveEventArgs.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Library { /// - /// Class UserDataSaveEventArgs + /// Class UserDataSaveEventArgs. /// public class UserDataSaveEventArgs : EventArgs { diff --git a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs index 70477fce73..1b7095c7bf 100644 --- a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs @@ -3,7 +3,7 @@ using MediaBrowser.Model.LiveTv; namespace MediaBrowser.Controller.LiveTv { /// - /// Class ChannelInfo + /// Class ChannelInfo. /// public class ChannelInfo { @@ -44,13 +44,13 @@ namespace MediaBrowser.Controller.LiveTv public ChannelType ChannelType { get; set; } /// - /// Supply the image path if it can be accessed directly from the file system + /// Supply the image path if it can be accessed directly from the file system. /// /// The image path. public string ImagePath { get; set; } /// - /// Supply the image url if it can be downloaded + /// Supply the image url if it can be downloaded. /// /// The image URL. public string ImageUrl { get; set; } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index bc3bf78f01..aa24d5ab8e 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -14,7 +14,7 @@ using MediaBrowser.Model.Querying; namespace MediaBrowser.Controller.LiveTv { /// - /// Manages all live tv services installed on the server + /// Manages all live tv services installed on the server. /// public interface ILiveTvManager { diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs index e17db34c6b..472b061e6a 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -140,7 +140,7 @@ namespace MediaBrowser.Controller.LiveTv /// /// Returns the folder containing the item. - /// If the item is a folder, it returns the folder itself + /// If the item is a folder, it returns the folder itself. /// /// The containing folder path. [JsonIgnore] diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs index 5d0f13192d..b847140f50 100644 --- a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Controller.LiveTv public string ChannelId { get; set; } /// - /// Name of the program + /// Name of the program. /// public string Name { get; set; } @@ -95,13 +95,13 @@ namespace MediaBrowser.Controller.LiveTv public string EpisodeTitle { get; set; } /// - /// Supply the image path if it can be accessed directly from the file system + /// Supply the image path if it can be accessed directly from the file system. /// /// The image path. public string ImagePath { get; set; } /// - /// Supply the image url if it can be downloaded + /// Supply the image url if it can be downloaded. /// /// The image URL. public string ImageUrl { get; set; } diff --git a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs index 432388d6b6..b9e0218ab2 100644 --- a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs +++ b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs @@ -169,13 +169,13 @@ namespace MediaBrowser.Controller.LiveTv public float? CommunityRating { get; set; } /// - /// Supply the image path if it can be accessed directly from the file system + /// Supply the image path if it can be accessed directly from the file system. /// /// The image path. public string ImagePath { get; set; } /// - /// Supply the image url if it can be downloaded + /// Supply the image url if it can be downloaded. /// /// The image URL. public string ImageUrl { get; set; } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 0ca42c0e00..680d92c2d7 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -108,7 +108,7 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// Gets the name of the output video codec + /// Gets the name of the output video codec. /// public string GetVideoEncoder(EncodingJobInfo state, EncodingOptions encodingOptions) { @@ -285,7 +285,7 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// Infers the audio codec based on the url + /// Infers the audio codec based on the url. /// public string InferAudioCodec(string container) { @@ -703,7 +703,7 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// Gets the video bitrate to specify on the command line + /// Gets the video bitrate to specify on the command line. /// public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, string defaultPreset) { @@ -1300,7 +1300,7 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// Gets the number of audio channels to specify on the command line + /// Gets the number of audio channels to specify on the command line. /// /// The state. /// The audio stream. @@ -1486,7 +1486,7 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// Determines which stream will be used for playback + /// Determines which stream will be used for playback. /// /// All stream. /// Index of the desired. @@ -1961,7 +1961,7 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// If we're going to put a fixed size on the command line, this will calculate it + /// If we're going to put a fixed size on the command line, this will calculate it. /// public string GetOutputSizeParam( EncodingJobInfo state, @@ -2505,7 +2505,7 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// Gets the name of the output video codec + /// Gets the name of the output video codec. /// protected string GetHardwareAcceleratedVideoDecoder(EncodingJobInfo state, EncodingOptions encodingOptions) { diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index acf1aae895..32973619d4 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -418,7 +418,7 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// Predicts the audio sample rate that will be in the output stream + /// Predicts the audio sample rate that will be in the output stream. /// public double? TargetVideoLevel { @@ -441,7 +441,7 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// Predicts the audio sample rate that will be in the output stream + /// Predicts the audio sample rate that will be in the output stream. /// public int? TargetVideoBitDepth { @@ -476,7 +476,7 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// Predicts the audio sample rate that will be in the output stream + /// Predicts the audio sample rate that will be in the output stream. /// public float? TargetFramerate { @@ -508,7 +508,7 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// Predicts the audio sample rate that will be in the output stream + /// Predicts the audio sample rate that will be in the output stream. /// public int? TargetPacketLength { @@ -524,7 +524,7 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// Predicts the audio sample rate that will be in the output stream + /// Predicts the audio sample rate that will be in the output stream. /// public string TargetVideoProfile { @@ -679,20 +679,20 @@ namespace MediaBrowser.Controller.MediaEncoding } /// - /// Enum TranscodingJobType + /// Enum TranscodingJobType. /// public enum TranscodingJobType { /// - /// The progressive + /// The progressive. /// Progressive, /// - /// The HLS + /// The HLS. /// Hls, /// - /// The dash + /// The dash. /// Dash } diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 37f0b11a74..e33c4ad0be 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -11,7 +11,7 @@ using MediaBrowser.Model.System; namespace MediaBrowser.Controller.MediaEncoding { /// - /// Interface IMediaEncoder + /// Interface IMediaEncoder. /// public interface IMediaEncoder : ITranscoderSupport { diff --git a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs index 5cedc3d576..6c9bbb043e 100644 --- a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs +++ b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs @@ -7,7 +7,7 @@ using MediaBrowser.Model.IO; namespace MediaBrowser.Controller.MediaEncoding { /// - /// Class MediaEncoderHelpers + /// Class MediaEncoderHelpers. /// public static class MediaEncoderHelpers { diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index df90c399b9..9e262d31ed 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -11,7 +11,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Net { /// - /// Starts sending data over a web socket periodically when a message is received, and then stops when a corresponding stop message is received + /// Starts sending data over a web socket periodically when a message is received, and then stops when a corresponding stop message is received. /// /// The type of the T return data type. /// The type of the T state type. @@ -20,7 +20,7 @@ namespace MediaBrowser.Controller.Net where TReturnDataType : class { /// - /// The _active connections + /// The _active connections. /// private readonly List> _activeConnections = new List>(); @@ -38,7 +38,7 @@ namespace MediaBrowser.Controller.Net protected abstract Task GetDataToSend(); /// - /// The logger + /// The logger. /// protected ILogger> Logger; @@ -78,7 +78,7 @@ namespace MediaBrowser.Controller.Net } /// - /// Starts sending messages over a web socket + /// Starts sending messages over a web socket. /// /// The message. private void Start(WebSocketMessageInfo message) @@ -180,7 +180,7 @@ namespace MediaBrowser.Controller.Net } /// - /// Stops sending messages over a web socket + /// Stops sending messages over a web socket. /// /// The message. private void Stop(WebSocketMessageInfo message) diff --git a/MediaBrowser.Controller/Net/IHttpResultFactory.cs b/MediaBrowser.Controller/Net/IHttpResultFactory.cs index 25404fa78d..609bd5f596 100644 --- a/MediaBrowser.Controller/Net/IHttpResultFactory.cs +++ b/MediaBrowser.Controller/Net/IHttpResultFactory.cs @@ -7,7 +7,7 @@ using MediaBrowser.Model.Services; namespace MediaBrowser.Controller.Net { /// - /// Interface IHttpResultFactory + /// Interface IHttpResultFactory. /// public interface IHttpResultFactory { diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs index efb5f4ac3f..e6609fae38 100644 --- a/MediaBrowser.Controller/Net/IHttpServer.cs +++ b/MediaBrowser.Controller/Net/IHttpServer.cs @@ -29,19 +29,19 @@ namespace MediaBrowser.Controller.Net void Init(IEnumerable serviceTypes, IEnumerable listener, IEnumerable urlPrefixes); /// - /// If set, all requests will respond with this message + /// If set, all requests will respond with this message. /// string GlobalResponse { get; set; } /// - /// The HTTP request handler + /// The HTTP request handler. /// /// /// Task RequestHandler(HttpContext context); /// - /// Get the default CORS headers + /// Get the default CORS headers. /// /// /// diff --git a/MediaBrowser.Controller/Net/IWebSocketListener.cs b/MediaBrowser.Controller/Net/IWebSocketListener.cs index 0f472a2bc7..7250a57b0a 100644 --- a/MediaBrowser.Controller/Net/IWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/IWebSocketListener.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Controller.Net { /// - ///This is an interface for listening to messages coming through a web socket connection + ///This is an interface for listening to messages coming through a web socket connection. /// public interface IWebSocketListener { diff --git a/MediaBrowser.Controller/Net/WebSocketMessageInfo.cs b/MediaBrowser.Controller/Net/WebSocketMessageInfo.cs index 5bf39cae6d..be0b3ddc3f 100644 --- a/MediaBrowser.Controller/Net/WebSocketMessageInfo.cs +++ b/MediaBrowser.Controller/Net/WebSocketMessageInfo.cs @@ -3,7 +3,7 @@ using MediaBrowser.Model.Net; namespace MediaBrowser.Controller.Net { /// - /// Class WebSocketMessageInfo + /// Class WebSocketMessageInfo. /// public class WebSocketMessageInfo : WebSocketMessage { diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index 75fc43a047..0ae1b8bbfa 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -9,12 +9,12 @@ using MediaBrowser.Model.Querying; namespace MediaBrowser.Controller.Persistence { /// - /// Provides an interface to implement an Item repository + /// Provides an interface to implement an Item repository. /// public interface IItemRepository : IRepository { /// - /// Saves an item + /// Saves an item. /// /// The item. /// The cancellation token. @@ -43,14 +43,14 @@ namespace MediaBrowser.Controller.Persistence BaseItem RetrieveItem(Guid id); /// - /// Gets chapters for an item + /// Gets chapters for an item. /// /// /// List GetChapters(BaseItem id); /// - /// Gets a single chapter for an item + /// Gets a single chapter for an item. /// /// /// diff --git a/MediaBrowser.Controller/Persistence/IRepository.cs b/MediaBrowser.Controller/Persistence/IRepository.cs index 56bf1dd5a8..42f2850762 100644 --- a/MediaBrowser.Controller/Persistence/IRepository.cs +++ b/MediaBrowser.Controller/Persistence/IRepository.cs @@ -3,12 +3,12 @@ using System; namespace MediaBrowser.Controller.Persistence { /// - /// Provides a base interface for all the repository interfaces + /// Provides a base interface for all the repository interfaces. /// public interface IRepository : IDisposable { /// - /// Gets the name of the repository + /// Gets the name of the repository. /// /// The name. string Name { get; } diff --git a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs index 4c327eeef9..ba7c9fd509 100644 --- a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs +++ b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs @@ -5,7 +5,7 @@ using MediaBrowser.Controller.Entities; namespace MediaBrowser.Controller.Persistence { /// - /// Provides an interface to implement a UserData repository + /// Provides an interface to implement a UserData repository. /// public interface IUserDataRepository : IRepository { @@ -30,14 +30,14 @@ namespace MediaBrowser.Controller.Persistence UserItemData GetUserData(long userId, List keys); /// - /// Return all user data associated with the given user + /// Return all user data associated with the given user. /// /// /// List GetAllUserData(long userId); /// - /// Save all user data associated with the given user + /// Save all user data associated with the given user. /// /// /// diff --git a/MediaBrowser.Controller/Plugins/IPluginConfigurationPage.cs b/MediaBrowser.Controller/Plugins/IPluginConfigurationPage.cs index c156da9246..077f5ab63e 100644 --- a/MediaBrowser.Controller/Plugins/IPluginConfigurationPage.cs +++ b/MediaBrowser.Controller/Plugins/IPluginConfigurationPage.cs @@ -4,7 +4,7 @@ using MediaBrowser.Common.Plugins; namespace MediaBrowser.Controller.Plugins { /// - /// Interface IConfigurationPage + /// Interface IConfigurationPage. /// public interface IPluginConfigurationPage { @@ -34,16 +34,16 @@ namespace MediaBrowser.Controller.Plugins } /// - /// Enum ConfigurationPageType + /// Enum ConfigurationPageType. /// public enum ConfigurationPageType { /// - /// The plugin configuration + /// The plugin configuration. /// PluginConfiguration, /// - /// The none + /// The none. /// None } diff --git a/MediaBrowser.Controller/Providers/IMetadataProvider.cs b/MediaBrowser.Controller/Providers/IMetadataProvider.cs index 3e595ff934..62b16dadd7 100644 --- a/MediaBrowser.Controller/Providers/IMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/IMetadataProvider.cs @@ -3,7 +3,7 @@ using MediaBrowser.Controller.Entities; namespace MediaBrowser.Controller.Providers { /// - /// Marker interface + /// Marker interface. /// public interface IMetadataProvider { diff --git a/MediaBrowser.Controller/Providers/MetadataRefreshMode.cs b/MediaBrowser.Controller/Providers/MetadataRefreshMode.cs index 02152ee332..6d49b55104 100644 --- a/MediaBrowser.Controller/Providers/MetadataRefreshMode.cs +++ b/MediaBrowser.Controller/Providers/MetadataRefreshMode.cs @@ -3,22 +3,22 @@ namespace MediaBrowser.Controller.Providers public enum MetadataRefreshMode { /// - /// The none + /// The none. /// None = 0, /// - /// The validation only + /// The validation only. /// ValidationOnly = 1, /// - /// Providers will be executed based on default rules + /// Providers will be executed based on default rules. /// Default = 2, /// - /// All providers will be executed to search for new metadata + /// All providers will be executed to search for new metadata. /// FullRefresh = 3 } diff --git a/MediaBrowser.Controller/Providers/MetadataResult.cs b/MediaBrowser.Controller/Providers/MetadataResult.cs index 59adaedfa9..83efed2a14 100644 --- a/MediaBrowser.Controller/Providers/MetadataResult.cs +++ b/MediaBrowser.Controller/Providers/MetadataResult.cs @@ -40,7 +40,7 @@ namespace MediaBrowser.Controller.Providers } /// - /// Not only does this clear, but initializes the list so that services can differentiate between a null list and zero people + /// Not only does this clear, but initializes the list so that services can differentiate between a null list and zero people. /// public void ResetPeople() { diff --git a/MediaBrowser.Controller/Providers/VideoContentType.cs b/MediaBrowser.Controller/Providers/VideoContentType.cs index c3b8964a37..49d587f6ce 100644 --- a/MediaBrowser.Controller/Providers/VideoContentType.cs +++ b/MediaBrowser.Controller/Providers/VideoContentType.cs @@ -1,17 +1,17 @@ namespace MediaBrowser.Controller.Providers { /// - /// Enum VideoContentType + /// Enum VideoContentType. /// public enum VideoContentType { /// - /// The episode + /// The episode. /// Episode = 0, /// - /// The movie + /// The movie. /// Movie = 1 } diff --git a/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs b/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs index 637a7e3f05..67acdd9a3c 100644 --- a/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs +++ b/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs @@ -4,7 +4,7 @@ using MediaBrowser.Controller.Library; namespace MediaBrowser.Controller.Resolvers { /// - /// Class ItemResolver + /// Class ItemResolver. /// /// public abstract class ItemResolver : IItemResolver @@ -27,7 +27,7 @@ namespace MediaBrowser.Controller.Resolvers public virtual ResolverPriority Priority => ResolverPriority.First; /// - /// Sets initial values on the newly resolved item + /// Sets initial values on the newly resolved item. /// /// The item. /// The args. diff --git a/MediaBrowser.Controller/Resolvers/IItemResolver.cs b/MediaBrowser.Controller/Resolvers/IItemResolver.cs index 16e37d2493..2e82b51f1e 100644 --- a/MediaBrowser.Controller/Resolvers/IItemResolver.cs +++ b/MediaBrowser.Controller/Resolvers/IItemResolver.cs @@ -7,7 +7,7 @@ using MediaBrowser.Model.IO; namespace MediaBrowser.Controller.Resolvers { /// - /// Interface IItemResolver + /// Interface IItemResolver. /// public interface IItemResolver { diff --git a/MediaBrowser.Controller/Resolvers/ResolverPriority.cs b/MediaBrowser.Controller/Resolvers/ResolverPriority.cs index e393100952..1911e5c1dd 100644 --- a/MediaBrowser.Controller/Resolvers/ResolverPriority.cs +++ b/MediaBrowser.Controller/Resolvers/ResolverPriority.cs @@ -1,25 +1,25 @@ namespace MediaBrowser.Controller.Resolvers { /// - /// Enum ResolverPriority + /// Enum ResolverPriority. /// public enum ResolverPriority { /// - /// The first + /// The first. /// First = 1, /// - /// The second + /// The second. /// Second = 2, /// - /// The third + /// The third. /// Third = 3, Fourth = 4, /// - /// The last + /// The last. /// Last = 5 } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 1fdb588ebf..e54f210506 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -13,7 +13,7 @@ using MediaBrowser.Model.SyncPlay; namespace MediaBrowser.Controller.Session { /// - /// Interface ISessionManager + /// Interface ISessionManager. /// public interface ISessionManager { @@ -79,14 +79,14 @@ namespace MediaBrowser.Controller.Session void UpdateDeviceName(string sessionId, string reportedDeviceName); /// - /// Used to report that playback has started for an item + /// Used to report that playback has started for an item. /// /// The info. /// Task. Task OnPlaybackStart(PlaybackStartInfo info); /// - /// Used to report playback progress for an item + /// Used to report playback progress for an item. /// /// The info. /// Task. @@ -96,7 +96,7 @@ namespace MediaBrowser.Controller.Session Task OnPlaybackProgress(PlaybackProgressInfo info, bool isAutomated); /// - /// Used to report that playback has ended for an item + /// Used to report that playback has ended for an item. /// /// The info. /// Task. diff --git a/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs b/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs index 31087edec7..727cbe639c 100644 --- a/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs +++ b/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs @@ -4,7 +4,7 @@ using MediaBrowser.Controller.Entities; namespace MediaBrowser.Controller.Sorting { /// - /// Interface IBaseItemComparer + /// Interface IBaseItemComparer. /// public interface IBaseItemComparer : IComparer { diff --git a/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs b/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs index 6f75d16de1..6d03d97ae3 100644 --- a/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs +++ b/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs @@ -3,7 +3,7 @@ using MediaBrowser.Controller.Library; namespace MediaBrowser.Controller.Sorting { /// - /// Represents a BaseItem comparer that requires a User to perform it's comparison + /// Represents a BaseItem comparer that requires a User to perform it's comparison. /// public interface IUserBaseItemComparer : IBaseItemComparer { diff --git a/MediaBrowser.Controller/Sync/IRemoteSyncProvider.cs b/MediaBrowser.Controller/Sync/IRemoteSyncProvider.cs index c0b62b7530..b2c53365c4 100644 --- a/MediaBrowser.Controller/Sync/IRemoteSyncProvider.cs +++ b/MediaBrowser.Controller/Sync/IRemoteSyncProvider.cs @@ -1,7 +1,7 @@ namespace MediaBrowser.Controller.Sync { /// - /// A marker interface + /// A marker interface. /// public interface IRemoteSyncProvider { diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index 0ceb55c572..da5a3d5bcb 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -14,14 +14,14 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.LocalMetadata.Parsers { /// - /// Provides a base class for parsing metadata xml + /// Provides a base class for parsing metadata xml. /// /// public class BaseItemXmlParser where T : BaseItem { /// - /// The logger + /// The logger. /// protected ILogger> Logger { get; private set; } protected IProviderManager ProviderManager { get; private set; } @@ -39,7 +39,7 @@ namespace MediaBrowser.LocalMetadata.Parsers } /// - /// Fetches metadata for an item from one xml file + /// Fetches metadata for an item from one xml file. /// /// The item. /// The metadata file. @@ -124,7 +124,7 @@ namespace MediaBrowser.LocalMetadata.Parsers private readonly CultureInfo _usCulture = new CultureInfo("en-US"); /// - /// Fetches metadata from one Xml Element + /// Fetches metadata from one Xml Element. /// /// The reader. /// The item result. @@ -1230,7 +1230,7 @@ namespace MediaBrowser.LocalMetadata.Parsers /// - /// Used to split names of comma or pipe delimeted genres and people + /// Used to split names of comma or pipe delimeted genres and people. /// /// The value. /// IEnumerable{System.String}. diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs index 3260f3051e..e6359f4fbe 100644 --- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs +++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs @@ -9,7 +9,7 @@ using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.MediaEncoding.BdInfo { /// - /// Class BdInfoExaminer + /// Class BdInfoExaminer. /// public class BdInfoExaminer : IBlurayExaminer { diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index a4896d5f90..59fd6b848c 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -25,7 +25,7 @@ using System.Diagnostics; namespace MediaBrowser.MediaEncoding.Encoder { /// - /// Class MediaEncoder + /// Class MediaEncoder. /// public class MediaEncoder : IMediaEncoder, IDisposable { @@ -425,7 +425,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } /// - /// The us culture + /// The us culture. /// protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); diff --git a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs index 78dc7b6078..3aa296f7f5 100644 --- a/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs +++ b/MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.MediaEncoding.Probing } /// - /// Gets a string from an FFProbeResult tags dictionary + /// Gets a string from an FFProbeResult tags dictionary. /// /// The tags. /// The key. @@ -52,7 +52,7 @@ namespace MediaBrowser.MediaEncoding.Probing } /// - /// Gets an int from an FFProbeResult tags dictionary + /// Gets an int from an FFProbeResult tags dictionary. /// /// The tags. /// The key. @@ -73,7 +73,7 @@ namespace MediaBrowser.MediaEncoding.Probing } /// - /// Gets a DateTime from an FFProbeResult tags dictionary + /// Gets a DateTime from an FFProbeResult tags dictionary. /// /// The tags. /// The key. @@ -94,7 +94,7 @@ namespace MediaBrowser.MediaEncoding.Probing } /// - /// Converts a dictionary to case insensitive + /// Converts a dictionary to case insensitive. /// /// The dict. /// Dictionary{System.StringSystem.String}. diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 7d57a691e6..4c48c038fc 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -515,7 +515,7 @@ namespace MediaBrowser.MediaEncoding.Probing } /// - /// Converts ffprobe stream info to our MediaAttachment class + /// Converts ffprobe stream info to our MediaAttachment class. /// /// The stream info. /// MediaAttachments. @@ -548,7 +548,7 @@ namespace MediaBrowser.MediaEncoding.Probing } /// - /// Converts ffprobe stream info to our MediaStream class + /// Converts ffprobe stream info to our MediaStream class. /// /// if set to true [is info]. /// The stream info. @@ -767,7 +767,7 @@ namespace MediaBrowser.MediaEncoding.Probing } /// - /// Gets a string from an FFProbeResult tags dictionary + /// Gets a string from an FFProbeResult tags dictionary. /// /// The tags. /// The key. @@ -1154,7 +1154,7 @@ namespace MediaBrowser.MediaEncoding.Probing } /// - /// Gets the studios from the tags collection + /// Gets the studios from the tags collection. /// /// The info. /// The tags. @@ -1191,7 +1191,7 @@ namespace MediaBrowser.MediaEncoding.Probing } /// - /// Gets the genres from the tags collection + /// Gets the genres from the tags collection. /// /// The information. /// The tags. diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 7e9894f0a5..763d7f641b 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -344,7 +344,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles } /// - /// The _semaphoreLocks + /// The _semaphoreLocks. /// private readonly ConcurrentDictionary _semaphoreLocks = new ConcurrentDictionary(); diff --git a/MediaBrowser.Model/Channels/ChannelFeatures.cs b/MediaBrowser.Model/Channels/ChannelFeatures.cs index 496102d83b..a55754eddc 100644 --- a/MediaBrowser.Model/Channels/ChannelFeatures.cs +++ b/MediaBrowser.Model/Channels/ChannelFeatures.cs @@ -38,7 +38,7 @@ namespace MediaBrowser.Model.Channels public ChannelMediaContentType[] ContentTypes { get; set; } /// - /// Represents the maximum number of records the channel allows retrieving at a time + /// Represents the maximum number of records the channel allows retrieving at a time. /// public int? MaxPageSize { get; set; } diff --git a/MediaBrowser.Model/Channels/ChannelQuery.cs b/MediaBrowser.Model/Channels/ChannelQuery.cs index d112600390..fd90e7f062 100644 --- a/MediaBrowser.Model/Channels/ChannelQuery.cs +++ b/MediaBrowser.Model/Channels/ChannelQuery.cs @@ -10,7 +10,7 @@ namespace MediaBrowser.Model.Channels public class ChannelQuery { /// - /// Fields to return within the items, in addition to basic information + /// Fields to return within the items, in addition to basic information. /// /// The fields. public ItemFields[] Fields { get; set; } @@ -34,7 +34,7 @@ namespace MediaBrowser.Model.Channels public int? StartIndex { get; set; } /// - /// The maximum number of items to return + /// The maximum number of items to return. /// /// The limit. public int? Limit { get; set; } diff --git a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs index cdd322c948..54f4fb293c 100644 --- a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs +++ b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Model.Configuration public class BaseApplicationConfiguration { /// - /// The number of days we should retain log files + /// The number of days we should retain log files. /// /// The log file retention days. public int LogFileRetentionDays { get; set; } diff --git a/MediaBrowser.Model/Configuration/MetadataPluginType.cs b/MediaBrowser.Model/Configuration/MetadataPluginType.cs index bff12799fa..4c5e952664 100644 --- a/MediaBrowser.Model/Configuration/MetadataPluginType.cs +++ b/MediaBrowser.Model/Configuration/MetadataPluginType.cs @@ -3,7 +3,7 @@ namespace MediaBrowser.Model.Configuration { /// - /// Enum MetadataPluginType + /// Enum MetadataPluginType. /// public enum MetadataPluginType { diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index afbe02dd36..7428876204 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -111,19 +111,19 @@ namespace MediaBrowser.Model.Configuration public string MetadataCountryCode { get; set; } /// - /// Characters to be replaced with a ' ' in strings to create a sort name + /// Characters to be replaced with a ' ' in strings to create a sort name. /// /// The sort replace characters. public string[] SortReplaceCharacters { get; set; } /// - /// Characters to be removed from strings to create a sort name + /// Characters to be removed from strings to create a sort name. /// /// The sort remove characters. public string[] SortRemoveCharacters { get; set; } /// - /// Words to be removed from strings to create a sort name + /// Words to be removed from strings to create a sort name. /// /// The sort remove words. public string[] SortRemoveWords { get; set; } diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs index 85d864eec4..d236ac2152 100644 --- a/MediaBrowser.Model/Configuration/UserConfiguration.cs +++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs @@ -7,7 +7,7 @@ using Jellyfin.Data.Enums; namespace MediaBrowser.Model.Configuration { /// - /// Class UserConfiguration + /// Class UserConfiguration. /// public class UserConfiguration { diff --git a/MediaBrowser.Model/Dlna/AudioOptions.cs b/MediaBrowser.Model/Dlna/AudioOptions.cs index fc555c5f70..baa99b9037 100644 --- a/MediaBrowser.Model/Dlna/AudioOptions.cs +++ b/MediaBrowser.Model/Dlna/AudioOptions.cs @@ -47,7 +47,7 @@ namespace MediaBrowser.Model.Dlna public int? MaxAudioChannels { get; set; } /// - /// The application's configured quality setting + /// The application's configured quality setting. /// public long? MaxBitrate { get; set; } diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 2444638030..28d8a64396 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -465,7 +465,7 @@ namespace MediaBrowser.Model.Dlna } /// - /// Returns the audio stream that will be used + /// Returns the audio stream that will be used. /// public MediaStream TargetAudioStream { @@ -481,7 +481,7 @@ namespace MediaBrowser.Model.Dlna } /// - /// Returns the video stream that will be used + /// Returns the video stream that will be used. /// public MediaStream TargetVideoStream { @@ -497,7 +497,7 @@ namespace MediaBrowser.Model.Dlna } /// - /// Predicts the audio sample rate that will be in the output stream + /// Predicts the audio sample rate that will be in the output stream. /// public int? TargetAudioSampleRate { @@ -509,7 +509,7 @@ namespace MediaBrowser.Model.Dlna } /// - /// Predicts the audio sample rate that will be in the output stream + /// Predicts the audio sample rate that will be in the output stream. /// public int? TargetAudioBitDepth { @@ -532,7 +532,7 @@ namespace MediaBrowser.Model.Dlna } /// - /// Predicts the audio sample rate that will be in the output stream + /// Predicts the audio sample rate that will be in the output stream. /// public int? TargetVideoBitDepth { @@ -579,7 +579,7 @@ namespace MediaBrowser.Model.Dlna } /// - /// Predicts the audio sample rate that will be in the output stream + /// Predicts the audio sample rate that will be in the output stream. /// public float? TargetFramerate { @@ -593,7 +593,7 @@ namespace MediaBrowser.Model.Dlna } /// - /// Predicts the audio sample rate that will be in the output stream + /// Predicts the audio sample rate that will be in the output stream. /// public double? TargetVideoLevel { @@ -680,7 +680,7 @@ namespace MediaBrowser.Model.Dlna } /// - /// Predicts the audio sample rate that will be in the output stream + /// Predicts the audio sample rate that will be in the output stream. /// public int? TargetPacketLength { @@ -694,7 +694,7 @@ namespace MediaBrowser.Model.Dlna } /// - /// Predicts the audio sample rate that will be in the output stream + /// Predicts the audio sample rate that will be in the output stream. /// public string TargetVideoProfile { @@ -732,7 +732,7 @@ namespace MediaBrowser.Model.Dlna } /// - /// Predicts the audio bitrate that will be in the output stream + /// Predicts the audio bitrate that will be in the output stream. /// public int? TargetAudioBitrate { @@ -746,7 +746,7 @@ namespace MediaBrowser.Model.Dlna } /// - /// Predicts the audio channels that will be in the output stream + /// Predicts the audio channels that will be in the output stream. /// public int? TargetAudioChannels { @@ -787,7 +787,7 @@ namespace MediaBrowser.Model.Dlna } /// - /// Predicts the audio codec that will be in the output stream + /// Predicts the audio codec that will be in the output stream. /// public string[] TargetAudioCodec { @@ -840,7 +840,7 @@ namespace MediaBrowser.Model.Dlna } /// - /// Predicts the audio channels that will be in the output stream + /// Predicts the audio channels that will be in the output stream. /// public long? TargetSize { diff --git a/MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs b/MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs index 7b02045909..e7fe8d6af2 100644 --- a/MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs +++ b/MediaBrowser.Model/Dlna/SubtitleDeliveryMethod.cs @@ -5,22 +5,22 @@ namespace MediaBrowser.Model.Dlna public enum SubtitleDeliveryMethod { /// - /// The encode + /// The encode. /// Encode = 0, /// - /// The embed + /// The embed. /// Embed = 1, /// - /// The external + /// The external. /// External = 2, /// - /// The HLS + /// The HLS. /// Hls = 3 } diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index c7f8f05844..cad18b5c8b 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -308,7 +308,7 @@ namespace MediaBrowser.Model.Dto public int? LocalTrailerCount { get; set; } /// - /// User data for this item based on the user it's being requested for + /// User data for this item based on the user it's being requested for. /// /// The user data. public UserItemDataDto UserData { get; set; } diff --git a/MediaBrowser.Model/Dto/ImageOptions.cs b/MediaBrowser.Model/Dto/ImageOptions.cs index 158e622a85..3f4405f1e5 100644 --- a/MediaBrowser.Model/Dto/ImageOptions.cs +++ b/MediaBrowser.Model/Dto/ImageOptions.cs @@ -61,7 +61,7 @@ namespace MediaBrowser.Model.Dto /// /// Gets or sets the image tag. - /// If set this will result in strong, unconditional response caching + /// If set this will result in strong, unconditional response caching. /// /// The hash. public string Tag { get; set; } diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index 74c2cb4f41..e78e60d526 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -28,7 +28,7 @@ namespace MediaBrowser.Model.Dto public string Name { get; set; } /// - /// Differentiate internet url vs local network + /// Differentiate internet url vs local network. /// public bool IsRemote { get; set; } diff --git a/MediaBrowser.Model/Entities/DisplayPreferences.cs b/MediaBrowser.Model/Entities/DisplayPreferences.cs index 0e5db01dd5..7e5c5be3b6 100644 --- a/MediaBrowser.Model/Entities/DisplayPreferences.cs +++ b/MediaBrowser.Model/Entities/DisplayPreferences.cs @@ -104,7 +104,7 @@ namespace MediaBrowser.Model.Entities public bool ShowSidebar { get; set; } /// - /// Gets or sets the client + /// Gets or sets the client. /// public string Client { get; set; } } diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index 3db72f78ac..33c14ec1bd 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -13,7 +13,7 @@ using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Model.Entities { /// - /// Class MediaStream + /// Class MediaStream. /// public class MediaStream { diff --git a/MediaBrowser.Model/Entities/MetadataProvider.cs b/MediaBrowser.Model/Entities/MetadataProvider.cs index bcc2b48e70..7fecf67b8e 100644 --- a/MediaBrowser.Model/Entities/MetadataProvider.cs +++ b/MediaBrowser.Model/Entities/MetadataProvider.cs @@ -3,28 +3,28 @@ namespace MediaBrowser.Model.Entities { /// - /// Enum MetadataProviders + /// Enum MetadataProviders. /// public enum MetadataProvider { /// - /// The imdb + /// The imdb. /// Imdb = 2, /// - /// The TMDB + /// The TMDB. /// Tmdb = 3, /// - /// The TVDB + /// The TVDB. /// Tvdb = 4, /// - /// The tvcom + /// The tvcom. /// Tvcom = 5, /// - /// Tmdb Collection Id + /// Tmdb Collection Id. /// TmdbCollection = 7, MusicBrainzAlbum = 8, diff --git a/MediaBrowser.Model/Entities/VirtualFolderInfo.cs b/MediaBrowser.Model/Entities/VirtualFolderInfo.cs index 2de02e403c..662fa1f782 100644 --- a/MediaBrowser.Model/Entities/VirtualFolderInfo.cs +++ b/MediaBrowser.Model/Entities/VirtualFolderInfo.cs @@ -7,7 +7,7 @@ using MediaBrowser.Model.Configuration; namespace MediaBrowser.Model.Entities { /// - /// Used to hold information about a user's list of configured virtual folders + /// Used to hold information about a user's list of configured virtual folders. /// public class VirtualFolderInfo { diff --git a/MediaBrowser.Model/IO/IZipClient.cs b/MediaBrowser.Model/IO/IZipClient.cs index 83e8a018d6..2daa54f227 100644 --- a/MediaBrowser.Model/IO/IZipClient.cs +++ b/MediaBrowser.Model/IO/IZipClient.cs @@ -5,7 +5,7 @@ using System.IO; namespace MediaBrowser.Model.IO { /// - /// Interface IZipClient + /// Interface IZipClient. /// public interface IZipClient { diff --git a/MediaBrowser.Model/LiveTv/ChannelType.cs b/MediaBrowser.Model/LiveTv/ChannelType.cs index b6974cb085..f4c55cb6d0 100644 --- a/MediaBrowser.Model/LiveTv/ChannelType.cs +++ b/MediaBrowser.Model/LiveTv/ChannelType.cs @@ -6,7 +6,7 @@ namespace MediaBrowser.Model.LiveTv public enum ChannelType { /// - /// The TV + /// The TV. /// TV, diff --git a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs index d1a94d8b30..3c5c39a16a 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs @@ -54,7 +54,7 @@ namespace MediaBrowser.Model.LiveTv public int? StartIndex { get; set; } /// - /// The maximum number of items to return + /// The maximum number of items to return. /// /// The limit. public int? Limit { get; set; } @@ -67,13 +67,13 @@ namespace MediaBrowser.Model.LiveTv public bool EnableUserData { get; set; } /// - /// Used to specific whether to return news or not + /// Used to specific whether to return news or not. /// /// If set to null, all programs will be returned public bool? IsNews { get; set; } /// - /// Used to specific whether to return movies or not + /// Used to specific whether to return movies or not. /// /// If set to null, all programs will be returned public bool? IsMovie { get; set; } @@ -93,7 +93,7 @@ namespace MediaBrowser.Model.LiveTv public string[] SortBy { get; set; } /// - /// The sort order to return results with + /// The sort order to return results with. /// /// The sort order. public SortOrder? SortOrder { get; set; } diff --git a/MediaBrowser.Model/LiveTv/RecordingQuery.cs b/MediaBrowser.Model/LiveTv/RecordingQuery.cs index 2649829300..de50adcec3 100644 --- a/MediaBrowser.Model/LiveTv/RecordingQuery.cs +++ b/MediaBrowser.Model/LiveTv/RecordingQuery.cs @@ -37,7 +37,7 @@ namespace MediaBrowser.Model.LiveTv public int? StartIndex { get; set; } /// - /// The maximum number of items to return + /// The maximum number of items to return. /// /// The limit. public int? Limit { get; set; } @@ -61,7 +61,7 @@ namespace MediaBrowser.Model.LiveTv public string SeriesTimerId { get; set; } /// - /// Fields to return within the items, in addition to basic information + /// Fields to return within the items, in addition to basic information. /// /// The fields. public ItemFields[] Fields { get; set; } diff --git a/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs b/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs index bda46dd2bc..b899a464b4 100644 --- a/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs +++ b/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs @@ -7,7 +7,7 @@ namespace MediaBrowser.Model.LiveTv public class SeriesTimerQuery { /// - /// Gets or sets the sort by - SortName, Priority + /// Gets or sets the sort by - SortName, Priority. /// /// The sort by. public string? SortBy { get; set; } diff --git a/MediaBrowser.Model/Net/NetworkShare.cs b/MediaBrowser.Model/Net/NetworkShare.cs index a40cf73e49..6344cbe21e 100644 --- a/MediaBrowser.Model/Net/NetworkShare.cs +++ b/MediaBrowser.Model/Net/NetworkShare.cs @@ -6,27 +6,27 @@ namespace MediaBrowser.Model.Net public class NetworkShare { /// - /// The name of the computer that this share belongs to + /// The name of the computer that this share belongs to. /// public string Server { get; set; } /// - /// Share name + /// Share name. /// public string Name { get; set; } /// - /// Local path + /// Local path. /// public string Path { get; set; } /// - /// Share type + /// Share type. /// public NetworkShareType ShareType { get; set; } /// - /// Comment + /// Comment. /// public string Remark { get; set; } } diff --git a/MediaBrowser.Model/Notifications/NotificationOption.cs b/MediaBrowser.Model/Notifications/NotificationOption.cs index 144949a3b7..ea363d9b17 100644 --- a/MediaBrowser.Model/Notifications/NotificationOption.cs +++ b/MediaBrowser.Model/Notifications/NotificationOption.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Model.Notifications public string Type { get; set; } /// - /// User Ids to not monitor (it's opt out) + /// User Ids to not monitor (it's opt out). /// public string[] DisabledMonitorUsers { get; set; } diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs index d7cc5ebbea..731d22aaf8 100644 --- a/MediaBrowser.Model/Querying/ItemFields.cs +++ b/MediaBrowser.Model/Querying/ItemFields.cs @@ -8,198 +8,198 @@ namespace MediaBrowser.Model.Querying public enum ItemFields { /// - /// The air time + /// The air time. /// AirTime, /// - /// The can delete + /// The can delete. /// CanDelete, /// - /// The can download + /// The can download. /// CanDownload, /// - /// The channel information + /// The channel information. /// ChannelInfo, /// - /// The chapters + /// The chapters. /// Chapters, ChildCount, /// - /// The cumulative run time ticks + /// The cumulative run time ticks. /// CumulativeRunTimeTicks, /// - /// The custom rating + /// The custom rating. /// CustomRating, /// - /// The date created of the item + /// The date created of the item. /// DateCreated, /// - /// The date last media added + /// The date last media added. /// DateLastMediaAdded, /// - /// Item display preferences + /// Item display preferences. /// DisplayPreferencesId, /// - /// The etag + /// The etag. /// Etag, /// - /// The external urls + /// The external urls. /// ExternalUrls, /// - /// Genres + /// Genres. /// Genres, /// - /// The home page URL + /// The home page URL. /// HomePageUrl, /// - /// The item counts + /// The item counts. /// ItemCounts, /// - /// The media source count + /// The media source count. /// MediaSourceCount, /// - /// The media versions + /// The media versions. /// MediaSources, OriginalTitle, /// - /// The item overview + /// The item overview. /// Overview, /// - /// The id of the item's parent + /// The id of the item's parent. /// ParentId, /// - /// The physical path of the item + /// The physical path of the item. /// Path, /// - /// The list of people for the item + /// The list of people for the item. /// People, PlayAccess, /// - /// The production locations + /// The production locations. /// ProductionLocations, /// - /// Imdb, tmdb, etc + /// Imdb, tmdb, etc. /// ProviderIds, /// - /// The aspect ratio of the primary image + /// The aspect ratio of the primary image. /// PrimaryImageAspectRatio, RecursiveItemCount, /// - /// The settings + /// The settings. /// Settings, /// - /// The screenshot image tags + /// The screenshot image tags. /// ScreenshotImageTags, SeriesPrimaryImage, /// - /// The series studio + /// The series studio. /// SeriesStudio, /// - /// The sort name of the item + /// The sort name of the item. /// SortName, /// - /// The special episode numbers + /// The special episode numbers. /// SpecialEpisodeNumbers, /// - /// The studios of the item + /// The studios of the item. /// Studios, BasicSyncInfo, /// - /// The synchronize information + /// The synchronize information. /// SyncInfo, /// - /// The taglines of the item + /// The taglines of the item. /// Taglines, /// - /// The tags + /// The tags. /// Tags, /// - /// The trailer url of the item + /// The trailer url of the item. /// RemoteTrailers, /// - /// The media streams + /// The media streams. /// MediaStreams, /// - /// The season user data + /// The season user data. /// SeasonUserData, /// - /// The service name + /// The service name. /// ServiceName, ThemeSongIds, diff --git a/MediaBrowser.Model/Querying/ItemSortBy.cs b/MediaBrowser.Model/Querying/ItemSortBy.cs index edf71c1a77..0b846bb96c 100644 --- a/MediaBrowser.Model/Querying/ItemSortBy.cs +++ b/MediaBrowser.Model/Querying/ItemSortBy.cs @@ -3,7 +3,7 @@ namespace MediaBrowser.Model.Querying { /// - /// These represent sort orders that are known by the core + /// These represent sort orders that are known by the core. /// public static class ItemSortBy { diff --git a/MediaBrowser.Model/Querying/NextUpQuery.cs b/MediaBrowser.Model/Querying/NextUpQuery.cs index 0df86cb22d..ee13ffc168 100644 --- a/MediaBrowser.Model/Querying/NextUpQuery.cs +++ b/MediaBrowser.Model/Querying/NextUpQuery.cs @@ -33,13 +33,13 @@ namespace MediaBrowser.Model.Querying public int? StartIndex { get; set; } /// - /// The maximum number of items to return + /// The maximum number of items to return. /// /// The limit. public int? Limit { get; set; } /// - /// Fields to return within the items, in addition to basic information + /// Fields to return within the items, in addition to basic information. /// /// The fields. public ItemFields[] Fields { get; set; } diff --git a/MediaBrowser.Model/Querying/QueryResult.cs b/MediaBrowser.Model/Querying/QueryResult.cs index 42586243da..490f48b84b 100644 --- a/MediaBrowser.Model/Querying/QueryResult.cs +++ b/MediaBrowser.Model/Querying/QueryResult.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Model.Querying public IReadOnlyList Items { get; set; } /// - /// The total number of records available + /// The total number of records available. /// /// The total record count. public int TotalRecordCount { get; set; } diff --git a/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs b/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs index ed1aa7ac6d..12d537492a 100644 --- a/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs +++ b/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs @@ -26,13 +26,13 @@ namespace MediaBrowser.Model.Querying public int? StartIndex { get; set; } /// - /// The maximum number of items to return + /// The maximum number of items to return. /// /// The limit. public int? Limit { get; set; } /// - /// Fields to return within the items, in addition to basic information + /// Fields to return within the items, in addition to basic information. /// /// The fields. public ItemFields[] Fields { get; set; } diff --git a/MediaBrowser.Model/Search/SearchQuery.cs b/MediaBrowser.Model/Search/SearchQuery.cs index 4470f1ad97..c58b13c35b 100644 --- a/MediaBrowser.Model/Search/SearchQuery.cs +++ b/MediaBrowser.Model/Search/SearchQuery.cs @@ -8,7 +8,7 @@ namespace MediaBrowser.Model.Search public class SearchQuery { /// - /// The user to localize search results for + /// The user to localize search results for. /// /// The user id. public Guid UserId { get; set; } @@ -26,7 +26,7 @@ namespace MediaBrowser.Model.Search public int? StartIndex { get; set; } /// - /// The maximum number of items to return + /// The maximum number of items to return. /// /// The limit. public int? Limit { get; set; } diff --git a/MediaBrowser.Model/Services/ApiMemberAttribute.cs b/MediaBrowser.Model/Services/ApiMemberAttribute.cs index 7c23eee448..63f3ecd55d 100644 --- a/MediaBrowser.Model/Services/ApiMemberAttribute.cs +++ b/MediaBrowser.Model/Services/ApiMemberAttribute.cs @@ -58,7 +58,7 @@ namespace MediaBrowser.Model.Services public string Route { get; set; } /// - /// Whether to exclude this property from being included in the ModelSchema + /// Whether to exclude this property from being included in the ModelSchema. /// public bool ExcludeInSchema { get; set; } } diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index f413f1e177..d8eddf27cd 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -23,7 +23,7 @@ namespace MediaBrowser.Model.Services string Verb { get; } /// - /// The request ContentType + /// The request ContentType. /// string ContentType { get; } @@ -32,7 +32,7 @@ namespace MediaBrowser.Model.Services string UserAgent { get; } /// - /// The expected Response ContentType for this request + /// The expected Response ContentType for this request. /// string ResponseContentType { get; set; } @@ -55,7 +55,7 @@ namespace MediaBrowser.Model.Services string RemoteIp { get; } /// - /// The value of the Authorization Header used to send the Api Key, null if not available + /// The value of the Authorization Header used to send the Api Key, null if not available. /// string Authorization { get; } @@ -68,7 +68,7 @@ namespace MediaBrowser.Model.Services long ContentLength { get; } /// - /// The value of the Referrer, null if not available + /// The value of the Referrer, null if not available. /// Uri UrlReferrer { get; } } diff --git a/MediaBrowser.Model/Services/IRequiresRequestStream.cs b/MediaBrowser.Model/Services/IRequiresRequestStream.cs index 622626edcc..3e5f2da42e 100644 --- a/MediaBrowser.Model/Services/IRequiresRequestStream.cs +++ b/MediaBrowser.Model/Services/IRequiresRequestStream.cs @@ -7,7 +7,7 @@ namespace MediaBrowser.Model.Services public interface IRequiresRequestStream { /// - /// The raw Http Request Input Stream + /// The raw Http Request Input Stream. /// Stream RequestStream { get; set; } } diff --git a/MediaBrowser.Model/Session/PlayRequest.cs b/MediaBrowser.Model/Session/PlayRequest.cs index 62b68b49ea..66775e7357 100644 --- a/MediaBrowser.Model/Session/PlayRequest.cs +++ b/MediaBrowser.Model/Session/PlayRequest.cs @@ -7,7 +7,7 @@ using MediaBrowser.Model.Services; namespace MediaBrowser.Model.Session { /// - /// Class PlayRequest + /// Class PlayRequest. /// public class PlayRequest { @@ -19,7 +19,7 @@ namespace MediaBrowser.Model.Session public Guid[] ItemIds { get; set; } /// - /// Gets or sets the start position ticks that the first item should be played at + /// Gets or sets the start position ticks that the first item should be played at. /// /// The start position ticks. [ApiMember(Name = "StartPositionTicks", Description = "The starting position of the first item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] diff --git a/MediaBrowser.Model/Sync/SyncCategory.cs b/MediaBrowser.Model/Sync/SyncCategory.cs index 215ac301e0..80ad5f56ec 100644 --- a/MediaBrowser.Model/Sync/SyncCategory.cs +++ b/MediaBrowser.Model/Sync/SyncCategory.cs @@ -5,15 +5,15 @@ namespace MediaBrowser.Model.Sync public enum SyncCategory { /// - /// The latest + /// The latest. /// Latest = 0, /// - /// The next up + /// The next up. /// NextUp = 1, /// - /// The resume + /// The resume. /// Resume = 2 } diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index a67c38c3ac..18ca74ee30 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -23,7 +23,7 @@ namespace MediaBrowser.Model.System }; /// - /// Class SystemInfo + /// Class SystemInfo. /// public class SystemInfo : PublicSystemInfo { diff --git a/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs b/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs index c79d7fe75d..b08acba2c6 100644 --- a/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs +++ b/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.Model.Tasks double? CurrentProgress { get; } /// - /// Gets the triggers that define when the task will run + /// Gets the triggers that define when the task will run. /// /// The triggers. /// value diff --git a/MediaBrowser.Model/Tasks/ITaskManager.cs b/MediaBrowser.Model/Tasks/ITaskManager.cs index 4a7f579ec5..363773ff74 100644 --- a/MediaBrowser.Model/Tasks/ITaskManager.cs +++ b/MediaBrowser.Model/Tasks/ITaskManager.cs @@ -10,7 +10,7 @@ namespace MediaBrowser.Model.Tasks public interface ITaskManager : IDisposable { /// - /// Gets the list of Scheduled Tasks + /// Gets the list of Scheduled Tasks. /// /// The scheduled tasks. IScheduledTaskWorker[] ScheduledTasks { get; } diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 3c94f62150..b16462936a 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -24,19 +24,19 @@ using Season = MediaBrowser.Controller.Entities.TV.Season; namespace MediaBrowser.Providers.Manager { /// - /// Class ImageSaver + /// Class ImageSaver. /// public class ImageSaver { private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); /// - /// The _config + /// The _config. /// private readonly IServerConfigurationManager _config; /// - /// The _directory watchers + /// The _directory watchers. /// private readonly ILibraryMonitor _libraryMonitor; private readonly IFileSystem _fileSystem; diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 7901503d3f..1091c3326b 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -168,7 +168,7 @@ namespace MediaBrowser.Providers.Manager } /// - /// Image types that are only one per item + /// Image types that are only one per item. /// private readonly ImageType[] _singularImages = { @@ -189,7 +189,7 @@ namespace MediaBrowser.Providers.Manager } /// - /// Determines if an item already contains the given images + /// Determines if an item already contains the given images. /// /// The item. /// The images. diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 5853c77144..989a4b5d14 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -38,7 +38,7 @@ using Series = MediaBrowser.Controller.Entities.TV.Series; namespace MediaBrowser.Providers.Manager { /// - /// Class ProviderManager + /// Class ProviderManager. /// public class ProviderManager : IProviderManager, IDisposable { diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs index ba87e05709..8f3df2760b 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs @@ -17,7 +17,7 @@ using MediaBrowser.Model.IO; namespace MediaBrowser.Providers.MediaInfo { /// - /// Uses ffmpeg to create video images + /// Uses ffmpeg to create video images. /// public class AudioImageProvider : IDynamicImageProvider { diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs index 73c89e8153..de377086a3 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs @@ -99,7 +99,7 @@ namespace MediaBrowser.Providers.MediaInfo } /// - /// Fetches data from the tags dictionary + /// Fetches data from the tags dictionary. /// /// The audio. /// The data. diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index ccbe27c1ff..29e6d78548 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -342,7 +342,7 @@ namespace MediaBrowser.Providers.MediaInfo } /// - /// Gets information about the longest playlist on a bdrom + /// Gets information about the longest playlist on a bdrom. /// /// The path. /// VideoStream. diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/MovieResult.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/MovieResult.cs index 51c26a61c5..c0a880bc9f 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/MovieResult.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/MovieResult.cs @@ -53,7 +53,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Search /// The vote_average. public double Vote_Average { get; set; } /// - /// For collection search results + /// For collection search results. /// public string Name { get; set; } /// diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index faeb48b12e..3c0922f39b 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -25,7 +25,7 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Plugins.Tmdb.Movies { /// - /// Class MovieDbProvider + /// Class MovieDbProvider. /// public class TmdbMovieProvider : IRemoteMetadataProvider, IHasOrder { @@ -129,7 +129,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies public string Name => TmdbUtils.ProviderName; /// - /// The _TMDB settings task + /// The _TMDB settings task. /// private TmdbSettingsResult _tmdbSettings; diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs index aabad3ada8..26259f1aa9 100644 --- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs @@ -108,7 +108,7 @@ namespace MediaBrowser.Providers.TV /// /// Returns true if a series has any seasons or episodes without season or episode numbers - /// If this data is missing no virtual items will be added in order to prevent possible duplicates + /// If this data is missing no virtual items will be added in order to prevent possible duplicates. /// private bool HasInvalidContent(IList allItems) { @@ -171,7 +171,7 @@ namespace MediaBrowser.Providers.TV } /// - /// Removes the virtual entry after a corresponding physical version has been added + /// Removes the virtual entry after a corresponding physical version has been added. /// private bool RemoveObsoleteOrMissingEpisodes( IEnumerable allRecursiveChildren, diff --git a/RSSDP/DiscoveredSsdpDevice.cs b/RSSDP/DiscoveredSsdpDevice.cs index 1244ce523d..d7561fc934 100644 --- a/RSSDP/DiscoveredSsdpDevice.cs +++ b/RSSDP/DiscoveredSsdpDevice.cs @@ -55,7 +55,7 @@ namespace Rssdp } /// - /// Returns the headers from the SSDP device response message + /// Returns the headers from the SSDP device response message. /// public HttpHeaders ResponseHeaders { get; set; } -- cgit v1.2.3 From 247f9c61e60ef774675cb4d6d1734d2ccdc6ee7a Mon Sep 17 00:00:00 2001 From: telans Date: Tue, 16 Jun 2020 09:43:52 +1200 Subject: fix SA1513/SA1516 --- DvdLib/Ifo/Cell.cs | 1 + DvdLib/Ifo/Chapter.cs | 2 + DvdLib/Ifo/Dvd.cs | 1 + DvdLib/Ifo/ProgramChain.cs | 4 + DvdLib/Ifo/Title.cs | 4 + Emby.Dlna/ContentDirectory/ControlHandler.cs | 1 + Emby.Dlna/Didl/DidlBuilder.cs | 3 + Emby.Dlna/DlnaManager.cs | 3 + Emby.Dlna/Eventing/EventManager.cs | 1 + Emby.Dlna/Eventing/EventSubscription.cs | 3 + Emby.Dlna/Main/DlnaEntryPoint.cs | 2 + Emby.Dlna/PlayTo/Device.cs | 2 + Emby.Dlna/PlayTo/PlayToController.cs | 5 + Emby.Dlna/PlayTo/PlayToManager.cs | 1 + Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs | 1 + Emby.Dlna/PlayTo/uBaseObject.cs | 2 + Emby.Dlna/Server/DescriptionXmlBuilder.cs | 5 + Emby.Dlna/Service/BaseControlHandler.cs | 4 + Emby.Dlna/Service/ServiceXmlBuilder.cs | 1 + Emby.Naming/AudioBook/AudioBookFilePathParser.cs | 1 + .../Data/SqliteItemRepository.cs | 115 ++++++++++++++++++++ .../Data/SqliteUserDataRepository.cs | 3 + .../Devices/DeviceManager.cs | 1 + Emby.Server.Implementations/Dto/DtoService.cs | 11 ++ .../HttpServer/HttpListenerHost.cs | 1 + .../HttpServer/RangeRequestWriter.cs | 7 ++ .../HttpServer/Security/AuthService.cs | 1 + .../HttpServer/Security/AuthorizationContext.cs | 2 + .../IO/ManagedFileSystem.cs | 2 + .../Library/ExclusiveLiveStream.cs | 2 + .../Library/LibraryManager.cs | 2 + .../Library/MediaSourceManager.cs | 5 + .../Library/Resolvers/SpecialFolderResolver.cs | 2 + .../Library/Resolvers/TV/EpisodeResolver.cs | 1 + .../Library/SearchEngine.cs | 1 + .../LiveTv/Listings/SchedulesDirect.cs | 118 +++++++++++++++++++++ .../LiveTv/Listings/XmlTvListingsProvider.cs | 1 + .../LiveTv/LiveTvManager.cs | 12 +++ .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 19 ++++ .../LiveTv/TunerHosts/LiveStream.cs | 3 + .../Playlists/PlaylistManager.cs | 1 + .../ScheduledTasks/ScheduledTaskWorker.cs | 5 + .../Services/ServiceMethod.cs | 1 + .../Services/ServicePath.cs | 3 + .../Services/StringMapTypeDeserializer.cs | 2 + .../Services/SwaggerService.cs | 34 ++++++ .../Session/SessionWebSocketListener.cs | 1 + .../Sorting/PremiereDateComparer.cs | 1 + Emby.Server.Implementations/TV/TVSeriesManager.cs | 1 + Jellyfin.Data/Entities/Artwork.cs | 3 + Jellyfin.Data/Entities/BookMetadata.cs | 1 + Jellyfin.Data/Entities/Chapter.cs | 5 + Jellyfin.Data/Entities/Collection.cs | 2 + Jellyfin.Data/Entities/CollectionItem.cs | 1 + Jellyfin.Data/Entities/Company.cs | 1 + Jellyfin.Data/Entities/CompanyMetadata.cs | 4 + Jellyfin.Data/Entities/Episode.cs | 1 + Jellyfin.Data/Entities/EpisodeMetadata.cs | 3 + Jellyfin.Data/Entities/Genre.cs | 2 + Jellyfin.Data/Entities/Library.cs | 2 + Jellyfin.Data/Entities/LibraryItem.cs | 3 + Jellyfin.Data/Entities/LibraryRoot.cs | 3 + Jellyfin.Data/Entities/MediaFile.cs | 3 + Jellyfin.Data/Entities/MediaFileStream.cs | 2 + Jellyfin.Data/Entities/Metadata.cs | 8 ++ Jellyfin.Data/Entities/MetadataProvider.cs | 2 + Jellyfin.Data/Entities/MetadataProviderId.cs | 2 + Jellyfin.Data/Entities/MovieMetadata.cs | 4 + Jellyfin.Data/Entities/MusicAlbumMetadata.cs | 3 + Jellyfin.Data/Entities/Person.cs | 6 ++ Jellyfin.Data/Entities/PersonRole.cs | 3 + Jellyfin.Data/Entities/Rating.cs | 3 + Jellyfin.Data/Entities/RatingSource.cs | 4 + Jellyfin.Data/Entities/Release.cs | 2 + Jellyfin.Data/Entities/Season.cs | 1 + Jellyfin.Data/Entities/SeasonMetadata.cs | 1 + Jellyfin.Data/Entities/Series.cs | 3 + Jellyfin.Data/Entities/SeriesMetadata.cs | 4 + Jellyfin.Data/Entities/Track.cs | 1 + Jellyfin.Server.Implementations/JellyfinDb.cs | 39 +++++++ MediaBrowser.Api/EnvironmentService.cs | 1 + MediaBrowser.Api/FilterService.cs | 6 ++ MediaBrowser.Api/IHasDtoOptions.cs | 1 + MediaBrowser.Api/Library/LibraryService.cs | 11 ++ MediaBrowser.Api/LiveTv/LiveTvService.cs | 15 +++ MediaBrowser.Api/Playback/BaseStreamingService.cs | 4 + MediaBrowser.Api/Playback/Hls/BaseHlsService.cs | 1 + MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 2 + MediaBrowser.Api/Playback/MediaInfoService.cs | 2 + .../Progressive/BaseProgressiveStreamingService.cs | 6 ++ .../Progressive/ProgressiveStreamWriter.cs | 2 + MediaBrowser.Api/Playback/StreamRequest.cs | 4 + MediaBrowser.Api/Playback/UniversalAudioService.cs | 20 ++++ MediaBrowser.Api/PluginService.cs | 9 ++ MediaBrowser.Api/SimilarItemsHelper.cs | 4 + MediaBrowser.Api/Subtitles/SubtitleService.cs | 2 + MediaBrowser.Api/SuggestionsService.cs | 5 + MediaBrowser.Api/TranscodingJob.cs | 8 ++ MediaBrowser.Api/TvShowsService.cs | 1 + .../UserLibrary/BaseItemsByNameService.cs | 1 + MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs | 3 + MediaBrowser.Api/UserLibrary/UserViewsService.cs | 3 + .../Authentication/IAuthenticationProvider.cs | 3 + .../Authentication/IPasswordResetProvider.cs | 3 + .../Channels/ChannelItemInfo.cs | 11 ++ .../Collections/CollectionCreationOptions.cs | 1 + MediaBrowser.Controller/Drawing/ImageHelper.cs | 1 + .../Drawing/ImageProcessingOptions.cs | 7 ++ MediaBrowser.Controller/Dto/DtoOptions.cs | 6 ++ .../Entities/AggregateFolder.cs | 1 + MediaBrowser.Controller/Entities/Audio/Audio.cs | 2 + .../Entities/Audio/MusicAlbum.cs | 1 + .../Entities/Audio/MusicArtist.cs | 1 + .../Entities/Audio/MusicGenre.cs | 2 + MediaBrowser.Controller/Entities/AudioBook.cs | 2 + MediaBrowser.Controller/Entities/BaseItem.cs | 16 +++ .../Entities/CollectionFolder.cs | 3 + MediaBrowser.Controller/Entities/Folder.cs | 21 ++++ MediaBrowser.Controller/Entities/Genre.cs | 2 + .../Entities/IHasMediaSources.cs | 2 + .../Entities/IHasProgramAttributes.cs | 8 ++ MediaBrowser.Controller/Entities/IHasSeries.cs | 3 + .../Entities/InternalItemsQuery.cs | 103 ++++++++++++++++++ MediaBrowser.Controller/Entities/LinkedChild.cs | 3 + MediaBrowser.Controller/Entities/PeopleHelper.cs | 1 + MediaBrowser.Controller/Entities/Person.cs | 2 + MediaBrowser.Controller/Entities/Photo.cs | 11 ++ MediaBrowser.Controller/Entities/Share.cs | 1 + MediaBrowser.Controller/Entities/Studio.cs | 2 + MediaBrowser.Controller/Entities/TV/Episode.cs | 6 ++ MediaBrowser.Controller/Entities/TV/Season.cs | 1 + MediaBrowser.Controller/Entities/TV/Series.cs | 2 + MediaBrowser.Controller/Entities/UserItemData.cs | 1 + .../Entities/UserViewBuilder.cs | 2 + MediaBrowser.Controller/Entities/Video.cs | 10 ++ MediaBrowser.Controller/Entities/Year.cs | 1 + MediaBrowser.Controller/IO/FileData.cs | 3 + MediaBrowser.Controller/Library/DeleteOptions.cs | 1 + MediaBrowser.Controller/Library/ILiveStream.cs | 5 + MediaBrowser.Controller/Library/ItemResolveArgs.cs | 2 + .../Library/PlaybackProgressEventArgs.cs | 9 ++ MediaBrowser.Controller/Library/Profiler.cs | 1 + MediaBrowser.Controller/Library/TVUtils.cs | 1 + MediaBrowser.Controller/LiveTv/ChannelInfo.cs | 3 + MediaBrowser.Controller/LiveTv/ILiveTvManager.cs | 3 + MediaBrowser.Controller/LiveTv/ITunerHost.cs | 1 + .../LiveTv/LiveTvConflictException.cs | 1 + MediaBrowser.Controller/LiveTv/ProgramInfo.cs | 1 + MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs | 1 + MediaBrowser.Controller/LiveTv/TimerInfo.cs | 7 ++ .../LiveTv/TunerChannelMapping.cs | 3 + .../MediaEncoding/EncodingHelper.cs | 28 +++++ .../MediaEncoding/EncodingJobInfo.cs | 8 ++ .../MediaEncoding/EncodingJobOptions.cs | 10 ++ .../MediaEncoding/MediaInfoRequest.cs | 4 + .../Net/AuthenticatedAttribute.cs | 3 + .../Net/BasePeriodicWebSocketListener.cs | 2 + MediaBrowser.Controller/Net/StaticResultOptions.cs | 5 + .../Providers/ImageRefreshOptions.cs | 2 + .../Providers/MetadataResult.cs | 1 + MediaBrowser.Controller/Resolvers/IItemResolver.cs | 1 + .../Security/AuthenticationInfo.cs | 1 + .../Session/AuthenticationRequest.cs | 8 ++ MediaBrowser.Controller/Session/SessionInfo.cs | 5 + .../Subtitles/SubtitleResponse.cs | 3 + .../Subtitles/SubtitleSearchRequest.cs | 12 +++ MediaBrowser.Controller/Sync/SyncedFileInfo.cs | 1 + MediaBrowser.Controller/SyncPlay/GroupInfo.cs | 1 + .../Images/EpisodeLocalImageProvider.cs | 1 + .../Parsers/BaseItemXmlParser.cs | 48 +++++++++ .../Parsers/BoxSetXmlParser.cs | 2 + .../Parsers/PlaylistXmlParser.cs | 2 + MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs | 7 ++ MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 1 + .../Probing/ProbeResultNormalizer.cs | 13 +++ MediaBrowser.MediaEncoding/Subtitles/AssParser.cs | 4 + MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs | 5 + MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs | 4 + .../Subtitles/SubtitleEncoder.cs | 9 ++ MediaBrowser.Model/Configuration/LibraryOptions.cs | 23 ++++ .../Configuration/UserConfiguration.cs | 4 + .../Configuration/XbmcMetadataOptions.cs | 1 + MediaBrowser.Model/Dlna/AudioOptions.cs | 1 + MediaBrowser.Model/Dlna/DeviceProfile.cs | 21 ++++ .../Dlna/MediaFormatProfileResolver.cs | 13 +++ MediaBrowser.Model/Dlna/StreamBuilder.cs | 21 ++++ MediaBrowser.Model/Dlna/StreamInfo.cs | 20 ++++ MediaBrowser.Model/Dto/BaseItemDto.cs | 25 +++++ MediaBrowser.Model/Dto/MediaSourceInfo.cs | 21 ++++ MediaBrowser.Model/Dto/MetadataEditorInfo.cs | 4 + MediaBrowser.Model/Dto/NameIdPair.cs | 1 + MediaBrowser.Model/Entities/MediaStream.cs | 13 +++ MediaBrowser.Model/Entities/MediaUrl.cs | 1 + MediaBrowser.Model/Entities/VirtualFolderInfo.cs | 1 + MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs | 1 + MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs | 2 + MediaBrowser.Model/LiveTv/LiveTvOptions.cs | 34 ++++++ MediaBrowser.Model/LiveTv/RecordingQuery.cs | 9 ++ MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs | 11 ++ MediaBrowser.Model/MediaInfo/MediaInfo.cs | 8 ++ .../MediaInfo/PlaybackInfoRequest.cs | 6 ++ MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs | 10 ++ MediaBrowser.Model/Providers/SubtitleOptions.cs | 6 ++ .../Providers/SubtitleProviderInfo.cs | 1 + MediaBrowser.Model/Querying/QueryFilters.cs | 5 + MediaBrowser.Model/Search/SearchHint.cs | 1 + MediaBrowser.Model/Search/SearchQuery.cs | 7 ++ MediaBrowser.Model/Services/IRequest.cs | 4 + MediaBrowser.Model/Services/IService.cs | 2 + MediaBrowser.Model/Session/ClientCapabilities.cs | 4 + MediaBrowser.Model/Session/PlayRequest.cs | 3 + MediaBrowser.Model/Session/PlaybackProgressInfo.cs | 2 + MediaBrowser.Model/Session/PlaybackStopInfo.cs | 1 + MediaBrowser.Model/Session/TranscodingInfo.cs | 8 ++ MediaBrowser.Model/Sync/SyncJob.cs | 2 + MediaBrowser.Model/Users/UserAction.cs | 6 ++ MediaBrowser.Model/Users/UserPolicy.cs | 20 ++++ MediaBrowser.Providers/Manager/ImageSaver.cs | 5 + .../Manager/ItemImageProvider.cs | 3 + MediaBrowser.Providers/Manager/MetadataService.cs | 7 ++ MediaBrowser.Providers/Manager/ProviderManager.cs | 1 + MediaBrowser.Providers/Manager/ProviderUtils.cs | 1 + .../MediaInfo/AudioImageProvider.cs | 1 + .../MediaInfo/FFProbeVideoInfo.cs | 3 + .../MediaInfo/VideoImageProvider.cs | 2 + .../Movies/MovieMetadataService.cs | 2 + .../Movies/TrailerMetadataService.cs | 2 + .../Playlists/PlaylistItemsProvider.cs | 4 + .../Plugins/AudioDb/AlbumProvider.cs | 37 +++++++ .../Plugins/AudioDb/ArtistProvider.cs | 40 +++++++ .../Plugins/MusicBrainz/AlbumProvider.cs | 13 +++ .../Plugins/MusicBrainz/ArtistProvider.cs | 6 ++ .../Plugins/Omdb/OmdbItemProvider.cs | 22 ++++ .../Tmdb/Models/Collections/CollectionImages.cs | 1 + .../Tmdb/Models/Collections/CollectionResult.cs | 6 ++ .../Plugins/Tmdb/Models/Collections/Part.cs | 4 + .../Plugins/Tmdb/Models/General/Backdrop.cs | 6 ++ .../Plugins/Tmdb/Models/General/Crew.cs | 5 + .../Plugins/Tmdb/Models/General/ExternalIds.cs | 4 + .../Plugins/Tmdb/Models/General/Genre.cs | 1 + .../Plugins/Tmdb/Models/General/Images.cs | 1 + .../Plugins/Tmdb/Models/General/Keyword.cs | 1 + .../Plugins/Tmdb/Models/General/Poster.cs | 6 ++ .../Plugins/Tmdb/Models/General/Profile.cs | 4 + .../Plugins/Tmdb/Models/General/Still.cs | 7 ++ .../Plugins/Tmdb/Models/General/Video.cs | 7 ++ .../Tmdb/Models/Movies/BelongsToCollection.cs | 3 + .../Plugins/Tmdb/Models/Movies/Cast.cs | 5 + .../Plugins/Tmdb/Models/Movies/Casts.cs | 1 + .../Plugins/Tmdb/Models/Movies/Country.cs | 2 + .../Plugins/Tmdb/Models/Movies/MovieResult.cs | 29 +++++ .../Tmdb/Models/Movies/ProductionCompany.cs | 1 + .../Tmdb/Models/Movies/ProductionCountry.cs | 1 + .../Plugins/Tmdb/Models/Movies/SpokenLanguage.cs | 1 + .../Plugins/Tmdb/Models/Movies/Youtube.cs | 2 + .../Plugins/Tmdb/Models/People/PersonResult.cs | 13 +++ .../Plugins/Tmdb/Models/Search/TvResult.cs | 8 ++ .../Plugins/Tmdb/Models/TV/Cast.cs | 5 + .../Plugins/Tmdb/Models/TV/ContentRating.cs | 1 + .../Plugins/Tmdb/Models/TV/CreatedBy.cs | 2 + .../Plugins/Tmdb/Models/TV/Credits.cs | 1 + .../Plugins/Tmdb/Models/TV/Episode.cs | 7 ++ .../Plugins/Tmdb/Models/TV/EpisodeCredits.cs | 2 + .../Plugins/Tmdb/Models/TV/EpisodeResult.cs | 13 +++ .../Plugins/Tmdb/Models/TV/GuestStar.cs | 5 + .../Plugins/Tmdb/Models/TV/Network.cs | 1 + .../Plugins/Tmdb/Models/TV/Season.cs | 4 + .../Plugins/Tmdb/Models/TV/SeasonResult.cs | 10 ++ .../Plugins/Tmdb/Models/TV/SeriesResult.cs | 29 +++++ .../Plugins/Tmdb/Movies/TmdbImageProvider.cs | 3 + .../Plugins/Tmdb/Movies/TmdbSettings.cs | 3 + .../Plugins/Tmdb/People/TmdbPersonImageProvider.cs | 3 + .../Plugins/Tmdb/People/TmdbPersonProvider.cs | 1 + .../Subtitles/SubtitleManager.cs | 1 + MediaBrowser.Providers/TV/DummySeasonProvider.cs | 1 + MediaBrowser.Providers/TV/SeriesMetadataService.cs | 2 + RSSDP/DiscoveredSsdpDevice.cs | 1 + RSSDP/HttpParserBase.cs | 1 + RSSDP/SsdpCommunicationsServer.cs | 1 + RSSDP/SsdpDevice.cs | 4 + RSSDP/SsdpDeviceLocator.cs | 1 + RSSDP/SsdpDevicePublisher.cs | 3 + RSSDP/SsdpEmbeddedDevice.cs | 1 + 283 files changed, 1810 insertions(+) (limited to 'Emby.Server.Implementations/Session') diff --git a/DvdLib/Ifo/Cell.cs b/DvdLib/Ifo/Cell.cs index 2eab400f7d..ea0b50e430 100644 --- a/DvdLib/Ifo/Cell.cs +++ b/DvdLib/Ifo/Cell.cs @@ -7,6 +7,7 @@ namespace DvdLib.Ifo public class Cell { public CellPlaybackInfo PlaybackInfo { get; private set; } + public CellPositionInfo PositionInfo { get; private set; } internal void ParsePlayback(BinaryReader br) diff --git a/DvdLib/Ifo/Chapter.cs b/DvdLib/Ifo/Chapter.cs index 1e69429f82..e786cb5536 100644 --- a/DvdLib/Ifo/Chapter.cs +++ b/DvdLib/Ifo/Chapter.cs @@ -5,7 +5,9 @@ namespace DvdLib.Ifo public class Chapter { public ushort ProgramChainNumber { get; private set; } + public ushort ProgramNumber { get; private set; } + public uint ChapterNumber { get; private set; } public Chapter(ushort pgcNum, ushort programNum, uint chapterNum) diff --git a/DvdLib/Ifo/Dvd.cs b/DvdLib/Ifo/Dvd.cs index ca20baa73f..1252bab503 100644 --- a/DvdLib/Ifo/Dvd.cs +++ b/DvdLib/Ifo/Dvd.cs @@ -125,6 +125,7 @@ namespace DvdLib.Ifo if (titleNum + 1 < numTitles && vtsFs.Position == (baseAddr + offsets[titleNum + 1])) break; chapNum++; } + while (vtsFs.Position < (baseAddr + endaddr)); } diff --git a/DvdLib/Ifo/ProgramChain.cs b/DvdLib/Ifo/ProgramChain.cs index 4860360afd..8048f4bbd6 100644 --- a/DvdLib/Ifo/ProgramChain.cs +++ b/DvdLib/Ifo/ProgramChain.cs @@ -22,7 +22,9 @@ namespace DvdLib.Ifo public readonly List Cells; public DvdTime PlaybackTime { get; private set; } + public UserOperation ProhibitedUserOperations { get; private set; } + public byte[] AudioStreamControl { get; private set; } // 8*2 entries public byte[] SubpictureStreamControl { get; private set; } // 32*4 entries @@ -33,9 +35,11 @@ namespace DvdLib.Ifo private ushort _goupProgramNumber; public ProgramPlaybackMode PlaybackMode { get; private set; } + public uint ProgramCount { get; private set; } public byte StillTime { get; private set; } + public byte[] Palette { get; private set; } // 16*4 entries private ushort _commandTableOffset; diff --git a/DvdLib/Ifo/Title.cs b/DvdLib/Ifo/Title.cs index abf806d2c0..4af3af754a 100644 --- a/DvdLib/Ifo/Title.cs +++ b/DvdLib/Ifo/Title.cs @@ -8,8 +8,11 @@ namespace DvdLib.Ifo public class Title { public uint TitleNumber { get; private set; } + public uint AngleCount { get; private set; } + public ushort ChapterCount { get; private set; } + public byte VideoTitleSetNumber { get; private set; } private ushort _parentalManagementMask; @@ -17,6 +20,7 @@ namespace DvdLib.Ifo private uint _vtsStartSector; // relative to start of entire disk public ProgramChain EntryProgramChain { get; private set; } + public readonly List ProgramChains; public readonly List Chapters; diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index de6b619ba5..291de5245b 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -1357,6 +1357,7 @@ namespace Emby.Dlna.ContentDirectory internal class ServerItem { public BaseItem Item { get; set; } + public StubType? StubType { get; set; } public ServerItem(BaseItem item) diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index 6ded76f7dd..aa7a118157 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -765,6 +765,7 @@ namespace Emby.Dlna.Didl { AddValue(writer, "dc", "rating", item.OfficialRating, NS_DC); } + if (filter.Contains("upnp:rating")) { AddValue(writer, "upnp", "rating", item.OfficialRating, NS_UPNP); @@ -1052,10 +1053,12 @@ namespace Emby.Dlna.Didl { return GetImageInfo(item, ImageType.Primary); } + if (item.HasImage(ImageType.Thumb)) { return GetImageInfo(item, ImageType.Thumb); } + if (item.HasImage(ImageType.Backdrop)) { if (item is Channel) diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index e5f4839508..ef8df854bb 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -438,6 +438,7 @@ namespace Emby.Dlna { throw new ArgumentException("Profile is missing Id"); } + if (string.IsNullOrEmpty(profile.Name)) { throw new ArgumentException("Profile is missing Name"); @@ -463,6 +464,7 @@ namespace Emby.Dlna { _profiles[path] = new Tuple(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile); } + SerializeToXml(profile, path); } @@ -492,6 +494,7 @@ namespace Emby.Dlna class InternalProfileInfo { internal DeviceProfileInfo Info { get; set; } + internal string Path { get; set; } } diff --git a/Emby.Dlna/Eventing/EventManager.cs b/Emby.Dlna/Eventing/EventManager.cs index 5a51569e21..edccfd1909 100644 --- a/Emby.Dlna/Eventing/EventManager.cs +++ b/Emby.Dlna/Eventing/EventManager.cs @@ -150,6 +150,7 @@ namespace Emby.Dlna.Eventing builder.Append(""); builder.Append(""); } + builder.Append(""); var options = new HttpRequestOptions diff --git a/Emby.Dlna/Eventing/EventSubscription.cs b/Emby.Dlna/Eventing/EventSubscription.cs index 51eaee9d77..40d73ee0e5 100644 --- a/Emby.Dlna/Eventing/EventSubscription.cs +++ b/Emby.Dlna/Eventing/EventSubscription.cs @@ -7,10 +7,13 @@ namespace Emby.Dlna.Eventing public class EventSubscription { public string Id { get; set; } + public string CallbackUrl { get; set; } + public string NotificationType { get; set; } public DateTime SubscriptionTime { get; set; } + public int TimeoutSeconds { get; set; } public long TriggerCount { get; set; } diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index a7b1d384d2..b965a09b92 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -320,6 +320,7 @@ namespace Emby.Dlna.Main { guid = text.GetMD5(); } + return guid.ToString("N", CultureInfo.InvariantCulture); } @@ -388,6 +389,7 @@ namespace Emby.Dlna.Main { _logger.LogError(ex, "Error disposing PlayTo manager"); } + _manager = null; } } diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index 86b72e264d..12757a1233 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -37,6 +37,7 @@ namespace Emby.Dlna.PlayTo RefreshVolumeIfNeeded().GetAwaiter().GetResult(); return _volume; } + set => _volume = value; } @@ -494,6 +495,7 @@ namespace Emby.Dlna.PlayTo return; } } + RestartTimerInactive(); } } diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index f1c69196a1..92a93d4349 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -425,6 +425,7 @@ namespace Emby.Dlna.PlayTo await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false); return; } + await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false); } } @@ -713,6 +714,7 @@ namespace Emby.Dlna.PlayTo throw new ArgumentException("Volume argument cannot be null"); } + default: return Task.CompletedTask; } @@ -798,12 +800,15 @@ namespace Emby.Dlna.PlayTo public int? SubtitleStreamIndex { get; set; } public string DeviceProfileId { get; set; } + public string DeviceId { get; set; } public string MediaSourceId { get; set; } + public string LiveStreamId { get; set; } public BaseItem Item { get; set; } + private MediaSourceInfo MediaSource; private IMediaSourceManager _mediaSourceManager; diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index c0500eb68b..240c8a7d98 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -132,6 +132,7 @@ namespace Emby.Dlna.PlayTo usn = usn.Substring(index); found = true; } + index = usn.IndexOf("::", StringComparison.OrdinalIgnoreCase); if (index != -1) { diff --git a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs index 3b169e5993..fa42b80e8b 100644 --- a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs +++ b/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs @@ -12,6 +12,7 @@ namespace Emby.Dlna.PlayTo public class MediaChangedEventArgs : EventArgs { public uBaseObject OldMediaInfo { get; set; } + public uBaseObject NewMediaInfo { get; set; } } } diff --git a/Emby.Dlna/PlayTo/uBaseObject.cs b/Emby.Dlna/PlayTo/uBaseObject.cs index a8ed5692c9..05c19299f1 100644 --- a/Emby.Dlna/PlayTo/uBaseObject.cs +++ b/Emby.Dlna/PlayTo/uBaseObject.cs @@ -44,10 +44,12 @@ namespace Emby.Dlna.PlayTo { return MediaBrowser.Model.Entities.MediaType.Audio; } + if (classType.IndexOf(MediaBrowser.Model.Entities.MediaType.Video, StringComparison.Ordinal) != -1) { return MediaBrowser.Model.Entities.MediaType.Video; } + if (classType.IndexOf("image", StringComparison.Ordinal) != -1) { return MediaBrowser.Model.Entities.MediaType.Photo; diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs index 5ecc81a2f1..7143c31094 100644 --- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs +++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs @@ -134,6 +134,7 @@ namespace Emby.Dlna.Server return result; } } + return c.ToString(CultureInfo.InvariantCulture); } @@ -157,18 +158,22 @@ namespace Emby.Dlna.Server { break; } + if (stringBuilder == null) { stringBuilder = new StringBuilder(); } + stringBuilder.Append(str, num, num2 - num); stringBuilder.Append(GetEscapeSequence(str[num2])); num = num2 + 1; } + if (stringBuilder == null) { return str; } + stringBuilder.Append(str, num, length - num); return stringBuilder.ToString(); } diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index 161a3434c5..699d325eac 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -18,6 +18,7 @@ namespace Emby.Dlna.Service private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/"; protected IServerConfigurationManager Config { get; } + protected ILogger Logger { get; } protected BaseControlHandler(IServerConfigurationManager config, ILogger logger) @@ -135,6 +136,7 @@ namespace Emby.Dlna.Service break; } + default: { await reader.SkipAsync().ConfigureAwait(false); @@ -211,7 +213,9 @@ namespace Emby.Dlna.Service private class ControlRequestInfo { public string LocalName { get; set; } + public string NamespaceURI { get; set; } + public Dictionary Headers { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); } diff --git a/Emby.Dlna/Service/ServiceXmlBuilder.cs b/Emby.Dlna/Service/ServiceXmlBuilder.cs index 62ffd9e42a..af557aa144 100644 --- a/Emby.Dlna/Service/ServiceXmlBuilder.cs +++ b/Emby.Dlna/Service/ServiceXmlBuilder.cs @@ -80,6 +80,7 @@ namespace Emby.Dlna.Service { builder.Append("" + DescriptionXmlBuilder.Escape(allowedValue) + ""); } + builder.Append(""); } diff --git a/Emby.Naming/AudioBook/AudioBookFilePathParser.cs b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs index 5494df9d63..3c874c62ca 100644 --- a/Emby.Naming/AudioBook/AudioBookFilePathParser.cs +++ b/Emby.Naming/AudioBook/AudioBookFilePathParser.cs @@ -64,6 +64,7 @@ namespace Emby.Naming.AudioBook { result.ChapterNumber = int.Parse(matches[0].Groups[0].Value); } + if (matches.Count > 1) { result.PartNumber = int.Parse(matches[matches.Count - 1].Groups[0].Value); diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index d331256611..f09ecaa297 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -793,6 +793,7 @@ namespace Emby.Server.Implementations.Data { saveItemStatement.TryBindNull("@Width"); } + if (item.Height > 0) { saveItemStatement.TryBind("@Height", item.Height); @@ -932,6 +933,7 @@ namespace Emby.Server.Implementations.Data { saveItemStatement.TryBindNull("@SeriesName"); } + if (string.IsNullOrWhiteSpace(userDataKey)) { saveItemStatement.TryBindNull("@UserDataKey"); @@ -1007,6 +1009,7 @@ namespace Emby.Server.Implementations.Data { artists = string.Join("|", hasArtists.Artists); } + saveItemStatement.TryBind("@Artists", artists); string albumArtists = null; @@ -1106,6 +1109,7 @@ namespace Emby.Server.Implementations.Data { continue; } + str.Append(ToValueString(i) + "|"); } @@ -1366,6 +1370,7 @@ namespace Emby.Server.Implementations.Data hasStartDate.StartDate = reader[index].ReadDateTime(); } } + index++; } @@ -1373,12 +1378,14 @@ namespace Emby.Server.Implementations.Data { item.EndDate = reader[index].TryReadDateTime(); } + index++; if (!reader.IsDBNull(index)) { item.ChannelId = new Guid(reader.GetString(index)); } + index++; if (enableProgramAttributes) @@ -1389,24 +1396,28 @@ namespace Emby.Server.Implementations.Data { hasProgramAttributes.IsMovie = reader.GetBoolean(index); } + index++; if (!reader.IsDBNull(index)) { hasProgramAttributes.IsSeries = reader.GetBoolean(index); } + index++; if (!reader.IsDBNull(index)) { hasProgramAttributes.EpisodeTitle = reader.GetString(index); } + index++; if (!reader.IsDBNull(index)) { hasProgramAttributes.IsRepeat = reader.GetBoolean(index); } + index++; } else @@ -1419,6 +1430,7 @@ namespace Emby.Server.Implementations.Data { item.CommunityRating = reader.GetFloat(index); } + index++; if (HasField(query, ItemFields.CustomRating)) @@ -1427,6 +1439,7 @@ namespace Emby.Server.Implementations.Data { item.CustomRating = reader.GetString(index); } + index++; } @@ -1434,6 +1447,7 @@ namespace Emby.Server.Implementations.Data { item.IndexNumber = reader.GetInt32(index); } + index++; if (HasField(query, ItemFields.Settings)) @@ -1442,18 +1456,21 @@ namespace Emby.Server.Implementations.Data { item.IsLocked = reader.GetBoolean(index); } + index++; if (!reader.IsDBNull(index)) { item.PreferredMetadataLanguage = reader.GetString(index); } + index++; if (!reader.IsDBNull(index)) { item.PreferredMetadataCountryCode = reader.GetString(index); } + index++; } @@ -1463,6 +1480,7 @@ namespace Emby.Server.Implementations.Data { item.Width = reader.GetInt32(index); } + index++; } @@ -1472,6 +1490,7 @@ namespace Emby.Server.Implementations.Data { item.Height = reader.GetInt32(index); } + index++; } @@ -1481,6 +1500,7 @@ namespace Emby.Server.Implementations.Data { item.DateLastRefreshed = reader[index].ReadDateTime(); } + index++; } @@ -1488,18 +1508,21 @@ namespace Emby.Server.Implementations.Data { item.Name = reader.GetString(index); } + index++; if (!reader.IsDBNull(index)) { item.Path = RestorePath(reader.GetString(index)); } + index++; if (!reader.IsDBNull(index)) { item.PremiereDate = reader[index].TryReadDateTime(); } + index++; if (HasField(query, ItemFields.Overview)) @@ -1508,6 +1531,7 @@ namespace Emby.Server.Implementations.Data { item.Overview = reader.GetString(index); } + index++; } @@ -1515,18 +1539,21 @@ namespace Emby.Server.Implementations.Data { item.ParentIndexNumber = reader.GetInt32(index); } + index++; if (!reader.IsDBNull(index)) { item.ProductionYear = reader.GetInt32(index); } + index++; if (!reader.IsDBNull(index)) { item.OfficialRating = reader.GetString(index); } + index++; if (HasField(query, ItemFields.SortName)) @@ -1535,6 +1562,7 @@ namespace Emby.Server.Implementations.Data { item.ForcedSortName = reader.GetString(index); } + index++; } @@ -1542,12 +1570,14 @@ namespace Emby.Server.Implementations.Data { item.RunTimeTicks = reader.GetInt64(index); } + index++; if (!reader.IsDBNull(index)) { item.Size = reader.GetInt64(index); } + index++; if (HasField(query, ItemFields.DateCreated)) @@ -1556,6 +1586,7 @@ namespace Emby.Server.Implementations.Data { item.DateCreated = reader[index].ReadDateTime(); } + index++; } @@ -1563,6 +1594,7 @@ namespace Emby.Server.Implementations.Data { item.DateModified = reader[index].ReadDateTime(); } + index++; item.Id = reader.GetGuid(index); @@ -1574,6 +1606,7 @@ namespace Emby.Server.Implementations.Data { item.Genres = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); } + index++; } @@ -1581,6 +1614,7 @@ namespace Emby.Server.Implementations.Data { item.ParentId = reader.GetGuid(index); } + index++; if (!reader.IsDBNull(index)) @@ -1590,6 +1624,7 @@ namespace Emby.Server.Implementations.Data item.Audio = audio; } } + index++; // TODO: Even if not needed by apps, the server needs it internally @@ -1603,6 +1638,7 @@ namespace Emby.Server.Implementations.Data liveTvChannel.ServiceName = reader.GetString(index); } } + index++; } @@ -1610,6 +1646,7 @@ namespace Emby.Server.Implementations.Data { item.IsInMixedFolder = reader.GetBoolean(index); } + index++; if (HasField(query, ItemFields.DateLastSaved)) @@ -1618,6 +1655,7 @@ namespace Emby.Server.Implementations.Data { item.DateLastSaved = reader[index].ReadDateTime(); } + index++; } @@ -1635,8 +1673,10 @@ namespace Emby.Server.Implementations.Data } } } + item.LockedFields = GetLockedFields(reader.GetString(index)).ToArray(); } + index++; } @@ -1646,6 +1686,7 @@ namespace Emby.Server.Implementations.Data { item.Studios = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); } + index++; } @@ -1655,6 +1696,7 @@ namespace Emby.Server.Implementations.Data { item.Tags = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); } + index++; } @@ -1674,9 +1716,11 @@ namespace Emby.Server.Implementations.Data } } } + trailer.TrailerTypes = GetTrailerTypes(reader.GetString(index)).ToArray(); } } + index++; } @@ -1686,6 +1730,7 @@ namespace Emby.Server.Implementations.Data { item.OriginalTitle = reader.GetString(index); } + index++; } @@ -1696,6 +1741,7 @@ namespace Emby.Server.Implementations.Data video.PrimaryVersionId = reader.GetString(index); } } + index++; if (HasField(query, ItemFields.DateLastMediaAdded)) @@ -1704,6 +1750,7 @@ namespace Emby.Server.Implementations.Data { folder.DateLastMediaAdded = reader[index].TryReadDateTime(); } + index++; } @@ -1711,18 +1758,21 @@ namespace Emby.Server.Implementations.Data { item.Album = reader.GetString(index); } + index++; if (!reader.IsDBNull(index)) { item.CriticRating = reader.GetFloat(index); } + index++; if (!reader.IsDBNull(index)) { item.IsVirtualItem = reader.GetBoolean(index); } + index++; if (item is IHasSeries hasSeriesName) @@ -1732,6 +1782,7 @@ namespace Emby.Server.Implementations.Data hasSeriesName.SeriesName = reader.GetString(index); } } + index++; if (hasEpisodeAttributes) @@ -1742,6 +1793,7 @@ namespace Emby.Server.Implementations.Data { episode.SeasonName = reader.GetString(index); } + index++; if (!reader.IsDBNull(index)) { @@ -1752,6 +1804,7 @@ namespace Emby.Server.Implementations.Data { index++; } + index++; } @@ -1765,6 +1818,7 @@ namespace Emby.Server.Implementations.Data hasSeries.SeriesId = reader.GetGuid(index); } } + index++; } @@ -1774,6 +1828,7 @@ namespace Emby.Server.Implementations.Data { item.PresentationUniqueKey = reader.GetString(index); } + index++; } @@ -1783,6 +1838,7 @@ namespace Emby.Server.Implementations.Data { item.InheritedParentalRatingValue = reader.GetInt32(index); } + index++; } @@ -1792,6 +1848,7 @@ namespace Emby.Server.Implementations.Data { item.ExternalSeriesId = reader.GetString(index); } + index++; } @@ -1801,6 +1858,7 @@ namespace Emby.Server.Implementations.Data { item.Tagline = reader.GetString(index); } + index++; } @@ -1808,6 +1866,7 @@ namespace Emby.Server.Implementations.Data { DeserializeProviderIds(reader.GetString(index), item); } + index++; if (query.DtoOptions.EnableImages) @@ -1816,6 +1875,7 @@ namespace Emby.Server.Implementations.Data { DeserializeImages(reader.GetString(index), item); } + index++; } @@ -1825,6 +1885,7 @@ namespace Emby.Server.Implementations.Data { item.ProductionLocations = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries).ToArray(); } + index++; } @@ -1834,6 +1895,7 @@ namespace Emby.Server.Implementations.Data { item.ExtraIds = SplitToGuids(reader.GetString(index)); } + index++; } @@ -1841,6 +1903,7 @@ namespace Emby.Server.Implementations.Data { item.TotalBitrate = reader.GetInt32(index); } + index++; if (!reader.IsDBNull(index)) @@ -1850,6 +1913,7 @@ namespace Emby.Server.Implementations.Data item.ExtraType = extraType; } } + index++; if (hasArtistFields) @@ -1858,12 +1922,14 @@ namespace Emby.Server.Implementations.Data { hasArtists.Artists = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); } + index++; if (item is IHasAlbumArtist hasAlbumArtists && !reader.IsDBNull(index)) { hasAlbumArtists.AlbumArtists = reader.GetString(index).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); } + index++; } @@ -1871,6 +1937,7 @@ namespace Emby.Server.Implementations.Data { item.ExternalId = reader.GetString(index); } + index++; if (HasField(query, ItemFields.SeriesPresentationUniqueKey)) @@ -1882,6 +1949,7 @@ namespace Emby.Server.Implementations.Data hasSeries.SeriesPresentationUniqueKey = reader.GetString(index); } } + index++; } @@ -1891,6 +1959,7 @@ namespace Emby.Server.Implementations.Data { program.ShowId = reader.GetString(index); } + index++; } @@ -1898,6 +1967,7 @@ namespace Emby.Server.Implementations.Data { item.OwnerId = reader.GetGuid(index); } + index++; return item; @@ -2473,6 +2543,7 @@ namespace Emby.Server.Implementations.Data { statement.TryBind("@SearchTermStartsWith", searchTerm + "%"); } + if (commandText.IndexOf("@SearchTermContains", StringComparison.OrdinalIgnoreCase) != -1) { statement.TryBind("@SearchTermContains", "%" + searchTerm + "%"); @@ -2743,6 +2814,7 @@ namespace Emby.Server.Implementations.Data { items[i] = newItem; } + return; } } @@ -2835,6 +2907,7 @@ namespace Emby.Server.Implementations.Data { statementTexts.Add(commandText); } + if (query.EnableTotalRecordCount) { commandText = string.Empty; @@ -3239,6 +3312,7 @@ namespace Emby.Server.Implementations.Data { statementTexts.Add(commandText); } + if (query.EnableTotalRecordCount) { commandText = string.Empty; @@ -3592,11 +3666,13 @@ namespace Emby.Server.Implementations.Data whereClauses.Add("IndexNumber=@IndexNumber"); statement?.TryBind("@IndexNumber", query.IndexNumber.Value); } + if (query.ParentIndexNumber.HasValue) { whereClauses.Add("ParentIndexNumber=@ParentIndexNumber"); statement?.TryBind("@ParentIndexNumber", query.ParentIndexNumber.Value); } + if (query.ParentIndexNumberNotEquals.HasValue) { whereClauses.Add("(ParentIndexNumber<>@ParentIndexNumberNotEquals or ParentIndexNumber is null)"); @@ -3882,6 +3958,7 @@ namespace Emby.Server.Implementations.Data { statement.TryBind(paramName, artistId.ToByteArray()); } + index++; } @@ -3902,6 +3979,7 @@ namespace Emby.Server.Implementations.Data { statement.TryBind(paramName, artistId.ToByteArray()); } + index++; } @@ -3922,8 +4000,10 @@ namespace Emby.Server.Implementations.Data { statement.TryBind(paramName, artistId.ToByteArray()); } + index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -3941,8 +4021,10 @@ namespace Emby.Server.Implementations.Data { statement.TryBind(paramName, albumId.ToByteArray()); } + index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -3960,8 +4042,10 @@ namespace Emby.Server.Implementations.Data { statement.TryBind(paramName, artistId.ToByteArray()); } + index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -3979,8 +4063,10 @@ namespace Emby.Server.Implementations.Data { statement.TryBind(paramName, genreId.ToByteArray()); } + index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -3996,8 +4082,10 @@ namespace Emby.Server.Implementations.Data { statement.TryBind("@Genre" + index, GetCleanValue(item)); } + index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -4013,8 +4101,10 @@ namespace Emby.Server.Implementations.Data { statement.TryBind("@Tag" + index, GetCleanValue(item)); } + index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -4030,8 +4120,10 @@ namespace Emby.Server.Implementations.Data { statement.TryBind("@ExcludeTag" + index, GetCleanValue(item)); } + index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -4050,8 +4142,10 @@ namespace Emby.Server.Implementations.Data { statement.TryBind(paramName, studioId.ToByteArray()); } + index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -4067,8 +4161,10 @@ namespace Emby.Server.Implementations.Data { statement.TryBind("@OfficialRating" + index, item); } + index++; } + var clause = "(" + string.Join(" OR ", clauses) + ")"; whereClauses.Add(clause); } @@ -4243,6 +4339,7 @@ namespace Emby.Server.Implementations.Data statement.TryBind("@IsVirtualItem", isVirtualItem.Value); } } + if (query.IsSpecialSeason.HasValue) { if (query.IsSpecialSeason.Value) @@ -4254,6 +4351,7 @@ namespace Emby.Server.Implementations.Data whereClauses.Add("IndexNumber <> 0"); } } + if (query.IsUnaired.HasValue) { if (query.IsUnaired.Value) @@ -4265,6 +4363,7 @@ namespace Emby.Server.Implementations.Data whereClauses.Add("PremiereDate < DATETIME('now')"); } } + var queryMediaTypes = query.MediaTypes.Where(IsValidMediaType).ToArray(); if (queryMediaTypes.Length == 1) { @@ -4280,6 +4379,7 @@ namespace Emby.Server.Implementations.Data whereClauses.Add("MediaType in (" + val + ")"); } + if (query.ItemIds.Length > 0) { var includeIds = new List(); @@ -4292,11 +4392,13 @@ namespace Emby.Server.Implementations.Data { statement.TryBind("@IncludeId" + index, id); } + index++; } whereClauses.Add("(" + string.Join(" OR ", includeIds) + ")"); } + if (query.ExcludeItemIds.Length > 0) { var excludeIds = new List(); @@ -4309,6 +4411,7 @@ namespace Emby.Server.Implementations.Data { statement.TryBind("@ExcludeId" + index, id); } + index++; } @@ -4333,6 +4436,7 @@ namespace Emby.Server.Implementations.Data { statement.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%"); } + index++; break; @@ -4375,6 +4479,7 @@ namespace Emby.Server.Implementations.Data { statement.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%"); } + index++; break; @@ -4425,6 +4530,7 @@ namespace Emby.Server.Implementations.Data { whereClauses.Add("(TopParentId=@TopParentId)"); } + if (statement != null) { statement.TryBind("@TopParentId", queryTopParentIds[0].ToString("N", CultureInfo.InvariantCulture)); @@ -4462,11 +4568,13 @@ namespace Emby.Server.Implementations.Data statement.TryBind("@AncestorId", query.AncestorIds[0]); } } + if (query.AncestorIds.Length > 1) { var inClause = string.Join(",", query.AncestorIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'")); whereClauses.Add(string.Format("Guid in (select itemId from AncestorIds where AncestorIdText in ({0}))", inClause)); } + if (!string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey)) { var inClause = "select guid from TypedBaseItems where PresentationUniqueKey=@AncestorWithPresentationUniqueKey"; @@ -4495,6 +4603,7 @@ namespace Emby.Server.Implementations.Data statement.TryBind("@UnratedType", query.BlockUnratedItems[0].ToString()); } } + if (query.BlockUnratedItems.Length > 1) { var inClause = string.Join(",", query.BlockUnratedItems.Select(i => "'" + i.ToString() + "'")); @@ -4969,6 +5078,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statement.TryBind("@ItemId", query.ItemId.ToByteArray()); } } + if (!query.AppearsInItemId.Equals(Guid.Empty)) { whereClauses.Add("Name in (Select Name from People where ItemId=@AppearsInItemId)"); @@ -4977,6 +5087,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statement.TryBind("@AppearsInItemId", query.AppearsInItemId.ToByteArray()); } } + var queryPersonTypes = query.PersonTypes.Where(IsValidPersonType).ToList(); if (queryPersonTypes.Count == 1) @@ -4993,6 +5104,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type whereClauses.Add("PersonType in (" + val + ")"); } + var queryExcludePersonTypes = query.ExcludePersonTypes.Where(IsValidPersonType).ToList(); if (queryExcludePersonTypes.Count == 1) @@ -5009,6 +5121,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type whereClauses.Add("PersonType not in (" + val + ")"); } + if (query.MaxListOrder.HasValue) { whereClauses.Add("ListOrder<=@MaxListOrder"); @@ -5017,6 +5130,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type statement.TryBind("@MaxListOrder", query.MaxListOrder.Value); } } + if (!string.IsNullOrWhiteSpace(query.NameContains)) { whereClauses.Add("Name like @NameContains"); @@ -5156,6 +5270,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type var typeString = string.Join(",", withItemTypes.Select(i => "'" + i + "'")); commandText += " AND ItemId In (select guid from typedbaseitems where type in (" + typeString + "))"; } + if (excludeItemTypes.Count > 0) { var typeString = string.Join(",", excludeItemTypes.Select(i => "'" + i + "'")); diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 7e66fa072e..023125c924 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -135,10 +135,12 @@ namespace Emby.Server.Implementations.Data { throw new ArgumentNullException(nameof(userData)); } + if (internalUserId <= 0) { throw new ArgumentNullException(nameof(internalUserId)); } + if (string.IsNullOrEmpty(key)) { throw new ArgumentNullException(nameof(key)); @@ -153,6 +155,7 @@ namespace Emby.Server.Implementations.Data { throw new ArgumentNullException(nameof(userData)); } + if (internalUserId <= 0) { throw new ArgumentNullException(nameof(internalUserId)); diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index 789cdfc119..e75745cc6e 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -169,6 +169,7 @@ namespace Emby.Server.Implementations.Devices { throw new ArgumentException("user not found"); } + if (string.IsNullOrEmpty(deviceId)) { throw new ArgumentNullException(nameof(deviceId)); diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 41ff7e3abf..f5a58cc6d6 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -277,6 +277,7 @@ namespace Emby.Server.Implementations.Dto dto.EpisodeTitle = dto.Name; dto.Name = dto.SeriesName; } + liveTvManager.AddInfoToRecordingDto(item, dto, activeRecording, user); } @@ -292,6 +293,7 @@ namespace Emby.Server.Implementations.Dto { continue; } + var containers = container.Split(new[] { ',' }); if (containers.Length < 2) { @@ -456,6 +458,7 @@ namespace Emby.Server.Implementations.Dto { dto.SeriesName = item.SeriesName; } + private static void SetPhotoProperties(BaseItemDto dto, Photo item) { dto.CameraMake = item.CameraMake; @@ -554,22 +557,27 @@ namespace Emby.Server.Implementations.Dto { return 0; } + if (i.IsType(PersonType.GuestStar)) { return 1; } + if (i.IsType(PersonType.Director)) { return 2; } + if (i.IsType(PersonType.Writer)) { return 3; } + if (i.IsType(PersonType.Producer)) { return 4; } + if (i.IsType(PersonType.Composer)) { return 4; @@ -1346,6 +1354,7 @@ namespace Emby.Server.Implementations.Dto dto.ParentLogoImageTag = GetTagAndFillBlurhash(dto, parent, image); } } + if (artLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && dto.ParentArtItemId == null) { var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art); @@ -1356,6 +1365,7 @@ namespace Emby.Server.Implementations.Dto dto.ParentArtImageTag = GetTagAndFillBlurhash(dto, parent, image); } } + if (thumbLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentThumbItemId == null || parent is Series) && !(parent is ICollectionFolder) && !(parent is UserView)) { var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb); @@ -1366,6 +1376,7 @@ namespace Emby.Server.Implementations.Dto dto.ParentThumbImageTag = GetTagAndFillBlurhash(dto, parent, image); } } + if (backdropLimit > 0 && !((dto.BackdropImageTags != null && dto.BackdropImageTags.Length > 0) || (dto.ParentBackdropImageTags != null && dto.ParentBackdropImageTags.Length > 0))) { var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList(); diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 1b6e4b5547..e52acc5489 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -453,6 +453,7 @@ namespace Emby.Server.Implementations.HttpServer { httpRes.Headers.Add(key, value); } + httpRes.ContentType = "text/plain"; await httpRes.WriteAsync(string.Empty, cancellationToken).ConfigureAwait(false); return; diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index 8b9028f6bc..94cedb918f 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -20,15 +20,21 @@ namespace Emby.Server.Implementations.HttpServer /// /// The source stream. private Stream SourceStream { 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; } + private readonly ILogger _logger; private const int BufferSize = 81920; @@ -139,6 +145,7 @@ namespace Emby.Server.Implementations.HttpServer { start = long.Parse(vals[0], UsCulture); } + if (!string.IsNullOrEmpty(vals[1])) { end = long.Parse(vals[1], UsCulture); diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 72959003a6..2e6ff65a6f 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -140,6 +140,7 @@ namespace Emby.Server.Implementations.HttpServer.Security { return true; } + if (authAttribtues.AllowLocalOnly && request.IsLocal) { return true; diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 4dffcd92d4..bbade00ff3 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -71,6 +71,7 @@ namespace Emby.Server.Implementations.HttpServer.Security { token = httpReq.Headers["X-MediaBrowser-Token"]; } + if (string.IsNullOrEmpty(token)) { token = httpReq.QueryString["api_key"]; @@ -160,6 +161,7 @@ namespace Emby.Server.Implementations.HttpServer.Security _authRepo.Update(tokenInfo); } } + httpReq.Items["OriginalAuthenticationInfo"] = tokenInfo; } diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index a7bbf6acc8..a3a3f91b72 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -628,6 +628,7 @@ namespace Emby.Server.Implementations.IO { return false; } + return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase); }); } @@ -682,6 +683,7 @@ namespace Emby.Server.Implementations.IO { return false; } + return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase); }); } diff --git a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs index 9a71868988..ab39a7223d 100644 --- a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs +++ b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs @@ -12,11 +12,13 @@ namespace Emby.Server.Implementations.Library public class ExclusiveLiveStream : ILiveStream { public int ConsumerCount { get; set; } + public string OriginalStreamId { get; set; } public string TunerHostId => null; public bool EnableStreamSharing { get; set; } + public MediaSourceInfo MediaSource { get; set; } public string UniqueId { get; private set; } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 1d4651da20..46f4332829 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2784,10 +2784,12 @@ namespace Emby.Server.Implementations.Library { throw new ArgumentNullException(nameof(path)); } + if (string.IsNullOrWhiteSpace(from)) { throw new ArgumentNullException(nameof(from)); } + if (string.IsNullOrWhiteSpace(to)) { throw new ArgumentNullException(nameof(to)); diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 919261027d..ceb36b389b 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -205,22 +205,27 @@ namespace Emby.Server.Implementations.Library { return MediaProtocol.Rtsp; } + if (path.StartsWith("Rtmp", StringComparison.OrdinalIgnoreCase)) { return MediaProtocol.Rtmp; } + if (path.StartsWith("Http", StringComparison.OrdinalIgnoreCase)) { return MediaProtocol.Http; } + if (path.StartsWith("rtp", StringComparison.OrdinalIgnoreCase)) { return MediaProtocol.Rtp; } + if (path.StartsWith("ftp", StringComparison.OrdinalIgnoreCase)) { return MediaProtocol.Ftp; } + if (path.StartsWith("udp", StringComparison.OrdinalIgnoreCase)) { return MediaProtocol.Udp; diff --git a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs index 4819f2fc0f..99f3041909 100644 --- a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs @@ -41,10 +41,12 @@ namespace Emby.Server.Implementations.Library.Resolvers { return new AggregateFolder(); } + if (string.Equals(args.Path, _appPaths.DefaultUserViewsPath, StringComparison.OrdinalIgnoreCase)) { return new UserRootFolder(); // if we got here and still a root - must be user root } + if (args.IsVf) { return new CollectionFolder diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index 7f477a0f08..2f7af60c0d 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -55,6 +55,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV episode.SeriesId = series.Id; episode.SeriesName = series.Name; } + if (season != null) { episode.SeasonId = season.Id; diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index b8c42cdf8a..3df9cc06f1 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -194,6 +194,7 @@ namespace Emby.Server.Implementations.Library { searchQuery.AncestorIds = new[] { searchQuery.ParentId }; } + searchQuery.ParentId = Guid.Empty; searchQuery.IncludeItemsByName = true; searchQuery.IncludeItemTypes = Array.Empty(); diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index fdd305f862..3709f8fe47 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -212,6 +212,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings { channelNumber = map.channel; } + if (string.IsNullOrWhiteSpace(channelNumber)) { channelNumber = map.atscMajor + "." + map.atscMinor; @@ -400,6 +401,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings { date = DateTime.SpecifyKind(date, DateTimeKind.Utc); } + return date; } @@ -622,6 +624,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings _lastErrorResponse = DateTime.UtcNow; } } + throw; } finally @@ -805,11 +808,13 @@ namespace Emby.Server.Implementations.LiveTv.Listings { throw new ArgumentException("Username is required"); } + if (string.IsNullOrEmpty(info.Password)) { throw new ArgumentException("Password is required"); } } + if (validateListings) { if (string.IsNullOrEmpty(info.ListingsId)) @@ -932,24 +937,35 @@ namespace Emby.Server.Implementations.LiveTv.Listings public class Token { public int code { get; set; } + public string message { get; set; } + public string serverID { get; set; } + public string token { get; set; } } + public class Lineup { public string lineup { get; set; } + public string name { get; set; } + public string transport { get; set; } + public string location { get; set; } + public string uri { get; set; } } public class Lineups { public int code { get; set; } + public string serverID { get; set; } + public string datetime { get; set; } + public List lineups { get; set; } } @@ -957,8 +973,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings public class Headends { public string headend { get; set; } + public string transport { get; set; } + public string location { get; set; } + public List lineups { get; set; } } @@ -967,59 +986,83 @@ namespace Emby.Server.Implementations.LiveTv.Listings public class Map { public string stationID { get; set; } + public string channel { get; set; } + public string logicalChannelNumber { get; set; } + public int uhfVhf { get; set; } + public int atscMajor { get; set; } + public int atscMinor { get; set; } } public class Broadcaster { public string city { get; set; } + public string state { get; set; } + public string postalcode { get; set; } + public string country { get; set; } } public class Logo { public string URL { get; set; } + public int height { get; set; } + public int width { get; set; } + public string md5 { get; set; } } public class Station { public string stationID { get; set; } + public string name { get; set; } + public string callsign { get; set; } + public List broadcastLanguage { get; set; } + public List descriptionLanguage { get; set; } + public Broadcaster broadcaster { get; set; } + public string affiliate { get; set; } + public Logo logo { get; set; } + public bool? isCommercialFree { get; set; } } public class Metadata { public string lineup { get; set; } + public string modified { get; set; } + public string transport { get; set; } } public class Channel { public List map { get; set; } + public List stations { get; set; } + public Metadata metadata { get; set; } } public class RequestScheduleForChannel { public string stationID { get; set; } + public List date { get; set; } } @@ -1029,29 +1072,43 @@ namespace Emby.Server.Implementations.LiveTv.Listings public class Rating { public string body { get; set; } + public string code { get; set; } } public class Multipart { public int partNumber { get; set; } + public int totalParts { get; set; } } public class Program { public string programID { get; set; } + public string airDateTime { get; set; } + public int duration { get; set; } + public string md5 { get; set; } + public List audioProperties { get; set; } + public List videoProperties { get; set; } + public List ratings { get; set; } + public bool? @new { get; set; } + public Multipart multipart { get; set; } + public string liveTapeDelay { get; set; } + public bool premiere { get; set; } + public bool repeat { get; set; } + public string isPremiereOrFinale { get; set; } } @@ -1060,16 +1117,22 @@ namespace Emby.Server.Implementations.LiveTv.Listings public class MetadataSchedule { public string modified { get; set; } + public string md5 { get; set; } + public string startDate { get; set; } + public string endDate { get; set; } + public int days { get; set; } } public class Day { public string stationID { get; set; } + public List programs { get; set; } + public MetadataSchedule metadata { get; set; } public Day() @@ -1092,24 +1155,28 @@ namespace Emby.Server.Implementations.LiveTv.Listings public class Description100 { public string descriptionLanguage { get; set; } + public string description { get; set; } } public class Description1000 { public string descriptionLanguage { get; set; } + public string description { get; set; } } public class DescriptionsProgram { public List description100 { get; set; } + public List description1000 { get; set; } } public class Gracenote { public int season { get; set; } + public int episode { get; set; } } @@ -1121,101 +1188,152 @@ namespace Emby.Server.Implementations.LiveTv.Listings public class ContentRating { public string body { get; set; } + public string code { get; set; } } public class Cast { public string billingOrder { get; set; } + public string role { get; set; } + public string nameId { get; set; } + public string personId { get; set; } + public string name { get; set; } + public string characterName { get; set; } } public class Crew { public string billingOrder { get; set; } + public string role { get; set; } + public string nameId { get; set; } + public string personId { get; set; } + public string name { get; set; } } public class QualityRating { public string ratingsBody { get; set; } + public string rating { get; set; } + public string minRating { get; set; } + public string maxRating { get; set; } + public string increment { get; set; } } public class Movie { public string year { get; set; } + public int duration { get; set; } + public List qualityRating { get; set; } } public class Recommendation { public string programID { get; set; } + public string title120 { get; set; } } public class ProgramDetails { public string audience { get; set; } + public string programID { get; set; } + public List titles { get; set; } + public EventDetails eventDetails { get; set; } + public DescriptionsProgram descriptions { get; set; } + public string originalAirDate { get; set; } + public List<string> genres { get; set; } + public string episodeTitle150 { get; set; } + public List<MetadataPrograms> metadata { get; set; } + public List<ContentRating> contentRating { get; set; } + public List<Cast> cast { get; set; } + public List<Crew> crew { get; set; } + public string entityType { get; set; } + public string showType { get; set; } + public bool hasImageArtwork { get; set; } + public string primaryImage { get; set; } + public string thumbImage { get; set; } + public string backdropImage { get; set; } + public string bannerImage { get; set; } + public string imageID { get; set; } + public string md5 { get; set; } + public List<string> contentAdvisory { get; set; } + public Movie movie { get; set; } + public List<Recommendation> recommendations { get; set; } } public class Caption { public string content { get; set; } + public string lang { get; set; } } public class ImageData { public string width { get; set; } + public string height { get; set; } + public string uri { get; set; } + public string size { get; set; } + public string aspect { get; set; } + public string category { get; set; } + public string text { get; set; } + public string primary { get; set; } + public string tier { get; set; } + public Caption caption { get; set; } } public class ShowImages { public string programID { get; set; } + public List<ImageData> data { get; set; } } } diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 077b5c7e50..0a93c46748 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -224,6 +224,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings { uniqueString = "-" + programInfo.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture); } + if (programInfo.EpisodeNumber.HasValue) { uniqueString = "-" + programInfo.EpisodeNumber.Value.ToString(CultureInfo.InvariantCulture); diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 42e93b7ff6..4c1de3bccf 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -556,6 +556,7 @@ namespace Emby.Server.Implementations.LiveTv { forceUpdate = true; } + item.ParentId = channel.Id; // item.ChannelType = channelType; @@ -575,6 +576,7 @@ namespace Emby.Server.Implementations.LiveTv { forceUpdate = true; } + item.ExternalSeriesId = seriesId; var isSeries = info.IsSeries || !string.IsNullOrEmpty(info.EpisodeTitle); @@ -589,30 +591,37 @@ namespace Emby.Server.Implementations.LiveTv { tags.Add("Live"); } + if (info.IsPremiere) { tags.Add("Premiere"); } + if (info.IsNews) { tags.Add("News"); } + if (info.IsSports) { tags.Add("Sports"); } + if (info.IsKids) { tags.Add("Kids"); } + if (info.IsRepeat) { tags.Add("Repeat"); } + if (info.IsMovie) { tags.Add("Movie"); } + if (isSeries) { tags.Add("Series"); @@ -635,6 +644,7 @@ namespace Emby.Server.Implementations.LiveTv { forceUpdate = true; } + item.IsSeries = isSeries; item.Name = info.Name; @@ -652,12 +662,14 @@ namespace Emby.Server.Implementations.LiveTv { forceUpdate = true; } + item.StartDate = info.StartDate; if (item.EndDate != info.EndDate) { forceUpdate = true; } + item.EndDate = info.EndDate; item.ProductionYear = info.ProductionYear; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index f14fcde2a2..dff113a2a7 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -170,6 +170,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun _modelCache[cacheKey] = response; } } + return response; } @@ -201,6 +202,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var name = line.Substring(0, index - 1); var currentChannel = line.Substring(index + 7); if (currentChannel != "none") { status = LiveTvTunerStatus.LiveTv; } else { status = LiveTvTunerStatus.Available; } + tuners.Add(new LiveTvTunerInfo { Name = name, @@ -229,11 +231,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun inside = true; continue; } + if (let == '>') { inside = false; continue; } + if (!inside) { buffer[bufferIndex] = let; @@ -331,12 +335,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private class Channels { public string GuideNumber { get; set; } + public string GuideName { get; set; } + public string VideoCodec { get; set; } + public string AudioCodec { get; set; } + public string URL { get; set; } + public bool Favorite { get; set; } + public bool DRM { get; set; } + public int HD { get; set; } } @@ -657,13 +668,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public class DiscoverResponse { public string FriendlyName { get; set; } + public string ModelNumber { get; set; } + public string FirmwareName { get; set; } + public string FirmwareVersion { get; set; } + public string DeviceID { get; set; } + public string DeviceAuth { get; set; } + public string BaseURL { get; set; } + public string LineupURL { get; set; } + public int TunerCount { get; set; } public bool SupportsTranscoding diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index 4decdc24f8..0333e723bc 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -58,12 +58,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts protected virtual int EmptyReadLimit => 1000; public MediaSourceInfo OriginalMediaSource { get; set; } + public MediaSourceInfo MediaSource { get; set; } public int ConsumerCount { get; set; } public string OriginalStreamId { get; set; } + public bool EnableStreamSharing { get; set; } + public string UniqueId { get; } public string TunerHostId { get; } diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 184d64e608..ac816ccd93 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -401,6 +401,7 @@ namespace Emby.Server.Implementations.Playlists { entry.Duration = TimeSpan.FromTicks(child.RunTimeTicks.Value); } + playlist.PlaylistEntries.Add(entry); } diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index e58c335a85..2031a1c6db 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -143,12 +143,14 @@ namespace Emby.Server.Implementations.ScheduledTasks Logger.LogError(ex, "Error deserializing {File}", path); } } + _readFromFile = true; } } return _lastExecutionResult; } + private set { _lastExecutionResult = value; @@ -261,6 +263,7 @@ namespace Emby.Server.Implementations.ScheduledTasks var triggers = InternalTriggers; return triggers.Select(i => i.Item1).ToArray(); } + set { if (value == null) @@ -640,6 +643,7 @@ namespace Emby.Server.Implementations.ScheduledTasks Logger.LogError(ex, "Error calling CancellationToken.Cancel();"); } } + var task = _currentTask; if (task != null) { @@ -675,6 +679,7 @@ namespace Emby.Server.Implementations.ScheduledTasks Logger.LogError(ex, "Error calling CancellationToken.Dispose();"); } } + if (wassRunning) { OnTaskCompleted(startTime, DateTime.UtcNow, TaskCompletionStatus.Aborted, null); diff --git a/Emby.Server.Implementations/Services/ServiceMethod.cs b/Emby.Server.Implementations/Services/ServiceMethod.cs index 59ee5908fd..5116cc04fe 100644 --- a/Emby.Server.Implementations/Services/ServiceMethod.cs +++ b/Emby.Server.Implementations/Services/ServiceMethod.cs @@ -9,6 +9,7 @@ namespace Emby.Server.Implementations.Services public string Id { get; set; } public ActionInvokerFn ServiceAction { get; set; } + public MediaBrowser.Model.Services.IHasRequestFilter[] RequestFilters { get; set; } public static string Key(Type serviceType, string method, string requestDtoName) diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs index 43869f98af..3b7ffaf2cd 100644 --- a/Emby.Server.Implementations/Services/ServicePath.cs +++ b/Emby.Server.Implementations/Services/ServicePath.cs @@ -62,7 +62,9 @@ namespace Emby.Server.Implementations.Services public string Path => this.restPath; public string Summary { get; private set; } + public string Description { get; private set; } + public bool IsHidden { get; private set; } public static string[] GetPathPartsForMatching(string pathInfo) @@ -159,6 +161,7 @@ namespace Emby.Server.Implementations.Services this.isWildcard[i] = true; variableName = variableName.Substring(0, variableName.Length - 1); } + this.variablesNames[i] = variableName; this.VariableArgsCount++; } diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs index d3d27ae584..165bb0fc42 100644 --- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs +++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs @@ -22,7 +22,9 @@ namespace Emby.Server.Implementations.Services } public Action<object, object> PropertySetFn { get; private set; } + public Func<string, object> PropertyParseStringFn { get; private set; } + public Type PropertyType { get; private set; } } diff --git a/Emby.Server.Implementations/Services/SwaggerService.cs b/Emby.Server.Implementations/Services/SwaggerService.cs index 16142a70df..4f011a678f 100644 --- a/Emby.Server.Implementations/Services/SwaggerService.cs +++ b/Emby.Server.Implementations/Services/SwaggerService.cs @@ -18,13 +18,21 @@ namespace Emby.Server.Implementations.Services public class SwaggerSpec { public string swagger { get; set; } + public string[] schemes { get; set; } + public SwaggerInfo info { get; set; } + public string host { get; set; } + public string basePath { get; set; } + public SwaggerTag[] tags { get; set; } + public IDictionary<string, Dictionary<string, SwaggerMethod>> paths { get; set; } + public Dictionary<string, SwaggerDefinition> definitions { get; set; } + public SwaggerComponents components { get; set; } } @@ -36,15 +44,20 @@ namespace Emby.Server.Implementations.Services public class SwaggerSecurityScheme { public string name { get; set; } + public string type { get; set; } + public string @in { get; set; } } public class SwaggerInfo { public string description { get; set; } + public string version { get; set; } + public string title { get; set; } + public string termsOfService { get; set; } public SwaggerConcactInfo contact { get; set; } @@ -53,36 +66,52 @@ namespace Emby.Server.Implementations.Services public class SwaggerConcactInfo { public string email { get; set; } + public string name { get; set; } + public string url { get; set; } } public class SwaggerTag { public string description { get; set; } + public string name { get; set; } } public class SwaggerMethod { public string summary { get; set; } + public string description { get; set; } + public string[] tags { get; set; } + public string operationId { get; set; } + public string[] consumes { get; set; } + public string[] produces { get; set; } + public SwaggerParam[] parameters { get; set; } + public Dictionary<string, SwaggerResponse> responses { get; set; } + public Dictionary<string, string[]>[] security { get; set; } } public class SwaggerParam { public string @in { get; set; } + public string name { get; set; } + public string description { get; set; } + public bool required { get; set; } + public string type { get; set; } + public string collectionFormat { get; set; } } @@ -97,15 +126,20 @@ namespace Emby.Server.Implementations.Services public class SwaggerDefinition { public string type { get; set; } + public Dictionary<string, SwaggerProperty> properties { get; set; } } public class SwaggerProperty { public string type { get; set; } + public string format { get; set; } + public string description { get; set; } + public string[] @enum { get; set; } + public string @default { get; set; } } diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index ef32c692c1..1e8b25e38e 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -167,6 +167,7 @@ namespace Emby.Server.Implementations.Session _logger.LogWarning("Multiple attempts to keep alive single WebSocket {0}", webSocket); return; } + webSocket.Closed += OnWebSocketClosed; webSocket.LastKeepAliveDate = DateTime.UtcNow; diff --git a/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs index 0c944a7a02..491057a851 100644 --- a/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs +++ b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs @@ -44,6 +44,7 @@ namespace Emby.Server.Implementations.Sorting // Don't blow up if the item has a bad ProductionYear, just return MinValue } } + return DateTime.MinValue; } diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index db056cc38d..21c12ae79f 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -256,6 +256,7 @@ namespace Emby.Server.Implementations.TV { items = items.Skip(query.StartIndex.Value); } + if (query.Limit.HasValue) { items = items.Take(query.Limit.Value); diff --git a/Jellyfin.Data/Entities/Artwork.cs b/Jellyfin.Data/Entities/Artwork.cs index 214fb4cb1b..bebcc37d33 100644 --- a/Jellyfin.Data/Entities/Artwork.cs +++ b/Jellyfin.Data/Entities/Artwork.cs @@ -89,6 +89,7 @@ namespace Jellyfin.Data.Entities GetId(ref value); return (_Id = value); } + protected set { int oldValue = _Id; @@ -127,6 +128,7 @@ namespace Jellyfin.Data.Entities GetPath(ref value); return (_Path = value); } + set { string oldValue = _Path; @@ -163,6 +165,7 @@ namespace Jellyfin.Data.Entities GetKind(ref value); return (_Kind = value); } + set { Enums.ArtKind oldValue = _Kind; diff --git a/Jellyfin.Data/Entities/BookMetadata.cs b/Jellyfin.Data/Entities/BookMetadata.cs index dd389b64a7..6c72c27322 100644 --- a/Jellyfin.Data/Entities/BookMetadata.cs +++ b/Jellyfin.Data/Entities/BookMetadata.cs @@ -84,6 +84,7 @@ namespace Jellyfin.Data.Entities GetISBN(ref value); return (_ISBN = value); } + set { long? oldValue = _ISBN; diff --git a/Jellyfin.Data/Entities/Chapter.cs b/Jellyfin.Data/Entities/Chapter.cs index 9b3a5e8275..3d59fe6b88 100644 --- a/Jellyfin.Data/Entities/Chapter.cs +++ b/Jellyfin.Data/Entities/Chapter.cs @@ -86,6 +86,7 @@ namespace Jellyfin.Data.Entities GetId(ref value); return (_Id = value); } + protected set { int oldValue = _Id; @@ -123,6 +124,7 @@ namespace Jellyfin.Data.Entities GetName(ref value); return (_Name = value); } + set { string oldValue = _Name; @@ -163,6 +165,7 @@ namespace Jellyfin.Data.Entities GetLanguage(ref value); return (_Language = value); } + set { string oldValue = _Language; @@ -199,6 +202,7 @@ namespace Jellyfin.Data.Entities GetTimeStart(ref value); return (_TimeStart = value); } + set { long oldValue = _TimeStart; @@ -231,6 +235,7 @@ namespace Jellyfin.Data.Entities GetTimeEnd(ref value); return (_TimeEnd = value); } + set { long? oldValue = _TimeEnd; diff --git a/Jellyfin.Data/Entities/Collection.cs b/Jellyfin.Data/Entities/Collection.cs index c040cfe336..caf20f9169 100644 --- a/Jellyfin.Data/Entities/Collection.cs +++ b/Jellyfin.Data/Entities/Collection.cs @@ -49,6 +49,7 @@ namespace Jellyfin.Data.Entities GetId(ref value); return (_Id = value); } + protected set { int oldValue = _Id; @@ -86,6 +87,7 @@ namespace Jellyfin.Data.Entities GetName(ref value); return (_Name = value); } + set { string oldValue = _Name; diff --git a/Jellyfin.Data/Entities/CollectionItem.cs b/Jellyfin.Data/Entities/CollectionItem.cs index c5e54c3a2d..1a10780500 100644 --- a/Jellyfin.Data/Entities/CollectionItem.cs +++ b/Jellyfin.Data/Entities/CollectionItem.cs @@ -93,6 +93,7 @@ namespace Jellyfin.Data.Entities GetId(ref value); return (_Id = value); } + protected set { int oldValue = _Id; diff --git a/Jellyfin.Data/Entities/Company.cs b/Jellyfin.Data/Entities/Company.cs index 7d6f3b2076..eefc581c66 100644 --- a/Jellyfin.Data/Entities/Company.cs +++ b/Jellyfin.Data/Entities/Company.cs @@ -101,6 +101,7 @@ namespace Jellyfin.Data.Entities GetId(ref value); return (_Id = value); } + protected set { int oldValue = _Id; diff --git a/Jellyfin.Data/Entities/CompanyMetadata.cs b/Jellyfin.Data/Entities/CompanyMetadata.cs index 1ad03b4f9c..6a601c6e35 100644 --- a/Jellyfin.Data/Entities/CompanyMetadata.cs +++ b/Jellyfin.Data/Entities/CompanyMetadata.cs @@ -85,6 +85,7 @@ namespace Jellyfin.Data.Entities GetDescription(ref value); return (_Description = value); } + set { string oldValue = _Description; @@ -122,6 +123,7 @@ namespace Jellyfin.Data.Entities GetHeadquarters(ref value); return (_Headquarters = value); } + set { string oldValue = _Headquarters; @@ -159,6 +161,7 @@ namespace Jellyfin.Data.Entities GetCountry(ref value); return (_Country = value); } + set { string oldValue = _Country; @@ -196,6 +199,7 @@ namespace Jellyfin.Data.Entities GetHomepage(ref value); return (_Homepage = value); } + set { string oldValue = _Homepage; diff --git a/Jellyfin.Data/Entities/Episode.cs b/Jellyfin.Data/Entities/Episode.cs index 88531205fc..6f4353a6f6 100644 --- a/Jellyfin.Data/Entities/Episode.cs +++ b/Jellyfin.Data/Entities/Episode.cs @@ -86,6 +86,7 @@ namespace Jellyfin.Data.Entities GetEpisodeNumber(ref value); return (_EpisodeNumber = value); } + set { int? oldValue = _EpisodeNumber; diff --git a/Jellyfin.Data/Entities/EpisodeMetadata.cs b/Jellyfin.Data/Entities/EpisodeMetadata.cs index 0aa4b42703..17057cb1db 100644 --- a/Jellyfin.Data/Entities/EpisodeMetadata.cs +++ b/Jellyfin.Data/Entities/EpisodeMetadata.cs @@ -85,6 +85,7 @@ namespace Jellyfin.Data.Entities GetOutline(ref value); return (_Outline = value); } + set { string oldValue = _Outline; @@ -122,6 +123,7 @@ namespace Jellyfin.Data.Entities GetPlot(ref value); return (_Plot = value); } + set { string oldValue = _Plot; @@ -159,6 +161,7 @@ namespace Jellyfin.Data.Entities GetTagline(ref value); return (_Tagline = value); } + set { string oldValue = _Tagline; diff --git a/Jellyfin.Data/Entities/Genre.cs b/Jellyfin.Data/Entities/Genre.cs index ff07106712..b56e356678 100644 --- a/Jellyfin.Data/Entities/Genre.cs +++ b/Jellyfin.Data/Entities/Genre.cs @@ -82,6 +82,7 @@ namespace Jellyfin.Data.Entities GetId(ref value); return (_Id = value); } + protected set { int oldValue = _Id; @@ -120,6 +121,7 @@ namespace Jellyfin.Data.Entities GetName(ref value); return (_Name = value); } + set { string oldValue = _Name; diff --git a/Jellyfin.Data/Entities/Library.cs b/Jellyfin.Data/Entities/Library.cs index a5cc5c8da4..4c7f7e7b71 100644 --- a/Jellyfin.Data/Entities/Library.cs +++ b/Jellyfin.Data/Entities/Library.cs @@ -77,6 +77,7 @@ namespace Jellyfin.Data.Entities GetId(ref value); return (_Id = value); } + protected set { int oldValue = _Id; @@ -115,6 +116,7 @@ namespace Jellyfin.Data.Entities GetName(ref value); return (_Name = value); } + set { string oldValue = _Name; diff --git a/Jellyfin.Data/Entities/LibraryItem.cs b/Jellyfin.Data/Entities/LibraryItem.cs index c2ba7059d9..bfc2f7ddbd 100644 --- a/Jellyfin.Data/Entities/LibraryItem.cs +++ b/Jellyfin.Data/Entities/LibraryItem.cs @@ -59,6 +59,7 @@ namespace Jellyfin.Data.Entities GetId(ref value); return (_Id = value); } + protected set { int oldValue = _Id; @@ -96,6 +97,7 @@ namespace Jellyfin.Data.Entities GetUrlId(ref value); return (_UrlId = value); } + set { Guid oldValue = _UrlId; @@ -132,6 +134,7 @@ namespace Jellyfin.Data.Entities GetDateAdded(ref value); return (_DateAdded = value); } + internal set { DateTime oldValue = _DateAdded; diff --git a/Jellyfin.Data/Entities/LibraryRoot.cs b/Jellyfin.Data/Entities/LibraryRoot.cs index 7823db02a8..fa1e6f14c6 100644 --- a/Jellyfin.Data/Entities/LibraryRoot.cs +++ b/Jellyfin.Data/Entities/LibraryRoot.cs @@ -77,6 +77,7 @@ namespace Jellyfin.Data.Entities GetId(ref value); return (_Id = value); } + protected set { int oldValue = _Id; @@ -116,6 +117,7 @@ namespace Jellyfin.Data.Entities GetPath(ref value); return (_Path = value); } + set { string oldValue = _Path; @@ -154,6 +156,7 @@ namespace Jellyfin.Data.Entities GetNetworkPath(ref value); return (_NetworkPath = value); } + set { string oldValue = _NetworkPath; diff --git a/Jellyfin.Data/Entities/MediaFile.cs b/Jellyfin.Data/Entities/MediaFile.cs index 94c39a28a5..1c14c6c874 100644 --- a/Jellyfin.Data/Entities/MediaFile.cs +++ b/Jellyfin.Data/Entities/MediaFile.cs @@ -90,6 +90,7 @@ namespace Jellyfin.Data.Entities GetId(ref value); return (_Id = value); } + protected set { int oldValue = _Id; @@ -129,6 +130,7 @@ namespace Jellyfin.Data.Entities GetPath(ref value); return (_Path = value); } + set { string oldValue = _Path; @@ -165,6 +167,7 @@ namespace Jellyfin.Data.Entities GetKind(ref value); return (_Kind = value); } + set { Enums.MediaFileKind oldValue = _Kind; diff --git a/Jellyfin.Data/Entities/MediaFileStream.cs b/Jellyfin.Data/Entities/MediaFileStream.cs index 723977fdf1..8819ddcd0d 100644 --- a/Jellyfin.Data/Entities/MediaFileStream.cs +++ b/Jellyfin.Data/Entities/MediaFileStream.cs @@ -81,6 +81,7 @@ namespace Jellyfin.Data.Entities GetId(ref value); return (_Id = value); } + protected set { int oldValue = _Id; @@ -117,6 +118,7 @@ namespace Jellyfin.Data.Entities GetStreamNumber(ref value); return (_StreamNumber = value); } + set { int oldValue = _StreamNumber; diff --git a/Jellyfin.Data/Entities/Metadata.cs b/Jellyfin.Data/Entities/Metadata.cs index 6558642cf1..4e5868e754 100644 --- a/Jellyfin.Data/Entities/Metadata.cs +++ b/Jellyfin.Data/Entities/Metadata.cs @@ -76,6 +76,7 @@ namespace Jellyfin.Data.Entities GetId(ref value); return (_Id = value); } + protected set { int oldValue = _Id; @@ -115,6 +116,7 @@ namespace Jellyfin.Data.Entities GetTitle(ref value); return (_Title = value); } + set { string oldValue = _Title; @@ -152,6 +154,7 @@ namespace Jellyfin.Data.Entities GetOriginalTitle(ref value); return (_OriginalTitle = value); } + set { string oldValue = _OriginalTitle; @@ -189,6 +192,7 @@ namespace Jellyfin.Data.Entities GetSortTitle(ref value); return (_SortTitle = value); } + set { string oldValue = _SortTitle; @@ -229,6 +233,7 @@ namespace Jellyfin.Data.Entities GetLanguage(ref value); return (_Language = value); } + set { string oldValue = _Language; @@ -261,6 +266,7 @@ namespace Jellyfin.Data.Entities GetReleaseDate(ref value); return (_ReleaseDate = value); } + set { DateTimeOffset? oldValue = _ReleaseDate; @@ -297,6 +303,7 @@ namespace Jellyfin.Data.Entities GetDateAdded(ref value); return (_DateAdded = value); } + internal set { DateTime oldValue = _DateAdded; @@ -333,6 +340,7 @@ namespace Jellyfin.Data.Entities GetDateModified(ref value); return (_DateModified = value); } + internal set { DateTime oldValue = _DateModified; diff --git a/Jellyfin.Data/Entities/MetadataProvider.cs b/Jellyfin.Data/Entities/MetadataProvider.cs index bf9689709b..9b09fc5a6c 100644 --- a/Jellyfin.Data/Entities/MetadataProvider.cs +++ b/Jellyfin.Data/Entities/MetadataProvider.cs @@ -77,6 +77,7 @@ namespace Jellyfin.Data.Entities GetId(ref value); return (_Id = value); } + protected set { int oldValue = _Id; @@ -115,6 +116,7 @@ namespace Jellyfin.Data.Entities GetName(ref value); return (_Name = value); } + set { string oldValue = _Name; diff --git a/Jellyfin.Data/Entities/MetadataProviderId.cs b/Jellyfin.Data/Entities/MetadataProviderId.cs index c49c6f42e4..bcd2bad54d 100644 --- a/Jellyfin.Data/Entities/MetadataProviderId.cs +++ b/Jellyfin.Data/Entities/MetadataProviderId.cs @@ -103,6 +103,7 @@ namespace Jellyfin.Data.Entities GetId(ref value); return (_Id = value); } + protected set { int oldValue = _Id; @@ -141,6 +142,7 @@ namespace Jellyfin.Data.Entities GetProviderId(ref value); return (_ProviderId = value); } + set { string oldValue = _ProviderId; diff --git a/Jellyfin.Data/Entities/MovieMetadata.cs b/Jellyfin.Data/Entities/MovieMetadata.cs index 1f8f1c2a00..6c9156a2da 100644 --- a/Jellyfin.Data/Entities/MovieMetadata.cs +++ b/Jellyfin.Data/Entities/MovieMetadata.cs @@ -90,6 +90,7 @@ namespace Jellyfin.Data.Entities GetOutline(ref value); return (_Outline = value); } + set { string oldValue = _Outline; @@ -127,6 +128,7 @@ namespace Jellyfin.Data.Entities GetPlot(ref value); return (_Plot = value); } + set { string oldValue = _Plot; @@ -164,6 +166,7 @@ namespace Jellyfin.Data.Entities GetTagline(ref value); return (_Tagline = value); } + set { string oldValue = _Tagline; @@ -201,6 +204,7 @@ namespace Jellyfin.Data.Entities GetCountry(ref value); return (_Country = value); } + set { string oldValue = _Country; diff --git a/Jellyfin.Data/Entities/MusicAlbumMetadata.cs b/Jellyfin.Data/Entities/MusicAlbumMetadata.cs index 7743890a67..cf0363b7d7 100644 --- a/Jellyfin.Data/Entities/MusicAlbumMetadata.cs +++ b/Jellyfin.Data/Entities/MusicAlbumMetadata.cs @@ -90,6 +90,7 @@ namespace Jellyfin.Data.Entities GetBarcode(ref value); return (_Barcode = value); } + set { string oldValue = _Barcode; @@ -127,6 +128,7 @@ namespace Jellyfin.Data.Entities GetLabelNumber(ref value); return (_LabelNumber = value); } + set { string oldValue = _LabelNumber; @@ -164,6 +166,7 @@ namespace Jellyfin.Data.Entities GetCountry(ref value); return (_Country = value); } + set { string oldValue = _Country; diff --git a/Jellyfin.Data/Entities/Person.cs b/Jellyfin.Data/Entities/Person.cs index f714188190..9cc568e6e6 100644 --- a/Jellyfin.Data/Entities/Person.cs +++ b/Jellyfin.Data/Entities/Person.cs @@ -85,6 +85,7 @@ namespace Jellyfin.Data.Entities GetId(ref value); return (_Id = value); } + protected set { int oldValue = _Id; @@ -121,6 +122,7 @@ namespace Jellyfin.Data.Entities GetUrlId(ref value); return (_UrlId = value); } + set { Guid oldValue = _UrlId; @@ -159,6 +161,7 @@ namespace Jellyfin.Data.Entities GetName(ref value); return (_Name = value); } + set { string oldValue = _Name; @@ -196,6 +199,7 @@ namespace Jellyfin.Data.Entities GetSourceId(ref value); return (_SourceId = value); } + set { string oldValue = _SourceId; @@ -232,6 +236,7 @@ namespace Jellyfin.Data.Entities GetDateAdded(ref value); return (_DateAdded = value); } + internal set { DateTime oldValue = _DateAdded; @@ -268,6 +273,7 @@ namespace Jellyfin.Data.Entities GetDateModified(ref value); return (_DateModified = value); } + internal set { DateTime oldValue = _DateModified; diff --git a/Jellyfin.Data/Entities/PersonRole.cs b/Jellyfin.Data/Entities/PersonRole.cs index a3d0471151..e0492ea6a5 100644 --- a/Jellyfin.Data/Entities/PersonRole.cs +++ b/Jellyfin.Data/Entities/PersonRole.cs @@ -91,6 +91,7 @@ namespace Jellyfin.Data.Entities GetId(ref value); return (_Id = value); } + protected set { int oldValue = _Id; @@ -128,6 +129,7 @@ namespace Jellyfin.Data.Entities GetRole(ref value); return (_Role = value); } + set { string oldValue = _Role; @@ -164,6 +166,7 @@ namespace Jellyfin.Data.Entities GetType(ref value); return (_Type = value); } + set { Enums.PersonRoleType oldValue = _Type; diff --git a/Jellyfin.Data/Entities/Rating.cs b/Jellyfin.Data/Entities/Rating.cs index 0c8b99ca2c..490090bfa6 100644 --- a/Jellyfin.Data/Entities/Rating.cs +++ b/Jellyfin.Data/Entities/Rating.cs @@ -81,6 +81,7 @@ namespace Jellyfin.Data.Entities GetId(ref value); return (_Id = value); } + protected set { int oldValue = _Id; @@ -117,6 +118,7 @@ namespace Jellyfin.Data.Entities GetValue(ref value); return (_Value = value); } + set { double oldValue = _Value; @@ -149,6 +151,7 @@ namespace Jellyfin.Data.Entities GetVotes(ref value); return (_Votes = value); } + set { int? oldValue = _Votes; diff --git a/Jellyfin.Data/Entities/RatingSource.cs b/Jellyfin.Data/Entities/RatingSource.cs index c829042b5c..cf8af2270c 100644 --- a/Jellyfin.Data/Entities/RatingSource.cs +++ b/Jellyfin.Data/Entities/RatingSource.cs @@ -88,6 +88,7 @@ namespace Jellyfin.Data.Entities GetId(ref value); return (_Id = value); } + protected set { int oldValue = _Id; @@ -125,6 +126,7 @@ namespace Jellyfin.Data.Entities GetName(ref value); return (_Name = value); } + set { string oldValue = _Name; @@ -161,6 +163,7 @@ namespace Jellyfin.Data.Entities GetMaximumValue(ref value); return (_MaximumValue = value); } + set { double oldValue = _MaximumValue; @@ -197,6 +200,7 @@ namespace Jellyfin.Data.Entities GetMinimumValue(ref value); return (_MinimumValue = value); } + set { double oldValue = _MinimumValue; diff --git a/Jellyfin.Data/Entities/Release.cs b/Jellyfin.Data/Entities/Release.cs index 35fcbb4b76..6be524a0f9 100644 --- a/Jellyfin.Data/Entities/Release.cs +++ b/Jellyfin.Data/Entities/Release.cs @@ -113,6 +113,7 @@ namespace Jellyfin.Data.Entities GetId(ref value); return (_Id = value); } + protected set { int oldValue = _Id; @@ -151,6 +152,7 @@ namespace Jellyfin.Data.Entities GetName(ref value); return (_Name = value); } + set { string oldValue = _Name; diff --git a/Jellyfin.Data/Entities/Season.cs b/Jellyfin.Data/Entities/Season.cs index 2a861b660a..631adb12cb 100644 --- a/Jellyfin.Data/Entities/Season.cs +++ b/Jellyfin.Data/Entities/Season.cs @@ -86,6 +86,7 @@ namespace Jellyfin.Data.Entities GetSeasonNumber(ref value); return (_SeasonNumber = value); } + set { int? oldValue = _SeasonNumber; diff --git a/Jellyfin.Data/Entities/SeasonMetadata.cs b/Jellyfin.Data/Entities/SeasonMetadata.cs index 10320c6bbd..2efbf6467c 100644 --- a/Jellyfin.Data/Entities/SeasonMetadata.cs +++ b/Jellyfin.Data/Entities/SeasonMetadata.cs @@ -86,6 +86,7 @@ namespace Jellyfin.Data.Entities GetOutline(ref value); return (_Outline = value); } + set { string oldValue = _Outline; diff --git a/Jellyfin.Data/Entities/Series.cs b/Jellyfin.Data/Entities/Series.cs index cf1d6b781b..386d3a8acd 100644 --- a/Jellyfin.Data/Entities/Series.cs +++ b/Jellyfin.Data/Entities/Series.cs @@ -67,6 +67,7 @@ namespace Jellyfin.Data.Entities GetAirsDayOfWeek(ref value); return (_AirsDayOfWeek = value); } + set { DayOfWeek? oldValue = _AirsDayOfWeek; @@ -102,6 +103,7 @@ namespace Jellyfin.Data.Entities GetAirsTime(ref value); return (_AirsTime = value); } + set { DateTimeOffset? oldValue = _AirsTime; @@ -134,6 +136,7 @@ namespace Jellyfin.Data.Entities GetFirstAired(ref value); return (_FirstAired = value); } + set { DateTimeOffset? oldValue = _FirstAired; diff --git a/Jellyfin.Data/Entities/SeriesMetadata.cs b/Jellyfin.Data/Entities/SeriesMetadata.cs index bb31c2e4e8..f49c5a3b22 100644 --- a/Jellyfin.Data/Entities/SeriesMetadata.cs +++ b/Jellyfin.Data/Entities/SeriesMetadata.cs @@ -90,6 +90,7 @@ namespace Jellyfin.Data.Entities GetOutline(ref value); return (_Outline = value); } + set { string oldValue = _Outline; @@ -127,6 +128,7 @@ namespace Jellyfin.Data.Entities GetPlot(ref value); return (_Plot = value); } + set { string oldValue = _Plot; @@ -164,6 +166,7 @@ namespace Jellyfin.Data.Entities GetTagline(ref value); return (_Tagline = value); } + set { string oldValue = _Tagline; @@ -201,6 +204,7 @@ namespace Jellyfin.Data.Entities GetCountry(ref value); return (_Country = value); } + set { string oldValue = _Country; diff --git a/Jellyfin.Data/Entities/Track.cs b/Jellyfin.Data/Entities/Track.cs index c9e8fd1c3b..fc9dfb3707 100644 --- a/Jellyfin.Data/Entities/Track.cs +++ b/Jellyfin.Data/Entities/Track.cs @@ -86,6 +86,7 @@ namespace Jellyfin.Data.Entities GetTrackNumber(ref value); return (_TrackNumber = value); } + set { int? oldValue = _TrackNumber; diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index f574ebc66b..d77cd432bf 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -34,37 +34,69 @@ namespace Jellyfin.Server.Implementations public virtual DbSet<Preference> Preferences { get; set; } public virtual DbSet<User> Users { get; set; } + /*public virtual DbSet<Artwork> Artwork { get; set; } + public virtual DbSet<Book> Books { get; set; } + public virtual DbSet<BookMetadata> BookMetadata { get; set; } + public virtual DbSet<Chapter> Chapters { get; set; } + public virtual DbSet<Collection> Collections { get; set; } + public virtual DbSet<CollectionItem> CollectionItems { get; set; } + public virtual DbSet<Company> Companies { get; set; } + public virtual DbSet<CompanyMetadata> CompanyMetadata { get; set; } + public virtual DbSet<CustomItem> CustomItems { get; set; } + public virtual DbSet<CustomItemMetadata> CustomItemMetadata { get; set; } + public virtual DbSet<Episode> Episodes { get; set; } + public virtual DbSet<EpisodeMetadata> EpisodeMetadata { get; set; } + public virtual DbSet<Genre> Genres { get; set; } + public virtual DbSet<Group> Groups { get; set; } + public virtual DbSet<Library> Libraries { get; set; } + public virtual DbSet<LibraryItem> LibraryItems { get; set; } + public virtual DbSet<LibraryRoot> LibraryRoot { get; set; } + public virtual DbSet<MediaFile> MediaFiles { get; set; } + public virtual DbSet<MediaFileStream> MediaFileStream { get; set; } + public virtual DbSet<Metadata> Metadata { get; set; } + public virtual DbSet<MetadataProvider> MetadataProviders { get; set; } + public virtual DbSet<MetadataProviderId> MetadataProviderIds { get; set; } + public virtual DbSet<Movie> Movies { get; set; } + public virtual DbSet<MovieMetadata> MovieMetadata { get; set; } + public virtual DbSet<MusicAlbum> MusicAlbums { get; set; } + public virtual DbSet<MusicAlbumMetadata> MusicAlbumMetadata { get; set; } + public virtual DbSet<Person> People { get; set; } + public virtual DbSet<PersonRole> PersonRoles { get; set; } + public virtual DbSet<Photo> Photo { get; set; } + public virtual DbSet<PhotoMetadata> PhotoMetadata { get; set; } + public virtual DbSet<ProviderMapping> ProviderMappings { get; set; } + public virtual DbSet<Rating> Ratings { get; set; } /// <summary> @@ -72,12 +104,19 @@ namespace Jellyfin.Server.Implementations /// store review ratings, not age ratings /// </summary> public virtual DbSet<RatingSource> RatingSources { get; set; } + public virtual DbSet<Release> Releases { get; set; } + public virtual DbSet<Season> Seasons { get; set; } + public virtual DbSet<SeasonMetadata> SeasonMetadata { get; set; } + public virtual DbSet<Series> Series { get; set; } + public virtual DbSet<SeriesMetadata> SeriesMetadata { get; set; } + public virtual DbSet<Track> Tracks { get; set; } + public virtual DbSet<TrackMetadata> TrackMetadata { get; set; }*/ /// <inheritdoc/> diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs index fddf784651..765774dee8 100644 --- a/MediaBrowser.Api/EnvironmentService.cs +++ b/MediaBrowser.Api/EnvironmentService.cs @@ -50,6 +50,7 @@ namespace MediaBrowser.Api public string Path { get; set; } public bool ValidateWriteable { get; set; } + public bool? IsFile { get; set; } } diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs index 833a684a5d..1b736c77da 100644 --- a/MediaBrowser.Api/FilterService.cs +++ b/MediaBrowser.Api/FilterService.cs @@ -73,11 +73,17 @@ namespace MediaBrowser.Api } public bool? IsAiring { get; set; } + public bool? IsMovie { get; set; } + public bool? IsSports { get; set; } + public bool? IsKids { get; set; } + public bool? IsNews { get; set; } + public bool? IsSeries { get; set; } + public bool? Recursive { get; set; } } diff --git a/MediaBrowser.Api/IHasDtoOptions.cs b/MediaBrowser.Api/IHasDtoOptions.cs index 03d3b3692f..33d498e8bd 100644 --- a/MediaBrowser.Api/IHasDtoOptions.cs +++ b/MediaBrowser.Api/IHasDtoOptions.cs @@ -3,6 +3,7 @@ namespace MediaBrowser.Api public interface IHasDtoOptions : IHasItemFields { bool? EnableImages { get; set; } + bool? EnableUserData { get; set; } int? ImageTypeLimit { get; set; } diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index eb64abb4d4..46bc43605a 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -285,29 +285,38 @@ namespace MediaBrowser.Api.Library public class GetLibraryOptionsInfo : IReturn<LibraryOptionsResult> { public string LibraryContentType { get; set; } + public bool IsNewLibrary { get; set; } } public class LibraryOptionInfo { public string Name { get; set; } + public bool DefaultEnabled { get; set; } } public class LibraryOptionsResult { public LibraryOptionInfo[] MetadataSavers { get; set; } + public LibraryOptionInfo[] MetadataReaders { get; set; } + public LibraryOptionInfo[] SubtitleFetchers { get; set; } + public LibraryTypeOptions[] TypeOptions { get; set; } } public class LibraryTypeOptions { public string Type { get; set; } + public LibraryOptionInfo[] MetadataFetchers { get; set; } + public LibraryOptionInfo[] ImageFetchers { get; set; } + public ImageType[] SupportedImageTypes { get; set; } + public ImageOption[] DefaultImageOptions { get; set; } } @@ -1036,6 +1045,7 @@ namespace MediaBrowser.Api.Library { break; } + item = parent; } @@ -1093,6 +1103,7 @@ namespace MediaBrowser.Api.Library { break; } + item = parent; } diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index b00a5fec8c..84141e9ae4 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -200,10 +200,15 @@ namespace MediaBrowser.Api.LiveTv public bool? EnableUserData { get; set; } public bool? IsMovie { get; set; } + public bool? IsSeries { get; set; } + public bool? IsKids { get; set; } + public bool? IsSports { get; set; } + public bool? IsNews { get; set; } + public bool? IsLibraryItem { get; set; } public GetRecordings() @@ -348,6 +353,7 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "HasAired", Description = "Optional. Filter by programs that have completed airing, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool? HasAired { get; set; } + public bool? IsAiring { get; set; } [ApiMember(Name = "MaxStartDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] @@ -407,6 +413,7 @@ namespace MediaBrowser.Api.LiveTv public bool? EnableUserData { get; set; } public string SeriesTimerId { get; set; } + public Guid LibrarySeriesId { get; set; } /// <summary> @@ -601,7 +608,9 @@ namespace MediaBrowser.Api.LiveTv public class AddListingProvider : ListingsProviderInfo, IReturn<ListingsProviderInfo> { public bool ValidateLogin { get; set; } + public bool ValidateListings { get; set; } + public string Pw { get; set; } } @@ -650,15 +659,20 @@ namespace MediaBrowser.Api.LiveTv { [ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query")] public string ProviderId { get; set; } + public string TunerChannelId { get; set; } + public string ProviderChannelId { get; set; } } public class ChannelMappingOptions { public List<TunerChannelMapping> TunerChannels { get; set; } + public List<NameIdPair> ProviderChannels { get; set; } + public NameValuePair[] Mappings { get; set; } + public string ProviderName { get; set; } } @@ -666,6 +680,7 @@ namespace MediaBrowser.Api.LiveTv public class GetLiveStreamFile { public string Id { get; set; } + public string Container { get; set; } } diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 2eb6198c00..009a957b65 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -303,6 +303,7 @@ namespace MediaBrowser.Api.Playback { StartThrottler(state, transcodingJob); } + Logger.LogDebug("StartFfMpeg() finished successfully"); return transcodingJob; @@ -608,6 +609,7 @@ namespace MediaBrowser.Api.Playback { throw new ArgumentException("Invalid timeseek header"); } + int index = value.IndexOf('-'); value = index == -1 ? value.Substring(Npt.Length) @@ -639,8 +641,10 @@ namespace MediaBrowser.Api.Playback { throw new ArgumentException("Invalid timeseek header"); } + timeFactor /= 60; } + return TimeSpan.FromSeconds(secondsSum).Ticks; } diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index c2d49a93b6..5a2bf2ea3d 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -146,6 +146,7 @@ namespace MediaBrowser.Api.Playback.Hls { ApiEntryPoint.Instance.OnTranscodeEndRequest(job); } + return ResultFactory.GetResult(GetLivePlaylistText(playlist, state.SegmentLength), MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>()); } diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index c0dfcf4c13..fe5f980b18 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -234,6 +234,7 @@ namespace MediaBrowser.Api.Playback.Hls Logger.LogDebug("Starting transcoding because segmentGap is {0} and max allowed gap is {1}. requestedIndex={2}", requestedIndex - currentTranscodingIndex.Value, segmentGapRequiringTranscodingChange, requestedIndex); startTranscoding = true; } + if (startTranscoding) { // If the playlist doesn't already exist, startup ffmpeg @@ -518,6 +519,7 @@ namespace MediaBrowser.Api.Playback.Hls { Logger.LogDebug("serving {0} as it's on disk and transcoding stopped", segmentPath); } + cancellationToken.ThrowIfCancellationRequested(); } else diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index 2dc62fda7c..b7ca1a031d 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -551,10 +551,12 @@ namespace MediaBrowser.Api.Playback { mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false"; } + if (!allowAudioStreamCopy) { mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false"; } + mediaSource.TranscodingContainer = streamInfo.Container; mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol; } diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 43cde440ce..85c7e0e7d6 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -88,14 +88,17 @@ namespace MediaBrowser.Api.Playback.Progressive { return ".ts"; } + if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase)) { return ".ogv"; } + if (string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase)) { return ".webm"; } + if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase)) { return ".asf"; @@ -111,14 +114,17 @@ namespace MediaBrowser.Api.Playback.Progressive { return ".aac"; } + if (string.Equals("mp3", audioCodec, StringComparison.OrdinalIgnoreCase)) { return ".mp3"; } + if (string.Equals("vorbis", audioCodec, StringComparison.OrdinalIgnoreCase)) { return ".ogg"; } + if (string.Equals("wma", audioCodec, StringComparison.OrdinalIgnoreCase)) { return ".wma"; diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs index ffc5e15542..b70fff128b 100644 --- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs +++ b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs @@ -23,6 +23,7 @@ namespace MediaBrowser.Api.Playback.Progressive private long _bytesWritten = 0; public long StartPosition { get; set; } + public bool AllowEndOfFile = true; private readonly IDirectStreamProvider _directStreamProvider; @@ -105,6 +106,7 @@ namespace MediaBrowser.Api.Playback.Progressive { eofCount++; } + await Task.Delay(100, cancellationToken).ConfigureAwait(false); } else diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs index 9ba8eda91f..397898a7ed 100644 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ b/MediaBrowser.Api/Playback/StreamRequest.cs @@ -12,11 +12,15 @@ namespace MediaBrowser.Api.Playback public string DeviceProfileId { get; set; } public string Params { get; set; } + public string PlaySessionId { get; set; } + public string Tag { get; set; } + public string SegmentContainer { get; set; } public int? SegmentLength { get; set; } + public int? MinSegments { get; set; } } diff --git a/MediaBrowser.Api/Playback/UniversalAudioService.cs b/MediaBrowser.Api/Playback/UniversalAudioService.cs index b2d101a5b1..d5d78cf37a 100644 --- a/MediaBrowser.Api/Playback/UniversalAudioService.cs +++ b/MediaBrowser.Api/Playback/UniversalAudioService.cs @@ -37,10 +37,13 @@ namespace MediaBrowser.Api.Playback public string DeviceId { get; set; } public Guid UserId { get; set; } + public string AudioCodec { get; set; } + public string Container { get; set; } public int? MaxAudioChannels { get; set; } + public int? TranscodingAudioChannels { get; set; } public long? MaxStreamingBitrate { get; set; } @@ -49,12 +52,17 @@ namespace MediaBrowser.Api.Playback public long? StartTimeTicks { get; set; } public string TranscodingContainer { get; set; } + public string TranscodingProtocol { get; set; } + public int? MaxAudioSampleRate { get; set; } + public int? MaxAudioBitDepth { get; set; } public bool EnableRedirection { get; set; } + public bool EnableRemoteMedia { get; set; } + public bool BreakOnNonKeyFrames { get; set; } public BaseUniversalRequest() @@ -114,16 +122,27 @@ namespace MediaBrowser.Api.Playback } protected IHttpClient HttpClient { get; private set; } + protected IUserManager UserManager { get; private set; } + protected ILibraryManager LibraryManager { get; private set; } + protected IIsoManager IsoManager { get; private set; } + protected IMediaEncoder MediaEncoder { get; private set; } + protected IFileSystem FileSystem { get; private set; } + protected IDlnaManager DlnaManager { get; private set; } + protected IDeviceManager DeviceManager { get; private set; } + protected IMediaSourceManager MediaSourceManager { get; private set; } + protected IJsonSerializer JsonSerializer { get; private set; } + protected IAuthorizationContext AuthorizationContext { get; private set; } + protected INetworkManager NetworkManager { get; private set; } public Task<object> Get(GetUniversalAudioStream request) @@ -328,6 +347,7 @@ namespace MediaBrowser.Api.Playback { return await service.Head(newRequest).ConfigureAwait(false); } + return await service.Get(newRequest).ConfigureAwait(false); } else diff --git a/MediaBrowser.Api/PluginService.cs b/MediaBrowser.Api/PluginService.cs index fd10757272..1d092a5d43 100644 --- a/MediaBrowser.Api/PluginService.cs +++ b/MediaBrowser.Api/PluginService.cs @@ -115,24 +115,33 @@ namespace MediaBrowser.Api public class RegistrationInfo { public string Name { get; set; } + public DateTime ExpirationDate { get; set; } + public bool IsTrial { get; set; } + public bool IsRegistered { get; set; } } public class MBRegistrationRecord { public DateTime ExpirationDate { get; set; } + public bool IsRegistered { get; set; } + public bool RegChecked { get; set; } + public bool RegError { get; set; } + public bool TrialVersion { get; set; } + public bool IsValid { get; set; } } public class PluginSecurityInfo { public string SupporterKey { get; set; } + public bool IsMBSupporter { get; set; } } /// <summary> diff --git a/MediaBrowser.Api/SimilarItemsHelper.cs b/MediaBrowser.Api/SimilarItemsHelper.cs index 90c324ff3f..c4df0a7ed6 100644 --- a/MediaBrowser.Api/SimilarItemsHelper.cs +++ b/MediaBrowser.Api/SimilarItemsHelper.cs @@ -179,18 +179,22 @@ namespace MediaBrowser.Api { return 5; } + if (string.Equals(i.Type, PersonType.Actor, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Actor, StringComparison.OrdinalIgnoreCase)) { return 3; } + if (string.Equals(i.Type, PersonType.Composer, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Composer, StringComparison.OrdinalIgnoreCase)) { return 3; } + if (string.Equals(i.Type, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase)) { return 3; } + if (string.Equals(i.Type, PersonType.Writer, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Writer, StringComparison.OrdinalIgnoreCase)) { return 2; diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs index f2968c6b5c..a70da8e56c 100644 --- a/MediaBrowser.Api/Subtitles/SubtitleService.cs +++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs @@ -97,6 +97,7 @@ namespace MediaBrowser.Api.Subtitles [ApiMember(Name = "CopyTimestamps", Description = "CopyTimestamps", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool CopyTimestamps { get; set; } + public bool AddVttTimeMap { get; set; } } @@ -214,6 +215,7 @@ namespace MediaBrowser.Api.Subtitles { request.Format = "json"; } + if (string.IsNullOrEmpty(request.Format)) { var item = (Video)_libraryManager.GetItemById(request.Id); diff --git a/MediaBrowser.Api/SuggestionsService.cs b/MediaBrowser.Api/SuggestionsService.cs index 32d3bde5cb..17afa8e79c 100644 --- a/MediaBrowser.Api/SuggestionsService.cs +++ b/MediaBrowser.Api/SuggestionsService.cs @@ -18,10 +18,15 @@ namespace MediaBrowser.Api public class GetSuggestedItems : IReturn<QueryResult<BaseItemDto>> { public string MediaType { get; set; } + public string Type { get; set; } + public Guid UserId { get; set; } + public bool EnableTotalRecordCount { get; set; } + public int? StartIndex { get; set; } + public int? Limit { get; set; } public string[] GetMediaTypes() diff --git a/MediaBrowser.Api/TranscodingJob.cs b/MediaBrowser.Api/TranscodingJob.cs index 8c24e3ce18..bfc311a272 100644 --- a/MediaBrowser.Api/TranscodingJob.cs +++ b/MediaBrowser.Api/TranscodingJob.cs @@ -32,6 +32,7 @@ namespace MediaBrowser.Api /// </summary> /// <value>The path.</value> public MediaSourceInfo MediaSource { get; set; } + public string Path { get; set; } /// <summary> /// Gets or sets the type. @@ -43,6 +44,7 @@ namespace MediaBrowser.Api /// </summary> /// <value>The process.</value> public Process Process { get; set; } + public ILogger Logger { get; private set; } /// <summary> /// Gets or sets the active request count. @@ -62,18 +64,23 @@ namespace MediaBrowser.Api public object ProcessLock = new object(); public bool HasExited { get; set; } + public bool IsUserPaused { get; set; } public string Id { get; set; } public float? Framerate { get; set; } + public double? CompletionPercentage { get; set; } public long? BytesDownloaded { get; set; } + public long? BytesTranscoded { get; set; } + public int? BitRate { get; set; } public long? TranscodingPositionTicks { get; set; } + public long? DownloadPositionTicks { get; set; } public TranscodingThrottler TranscodingThrottler { get; set; } @@ -81,6 +88,7 @@ namespace MediaBrowser.Api private readonly object _timerLock = new object(); public DateTime LastPingDate { get; set; } + public int PingTimeout { get; set; } public TranscodingJob(ILogger logger) diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index 23062b67b7..3e8daef033 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -73,6 +73,7 @@ namespace MediaBrowser.Api [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] public bool? EnableUserData { get; set; } + public bool EnableTotalRecordCount { get; set; } public GetNextUpEpisodes() diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 4802849f48..3d9db90bd6 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -210,6 +210,7 @@ namespace MediaBrowser.Api.UserLibrary { SetItemCounts(dto, i.Item2); } + return dto; }); diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs index 7561b5c892..d9b52b10ef 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs @@ -322,8 +322,11 @@ namespace MediaBrowser.Api.UserLibrary public bool? CollapseBoxSetItems { get; set; } public int? MinWidth { get; set; } + public int? MinHeight { get; set; } + public int? MaxWidth { get; set; } + public int? MaxHeight { get; set; } /// <summary> diff --git a/MediaBrowser.Api/UserLibrary/UserViewsService.cs b/MediaBrowser.Api/UserLibrary/UserViewsService.cs index 73d5ec6dec..6f1620dddf 100644 --- a/MediaBrowser.Api/UserLibrary/UserViewsService.cs +++ b/MediaBrowser.Api/UserLibrary/UserViewsService.cs @@ -27,6 +27,7 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "IncludeExternalContent", Description = "Whether or not to include external views such as channels or live tv", IsRequired = true, DataType = "boolean", ParameterType = "query", Verb = "GET")] public bool? IncludeExternalContent { get; set; } + public bool IncludeHidden { get; set; } public string PresetViews { get; set; } @@ -80,6 +81,7 @@ namespace MediaBrowser.Api.UserLibrary { query.IncludeExternalContent = request.IncludeExternalContent.Value; } + query.IncludeHidden = request.IncludeHidden; if (!string.IsNullOrWhiteSpace(request.PresetViews)) @@ -140,6 +142,7 @@ namespace MediaBrowser.Api.UserLibrary class SpecialViewOption { public string Name { get; set; } + public string Id { get; set; } } } diff --git a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs index c0324a3841..b10233c71c 100644 --- a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs +++ b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs @@ -7,7 +7,9 @@ namespace MediaBrowser.Controller.Authentication public interface IAuthenticationProvider { string Name { get; } + bool IsEnabled { get; } + Task<ProviderAuthenticationResult> Authenticate(string username, string password); bool HasPassword(User user); Task ChangePassword(User user, string newPassword); @@ -28,6 +30,7 @@ namespace MediaBrowser.Controller.Authentication public class ProviderAuthenticationResult { public string Username { get; set; } + public string DisplayName { get; set; } } } diff --git a/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs index d9b814f694..693df80ac2 100644 --- a/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs +++ b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs @@ -8,7 +8,9 @@ namespace MediaBrowser.Controller.Authentication public interface IPasswordResetProvider { string Name { get; } + bool IsEnabled { get; } + Task<ForgotPasswordResult> StartForgotPasswordProcess(User user, bool isInNetwork); Task<PinRedeemResult> RedeemPasswordResetPin(string pin); } @@ -16,6 +18,7 @@ namespace MediaBrowser.Controller.Authentication public class PasswordPinCreationResult { public string PinFile { get; set; } + public DateTime ExpirationDate { get; set; } } } diff --git a/MediaBrowser.Controller/Channels/ChannelItemInfo.cs b/MediaBrowser.Controller/Channels/ChannelItemInfo.cs index aff68883b8..00d4d9cb3e 100644 --- a/MediaBrowser.Controller/Channels/ChannelItemInfo.cs +++ b/MediaBrowser.Controller/Channels/ChannelItemInfo.cs @@ -24,7 +24,9 @@ namespace MediaBrowser.Controller.Channels public string Overview { get; set; } public List<string> Genres { get; set; } + public List<string> Studios { get; set; } + public List<string> Tags { get; set; } public List<PersonInfo> People { get; set; } @@ -34,26 +36,33 @@ namespace MediaBrowser.Controller.Channels public long? RunTimeTicks { get; set; } public string ImageUrl { get; set; } + public string OriginalTitle { get; set; } public ChannelMediaType MediaType { get; set; } + public ChannelFolderType FolderType { get; set; } public ChannelMediaContentType ContentType { get; set; } + public ExtraType ExtraType { get; set; } + public List<TrailerType> TrailerTypes { get; set; } public Dictionary<string, string> ProviderIds { get; set; } public DateTime? PremiereDate { get; set; } + public int? ProductionYear { get; set; } public DateTime? DateCreated { get; set; } public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } public int? IndexNumber { get; set; } + public int? ParentIndexNumber { get; set; } public List<MediaSourceInfo> MediaSources { get; set; } @@ -63,7 +72,9 @@ namespace MediaBrowser.Controller.Channels public List<string> Artists { get; set; } public List<string> AlbumArtists { get; set; } + public bool IsLiveStream { get; set; } + public string Etag { get; set; } public ChannelItemInfo() diff --git a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs index 51fe4ce290..1e7549d2bc 100644 --- a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs +++ b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs @@ -15,6 +15,7 @@ namespace MediaBrowser.Controller.Collections public Dictionary<string, string> ProviderIds { get; set; } public string[] ItemIdList { get; set; } + public Guid[] UserIds { get; set; } public CollectionCreationOptions() diff --git a/MediaBrowser.Controller/Drawing/ImageHelper.cs b/MediaBrowser.Controller/Drawing/ImageHelper.cs index c87a248b58..e1273fe7f3 100644 --- a/MediaBrowser.Controller/Drawing/ImageHelper.cs +++ b/MediaBrowser.Controller/Drawing/ImageHelper.cs @@ -16,6 +16,7 @@ namespace MediaBrowser.Controller.Drawing return newSize; } + return GetSizeEstimate(options); } diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs index 870e0278e4..31d2c1bd49 100644 --- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs +++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs @@ -15,6 +15,7 @@ namespace MediaBrowser.Controller.Drawing } public Guid ItemId { get; set; } + public BaseItem Item { get; set; } public ItemImageInfo Image { get; set; } @@ -38,12 +39,15 @@ namespace MediaBrowser.Controller.Drawing public bool AddPlayedIndicator { get; set; } public int? UnplayedCount { get; set; } + public int? Blur { get; set; } public double PercentPlayed { get; set; } public string BackgroundColor { get; set; } + public string ForegroundLayer { get; set; } + public bool RequiresAutoOrientation { get; set; } private bool HasDefaultOptions(string originalImagePath) @@ -73,14 +77,17 @@ namespace MediaBrowser.Controller.Drawing { return false; } + if (Height.HasValue && !sizeValue.Height.Equals(Height.Value)) { return false; } + if (MaxWidth.HasValue && sizeValue.Width > MaxWidth.Value) { return false; } + if (MaxHeight.HasValue && sizeValue.Height > MaxHeight.Value) { return false; diff --git a/MediaBrowser.Controller/Dto/DtoOptions.cs b/MediaBrowser.Controller/Dto/DtoOptions.cs index cdaf95f5ce..cf301f1e44 100644 --- a/MediaBrowser.Controller/Dto/DtoOptions.cs +++ b/MediaBrowser.Controller/Dto/DtoOptions.cs @@ -14,11 +14,17 @@ namespace MediaBrowser.Controller.Dto }; public ItemFields[] Fields { get; set; } + public ImageType[] ImageTypes { get; set; } + public int ImageTypeLimit { get; set; } + public bool EnableImages { get; set; } + public bool AddProgramRecordingInfo { get; set; } + public bool EnableUserData { get; set; } + public bool AddCurrentProgram { get; set; } public DtoOptions() diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs index 54540e8921..fd0dc31cc4 100644 --- a/MediaBrowser.Controller/Entities/AggregateFolder.cs +++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs @@ -195,6 +195,7 @@ namespace MediaBrowser.Controller.Entities return child; } } + return null; } } diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index a8ea2157d5..7d10e68317 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -93,6 +93,7 @@ namespace MediaBrowser.Controller.Entities.Audio { songKey = ParentIndexNumber.Value.ToString("0000") + "-" + songKey; } + songKey += Name; if (!string.IsNullOrEmpty(Album)) @@ -117,6 +118,7 @@ namespace MediaBrowser.Controller.Entities.Audio { return UnratedItem.Music; } + return base.GetBlockUnratedType(); } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index f7b2f95498..c3514e0f62 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -56,6 +56,7 @@ namespace MediaBrowser.Controller.Entities.Audio { return LibraryManager.GetArtist(name, options); } + return null; } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 63db3cfab2..cbba1914a5 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -135,6 +135,7 @@ namespace MediaBrowser.Controller.Entities.Audio list.Add("Artist-" + (item.Name ?? string.Empty).RemoveDiacritics()); return list; } + public override string CreatePresentationUniqueKey() { return "Artist-" + (Name ?? string.Empty).RemoveDiacritics(); diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs index 537e9630be..61f35b9427 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs @@ -18,6 +18,7 @@ namespace MediaBrowser.Controller.Entities.Audio list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics()); return list; } + public override string CreatePresentationUniqueKey() { return GetUserDataKeys()[0]; @@ -94,6 +95,7 @@ namespace MediaBrowser.Controller.Entities.Audio Logger.LogDebug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath); return true; } + return base.RequiresRefresh(); } diff --git a/MediaBrowser.Controller/Entities/AudioBook.cs b/MediaBrowser.Controller/Entities/AudioBook.cs index 4adaf4c6e6..11ff8a2573 100644 --- a/MediaBrowser.Controller/Entities/AudioBook.cs +++ b/MediaBrowser.Controller/Entities/AudioBook.cs @@ -24,10 +24,12 @@ namespace MediaBrowser.Controller.Entities { return SeriesName; } + public string FindSeriesName() { return SeriesName; } + public string FindSeriesPresentationUniqueKey() { return SeriesPresentationUniqueKey; diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index f2de1f2b19..d356cdfd69 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -108,6 +108,7 @@ namespace MediaBrowser.Controller.Entities public string PreferredMetadataLanguage { get; set; } public long? Size { get; set; } + public string Container { get; set; } [JsonIgnore] @@ -448,6 +449,7 @@ namespace MediaBrowser.Controller.Entities // hack alert return true; } + if (SourceType == SourceType.Channel) { // hack alert @@ -559,15 +561,25 @@ namespace MediaBrowser.Controller.Entities /// The logger /// </summary> public static ILoggerFactory LoggerFactory { get; set; } + public static ILogger<BaseItem> Logger { get; set; } + public static ILibraryManager LibraryManager { get; set; } + public static IServerConfigurationManager ConfigurationManager { get; set; } + public static IProviderManager ProviderManager { get; set; } + public static ILocalizationManager LocalizationManager { get; set; } + public static IItemRepository ItemRepository { get; set; } + public static IFileSystem FileSystem { get; set; } + public static IUserDataManager UserDataManager { get; set; } + public static IChannelManager ChannelManager { get; set; } + public static IMediaSourceManager MediaSourceManager { get; set; } /// <summary> @@ -644,8 +656,10 @@ namespace MediaBrowser.Controller.Entities _sortName = CreateSortName(); } } + return _sortName; } + set => _sortName = value; } @@ -814,6 +828,7 @@ namespace MediaBrowser.Controller.Entities return item; } } + return null; } @@ -837,6 +852,7 @@ namespace MediaBrowser.Controller.Entities { return null; } + return LibraryManager.GetItemById(id); } } diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index e5adf88d12..5023c16035 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -23,7 +23,9 @@ namespace MediaBrowser.Controller.Entities public class CollectionFolder : Folder, ICollectionFolder { public static IXmlSerializer XmlSerializer { get; set; } + public static IJsonSerializer JsonSerializer { get; set; } + public static IServerApplicationHost ApplicationHost { get; set; } public CollectionFolder() @@ -155,6 +157,7 @@ namespace MediaBrowser.Controller.Entities } public string[] PhysicalLocationsList { get; set; } + public Guid[] PhysicalFolderIds { get; set; } protected override FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService) diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 3a01b43795..77551702a6 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -126,10 +126,12 @@ namespace MediaBrowser.Controller.Entities { return false; } + if (this is UserView) { return false; } + return true; } @@ -156,6 +158,7 @@ namespace MediaBrowser.Controller.Entities { item.DateCreated = DateTime.UtcNow; } + if (item.DateModified == DateTime.MinValue) { item.DateModified = DateTime.UtcNow; @@ -501,6 +504,7 @@ namespace MediaBrowser.Controller.Entities { await series.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); } + await container.RefreshAllMetadata(refreshOptions, progress, cancellationToken).ConfigureAwait(false); } @@ -939,6 +943,7 @@ namespace MediaBrowser.Controller.Entities { items = items.Where(i => string.Compare(query.NameStartsWithOrGreater, i.SortName, StringComparison.CurrentCultureIgnoreCase) < 1); } + if (!string.IsNullOrEmpty(query.NameStartsWith)) { items = items.Where(i => i.SortName.StartsWith(query.NameStartsWith, StringComparison.OrdinalIgnoreCase)); @@ -989,18 +994,22 @@ namespace MediaBrowser.Controller.Entities { return false; } + if (queryParent is Series) { return false; } + if (queryParent is Season) { return false; } + if (queryParent is MusicAlbum) { return false; } + if (queryParent is MusicArtist) { return false; @@ -1030,22 +1039,27 @@ namespace MediaBrowser.Controller.Entities { return false; } + if (request.IsFavoriteOrLiked.HasValue) { return false; } + if (request.IsLiked.HasValue) { return false; } + if (request.IsPlayed.HasValue) { return false; } + if (request.IsResumable.HasValue) { return false; } + if (request.IsFolder.HasValue) { return false; @@ -1391,6 +1405,7 @@ namespace MediaBrowser.Controller.Entities list.Add(child); } } + return list; } @@ -1413,6 +1428,7 @@ namespace MediaBrowser.Controller.Entities return true; } } + return false; } @@ -1665,22 +1681,27 @@ namespace MediaBrowser.Controller.Entities { return false; } + if (this is UserView) { return false; } + if (this is UserRootFolder) { return false; } + if (this is Channel) { return false; } + if (SourceType != SourceType.Library) { return false; } + var iItemByName = this as IItemByName; if (iItemByName != null) { diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs index 773c7df341..55634aa5e3 100644 --- a/MediaBrowser.Controller/Entities/Genre.cs +++ b/MediaBrowser.Controller/Entities/Genre.cs @@ -19,6 +19,7 @@ namespace MediaBrowser.Controller.Entities list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics()); return list; } + public override string CreatePresentationUniqueKey() { return GetUserDataKeys()[0]; @@ -92,6 +93,7 @@ namespace MediaBrowser.Controller.Entities Logger.LogDebug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath); return true; } + return base.RequiresRefresh(); } diff --git a/MediaBrowser.Controller/Entities/IHasMediaSources.cs b/MediaBrowser.Controller/Entities/IHasMediaSources.cs index 4635b90629..213c0a7946 100644 --- a/MediaBrowser.Controller/Entities/IHasMediaSources.cs +++ b/MediaBrowser.Controller/Entities/IHasMediaSources.cs @@ -13,7 +13,9 @@ namespace MediaBrowser.Controller.Entities List<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution); List<MediaStream> GetMediaStreams(); Guid Id { get; set; } + long? RunTimeTicks { get; set; } + string Path { get; } } } diff --git a/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs b/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs index 777b408287..fd1c19c977 100644 --- a/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs +++ b/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs @@ -5,13 +5,21 @@ namespace MediaBrowser.Controller.Entities public interface IHasProgramAttributes { bool IsMovie { get; set; } + bool IsSports { get; } + bool IsNews { get; } + bool IsKids { get; } + bool IsRepeat { get; set; } + bool IsSeries { get; set; } + ProgramAudio? Audio { get; set; } + string EpisodeTitle { get; set; } + string ServiceName { get; set; } } } diff --git a/MediaBrowser.Controller/Entities/IHasSeries.cs b/MediaBrowser.Controller/Entities/IHasSeries.cs index 7da53f730a..475a2ab856 100644 --- a/MediaBrowser.Controller/Entities/IHasSeries.cs +++ b/MediaBrowser.Controller/Entities/IHasSeries.cs @@ -9,11 +9,14 @@ namespace MediaBrowser.Controller.Entities /// </summary> /// <value>The name of the series.</value> string SeriesName { get; set; } + string FindSeriesName(); string FindSeriesSortName(); Guid SeriesId { get; set; } + Guid FindSeriesId(); string SeriesPresentationUniqueKey { get; set; } + string FindSeriesPresentationUniqueKey(); } } diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index 496bee857e..466cda67cb 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -21,100 +21,167 @@ namespace MediaBrowser.Controller.Entities public BaseItem SimilarTo { get; set; } public bool? IsFolder { get; set; } + public bool? IsFavorite { get; set; } + public bool? IsFavoriteOrLiked { get; set; } + public bool? IsLiked { get; set; } + public bool? IsPlayed { get; set; } + public bool? IsResumable { get; set; } + public bool? IncludeItemsByName { get; set; } public string[] MediaTypes { get; set; } + public string[] IncludeItemTypes { get; set; } + public string[] ExcludeItemTypes { get; set; } + public string[] ExcludeTags { get; set; } + public string[] ExcludeInheritedTags { get; set; } + public string[] Genres { get; set; } public bool? IsSpecialSeason { get; set; } + public bool? IsMissing { get; set; } + public bool? IsUnaired { get; set; } + public bool? CollapseBoxSetItems { get; set; } public string NameStartsWithOrGreater { get; set; } + public string NameStartsWith { get; set; } + public string NameLessThan { get; set; } + public string NameContains { get; set; } + public string MinSortName { get; set; } public string PresentationUniqueKey { get; set; } + public string Path { get; set; } + public string Name { get; set; } public string Person { get; set; } + public Guid[] PersonIds { get; set; } + public Guid[] ItemIds { get; set; } + public Guid[] ExcludeItemIds { get; set; } + public string AdjacentTo { get; set; } + public string[] PersonTypes { get; set; } public bool? Is3D { get; set; } + public bool? IsHD { get; set; } + public bool? IsLocked { get; set; } + public bool? IsPlaceHolder { get; set; } public bool? HasImdbId { get; set; } + public bool? HasOverview { get; set; } + public bool? HasTmdbId { get; set; } + public bool? HasOfficialRating { get; set; } + public bool? HasTvdbId { get; set; } + public bool? HasThemeSong { get; set; } + public bool? HasThemeVideo { get; set; } + public bool? HasSubtitles { get; set; } + public bool? HasSpecialFeature { get; set; } + public bool? HasTrailer { get; set; } + public bool? HasParentalRating { get; set; } public Guid[] StudioIds { get; set; } + public Guid[] GenreIds { get; set; } + public ImageType[] ImageTypes { get; set; } + public VideoType[] VideoTypes { get; set; } + public UnratedItem[] BlockUnratedItems { get; set; } + public int[] Years { get; set; } + public string[] Tags { get; set; } + public string[] OfficialRatings { get; set; } public DateTime? MinPremiereDate { get; set; } + public DateTime? MaxPremiereDate { get; set; } + public DateTime? MinStartDate { get; set; } + public DateTime? MaxStartDate { get; set; } + public DateTime? MinEndDate { get; set; } + public DateTime? MaxEndDate { get; set; } + public bool? IsAiring { get; set; } public bool? IsMovie { get; set; } + public bool? IsSports { get; set; } + public bool? IsKids { get; set; } + public bool? IsNews { get; set; } + public bool? IsSeries { get; set; } + public int? MinIndexNumber { get; set; } + public int? AiredDuringSeason { get; set; } + public double? MinCriticRating { get; set; } + public double? MinCommunityRating { get; set; } public Guid[] ChannelIds { get; set; } public int? ParentIndexNumber { get; set; } + public int? ParentIndexNumberNotEquals { get; set; } + public int? IndexNumber { get; set; } + public int? MinParentalRating { get; set; } + public int? MaxParentalRating { get; set; } public bool? HasDeadParentId { get; set; } + public bool? IsVirtualItem { get; set; } public Guid ParentId { get; set; } + public string ParentType { get; set; } + public Guid[] AncestorIds { get; set; } + public Guid[] TopParentIds { get; set; } public BaseItem Parent @@ -135,41 +202,65 @@ namespace MediaBrowser.Controller.Entities } public string[] PresetViews { get; set; } + public TrailerType[] TrailerTypes { get; set; } + public SourceType[] SourceTypes { get; set; } public SeriesStatus[] SeriesStatuses { get; set; } + public string ExternalSeriesId { get; set; } + public string ExternalId { get; set; } public Guid[] AlbumIds { get; set; } + public Guid[] ArtistIds { get; set; } + public Guid[] ExcludeArtistIds { get; set; } + public string AncestorWithPresentationUniqueKey { get; set; } + public string SeriesPresentationUniqueKey { get; set; } public bool GroupByPresentationUniqueKey { get; set; } + public bool GroupBySeriesPresentationUniqueKey { get; set; } + public bool EnableTotalRecordCount { get; set; } + public bool ForceDirect { get; set; } + public Dictionary<string, string> ExcludeProviderIds { get; set; } + public bool EnableGroupByMetadataKey { get; set; } + public bool? HasChapterImages { get; set; } public IReadOnlyList<(string, SortOrder)> OrderBy { get; set; } public DateTime? MinDateCreated { get; set; } + public DateTime? MinDateLastSaved { get; set; } + public DateTime? MinDateLastSavedForUser { get; set; } public DtoOptions DtoOptions { get; set; } + public int MinSimilarityScore { get; set; } + public string HasNoAudioTrackWithLanguage { get; set; } + public string HasNoInternalSubtitleTrackWithLanguage { get; set; } + public string HasNoExternalSubtitleTrackWithLanguage { get; set; } + public string HasNoSubtitleTrackWithLanguage { get; set; } + public bool? IsDeadArtist { get; set; } + public bool? IsDeadStudio { get; set; } + public bool? IsDeadPerson { get; set; } public InternalItemsQuery() @@ -240,17 +331,29 @@ namespace MediaBrowser.Controller.Entities } public Dictionary<string, string> HasAnyProviderId { get; set; } + public Guid[] AlbumArtistIds { get; set; } + public Guid[] BoxSetLibraryFolders { get; set; } + public Guid[] ContributingArtistIds { get; set; } + public bool? HasAired { get; set; } + public bool? HasOwnerId { get; set; } + public bool? Is4K { get; set; } + public int? MaxHeight { get; set; } + public int? MaxWidth { get; set; } + public int? MinHeight { get; set; } + public int? MinWidth { get; set; } + public string SearchTerm { get; set; } + public string SeriesTimerId { get; set; } } } diff --git a/MediaBrowser.Controller/Entities/LinkedChild.cs b/MediaBrowser.Controller/Entities/LinkedChild.cs index d88c31007a..cb698794bf 100644 --- a/MediaBrowser.Controller/Entities/LinkedChild.cs +++ b/MediaBrowser.Controller/Entities/LinkedChild.cs @@ -9,7 +9,9 @@ namespace MediaBrowser.Controller.Entities public class LinkedChild { public string Path { get; set; } + public LinkedChildType Type { get; set; } + public string LibraryItemId { get; set; } [JsonIgnore] @@ -63,6 +65,7 @@ namespace MediaBrowser.Controller.Entities { return _fileSystem.AreEqual(x.Path, y.Path); } + return false; } diff --git a/MediaBrowser.Controller/Entities/PeopleHelper.cs b/MediaBrowser.Controller/Entities/PeopleHelper.cs index 2fb613768d..c394957593 100644 --- a/MediaBrowser.Controller/Entities/PeopleHelper.cs +++ b/MediaBrowser.Controller/Entities/PeopleHelper.cs @@ -113,6 +113,7 @@ namespace MediaBrowser.Controller.Entities return true; } } + return false; } } diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index 9e4f9d47ed..56106a266d 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -19,6 +19,7 @@ namespace MediaBrowser.Controller.Entities list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics()); return list; } + public override string CreatePresentationUniqueKey() { return GetUserDataKeys()[0]; @@ -114,6 +115,7 @@ namespace MediaBrowser.Controller.Entities Logger.LogDebug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath); return true; } + return base.RequiresRefresh(); } diff --git a/MediaBrowser.Controller/Entities/Photo.cs b/MediaBrowser.Controller/Entities/Photo.cs index 5ebc9f16ad..82d0826c5a 100644 --- a/MediaBrowser.Controller/Entities/Photo.cs +++ b/MediaBrowser.Controller/Entities/Photo.cs @@ -29,6 +29,7 @@ namespace MediaBrowser.Controller.Entities return photoAlbum; } } + return null; } } @@ -68,17 +69,27 @@ namespace MediaBrowser.Controller.Entities } public string CameraMake { get; set; } + public string CameraModel { get; set; } + public string Software { get; set; } + public double? ExposureTime { get; set; } + public double? FocalLength { get; set; } + public ImageOrientation? Orientation { get; set; } + public double? Aperture { get; set; } + public double? ShutterSpeed { get; set; } public double? Latitude { get; set; } + public double? Longitude { get; set; } + public double? Altitude { get; set; } + public int? IsoSpeedRating { get; set; } } } diff --git a/MediaBrowser.Controller/Entities/Share.cs b/MediaBrowser.Controller/Entities/Share.cs index c17789ccc6..a51f2b4523 100644 --- a/MediaBrowser.Controller/Entities/Share.cs +++ b/MediaBrowser.Controller/Entities/Share.cs @@ -8,6 +8,7 @@ namespace MediaBrowser.Controller.Entities public class Share { public string UserId { get; set; } + public bool CanEdit { get; set; } } } diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs index 068032317d..b5ee1e9527 100644 --- a/MediaBrowser.Controller/Entities/Studio.cs +++ b/MediaBrowser.Controller/Entities/Studio.cs @@ -18,6 +18,7 @@ namespace MediaBrowser.Controller.Entities list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics()); return list; } + public override string CreatePresentationUniqueKey() { return GetUserDataKeys()[0]; @@ -93,6 +94,7 @@ namespace MediaBrowser.Controller.Entities Logger.LogDebug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath); return true; } + return base.RequiresRefresh(); } diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 4ec60e7cd3..ec95c0e669 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -34,7 +34,9 @@ namespace MediaBrowser.Controller.Entities.TV /// </summary> /// <value>The aired season.</value> public int? AirsBeforeSeasonNumber { get; set; } + public int? AirsAfterSeasonNumber { get; set; } + public int? AirsBeforeEpisodeNumber { get; set; } /// <summary> @@ -94,6 +96,7 @@ namespace MediaBrowser.Controller.Entities.TV { take--; } + list.InsertRange(0, seriesUserDataKeys.Take(take).Select(i => i + ParentIndexNumber.Value.ToString("000") + IndexNumber.Value.ToString("000"))); } @@ -114,6 +117,7 @@ namespace MediaBrowser.Controller.Entities.TV { seriesId = FindSeriesId(); } + return !seriesId.Equals(Guid.Empty) ? (LibraryManager.GetItemById(seriesId) as Series) : null; } } @@ -128,6 +132,7 @@ namespace MediaBrowser.Controller.Entities.TV { seasonId = FindSeasonId(); } + return !seasonId.Equals(Guid.Empty) ? (LibraryManager.GetItemById(seasonId) as Season) : null; } } @@ -160,6 +165,7 @@ namespace MediaBrowser.Controller.Entities.TV { return "Season " + ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture); } + return "Season Unknown"; } diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 7dfd1a7597..c96acf9ca0 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -81,6 +81,7 @@ namespace MediaBrowser.Controller.Entities.TV { seriesId = FindSeriesId(); } + return seriesId == Guid.Empty ? null : (LibraryManager.GetItemById(seriesId) as Series); } } diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index a519089b3e..315da7a3bf 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -30,6 +30,7 @@ namespace MediaBrowser.Controller.Entities.TV } public DayOfWeek[] AirDays { get; set; } + public string AirTime { get; set; } [JsonIgnore] @@ -150,6 +151,7 @@ namespace MediaBrowser.Controller.Entities.TV { query.IncludeItemTypes = new[] { typeof(Episode).Name }; } + query.IsVirtualItem = false; query.Limit = 0; var totalRecordCount = LibraryManager.GetCount(query); diff --git a/MediaBrowser.Controller/Entities/UserItemData.cs b/MediaBrowser.Controller/Entities/UserItemData.cs index ab425ee0f9..4d8db99c49 100644 --- a/MediaBrowser.Controller/Entities/UserItemData.cs +++ b/MediaBrowser.Controller/Entities/UserItemData.cs @@ -105,6 +105,7 @@ namespace MediaBrowser.Controller.Entities return null; } + set { if (value.HasValue) diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index dbfef07776..061e6001c9 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -960,6 +960,7 @@ namespace MediaBrowser.Controller.Entities .OfType<Folder>() .Where(UserView.IsEligibleForGrouping); } + return _libraryManager.GetUserRootFolder() .GetChildren(user, true) .OfType<Folder>() @@ -978,6 +979,7 @@ namespace MediaBrowser.Controller.Entities return folder != null && viewTypes.Contains(folder.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase); }).ToArray(); } + return GetMediaFolders(user) .Where(i => { diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 4cfa0e74d6..8b534f05da 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -28,7 +28,9 @@ namespace MediaBrowser.Controller.Entities public string PrimaryVersionId { get; set; } public string[] AdditionalParts { get; set; } + public string[] LocalAlternateVersions { get; set; } + public LinkedChild[] LinkedAlternateVersions { get; set; } [JsonIgnore] @@ -52,15 +54,18 @@ namespace MediaBrowser.Controller.Entities { return false; } + if (extraType.Value == Model.Entities.ExtraType.ThemeVideo) { return false; } + if (extraType.Value == Model.Entities.ExtraType.Trailer) { return false; } } + return true; } } @@ -196,6 +201,7 @@ namespace MediaBrowser.Controller.Entities return video.MediaSourceCount; } } + return LinkedAlternateVersions.Length + LocalAlternateVersions.Length + 1; } } @@ -390,11 +396,13 @@ namespace MediaBrowser.Controller.Entities AdditionalParts = newVideo.AdditionalParts; updateType |= ItemUpdateType.MetadataImport; } + if (!LocalAlternateVersions.SequenceEqual(newVideo.LocalAlternateVersions, StringComparer.Ordinal)) { LocalAlternateVersions = newVideo.LocalAlternateVersions; updateType |= ItemUpdateType.MetadataImport; } + if (VideoType != newVideo.VideoType) { VideoType = newVideo.VideoType; @@ -416,6 +424,7 @@ namespace MediaBrowser.Controller.Entities .Select(i => i.FullName) .ToArray(); } + if (videoType == VideoType.BluRay) { return FileSystem.GetFiles(rootPath, new[] { ".m2ts" }, false, true) @@ -425,6 +434,7 @@ namespace MediaBrowser.Controller.Entities .Select(i => i.FullName) .ToArray(); } + return Array.Empty<string>(); } diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs index a01ef5c316..d65b90c256 100644 --- a/MediaBrowser.Controller/Entities/Year.cs +++ b/MediaBrowser.Controller/Entities/Year.cs @@ -103,6 +103,7 @@ namespace MediaBrowser.Controller.Entities Logger.LogDebug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath); return true; } + return base.RequiresRefresh(); } diff --git a/MediaBrowser.Controller/IO/FileData.cs b/MediaBrowser.Controller/IO/FileData.cs index aa73738155..e655f50eb7 100644 --- a/MediaBrowser.Controller/IO/FileData.cs +++ b/MediaBrowser.Controller/IO/FileData.cs @@ -20,6 +20,7 @@ namespace MediaBrowser.Controller.IO { dict[file.FullName] = file; } + return dict; } @@ -49,6 +50,7 @@ namespace MediaBrowser.Controller.IO { throw new ArgumentNullException(nameof(path)); } + if (args == null) { throw new ArgumentNullException(nameof(args)); @@ -116,6 +118,7 @@ namespace MediaBrowser.Controller.IO returnResult[index] = value; index++; } + return returnResult; } } diff --git a/MediaBrowser.Controller/Library/DeleteOptions.cs b/MediaBrowser.Controller/Library/DeleteOptions.cs index 751b904816..2944d82592 100644 --- a/MediaBrowser.Controller/Library/DeleteOptions.cs +++ b/MediaBrowser.Controller/Library/DeleteOptions.cs @@ -3,6 +3,7 @@ namespace MediaBrowser.Controller.Library public class DeleteOptions { public bool DeleteFileLocation { get; set; } + public bool DeleteFromExternalProvider { get; set; } public DeleteOptions() diff --git a/MediaBrowser.Controller/Library/ILiveStream.cs b/MediaBrowser.Controller/Library/ILiveStream.cs index 734932f17c..7c9a9b20e0 100644 --- a/MediaBrowser.Controller/Library/ILiveStream.cs +++ b/MediaBrowser.Controller/Library/ILiveStream.cs @@ -9,10 +9,15 @@ namespace MediaBrowser.Controller.Library Task Open(CancellationToken openCancellationToken); Task Close(); int ConsumerCount { get; set; } + string OriginalStreamId { get; set; } + string TunerHostId { get; } + bool EnableStreamSharing { get; } + MediaSourceInfo MediaSource { get; set; } + string UniqueId { get; } } } diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs index cca85cd3b9..096708ee3f 100644 --- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs +++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs @@ -129,6 +129,7 @@ namespace MediaBrowser.Controller.Library return item != null; } + return false; } @@ -256,6 +257,7 @@ namespace MediaBrowser.Controller.Library if (args.Path == null && Path == null) return true; return args.Path != null && BaseItem.FileSystem.AreEqual(args.Path, Path); } + return false; } diff --git a/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs b/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs index b4e2051845..1ed69975ca 100644 --- a/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs +++ b/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs @@ -13,18 +13,27 @@ namespace MediaBrowser.Controller.Library public class PlaybackProgressEventArgs : EventArgs { public List<User> Users { get; set; } + public long? PlaybackPositionTicks { get; set; } + public BaseItem Item { get; set; } + public BaseItemDto MediaInfo { get; set; } + public string MediaSourceId { get; set; } + public bool IsPaused { get; set; } + public bool IsAutomated { get; set; } public string DeviceId { get; set; } + public string DeviceName { get; set; } + public string ClientName { get; set; } public string PlaySessionId { get; set; } + public SessionInfo Session { get; set; } public PlaybackProgressEventArgs() diff --git a/MediaBrowser.Controller/Library/Profiler.cs b/MediaBrowser.Controller/Library/Profiler.cs index 0febef3d3e..c4935868db 100644 --- a/MediaBrowser.Controller/Library/Profiler.cs +++ b/MediaBrowser.Controller/Library/Profiler.cs @@ -67,6 +67,7 @@ namespace MediaBrowser.Controller.Library message = string.Format("{0} took {1} seconds.", _name, ((float)_stopwatch.ElapsedMilliseconds / 1000).ToString("#0.000")); } + _logger.LogInformation(message); } } diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs index fd5fb6748f..885488851f 100644 --- a/MediaBrowser.Controller/Library/TVUtils.cs +++ b/MediaBrowser.Controller/Library/TVUtils.cs @@ -40,6 +40,7 @@ namespace MediaBrowser.Controller.Library return new DayOfWeek[] { }; } + return null; } } diff --git a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs index 70477fce73..384ca62aaa 100644 --- a/MediaBrowser.Controller/LiveTv/ChannelInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ChannelInfo.cs @@ -67,8 +67,11 @@ namespace MediaBrowser.Controller.LiveTv public bool? IsFavorite { get; set; } public bool? IsHD { get; set; } + public string AudioCodec { get; set; } + public string VideoCodec { get; set; } + public string[] Tags { get; set; } } } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index bc3bf78f01..4ac40fe88f 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -286,8 +286,11 @@ namespace MediaBrowser.Controller.LiveTv public class ActiveRecordingInfo { public string Id { get; set; } + public string Path { get; set; } + public TimerInfo Timer { get; set; } + public CancellationTokenSource CancellationTokenSource { get; set; } } } diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs index 240ba8c239..3679e4f78f 100644 --- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs +++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs @@ -50,6 +50,7 @@ namespace MediaBrowser.Controller.LiveTv get; } } + public interface IConfigurableTunerHost { /// <summary> diff --git a/MediaBrowser.Controller/LiveTv/LiveTvConflictException.cs b/MediaBrowser.Controller/LiveTv/LiveTvConflictException.cs index 92b8ee67ca..0e09d1aeba 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvConflictException.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvConflictException.cs @@ -10,6 +10,7 @@ namespace MediaBrowser.Controller.LiveTv public LiveTvConflictException() { } + public LiveTvConflictException(string message) : base(message) { diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs index 5d0f13192d..aa7ad6ff7c 100644 --- a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs @@ -199,6 +199,7 @@ namespace MediaBrowser.Controller.LiveTv public string Etag { get; set; } public Dictionary<string, string> ProviderIds { get; set; } + public Dictionary<string, string> SeriesProviderIds { get; set; } public ProgramInfo() diff --git a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs index 4fbd496c5d..6e7acaae39 100644 --- a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs @@ -57,6 +57,7 @@ namespace MediaBrowser.Controller.LiveTv public bool RecordAnyChannel { get; set; } public int KeepUpTo { get; set; } + public KeepUntil KeepUntil { get; set; } public bool SkipEpisodesInLibrary { get; set; } diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs index 46774b2b7a..df98bb6af8 100644 --- a/MediaBrowser.Controller/LiveTv/TimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs @@ -18,7 +18,9 @@ namespace MediaBrowser.Controller.LiveTv } public Dictionary<string, string> ProviderIds { get; set; } + public Dictionary<string, string> SeriesProviderIds { get; set; } + public string[] Tags { get; set; } /// <summary> @@ -146,10 +148,15 @@ namespace MediaBrowser.Controller.LiveTv public bool IsRepeat { get; set; } public string HomePageUrl { get; set; } + public float? CommunityRating { get; set; } + public string OfficialRating { get; set; } + public string[] Genres { get; set; } + public string RecordingPath { get; set; } + public KeepUntil KeepUntil { get; set; } } } diff --git a/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs b/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs index cb02da6352..df3f55c26c 100644 --- a/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs +++ b/MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs @@ -3,8 +3,11 @@ namespace MediaBrowser.Controller.LiveTv public class TunerChannelMapping { public string Name { get; set; } + public string ProviderChannelName { get; set; } + public string ProviderChannelId { get; set; } + public string Id { get; set; } } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index ec90d28f7a..4fff46f4e0 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1699,6 +1699,7 @@ namespace MediaBrowser.Controller.MediaEncoding { return (null, null); } + if (!videoHeight.HasValue && !requestedHeight.HasValue) { return (null, null); @@ -2569,8 +2570,10 @@ namespace MediaBrowser.Controller.MediaEncoding encodingOptions.HardwareDecodingCodecs = Array.Empty<string>(); return null; } + return "-c:v h264_qsv"; } + break; case "hevc": case "h265": @@ -2579,18 +2582,21 @@ namespace MediaBrowser.Controller.MediaEncoding return (isColorDepth10 && !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : "-c:v hevc_qsv"; } + break; case "mpeg2video": if (_mediaEncoder.SupportsDecoder("mpeg2_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase)) { return "-c:v mpeg2_qsv"; } + break; case "vc1": if (_mediaEncoder.SupportsDecoder("vc1_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase)) { return "-c:v vc1_qsv"; } + break; case "vp8": if (_mediaEncoder.SupportsDecoder("vp8_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("vp8", StringComparer.OrdinalIgnoreCase)) @@ -2615,8 +2621,16 @@ namespace MediaBrowser.Controller.MediaEncoding case "h264": if (_mediaEncoder.SupportsDecoder("h264_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase)) { + // cuvid decoder does not support 10-bit input. + if ((videoStream.BitDepth ?? 8) > 8) + { + encodingOptions.HardwareDecodingCodecs = Array.Empty<string>(); + return null; + } + return "-c:v h264_cuvid"; } + break; case "hevc": case "h265": @@ -2625,24 +2639,28 @@ namespace MediaBrowser.Controller.MediaEncoding return (isColorDepth10 && !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : "-c:v hevc_cuvid"; } + break; case "mpeg2video": if (_mediaEncoder.SupportsDecoder("mpeg2_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase)) { return "-c:v mpeg2_cuvid"; } + break; case "vc1": if (_mediaEncoder.SupportsDecoder("vc1_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase)) { return "-c:v vc1_cuvid"; } + break; case "mpeg4": if (_mediaEncoder.SupportsDecoder("mpeg4_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase)) { return "-c:v mpeg4_cuvid"; } + break; case "vp8": if (_mediaEncoder.SupportsDecoder("vp8_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("vp8", StringComparer.OrdinalIgnoreCase)) @@ -2669,6 +2687,7 @@ namespace MediaBrowser.Controller.MediaEncoding { return "-c:v h264_mediacodec"; } + break; case "hevc": case "h265": @@ -2677,24 +2696,28 @@ namespace MediaBrowser.Controller.MediaEncoding return (isColorDepth10 && !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : "-c:v hevc_mediacodec"; } + break; case "mpeg2video": if (_mediaEncoder.SupportsDecoder("mpeg2_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase)) { return "-c:v mpeg2_mediacodec"; } + break; case "mpeg4": if (_mediaEncoder.SupportsDecoder("mpeg4_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase)) { return "-c:v mpeg4_mediacodec"; } + break; case "vp8": if (_mediaEncoder.SupportsDecoder("vp8_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("vp8", StringComparer.OrdinalIgnoreCase)) { return "-c:v vp8_mediacodec"; } + break; case "vp9": if (_mediaEncoder.SupportsDecoder("vp9_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase)) @@ -2702,6 +2725,7 @@ namespace MediaBrowser.Controller.MediaEncoding return (isColorDepth10 && !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : "-c:v vp9_mediacodec"; } + break; } } @@ -2715,24 +2739,28 @@ namespace MediaBrowser.Controller.MediaEncoding { return "-c:v h264_mmal"; } + break; case "mpeg2video": if (_mediaEncoder.SupportsDecoder("mpeg2_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase)) { return "-c:v mpeg2_mmal"; } + break; case "mpeg4": if (_mediaEncoder.SupportsDecoder("mpeg4_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase)) { return "-c:v mpeg4_mmal"; } + break; case "vc1": if (_mediaEncoder.SupportsDecoder("vc1_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase)) { return "-c:v vc1_mmal"; } + break; } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index acf1aae895..0d6654f851 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -127,13 +127,19 @@ namespace MediaBrowser.Controller.MediaEncoding public string AlbumCoverPath { get; set; } public string InputAudioSync { get; set; } + public string InputVideoSync { get; set; } + public TransportStreamTimestamp InputTimestamp { get; set; } public MediaStream AudioStream { get; set; } + public string[] SupportedAudioCodecs { get; set; } + public string[] SupportedVideoCodecs { get; set; } + public string InputContainer { get; set; } + public IsoType? IsoType { get; set; } public BaseEncodingJobOptions BaseRequest { get; set; } @@ -293,6 +299,7 @@ namespace MediaBrowser.Controller.MediaEncoding } public bool IsVideoRequest { get; set; } + public TranscodingJobType TranscodingType { get; set; } public EncodingJobInfo(TranscodingJobType jobType) @@ -672,6 +679,7 @@ namespace MediaBrowser.Controller.MediaEncoding } public IProgress<double> Progress { get; set; } + public virtual void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate) { Progress.Report(percentComplete.Value); diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs index addc88174f..8f6fcb9ab1 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs @@ -9,9 +9,11 @@ namespace MediaBrowser.Controller.MediaEncoding public class EncodingJobOptions : BaseEncodingJobOptions { public string OutputDirectory { get; set; } + public string ItemId { get; set; } public string TempDirectory { get; set; } + public bool ReadInputAtNativeFramerate { get; set; } /// <summary> @@ -47,6 +49,7 @@ namespace MediaBrowser.Controller.MediaEncoding { SubtitleStreamIndex = info.SubtitleStreamIndex; } + StreamOptions = info.StreamOptions; } } @@ -81,7 +84,9 @@ namespace MediaBrowser.Controller.MediaEncoding public bool EnableAutoStreamCopy { get; set; } public bool AllowVideoStreamCopy { get; set; } + public bool AllowAudioStreamCopy { get; set; } + public bool BreakOnNonKeyFrames { get; set; } /// <summary> @@ -197,10 +202,15 @@ namespace MediaBrowser.Controller.MediaEncoding [ApiMember(Name = "MaxVideoBitDepth", Description = "Optional.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? MaxVideoBitDepth { get; set; } + public bool RequireAvc { get; set; } + public bool DeInterlace { get; set; } + public bool RequireNonAnamorphic { get; set; } + public int? TranscodingMaxAudioChannels { get; set; } + public int? CpuCoreLimit { get; set; } public string LiveStreamId { get; set; } diff --git a/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs b/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs index b78ef0b806..39a47792ae 100644 --- a/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs +++ b/MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs @@ -8,9 +8,13 @@ namespace MediaBrowser.Controller.MediaEncoding public class MediaInfoRequest { public MediaSourceInfo MediaSource { get; set; } + public bool ExtractChapters { get; set; } + public DlnaProfileType MediaType { get; set; } + public IIsoMount MountedIso { get; set; } + public string[] PlayableStreamFileNames { get; set; } public MediaInfoRequest() diff --git a/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs b/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs index 29fb81e32a..ba3c715b88 100644 --- a/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs +++ b/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs @@ -58,8 +58,11 @@ namespace MediaBrowser.Controller.Net public interface IAuthenticationAttributes { bool EscapeParentalControl { get; } + bool AllowBeforeStartupWizard { get; } + bool AllowLocal { get; } + bool AllowLocalOnly { get; } string[] GetRoles(); diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index df90c399b9..43016e1c9c 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -254,7 +254,9 @@ namespace MediaBrowser.Controller.Net public class WebSocketListenerState { public DateTime DateLastSendUtc { get; set; } + public long InitialDelayMs { get; set; } + public long IntervalMs { get; set; } } } diff --git a/MediaBrowser.Controller/Net/StaticResultOptions.cs b/MediaBrowser.Controller/Net/StaticResultOptions.cs index 071beaed19..85772e0368 100644 --- a/MediaBrowser.Controller/Net/StaticResultOptions.cs +++ b/MediaBrowser.Controller/Net/StaticResultOptions.cs @@ -8,8 +8,11 @@ namespace MediaBrowser.Controller.Net public class StaticResultOptions { public string ContentType { get; set; } + public TimeSpan? CacheDuration { get; set; } + public DateTime? DateLastModified { get; set; } + public Func<Task<Stream>> ContentFactory { get; set; } public bool IsHeadRequest { get; set; } @@ -17,9 +20,11 @@ namespace MediaBrowser.Controller.Net public IDictionary<string, string> ResponseHeaders { get; set; } public Action OnComplete { get; set; } + public Action OnError { get; set; } public string Path { get; set; } + public long? ContentLength { get; set; } public FileShare FileShare { get; set; } diff --git a/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs b/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs index aac41369c3..3f8c409f5c 100644 --- a/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs +++ b/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs @@ -7,11 +7,13 @@ namespace MediaBrowser.Controller.Providers public class ImageRefreshOptions { public MetadataRefreshMode ImageRefreshMode { get; set; } + public IDirectoryService DirectoryService { get; private set; } public bool ReplaceAllImages { get; set; } public ImageType[] ReplaceImages { get; set; } + public bool IsAutomated { get; set; } public ImageRefreshOptions(IDirectoryService directoryService) diff --git a/MediaBrowser.Controller/Providers/MetadataResult.cs b/MediaBrowser.Controller/Providers/MetadataResult.cs index 59adaedfa9..af955774f0 100644 --- a/MediaBrowser.Controller/Providers/MetadataResult.cs +++ b/MediaBrowser.Controller/Providers/MetadataResult.cs @@ -48,6 +48,7 @@ namespace MediaBrowser.Controller.Providers { People = new List<PersonInfo>(); } + People.Clear(); } diff --git a/MediaBrowser.Controller/Resolvers/IItemResolver.cs b/MediaBrowser.Controller/Resolvers/IItemResolver.cs index 16e37d2493..751ca8098c 100644 --- a/MediaBrowser.Controller/Resolvers/IItemResolver.cs +++ b/MediaBrowser.Controller/Resolvers/IItemResolver.cs @@ -35,6 +35,7 @@ namespace MediaBrowser.Controller.Resolvers public class MultiItemResolverResult { public List<BaseItem> Items { get; set; } + public List<FileSystemMetadata> ExtraFiles { get; set; } public MultiItemResolverResult() diff --git a/MediaBrowser.Controller/Security/AuthenticationInfo.cs b/MediaBrowser.Controller/Security/AuthenticationInfo.cs index 8282135884..1d0b959b73 100644 --- a/MediaBrowser.Controller/Security/AuthenticationInfo.cs +++ b/MediaBrowser.Controller/Security/AuthenticationInfo.cs @@ -65,6 +65,7 @@ namespace MediaBrowser.Controller.Security public DateTime? DateRevoked { get; set; } public DateTime DateLastActivity { get; set; } + public string UserName { get; set; } } } diff --git a/MediaBrowser.Controller/Session/AuthenticationRequest.cs b/MediaBrowser.Controller/Session/AuthenticationRequest.cs index a28f47a9c6..685ca3bddc 100644 --- a/MediaBrowser.Controller/Session/AuthenticationRequest.cs +++ b/MediaBrowser.Controller/Session/AuthenticationRequest.cs @@ -5,13 +5,21 @@ namespace MediaBrowser.Controller.Session public class AuthenticationRequest { public string Username { get; set; } + public Guid UserId { get; set; } + public string Password { get; set; } + public string PasswordSha1 { get; set; } + public string App { get; set; } + public string AppVersion { get; set; } + public string DeviceId { get; set; } + public string DeviceName { get; set; } + public string RemoteEndPoint { get; set; } } } diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 2ba7c9fec0..36bc11be4f 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -61,6 +61,7 @@ namespace MediaBrowser.Controller.Session { return Array.Empty<string>(); } + return Capabilities.PlayableMediaTypes; } } @@ -154,6 +155,7 @@ namespace MediaBrowser.Controller.Session return true; } } + if (controllers.Length > 0) { return false; @@ -255,6 +257,7 @@ namespace MediaBrowser.Controller.Session return true; } } + return false; } @@ -292,6 +295,7 @@ namespace MediaBrowser.Controller.Session { return; } + if (progressInfo.IsPaused) { return; @@ -334,6 +338,7 @@ namespace MediaBrowser.Controller.Session _progressTimer.Dispose(); _progressTimer = null; } + _lastProgressInfo = null; } } diff --git a/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs b/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs index b8ba35a5fe..ad6025927c 100644 --- a/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs +++ b/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs @@ -5,8 +5,11 @@ namespace MediaBrowser.Controller.Subtitles public class SubtitleResponse { public string Language { get; set; } + public string Format { get; set; } + public bool IsForced { get; set; } + public Stream Stream { get; set; } } } diff --git a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs index 61dc72258e..a202723b99 100644 --- a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs +++ b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs @@ -8,23 +8,35 @@ namespace MediaBrowser.Controller.Subtitles public class SubtitleSearchRequest : IHasProviderIds { public string Language { get; set; } + public string TwoLetterISOLanguageName { get; set; } public VideoContentType ContentType { get; set; } public string MediaPath { get; set; } + public string SeriesName { get; set; } + public string Name { get; set; } + public int? IndexNumber { get; set; } + public int? IndexNumberEnd { get; set; } + public int? ParentIndexNumber { get; set; } + public int? ProductionYear { get; set; } + public long? RuntimeTicks { get; set; } + public bool IsPerfectMatch { get; set; } + public Dictionary<string, string> ProviderIds { get; set; } public bool SearchAllProviders { get; set; } + public string[] DisabledSubtitleFetchers { get; set; } + public string[] SubtitleFetcherOrder { get; set; } public SubtitleSearchRequest() diff --git a/MediaBrowser.Controller/Sync/SyncedFileInfo.cs b/MediaBrowser.Controller/Sync/SyncedFileInfo.cs index 2ff40addb0..687a46d78f 100644 --- a/MediaBrowser.Controller/Sync/SyncedFileInfo.cs +++ b/MediaBrowser.Controller/Sync/SyncedFileInfo.cs @@ -10,6 +10,7 @@ namespace MediaBrowser.Controller.Sync /// </summary> /// <value>The path.</value> public string Path { get; set; } + public string[] PathParts { get; set; } /// <summary> /// Gets or sets the protocol. diff --git a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs index ef8df7d027..d0fac1efa3 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs @@ -122,6 +122,7 @@ namespace MediaBrowser.Controller.SyncPlay { max = Math.Max(max, session.Ping); } + return max; } diff --git a/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs index 2f4cca5ff6..9d23d80b82 100644 --- a/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs @@ -75,6 +75,7 @@ namespace MediaBrowser.LocalMetadata.Images } } } + return list; } } diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index 0ceb55c572..f954d4192f 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -24,6 +24,7 @@ namespace MediaBrowser.LocalMetadata.Parsers /// The logger /// </summary> protected ILogger<BaseItemXmlParser<T>> Logger { get; private set; } + protected IProviderManager ProviderManager { get; private set; } private Dictionary<string, string> _validProviderIds; @@ -150,6 +151,7 @@ namespace MediaBrowser.LocalMetadata.Parsers Logger.LogWarning("Invalid Added value found: " + val); } } + break; } @@ -161,6 +163,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { item.OriginalTitle = val; } + break; } @@ -191,6 +194,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { item.ForcedSortName = val; } + break; } @@ -274,6 +278,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { reader.Read(); } + break; } @@ -290,6 +295,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { reader.Read(); } + break; } @@ -302,6 +308,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { item.OfficialRating = rating; } + break; } @@ -313,6 +320,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { item.CustomRating = val; } + break; } @@ -327,6 +335,7 @@ namespace MediaBrowser.LocalMetadata.Parsers item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks; } } + break; } @@ -339,6 +348,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { hasAspectRatio.AspectRatio = val; } + break; } @@ -350,6 +360,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { item.IsLocked = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); } + break; } @@ -361,8 +372,10 @@ namespace MediaBrowser.LocalMetadata.Parsers { continue; } + item.AddStudio(name); } + break; } @@ -374,8 +387,10 @@ namespace MediaBrowser.LocalMetadata.Parsers { continue; } + itemResult.AddPerson(p); } + break; } case "Writer": @@ -386,8 +401,10 @@ namespace MediaBrowser.LocalMetadata.Parsers { continue; } + itemResult.AddPerson(p); } + break; } @@ -411,9 +428,11 @@ namespace MediaBrowser.LocalMetadata.Parsers { continue; } + itemResult.AddPerson(p); } } + break; } @@ -425,8 +444,10 @@ namespace MediaBrowser.LocalMetadata.Parsers { continue; } + itemResult.AddPerson(p); } + break; } @@ -438,6 +459,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { item.AddTrailerUrl(val); } + break; } @@ -453,6 +475,7 @@ namespace MediaBrowser.LocalMetadata.Parsers hasDisplayOrder.DisplayOrder = val; } } + break; } @@ -469,6 +492,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { reader.Read(); } + break; } @@ -501,6 +525,7 @@ namespace MediaBrowser.LocalMetadata.Parsers item.CommunityRating = val; } } + break; } @@ -544,6 +569,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { item.SetProviderId(MetadataProvider.TmdbCollection, tmdbCollection); } + break; case "Genres": @@ -559,6 +585,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { reader.Read(); } + break; } @@ -575,6 +602,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { reader.Read(); } + break; } @@ -591,6 +619,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { reader.Read(); } + break; } @@ -607,6 +636,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { reader.Read(); } + break; } @@ -627,6 +657,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { reader.Read(); } + break; } @@ -659,6 +690,7 @@ namespace MediaBrowser.LocalMetadata.Parsers video.Video3DFormat = Video3DFormat.MVC; } } + break; } @@ -682,6 +714,7 @@ namespace MediaBrowser.LocalMetadata.Parsers } } } + private void FetchFromSharesNode(XmlReader reader, IHasShares item) { var list = new List<Share>(); @@ -716,6 +749,7 @@ namespace MediaBrowser.LocalMetadata.Parsers break; } + default: { reader.Skip(); @@ -791,6 +825,7 @@ namespace MediaBrowser.LocalMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { } + break; } @@ -831,8 +866,10 @@ namespace MediaBrowser.LocalMetadata.Parsers { item.Tagline = val; } + break; } + default: reader.Skip(); break; @@ -870,6 +907,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { item.AddGenre(genre); } + break; } @@ -907,6 +945,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { tags.Add(tag); } + break; } @@ -949,6 +988,7 @@ namespace MediaBrowser.LocalMetadata.Parsers reader.Read(); continue; } + using (var subtree = reader.ReadSubtree()) { foreach (var person in GetPersonsFromXmlNode(subtree)) @@ -957,9 +997,11 @@ namespace MediaBrowser.LocalMetadata.Parsers { continue; } + item.AddPerson(person); } } + break; } @@ -995,6 +1037,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { item.AddTrailerUrl(val); } + break; } @@ -1035,6 +1078,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { item.AddStudio(studio); } + break; } @@ -1084,6 +1128,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { type = val; } + break; } @@ -1095,6 +1140,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { role = val; } + break; } case "SortOrder": @@ -1108,6 +1154,7 @@ namespace MediaBrowser.LocalMetadata.Parsers sortOrder = intVal; } } + break; } @@ -1206,6 +1253,7 @@ namespace MediaBrowser.LocalMetadata.Parsers item.CanEdit = string.Equals(reader.ReadElementContentAsString(), "true", StringComparison.OrdinalIgnoreCase); break; } + default: { reader.Skip(); diff --git a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs index ca11a079d2..dd4eefa500 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs @@ -26,6 +26,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { reader.Read(); } + break; default: @@ -69,6 +70,7 @@ namespace MediaBrowser.LocalMetadata.Parsers break; } + default: { reader.Skip(); diff --git a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs index 54710cd825..941ed1b95c 100644 --- a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs @@ -35,6 +35,7 @@ namespace MediaBrowser.LocalMetadata.Parsers { reader.Read(); } + break; default: @@ -77,6 +78,7 @@ namespace MediaBrowser.LocalMetadata.Parsers break; } + default: { reader.Skip(); diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index 071902393f..70fd63ff32 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs @@ -32,10 +32,15 @@ namespace MediaBrowser.LocalMetadata.Savers } protected IFileSystem FileSystem { get; private set; } + protected IServerConfigurationManager ConfigurationManager { get; private set; } + protected ILibraryManager LibraryManager { get; private set; } + protected IUserManager UserManager { get; private set; } + protected IUserDataManager UserDataManager { get; private set; } + protected ILogger<BaseXmlSaver> Logger { get; private set; } public string Name => XmlProviderUtils.Name; @@ -185,6 +190,7 @@ namespace MediaBrowser.LocalMetadata.Savers { writer.WriteElementString("OriginalTitle", item.OriginalTitle); } + if (!string.IsNullOrEmpty(item.CustomRating)) { writer.WriteElementString("CustomRating", item.CustomRating); @@ -278,6 +284,7 @@ namespace MediaBrowser.LocalMetadata.Savers { writer.WriteElementString("Language", item.PreferredMetadataLanguage); } + if (!string.IsNullOrEmpty(item.PreferredMetadataCountryCode)) { writer.WriteElementString("CountryCode", item.PreferredMetadataCountryCode); diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 82f6ce15ec..a8df27d38b 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -229,6 +229,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { return inJellyfinPath; } + var values = Environment.GetEnvironmentVariable("PATH"); foreach (var path in values.Split(Path.PathSeparator)) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 7d57a691e6..918694e66e 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -93,6 +93,7 @@ namespace MediaBrowser.MediaEncoding.Probing { overview = FFProbeHelpers.GetDictionaryValue(tags, "description"); } + if (string.IsNullOrWhiteSpace(overview)) { overview = FFProbeHelpers.GetDictionaryValue(tags, "desc"); @@ -274,10 +275,12 @@ namespace MediaBrowser.MediaEncoding.Probing reader.Read(); continue; } + using (var subtree = reader.ReadSubtree()) { ReadFromDictNode(subtree, info); } + break; default: reader.Skip(); @@ -319,6 +322,7 @@ namespace MediaBrowser.MediaEncoding.Probing { ProcessPairs(currentKey, pairs, info); } + currentKey = reader.ReadElementContentAsString(); pairs = new List<NameValuePair>(); break; @@ -332,6 +336,7 @@ namespace MediaBrowser.MediaEncoding.Probing Value = value }); } + break; case "array": if (reader.IsEmptyElement) @@ -339,6 +344,7 @@ namespace MediaBrowser.MediaEncoding.Probing reader.Read(); continue; } + using (var subtree = reader.ReadSubtree()) { if (!string.IsNullOrWhiteSpace(currentKey)) @@ -346,6 +352,7 @@ namespace MediaBrowser.MediaEncoding.Probing pairs.AddRange(ReadValueArray(subtree)); } } + break; default: reader.Skip(); @@ -381,6 +388,7 @@ namespace MediaBrowser.MediaEncoding.Probing reader.Read(); continue; } + using (var subtree = reader.ReadSubtree()) { var dict = GetNameValuePair(subtree); @@ -389,6 +397,7 @@ namespace MediaBrowser.MediaEncoding.Probing pairs.Add(dict); } } + break; default: reader.Skip(); @@ -948,6 +957,7 @@ namespace MediaBrowser.MediaEncoding.Probing { peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Composer }); } + audio.People = peoples.ToArray(); } @@ -979,6 +989,7 @@ namespace MediaBrowser.MediaEncoding.Probing { peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Writer }); } + audio.People = peoples.ToArray(); } @@ -1012,6 +1023,7 @@ namespace MediaBrowser.MediaEncoding.Probing { albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album artist"); } + if (string.IsNullOrWhiteSpace(albumArtist)) { albumArtist = FFProbeHelpers.GetDictionaryValue(tags, "album_artist"); @@ -1175,6 +1187,7 @@ namespace MediaBrowser.MediaEncoding.Probing { continue; } + if (info.AlbumArtists.Contains(studio, StringComparer.OrdinalIgnoreCase)) { continue; diff --git a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs index f44cf14523..0e2d70017c 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs @@ -23,6 +23,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles string line; while (reader.ReadLine() != "[Events]") { } + var headers = ParseFieldHeaders(reader.ReadLine()); while ((line = reader.ReadLine()) != null) @@ -56,6 +57,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles trackEvents.Add(subEvent); } } + trackInfo.TrackEvents = trackEvents.ToArray(); return trackInfo; } @@ -112,11 +114,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles { pre = s.Substring(0, 5) + "}"; } + int indexOfEnd = p.Text.IndexOf('}'); p.Text = p.Text.Remove(indexOfBegin, (indexOfEnd - indexOfBegin) + 1); indexOfBegin = p.Text.IndexOf('{'); } + p.Text = pre + p.Text; } } diff --git a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs index c98dd15024..a8d383a2ad 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs @@ -35,6 +35,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles { continue; } + var subEvent = new SubtitleTrackEvent { Id = line }; line = reader.ReadLine(); @@ -52,6 +53,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles _logger.LogWarning("Unrecognized line in srt: {0}", line); continue; } + subEvent.StartPositionTicks = GetTicks(time[0]); var endTime = time[1]; var idx = endTime.IndexOf(" ", StringComparison.Ordinal); @@ -65,8 +67,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles { break; } + multiline.Add(line); } + subEvent.Text = string.Join(ParserValues.NewLine, multiline); subEvent.Text = subEvent.Text.Replace(@"\N", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase); subEvent.Text = Regex.Replace(subEvent.Text, @"\{(?:\\\d?[\w.-]+(?:\([^\)]*\)|&H?[0-9A-Fa-f]+&|))+\}", string.Empty, RegexOptions.IgnoreCase); @@ -76,6 +80,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles trackEvents.Add(subEvent); } } + trackInfo.TrackEvents = trackEvents.ToArray(); return trackInfo; } diff --git a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs index bae2f54176..9a8fcc4316 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs @@ -135,6 +135,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles // subtitle.Renumber(1); } + trackInfo.TrackEvents = trackEvents.ToArray(); return trackInfo; } @@ -302,6 +303,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles return count; index = text.IndexOf(tag, index + 1); } + return count; } @@ -329,6 +331,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles { rest = string.Empty; } + extraTags += " size=\"" + fontSize.Substring(2) + "\""; } else if (rest.StartsWith("fn") && rest.Length > 2) @@ -344,6 +347,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles { rest = string.Empty; } + extraTags += " face=\"" + fontName.Substring(2) + "\""; } else if (rest.StartsWith("c") && rest.Length > 2) diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index e35a626d83..b9d526a223 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -115,6 +115,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles { throw new ArgumentNullException(nameof(item)); } + if (string.IsNullOrWhiteSpace(mediaSourceId)) { throw new ArgumentNullException(nameof(mediaSourceId)); @@ -271,8 +272,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles } public string Path { get; set; } + public MediaProtocol Protocol { get; set; } + public string Format { get; set; } + public bool IsExternal { get; set; } } @@ -287,10 +291,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles { return new SrtParser(_logger); } + if (string.Equals(format, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase)) { return new SsaParser(); } + if (string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase)) { return new AssParser(); @@ -315,14 +321,17 @@ namespace MediaBrowser.MediaEncoding.Subtitles { return new JsonWriter(); } + if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase)) { return new SrtWriter(); } + if (string.Equals(format, SubtitleFormat.VTT, StringComparison.OrdinalIgnoreCase)) { return new VttWriter(); } + if (string.Equals(format, SubtitleFormat.TTML, StringComparison.OrdinalIgnoreCase)) { return new TtmlWriter(); diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index 4229a4335b..890469d361 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -10,17 +10,27 @@ namespace MediaBrowser.Model.Configuration public class LibraryOptions { public bool EnablePhotos { get; set; } + public bool EnableRealtimeMonitor { get; set; } + public bool EnableChapterImageExtraction { get; set; } + public bool ExtractChapterImagesDuringLibraryScan { get; set; } + public bool DownloadImagesInAdvance { get; set; } + public MediaPathInfo[] PathInfos { get; set; } public bool SaveLocalMetadata { get; set; } + public bool EnableInternetProviders { get; set; } + public bool ImportMissingEpisodes { get; set; } + public bool EnableAutomaticSeriesGrouping { get; set; } + public bool EnableEmbeddedTitles { get; set; } + public bool EnableEmbeddedEpisodeInfos { get; set; } public int AutomaticRefreshIntervalDays { get; set; } @@ -38,17 +48,25 @@ namespace MediaBrowser.Model.Configuration public string MetadataCountryCode { get; set; } public string SeasonZeroDisplayName { get; set; } + public string[] MetadataSavers { get; set; } + public string[] DisabledLocalMetadataReaders { get; set; } + public string[] LocalMetadataReaderOrder { get; set; } public string[] DisabledSubtitleFetchers { get; set; } + public string[] SubtitleFetcherOrder { get; set; } public bool SkipSubtitlesIfEmbeddedSubtitlesPresent { get; set; } + public bool SkipSubtitlesIfAudioTrackMatches { get; set; } + public string[] SubtitleDownloadLanguages { get; set; } + public bool RequirePerfectSubtitleMatch { get; set; } + public bool SaveSubtitlesWithMedia { get; set; } public TypeOptions[] TypeOptions { get; set; } @@ -89,17 +107,22 @@ namespace MediaBrowser.Model.Configuration public class MediaPathInfo { public string Path { get; set; } + public string NetworkPath { get; set; } } public class TypeOptions { public string Type { get; set; } + public string[] MetadataFetchers { get; set; } + public string[] MetadataFetcherOrder { get; set; } public string[] ImageFetchers { get; set; } + public string[] ImageFetcherOrder { get; set; } + public ImageOption[] ImageOptions { get; set; } public ImageOption GetImageOptions(ImageType type) diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs index 85d864eec4..a5179f3ca3 100644 --- a/MediaBrowser.Model/Configuration/UserConfiguration.cs +++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs @@ -34,6 +34,7 @@ namespace MediaBrowser.Model.Configuration public string[] GroupedFolders { get; set; } public SubtitlePlaybackMode SubtitleMode { get; set; } + public bool DisplayCollectionsView { get; set; } public bool EnableLocalPassword { get; set; } @@ -41,12 +42,15 @@ namespace MediaBrowser.Model.Configuration public string[] OrderedViews { get; set; } public string[] LatestItemsExcludes { get; set; } + public string[] MyMediaExcludes { get; set; } public bool HidePlayedInLatest { get; set; } public bool RememberAudioSelections { get; set; } + public bool RememberSubtitleSelections { get; set; } + public bool EnableNextEpisodeAutoPlay { get; set; } /// <summary> diff --git a/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs b/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs index c48a381928..4d5f996f84 100644 --- a/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs +++ b/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs @@ -10,6 +10,7 @@ namespace MediaBrowser.Model.Configuration public string ReleaseDateFormat { get; set; } public bool SaveImagePathsInNfo { get; set; } + public bool EnablePathSubstitution { get; set; } public bool EnableExtraThumbsDuplication { get; set; } diff --git a/MediaBrowser.Model/Dlna/AudioOptions.cs b/MediaBrowser.Model/Dlna/AudioOptions.cs index fc555c5f70..1468b0414f 100644 --- a/MediaBrowser.Model/Dlna/AudioOptions.cs +++ b/MediaBrowser.Model/Dlna/AudioOptions.cs @@ -85,6 +85,7 @@ namespace MediaBrowser.Model.Dlna { return Profile.MaxStaticMusicBitrate; } + return Profile.MaxStaticBitrate; } diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs index 6462ffdc13..7e921b1fdf 100644 --- a/MediaBrowser.Model/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs @@ -27,16 +27,25 @@ namespace MediaBrowser.Model.Dlna public DeviceIdentification Identification { get; set; } public string FriendlyName { get; set; } + public string Manufacturer { get; set; } + public string ManufacturerUrl { get; set; } + public string ModelName { get; set; } + public string ModelDescription { get; set; } + public string ModelNumber { get; set; } + public string ModelUrl { get; set; } + public string SerialNumber { get; set; } public bool EnableAlbumArtInDidl { get; set; } + public bool EnableSingleAlbumArtLimit { get; set; } + public bool EnableSingleSubtitleLimit { get; set; } public string SupportedMediaTypes { get; set; } @@ -46,15 +55,19 @@ namespace MediaBrowser.Model.Dlna public string AlbumArtPn { get; set; } public int MaxAlbumArtWidth { get; set; } + public int MaxAlbumArtHeight { get; set; } public int? MaxIconWidth { get; set; } + public int? MaxIconHeight { get; set; } public long? MaxStreamingBitrate { get; set; } + public long? MaxStaticBitrate { get; set; } public int? MusicStreamingTranscodingBitrate { get; set; } + public int? MaxStaticMusicBitrate { get; set; } /// <summary> @@ -65,10 +78,13 @@ namespace MediaBrowser.Model.Dlna public string ProtocolInfo { get; set; } public int TimelineOffsetSeconds { get; set; } + public bool RequiresPlainVideoItems { get; set; } + public bool RequiresPlainFolders { get; set; } public bool EnableMSMediaReceiverRegistrar { get; set; } + public bool IgnoreTranscodeByteRangeRequests { get; set; } public XmlAttribute[] XmlRootAttributes { get; set; } @@ -88,6 +104,7 @@ namespace MediaBrowser.Model.Dlna public ContainerProfile[] ContainerProfiles { get; set; } public CodecProfile[] CodecProfiles { get; set; } + public ResponseProfile[] ResponseProfiles { get; set; } public SubtitleProfile[] SubtitleProfiles { get; set; } @@ -169,6 +186,7 @@ namespace MediaBrowser.Model.Dlna return i; } + return null; } @@ -209,6 +227,7 @@ namespace MediaBrowser.Model.Dlna return i; } + return null; } @@ -254,6 +273,7 @@ namespace MediaBrowser.Model.Dlna return i; } + return null; } @@ -318,6 +338,7 @@ namespace MediaBrowser.Model.Dlna return i; } + return null; } } diff --git a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs index c03a8060f6..47cc892107 100644 --- a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs +++ b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs @@ -107,6 +107,7 @@ namespace MediaBrowser.Model.Dlna return list.ToArray(); } + if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)) { if (string.Equals(audioCodec, "lpcm", StringComparison.OrdinalIgnoreCase)) @@ -150,8 +151,10 @@ namespace MediaBrowser.Model.Dlna { return new MediaFormatProfile[] { MediaFormatProfile.VC1_TS_AP_L2_AC3_ISO }; } + return new MediaFormatProfile[] { MediaFormatProfile.VC1_TS_AP_L1_AC3_ISO }; } + if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase)) { suffix = string.Equals(suffix, "_ISO", StringComparison.OrdinalIgnoreCase) ? suffix : "_T"; @@ -190,10 +193,12 @@ namespace MediaBrowser.Model.Dlna { return MediaFormatProfile.AVC_MP4_MP_SD_AC3; } + if (string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)) { return MediaFormatProfile.AVC_MP4_MP_SD_MPEG1_L3; } + if (width.HasValue && height.HasValue) { if ((width.Value <= 720) && (height.Value <= 576)) @@ -277,6 +282,7 @@ namespace MediaBrowser.Model.Dlna { return MediaFormatProfile.WMVMED_FULL; } + return MediaFormatProfile.WMVMED_PRO; } } @@ -285,6 +291,7 @@ namespace MediaBrowser.Model.Dlna { return MediaFormatProfile.WMVHIGH_FULL; } + return MediaFormatProfile.WMVHIGH_PRO; } @@ -342,6 +349,7 @@ namespace MediaBrowser.Model.Dlna { return MediaFormatProfile.WMA_BASE; } + return MediaFormatProfile.WMA_FULL; } @@ -353,14 +361,17 @@ namespace MediaBrowser.Model.Dlna { return MediaFormatProfile.LPCM16_44_MONO; } + if (frequency.Value == 44100 && channels.Value == 2) { return MediaFormatProfile.LPCM16_44_STEREO; } + if (frequency.Value == 48000 && channels.Value == 1) { return MediaFormatProfile.LPCM16_48_MONO; } + if (frequency.Value == 48000 && channels.Value == 2) { return MediaFormatProfile.LPCM16_48_STEREO; @@ -378,6 +389,7 @@ namespace MediaBrowser.Model.Dlna { return MediaFormatProfile.AAC_ISO_320; } + return MediaFormatProfile.AAC_ISO; } @@ -387,6 +399,7 @@ namespace MediaBrowser.Model.Dlna { return MediaFormatProfile.AAC_ADTS_320; } + return MediaFormatProfile.AAC_ADTS; } diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 3fe5cf7741..06bd244763 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -627,10 +627,12 @@ namespace MediaBrowser.Model.Dlna { playlistItem.MinSegments = transcodingProfile.MinSegments; } + if (transcodingProfile.SegmentLength > 0) { playlistItem.SegmentLength = transcodingProfile.SegmentLength; } + playlistItem.SubProtocol = transcodingProfile.Protocol; if (!string.IsNullOrEmpty(transcodingProfile.MaxAudioChannels) @@ -947,6 +949,7 @@ namespace MediaBrowser.Model.Dlna { return (PlayMethod.DirectPlay, new List<TranscodeReason>()); } + if (options.ForceDirectStream) { return (PlayMethod.DirectStream, new List<TranscodeReason>()); @@ -1261,6 +1264,7 @@ namespace MediaBrowser.Model.Dlna return true; } } + return false; } @@ -1363,14 +1367,17 @@ namespace MediaBrowser.Model.Dlna { throw new ArgumentException("ItemId is required"); } + if (string.IsNullOrEmpty(options.DeviceId)) { throw new ArgumentException("DeviceId is required"); } + if (options.Profile == null) { throw new ArgumentException("Profile is required"); } + if (options.MediaSources == null) { throw new ArgumentException("MediaSources is required"); @@ -1418,6 +1425,7 @@ namespace MediaBrowser.Model.Dlna item.AudioBitrate = Math.Max(num, item.AudioBitrate ?? num); } } + break; } case ProfileConditionValue.AudioChannels: @@ -1452,6 +1460,7 @@ namespace MediaBrowser.Model.Dlna item.SetOption(qualifier, "audiochannels", Math.Max(num, item.GetTargetAudioChannels(qualifier) ?? num).ToString(CultureInfo.InvariantCulture)); } } + break; } case ProfileConditionValue.IsAvc: @@ -1472,6 +1481,7 @@ namespace MediaBrowser.Model.Dlna item.RequireAvc = true; } } + break; } case ProfileConditionValue.IsAnamorphic: @@ -1492,6 +1502,7 @@ namespace MediaBrowser.Model.Dlna item.RequireNonAnamorphic = true; } } + break; } case ProfileConditionValue.IsInterlaced: @@ -1522,6 +1533,7 @@ namespace MediaBrowser.Model.Dlna item.SetOption(qualifier, "deinterlace", "true"); } } + break; } case ProfileConditionValue.AudioProfile: @@ -1567,6 +1579,7 @@ namespace MediaBrowser.Model.Dlna item.SetOption(qualifier, "maxrefframes", Math.Max(num, item.GetTargetRefFrames(qualifier) ?? num).ToString(CultureInfo.InvariantCulture)); } } + break; } case ProfileConditionValue.VideoBitDepth: @@ -1601,6 +1614,7 @@ namespace MediaBrowser.Model.Dlna item.SetOption(qualifier, "videobitdepth", Math.Max(num, item.GetTargetVideoBitDepth(qualifier) ?? num).ToString(CultureInfo.InvariantCulture)); } } + break; } case ProfileConditionValue.VideoProfile: @@ -1623,6 +1637,7 @@ namespace MediaBrowser.Model.Dlna item.SetOption(qualifier, "profile", string.Join(",", values)); } } + break; } case ProfileConditionValue.Height: @@ -1647,6 +1662,7 @@ namespace MediaBrowser.Model.Dlna item.MaxHeight = Math.Max(num, item.MaxHeight ?? num); } } + break; } case ProfileConditionValue.VideoBitrate: @@ -1671,6 +1687,7 @@ namespace MediaBrowser.Model.Dlna item.VideoBitrate = Math.Max(num, item.VideoBitrate ?? num); } } + break; } case ProfileConditionValue.VideoFramerate: @@ -1695,6 +1712,7 @@ namespace MediaBrowser.Model.Dlna item.MaxFramerate = Math.Max(num, item.MaxFramerate ?? num); } } + break; } case ProfileConditionValue.VideoLevel: @@ -1719,6 +1737,7 @@ namespace MediaBrowser.Model.Dlna item.SetOption(qualifier, "level", Math.Max(num, item.GetTargetVideoLevel(qualifier) ?? num).ToString(CultureInfo.InvariantCulture)); } } + break; } case ProfileConditionValue.Width: @@ -1743,8 +1762,10 @@ namespace MediaBrowser.Model.Dlna item.MaxWidth = Math.Max(num, item.MaxWidth ?? num); } } + break; } + default: break; } diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 2444638030..9e9d0b7e11 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -69,6 +69,7 @@ namespace MediaBrowser.Model.Dlna public Guid ItemId { get; set; } public PlayMethod PlayMethod { get; set; } + public EncodingContext Context { get; set; } public DlnaProfileType MediaType { get; set; } @@ -80,15 +81,23 @@ namespace MediaBrowser.Model.Dlna public long StartPositionTicks { get; set; } public int? SegmentLength { get; set; } + public int? MinSegments { get; set; } + public bool BreakOnNonKeyFrames { get; set; } public bool RequireAvc { get; set; } + public bool RequireNonAnamorphic { get; set; } + public bool CopyTimestamps { get; set; } + public bool EnableMpegtsM2TsMode { get; set; } + public bool EnableSubtitlesInManifest { get; set; } + public string[] AudioCodecs { get; set; } + public string[] VideoCodecs { get; set; } public int? AudioStreamIndex { get; set; } @@ -96,6 +105,7 @@ namespace MediaBrowser.Model.Dlna public int? SubtitleStreamIndex { get; set; } public int? TranscodingMaxAudioChannels { get; set; } + public int? GlobalMaxAudioChannels { get; set; } public int? AudioBitrate { get; set; } @@ -103,12 +113,15 @@ namespace MediaBrowser.Model.Dlna public int? VideoBitrate { get; set; } public int? MaxWidth { get; set; } + public int? MaxHeight { get; set; } public float? MaxFramerate { get; set; } public DeviceProfile DeviceProfile { get; set; } + public string DeviceProfileId { get; set; } + public string DeviceId { get; set; } public long? RunTimeTicks { get; set; } @@ -120,10 +133,13 @@ namespace MediaBrowser.Model.Dlna public MediaSourceInfo MediaSource { get; set; } public string[] SubtitleCodecs { get; set; } + public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; } + public string SubtitleFormat { get; set; } public string PlaySessionId { get; set; } + public TranscodeReason[] TranscodeReasons { get; set; } public Dictionary<string, string> StreamOptions { get; private set; } @@ -160,11 +176,13 @@ namespace MediaBrowser.Model.Dlna { continue; } + if (string.Equals(pair.Name, "SubtitleStreamIndex", StringComparison.OrdinalIgnoreCase) && string.Equals(pair.Value, "-1", StringComparison.OrdinalIgnoreCase)) { continue; } + if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase) && string.Equals(pair.Value, "false", StringComparison.OrdinalIgnoreCase)) { @@ -993,6 +1011,7 @@ namespace MediaBrowser.Model.Dlna { return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue); } + return GetMediaStreamCount(MediaStreamType.Video, 1); } } @@ -1005,6 +1024,7 @@ namespace MediaBrowser.Model.Dlna { return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue); } + return GetMediaStreamCount(MediaStreamType.Audio, 1); } } diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index c7f8f05844..62005b901d 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -62,17 +62,23 @@ namespace MediaBrowser.Model.Dto public DateTime? DateCreated { get; set; } public DateTime? DateLastMediaAdded { get; set; } + public string ExtraType { get; set; } public int? AirsBeforeSeasonNumber { get; set; } + public int? AirsAfterSeasonNumber { get; set; } + public int? AirsBeforeEpisodeNumber { get; set; } + public bool? CanDelete { get; set; } + public bool? CanDownload { get; set; } public bool? HasSubtitles { get; set; } public string PreferredMetadataLanguage { get; set; } + public string PreferredMetadataCountryCode { get; set; } /// <summary> @@ -87,6 +93,7 @@ namespace MediaBrowser.Model.Dto /// </summary> /// <value>The name of the sort.</value> public string SortName { get; set; } + public string ForcedSortName { get; set; } /// <summary> @@ -146,6 +153,7 @@ namespace MediaBrowser.Model.Dto /// </summary> /// <value>The channel identifier.</value> public Guid ChannelId { get; set; } + public string ChannelName { get; set; } /// <summary> @@ -213,6 +221,7 @@ namespace MediaBrowser.Model.Dto /// </summary> /// <value>The number.</value> public string Number { get; set; } + public string ChannelNumber { get; set; } /// <summary> @@ -467,6 +476,7 @@ namespace MediaBrowser.Model.Dto /// </summary> /// <value>The part count.</value> public int? PartCount { get; set; } + public int? MediaSourceCount { get; set; } /// <summary> @@ -599,6 +609,7 @@ namespace MediaBrowser.Model.Dto /// </summary> /// <value>The series count.</value> public int? SeriesCount { get; set; } + public int? ProgramCount { get; set; } /// <summary> /// Gets or sets the episode count. @@ -615,6 +626,7 @@ namespace MediaBrowser.Model.Dto /// </summary> /// <value>The album count.</value> public int? AlbumCount { get; set; } + public int? ArtistCount { get; set; } /// <summary> /// Gets or sets the music video count. @@ -629,18 +641,31 @@ namespace MediaBrowser.Model.Dto public bool? LockData { get; set; } public int? Width { get; set; } + public int? Height { get; set; } + public string CameraMake { get; set; } + public string CameraModel { get; set; } + public string Software { get; set; } + public double? ExposureTime { get; set; } + public double? FocalLength { get; set; } + public ImageOrientation? ImageOrientation { get; set; } + public double? Aperture { get; set; } + public double? ShutterSpeed { get; set; } + public double? Latitude { get; set; } + public double? Longitude { get; set; } + public double? Altitude { get; set; } + public int? IsoSpeedRating { get; set; } /// <summary> diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index 74c2cb4f41..0c9e11f8f4 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -13,16 +13,19 @@ namespace MediaBrowser.Model.Dto public class MediaSourceInfo { public MediaProtocol Protocol { get; set; } + public string Id { get; set; } public string Path { get; set; } public string EncoderPath { get; set; } + public MediaProtocol? EncoderProtocol { get; set; } public MediaSourceType Type { get; set; } public string Container { get; set; } + public long? Size { get; set; } public string Name { get; set; } @@ -33,19 +36,33 @@ namespace MediaBrowser.Model.Dto public bool IsRemote { get; set; } public string ETag { get; set; } + public long? RunTimeTicks { get; set; } + public bool ReadAtNativeFramerate { get; set; } + public bool IgnoreDts { get; set; } + public bool IgnoreIndex { get; set; } + public bool GenPtsInput { get; set; } + public bool SupportsTranscoding { get; set; } + public bool SupportsDirectStream { get; set; } + public bool SupportsDirectPlay { get; set; } + public bool IsInfiniteStream { get; set; } + public bool RequiresOpening { get; set; } + public string OpenToken { get; set; } + public bool RequiresClosing { get; set; } + public string LiveStreamId { get; set; } + public int? BufferMs { get; set; } public bool RequiresLooping { get; set; } @@ -67,10 +84,13 @@ namespace MediaBrowser.Model.Dto public int? Bitrate { get; set; } public TransportStreamTimestamp? Timestamp { get; set; } + public Dictionary<string, string> RequiredHttpHeaders { get; set; } public string TranscodingUrl { get; set; } + public string TranscodingSubProtocol { get; set; } + public string TranscodingContainer { get; set; } public int? AnalyzeDurationMs { get; set; } @@ -118,6 +138,7 @@ namespace MediaBrowser.Model.Dto public TranscodeReason[] TranscodeReasons { get; set; } public int? DefaultAudioStreamIndex { get; set; } + public int? DefaultSubtitleStreamIndex { get; set; } public MediaStream GetDefaultAudioStream(int? defaultIndex) diff --git a/MediaBrowser.Model/Dto/MetadataEditorInfo.cs b/MediaBrowser.Model/Dto/MetadataEditorInfo.cs index 1d840a3000..e4f38d6af3 100644 --- a/MediaBrowser.Model/Dto/MetadataEditorInfo.cs +++ b/MediaBrowser.Model/Dto/MetadataEditorInfo.cs @@ -11,11 +11,15 @@ namespace MediaBrowser.Model.Dto public class MetadataEditorInfo { public ParentalRating[] ParentalRatingOptions { get; set; } + public CountryInfo[] Countries { get; set; } + public CultureDto[] Cultures { get; set; } + public ExternalIdInfo[] ExternalIdInfos { get; set; } public string ContentType { get; set; } + public NameValuePair[] ContentTypeOptions { get; set; } public MetadataEditorInfo() diff --git a/MediaBrowser.Model/Dto/NameIdPair.cs b/MediaBrowser.Model/Dto/NameIdPair.cs index efb2c157c2..45c2fb35db 100644 --- a/MediaBrowser.Model/Dto/NameIdPair.cs +++ b/MediaBrowser.Model/Dto/NameIdPair.cs @@ -23,6 +23,7 @@ namespace MediaBrowser.Model.Dto public class NameGuidPair { public string Name { get; set; } + public Guid Id { get; set; } } } diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index 3db72f78ac..f0f7bf838c 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -125,6 +125,7 @@ namespace MediaBrowser.Model.Entities { attributes.Add(StringHelper.FirstToUpper(Language)); } + if (!string.IsNullOrEmpty(Codec) && !string.Equals(Codec, "dca", StringComparison.OrdinalIgnoreCase)) { attributes.Add(AudioCodec.GetFriendlyName(Codec)); @@ -142,6 +143,7 @@ namespace MediaBrowser.Model.Entities { attributes.Add(Channels.Value.ToString(CultureInfo.InvariantCulture) + " ch"); } + if (IsDefault) { attributes.Add("Default"); @@ -227,30 +229,37 @@ namespace MediaBrowser.Model.Entities { return "4K"; } + if (width >= 2500) { if (i.IsInterlaced) { return "1440i"; } + return "1440p"; } + if (width >= 1900 || height >= 1000) { if (i.IsInterlaced) { return "1080i"; } + return "1080p"; } + if (width >= 1260 || height >= 700) { if (i.IsInterlaced) { return "720i"; } + return "720p"; } + if (width >= 700 || height >= 440) { @@ -258,11 +267,13 @@ namespace MediaBrowser.Model.Entities { return "480i"; } + return "480p"; } return "SD"; } + return null; } @@ -448,6 +459,7 @@ namespace MediaBrowser.Model.Entities { return false; } + if (string.Equals(fromCodec, "ssa", StringComparison.OrdinalIgnoreCase)) { return false; @@ -458,6 +470,7 @@ namespace MediaBrowser.Model.Entities { return false; } + if (string.Equals(toCodec, "ssa", StringComparison.OrdinalIgnoreCase)) { return false; diff --git a/MediaBrowser.Model/Entities/MediaUrl.cs b/MediaBrowser.Model/Entities/MediaUrl.cs index 74f982437f..80ceaa765a 100644 --- a/MediaBrowser.Model/Entities/MediaUrl.cs +++ b/MediaBrowser.Model/Entities/MediaUrl.cs @@ -6,6 +6,7 @@ namespace MediaBrowser.Model.Entities public class MediaUrl { public string Url { get; set; } + public string Name { get; set; } } } diff --git a/MediaBrowser.Model/Entities/VirtualFolderInfo.cs b/MediaBrowser.Model/Entities/VirtualFolderInfo.cs index 2de02e403c..f8df057618 100644 --- a/MediaBrowser.Model/Entities/VirtualFolderInfo.cs +++ b/MediaBrowser.Model/Entities/VirtualFolderInfo.cs @@ -52,6 +52,7 @@ namespace MediaBrowser.Model.Entities public string PrimaryImageItemId { get; set; } public double? RefreshProgress { get; set; } + public string RefreshStatus { get; set; } } } diff --git a/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs index 45970cf6b1..07e76d9600 100644 --- a/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs @@ -124,6 +124,7 @@ namespace MediaBrowser.Model.LiveTv /// </summary> /// <value><c>true</c> if this instance is post padding required; otherwise, <c>false</c>.</value> public bool IsPostPaddingRequired { get; set; } + public KeepUntil KeepUntil { get; set; } } } diff --git a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs index d1a94d8b30..fe23906898 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs @@ -64,6 +64,7 @@ namespace MediaBrowser.Model.LiveTv /// </summary> /// <value><c>true</c> if [add current program]; otherwise, <c>false</c>.</value> public bool AddCurrentProgram { get; set; } + public bool EnableUserData { get; set; } /// <summary> @@ -88,6 +89,7 @@ namespace MediaBrowser.Model.LiveTv /// </summary> /// <value><c>null</c> if [is sports] contains no value, <c>true</c> if [is sports]; otherwise, <c>false</c>.</value> public bool? IsSports { get; set; } + public bool? IsSeries { get; set; } public string[] SortBy { get; set; } diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs index 69c43efd47..789de3198a 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs @@ -9,21 +9,29 @@ namespace MediaBrowser.Model.LiveTv public class LiveTvOptions { public int? GuideDays { get; set; } + public string RecordingPath { get; set; } + public string MovieRecordingPath { get; set; } + public string SeriesRecordingPath { get; set; } + public bool EnableRecordingSubfolders { get; set; } + public bool EnableOriginalAudioWithEncodedRecordings { get; set; } public TunerHostInfo[] TunerHosts { get; set; } + public ListingsProviderInfo[] ListingProviders { get; set; } public int PrePaddingSeconds { get; set; } + public int PostPaddingSeconds { get; set; } public string[] MediaLocationsCreated { get; set; } public string RecordingPostProcessor { get; set; } + public string RecordingPostProcessorArguments { get; set; } public LiveTvOptions() @@ -38,15 +46,25 @@ namespace MediaBrowser.Model.LiveTv public class TunerHostInfo { public string Id { get; set; } + public string Url { get; set; } + public string Type { get; set; } + public string DeviceId { get; set; } + public string FriendlyName { get; set; } + public bool ImportFavoritesOnly { get; set; } + public bool AllowHWTranscoding { get; set; } + public bool EnableStreamLooping { get; set; } + public string Source { get; set; } + public int TunerCount { get; set; } + public string UserAgent { get; set; } public TunerHostInfo() @@ -58,23 +76,39 @@ namespace MediaBrowser.Model.LiveTv public class ListingsProviderInfo { public string Id { get; set; } + public string Type { get; set; } + public string Username { get; set; } + public string Password { get; set; } + public string ListingsId { get; set; } + public string ZipCode { get; set; } + public string Country { get; set; } + public string Path { get; set; } public string[] EnabledTuners { get; set; } + public bool EnableAllTuners { get; set; } + public string[] NewsCategories { get; set; } + public string[] SportsCategories { get; set; } + public string[] KidsCategories { get; set; } + public string[] MovieCategories { get; set; } + public NameValuePair[] ChannelMappings { get; set; } + public string MoviePrefix { get; set; } + public string PreferredLanguage { get; set; } + public string UserAgent { get; set; } public ListingsProviderInfo() diff --git a/MediaBrowser.Model/LiveTv/RecordingQuery.cs b/MediaBrowser.Model/LiveTv/RecordingQuery.cs index 2649829300..25755483a7 100644 --- a/MediaBrowser.Model/LiveTv/RecordingQuery.cs +++ b/MediaBrowser.Model/LiveTv/RecordingQuery.cs @@ -65,14 +65,23 @@ namespace MediaBrowser.Model.LiveTv /// </summary> /// <value>The fields.</value> public ItemFields[] Fields { get; set; } + public bool? EnableImages { get; set; } + public bool? IsLibraryItem { get; set; } + public bool? IsNews { get; set; } + public bool? IsMovie { get; set; } + public bool? IsSeries { get; set; } + public bool? IsKids { get; set; } + public bool? IsSports { get; set; } + public int? ImageTypeLimit { get; set; } + public ImageType[] EnableImageTypes { get; set; } public bool EnableTotalRecordCount { get; set; } diff --git a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs index cce508809b..83bda5d569 100644 --- a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs +++ b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs @@ -32,18 +32,29 @@ namespace MediaBrowser.Model.MediaInfo } public string OpenToken { get; set; } + public Guid UserId { get; set; } + public string PlaySessionId { get; set; } + public long? MaxStreamingBitrate { get; set; } + public long? StartTimeTicks { get; set; } + public int? AudioStreamIndex { get; set; } + public int? SubtitleStreamIndex { get; set; } + public int? MaxAudioChannels { get; set; } + public Guid ItemId { get; set; } + public DeviceProfile DeviceProfile { get; set; } public bool EnableDirectPlay { get; set; } + public bool EnableDirectStream { get; set; } + public MediaProtocol[] DirectPlayProtocols { get; set; } } } diff --git a/MediaBrowser.Model/MediaInfo/MediaInfo.cs b/MediaBrowser.Model/MediaInfo/MediaInfo.cs index 97b9799357..472055c22c 100644 --- a/MediaBrowser.Model/MediaInfo/MediaInfo.cs +++ b/MediaBrowser.Model/MediaInfo/MediaInfo.cs @@ -35,13 +35,21 @@ namespace MediaBrowser.Model.MediaInfo /// </summary> /// <value>The studios.</value> public string[] Studios { get; set; } + public string[] Genres { get; set; } + public string ShowName { get; set; } + public int? IndexNumber { get; set; } + public int? ParentIndexNumber { get; set; } + public int? ProductionYear { get; set; } + public DateTime? PremiereDate { get; set; } + public BaseItemPerson[] People { get; set; } + public Dictionary<string, string> ProviderIds { get; set; } /// <summary> diff --git a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs index 82e13e0ebe..3216856777 100644 --- a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs +++ b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs @@ -29,11 +29,17 @@ namespace MediaBrowser.Model.MediaInfo public DeviceProfile DeviceProfile { get; set; } public bool EnableDirectPlay { get; set; } + public bool EnableDirectStream { get; set; } + public bool EnableTranscoding { get; set; } + public bool AllowVideoStreamCopy { get; set; } + public bool AllowAudioStreamCopy { get; set; } + public bool IsPlayback { get; set; } + public bool AutoOpenLiveStream { get; set; } public MediaProtocol[] DirectPlayProtocols { get; set; } diff --git a/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs b/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs index d9f7a852ca..a8d88d8a1b 100644 --- a/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs +++ b/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs @@ -8,15 +8,25 @@ namespace MediaBrowser.Model.Providers public class RemoteSubtitleInfo { public string ThreeLetterISOLanguageName { get; set; } + public string Id { get; set; } + public string ProviderName { get; set; } + public string Name { get; set; } + public string Format { get; set; } + public string Author { get; set; } + public string Comment { get; set; } + public DateTime? DateCreated { get; set; } + public float? CommunityRating { get; set; } + public int? DownloadCount { get; set; } + public bool? IsHashMatch { get; set; } } } diff --git a/MediaBrowser.Model/Providers/SubtitleOptions.cs b/MediaBrowser.Model/Providers/SubtitleOptions.cs index c073795704..5702c460b0 100644 --- a/MediaBrowser.Model/Providers/SubtitleOptions.cs +++ b/MediaBrowser.Model/Providers/SubtitleOptions.cs @@ -8,13 +8,19 @@ namespace MediaBrowser.Model.Providers public class SubtitleOptions { public bool SkipIfEmbeddedSubtitlesPresent { get; set; } + public bool SkipIfAudioTrackMatches { get; set; } + public string[] DownloadLanguages { get; set; } + public bool DownloadMovieSubtitles { get; set; } + public bool DownloadEpisodeSubtitles { get; set; } public string OpenSubtitlesUsername { get; set; } + public string OpenSubtitlesPasswordHash { get; set; } + public bool IsOpenSubtitleVipAccount { get; set; } public bool RequirePerfectMatch { get; set; } diff --git a/MediaBrowser.Model/Providers/SubtitleProviderInfo.cs b/MediaBrowser.Model/Providers/SubtitleProviderInfo.cs index ee25be4b6c..7a7e7b9ec3 100644 --- a/MediaBrowser.Model/Providers/SubtitleProviderInfo.cs +++ b/MediaBrowser.Model/Providers/SubtitleProviderInfo.cs @@ -6,6 +6,7 @@ namespace MediaBrowser.Model.Providers public class SubtitleProviderInfo { public string Name { get; set; } + public string Id { get; set; } } } diff --git a/MediaBrowser.Model/Querying/QueryFilters.cs b/MediaBrowser.Model/Querying/QueryFilters.cs index e04208f766..6e4d251818 100644 --- a/MediaBrowser.Model/Querying/QueryFilters.cs +++ b/MediaBrowser.Model/Querying/QueryFilters.cs @@ -9,8 +9,11 @@ namespace MediaBrowser.Model.Querying public class QueryFiltersLegacy { public string[] Genres { get; set; } + public string[] Tags { get; set; } + public string[] OfficialRatings { get; set; } + public int[] Years { get; set; } public QueryFiltersLegacy() @@ -21,9 +24,11 @@ namespace MediaBrowser.Model.Querying Years = Array.Empty<int>(); } } + public class QueryFilters { public NameGuidPair[] Genres { get; set; } + public string[] Tags { get; set; } public QueryFilters() diff --git a/MediaBrowser.Model/Search/SearchHint.cs b/MediaBrowser.Model/Search/SearchHint.cs index c7a721df6e..983dbd2bc0 100644 --- a/MediaBrowser.Model/Search/SearchHint.cs +++ b/MediaBrowser.Model/Search/SearchHint.cs @@ -100,6 +100,7 @@ namespace MediaBrowser.Model.Search public string MediaType { get; set; } public DateTime? StartDate { get; set; } + public DateTime? EndDate { get; set; } /// <summary> diff --git a/MediaBrowser.Model/Search/SearchQuery.cs b/MediaBrowser.Model/Search/SearchQuery.cs index 4470f1ad97..297199f61c 100644 --- a/MediaBrowser.Model/Search/SearchQuery.cs +++ b/MediaBrowser.Model/Search/SearchQuery.cs @@ -32,14 +32,21 @@ namespace MediaBrowser.Model.Search public int? Limit { get; set; } public bool IncludePeople { get; set; } + public bool IncludeMedia { get; set; } + public bool IncludeGenres { get; set; } + public bool IncludeStudios { get; set; } + public bool IncludeArtists { get; set; } public string[] MediaTypes { get; set; } + public string[] IncludeItemTypes { get; set; } + public string[] ExcludeItemTypes { get; set; } + public string ParentId { get; set; } public bool? IsMovie { get; set; } diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index f413f1e177..89622f3110 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -76,9 +76,13 @@ namespace MediaBrowser.Model.Services public interface IHttpFile { string Name { get; } + string FileName { get; } + long ContentLength { get; } + string ContentType { get; } + Stream InputStream { get; } } diff --git a/MediaBrowser.Model/Services/IService.cs b/MediaBrowser.Model/Services/IService.cs index a26d394558..5233f57abb 100644 --- a/MediaBrowser.Model/Services/IService.cs +++ b/MediaBrowser.Model/Services/IService.cs @@ -8,6 +8,8 @@ namespace MediaBrowser.Model.Services } public interface IReturn { } + public interface IReturn<T> : IReturn { } + public interface IReturnVoid : IReturn { } } diff --git a/MediaBrowser.Model/Session/ClientCapabilities.cs b/MediaBrowser.Model/Session/ClientCapabilities.cs index 51db66d212..d3878ca308 100644 --- a/MediaBrowser.Model/Session/ClientCapabilities.cs +++ b/MediaBrowser.Model/Session/ClientCapabilities.cs @@ -13,15 +13,19 @@ namespace MediaBrowser.Model.Session public string[] SupportedCommands { get; set; } public bool SupportsMediaControl { get; set; } + public bool SupportsContentUploading { get; set; } + public string MessageCallbackUrl { get; set; } public bool SupportsPersistentIdentifier { get; set; } + public bool SupportsSync { get; set; } public DeviceProfile DeviceProfile { get; set; } public string AppStoreUrl { get; set; } + public string IconUrl { get; set; } public ClientCapabilities() diff --git a/MediaBrowser.Model/Session/PlayRequest.cs b/MediaBrowser.Model/Session/PlayRequest.cs index 62b68b49ea..1a51e23c99 100644 --- a/MediaBrowser.Model/Session/PlayRequest.cs +++ b/MediaBrowser.Model/Session/PlayRequest.cs @@ -39,8 +39,11 @@ namespace MediaBrowser.Model.Session public Guid ControllingUserId { get; set; } public int? SubtitleStreamIndex { get; set; } + public int? AudioStreamIndex { get; set; } + public string MediaSourceId { get; set; } + public int? StartIndex { get; set; } } } diff --git a/MediaBrowser.Model/Session/PlaybackProgressInfo.cs b/MediaBrowser.Model/Session/PlaybackProgressInfo.cs index 6b4cfe4f0d..21bcabf1d9 100644 --- a/MediaBrowser.Model/Session/PlaybackProgressInfo.cs +++ b/MediaBrowser.Model/Session/PlaybackProgressInfo.cs @@ -105,6 +105,7 @@ namespace MediaBrowser.Model.Session public RepeatMode RepeatMode { get; set; } public QueueItem[] NowPlayingQueue { get; set; } + public string PlaylistItemId { get; set; } } @@ -118,6 +119,7 @@ namespace MediaBrowser.Model.Session public class QueueItem { public Guid Id { get; set; } + public string PlaylistItemId { get; set; } } } diff --git a/MediaBrowser.Model/Session/PlaybackStopInfo.cs b/MediaBrowser.Model/Session/PlaybackStopInfo.cs index b0827ac99c..aa29bb249e 100644 --- a/MediaBrowser.Model/Session/PlaybackStopInfo.cs +++ b/MediaBrowser.Model/Session/PlaybackStopInfo.cs @@ -62,6 +62,7 @@ namespace MediaBrowser.Model.Session public string NextMediaType { get; set; } public string PlaylistItemId { get; set; } + public QueueItem[] NowPlayingQueue { get; set; } } } diff --git a/MediaBrowser.Model/Session/TranscodingInfo.cs b/MediaBrowser.Model/Session/TranscodingInfo.cs index d6dc83413b..e832c2f6fa 100644 --- a/MediaBrowser.Model/Session/TranscodingInfo.cs +++ b/MediaBrowser.Model/Session/TranscodingInfo.cs @@ -8,17 +8,25 @@ namespace MediaBrowser.Model.Session public class TranscodingInfo { public string AudioCodec { get; set; } + public string VideoCodec { get; set; } + public string Container { get; set; } + public bool IsVideoDirect { get; set; } + public bool IsAudioDirect { get; set; } + public int? Bitrate { get; set; } public float? Framerate { get; set; } + public double? CompletionPercentage { get; set; } public int? Width { get; set; } + public int? Height { get; set; } + public int? AudioChannels { get; set; } public TranscodeReason[] TranscodeReasons { get; set; } diff --git a/MediaBrowser.Model/Sync/SyncJob.cs b/MediaBrowser.Model/Sync/SyncJob.cs index 3cc9ff7267..b9290b6e83 100644 --- a/MediaBrowser.Model/Sync/SyncJob.cs +++ b/MediaBrowser.Model/Sync/SyncJob.cs @@ -122,7 +122,9 @@ namespace MediaBrowser.Model.Sync public int ItemCount { get; set; } public string ParentName { get; set; } + public string PrimaryImageItemId { get; set; } + public string PrimaryImageTag { get; set; } public SyncJob() diff --git a/MediaBrowser.Model/Users/UserAction.cs b/MediaBrowser.Model/Users/UserAction.cs index 36b8e6ee52..7646db4a82 100644 --- a/MediaBrowser.Model/Users/UserAction.cs +++ b/MediaBrowser.Model/Users/UserAction.cs @@ -8,11 +8,17 @@ namespace MediaBrowser.Model.Users public class UserAction { public string Id { get; set; } + public string ServerId { get; set; } + public Guid UserId { get; set; } + public Guid ItemId { get; set; } + public UserActionType Type { get; set; } + public DateTime Date { get; set; } + public long? PositionTicks { get; set; } } } diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index 2fd27d3b9a..caf2e0f549 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -35,24 +35,37 @@ namespace MediaBrowser.Model.Users public int? MaxParentalRating { get; set; } public string[] BlockedTags { get; set; } + public bool EnableUserPreferenceAccess { get; set; } + public AccessSchedule[] AccessSchedules { get; set; } + public UnratedItem[] BlockUnratedItems { get; set; } + public bool EnableRemoteControlOfOtherUsers { get; set; } + public bool EnableSharedDeviceControl { get; set; } + public bool EnableRemoteAccess { get; set; } public bool EnableLiveTvManagement { get; set; } + public bool EnableLiveTvAccess { get; set; } public bool EnableMediaPlayback { get; set; } + public bool EnableAudioPlaybackTranscoding { get; set; } + public bool EnableVideoPlaybackTranscoding { get; set; } + public bool EnablePlaybackRemuxing { get; set; } + public bool ForceRemoteSourceTranscoding { get; set; } public bool EnableContentDeletion { get; set; } + public string[] EnableContentDeletionFromFolders { get; set; } + public bool EnableContentDownloading { get; set; } /// <summary> @@ -60,29 +73,36 @@ namespace MediaBrowser.Model.Users /// </summary> /// <value><c>true</c> if [enable synchronize]; otherwise, <c>false</c>.</value> public bool EnableSyncTranscoding { get; set; } + public bool EnableMediaConversion { get; set; } public string[] EnabledDevices { get; set; } + public bool EnableAllDevices { get; set; } public string[] EnabledChannels { get; set; } + public bool EnableAllChannels { get; set; } public string[] EnabledFolders { get; set; } + public bool EnableAllFolders { get; set; } public int InvalidLoginAttemptCount { get; set; } + public int LoginAttemptsBeforeLockout { get; set; } public bool EnablePublicSharing { get; set; } public string[] BlockedMediaFolders { get; set; } + public string[] BlockedChannels { get; set; } public int RemoteClientBitrateLimit { get; set; } [XmlElement(ElementName = "AuthenticationProviderId")] public string AuthenticationProviderId { get; set; } + public string PasswordResetProviderId { get; set; } /// <summary> diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 3c94f62150..2f5aa18195 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -104,6 +104,7 @@ namespace MediaBrowser.Providers.Manager } } } + if (saveLocallyWithMedia.HasValue && !saveLocallyWithMedia.Value) { saveLocally = saveLocallyWithMedia.Value; @@ -147,6 +148,7 @@ namespace MediaBrowser.Providers.Manager { retryPath = retryPaths[currentPathIndex]; } + var savedPath = await SaveImageToLocation(source, path, retryPath, cancellationToken).ConfigureAwait(false); savedPaths.Add(savedPath); currentPathIndex++; @@ -460,6 +462,7 @@ namespace MediaBrowser.Providers.Manager { filename = folderName; } + path = Path.Combine(item.GetInternalMetadataPath(), filename + extension); } @@ -551,6 +554,7 @@ namespace MediaBrowser.Providers.Manager { list.Add(Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + extension)); } + return list.ToArray(); } @@ -619,6 +623,7 @@ namespace MediaBrowser.Providers.Manager { imageFilename = "poster"; } + var folder = Path.GetDirectoryName(item.Path); return Path.Combine(folder, Path.GetFileNameWithoutExtension(item.Path) + "-" + imageFilename + extension); diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 7901503d3f..3d60979f3e 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -58,6 +58,7 @@ namespace MediaBrowser.Providers.Manager { ClearImages(item, ImageType.Backdrop); } + if (refreshOptions.IsReplacingImage(ImageType.Screenshot)) { ClearImages(item, ImageType.Screenshot); @@ -472,6 +473,7 @@ namespace MediaBrowser.Providers.Manager { continue; } + break; } } @@ -585,6 +587,7 @@ namespace MediaBrowser.Providers.Manager { continue; } + break; } } diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 73fb63743e..a3920d26f3 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -210,6 +210,7 @@ namespace MediaBrowser.Providers.Manager LibraryManager.UpdatePeople(baseItem, result.People); SavePeopleMetadata(result.People, libraryOptions, cancellationToken); } + result.Item.UpdateToRepository(reason, cancellationToken); } @@ -324,6 +325,7 @@ namespace MediaBrowser.Providers.Manager { return true; } + var folder = item as Folder; if (folder != null) { @@ -422,6 +424,7 @@ namespace MediaBrowser.Providers.Manager { dateLastMediaAdded = childDateCreated; } + any = true; } } @@ -726,6 +729,7 @@ namespace MediaBrowser.Providers.Manager { hasLocalMetadata = true; } + break; } @@ -874,6 +878,7 @@ namespace MediaBrowser.Providers.Manager { return "en"; } + return language; } @@ -924,7 +929,9 @@ namespace MediaBrowser.Providers.Manager public class RefreshResult { public ItemUpdateType UpdateType { get; set; } + public string ErrorMessage { get; set; } + public int Failures { get; set; } } } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 5853c77144..5b1192c305 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -787,6 +787,7 @@ namespace MediaBrowser.Providers.Manager { searchInfo.SearchInfo.MetadataLanguage = _configurationManager.Configuration.PreferredMetadataLanguage; } + if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataCountryCode)) { searchInfo.SearchInfo.MetadataCountryCode = _configurationManager.Configuration.MetadataCountryCode; diff --git a/MediaBrowser.Providers/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs index 60410032e5..4f49dc1c98 100644 --- a/MediaBrowser.Providers/Manager/ProviderUtils.cs +++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs @@ -26,6 +26,7 @@ namespace MediaBrowser.Providers.Manager { throw new ArgumentNullException(nameof(source)); } + if (target == null) { throw new ArgumentNullException(nameof(target)); diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs index ba87e05709..ef4e1dde9b 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs @@ -129,6 +129,7 @@ namespace MediaBrowser.Providers.MediaInfo { return false; } + if (!item.IsFileProtocol) { return false; diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index ccbe27c1ff..54847c8a9c 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -404,6 +404,7 @@ namespace MediaBrowser.Providers.MediaInfo video.ProductionYear = data.ProductionYear; } } + if (data.PremiereDate.HasValue) { if (!video.PremiereDate.HasValue || isFullRefresh) @@ -411,6 +412,7 @@ namespace MediaBrowser.Providers.MediaInfo video.PremiereDate = data.PremiereDate; } } + if (data.IndexNumber.HasValue) { if (!video.IndexNumber.HasValue || isFullRefresh) @@ -418,6 +420,7 @@ namespace MediaBrowser.Providers.MediaInfo video.IndexNumber = data.IndexNumber; } } + if (data.ParentIndexNumber.HasValue) { if (!video.ParentIndexNumber.HasValue || isFullRefresh) diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index 08e503a71b..7f4a8a372e 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -93,6 +93,7 @@ namespace MediaBrowser.Providers.MediaInfo { videoIndex++; } + if (mediaStream == imageStream) { break; @@ -132,6 +133,7 @@ namespace MediaBrowser.Providers.MediaInfo { return false; } + if (!item.IsFileProtocol) { return false; diff --git a/MediaBrowser.Providers/Movies/MovieMetadataService.cs b/MediaBrowser.Providers/Movies/MovieMetadataService.cs index 9faba47981..61d8c82632 100644 --- a/MediaBrowser.Providers/Movies/MovieMetadataService.cs +++ b/MediaBrowser.Providers/Movies/MovieMetadataService.cs @@ -28,10 +28,12 @@ namespace MediaBrowser.Providers.Movies { return false; } + if (!item.ProductionYear.HasValue) { return false; } + return base.IsFullLocalMetadata(item); } diff --git a/MediaBrowser.Providers/Movies/TrailerMetadataService.cs b/MediaBrowser.Providers/Movies/TrailerMetadataService.cs index b45d2b7450..09519c7a33 100644 --- a/MediaBrowser.Providers/Movies/TrailerMetadataService.cs +++ b/MediaBrowser.Providers/Movies/TrailerMetadataService.cs @@ -28,10 +28,12 @@ namespace MediaBrowser.Providers.Movies { return false; } + if (!item.ProductionYear.HasValue) { return false; } + return base.IsFullLocalMetadata(item); } diff --git a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs index ed0601c00e..4ad4f890a0 100644 --- a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs +++ b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs @@ -61,18 +61,22 @@ namespace MediaBrowser.Providers.Playlists { return GetWplItems(stream); } + if (string.Equals(".zpl", extension, StringComparison.OrdinalIgnoreCase)) { return GetZplItems(stream); } + if (string.Equals(".m3u", extension, StringComparison.OrdinalIgnoreCase)) { return GetM3uItems(stream); } + if (string.Equals(".m3u8", extension, StringComparison.OrdinalIgnoreCase)) { return GetM3u8Items(stream); } + if (string.Equals(".pls", extension, StringComparison.OrdinalIgnoreCase)) { return GetPlsItems(stream); diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs index b1a54f22ff..96224b366e 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs @@ -210,42 +210,79 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public class Album { public string idAlbum { get; set; } + public string idArtist { get; set; } + public string strAlbum { get; set; } + public string strArtist { get; set; } + public string intYearReleased { get; set; } + public string strGenre { get; set; } + public string strSubGenre { get; set; } + public string strReleaseFormat { get; set; } + public string intSales { get; set; } + public string strAlbumThumb { get; set; } + public string strAlbumCDart { get; set; } + public string strDescriptionEN { get; set; } + public string strDescriptionDE { get; set; } + public string strDescriptionFR { get; set; } + public string strDescriptionCN { get; set; } + public string strDescriptionIT { get; set; } + public string strDescriptionJP { get; set; } + public string strDescriptionRU { get; set; } + public string strDescriptionES { get; set; } + public string strDescriptionPT { get; set; } + public string strDescriptionSE { get; set; } + public string strDescriptionNL { get; set; } + public string strDescriptionHU { get; set; } + public string strDescriptionNO { get; set; } + public string strDescriptionIL { get; set; } + public string strDescriptionPL { get; set; } + public object intLoved { get; set; } + public object intScore { get; set; } + public string strReview { get; set; } + public object strMood { get; set; } + public object strTheme { get; set; } + public object strSpeed { get; set; } + public object strLocation { get; set; } + public string strMusicBrainzID { get; set; } + public string strMusicBrainzArtistID { get; set; } + public object strItunesID { get; set; } + public object strAmazonID { get; set; } + public string strLocked { get; set; } } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs index fdba779be8..14bbcddcea 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs @@ -199,45 +199,85 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public class Artist { public string idArtist { get; set; } + public string strArtist { get; set; } + public string strArtistAlternate { get; set; } + public object idLabel { get; set; } + public string intFormedYear { get; set; } + public string intBornYear { get; set; } + public object intDiedYear { get; set; } + public object strDisbanded { get; set; } + public string strGenre { get; set; } + public string strSubGenre { get; set; } + public string strWebsite { get; set; } + public string strFacebook { get; set; } + public string strTwitter { get; set; } + public string strBiographyEN { get; set; } + public string strBiographyDE { get; set; } + public string strBiographyFR { get; set; } + public string strBiographyCN { get; set; } + public string strBiographyIT { get; set; } + public string strBiographyJP { get; set; } + public string strBiographyRU { get; set; } + public string strBiographyES { get; set; } + public string strBiographyPT { get; set; } + public string strBiographySE { get; set; } + public string strBiographyNL { get; set; } + public string strBiographyHU { get; set; } + public string strBiographyNO { get; set; } + public string strBiographyIL { get; set; } + public string strBiographyPL { get; set; } + public string strGender { get; set; } + public string intMembers { get; set; } + public string strCountry { get; set; } + public string strCountryCode { get; set; } + public string strArtistThumb { get; set; } + public string strArtistLogo { get; set; } + public string strArtistFanart { get; set; } + public string strArtistFanart2 { get; set; } + public string strArtistFanart3 { get; set; } + public string strArtistBanner { get; set; } + public string strMusicBrainzID { get; set; } + public object strLastFMChart { get; set; } + public string strLocked { get; set; } } diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs index 0a2c7c1241..78b5001991 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/AlbumProvider.cs @@ -361,6 +361,7 @@ namespace MediaBrowser.Providers.Music return ParseReleaseList(subReader).ToList(); } } + default: { reader.Skip(); @@ -396,6 +397,7 @@ namespace MediaBrowser.Providers.Music reader.Read(); continue; } + var releaseId = reader.GetAttribute("id"); using (var subReader = reader.ReadSubtree()) @@ -406,8 +408,10 @@ namespace MediaBrowser.Providers.Music yield return release; } } + break; } + default: { reader.Skip(); @@ -453,6 +457,7 @@ namespace MediaBrowser.Providers.Music { result.Year = date.Year; } + break; } case "annotation": @@ -480,6 +485,7 @@ namespace MediaBrowser.Providers.Music break; } + default: { reader.Skip(); @@ -518,6 +524,7 @@ namespace MediaBrowser.Providers.Music return ParseArtistNameCredit(subReader); } } + default: { reader.Skip(); @@ -556,6 +563,7 @@ namespace MediaBrowser.Providers.Music return ParseArtistArtistCredit(subReader, id); } } + default: { reader.Skip(); @@ -593,6 +601,7 @@ namespace MediaBrowser.Providers.Music name = reader.ReadElementContentAsString(); break; } + default: { reader.Skip(); @@ -680,11 +689,13 @@ namespace MediaBrowser.Providers.Music reader.Read(); continue; } + using (var subReader = reader.ReadSubtree()) { return GetFirstReleaseGroupId(subReader); } } + default: { reader.Skip(); @@ -719,6 +730,7 @@ namespace MediaBrowser.Providers.Music { return reader.GetAttribute("id"); } + default: { reader.Skip(); @@ -780,6 +792,7 @@ namespace MediaBrowser.Providers.Music // We retry a finite number of times, and only whilst MB is indicating 503 (throttling) } + while (attempts < MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable); // Log error if unable to query MB database due to throttling diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs index 9d93dbdd1c..101af162d5 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs @@ -108,11 +108,13 @@ namespace MediaBrowser.Providers.Music reader.Read(); continue; } + using (var subReader = reader.ReadSubtree()) { return ParseArtistList(subReader).ToList(); } } + default: { reader.Skip(); @@ -150,6 +152,7 @@ namespace MediaBrowser.Providers.Music reader.Read(); continue; } + var mbzId = reader.GetAttribute("id"); using (var subReader = reader.ReadSubtree()) @@ -160,8 +163,10 @@ namespace MediaBrowser.Providers.Music yield return artist; } } + break; } + default: { reader.Skip(); @@ -202,6 +207,7 @@ namespace MediaBrowser.Providers.Music result.Overview = reader.ReadElementContentAsString(); break; } + default: { // there is sort-name if ever needed diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs index 4a29ba4d06..b12d2a3885 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs @@ -286,27 +286,49 @@ namespace MediaBrowser.Providers.Plugins.Omdb class SearchResult { public string Title { get; set; } + public string Year { get; set; } + public string Rated { get; set; } + public string Released { get; set; } + public string Season { get; set; } + public string Episode { get; set; } + public string Runtime { get; set; } + public string Genre { get; set; } + public string Director { get; set; } + public string Writer { get; set; } + public string Actors { get; set; } + public string Plot { get; set; } + public string Language { get; set; } + public string Country { get; set; } + public string Awards { get; set; } + public string Poster { get; set; } + public string Metascore { get; set; } + public string imdbRating { get; set; } + public string imdbVotes { get; set; } + public string imdbID { get; set; } + public string seriesID { get; set; } + public string Type { get; set; } + public string Response { get; set; } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionImages.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionImages.cs index 2410ca16b5..4ebcaeeb6c 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionImages.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionImages.cs @@ -6,6 +6,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Collections public class CollectionImages { public List<Backdrop> Backdrops { get; set; } + public List<Poster> Posters { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionResult.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionResult.cs index 3437552df7..9228bec9ce 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionResult.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionResult.cs @@ -5,11 +5,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Collections public class CollectionResult { public int Id { get; set; } + public string Name { get; set; } + public string Overview { get; set; } + public string Poster_Path { get; set; } + public string Backdrop_Path { get; set; } + public List<Part> Parts { get; set; } + public CollectionImages Images { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/Part.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/Part.cs index 462fdab534..3a464e0537 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/Part.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/Part.cs @@ -3,9 +3,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Collections public class Part { public string Title { get; set; } + public int Id { get; set; } + public string Release_Date { get; set; } + public string Poster_Path { get; set; } + public string Backdrop_Path { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Backdrop.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Backdrop.cs index 35e3e21126..add7a38d84 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Backdrop.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Backdrop.cs @@ -3,11 +3,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General public class Backdrop { public double Aspect_Ratio { get; set; } + public string File_Path { get; set; } + public int Height { get; set; } + public string Iso_639_1 { get; set; } + public double Vote_Average { get; set; } + public int Vote_Count { get; set; } + public int Width { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Crew.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Crew.cs index 6a5e74ddbe..3f0fe7fad1 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Crew.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Crew.cs @@ -3,10 +3,15 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General public class Crew { public int Id { get; set; } + public string Credit_Id { get; set; } + public string Name { get; set; } + public string Department { get; set; } + public string Job { get; set; } + public string Profile_Path { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/ExternalIds.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/ExternalIds.cs index a083f6e9c9..8082a5e586 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/ExternalIds.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/ExternalIds.cs @@ -3,9 +3,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General public class ExternalIds { public string Imdb_Id { get; set; } + public object Freebase_Id { get; set; } + public string Freebase_Mid { get; set; } + public int Tvdb_Id { get; set; } + public int Tvrage_Id { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Genre.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Genre.cs index 7f1a394c36..d7b18ff8c7 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Genre.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Genre.cs @@ -3,6 +3,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General public class Genre { public int Id { get; set; } + public string Name { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Images.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Images.cs index 166f9b7408..bbeac878a0 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Images.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Images.cs @@ -5,6 +5,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General public class Images { public List<Backdrop> Backdrops { get; set; } + public List<Poster> Posters { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keyword.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keyword.cs index 72f417be5d..07cab86a04 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keyword.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keyword.cs @@ -3,6 +3,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General public class Keyword { public int Id { get; set; } + public string Name { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Poster.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Poster.cs index 0cf04a6ce1..3ac89a77de 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Poster.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Poster.cs @@ -3,11 +3,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General public class Poster { public double Aspect_Ratio { get; set; } + public string File_Path { get; set; } + public int Height { get; set; } + public string Iso_639_1 { get; set; } + public double Vote_Average { get; set; } + public int Vote_Count { get; set; } + public int Width { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Profile.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Profile.cs index b45cfc30f2..57edbe74c8 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Profile.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Profile.cs @@ -3,9 +3,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General public class Profile { public string File_Path { get; set; } + public int Width { get; set; } + public int Height { get; set; } + public object Iso_639_1 { get; set; } + public double Aspect_Ratio { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Still.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Still.cs index 9fc82cfee7..1507c6577d 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Still.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Still.cs @@ -3,12 +3,19 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General public class Still { public double Aspect_Ratio { get; set; } + public string File_Path { get; set; } + public int Height { get; set; } + public string Id { get; set; } + public string Iso_639_1 { get; set; } + public double Vote_Average { get; set; } + public int Vote_Count { get; set; } + public int Width { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Video.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Video.cs index 19bfd62f61..e0fef6cce5 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Video.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Video.cs @@ -3,12 +3,19 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General public class Video { public string Id { get; set; } + public string Iso_639_1 { get; set; } + public string Iso_3166_1 { get; set; } + public string Key { get; set; } + public string Name { get; set; } + public string Site { get; set; } + public string Size { get; set; } + public string Type { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/BelongsToCollection.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/BelongsToCollection.cs index aaca57f052..af5bc9282f 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/BelongsToCollection.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/BelongsToCollection.cs @@ -3,8 +3,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies public class BelongsToCollection { public int Id { get; set; } + public string Name { get; set; } + public string Poster_Path { get; set; } + public string Backdrop_Path { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Cast.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Cast.cs index d70f218aa7..6775350b71 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Cast.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Cast.cs @@ -3,10 +3,15 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies public class Cast { public int Id { get; set; } + public string Name { get; set; } + public string Character { get; set; } + public int Order { get; set; } + public int Cast_Id { get; set; } + public string Profile_Path { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Casts.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Casts.cs index c41699bc77..5601de85eb 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Casts.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Casts.cs @@ -6,6 +6,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies public class Casts { public List<Cast> Cast { get; set; } + public List<Crew> Crew { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Country.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Country.cs index 71d1f7c242..f4cbc41f6e 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Country.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Country.cs @@ -5,7 +5,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies public class Country { public string Iso_3166_1 { get; set; } + public string Certification { get; set; } + public DateTime Release_Date { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/MovieResult.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/MovieResult.cs index 2a9b9779af..8e25e4fb34 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/MovieResult.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/MovieResult.cs @@ -6,34 +6,63 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies public class MovieResult { public bool Adult { get; set; } + public string Backdrop_Path { get; set; } + public BelongsToCollection Belongs_To_Collection { get; set; } + public int Budget { get; set; } + public List<Genre> Genres { get; set; } + public string Homepage { get; set; } + public int Id { get; set; } + public string Imdb_Id { get; set; } + public string Original_Title { get; set; } + public string Original_Name { get; set; } + public string Overview { get; set; } + public double Popularity { get; set; } + public string Poster_Path { get; set; } + public List<ProductionCompany> Production_Companies { get; set; } + public List<ProductionCountry> Production_Countries { get; set; } + public string Release_Date { get; set; } + public int Revenue { get; set; } + public int Runtime { get; set; } + public List<SpokenLanguage> Spoken_Languages { get; set; } + public string Status { get; set; } + public string Tagline { get; set; } + public string Title { get; set; } + public string Name { get; set; } + public double Vote_Average { get; set; } + public int Vote_Count { get; set; } + public Casts Casts { get; set; } + public Releases Releases { get; set; } + public Images Images { get; set; } + public Keywords Keywords { get; set; } + public Trailers Trailers { get; set; } public string GetOriginalTitle() diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCompany.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCompany.cs index 11158ade5d..ba8e42fdde 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCompany.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCompany.cs @@ -3,6 +3,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies public class ProductionCompany { public string Name { get; set; } + public int Id { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCountry.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCountry.cs index 43d00fe7a2..a313605bd3 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCountry.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCountry.cs @@ -3,6 +3,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies public class ProductionCountry { public string Iso_3166_1 { get; set; } + public string Name { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/SpokenLanguage.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/SpokenLanguage.cs index 41defa9d08..9469a41f1f 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/SpokenLanguage.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/SpokenLanguage.cs @@ -3,6 +3,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies public class SpokenLanguage { public string Iso_639_1 { get; set; } + public string Name { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Youtube.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Youtube.cs index 6be4ef5b53..499e368a48 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Youtube.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Youtube.cs @@ -3,7 +3,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Movies public class Youtube { public string Name { get; set; } + public string Size { get; set; } + public string Source { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/People/PersonResult.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/People/PersonResult.cs index 50c47eefd0..076648a6cf 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/People/PersonResult.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/People/PersonResult.cs @@ -6,18 +6,31 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.People public class PersonResult { public bool Adult { get; set; } + public List<string> Also_Known_As { get; set; } + public string Biography { get; set; } + public string Birthday { get; set; } + public string Deathday { get; set; } + public string Homepage { get; set; } + public int Id { get; set; } + public string Imdb_Id { get; set; } + public string Name { get; set; } + public string Place_Of_Birth { get; set; } + public double Popularity { get; set; } + public string Profile_Path { get; set; } + public PersonImages Images { get; set; } + public ExternalIds External_Ids { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/TvResult.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/TvResult.cs index b7fbd294c2..c611bcd5f4 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/TvResult.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Search/TvResult.cs @@ -3,13 +3,21 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Search public class TvResult { public string Backdrop_Path { get; set; } + public string First_Air_Date { get; set; } + public int Id { get; set; } + public string Original_Name { get; set; } + public string Poster_Path { get; set; } + public double Popularity { get; set; } + public string Name { get; set; } + public double Vote_Average { get; set; } + public int Vote_Count { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Cast.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Cast.cs index 9c770545c7..ebf7ba6e4c 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Cast.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Cast.cs @@ -3,10 +3,15 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV public class Cast { public string Character { get; set; } + public string Credit_Id { get; set; } + public int Id { get; set; } + public string Name { get; set; } + public string Profile_Path { get; set; } + public int Order { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/ContentRating.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/ContentRating.cs index bccb234e7d..9de674e7fb 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/ContentRating.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/ContentRating.cs @@ -3,6 +3,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV public class ContentRating { public string Iso_3166_1 { get; set; } + public string Rating { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/CreatedBy.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/CreatedBy.cs index 35e8eaecb8..1ef65bb986 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/CreatedBy.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/CreatedBy.cs @@ -3,7 +3,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV public class CreatedBy { public int Id { get; set; } + public string Name { get; set; } + public string Profile_Path { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Credits.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Credits.cs index ebf412c2df..836fbcbe51 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Credits.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Credits.cs @@ -6,6 +6,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV public class Credits { public List<Cast> Cast { get; set; } + public List<Crew> Crew { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Episode.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Episode.cs index 8203632b7c..a38012e31c 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Episode.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Episode.cs @@ -3,12 +3,19 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV public class Episode { public string Air_Date { get; set; } + public int Episode_Number { get; set; } + public int Id { get; set; } + public string Name { get; set; } + public string Overview { get; set; } + public string Still_Path { get; set; } + public double Vote_Average { get; set; } + public int Vote_Count { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeCredits.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeCredits.cs index f89859f85b..5068e8f9bc 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeCredits.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeCredits.cs @@ -6,7 +6,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV public class EpisodeCredits { public List<Cast> Cast { get; set; } + public List<Crew> Crew { get; set; } + public List<GuestStar> Guest_Stars { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeResult.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeResult.cs index e25b65d70a..a4d6a130ef 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeResult.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeResult.cs @@ -6,18 +6,31 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV public class EpisodeResult { public DateTime Air_Date { get; set; } + public int Episode_Number { get; set; } + public string Name { get; set; } + public string Overview { get; set; } + public int Id { get; set; } + public object Production_Code { get; set; } + public int Season_Number { get; set; } + public string Still_Path { get; set; } + public double Vote_Average { get; set; } + public int Vote_Count { get; set; } + public StillImages Images { get; set; } + public ExternalIds External_Ids { get; set; } + public EpisodeCredits Credits { get; set; } + public Tmdb.Models.General.Videos Videos { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/GuestStar.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/GuestStar.cs index 260f3f610f..da5e631717 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/GuestStar.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/GuestStar.cs @@ -3,10 +3,15 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV public class GuestStar { public int Id { get; set; } + public string Name { get; set; } + public string Credit_Id { get; set; } + public string Character { get; set; } + public int Order { get; set; } + public string Profile_Path { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Network.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Network.cs index 5ed3108276..0eba92ae27 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Network.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Network.cs @@ -3,6 +3,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV public class Network { public int Id { get; set; } + public string Name { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Season.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Season.cs index fddf950ee8..2e39c5901c 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Season.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Season.cs @@ -3,9 +3,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV public class Season { public string Air_Date { get; set; } + public int Episode_Count { get; set; } + public int Id { get; set; } + public string Poster_Path { get; set; } + public int Season_Number { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeasonResult.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeasonResult.cs index 13b4c30f80..328bd1ebc7 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeasonResult.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeasonResult.cs @@ -7,15 +7,25 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV public class SeasonResult { public DateTime Air_Date { get; set; } + public List<Episode> Episodes { get; set; } + public string Name { get; set; } + public string Overview { get; set; } + public int Id { get; set; } + public string Poster_Path { get; set; } + public int Season_Number { get; set; } + public Credits Credits { get; set; } + public SeasonImages Images { get; set; } + public ExternalIds External_Ids { get; set; } + public General.Videos Videos { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeriesResult.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeriesResult.cs index 5c1666c77c..499249b8ee 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeriesResult.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeriesResult.cs @@ -7,34 +7,63 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Models.TV public class SeriesResult { public string Backdrop_Path { get; set; } + public List<CreatedBy> Created_By { get; set; } + public List<int> Episode_Run_Time { get; set; } + public DateTime First_Air_Date { get; set; } + public List<Genre> Genres { get; set; } + public string Homepage { get; set; } + public int Id { get; set; } + public bool In_Production { get; set; } + public List<string> Languages { get; set; } + public DateTime Last_Air_Date { get; set; } + public string Name { get; set; } + public List<Network> Networks { get; set; } + public int Number_Of_Episodes { get; set; } + public int Number_Of_Seasons { get; set; } + public string Original_Name { get; set; } + public List<string> Origin_Country { get; set; } + public string Overview { get; set; } + public string Popularity { get; set; } + public string Poster_Path { get; set; } + public List<Season> Seasons { get; set; } + public string Status { get; set; } + public double Vote_Average { get; set; } + public int Vote_Count { get; set; } + public Credits Credits { get; set; } + public Images Images { get; set; } + public Keywords Keywords { get; set; } + public ExternalIds External_Ids { get; set; } + public General.Videos Videos { get; set; } + public ContentRatings Content_Ratings { get; set; } + public string ResultLanguage { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs index a11c89459d..8ecd6b917e 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs @@ -107,6 +107,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies { return 3; } + if (!isLanguageEn) { if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase)) @@ -114,10 +115,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies return 2; } } + if (string.IsNullOrEmpty(i.Language)) { return isLanguageEn ? 3 : 2; } + return 0; }) .ThenByDescending(i => i.CommunityRating ?? 0) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSettings.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSettings.cs index 03669ca67c..3a45d4a559 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSettings.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSettings.cs @@ -5,8 +5,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies internal class TmdbImageSettings { public List<string> backdrop_sizes { get; set; } + public string secure_base_url { get; set; } + public List<string> poster_sizes { get; set; } + public List<string> profile_sizes { get; set; } public string GetImageUrl(string image) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs index 525c0072b3..edd90475d2 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs @@ -98,6 +98,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People { return 3; } + if (!isLanguageEn) { if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase)) @@ -105,10 +106,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People return 2; } } + if (string.IsNullOrEmpty(i.Language)) { return isLanguageEn ? 3 : 2; } + return 0; }) .ThenByDescending(i => i.CommunityRating ?? 0) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs index 654e42a90c..a13d41dc22 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs @@ -173,6 +173,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People { item.ProductionLocations = new string[] { info.Place_Of_Birth }; } + item.Overview = info.Biography; if (DateTime.TryParseExact(info.Birthday, "yyyy-MM-dd", new CultureInfo("en-US"), DateTimeStyles.None, out var date)) diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 8086533eb6..24c29a2199 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -104,6 +104,7 @@ namespace MediaBrowser.Providers.Subtitles _logger.LogError(ex, "Error downloading subtitles from {Provider}", provider.Name); } } + return Array.Empty<RemoteSubtitleInfo>(); } diff --git a/MediaBrowser.Providers/TV/DummySeasonProvider.cs b/MediaBrowser.Providers/TV/DummySeasonProvider.cs index 6e0511971b..92c42e9d8f 100644 --- a/MediaBrowser.Providers/TV/DummySeasonProvider.cs +++ b/MediaBrowser.Providers/TV/DummySeasonProvider.cs @@ -72,6 +72,7 @@ namespace MediaBrowser.Providers.TV { seasons = series.Children.OfType<Season>().ToList(); } + var existingSeason = seasons .FirstOrDefault(i => i.IndexNumber.HasValue && i.IndexNumber.Value == seasonNumber); diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs index 7a753e8324..6d303fab10 100644 --- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs @@ -67,10 +67,12 @@ namespace MediaBrowser.Providers.TV { return false; } + if (!item.ProductionYear.HasValue) { return false; } + return base.IsFullLocalMetadata(item); } diff --git a/RSSDP/DiscoveredSsdpDevice.cs b/RSSDP/DiscoveredSsdpDevice.cs index 1244ce523d..9093199dd6 100644 --- a/RSSDP/DiscoveredSsdpDevice.cs +++ b/RSSDP/DiscoveredSsdpDevice.cs @@ -45,6 +45,7 @@ namespace Rssdp public DateTimeOffset AsAt { get { return _AsAt; } + set { if (_AsAt != value) diff --git a/RSSDP/HttpParserBase.cs b/RSSDP/HttpParserBase.cs index c2eb0bf92f..e7172cb1c0 100644 --- a/RSSDP/HttpParserBase.cs +++ b/RSSDP/HttpParserBase.cs @@ -135,6 +135,7 @@ namespace Rssdp.Infrastructure ParseHeader(line, headers, contentHeaders); } + return lineIndex; } diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index 863f2b15c2..fe49fb7d36 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -311,6 +311,7 @@ namespace Rssdp.Infrastructure public bool IsShared { get { return _IsShared; } + set { _IsShared = value; } } diff --git a/RSSDP/SsdpDevice.cs b/RSSDP/SsdpDevice.cs index 691ba2a14a..f89bafb196 100644 --- a/RSSDP/SsdpDevice.cs +++ b/RSSDP/SsdpDevice.cs @@ -90,6 +90,7 @@ namespace Rssdp { return _DeviceType; } + set { _DeviceType = value; @@ -111,6 +112,7 @@ namespace Rssdp { return _DeviceTypeNamespace; } + set { _DeviceTypeNamespace = value; @@ -130,6 +132,7 @@ namespace Rssdp { return _DeviceVersion; } + set { _DeviceVersion = value; @@ -181,6 +184,7 @@ namespace Rssdp else return _Udn; } + set { _Udn = value; diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs index a626e13b9a..9b48cf31ce 100644 --- a/RSSDP/SsdpDeviceLocator.cs +++ b/RSSDP/SsdpDeviceLocator.cs @@ -579,6 +579,7 @@ namespace Rssdp.Infrastructure return d; } } + return null; } diff --git a/RSSDP/SsdpDevicePublisher.cs b/RSSDP/SsdpDevicePublisher.cs index 5dfb6a8c20..b4cf2fb484 100644 --- a/RSSDP/SsdpDevicePublisher.cs +++ b/RSSDP/SsdpDevicePublisher.cs @@ -156,6 +156,7 @@ namespace Rssdp.Infrastructure public bool SupportPnpRootDevice { get { return _SupportPnpRootDevice; } + set { _SupportPnpRootDevice = value; @@ -564,7 +565,9 @@ namespace Rssdp.Infrastructure private class SearchRequest { public IPEndPoint EndPoint { get; set; } + public DateTime Received { get; set; } + public string SearchTarget { get; set; } public string Key diff --git a/RSSDP/SsdpEmbeddedDevice.cs b/RSSDP/SsdpEmbeddedDevice.cs index 4810703d74..ff644993b5 100644 --- a/RSSDP/SsdpEmbeddedDevice.cs +++ b/RSSDP/SsdpEmbeddedDevice.cs @@ -33,6 +33,7 @@ namespace Rssdp { return _RootDevice; } + internal set { _RootDevice = value; -- cgit v1.2.3 From ef8bec23c4f1ce9b97a155913ed57bc387184e22 Mon Sep 17 00:00:00 2001 From: crobibero <cody@robibe.ro> Date: Wed, 24 Jun 2020 15:09:15 -0600 Subject: revert missing session fields --- Emby.Server.Implementations/Session/SessionManager.cs | 3 ++- MediaBrowser.Controller/Session/SessionInfo.cs | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 75fdedd107..d069d1ada8 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -502,7 +502,8 @@ namespace Emby.Server.Implementations.Session Client = appName, DeviceId = deviceId, ApplicationVersion = appVersion, - Id = key.GetMD5().ToString("N", CultureInfo.InvariantCulture) + Id = key.GetMD5().ToString("N", CultureInfo.InvariantCulture), + ServerId = _appHost.SystemId }; var username = user?.Username; diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 36bc11be4f..4b088998cd 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -108,6 +108,12 @@ namespace MediaBrowser.Controller.Session /// <value>The name of the device.</value> public string DeviceName { get; set; } + /// <summary> + /// Gets or sets the type of the device. + /// </summary> + /// <value>The type of the device.</value> + public string DeviceType { get; set; } + /// <summary> /// Gets or sets the now playing item. /// </summary> @@ -215,8 +221,17 @@ namespace MediaBrowser.Controller.Session public string PlaylistItemId { get; set; } + public string ServerId { get; set; } + public string UserPrimaryImageTag { get; set; } + /// <summary> + /// Gets or sets the supported commands. + /// </summary> + /// <value>The supported commands.</value> + public string[] SupportedCommands + => Capabilities == null ? Array.Empty<string>() : Capabilities.SupportedCommands; + public Tuple<ISessionController, bool> EnsureController<T>(Func<SessionInfo, ISessionController> factory) { var controllers = SessionControllers.ToList(); -- cgit v1.2.3 From 3cca8db9059e7c0316d829f85d05dcb03ae70a95 Mon Sep 17 00:00:00 2001 From: Patrick Barron <barronpm@gmail.com> Date: Tue, 7 Jul 2020 18:20:17 -0400 Subject: Fix log spam from EF Core --- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index d069d1ada8..ca9f95c707 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -296,7 +296,7 @@ namespace Emby.Server.Implementations.Session } catch (DbUpdateConcurrencyException e) { - _logger.LogWarning(e, "Error updating user's last activity date."); + _logger.LogDebug(e, "Error updating user's last activity date."); } } } -- cgit v1.2.3 From 01e781035fc974c329f23892ea95bae66baa82f1 Mon Sep 17 00:00:00 2001 From: Bond_009 <Bond.009@outlook.com> Date: Fri, 24 Jul 2020 16:37:54 +0200 Subject: Fix warnings --- .../Data/SqliteItemRepository.cs | 45 ++++++++------- .../HttpServer/HttpListenerHost.cs | 4 +- Emby.Server.Implementations/IO/FileRefresher.cs | 3 +- .../Library/MediaSourceManager.cs | 35 ++++++------ .../TunerHosts/HdHomerun/HdHomerunManager.cs | 11 ++-- Emby.Server.Implementations/Net/UdpSocket.cs | 43 ++++++++------- .../Playlists/PlaylistManager.cs | 64 +++++++--------------- .../Services/ServiceController.cs | 18 ++++-- .../Services/ServiceHandler.cs | 8 +-- .../Session/SessionManager.cs | 4 +- .../Session/SessionWebSocketListener.cs | 30 +++++----- .../Sorting/AiredEpisodeOrderComparer.cs | 4 +- .../SyncPlay/SyncPlayController.cs | 60 ++++++++++---------- 13 files changed, 163 insertions(+), 166 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 9f5566424c..3ae167890b 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -2776,82 +2776,82 @@ namespace Emby.Server.Implementations.Data private string FixUnicodeChars(string buffer) { - if (buffer.IndexOf('\u2013') > -1) + if (buffer.IndexOf('\u2013', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u2013', '-'); // en dash } - if (buffer.IndexOf('\u2014') > -1) + if (buffer.IndexOf('\u2014', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u2014', '-'); // em dash } - if (buffer.IndexOf('\u2015') > -1) + if (buffer.IndexOf('\u2015', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u2015', '-'); // horizontal bar } - if (buffer.IndexOf('\u2017') > -1) + if (buffer.IndexOf('\u2017', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u2017', '_'); // double low line } - if (buffer.IndexOf('\u2018') > -1) + if (buffer.IndexOf('\u2018', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u2018', '\''); // left single quotation mark } - if (buffer.IndexOf('\u2019') > -1) + if (buffer.IndexOf('\u2019', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u2019', '\''); // right single quotation mark } - if (buffer.IndexOf('\u201a') > -1) + if (buffer.IndexOf('\u201a', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u201a', ','); // single low-9 quotation mark } - if (buffer.IndexOf('\u201b') > -1) + if (buffer.IndexOf('\u201b', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u201b', '\''); // single high-reversed-9 quotation mark } - if (buffer.IndexOf('\u201c') > -1) + if (buffer.IndexOf('\u201c', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u201c', '\"'); // left double quotation mark } - if (buffer.IndexOf('\u201d') > -1) + if (buffer.IndexOf('\u201d', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u201d', '\"'); // right double quotation mark } - if (buffer.IndexOf('\u201e') > -1) + if (buffer.IndexOf('\u201e', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u201e', '\"'); // double low-9 quotation mark } - if (buffer.IndexOf('\u2026') > -1) + if (buffer.IndexOf('\u2026', StringComparison.Ordinal) > -1) { - buffer = buffer.Replace("\u2026", "..."); // horizontal ellipsis + buffer = buffer.Replace("\u2026", "...", StringComparison.Ordinal); // horizontal ellipsis } - if (buffer.IndexOf('\u2032') > -1) + if (buffer.IndexOf('\u2032', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u2032', '\''); // prime } - if (buffer.IndexOf('\u2033') > -1) + if (buffer.IndexOf('\u2033', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u2033', '\"'); // double prime } - if (buffer.IndexOf('\u0060') > -1) + if (buffer.IndexOf('\u0060', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u0060', '\''); // grave accent } - if (buffer.IndexOf('\u00B4') > -1) + if (buffer.IndexOf('\u00B4', StringComparison.Ordinal) > -1) { buffer = buffer.Replace('\u00B4', '\''); // acute accent } @@ -3000,7 +3000,6 @@ namespace Emby.Server.Implementations.Data { connection.RunInTransaction(db => { - var statements = PrepareAll(db, statementTexts).ToList(); if (!isReturningZeroItems) @@ -4670,8 +4669,12 @@ namespace Emby.Server.Implementations.Data if (query.BlockUnratedItems.Length > 1) { - var inClause = string.Join(",", query.BlockUnratedItems.Select(i => "'" + i.ToString() + "'")); - whereClauses.Add(string.Format("(InheritedParentalRatingValue > 0 or UnratedType not in ({0}))", inClause)); + var inClause = string.Join(',', query.BlockUnratedItems.Select(i => "'" + i.ToString() + "'")); + whereClauses.Add( + string.Format( + CultureInfo.InvariantCulture, + "(InheritedParentalRatingValue > 0 or UnratedType not in ({0}))", + inClause)); } if (query.ExcludeInheritedTags.Length > 0) @@ -4680,7 +4683,7 @@ namespace Emby.Server.Implementations.Data if (statement == null) { int index = 0; - string excludedTags = string.Join(",", query.ExcludeInheritedTags.Select(t => paramName + index++)); + string excludedTags = string.Join(',', query.ExcludeInheritedTags.Select(t => paramName + index++)); whereClauses.Add("((select CleanValue from itemvalues where ItemId=Guid and Type=6 and cleanvalue in (" + excludedTags + ")) is null)"); } else diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index c3428ee62a..0d4a789b56 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -449,7 +449,7 @@ namespace Emby.Server.Implementations.HttpServer if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase)) { httpRes.StatusCode = 200; - foreach(var (key, value) in GetDefaultCorsHeaders(httpReq)) + foreach (var (key, value) in GetDefaultCorsHeaders(httpReq)) { httpRes.Headers.Add(key, value); } @@ -486,7 +486,7 @@ namespace Emby.Server.Implementations.HttpServer var handler = GetServiceHandler(httpReq); if (handler != null) { - await handler.ProcessRequestAsync(this, httpReq, httpRes, _logger, cancellationToken).ConfigureAwait(false); + await handler.ProcessRequestAsync(this, httpReq, httpRes, cancellationToken).ConfigureAwait(false); } else { diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs index ef93779aab..fe74f1de73 100644 --- a/Emby.Server.Implementations/IO/FileRefresher.cs +++ b/Emby.Server.Implementations/IO/FileRefresher.cs @@ -21,6 +21,7 @@ namespace Emby.Server.Implementations.IO private readonly List<string> _affectedPaths = new List<string>(); private readonly object _timerLock = new object(); private Timer _timer; + private bool _disposed; public FileRefresher(string path, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger) { @@ -213,11 +214,11 @@ namespace Emby.Server.Implementations.IO } } - private bool _disposed; public void Dispose() { _disposed = true; DisposeTimer(); + GC.SuppressFinalize(this); } } } diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 4e1316abf2..67cf8bf5ba 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -46,8 +46,6 @@ namespace Emby.Server.Implementations.Library private readonly Dictionary<string, ILiveStream> _openStreams = new Dictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase); private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); - private readonly object _disposeLock = new object(); - private IMediaSourceProvider[] _providers; public MediaSourceManager( @@ -623,12 +621,14 @@ namespace Emby.Server.Implementations.Library if (liveStreamInfo is IDirectStreamProvider) { - var info = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest - { - MediaSource = mediaSource, - ExtractChapters = false, - MediaType = DlnaProfileType.Video - }, cancellationToken).ConfigureAwait(false); + var info = await _mediaEncoder.GetMediaInfo( + new MediaInfoRequest + { + MediaSource = mediaSource, + ExtractChapters = false, + MediaType = DlnaProfileType.Video + }, + cancellationToken).ConfigureAwait(false); mediaSource.MediaStreams = info.MediaStreams; mediaSource.Container = info.Container; @@ -859,21 +859,21 @@ namespace Emby.Server.Implementations.Library } } - private Tuple<IMediaSourceProvider, string> GetProvider(string key) + private (IMediaSourceProvider, string) GetProvider(string key) { if (string.IsNullOrEmpty(key)) { - throw new ArgumentException("key"); + throw new ArgumentException("Key can't be empty.", nameof(key)); } var keys = key.Split(new[] { LiveStreamIdDelimeter }, 2); var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture), keys[0], StringComparison.OrdinalIgnoreCase)); - var splitIndex = key.IndexOf(LiveStreamIdDelimeter); + var splitIndex = key.IndexOf(LiveStreamIdDelimeter, StringComparison.Ordinal); var keyId = key.Substring(splitIndex + 1); - return new Tuple<IMediaSourceProvider, string>(provider, keyId); + return (provider, keyId); } /// <summary> @@ -893,15 +893,12 @@ namespace Emby.Server.Implementations.Library { if (dispose) { - lock (_disposeLock) + foreach (var key in _openStreams.Keys.ToList()) { - foreach (var key in _openStreams.Keys.ToList()) - { - var task = CloseLiveStream(key); - - Task.WaitAll(task); - } + CloseLiveStream(key).GetAwaiter().GetResult(); } + + _liveStreamSemaphore.Dispose(); } } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index 57c5b75002..d4a88e299f 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } } - public class HdHomerunManager : IDisposable + public sealed class HdHomerunManager : IDisposable { public const int HdHomeRunPort = 65001; @@ -105,6 +105,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun StopStreaming(socket).GetAwaiter().GetResult(); } } + + GC.SuppressFinalize(this); } public async Task<bool> CheckTunerAvailability(IPAddress remoteIp, int tuner, CancellationToken cancellationToken) @@ -162,7 +164,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } _activeTuner = i; - var lockKeyString = string.Format("{0:d}", lockKeyValue); + var lockKeyString = string.Format(CultureInfo.InvariantCulture, "{0:d}", lockKeyValue); var lockkeyMsg = CreateSetMessage(i, "lockkey", lockKeyString, null); await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false); int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); @@ -173,8 +175,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun continue; } - var commandList = commands.GetCommands(); - foreach (var command in commandList) + foreach (var command in commands.GetCommands()) { var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, lockKeyValue); await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false); @@ -188,7 +189,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } } - var targetValue = string.Format("rtp://{0}:{1}", localIp, localPort); + var targetValue = string.Format(CultureInfo.InvariantCulture, "rtp://{0}:{1}", localIp, localPort); var targetMsg = CreateSetMessage(i, "target", targetValue, lockKeyValue); await stream.WriteAsync(targetMsg, 0, targetMsg.Length, cancellationToken).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/Net/UdpSocket.cs b/Emby.Server.Implementations/Net/UdpSocket.cs index b51c034460..4e25768cf6 100644 --- a/Emby.Server.Implementations/Net/UdpSocket.cs +++ b/Emby.Server.Implementations/Net/UdpSocket.cs @@ -15,13 +15,11 @@ namespace Emby.Server.Implementations.Net public sealed class UdpSocket : ISocket, IDisposable { private Socket _socket; - private int _localPort; + private readonly int _localPort; private bool _disposed = false; public Socket Socket => _socket; - public IPAddress LocalIPAddress { get; } - private readonly SocketAsyncEventArgs _receiveSocketAsyncEventArgs = new SocketAsyncEventArgs() { SocketFlags = SocketFlags.None @@ -51,18 +49,33 @@ namespace Emby.Server.Implementations.Net InitReceiveSocketAsyncEventArgs(); } + public UdpSocket(Socket socket, IPEndPoint endPoint) + { + if (socket == null) + { + throw new ArgumentNullException(nameof(socket)); + } + + _socket = socket; + _socket.Connect(endPoint); + + InitReceiveSocketAsyncEventArgs(); + } + + public IPAddress LocalIPAddress { get; } + private void InitReceiveSocketAsyncEventArgs() { var receiveBuffer = new byte[8192]; _receiveSocketAsyncEventArgs.SetBuffer(receiveBuffer, 0, receiveBuffer.Length); - _receiveSocketAsyncEventArgs.Completed += _receiveSocketAsyncEventArgs_Completed; + _receiveSocketAsyncEventArgs.Completed += OnReceiveSocketAsyncEventArgsCompleted; var sendBuffer = new byte[8192]; _sendSocketAsyncEventArgs.SetBuffer(sendBuffer, 0, sendBuffer.Length); - _sendSocketAsyncEventArgs.Completed += _sendSocketAsyncEventArgs_Completed; + _sendSocketAsyncEventArgs.Completed += OnSendSocketAsyncEventArgsCompleted; } - private void _receiveSocketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e) + private void OnReceiveSocketAsyncEventArgsCompleted(object sender, SocketAsyncEventArgs e) { var tcs = _currentReceiveTaskCompletionSource; if (tcs != null) @@ -86,7 +99,7 @@ namespace Emby.Server.Implementations.Net } } - private void _sendSocketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e) + private void OnSendSocketAsyncEventArgsCompleted(object sender, SocketAsyncEventArgs e) { var tcs = _currentSendTaskCompletionSource; if (tcs != null) @@ -104,19 +117,6 @@ namespace Emby.Server.Implementations.Net } } - public UdpSocket(Socket socket, IPEndPoint endPoint) - { - if (socket == null) - { - throw new ArgumentNullException(nameof(socket)); - } - - _socket = socket; - _socket.Connect(endPoint); - - InitReceiveSocketAsyncEventArgs(); - } - public IAsyncResult BeginReceive(byte[] buffer, int offset, int count, AsyncCallback callback) { ThrowIfDisposed(); @@ -247,6 +247,7 @@ namespace Emby.Server.Implementations.Net } } + /// <inheritdoc /> public void Dispose() { if (_disposed) @@ -255,6 +256,8 @@ namespace Emby.Server.Implementations.Net } _socket?.Dispose(); + _receiveSocketAsyncEventArgs.Dispose(); + _sendSocketAsyncEventArgs.Dispose(); _currentReceiveTaskCompletionSource?.TrySetCanceled(); _currentSendTaskCompletionSource?.TrySetCanceled(); diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 5dd1af4b87..38ceadedbb 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -349,16 +349,14 @@ namespace Emby.Server.Implementations.Playlists AlbumTitle = child.Album }; - var hasAlbumArtist = child as IHasAlbumArtist; - if (hasAlbumArtist != null) + if (child is IHasAlbumArtist hasAlbumArtist) { - entry.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); + entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : null; } - var hasArtist = child as IHasArtist; - if (hasArtist != null) + if (child is IHasArtist hasArtist) { - entry.TrackArtist = hasArtist.Artists.FirstOrDefault(); + entry.TrackArtist = hasArtist.Artists.Count > 0 ? hasArtist.Artists[0] : null; } if (child.RunTimeTicks.HasValue) @@ -385,16 +383,14 @@ namespace Emby.Server.Implementations.Playlists AlbumTitle = child.Album }; - var hasAlbumArtist = child as IHasAlbumArtist; - if (hasAlbumArtist != null) + if (child is IHasAlbumArtist hasAlbumArtist) { - entry.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); + entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : null; } - var hasArtist = child as IHasArtist; - if (hasArtist != null) + if (child is IHasArtist hasArtist) { - entry.TrackArtist = hasArtist.Artists.FirstOrDefault(); + entry.TrackArtist = hasArtist.Artists.Count > 0 ? hasArtist.Artists[0] : null; } if (child.RunTimeTicks.HasValue) @@ -411,8 +407,10 @@ namespace Emby.Server.Implementations.Playlists if (string.Equals(".m3u", extension, StringComparison.OrdinalIgnoreCase)) { - var playlist = new M3uPlaylist(); - playlist.IsExtended = true; + var playlist = new M3uPlaylist + { + IsExtended = true + }; foreach (var child in item.GetLinkedChildren()) { var entry = new M3uPlaylistEntry() @@ -422,10 +420,9 @@ namespace Emby.Server.Implementations.Playlists Album = child.Album }; - var hasAlbumArtist = child as IHasAlbumArtist; - if (hasAlbumArtist != null) + if (child is IHasAlbumArtist hasAlbumArtist) { - entry.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); + entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : null; } if (child.RunTimeTicks.HasValue) @@ -453,10 +450,9 @@ namespace Emby.Server.Implementations.Playlists Album = child.Album }; - var hasAlbumArtist = child as IHasAlbumArtist; - if (hasAlbumArtist != null) + if (child is IHasAlbumArtist hasAlbumArtist) { - entry.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); + entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : null; } if (child.RunTimeTicks.HasValue) @@ -514,7 +510,7 @@ namespace Emby.Server.Implementations.Playlists if (!folderPath.EndsWith(Path.DirectorySeparatorChar)) { - folderPath = folderPath + Path.DirectorySeparatorChar; + folderPath += Path.DirectorySeparatorChar; } var folderUri = new Uri(folderPath); @@ -537,32 +533,12 @@ namespace Emby.Server.Implementations.Playlists return relativePath; } - private static string UnEscape(string content) - { - if (content == null) - { - return content; - } - - return content.Replace("&", "&").Replace("'", "'").Replace(""", "\"").Replace(">", ">").Replace("<", "<"); - } - - private static string Escape(string content) - { - if (content == null) - { - return null; - } - - return content.Replace("&", "&").Replace("'", "'").Replace("\"", """).Replace(">", ">").Replace("<", "<"); - } - public Folder GetPlaylistsFolder(Guid userId) { - var typeName = "PlaylistsFolder"; + const string TypeName = "PlaylistsFolder"; - return _libraryManager.RootFolder.Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, typeName, StringComparison.Ordinal)) ?? - _libraryManager.GetUserRootFolder().Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, typeName, StringComparison.Ordinal)); + return _libraryManager.RootFolder.Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, TypeName, StringComparison.Ordinal)) ?? + _libraryManager.GetUserRootFolder().Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, TypeName, StringComparison.Ordinal)); } } } diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs index d884d4f379..47e7261e83 100644 --- a/Emby.Server.Implementations/Services/ServiceController.cs +++ b/Emby.Server.Implementations/Services/ServiceController.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Threading.Tasks; using Emby.Server.Implementations.HttpServer; using MediaBrowser.Model.Services; @@ -91,12 +92,22 @@ namespace Emby.Server.Implementations.Services { if (restPath.Path[0] != '/') { - throw new ArgumentException(string.Format("Route '{0}' on '{1}' must start with a '/'", restPath.Path, restPath.RequestType.GetMethodName())); + throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + "Route '{0}' on '{1}' must start with a '/'", + restPath.Path, + restPath.RequestType.GetMethodName())); } if (restPath.Path.IndexOfAny(InvalidRouteChars) != -1) { - throw new ArgumentException(string.Format("Route '{0}' on '{1}' contains invalid chars. ", restPath.Path, restPath.RequestType.GetMethodName())); + throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + "Route '{0}' on '{1}' contains invalid chars. ", + restPath.Path, + restPath.RequestType.GetMethodName())); } if (RestPathMap.TryGetValue(restPath.FirstMatchHashKey, out List<RestPath> pathsAtFirstMatch)) @@ -179,8 +190,7 @@ namespace Emby.Server.Implementations.Services var service = httpHost.CreateInstance(serviceType); - var serviceRequiresContext = service as IRequiresRequest; - if (serviceRequiresContext != null) + if (service is IRequiresRequest serviceRequiresContext) { serviceRequiresContext.Request = req; } diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs index 3d4e1ca77b..148cf408cd 100644 --- a/Emby.Server.Implementations/Services/ServiceHandler.cs +++ b/Emby.Server.Implementations/Services/ServiceHandler.cs @@ -67,7 +67,7 @@ namespace Emby.Server.Implementations.Services } } - public async Task ProcessRequestAsync(HttpListenerHost httpHost, IRequest httpReq, HttpResponse httpRes, ILogger logger, CancellationToken cancellationToken) + public async Task ProcessRequestAsync(HttpListenerHost httpHost, IRequest httpReq, HttpResponse httpRes, CancellationToken cancellationToken) { httpReq.Items["__route"] = _restPath; @@ -76,10 +76,10 @@ namespace Emby.Server.Implementations.Services httpReq.ResponseContentType = _responseContentType; } - var request = await CreateRequest(httpHost, httpReq, _restPath, logger).ConfigureAwait(false); + var request = await CreateRequest(httpHost, httpReq, _restPath).ConfigureAwait(false); httpHost.ApplyRequestFilters(httpReq, httpRes, request); - + httpRes.HttpContext.SetServiceStackRequest(httpReq); var response = await httpHost.ServiceController.Execute(httpHost, request, httpReq).ConfigureAwait(false); @@ -92,7 +92,7 @@ namespace Emby.Server.Implementations.Services await ResponseHelper.WriteToResponse(httpRes, httpReq, response, cancellationToken).ConfigureAwait(false); } - public static async Task<object> CreateRequest(HttpListenerHost host, IRequest httpReq, RestPath restPath, ILogger logger) + public static async Task<object> CreateRequest(HttpListenerHost host, IRequest httpReq, RestPath restPath) { var requestType = restPath.RequestType; diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index ca9f95c707..862a7296ca 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -848,8 +848,8 @@ namespace Emby.Server.Implementations.Session /// </summary> /// <param name="info">The info.</param> /// <returns>Task.</returns> - /// <exception cref="ArgumentNullException">info</exception> - /// <exception cref="ArgumentOutOfRangeException">positionTicks</exception> + /// <exception cref="ArgumentNullException"><c>info</c> is <c>null</c>.</exception> + /// <exception cref="ArgumentOutOfRangeException"><c>info.PositionTicks</c> is <c>null</c> or negative.</exception> public async Task OnPlaybackStopped(PlaybackStopInfo info) { CheckDisposed(); diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index b9db6ecd0e..8bebd37dc8 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -93,7 +93,7 @@ namespace Emby.Server.Implementations.Session if (session != null) { EnsureController(session, e.Argument); - await KeepAliveWebSocket(e.Argument); + await KeepAliveWebSocket(e.Argument).ConfigureAwait(false); } else { @@ -177,7 +177,7 @@ namespace Emby.Server.Implementations.Session // Notify WebSocket about timeout try { - await SendForceKeepAlive(webSocket); + await SendForceKeepAlive(webSocket).ConfigureAwait(false); } catch (WebSocketException exception) { @@ -233,6 +233,7 @@ namespace Emby.Server.Implementations.Session if (_keepAliveCancellationToken != null) { _keepAliveCancellationToken.Cancel(); + _keepAliveCancellationToken.Dispose(); _keepAliveCancellationToken = null; } } @@ -268,7 +269,7 @@ namespace Emby.Server.Implementations.Session lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout).ToList(); } - if (inactive.Any()) + if (inactive.Count > 0) { _logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count); } @@ -277,7 +278,7 @@ namespace Emby.Server.Implementations.Session { try { - await SendForceKeepAlive(webSocket); + await SendForceKeepAlive(webSocket).ConfigureAwait(false); } catch (WebSocketException exception) { @@ -288,7 +289,7 @@ namespace Emby.Server.Implementations.Session lock (_webSocketsLock) { - if (lost.Any()) + if (lost.Count > 0) { _logger.LogInformation("Lost {0} WebSockets.", lost.Count); foreach (var webSocket in lost) @@ -298,7 +299,7 @@ namespace Emby.Server.Implementations.Session } } - if (!_webSockets.Any()) + if (_webSockets.Count == 0) { StopKeepAlive(); } @@ -312,11 +313,13 @@ namespace Emby.Server.Implementations.Session /// <returns>Task.</returns> private Task SendForceKeepAlive(IWebSocketConnection webSocket) { - return webSocket.SendAsync(new WebSocketMessage<int> - { - MessageType = "ForceKeepAlive", - Data = WebSocketLostTimeout - }, CancellationToken.None); + return webSocket.SendAsync( + new WebSocketMessage<int> + { + MessageType = "ForceKeepAlive", + Data = WebSocketLostTimeout + }, + CancellationToken.None); } /// <summary> @@ -330,12 +333,11 @@ namespace Emby.Server.Implementations.Session { while (!cancellationToken.IsCancellationRequested) { - await callback(); - Task task = Task.Delay(interval, cancellationToken); + await callback().ConfigureAwait(false); try { - await task; + await Task.Delay(interval, cancellationToken).ConfigureAwait(false); } catch (TaskCanceledException) { diff --git a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs index 2b7d818be0..1f68a9c810 100644 --- a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs +++ b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs @@ -154,8 +154,8 @@ namespace Emby.Server.Implementations.Sorting private static int CompareEpisodes(Episode x, Episode y) { - var xValue = (x.ParentIndexNumber ?? -1) * 1000 + (x.IndexNumber ?? -1); - var yValue = (y.ParentIndexNumber ?? -1) * 1000 + (y.IndexNumber ?? -1); + var xValue = ((x.ParentIndexNumber ?? -1) * 1000) + (x.IndexNumber ?? -1); + var yValue = ((y.ParentIndexNumber ?? -1) * 1000) + (y.IndexNumber ?? -1); return xValue.CompareTo(yValue); } diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs index 39d17833ff..80b977731c 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -27,14 +28,17 @@ namespace Emby.Server.Implementations.SyncPlay /// All sessions will receive the message. /// </summary> AllGroup = 0, + /// <summary> /// Only the specified session will receive the message. /// </summary> CurrentSession = 1, + /// <summary> /// All sessions, except the current one, will receive the message. /// </summary> AllExceptCurrentSession = 2, + /// <summary> /// Only sessions that are not buffering will receive the message. /// </summary> @@ -56,15 +60,6 @@ namespace Emby.Server.Implementations.SyncPlay /// </summary> private readonly GroupInfo _group = new GroupInfo(); - /// <inheritdoc /> - public Guid GetGroupId() => _group.GroupId; - - /// <inheritdoc /> - public Guid GetPlayingItemId() => _group.PlayingItem.Id; - - /// <inheritdoc /> - public bool IsGroupEmpty() => _group.IsEmpty(); - /// <summary> /// Initializes a new instance of the <see cref="SyncPlayController" /> class. /// </summary> @@ -78,6 +73,15 @@ namespace Emby.Server.Implementations.SyncPlay _syncPlayManager = syncPlayManager; } + /// <inheritdoc /> + public Guid GetGroupId() => _group.GroupId; + + /// <inheritdoc /> + public Guid GetPlayingItemId() => _group.PlayingItem.Id; + + /// <inheritdoc /> + public bool IsGroupEmpty() => _group.IsEmpty(); + /// <summary> /// Converts DateTime to UTC string. /// </summary> @@ -85,7 +89,7 @@ namespace Emby.Server.Implementations.SyncPlay /// <value>The UTC string.</value> private string DateToUTCString(DateTime date) { - return date.ToUniversalTime().ToString("o"); + return date.ToUniversalTime().ToString("o", CultureInfo.InvariantCulture); } /// <summary> @@ -94,23 +98,23 @@ namespace Emby.Server.Implementations.SyncPlay /// <param name="from">The current session.</param> /// <param name="type">The filtering type.</param> /// <value>The array of sessions matching the filter.</value> - private SessionInfo[] FilterSessions(SessionInfo from, BroadcastType type) + private IEnumerable<SessionInfo> FilterSessions(SessionInfo from, BroadcastType type) { switch (type) { case BroadcastType.CurrentSession: return new SessionInfo[] { from }; case BroadcastType.AllGroup: - return _group.Participants.Values.Select( - session => session.Session).ToArray(); + return _group.Participants.Values + .Select(session => session.Session); case BroadcastType.AllExceptCurrentSession: - return _group.Participants.Values.Select( - session => session.Session).Where( - session => !session.Id.Equals(from.Id)).ToArray(); + return _group.Participants.Values + .Select(session => session.Session) + .Where(session => !session.Id.Equals(from.Id, StringComparison.Ordinal)); case BroadcastType.AllReady: - return _group.Participants.Values.Where( - session => !session.IsBuffering).Select( - session => session.Session).ToArray(); + return _group.Participants.Values + .Where(session => !session.IsBuffering) + .Select(session => session.Session); default: return Array.Empty<SessionInfo>(); } @@ -128,10 +132,9 @@ namespace Emby.Server.Implementations.SyncPlay { IEnumerable<Task> GetTasks() { - SessionInfo[] sessions = FilterSessions(from, type); - foreach (var session in sessions) + foreach (var session in FilterSessions(from, type)) { - yield return _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), message, cancellationToken); + yield return _sessionManager.SendSyncPlayGroupUpdate(session.Id, message, cancellationToken); } } @@ -150,10 +153,9 @@ namespace Emby.Server.Implementations.SyncPlay { IEnumerable<Task> GetTasks() { - SessionInfo[] sessions = FilterSessions(from, type); - foreach (var session in sessions) + foreach (var session in FilterSessions(from, type)) { - yield return _sessionManager.SendSyncPlayCommand(session.Id.ToString(), message, cancellationToken); + yield return _sessionManager.SendSyncPlayCommand(session.Id, message, cancellationToken); } } @@ -236,9 +238,11 @@ namespace Emby.Server.Implementations.SyncPlay } else { - var playRequest = new PlayRequest(); - playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id }; - playRequest.StartPositionTicks = _group.PositionTicks; + var playRequest = new PlayRequest + { + ItemIds = new Guid[] { _group.PlayingItem.Id }, + StartPositionTicks = _group.PositionTicks + }; var update = NewSyncPlayGroupUpdate(GroupUpdateType.PrepareSession, playRequest); SendGroupUpdate(session, BroadcastType.CurrentSession, update, cancellationToken); } -- cgit v1.2.3 From ca1f15af19e26f8f610a7b56cd6b15a6a308a58f Mon Sep 17 00:00:00 2001 From: Patrick Barron <barronpm@gmail.com> Date: Thu, 13 Aug 2020 20:48:28 -0400 Subject: Move GenericEventArgs to Jellyfin.Data.Events --- Emby.Dlna/PlayTo/PlayToController.cs | 2 +- Emby.Dlna/PlayTo/PlayToManager.cs | 2 +- Emby.Dlna/Ssdp/DeviceDiscovery.cs | 2 +- Emby.Notifications/NotificationEntryPoint.cs | 2 +- .../Activity/ActivityLogEntryPoint.cs | 2 +- .../Configuration/ServerConfigurationManager.cs | 2 +- .../Devices/DeviceManager.cs | 2 +- .../EntryPoints/ExternalPortForwarding.cs | 2 +- .../EntryPoints/LibraryChangedNotifier.cs | 2 +- .../EntryPoints/RecordingNotifier.cs | 9 ++++---- .../EntryPoints/ServerEventNotifier.cs | 2 +- .../HttpServer/HttpListenerHost.cs | 2 +- .../LiveTv/EmbyTV/EmbyTV.cs | 2 +- .../LiveTv/EmbyTV/TimerManager.cs | 2 +- .../LiveTv/LiveTvManager.cs | 2 +- .../ScheduledTasks/ScheduledTaskWorker.cs | 3 +-- .../ScheduledTasks/TaskManager.cs | 2 +- .../Session/SessionManager.cs | 2 +- .../Session/SessionWebSocketListener.cs | 2 +- .../ActivityLogWebSocketListener.cs | 2 +- .../ScheduledTasksWebSocketListener.cs | 2 +- Jellyfin.Data/Events/GenericEventArgs.cs | 26 ++++++++++++++++++++++ .../Activity/ActivityManager.cs | 2 +- .../Users/DeviceAccessEntryPoint.cs | 2 +- .../Users/UserManager.cs | 2 +- MediaBrowser.Controller/Devices/IDeviceManager.cs | 2 +- MediaBrowser.Controller/Library/IUserManager.cs | 2 +- MediaBrowser.Controller/LiveTv/ILiveTvManager.cs | 2 +- MediaBrowser.Controller/Net/IHttpServer.cs | 2 +- .../Providers/IProviderManager.cs | 2 +- MediaBrowser.Controller/Session/ISessionManager.cs | 2 +- MediaBrowser.Model/Activity/IActivityManager.cs | 2 +- MediaBrowser.Model/Dlna/IDeviceDiscovery.cs | 2 +- MediaBrowser.Model/Events/GenericEventArgs.cs | 26 ---------------------- MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs | 2 +- MediaBrowser.Model/Tasks/ITaskManager.cs | 2 +- MediaBrowser.Providers/Manager/ProviderManager.cs | 2 +- 37 files changed, 65 insertions(+), 65 deletions(-) create mode 100644 Jellyfin.Data/Events/GenericEventArgs.cs delete mode 100644 MediaBrowser.Model/Events/GenericEventArgs.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 92a93d4349..1f0da8d753 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Emby.Dlna.Didl; using Jellyfin.Data.Entities; +using Jellyfin.Data.Events; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; @@ -18,7 +19,6 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Session; using Microsoft.AspNetCore.WebUtilities; diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index 512589e4d7..00edff1a65 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -16,7 +17,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs index 7daac96d1d..18ee188fd0 100644 --- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs +++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs @@ -3,9 +3,9 @@ using System; using System.Collections.Generic; using System.Linq; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Events; using Rssdp; using Rssdp.Infrastructure; diff --git a/Emby.Notifications/NotificationEntryPoint.cs b/Emby.Notifications/NotificationEntryPoint.cs index b923fd26ce..ded22d26cc 100644 --- a/Emby.Notifications/NotificationEntryPoint.cs +++ b/Emby.Notifications/NotificationEntryPoint.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Events; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; @@ -13,7 +14,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Notifications; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Activity; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Notifications; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 39b6361b7c..75a7916869 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -2,6 +2,7 @@ using System; using System.Globalization; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Events; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; using MediaBrowser.Controller.Authentication; @@ -9,7 +10,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Activity; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Notifications; using MediaBrowser.Model.Updates; diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs index a15295fca4..f05a30a897 100644 --- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -2,11 +2,11 @@ using System; using System.Globalization; using System.IO; using Emby.Server.Implementations.AppBase; +using Jellyfin.Data.Events; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Events; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index cc4b407f5c..f98c694c46 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -7,13 +7,13 @@ using System.IO; using System.Linq; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Data.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Security; using MediaBrowser.Model.Devices; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index 9fce49425e..2e8cc76d23 100644 --- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -7,11 +7,11 @@ using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Events; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Events; using Microsoft.Extensions.Logging; using Mono.Nat; diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 1deef7f720..c9d21d9638 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -15,7 +16,6 @@ using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.EntryPoints diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index 6327359106..44d2580d68 100644 --- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Enums; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Plugins; @@ -43,22 +44,22 @@ namespace Emby.Server.Implementations.EntryPoints return Task.CompletedTask; } - private async void OnLiveTvManagerSeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e) + private async void OnLiveTvManagerSeriesTimerCreated(object sender, GenericEventArgs<TimerEventInfo> e) { await SendMessage("SeriesTimerCreated", e.Argument).ConfigureAwait(false); } - private async void OnLiveTvManagerTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e) + private async void OnLiveTvManagerTimerCreated(object sender, GenericEventArgs<TimerEventInfo> e) { await SendMessage("TimerCreated", e.Argument).ConfigureAwait(false); } - private async void OnLiveTvManagerSeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e) + private async void OnLiveTvManagerSeriesTimerCancelled(object sender, GenericEventArgs<TimerEventInfo> e) { await SendMessage("SeriesTimerCancelled", e.Argument).ConfigureAwait(false); } - private async void OnLiveTvManagerTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e) + private async void OnLiveTvManagerTimerCancelled(object sender, GenericEventArgs<TimerEventInfo> e) { await SendMessage("TimerCancelled", e.Argument).ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs index 826d4d8dc3..d023591e19 100644 --- a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs @@ -4,13 +4,13 @@ using System.Globalization; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Events; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; using MediaBrowser.Controller; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Updates; diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index dafdd5b7bf..fe39bb4b29 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -12,13 +12,13 @@ using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Services; using Emby.Server.Implementations.SocketSharp; +using Jellyfin.Data.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 80e09f0a34..09c52d95bb 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using System.Xml; using Emby.Server.Implementations.Library; using Jellyfin.Data.Enums; +using Jellyfin.Data.Events; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; @@ -29,7 +30,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; using MediaBrowser.Model.IO; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.MediaInfo; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index 285a59a249..dd479b7d1b 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -5,8 +5,8 @@ using System.Collections.Concurrent; using System.Globalization; using System.Linq; using System.Threading; +using Jellyfin.Data.Events; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Model.Events; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 1b075d86a8..ef505c78e6 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Emby.Server.Implementations.Library; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Data.Events; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; @@ -24,7 +25,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.LiveTv; diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 8a900f42cd..36faae65d2 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -6,11 +6,10 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Events; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index 81096026bd..fff52ff88a 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -5,8 +5,8 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Jellyfin.Data.Events; using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 862a7296ca..6e41244639 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Data.Events; using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; @@ -24,7 +25,6 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Model.Devices; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 8bebd37dc8..1da7a64730 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -4,9 +4,9 @@ using System.Linq; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Net; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; diff --git a/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs index 6395b8d62f..849b3b7095 100644 --- a/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs +++ b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs @@ -1,8 +1,8 @@ using System; using System.Threading.Tasks; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Activity; -using MediaBrowser.Model.Events; using Microsoft.Extensions.Logging; namespace Jellyfin.Api.WebSocketListeners diff --git a/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs index 12f815ff75..8a966c1376 100644 --- a/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs +++ b/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; diff --git a/Jellyfin.Data/Events/GenericEventArgs.cs b/Jellyfin.Data/Events/GenericEventArgs.cs new file mode 100644 index 0000000000..7b9a5111ee --- /dev/null +++ b/Jellyfin.Data/Events/GenericEventArgs.cs @@ -0,0 +1,26 @@ +using System; + +namespace Jellyfin.Data.Events +{ + /// <summary> + /// Provides a generic EventArgs subclass that can hold any kind of object. + /// </summary> + /// <typeparam name="T">The type of this event.</typeparam> + public class GenericEventArgs<T> : EventArgs + { + /// <summary> + /// Initializes a new instance of the <see cref="GenericEventArgs{T}"/> class. + /// </summary> + /// <param name="arg">The argument.</param> + public GenericEventArgs(T arg) + { + Argument = arg; + } + + /// <summary> + /// Gets the argument. + /// </summary> + /// <value>The argument.</value> + public T Argument { get; } + } +} diff --git a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs index 2deefbe819..09f2611e4e 100644 --- a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs +++ b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs @@ -2,8 +2,8 @@ using System; using System.Linq; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Events; using MediaBrowser.Model.Activity; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Querying; namespace Jellyfin.Server.Implementations.Activity diff --git a/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs b/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs index 140853e529..1fb89c4a63 100644 --- a/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs +++ b/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs @@ -4,12 +4,12 @@ using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Events; namespace Jellyfin.Server.Implementations.Users { diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 11402ee055..267c1c1033 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -10,6 +10,7 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Data.Events; using MediaBrowser.Common; using MediaBrowser.Common.Cryptography; using MediaBrowser.Common.Extensions; @@ -21,7 +22,6 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Users; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs index 7d279230b5..a038d84d81 100644 --- a/MediaBrowser.Controller/Devices/IDeviceManager.cs +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -1,7 +1,7 @@ using System; using Jellyfin.Data.Entities; +using Jellyfin.Data.Events; using MediaBrowser.Model.Devices; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 6685861a9a..c8d8375b38 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -2,9 +2,9 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Events; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Users; namespace MediaBrowser.Controller.Library diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index f619b011bf..d6f629a1b9 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -3,11 +3,11 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Events; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Querying; diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs index e6609fae38..b04ebda8ca 100644 --- a/MediaBrowser.Controller/Net/IHttpServer.cs +++ b/MediaBrowser.Controller/Net/IHttpServer.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using MediaBrowser.Model.Events; +using Jellyfin.Data.Events; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index 955db0278c..ef744ee3c1 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -4,12 +4,12 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Events; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Providers; namespace MediaBrowser.Controller.Providers diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index e54f210506..d461a9281a 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -2,11 +2,11 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Security; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Events; using MediaBrowser.Model.Session; using MediaBrowser.Model.SyncPlay; diff --git a/MediaBrowser.Model/Activity/IActivityManager.cs b/MediaBrowser.Model/Activity/IActivityManager.cs index 9dab5e77b7..2362f7e927 100644 --- a/MediaBrowser.Model/Activity/IActivityManager.cs +++ b/MediaBrowser.Model/Activity/IActivityManager.cs @@ -4,7 +4,7 @@ using System; using System.Linq; using System.Threading.Tasks; using Jellyfin.Data.Entities; -using MediaBrowser.Model.Events; +using Jellyfin.Data.Events; using MediaBrowser.Model.Querying; namespace MediaBrowser.Model.Activity diff --git a/MediaBrowser.Model/Dlna/IDeviceDiscovery.cs b/MediaBrowser.Model/Dlna/IDeviceDiscovery.cs index 76c9a4b044..05209e53d0 100644 --- a/MediaBrowser.Model/Dlna/IDeviceDiscovery.cs +++ b/MediaBrowser.Model/Dlna/IDeviceDiscovery.cs @@ -1,7 +1,7 @@ #pragma warning disable CS1591 using System; -using MediaBrowser.Model.Events; +using Jellyfin.Data.Events; namespace MediaBrowser.Model.Dlna { diff --git a/MediaBrowser.Model/Events/GenericEventArgs.cs b/MediaBrowser.Model/Events/GenericEventArgs.cs deleted file mode 100644 index 347ea2281e..0000000000 --- a/MediaBrowser.Model/Events/GenericEventArgs.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; - -namespace MediaBrowser.Model.Events -{ - /// <summary> - /// Provides a generic EventArgs subclass that can hold any kind of object. - /// </summary> - /// <typeparam name="T">The type of this event.</typeparam> - public class GenericEventArgs<T> : EventArgs - { - /// <summary> - /// Initializes a new instance of the <see cref="GenericEventArgs{T}"/> class. - /// </summary> - /// <param name="arg">The argument.</param> - public GenericEventArgs(T arg) - { - Argument = arg; - } - - /// <summary> - /// Gets the argument. - /// </summary> - /// <value>The argument.</value> - public T Argument { get; } - } -} diff --git a/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs b/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs index b08acba2c6..2f05e08c51 100644 --- a/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs +++ b/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs @@ -1,6 +1,6 @@ #nullable disable using System; -using MediaBrowser.Model.Events; +using Jellyfin.Data.Events; namespace MediaBrowser.Model.Tasks { diff --git a/MediaBrowser.Model/Tasks/ITaskManager.cs b/MediaBrowser.Model/Tasks/ITaskManager.cs index 363773ff74..02b29074e3 100644 --- a/MediaBrowser.Model/Tasks/ITaskManager.cs +++ b/MediaBrowser.Model/Tasks/ITaskManager.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using MediaBrowser.Model.Events; +using Jellyfin.Data.Events; namespace MediaBrowser.Model.Tasks { diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index bbd7166e68..9f63c60465 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -9,6 +9,7 @@ using System.Net.Mime; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Events; using MediaBrowser.Common.Net; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; @@ -22,7 +23,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Subtitles; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using MediaBrowser.Model.Providers; -- cgit v1.2.3 From adabb4b84285cdb8df769986a681b0440b1e80bd Mon Sep 17 00:00:00 2001 From: Patrick Barron <barronpm@gmail.com> Date: Sat, 15 Aug 2020 15:55:31 -0400 Subject: Use IEventManager in SessionManager --- .../Session/SessionManager.cs | 115 +++++++++++---------- 1 file changed, 60 insertions(+), 55 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 6e41244639..dad414142b 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -18,6 +18,8 @@ using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Events; +using MediaBrowser.Controller.Events.Session; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; @@ -40,25 +42,16 @@ namespace Emby.Server.Implementations.Session /// </summary> public class SessionManager : ISessionManager, IDisposable { - /// <summary> - /// The user data repository. - /// </summary> private readonly IUserDataManager _userDataManager; - - /// <summary> - /// The logger. - /// </summary> private readonly ILogger<SessionManager> _logger; - + private readonly IEventManager _eventManager; private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; private readonly IMusicManager _musicManager; private readonly IDtoService _dtoService; private readonly IImageProcessor _imageProcessor; private readonly IMediaSourceManager _mediaSourceManager; - private readonly IServerApplicationHost _appHost; - private readonly IAuthenticationRepository _authRepo; private readonly IDeviceManager _deviceManager; @@ -75,6 +68,7 @@ namespace Emby.Server.Implementations.Session public SessionManager( ILogger<SessionManager> logger, + IEventManager eventManager, IUserDataManager userDataManager, ILibraryManager libraryManager, IUserManager userManager, @@ -87,6 +81,7 @@ namespace Emby.Server.Implementations.Session IMediaSourceManager mediaSourceManager) { _logger = logger; + _eventManager = eventManager; _userDataManager = userDataManager; _libraryManager = libraryManager; _userManager = userManager; @@ -209,6 +204,8 @@ namespace Emby.Server.Implementations.Session } } + _eventManager.Publish(new SessionStartedEventArgs(info)); + EventHelper.QueueEventIfNotNull( SessionStarted, this, @@ -230,6 +227,8 @@ namespace Emby.Server.Implementations.Session }, _logger); + _eventManager.Publish(new SessionEndedEventArgs(info)); + info.Dispose(); } @@ -667,22 +666,26 @@ namespace Emby.Server.Implementations.Session } } + var eventArgs = new PlaybackProgressEventArgs + { + Item = libraryItem, + Users = users, + MediaSourceId = info.MediaSourceId, + MediaInfo = info.Item, + DeviceName = session.DeviceName, + ClientName = session.Client, + DeviceId = session.DeviceId, + Session = session + }; + + await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false); + // Nothing to save here // Fire events to inform plugins EventHelper.QueueEventIfNotNull( PlaybackStart, this, - new PlaybackProgressEventArgs - { - Item = libraryItem, - Users = users, - MediaSourceId = info.MediaSourceId, - MediaInfo = info.Item, - DeviceName = session.DeviceName, - ClientName = session.Client, - DeviceId = session.DeviceId, - Session = session - }, + eventArgs, _logger); StartIdleCheckTimer(); @@ -750,23 +753,25 @@ namespace Emby.Server.Implementations.Session } } - PlaybackProgress?.Invoke( - this, - new PlaybackProgressEventArgs - { - Item = libraryItem, - Users = users, - PlaybackPositionTicks = session.PlayState.PositionTicks, - MediaSourceId = session.PlayState.MediaSourceId, - MediaInfo = info.Item, - DeviceName = session.DeviceName, - ClientName = session.Client, - DeviceId = session.DeviceId, - IsPaused = info.IsPaused, - PlaySessionId = info.PlaySessionId, - IsAutomated = isAutomated, - Session = session - }); + var eventArgs = new PlaybackProgressEventArgs + { + Item = libraryItem, + Users = users, + PlaybackPositionTicks = session.PlayState.PositionTicks, + MediaSourceId = session.PlayState.MediaSourceId, + MediaInfo = info.Item, + DeviceName = session.DeviceName, + ClientName = session.Client, + DeviceId = session.DeviceId, + IsPaused = info.IsPaused, + PlaySessionId = info.PlaySessionId, + IsAutomated = isAutomated, + Session = session + }; + + await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false); + + PlaybackProgress?.Invoke(this, eventArgs); if (!isAutomated) { @@ -943,23 +948,23 @@ namespace Emby.Server.Implementations.Session } } - EventHelper.QueueEventIfNotNull( - PlaybackStopped, - this, - new PlaybackStopEventArgs - { - Item = libraryItem, - Users = users, - PlaybackPositionTicks = info.PositionTicks, - PlayedToCompletion = playedToCompletion, - MediaSourceId = info.MediaSourceId, - MediaInfo = info.Item, - DeviceName = session.DeviceName, - ClientName = session.Client, - DeviceId = session.DeviceId, - Session = session - }, - _logger); + var eventArgs = new PlaybackStopEventArgs + { + Item = libraryItem, + Users = users, + PlaybackPositionTicks = info.PositionTicks, + PlayedToCompletion = playedToCompletion, + MediaSourceId = info.MediaSourceId, + MediaInfo = info.Item, + DeviceName = session.DeviceName, + ClientName = session.Client, + DeviceId = session.DeviceId, + Session = session + }; + + await _eventManager.PublishAsync(eventArgs).ConfigureAwait(false); + + EventHelper.QueueEventIfNotNull(PlaybackStopped, this, eventArgs, _logger); } private bool OnPlaybackStopped(User user, BaseItem item, long? positionTicks, bool playbackFailed) -- cgit v1.2.3 From 5f1a86324170387f12602d77dad7249faf30548f Mon Sep 17 00:00:00 2001 From: Matt Montgomery <33811686+ConfusedPolarBear@users.noreply.github.com> Date: Mon, 17 Aug 2020 16:36:45 -0500 Subject: Apply suggestions from code review --- .../QuickConnect/QuickConnectManager.cs | 38 +++++++++------------- .../Session/SessionManager.cs | 2 +- Jellyfin.Api/Controllers/QuickConnectController.cs | 34 +++++++++---------- Jellyfin.Api/Controllers/UserController.cs | 4 +-- .../QuickConnect/IQuickConnect.cs | 12 +++---- .../QuickConnect/QuickConnectResult.cs | 5 --- 6 files changed, 40 insertions(+), 55 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs index 949c3b5058..52e934229a 100644 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs @@ -3,17 +3,16 @@ using System.Collections.Concurrent; using System.Globalization; using System.Linq; using System.Security.Cryptography; +using MediaBrowser.Common; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; +using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.QuickConnect; using MediaBrowser.Controller.Security; using MediaBrowser.Model.QuickConnect; -using Microsoft.AspNetCore.Http; -using MediaBrowser.Common; using Microsoft.Extensions.Logging; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Authentication; namespace Emby.Server.Implementations.QuickConnect { @@ -60,7 +59,7 @@ namespace Emby.Server.Implementations.QuickConnect public int CodeLength { get; set; } = 6; /// <inheritdoc/> - public string TokenNamePrefix { get; set; } = "QuickConnect-"; + public string TokenName { get; set; } = "QuickConnect"; /// <inheritdoc/> public QuickConnectState State { get; private set; } = QuickConnectState.Unavailable; @@ -82,7 +81,7 @@ namespace Emby.Server.Implementations.QuickConnect /// <inheritdoc/> public void Activate() { - DateActivated = DateTime.Now; + DateActivated = DateTime.UtcNow; SetState(QuickConnectState.Active); } @@ -101,7 +100,7 @@ namespace Emby.Server.Implementations.QuickConnect } /// <inheritdoc/> - public QuickConnectResult TryConnect(string friendlyName) + public QuickConnectResult TryConnect() { ExpireRequests(); @@ -111,14 +110,11 @@ namespace Emby.Server.Implementations.QuickConnect throw new AuthenticationException("Quick connect is not active on this server"); } - _logger.LogDebug("Got new quick connect request from {friendlyName}", friendlyName); - var code = GenerateCode(); var result = new QuickConnectResult() { Secret = GenerateSecureRandom(), - FriendlyName = friendlyName, - DateAdded = DateTime.Now, + DateAdded = DateTime.UtcNow, Code = code }; @@ -162,13 +158,11 @@ namespace Emby.Server.Implementations.QuickConnect } /// <inheritdoc/> - public bool AuthorizeRequest(HttpRequest request, string code) + public bool AuthorizeRequest(Guid userId, string code) { ExpireRequests(); AssertActive(); - var auth = _authContext.GetAuthorizationInfo(request); - if (!_currentRequests.TryGetValue(code, out QuickConnectResult result)) { throw new ResourceNotFoundException("Unable to find request"); @@ -182,21 +176,21 @@ namespace Emby.Server.Implementations.QuickConnect result.Authentication = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); // Change the time on the request so it expires one minute into the future. It can't expire immediately as otherwise some clients wouldn't ever see that they have been authenticated. - var added = result.DateAdded ?? DateTime.Now.Subtract(new TimeSpan(0, Timeout, 0)); - result.DateAdded = added.Subtract(new TimeSpan(0, Timeout - 1, 0)); + var added = result.DateAdded ?? DateTime.UtcNow.Subtract(TimeSpan.FromMinutes(Timeout)); + result.DateAdded = added.Subtract(TimeSpan.FromMinutes(Timeout - 1)); _authenticationRepository.Create(new AuthenticationInfo { - AppName = TokenNamePrefix + result.FriendlyName, + AppName = TokenName, AccessToken = result.Authentication, DateCreated = DateTime.UtcNow, DeviceId = _appHost.SystemId, DeviceName = _appHost.FriendlyName, AppVersion = _appHost.ApplicationVersionString, - UserId = auth.UserId + UserId = userId }); - _logger.LogInformation("Allowing device {FriendlyName} to login as user {Username} with quick connect code {Code}", result.FriendlyName, auth.User.Username, result.Code); + _logger.LogDebug("Authorizing device with code {Code} to login as user {userId}", code, userId); return true; } @@ -210,7 +204,7 @@ namespace Emby.Server.Implementations.QuickConnect UserId = user }); - var tokens = raw.Items.Where(x => x.AppName.StartsWith(TokenNamePrefix, StringComparison.CurrentCulture)); + var tokens = raw.Items.Where(x => x.AppName.StartsWith(TokenName, StringComparison.CurrentCulture)); var removed = 0; foreach (var token in tokens) @@ -256,7 +250,7 @@ namespace Emby.Server.Implementations.QuickConnect public void ExpireRequests(bool expireAll = false) { // Check if quick connect should be deactivated - if (State == QuickConnectState.Active && DateTime.Now > DateActivated.AddMinutes(Timeout) && !expireAll) + if (State == QuickConnectState.Active && DateTime.UtcNow > DateActivated.AddMinutes(Timeout) && !expireAll) { _logger.LogDebug("Quick connect time expired, deactivating"); SetState(QuickConnectState.Available); @@ -270,7 +264,7 @@ namespace Emby.Server.Implementations.QuickConnect for (int i = 0; i < values.Count; i++) { var added = values[i].DateAdded ?? DateTime.UnixEpoch; - if (DateTime.Now > added.AddMinutes(Timeout) || expireAll) + if (DateTime.UtcNow > added.AddMinutes(Timeout) || expireAll) { code = values[i].Code; _logger.LogDebug("Removing expired request {code}", code); diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 8a8223ee7f..fbe8e065c0 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1433,7 +1433,7 @@ namespace Emby.Server.Implementations.Session Limit = 1 }); - if (result.TotalRecordCount < 1) + if (result.TotalRecordCount == 0) { throw new SecurityException("Unknown quick connect token"); } diff --git a/Jellyfin.Api/Controllers/QuickConnectController.cs b/Jellyfin.Api/Controllers/QuickConnectController.cs index 1625bcffe7..b1ee2ff53b 100644 --- a/Jellyfin.Api/Controllers/QuickConnectController.cs +++ b/Jellyfin.Api/Controllers/QuickConnectController.cs @@ -1,8 +1,8 @@ +using System; using System.ComponentModel.DataAnnotations; using Jellyfin.Api.Constants; using Jellyfin.Api.Helpers; using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.QuickConnect; using MediaBrowser.Model.QuickConnect; @@ -18,22 +18,18 @@ namespace Jellyfin.Api.Controllers public class QuickConnectController : BaseJellyfinApiController { private readonly IQuickConnect _quickConnect; - private readonly IUserManager _userManager; private readonly IAuthorizationContext _authContext; /// <summary> /// Initializes a new instance of the <see cref="QuickConnectController"/> class. /// </summary> /// <param name="quickConnect">Instance of the <see cref="IQuickConnect"/> interface.</param> - /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param> public QuickConnectController( IQuickConnect quickConnect, - IUserManager userManager, IAuthorizationContext authContext) { _quickConnect = quickConnect; - _userManager = userManager; _authContext = authContext; } @@ -53,15 +49,14 @@ namespace Jellyfin.Api.Controllers /// <summary> /// Initiate a new quick connect request. /// </summary> - /// <param name="friendlyName">Device friendly name.</param> /// <response code="200">Quick connect request successfully created.</response> /// <response code="401">Quick connect is not active on this server.</response> /// <returns>A <see cref="QuickConnectResult"/> with a secret and code for future use or an error message.</returns> [HttpGet("Initiate")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<QuickConnectResult> Initiate([FromQuery] string? friendlyName) + public ActionResult<QuickConnectResult> Initiate() { - return _quickConnect.TryConnect(friendlyName); + return _quickConnect.TryConnect(); } /// <summary> @@ -74,12 +69,11 @@ namespace Jellyfin.Api.Controllers [HttpGet("Connect")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult<QuickConnectResult> Connect([FromQuery] string? secret) + public ActionResult<QuickConnectResult> Connect([FromQuery, Required] string secret) { try { - var result = _quickConnect.CheckRequestStatus(secret); - return result; + return _quickConnect.CheckRequestStatus(secret); } catch (ResourceNotFoundException) { @@ -117,9 +111,9 @@ namespace Jellyfin.Api.Controllers [HttpPost("Available")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult Available([FromQuery] QuickConnectState? status) + public ActionResult Available([FromQuery] QuickConnectState status = QuickConnectState.Available) { - _quickConnect.SetState(status ?? QuickConnectState.Available); + _quickConnect.SetState(status); return NoContent(); } @@ -127,16 +121,22 @@ namespace Jellyfin.Api.Controllers /// Authorizes a pending quick connect request. /// </summary> /// <param name="code">Quick connect code to authorize.</param> + /// <param name="userId">User id.</param> /// <response code="200">Quick connect result authorized successfully.</response> - /// <response code="400">Missing quick connect code.</response> + /// <response code="403">User is not allowed to authorize quick connect requests.</response> /// <returns>Boolean indicating if the authorization was successful.</returns> [HttpPost("Authorize")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - public ActionResult<bool> Authorize([FromQuery, Required] string? code) + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public ActionResult<bool> Authorize([FromQuery, Required] string code, [FromQuery, Required] Guid userId) { - return _quickConnect.AuthorizeRequest(Request, code); + if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true)) + { + return Forbid("User is not allowed to authorize quick connect requests."); + } + + return _quickConnect.AuthorizeRequest(userId, code); } /// <summary> diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index 355816bd32..d67f82219a 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -239,11 +239,9 @@ namespace Jellyfin.Api.Controllers DeviceName = auth.Device, }; - var result = await _sessionManager.AuthenticateQuickConnect( + return await _sessionManager.AuthenticateQuickConnect( authRequest, request.Token).ConfigureAwait(false); - - return result; } catch (SecurityException e) { diff --git a/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs b/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs index fd7e973f67..959a2d7712 100644 --- a/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs +++ b/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs @@ -1,6 +1,5 @@ using System; using MediaBrowser.Model.QuickConnect; -using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.QuickConnect { @@ -15,9 +14,9 @@ namespace MediaBrowser.Controller.QuickConnect int CodeLength { get; set; } /// <summary> - /// Gets or sets the string to prefix internal access tokens with. + /// Gets or sets the name of internal access tokens. /// </summary> - string TokenNamePrefix { get; set; } + string TokenName { get; set; } /// <summary> /// Gets the current state of quick connect. @@ -48,9 +47,8 @@ namespace MediaBrowser.Controller.QuickConnect /// <summary> /// Initiates a new quick connect request. /// </summary> - /// <param name="friendlyName">Friendly device name to display in the request UI.</param> /// <returns>A quick connect result with tokens to proceed or throws an exception if not active.</returns> - QuickConnectResult TryConnect(string friendlyName); + QuickConnectResult TryConnect(); /// <summary> /// Checks the status of an individual request. @@ -62,10 +60,10 @@ namespace MediaBrowser.Controller.QuickConnect /// <summary> /// Authorizes a quick connect request to connect as the calling user. /// </summary> - /// <param name="request">HTTP request object.</param> + /// <param name="userId">User id.</param> /// <param name="code">Identifying code for the request.</param> /// <returns>A boolean indicating if the authorization completed successfully.</returns> - bool AuthorizeRequest(HttpRequest request, string code); + bool AuthorizeRequest(Guid userId, string code); /// <summary> /// Expire quick connect requests that are over the time limit. If <paramref name="expireAll"/> is true, all requests are unconditionally expired. diff --git a/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs b/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs index a10d60d57e..0fa40b6a72 100644 --- a/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs +++ b/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs @@ -22,11 +22,6 @@ namespace MediaBrowser.Model.QuickConnect /// </summary> public string? Code { get; set; } - /// <summary> - /// Gets or sets the device friendly name. - /// </summary> - public string? FriendlyName { get; set; } - /// <summary> /// Gets or sets the private access token. /// </summary> -- cgit v1.2.3 From 571d0570f5560bde79d21c33173742f6a31e24cf Mon Sep 17 00:00:00 2001 From: Claus Vium <clausvium@gmail.com> Date: Thu, 3 Sep 2020 11:32:22 +0200 Subject: Kill HttpListenerHost --- Emby.Server.Implementations/ApplicationHost.cs | 19 +- .../ConfigurationOptions.cs | 2 +- .../HttpServer/HttpListenerHost.cs | 315 --------------------- .../HttpServer/WebSocketManager.cs | 102 +++++++ .../Session/SessionWebSocketListener.cs | 12 +- .../Extensions/ApiApplicationBuilderExtensions.cs | 61 ++++ .../Middleware/BaseUrlRedirectionMiddleware.cs | 62 ++++ .../Middleware/CorsOptionsResponseMiddleware.cs | 69 +++++ .../IpBasedAccessValidationMiddleware.cs | 76 +++++ .../Middleware/LanFilteringMiddleware.cs | 76 +++++ .../Middleware/ServerStartupMessageMiddleware.cs | 38 +++ .../Middleware/WebSocketHandlerMiddleware.cs | 40 +++ Jellyfin.Server/Program.cs | 4 +- Jellyfin.Server/Startup.cs | 12 +- .../Extensions/ConfigurationExtensions.cs | 6 + MediaBrowser.Controller/IServerApplicationHost.cs | 3 +- MediaBrowser.Controller/Net/IHttpServer.cs | 50 ---- MediaBrowser.Controller/Net/IWebSocketManager.cs | 32 +++ 18 files changed, 589 insertions(+), 390 deletions(-) delete mode 100644 Emby.Server.Implementations/HttpServer/HttpListenerHost.cs create mode 100644 Emby.Server.Implementations/HttpServer/WebSocketManager.cs create mode 100644 Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs create mode 100644 Jellyfin.Server/Middleware/CorsOptionsResponseMiddleware.cs create mode 100644 Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs create mode 100644 Jellyfin.Server/Middleware/LanFilteringMiddleware.cs create mode 100644 Jellyfin.Server/Middleware/ServerStartupMessageMiddleware.cs create mode 100644 Jellyfin.Server/Middleware/WebSocketHandlerMiddleware.cs delete mode 100644 MediaBrowser.Controller/Net/IHttpServer.cs create mode 100644 MediaBrowser.Controller/Net/IWebSocketManager.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 5ed0ad4155..c8af6b73a5 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -96,12 +96,12 @@ using MediaBrowser.Providers.Manager; using MediaBrowser.Providers.Plugins.TheTvdb; using MediaBrowser.Providers.Subtitles; using MediaBrowser.XbmcMetadata.Providers; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Prometheus.DotNetRuntime; using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; +using WebSocketManager = Emby.Server.Implementations.HttpServer.WebSocketManager; namespace Emby.Server.Implementations { @@ -122,9 +122,11 @@ namespace Emby.Server.Implementations private IMediaEncoder _mediaEncoder; private ISessionManager _sessionManager; - private IHttpServer _httpServer; + private IWebSocketManager _webSocketManager; private IHttpClient _httpClient; + private string[] _urlPrefixes; + /// <summary> /// Gets a value indicating whether this instance can self restart. /// </summary> @@ -444,7 +446,6 @@ namespace Emby.Server.Implementations Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed); Logger.LogInformation("Core startup complete"); - _httpServer.GlobalResponse = null; stopWatch.Restart(); await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false); @@ -500,9 +501,6 @@ namespace Emby.Server.Implementations RegisterServices(); } - public Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next) - => _httpServer.RequestHandler(context, next); - /// <summary> /// Registers services/resources with the service collection that will be available via DI. /// </summary> @@ -577,7 +575,7 @@ namespace Emby.Server.Implementations ServiceCollection.AddSingleton<ISearchEngine, SearchEngine>(); - ServiceCollection.AddSingleton<IHttpServer, HttpListenerHost>(); + ServiceCollection.AddSingleton<IWebSocketManager, WebSocketManager>(); ServiceCollection.AddSingleton<IImageProcessor, ImageProcessor>(); @@ -650,7 +648,7 @@ namespace Emby.Server.Implementations _mediaEncoder = Resolve<IMediaEncoder>(); _sessionManager = Resolve<ISessionManager>(); - _httpServer = Resolve<IHttpServer>(); + _webSocketManager = Resolve<IWebSocketManager>(); _httpClient = Resolve<IHttpClient>(); ((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize(); @@ -771,7 +769,8 @@ namespace Emby.Server.Implementations .Where(i => i != null) .ToArray(); - _httpServer.Init(GetExports<IWebSocketListener>(), GetUrlPrefixes()); + _urlPrefixes = GetUrlPrefixes().ToArray(); + _webSocketManager.Init(GetExports<IWebSocketListener>()); Resolve<ILibraryManager>().AddParts( GetExports<IResolverIgnoreRule>(), @@ -937,7 +936,7 @@ namespace Emby.Server.Implementations } } - if (!_httpServer.UrlPrefixes.SequenceEqual(GetUrlPrefixes(), StringComparer.OrdinalIgnoreCase)) + if (!_urlPrefixes.SequenceEqual(GetUrlPrefixes(), StringComparer.OrdinalIgnoreCase)) { requiresRestart = true; } diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 64ccff53b3..fde6fa1153 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -15,7 +15,7 @@ namespace Emby.Server.Implementations public static Dictionary<string, string> DefaultConfiguration => new Dictionary<string, string> { { HostWebClientKey, bool.TrueString }, - { HttpListenerHost.DefaultRedirectKey, "web/index.html" }, + { DefaultRedirectKey, "web/index.html" }, { FfmpegProbeSizeKey, "1G" }, { FfmpegAnalyzeDurationKey, "200M" }, { PlaylistsAllowDuplicatesKey, bool.TrueString }, diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs deleted file mode 100644 index 27369960b0..0000000000 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ /dev/null @@ -1,315 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.WebSockets; -using System.Threading.Tasks; -using Jellyfin.Data.Events; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Globalization; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Primitives; - -namespace Emby.Server.Implementations.HttpServer -{ - public class HttpListenerHost : IHttpServer - { - /// <summary> - /// The key for a setting that specifies the default redirect path - /// to use for requests where the URL base prefix is invalid or missing. - /// </summary> - public const string DefaultRedirectKey = "HttpListenerHost:DefaultRedirectPath"; - - private readonly ILogger<HttpListenerHost> _logger; - private readonly ILoggerFactory _loggerFactory; - private readonly IServerConfigurationManager _config; - private readonly INetworkManager _networkManager; - private readonly string _defaultRedirectPath; - private readonly string _baseUrlPrefix; - - private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>(); - private bool _disposed = false; - - public HttpListenerHost( - ILogger<HttpListenerHost> logger, - IServerConfigurationManager config, - IConfiguration configuration, - INetworkManager networkManager, - ILocalizationManager localizationManager, - ILoggerFactory loggerFactory) - { - _logger = logger; - _config = config; - _defaultRedirectPath = configuration[DefaultRedirectKey]; - _baseUrlPrefix = _config.Configuration.BaseUrl; - _networkManager = networkManager; - _loggerFactory = loggerFactory; - - Instance = this; - GlobalResponse = localizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); - } - - public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected; - - public static HttpListenerHost Instance { get; protected set; } - - public string[] UrlPrefixes { get; private set; } - - public string GlobalResponse { get; set; } - - private static string NormalizeConfiguredLocalAddress(string address) - { - var add = address.AsSpan().Trim('/'); - int index = add.IndexOf('/'); - if (index != -1) - { - add = add.Slice(index + 1); - } - - return add.TrimStart('/').ToString(); - } - - private bool ValidateHost(string host) - { - var hosts = _config - .Configuration - .LocalNetworkAddresses - .Select(NormalizeConfiguredLocalAddress) - .ToList(); - - if (hosts.Count == 0) - { - return true; - } - - host ??= string.Empty; - - if (_networkManager.IsInPrivateAddressSpace(host)) - { - hosts.Add("localhost"); - hosts.Add("127.0.0.1"); - - return hosts.Any(i => host.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1); - } - - return true; - } - - private bool ValidateRequest(string remoteIp, bool isLocal) - { - if (isLocal) - { - return true; - } - - if (_config.Configuration.EnableRemoteAccess) - { - var addressFilter = _config.Configuration.RemoteIPFilter.Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); - - if (addressFilter.Length > 0 && !_networkManager.IsInLocalNetwork(remoteIp)) - { - if (_config.Configuration.IsRemoteIPFilterBlacklist) - { - return !_networkManager.IsAddressInSubnets(remoteIp, addressFilter); - } - else - { - return _networkManager.IsAddressInSubnets(remoteIp, addressFilter); - } - } - } - else - { - if (!_networkManager.IsInLocalNetwork(remoteIp)) - { - return false; - } - } - - return true; - } - - /// <inheritdoc /> - public Task RequestHandler(HttpContext context, Func<Task> next) - { - if (context.WebSockets.IsWebSocketRequest) - { - return WebSocketRequestHandler(context); - } - - return HttpRequestHandler(context, next); - } - - /// <summary> - /// Overridable method that can be used to implement a custom handler. - /// </summary> - private async Task HttpRequestHandler(HttpContext httpContext, Func<Task> next) - { - var cancellationToken = httpContext.RequestAborted; - var httpRes = httpContext.Response; - var host = httpContext.Request.Host.ToString(); - var localPath = httpContext.Request.Path.ToString(); - string remoteIp = httpContext.Request.RemoteIp(); - - if (_disposed) - { - httpRes.StatusCode = 503; - httpRes.ContentType = "text/plain"; - await httpRes.WriteAsync("Server shutting down", cancellationToken).ConfigureAwait(false); - return; - } - - if (!ValidateHost(host)) - { - httpRes.StatusCode = 400; - httpRes.ContentType = "text/plain"; - await httpRes.WriteAsync("Invalid host", cancellationToken).ConfigureAwait(false); - return; - } - - if (!ValidateRequest(remoteIp, httpContext.Request.IsLocal())) - { - httpRes.StatusCode = 403; - httpRes.ContentType = "text/plain"; - await httpRes.WriteAsync("Forbidden", cancellationToken).ConfigureAwait(false); - return; - } - - if (string.Equals(httpContext.Request.Method, "OPTIONS", StringComparison.OrdinalIgnoreCase)) - { - httpRes.StatusCode = 200; - foreach (var (key, value) in GetDefaultCorsHeaders(httpContext)) - { - httpRes.Headers.Add(key, value); - } - - httpRes.ContentType = "text/plain"; - await httpRes.WriteAsync(string.Empty, cancellationToken).ConfigureAwait(false); - return; - } - - if (string.Equals(localPath, _baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase) - || string.Equals(localPath, _baseUrlPrefix, StringComparison.OrdinalIgnoreCase) - || string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase) - || string.IsNullOrEmpty(localPath) - || !localPath.StartsWith(_baseUrlPrefix, StringComparison.OrdinalIgnoreCase)) - { - // Always redirect back to the default path if the base prefix is invalid or missing - _logger.LogDebug("Normalizing a URL at {0}", localPath); - httpRes.Redirect(_baseUrlPrefix + "/" + _defaultRedirectPath); - return; - } - - if (!string.IsNullOrEmpty(GlobalResponse)) - { - // We don't want the address pings in ApplicationHost to fail - if (localPath.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) == -1) - { - httpRes.StatusCode = 503; - httpRes.ContentType = "text/html"; - await httpRes.WriteAsync(GlobalResponse, cancellationToken).ConfigureAwait(false); - return; - } - } - - await next().ConfigureAwait(false); - } - - private async Task WebSocketRequestHandler(HttpContext context) - { - if (_disposed) - { - return; - } - - try - { - _logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress); - - WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false); - - using var connection = new WebSocketConnection( - _loggerFactory.CreateLogger<WebSocketConnection>(), - webSocket, - context.Connection.RemoteIpAddress, - context.Request.Query) - { - OnReceive = ProcessWebSocketMessageReceived - }; - - WebSocketConnected?.Invoke(this, new GenericEventArgs<IWebSocketConnection>(connection)); - - await connection.ProcessAsync().ConfigureAwait(false); - _logger.LogInformation("WS {IP} closed", context.Connection.RemoteIpAddress); - } - catch (Exception ex) // Otherwise ASP.Net will ignore the exception - { - _logger.LogError(ex, "WS {IP} WebSocketRequestHandler error", context.Connection.RemoteIpAddress); - if (!context.Response.HasStarted) - { - context.Response.StatusCode = 500; - } - } - } - - /// <inheritdoc /> - public IDictionary<string, string> GetDefaultCorsHeaders(HttpContext httpContext) - { - var origin = httpContext.Request.Headers["Origin"]; - if (origin == StringValues.Empty) - { - origin = httpContext.Request.Headers["Host"]; - if (origin == StringValues.Empty) - { - origin = "*"; - } - } - - var headers = new Dictionary<string, string>(); - headers.Add("Access-Control-Allow-Origin", origin); - headers.Add("Access-Control-Allow-Credentials", "true"); - headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); - headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization, Cookie"); - return headers; - } - - /// <summary> - /// Adds the rest handlers. - /// </summary> - /// <param name="listeners">The web socket listeners.</param> - /// <param name="urlPrefixes">The URL prefixes. See <see cref="UrlPrefixes"/>.</param> - public void Init(IEnumerable<IWebSocketListener> listeners, IEnumerable<string> urlPrefixes) - { - _webSocketListeners = listeners.ToArray(); - UrlPrefixes = urlPrefixes.ToArray(); - } - - /// <summary> - /// Processes the web socket message received. - /// </summary> - /// <param name="result">The result.</param> - private Task ProcessWebSocketMessageReceived(WebSocketMessageInfo result) - { - if (_disposed) - { - return Task.CompletedTask; - } - - IEnumerable<Task> GetTasks() - { - foreach (var x in _webSocketListeners) - { - yield return x.ProcessMessageAsync(result); - } - } - - return Task.WhenAll(GetTasks()); - } - } -} diff --git a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs new file mode 100644 index 0000000000..89c1b7ea08 --- /dev/null +++ b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs @@ -0,0 +1,102 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.WebSockets; +using System.Threading.Tasks; +using Jellyfin.Data.Events; +using MediaBrowser.Controller.Net; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.HttpServer +{ + public class WebSocketManager : IWebSocketManager + { + private readonly ILogger<WebSocketManager> _logger; + private readonly ILoggerFactory _loggerFactory; + + private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>(); + private bool _disposed = false; + + public WebSocketManager( + ILogger<WebSocketManager> logger, + ILoggerFactory loggerFactory) + { + _logger = logger; + _loggerFactory = loggerFactory; + } + + public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected; + + /// <inheritdoc /> + public async Task WebSocketRequestHandler(HttpContext context) + { + if (_disposed) + { + return; + } + + try + { + _logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress); + + WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false); + + using var connection = new WebSocketConnection( + _loggerFactory.CreateLogger<WebSocketConnection>(), + webSocket, + context.Connection.RemoteIpAddress, + context.Request.Query) + { + OnReceive = ProcessWebSocketMessageReceived + }; + + WebSocketConnected?.Invoke(this, new GenericEventArgs<IWebSocketConnection>(connection)); + + await connection.ProcessAsync().ConfigureAwait(false); + _logger.LogInformation("WS {IP} closed", context.Connection.RemoteIpAddress); + } + catch (Exception ex) // Otherwise ASP.Net will ignore the exception + { + _logger.LogError(ex, "WS {IP} WebSocketRequestHandler error", context.Connection.RemoteIpAddress); + if (!context.Response.HasStarted) + { + context.Response.StatusCode = 500; + } + } + } + + /// <summary> + /// Adds the rest handlers. + /// </summary> + /// <param name="listeners">The web socket listeners.</param> + public void Init(IEnumerable<IWebSocketListener> listeners) + { + _webSocketListeners = listeners.ToArray(); + } + + /// <summary> + /// Processes the web socket message received. + /// </summary> + /// <param name="result">The result.</param> + private Task ProcessWebSocketMessageReceived(WebSocketMessageInfo result) + { + if (_disposed) + { + return Task.CompletedTask; + } + + IEnumerable<Task> GetTasks() + { + foreach (var x in _webSocketListeners) + { + yield return x.ProcessMessageAsync(result); + } + } + + return Task.WhenAll(GetTasks()); + } + } +} diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 1da7a64730..15c2af220d 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.Session private readonly ILogger<SessionWebSocketListener> _logger; private readonly ILoggerFactory _loggerFactory; - private readonly IHttpServer _httpServer; + private readonly IWebSocketManager _webSocketManager; /// <summary> /// The KeepAlive cancellation token. @@ -72,19 +72,19 @@ namespace Emby.Server.Implementations.Session /// <param name="logger">The logger.</param> /// <param name="sessionManager">The session manager.</param> /// <param name="loggerFactory">The logger factory.</param> - /// <param name="httpServer">The HTTP server.</param> + /// <param name="webSocketManager">The HTTP server.</param> public SessionWebSocketListener( ILogger<SessionWebSocketListener> logger, ISessionManager sessionManager, ILoggerFactory loggerFactory, - IHttpServer httpServer) + IWebSocketManager webSocketManager) { _logger = logger; _sessionManager = sessionManager; _loggerFactory = loggerFactory; - _httpServer = httpServer; + _webSocketManager = webSocketManager; - httpServer.WebSocketConnected += OnServerManagerWebSocketConnected; + webSocketManager.WebSocketConnected += OnServerManagerWebSocketConnected; } private async void OnServerManagerWebSocketConnected(object sender, GenericEventArgs<IWebSocketConnection> e) @@ -121,7 +121,7 @@ namespace Emby.Server.Implementations.Session /// <inheritdoc /> public void Dispose() { - _httpServer.WebSocketConnected -= OnServerManagerWebSocketConnected; + _webSocketManager.WebSocketConnected -= OnServerManagerWebSocketConnected; StopKeepAlive(); } diff --git a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs index 745567703f..33a8d75320 100644 --- a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs @@ -1,3 +1,4 @@ +using Jellyfin.Server.Middleware; using MediaBrowser.Controller.Configuration; using Microsoft.AspNetCore.Builder; @@ -46,5 +47,65 @@ namespace Jellyfin.Server.Extensions c.RoutePrefix = $"{baseUrl}api-docs/redoc"; }); } + + /// <summary> + /// Adds IP based access validation to the application pipeline. + /// </summary> + /// <param name="appBuilder">The application builder.</param> + /// <returns>The updated application builder.</returns> + public static IApplicationBuilder UseIpBasedAccessValidation(this IApplicationBuilder appBuilder) + { + return appBuilder.UseMiddleware<IpBasedAccessValidationMiddleware>(); + } + + /// <summary> + /// Adds LAN based access filtering to the application pipeline. + /// </summary> + /// <param name="appBuilder">The application builder.</param> + /// <returns>The updated application builder.</returns> + public static IApplicationBuilder UseLanFiltering(this IApplicationBuilder appBuilder) + { + return appBuilder.UseMiddleware<LanFilteringMiddleware>(); + } + + /// <summary> + /// Adds CORS OPTIONS request handling to the application pipeline. + /// </summary> + /// <param name="appBuilder">The application builder.</param> + /// <returns>The updated application builder.</returns> + public static IApplicationBuilder UseCorsOptionsResponse(this IApplicationBuilder appBuilder) + { + return appBuilder.UseMiddleware<CorsOptionsResponseMiddleware>(); + } + + /// <summary> + /// Adds base url redirection to the application pipeline. + /// </summary> + /// <param name="appBuilder">The application builder.</param> + /// <returns>The updated application builder.</returns> + public static IApplicationBuilder UseBaseUrlRedirection(this IApplicationBuilder appBuilder) + { + return appBuilder.UseMiddleware<BaseUrlRedirectionMiddleware>(); + } + + /// <summary> + /// Adds a custom message during server startup to the application pipeline. + /// </summary> + /// <param name="appBuilder">The application builder.</param> + /// <returns>The updated application builder.</returns> + public static IApplicationBuilder UseServerStartupMessage(this IApplicationBuilder appBuilder) + { + return appBuilder.UseMiddleware<ServerStartupMessageMiddleware>(); + } + + /// <summary> + /// Adds a WebSocket request handler to the application pipeline. + /// </summary> + /// <param name="appBuilder">The application builder.</param> + /// <returns>The updated application builder.</returns> + public static IApplicationBuilder UseWebSocketHandler(this IApplicationBuilder appBuilder) + { + return appBuilder.UseMiddleware<WebSocketHandlerMiddleware>(); + } } } diff --git a/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs b/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs new file mode 100644 index 0000000000..9316737bdf --- /dev/null +++ b/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs @@ -0,0 +1,62 @@ +using System; +using System.Threading.Tasks; +using MediaBrowser.Controller.Configuration; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using ConfigurationExtensions = MediaBrowser.Controller.Extensions.ConfigurationExtensions; + +namespace Jellyfin.Server.Middleware +{ + /// <summary> + /// Redirect requests without baseurl prefix to the baseurl prefixed URL. + /// </summary> + public class BaseUrlRedirectionMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger<BaseUrlRedirectionMiddleware> _logger; + private readonly IConfiguration _configuration; + + /// <summary> + /// Initializes a new instance of the <see cref="BaseUrlRedirectionMiddleware"/> class. + /// </summary> + /// <param name="next">The next delegate in the pipeline.</param> + /// <param name="logger">The logger.</param> + /// <param name="configuration">The application configuration.</param> + public BaseUrlRedirectionMiddleware( + RequestDelegate next, + ILogger<BaseUrlRedirectionMiddleware> logger, + IConfiguration configuration) + { + _next = next; + _logger = logger; + _configuration = configuration; + } + + /// <summary> + /// Executes the middleware action. + /// </summary> + /// <param name="httpContext">The current HTTP context.</param> + /// <param name="serverConfigurationManager">The server configuration manager.</param> + /// <returns>The async task.</returns> + public async Task Invoke(HttpContext httpContext, IServerConfigurationManager serverConfigurationManager) + { + var localPath = httpContext.Request.Path.ToString(); + var baseUrlPrefix = serverConfigurationManager.Configuration.BaseUrl; + + if (string.Equals(localPath, baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase) + || string.Equals(localPath, baseUrlPrefix, StringComparison.OrdinalIgnoreCase) + || string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase) + || string.IsNullOrEmpty(localPath) + || !localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase)) + { + // Always redirect back to the default path if the base prefix is invalid or missing + _logger.LogDebug("Normalizing an URL at {LocalPath}", localPath); + httpContext.Response.Redirect(baseUrlPrefix + "/" + _configuration[ConfigurationExtensions.DefaultRedirectKey]); + return; + } + + await _next(httpContext).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server/Middleware/CorsOptionsResponseMiddleware.cs b/Jellyfin.Server/Middleware/CorsOptionsResponseMiddleware.cs new file mode 100644 index 0000000000..8214f89079 --- /dev/null +++ b/Jellyfin.Server/Middleware/CorsOptionsResponseMiddleware.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Net.Mime; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; + +namespace Jellyfin.Server.Middleware +{ + /// <summary> + /// Middleware for handling OPTIONS requests. + /// </summary> + public class CorsOptionsResponseMiddleware + { + private readonly RequestDelegate _next; + + /// <summary> + /// Initializes a new instance of the <see cref="CorsOptionsResponseMiddleware"/> class. + /// </summary> + /// <param name="next">The next delegate in the pipeline.</param> + public CorsOptionsResponseMiddleware(RequestDelegate next) + { + _next = next; + } + + /// <summary> + /// Executes the middleware action. + /// </summary> + /// <param name="httpContext">The current HTTP context.</param> + /// <returns>The async task.</returns> + public async Task Invoke(HttpContext httpContext) + { + if (string.Equals(httpContext.Request.Method, HttpMethods.Options, StringComparison.OrdinalIgnoreCase)) + { + httpContext.Response.StatusCode = 200; + foreach (var (key, value) in GetDefaultCorsHeaders(httpContext)) + { + httpContext.Response.Headers.Add(key, value); + } + + httpContext.Response.ContentType = MediaTypeNames.Text.Plain; + await httpContext.Response.WriteAsync(string.Empty, httpContext.RequestAborted).ConfigureAwait(false); + return; + } + + await _next(httpContext).ConfigureAwait(false); + } + + private static IDictionary<string, string> GetDefaultCorsHeaders(HttpContext httpContext) + { + var origin = httpContext.Request.Headers["Origin"]; + if (origin == StringValues.Empty) + { + origin = httpContext.Request.Headers["Host"]; + if (origin == StringValues.Empty) + { + origin = "*"; + } + } + + var headers = new Dictionary<string, string>(); + headers.Add("Access-Control-Allow-Origin", origin); + headers.Add("Access-Control-Allow-Credentials", "true"); + headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); + headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization, Cookie"); + return headers; + } + } +} diff --git a/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs b/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs new file mode 100644 index 0000000000..59b5fb1ed2 --- /dev/null +++ b/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs @@ -0,0 +1,76 @@ +using System.Linq; +using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Server.Middleware +{ + /// <summary> + /// Validates the IP of requests coming from local networks wrt. remote access. + /// </summary> + public class IpBasedAccessValidationMiddleware + { + private readonly RequestDelegate _next; + + /// <summary> + /// Initializes a new instance of the <see cref="IpBasedAccessValidationMiddleware"/> class. + /// </summary> + /// <param name="next">The next delegate in the pipeline.</param> + public IpBasedAccessValidationMiddleware(RequestDelegate next) + { + _next = next; + } + + /// <summary> + /// Executes the middleware action. + /// </summary> + /// <param name="httpContext">The current HTTP context.</param> + /// <param name="networkManager">The network manager.</param> + /// <param name="serverConfigurationManager">The server configuration manager.</param> + /// <returns>The async task.</returns> + public async Task Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager) + { + if (httpContext.Request.IsLocal()) + { + await _next(httpContext).ConfigureAwait(false); + return; + } + + var remoteIp = httpContext.Request.RemoteIp(); + + if (serverConfigurationManager.Configuration.EnableRemoteAccess) + { + var addressFilter = serverConfigurationManager.Configuration.RemoteIPFilter.Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); + + if (addressFilter.Length > 0 && !networkManager.IsInLocalNetwork(remoteIp)) + { + if (serverConfigurationManager.Configuration.IsRemoteIPFilterBlacklist) + { + if (networkManager.IsAddressInSubnets(remoteIp, addressFilter)) + { + return; + } + } + else + { + if (!networkManager.IsAddressInSubnets(remoteIp, addressFilter)) + { + return; + } + } + } + } + else + { + if (!networkManager.IsInLocalNetwork(remoteIp)) + { + return; + } + } + + await _next(httpContext).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs b/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs new file mode 100644 index 0000000000..9d795145aa --- /dev/null +++ b/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs @@ -0,0 +1,76 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Server.Middleware +{ + /// <summary> + /// Validates the LAN host IP based on application configuration. + /// </summary> + public class LanFilteringMiddleware + { + private readonly RequestDelegate _next; + + /// <summary> + /// Initializes a new instance of the <see cref="LanFilteringMiddleware"/> class. + /// </summary> + /// <param name="next">The next delegate in the pipeline.</param> + public LanFilteringMiddleware(RequestDelegate next) + { + _next = next; + } + + /// <summary> + /// Executes the middleware action. + /// </summary> + /// <param name="httpContext">The current HTTP context.</param> + /// <param name="networkManager">The network manager.</param> + /// <param name="serverConfigurationManager">The server configuration manager.</param> + /// <returns>The async task.</returns> + public async Task Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager) + { + var currentHost = httpContext.Request.Host.ToString(); + var hosts = serverConfigurationManager + .Configuration + .LocalNetworkAddresses + .Select(NormalizeConfiguredLocalAddress) + .ToList(); + + if (hosts.Count == 0) + { + await _next(httpContext).ConfigureAwait(false); + return; + } + + currentHost ??= string.Empty; + + if (networkManager.IsInPrivateAddressSpace(currentHost)) + { + hosts.Add("localhost"); + hosts.Add("127.0.0.1"); + + if (hosts.All(i => currentHost.IndexOf(i, StringComparison.OrdinalIgnoreCase) == -1)) + { + return; + } + } + + await _next(httpContext).ConfigureAwait(false); + } + + private static string NormalizeConfiguredLocalAddress(string address) + { + var add = address.AsSpan().Trim('/'); + int index = add.IndexOf('/'); + if (index != -1) + { + add = add.Slice(index + 1); + } + + return add.TrimStart('/').ToString(); + } + } +} diff --git a/Jellyfin.Server/Middleware/ServerStartupMessageMiddleware.cs b/Jellyfin.Server/Middleware/ServerStartupMessageMiddleware.cs new file mode 100644 index 0000000000..4f347d6d3b --- /dev/null +++ b/Jellyfin.Server/Middleware/ServerStartupMessageMiddleware.cs @@ -0,0 +1,38 @@ +using System.Net.Mime; +using System.Threading.Tasks; +using MediaBrowser.Model.Globalization; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Server.Middleware +{ + /// <summary> + /// Shows a custom message during server startup. + /// </summary> + public class ServerStartupMessageMiddleware + { + private readonly RequestDelegate _next; + + /// <summary> + /// Initializes a new instance of the <see cref="ServerStartupMessageMiddleware"/> class. + /// </summary> + /// <param name="next">The next delegate in the pipeline.</param> + public ServerStartupMessageMiddleware(RequestDelegate next) + { + _next = next; + } + + /// <summary> + /// Executes the middleware action. + /// </summary> + /// <param name="httpContext">The current HTTP context.</param> + /// <param name="localizationManager">The localization manager.</param> + /// <returns>The async task.</returns> + public async Task Invoke(HttpContext httpContext, ILocalizationManager localizationManager) + { + var message = localizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); + httpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; + httpContext.Response.ContentType = MediaTypeNames.Text.Html; + await httpContext.Response.WriteAsync(message, httpContext.RequestAborted).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server/Middleware/WebSocketHandlerMiddleware.cs b/Jellyfin.Server/Middleware/WebSocketHandlerMiddleware.cs new file mode 100644 index 0000000000..b7a5d2b346 --- /dev/null +++ b/Jellyfin.Server/Middleware/WebSocketHandlerMiddleware.cs @@ -0,0 +1,40 @@ +using System.Threading.Tasks; +using MediaBrowser.Controller.Net; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Server.Middleware +{ + /// <summary> + /// Handles WebSocket requests. + /// </summary> + public class WebSocketHandlerMiddleware + { + private readonly RequestDelegate _next; + + /// <summary> + /// Initializes a new instance of the <see cref="WebSocketHandlerMiddleware"/> class. + /// </summary> + /// <param name="next">The next delegate in the pipeline.</param> + public WebSocketHandlerMiddleware(RequestDelegate next) + { + _next = next; + } + + /// <summary> + /// Executes the middleware action. + /// </summary> + /// <param name="httpContext">The current HTTP context.</param> + /// <param name="webSocketManager">The WebSocket connection manager.</param> + /// <returns>The async task.</returns> + public async Task Invoke(HttpContext httpContext, IWebSocketManager webSocketManager) + { + if (!httpContext.WebSockets.IsWebSocketRequest) + { + await _next(httpContext).ConfigureAwait(false); + return; + } + + await webSocketManager.WebSocketRequestHandler(httpContext).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 14cc5f4c24..b9a90f9dbf 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -11,7 +11,6 @@ using System.Threading; using System.Threading.Tasks; using CommandLine; using Emby.Server.Implementations; -using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.IO; using Emby.Server.Implementations.Networking; using Jellyfin.Api.Controllers; @@ -28,6 +27,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Serilog; using Serilog.Extensions.Logging; using SQLitePCL; +using ConfigurationExtensions = MediaBrowser.Controller.Extensions.ConfigurationExtensions; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Jellyfin.Server @@ -594,7 +594,7 @@ namespace Jellyfin.Server var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration; if (startupConfig != null && !startupConfig.HostWebClient()) { - inMemoryDefaultConfig[HttpListenerHost.DefaultRedirectKey] = "api-docs/swagger"; + inMemoryDefaultConfig[ConfigurationExtensions.DefaultRedirectKey] = "api-docs/swagger"; } return config diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs index 9316ab79ef..80f6794206 100644 --- a/Jellyfin.Server/Startup.cs +++ b/Jellyfin.Server/Startup.cs @@ -84,11 +84,9 @@ namespace Jellyfin.Server /// </summary> /// <param name="app">The application builder.</param> /// <param name="env">The webhost environment.</param> - /// <param name="serverApplicationHost">The server application host.</param> public void Configure( IApplicationBuilder app, - IWebHostEnvironment env, - IServerApplicationHost serverApplicationHost) + IWebHostEnvironment env) { if (env.IsDevelopment()) { @@ -120,7 +118,11 @@ namespace Jellyfin.Server app.UseHttpMetrics(); } - app.Use(serverApplicationHost.ExecuteHttpHandlerAsync); + app.UseLanFiltering(); + app.UseIpBasedAccessValidation(); + app.UseCorsOptionsResponse(); + app.UseBaseUrlRedirection(); + app.UseWebSocketHandler(); app.UseEndpoints(endpoints => { @@ -131,6 +133,8 @@ namespace Jellyfin.Server } }); + app.UseServerStartupMessage(); + // Add type descriptor for legacy datetime parsing. TypeDescriptor.AddAttributes(typeof(DateTime?), new TypeConverterAttribute(typeof(DateTimeTypeConverter))); } diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs index 4c2209b67c..f9285c7682 100644 --- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs +++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs @@ -8,6 +8,12 @@ namespace MediaBrowser.Controller.Extensions /// </summary> public static class ConfigurationExtensions { + /// <summary> + /// The key for a setting that specifies the default redirect path + /// to use for requests where the URL base prefix is invalid or missing.. + /// </summary> + public const string DefaultRedirectKey = "DefaultRedirectPath"; + /// <summary> /// The key for a setting that indicates whether the application should host web client content. /// </summary> diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 39b896c0f5..d482c19d98 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -117,8 +117,7 @@ namespace MediaBrowser.Controller IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo(); string ExpandVirtualPath(string path); - string ReverseVirtualPath(string path); - Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next); + string ReverseVirtualPath(string path); } } diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs deleted file mode 100644 index 6739f2fa62..0000000000 --- a/MediaBrowser.Controller/Net/IHttpServer.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Jellyfin.Data.Events; -using Microsoft.AspNetCore.Http; - -namespace MediaBrowser.Controller.Net -{ - /// <summary> - /// Interface IHttpServer. - /// </summary> - public interface IHttpServer - { - /// <summary> - /// Gets the URL prefix. - /// </summary> - /// <value>The URL prefix.</value> - string[] UrlPrefixes { get; } - - /// <summary> - /// Occurs when [web socket connected]. - /// </summary> - event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected; - - /// <summary> - /// Inits this instance. - /// </summary> - void Init(IEnumerable<IWebSocketListener> listener, IEnumerable<string> urlPrefixes); - - /// <summary> - /// If set, all requests will respond with this message. - /// </summary> - string GlobalResponse { get; set; } - - /// <summary> - /// The HTTP request handler. - /// </summary> - /// <param name="context">The current HTTP context.</param> - /// <param name="next">The next middleware in the ASP.NET pipeline.</param> - /// <returns>The task.</returns> - Task RequestHandler(HttpContext context, Func<Task> next); - - /// <summary> - /// Get the default CORS headers. - /// </summary> - /// <param name="httpContext">The HTTP context of the current request.</param> - /// <returns>The default CORS headers for the context.</returns> - IDictionary<string, string> GetDefaultCorsHeaders(HttpContext httpContext); - } -} diff --git a/MediaBrowser.Controller/Net/IWebSocketManager.cs b/MediaBrowser.Controller/Net/IWebSocketManager.cs new file mode 100644 index 0000000000..e9f00ae88b --- /dev/null +++ b/MediaBrowser.Controller/Net/IWebSocketManager.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Jellyfin.Data.Events; +using Microsoft.AspNetCore.Http; + +namespace MediaBrowser.Controller.Net +{ + /// <summary> + /// Interface IHttpServer. + /// </summary> + public interface IWebSocketManager + { + /// <summary> + /// Occurs when [web socket connected]. + /// </summary> + event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected; + + /// <summary> + /// Inits this instance. + /// </summary> + /// <param name="listeners">The websocket listeners.</param> + void Init(IEnumerable<IWebSocketListener> listeners); + + /// <summary> + /// The HTTP request handler. + /// </summary> + /// <param name="context">The current HTTP context.</param> + /// <returns>The task.</returns> + Task WebSocketRequestHandler(HttpContext context); + } +} -- cgit v1.2.3 From 3459655bb401595f62c21513964876b2f4549fed Mon Sep 17 00:00:00 2001 From: Niels van Velzen <git@ndat.nl> Date: Mon, 21 Sep 2020 16:53:00 +0200 Subject: Use GeneralCommandType enum in GeneralCommand name --- CONTRIBUTORS.md | 1 + Emby.Dlna/PlayTo/PlayToController.cs | 87 ++++++++++------------ .../Session/SessionManager.cs | 4 +- Jellyfin.Api/Controllers/SessionController.cs | 14 ++-- MediaBrowser.Model/Session/GeneralCommand.cs | 2 +- 5 files changed, 54 insertions(+), 54 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index f1fe65064b..efd83012e9 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -135,6 +135,7 @@ - [YouKnowBlom](https://github.com/YouKnowBlom) - [KristupasSavickas](https://github.com/KristupasSavickas) - [Pusta](https://github.com/pusta) + - [nielsvanvelzen](https://github.com/nielsvanvelzen) # Emby Contributors diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 328759c5bc..460ac2d8d1 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -669,62 +669,57 @@ namespace Emby.Dlna.PlayTo private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken) { - if (Enum.TryParse(command.Name, true, out GeneralCommandType commandType)) - { - switch (commandType) - { - case GeneralCommandType.VolumeDown: - return _device.VolumeDown(cancellationToken); - case GeneralCommandType.VolumeUp: - return _device.VolumeUp(cancellationToken); - case GeneralCommandType.Mute: - return _device.Mute(cancellationToken); - case GeneralCommandType.Unmute: - return _device.Unmute(cancellationToken); - case GeneralCommandType.ToggleMute: - return _device.ToggleMute(cancellationToken); - case GeneralCommandType.SetAudioStreamIndex: - if (command.Arguments.TryGetValue("Index", out string index)) + switch (command.Name) + { + case GeneralCommandType.VolumeDown: + return _device.VolumeDown(cancellationToken); + case GeneralCommandType.VolumeUp: + return _device.VolumeUp(cancellationToken); + case GeneralCommandType.Mute: + return _device.Mute(cancellationToken); + case GeneralCommandType.Unmute: + return _device.Unmute(cancellationToken); + case GeneralCommandType.ToggleMute: + return _device.ToggleMute(cancellationToken); + case GeneralCommandType.SetAudioStreamIndex: + if (command.Arguments.TryGetValue("Index", out string index)) + { + if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val)) { - if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val)) - { - return SetAudioStreamIndex(val); - } - - throw new ArgumentException("Unsupported SetAudioStreamIndex value supplied."); + return SetAudioStreamIndex(val); } - throw new ArgumentException("SetAudioStreamIndex argument cannot be null"); - case GeneralCommandType.SetSubtitleStreamIndex: - if (command.Arguments.TryGetValue("Index", out index)) - { - if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val)) - { - return SetSubtitleStreamIndex(val); - } + throw new ArgumentException("Unsupported SetAudioStreamIndex value supplied."); + } - throw new ArgumentException("Unsupported SetSubtitleStreamIndex value supplied."); + throw new ArgumentException("SetAudioStreamIndex argument cannot be null"); + case GeneralCommandType.SetSubtitleStreamIndex: + if (command.Arguments.TryGetValue("Index", out index)) + { + if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val)) + { + return SetSubtitleStreamIndex(val); } - throw new ArgumentException("SetSubtitleStreamIndex argument cannot be null"); - case GeneralCommandType.SetVolume: - if (command.Arguments.TryGetValue("Volume", out string vol)) - { - if (int.TryParse(vol, NumberStyles.Integer, _usCulture, out var volume)) - { - return _device.SetVolume(volume, cancellationToken); - } + throw new ArgumentException("Unsupported SetSubtitleStreamIndex value supplied."); + } - throw new ArgumentException("Unsupported volume value supplied."); + throw new ArgumentException("SetSubtitleStreamIndex argument cannot be null"); + case GeneralCommandType.SetVolume: + if (command.Arguments.TryGetValue("Volume", out string vol)) + { + if (int.TryParse(vol, NumberStyles.Integer, _usCulture, out var volume)) + { + return _device.SetVolume(volume, cancellationToken); } - throw new ArgumentException("Volume argument cannot be null"); - default: - return Task.CompletedTask; - } - } + throw new ArgumentException("Unsupported volume value supplied."); + } - return Task.CompletedTask; + throw new ArgumentException("Volume argument cannot be null"); + default: + return Task.CompletedTask; + } } private async Task SetAudioStreamIndex(int? newIndex) diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index ca8e0e29bb..e42d478533 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1037,7 +1037,7 @@ namespace Emby.Server.Implementations.Session var generalCommand = new GeneralCommand { - Name = GeneralCommandType.DisplayMessage.ToString() + Name = GeneralCommandType.DisplayMessage }; generalCommand.Arguments["Header"] = command.Header; @@ -1268,7 +1268,7 @@ namespace Emby.Server.Implementations.Session { var generalCommand = new GeneralCommand { - Name = GeneralCommandType.DisplayContent.ToString(), + Name = GeneralCommandType.DisplayContent, Arguments = { ["ItemId"] = command.ItemId, diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index b00675d679..5a2a3cdc09 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -217,16 +217,15 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string sessionId, [FromRoute, Required] string command) { - var name = command; - if (Enum.TryParse(name, true, out GeneralCommandType commandType)) + if (!Enum.TryParse(command, true, out GeneralCommandType commandType)) { - name = commandType.ToString(); + return BadRequest(); } var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request); var generalCommand = new GeneralCommand { - Name = name, + Name = commandType, ControllingUserId = currentSession.UserId }; @@ -249,11 +248,16 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] string sessionId, [FromRoute, Required] string command) { + if (!Enum.TryParse(command, true, out GeneralCommandType commandType)) + { + return BadRequest(); + } + var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request); var generalCommand = new GeneralCommand { - Name = command, + Name = commandType, ControllingUserId = currentSession.UserId }; diff --git a/MediaBrowser.Model/Session/GeneralCommand.cs b/MediaBrowser.Model/Session/GeneralCommand.cs index 9794bd2929..77bb6bcf77 100644 --- a/MediaBrowser.Model/Session/GeneralCommand.cs +++ b/MediaBrowser.Model/Session/GeneralCommand.cs @@ -8,7 +8,7 @@ namespace MediaBrowser.Model.Session { public class GeneralCommand { - public string Name { get; set; } + public GeneralCommandType Name { get; set; } public Guid ControllingUserId { get; set; } -- cgit v1.2.3 From 72534f9d667f2457cbe96ea870d140528e366ba2 Mon Sep 17 00:00:00 2001 From: Niels van Velzen <git@ndat.nl> Date: Fri, 25 Sep 2020 09:25:59 +0200 Subject: Use SessionMessageType for WebSocket messages --- Emby.Dlna/PlayTo/PlayToController.cs | 8 ++-- .../EntryPoints/LibraryChangedNotifier.cs | 7 +-- .../EntryPoints/RecordingNotifier.cs | 11 ++--- .../EntryPoints/UserDataChangeNotifier.cs | 2 +- .../HttpServer/WebSocketConnection.cs | 5 ++- .../Session/SessionManager.cs | 28 ++++++------ .../Session/SessionWebSocketListener.cs | 3 +- .../Session/WebSocketController.cs | 3 +- .../ActivityLogWebSocketListener.cs | 14 +++--- .../ScheduledTasksWebSocketListener.cs | 14 +++--- .../SessionInfoWebSocketListener.cs | 9 +++- .../Consumers/System/TaskCompletedNotifier.cs | 3 +- .../Updates/PluginInstallationCancelledNotifier.cs | 3 +- .../Updates/PluginInstallationFailedNotifier.cs | 3 +- .../Consumers/Updates/PluginInstalledNotifier.cs | 3 +- .../Consumers/Updates/PluginInstallingNotifier.cs | 3 +- .../Consumers/Updates/PluginUninstalledNotifier.cs | 3 +- .../Events/Consumers/Users/UserDeletedNotifier.cs | 3 +- .../Events/Consumers/Users/UserUpdatedNotifier.cs | 3 +- .../Net/BasePeriodicWebSocketListener.cs | 27 +++++++++--- .../Session/ISessionController.cs | 3 +- MediaBrowser.Controller/Session/ISessionManager.cs | 8 ++-- MediaBrowser.Model/Net/WebSocketMessage.cs | 3 +- MediaBrowser.Model/Session/SessionMessageType.cs | 50 ++++++++++++++++++++++ 24 files changed, 156 insertions(+), 63 deletions(-) create mode 100644 MediaBrowser.Model/Session/SessionMessageType.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 460ac2d8d1..d09a11930d 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -811,7 +811,7 @@ namespace Emby.Dlna.PlayTo } /// <inheritdoc /> - public Task SendMessage<T>(string name, Guid messageId, T data, CancellationToken cancellationToken) + public Task SendMessage<T>(SessionMessageType name, Guid messageId, T data, CancellationToken cancellationToken) { if (_disposed) { @@ -823,17 +823,17 @@ namespace Emby.Dlna.PlayTo return Task.CompletedTask; } - if (string.Equals(name, "Play", StringComparison.OrdinalIgnoreCase)) + if (name == SessionMessageType.Play) { return SendPlayCommand(data as PlayRequest, cancellationToken); } - if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase)) + if (name == SessionMessageType.PlayState) { return SendPlaystateCommand(data as PlaystateRequest, cancellationToken); } - if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase)) + if (name == SessionMessageType.GeneralCommand) { return SendGeneralCommand(data as GeneralCommand, cancellationToken); } diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index c9d21d9638..ff64e217a0 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -16,6 +16,7 @@ using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.EntryPoints @@ -105,7 +106,7 @@ namespace Emby.Server.Implementations.EntryPoints try { - _sessionManager.SendMessageToAdminSessions("RefreshProgress", dict, CancellationToken.None); + _sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, dict, CancellationToken.None); } catch { @@ -123,7 +124,7 @@ namespace Emby.Server.Implementations.EntryPoints try { - _sessionManager.SendMessageToAdminSessions("RefreshProgress", collectionFolderDict, CancellationToken.None); + _sessionManager.SendMessageToAdminSessions(SessionMessageType.RefreshProgress, collectionFolderDict, CancellationToken.None); } catch { @@ -345,7 +346,7 @@ namespace Emby.Server.Implementations.EntryPoints try { - await _sessionManager.SendMessageToUserSessions(new List<Guid> { userId }, "LibraryChanged", info, cancellationToken).ConfigureAwait(false); + await _sessionManager.SendMessageToUserSessions(new List<Guid> { userId }, SessionMessageType.LibraryChanged, info, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index 44d2580d68..824bb85f44 100644 --- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -10,6 +10,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.EntryPoints @@ -46,25 +47,25 @@ namespace Emby.Server.Implementations.EntryPoints private async void OnLiveTvManagerSeriesTimerCreated(object sender, GenericEventArgs<TimerEventInfo> e) { - await SendMessage("SeriesTimerCreated", e.Argument).ConfigureAwait(false); + await SendMessage(SessionMessageType.SeriesTimerCreated, e.Argument).ConfigureAwait(false); } private async void OnLiveTvManagerTimerCreated(object sender, GenericEventArgs<TimerEventInfo> e) { - await SendMessage("TimerCreated", e.Argument).ConfigureAwait(false); + await SendMessage(SessionMessageType.TimerCreated, e.Argument).ConfigureAwait(false); } private async void OnLiveTvManagerSeriesTimerCancelled(object sender, GenericEventArgs<TimerEventInfo> e) { - await SendMessage("SeriesTimerCancelled", e.Argument).ConfigureAwait(false); + await SendMessage(SessionMessageType.SeriesTimerCancelled, e.Argument).ConfigureAwait(false); } private async void OnLiveTvManagerTimerCancelled(object sender, GenericEventArgs<TimerEventInfo> e) { - await SendMessage("TimerCancelled", e.Argument).ConfigureAwait(false); + await SendMessage(SessionMessageType.TimerCancelled, e.Argument).ConfigureAwait(false); } - private async Task SendMessage(string name, TimerEventInfo info) + private async Task SendMessage(SessionMessageType name, TimerEventInfo info) { var users = _userManager.Users.Where(i => i.HasPermission(PermissionKind.EnableLiveTvAccess)).Select(i => i.Id).ToList(); diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index 3618b88c5d..ff09cc81ed 100644 --- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -116,7 +116,7 @@ namespace Emby.Server.Implementations.EntryPoints private Task SendNotifications(Guid userId, List<BaseItem> changedItems, CancellationToken cancellationToken) { - return _sessionManager.SendMessageToUserSessions(new List<Guid> { userId }, "UserDataChanged", () => GetUserDataChangeInfo(userId, changedItems), cancellationToken); + return _sessionManager.SendMessageToUserSessions(new List<Guid> { userId }, SessionMessageType.UserDataChanged, () => GetUserDataChangeInfo(userId, changedItems), cancellationToken); } private UserDataChangeInfo GetUserDataChangeInfo(Guid userId, List<BaseItem> changedItems) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 7eae4e7646..fed2addf80 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using MediaBrowser.Common.Json; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Net; +using MediaBrowser.Model.Session; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -227,7 +228,7 @@ namespace Emby.Server.Implementations.HttpServer Connection = this }; - if (info.MessageType.Equals("KeepAlive", StringComparison.Ordinal)) + if (info.MessageType == SessionMessageType.KeepAlive) { await SendKeepAliveResponse().ConfigureAwait(false); } @@ -244,7 +245,7 @@ namespace Emby.Server.Implementations.HttpServer new WebSocketMessage<string> { MessageId = Guid.NewGuid(), - MessageType = "KeepAlive" + MessageType = SessionMessageType.KeepAlive }, CancellationToken.None); } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index e42d478533..df1b1e4525 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1064,10 +1064,10 @@ namespace Emby.Server.Implementations.Session AssertCanControl(session, controllingSession); } - return SendMessageToSession(session, "GeneralCommand", command, cancellationToken); + return SendMessageToSession(session, SessionMessageType.GeneralCommand, command, cancellationToken); } - private static async Task SendMessageToSession<T>(SessionInfo session, string name, T data, CancellationToken cancellationToken) + private static async Task SendMessageToSession<T>(SessionInfo session, SessionMessageType name, T data, CancellationToken cancellationToken) { var controllers = session.SessionControllers; var messageId = Guid.NewGuid(); @@ -1078,7 +1078,7 @@ namespace Emby.Server.Implementations.Session } } - private static Task SendMessageToSessions<T>(IEnumerable<SessionInfo> sessions, string name, T data, CancellationToken cancellationToken) + private static Task SendMessageToSessions<T>(IEnumerable<SessionInfo> sessions, SessionMessageType name, T data, CancellationToken cancellationToken) { IEnumerable<Task> GetTasks() { @@ -1178,7 +1178,7 @@ namespace Emby.Server.Implementations.Session } } - await SendMessageToSession(session, "Play", command, cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, SessionMessageType.Play, command, cancellationToken).ConfigureAwait(false); } /// <inheritdoc /> @@ -1186,7 +1186,7 @@ namespace Emby.Server.Implementations.Session { CheckDisposed(); var session = GetSessionToRemoteControl(sessionId); - await SendMessageToSession(session, "SyncPlayCommand", command, cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, SessionMessageType.SyncPlayCommand, command, cancellationToken).ConfigureAwait(false); } /// <inheritdoc /> @@ -1194,7 +1194,7 @@ namespace Emby.Server.Implementations.Session { CheckDisposed(); var session = GetSessionToRemoteControl(sessionId); - await SendMessageToSession(session, "SyncPlayGroupUpdate", command, cancellationToken).ConfigureAwait(false); + await SendMessageToSession(session, SessionMessageType.SyncPlayGroupUpdate, command, cancellationToken).ConfigureAwait(false); } private IEnumerable<BaseItem> TranslateItemForPlayback(Guid id, User user) @@ -1297,7 +1297,7 @@ namespace Emby.Server.Implementations.Session } } - return SendMessageToSession(session, "Playstate", command, cancellationToken); + return SendMessageToSession(session, SessionMessageType.PlayState, command, cancellationToken); } private static void AssertCanControl(SessionInfo session, SessionInfo controllingSession) @@ -1322,7 +1322,7 @@ namespace Emby.Server.Implementations.Session { CheckDisposed(); - return SendMessageToSessions(Sessions, "RestartRequired", string.Empty, cancellationToken); + return SendMessageToSessions(Sessions, SessionMessageType.RestartRequired, string.Empty, cancellationToken); } /// <summary> @@ -1334,7 +1334,7 @@ namespace Emby.Server.Implementations.Session { CheckDisposed(); - return SendMessageToSessions(Sessions, "ServerShuttingDown", string.Empty, cancellationToken); + return SendMessageToSessions(Sessions, SessionMessageType.ServerShuttingDown, string.Empty, cancellationToken); } /// <summary> @@ -1348,7 +1348,7 @@ namespace Emby.Server.Implementations.Session _logger.LogDebug("Beginning SendServerRestartNotification"); - return SendMessageToSessions(Sessions, "ServerRestarting", string.Empty, cancellationToken); + return SendMessageToSessions(Sessions, SessionMessageType.ServerRestarting, string.Empty, cancellationToken); } /// <summary> @@ -1866,7 +1866,7 @@ namespace Emby.Server.Implementations.Session } /// <inheritdoc /> - public Task SendMessageToAdminSessions<T>(string name, T data, CancellationToken cancellationToken) + public Task SendMessageToAdminSessions<T>(SessionMessageType name, T data, CancellationToken cancellationToken) { CheckDisposed(); @@ -1879,7 +1879,7 @@ namespace Emby.Server.Implementations.Session } /// <inheritdoc /> - public Task SendMessageToUserSessions<T>(List<Guid> userIds, string name, Func<T> dataFn, CancellationToken cancellationToken) + public Task SendMessageToUserSessions<T>(List<Guid> userIds, SessionMessageType name, Func<T> dataFn, CancellationToken cancellationToken) { CheckDisposed(); @@ -1894,7 +1894,7 @@ namespace Emby.Server.Implementations.Session } /// <inheritdoc /> - public Task SendMessageToUserSessions<T>(List<Guid> userIds, string name, T data, CancellationToken cancellationToken) + public Task SendMessageToUserSessions<T>(List<Guid> userIds, SessionMessageType name, T data, CancellationToken cancellationToken) { CheckDisposed(); @@ -1903,7 +1903,7 @@ namespace Emby.Server.Implementations.Session } /// <inheritdoc /> - public Task SendMessageToUserDeviceSessions<T>(string deviceId, string name, T data, CancellationToken cancellationToken) + public Task SendMessageToUserDeviceSessions<T>(string deviceId, SessionMessageType name, T data, CancellationToken cancellationToken) { CheckDisposed(); diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 15c2af220d..a5f8479537 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -8,6 +8,7 @@ using Jellyfin.Data.Events; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Net; +using MediaBrowser.Model.Session; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -316,7 +317,7 @@ namespace Emby.Server.Implementations.Session return webSocket.SendAsync( new WebSocketMessage<int> { - MessageType = "ForceKeepAlive", + MessageType = SessionMessageType.ForceKeepAlive, Data = WebSocketLostTimeout }, CancellationToken.None); diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index 94604ca1e0..b986ffa1cd 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Net; +using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Session @@ -65,7 +66,7 @@ namespace Emby.Server.Implementations.Session /// <inheritdoc /> public Task SendMessage<T>( - string name, + SessionMessageType name, Guid messageId, T data, CancellationToken cancellationToken) diff --git a/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs index 849b3b7095..77d55828d1 100644 --- a/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs +++ b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Jellyfin.Data.Events; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; namespace Jellyfin.Api.WebSocketListeners @@ -29,11 +30,14 @@ namespace Jellyfin.Api.WebSocketListeners _activityManager.EntryCreated += OnEntryCreated; } - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - protected override string Name => "ActivityLogEntry"; + /// <inheritdoc /> + protected override SessionMessageType Type => SessionMessageType.ActivityLogEntry; + + /// <inheritdoc /> + protected override SessionMessageType StartType => SessionMessageType.ActivityLogEntryStart; + + /// <inheritdoc /> + protected override SessionMessageType StopType => SessionMessageType.ActivityLogEntryStop; /// <summary> /// Gets the data to send. diff --git a/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs index 8a966c1376..80314b9236 100644 --- a/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs +++ b/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; using Jellyfin.Data.Events; using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Session; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; @@ -33,11 +34,14 @@ namespace Jellyfin.Api.WebSocketListeners _taskManager.TaskCompleted += OnTaskCompleted; } - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - protected override string Name => "ScheduledTasksInfo"; + /// <inheritdoc /> + protected override SessionMessageType Type => SessionMessageType.ScheduledTasksInfo; + + /// <inheritdoc /> + protected override SessionMessageType StartType => SessionMessageType.ScheduledTasksInfoStart; + + /// <inheritdoc /> + protected override SessionMessageType StopType => SessionMessageType.ScheduledTasksInfoStop; /// <summary> /// Gets the data to send. diff --git a/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs index 1fb5dc412c..1cf43a0053 100644 --- a/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs +++ b/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; namespace Jellyfin.Api.WebSocketListeners @@ -34,7 +35,13 @@ namespace Jellyfin.Api.WebSocketListeners } /// <inheritdoc /> - protected override string Name => "Sessions"; + protected override SessionMessageType Type => SessionMessageType.Sessions; + + /// <inheritdoc /> + protected override SessionMessageType StartType => SessionMessageType.SessionsStart; + + /// <inheritdoc /> + protected override SessionMessageType StopType => SessionMessageType.SessionsStop; /// <summary> /// Gets the data to send. diff --git a/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedNotifier.cs index 80ed56cd8f..0993c6df7b 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/System/TaskCompletedNotifier.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; using MediaBrowser.Model.Tasks; namespace Jellyfin.Server.Implementations.Events.Consumers.System @@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.System /// <inheritdoc /> public async Task OnEvent(TaskCompletionEventArgs eventArgs) { - await _sessionManager.SendMessageToAdminSessions("ScheduledTaskEnded", eventArgs.Result, CancellationToken.None).ConfigureAwait(false); + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.ScheduledTaskEnded, eventArgs.Result, CancellationToken.None).ConfigureAwait(false); } } } diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationCancelledNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationCancelledNotifier.cs index 1c600683a9..1d790da6b2 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationCancelledNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationCancelledNotifier.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Events.Updates; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; namespace Jellyfin.Server.Implementations.Events.Consumers.Updates { @@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates /// <inheritdoc /> public async Task OnEvent(PluginInstallationCancelledEventArgs eventArgs) { - await _sessionManager.SendMessageToAdminSessions("PackageInstallationCancelled", eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageInstallationCancelled, eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); } } } diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedNotifier.cs index ea0c878d42..a1faf18fc4 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallationFailedNotifier.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using MediaBrowser.Common.Updates; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; namespace Jellyfin.Server.Implementations.Events.Consumers.Updates { @@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates /// <inheritdoc /> public async Task OnEvent(InstallationFailedEventArgs eventArgs) { - await _sessionManager.SendMessageToAdminSessions("PackageInstallationFailed", eventArgs.InstallationInfo, CancellationToken.None).ConfigureAwait(false); + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageInstallationFailed, eventArgs.InstallationInfo, CancellationToken.None).ConfigureAwait(false); } } } diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledNotifier.cs index 3dda5a04c4..bd1a714045 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstalledNotifier.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Events.Updates; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; namespace Jellyfin.Server.Implementations.Events.Consumers.Updates { @@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates /// <inheritdoc /> public async Task OnEvent(PluginInstalledEventArgs eventArgs) { - await _sessionManager.SendMessageToAdminSessions("PackageInstallationCompleted", eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageInstallationCompleted, eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); } } } diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallingNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallingNotifier.cs index f691d11a7d..b513ac64ae 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallingNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginInstallingNotifier.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Events.Updates; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; namespace Jellyfin.Server.Implementations.Events.Consumers.Updates { @@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates /// <inheritdoc /> public async Task OnEvent(PluginInstallingEventArgs eventArgs) { - await _sessionManager.SendMessageToAdminSessions("PackageInstalling", eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageInstalling, eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); } } } diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledNotifier.cs index 709692f6bb..1fd7b9adfc 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Updates/PluginUninstalledNotifier.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Events.Updates; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; namespace Jellyfin.Server.Implementations.Events.Consumers.Updates { @@ -25,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Updates /// <inheritdoc /> public async Task OnEvent(PluginUninstalledEventArgs eventArgs) { - await _sessionManager.SendMessageToAdminSessions("PluginUninstalled", eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); + await _sessionManager.SendMessageToAdminSessions(SessionMessageType.PackageUninstalled, eventArgs.Argument, CancellationToken.None).ConfigureAwait(false); } } } diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedNotifier.cs index 10367a939b..303e886210 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserDeletedNotifier.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Jellyfin.Data.Events.Users; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; namespace Jellyfin.Server.Implementations.Events.Consumers.Users { @@ -30,7 +31,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Users { await _sessionManager.SendMessageToUserSessions( new List<Guid> { eventArgs.Argument.Id }, - "UserDeleted", + SessionMessageType.UserDeleted, eventArgs.Argument.Id.ToString("N", CultureInfo.InvariantCulture), CancellationToken.None).ConfigureAwait(false); } diff --git a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs index 6081dd044f..a14911b94a 100644 --- a/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs +++ b/Jellyfin.Server.Implementations/Events/Consumers/Users/UserUpdatedNotifier.cs @@ -6,6 +6,7 @@ using Jellyfin.Data.Events.Users; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Session; namespace Jellyfin.Server.Implementations.Events.Consumers.Users { @@ -33,7 +34,7 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Users { await _sessionManager.SendMessageToUserSessions( new List<Guid> { e.Argument.Id }, - "UserUpdated", + SessionMessageType.UserUpdated, _userManager.GetUserDto(e.Argument), CancellationToken.None).ConfigureAwait(false); } diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index 916dea58be..28227603b2 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -8,6 +8,7 @@ using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Net; +using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Net @@ -28,10 +29,22 @@ namespace MediaBrowser.Controller.Net new List<Tuple<IWebSocketConnection, CancellationTokenSource, TStateType>>(); /// <summary> - /// Gets the name. + /// Gets the type used for the messages sent to the client. /// </summary> - /// <value>The name.</value> - protected abstract string Name { get; } + /// <value>The type.</value> + protected abstract SessionMessageType Type { get; } + + /// <summary> + /// Gets the message type received from the client to start sending messages. + /// </summary> + /// <value>The type.</value> + protected abstract SessionMessageType StartType { get; } + + /// <summary> + /// Gets the message type received from the client to stop sending messages. + /// </summary> + /// <value>The type.</value> + protected abstract SessionMessageType StopType { get; } /// <summary> /// Gets the data to send. @@ -66,12 +79,12 @@ namespace MediaBrowser.Controller.Net throw new ArgumentNullException(nameof(message)); } - if (string.Equals(message.MessageType, Name + "Start", StringComparison.OrdinalIgnoreCase)) + if (message.MessageType == StartType) { Start(message); } - if (string.Equals(message.MessageType, Name + "Stop", StringComparison.OrdinalIgnoreCase)) + if (message.MessageType == StopType) { Stop(message); } @@ -159,7 +172,7 @@ namespace MediaBrowser.Controller.Net new WebSocketMessage<TReturnDataType> { MessageId = Guid.NewGuid(), - MessageType = Name, + MessageType = Type, Data = data }, cancellationToken).ConfigureAwait(false); @@ -176,7 +189,7 @@ namespace MediaBrowser.Controller.Net } catch (Exception ex) { - Logger.LogError(ex, "Error sending web socket message {Name}", Name); + Logger.LogError(ex, "Error sending web socket message {Name}", Type); DisposeConnection(tuple); } } diff --git a/MediaBrowser.Controller/Session/ISessionController.cs b/MediaBrowser.Controller/Session/ISessionController.cs index 22d6e2a04e..bc4ccd44ca 100644 --- a/MediaBrowser.Controller/Session/ISessionController.cs +++ b/MediaBrowser.Controller/Session/ISessionController.cs @@ -3,6 +3,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Session; namespace MediaBrowser.Controller.Session { @@ -23,6 +24,6 @@ namespace MediaBrowser.Controller.Session /// <summary> /// Sends the message. /// </summary> - Task SendMessage<T>(string name, Guid messageId, T data, CancellationToken cancellationToken); + Task SendMessage<T>(SessionMessageType name, Guid messageId, T data, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 228b2331dc..04c3004ee6 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -188,16 +188,16 @@ namespace MediaBrowser.Controller.Session /// <param name="data">The data.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task SendMessageToAdminSessions<T>(string name, T data, CancellationToken cancellationToken); + Task SendMessageToAdminSessions<T>(SessionMessageType name, T data, CancellationToken cancellationToken); /// <summary> /// Sends the message to user sessions. /// </summary> /// <typeparam name="T"></typeparam> /// <returns>Task.</returns> - Task SendMessageToUserSessions<T>(List<Guid> userIds, string name, T data, CancellationToken cancellationToken); + Task SendMessageToUserSessions<T>(List<Guid> userIds, SessionMessageType name, T data, CancellationToken cancellationToken); - Task SendMessageToUserSessions<T>(List<Guid> userIds, string name, Func<T> dataFn, CancellationToken cancellationToken); + Task SendMessageToUserSessions<T>(List<Guid> userIds, SessionMessageType name, Func<T> dataFn, CancellationToken cancellationToken); /// <summary> /// Sends the message to user device sessions. @@ -208,7 +208,7 @@ namespace MediaBrowser.Controller.Session /// <param name="data">The data.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task SendMessageToUserDeviceSessions<T>(string deviceId, string name, T data, CancellationToken cancellationToken); + Task SendMessageToUserDeviceSessions<T>(string deviceId, SessionMessageType name, T data, CancellationToken cancellationToken); /// <summary> /// Sends the restart required message. diff --git a/MediaBrowser.Model/Net/WebSocketMessage.cs b/MediaBrowser.Model/Net/WebSocketMessage.cs index 660eebeda6..bffbbe612d 100644 --- a/MediaBrowser.Model/Net/WebSocketMessage.cs +++ b/MediaBrowser.Model/Net/WebSocketMessage.cs @@ -2,6 +2,7 @@ #pragma warning disable CS1591 using System; +using MediaBrowser.Model.Session; namespace MediaBrowser.Model.Net { @@ -15,7 +16,7 @@ namespace MediaBrowser.Model.Net /// Gets or sets the type of the message. /// </summary> /// <value>The type of the message.</value> - public string MessageType { get; set; } + public SessionMessageType MessageType { get; set; } public Guid MessageId { get; set; } diff --git a/MediaBrowser.Model/Session/SessionMessageType.cs b/MediaBrowser.Model/Session/SessionMessageType.cs new file mode 100644 index 0000000000..23c41026dc --- /dev/null +++ b/MediaBrowser.Model/Session/SessionMessageType.cs @@ -0,0 +1,50 @@ +#pragma warning disable CS1591 + +namespace MediaBrowser.Model.Session +{ + /// <summary> + /// The different kinds of messages that are used in the WebSocket api. + /// </summary> + public enum SessionMessageType + { + // Server -> Client + ForceKeepAlive, + GeneralCommand, + UserDataChanged, + Sessions, + Play, + SyncPlayCommand, + SyncPlayGroupUpdate, + PlayState, + RestartRequired, + ServerShuttingDown, + ServerRestarting, + LibraryChanged, + UserDeleted, + UserUpdated, + SeriesTimerCreated, + TimerCreated, + SeriesTimerCancelled, + TimerCancelled, + RefreshProgress, + ScheduledTaskEnded, + PackageInstallationCancelled, + PackageInstallationFailed, + PackageInstallationCompleted, + PackageInstalling, + PackageUninstalled, + ActivityLogEntry, + ScheduledTasksInfo, + + // Client -> Server + ActivityLogEntryStart, + ActivityLogEntryStop, + SessionsStart, + SessionsStop, + ScheduledTasksInfoStart, + ScheduledTasksInfoStop, + + // Shared + KeepAlive, + } +} -- cgit v1.2.3 From 5a7dda337f4cdda0d0c61adef3d2b13772e708d0 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" <joshua@boniface.me> Date: Sun, 4 Oct 2020 11:50:00 -0400 Subject: Add active session tracking Adds a flag for a maximum number of user sessions, as well as an authentication check to ensure that the user is not above this level. --- Emby.Server.Implementations/Session/SessionManager.cs | 13 +++++++++++++ Jellyfin.Data/Entities/User.cs | 5 +++++ Jellyfin.Server.Implementations/Users/UserManager.cs | 2 ++ MediaBrowser.Model/Users/UserPolicy.cs | 6 ++++++ 4 files changed, 26 insertions(+) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index e42d478533..5903d395a2 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1484,6 +1484,19 @@ namespace Emby.Server.Implementations.Session throw new SecurityException("User is not allowed access from this device."); } + var sessionsCount = Sessions.Where(i => string.Equals(i.UserId, user.Id)).ToList().Count; + int maxActiveSessions = user.MaxActiveSessions; + _logger.LogDebug("Current/Max sessions for user {User}: {Sessions}/{Max}", user.Username, sessionsCount, maxActiveSessions); + if (maxActiveSessions >= 0 && sessionsCount >= maxActiveSessions) + { + throw new SecurityException( + "User {User} is at their maximum number of sessions ({Sessions}/{Max}).", + user.Username, + sessionsCount, + maxActiveSessions + ) + } + var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName); var session = LogSessionActivity( diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index f7ab57a1b1..daa4de0b57 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -188,6 +188,11 @@ namespace Jellyfin.Data.Entities /// </summary> public int? LoginAttemptsBeforeLockout { get; set; } + /// <summary> + /// Gets or sets the maximum number of active sessions the user can have at once. + /// </summary> + public int? MaxActiveSessions { get; set; } + /// <summary> /// Gets or sets the subtitle mode. /// </summary> diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 8f04baa089..43698efb75 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -379,6 +379,7 @@ namespace Jellyfin.Server.Implementations.Users PasswordResetProviderId = user.PasswordResetProviderId, InvalidLoginAttemptCount = user.InvalidLoginAttemptCount, LoginAttemptsBeforeLockout = user.LoginAttemptsBeforeLockout ?? -1, + MaxActiveSessions = user.MaxActiveSessions ?? -1, IsAdministrator = user.HasPermission(PermissionKind.IsAdministrator), IsHidden = user.HasPermission(PermissionKind.IsHidden), IsDisabled = user.HasPermission(PermissionKind.IsDisabled), @@ -701,6 +702,7 @@ namespace Jellyfin.Server.Implementations.Users user.PasswordResetProviderId = policy.PasswordResetProviderId; user.InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount; user.LoginAttemptsBeforeLockout = maxLoginAttempts; + user.MaxActiveSessions = policy.MaxActiveSessions; user.SyncPlayAccess = policy.SyncPlayAccess; user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator); user.SetPermission(PermissionKind.IsHidden, policy.IsHidden); diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index a1f01f7e8e..53dcb6bbd8 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -92,6 +92,10 @@ namespace MediaBrowser.Model.Users public int LoginAttemptsBeforeLockout { get; set; } + public int ActiveSessionCount { get; set; } + + public int MaxActiveSessions { get; set; } + public bool EnablePublicSharing { get; set; } public Guid[] BlockedMediaFolders { get; set; } @@ -144,6 +148,8 @@ namespace MediaBrowser.Model.Users LoginAttemptsBeforeLockout = -1; + MaxActiveSessions = -1; + EnableAllChannels = true; EnabledChannels = Array.Empty<Guid>(); -- cgit v1.2.3 From 975fca51583de23bafb6720104a2a6857ab29b18 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" <joshua@boniface.me> Date: Sun, 4 Oct 2020 13:19:56 -0400 Subject: Fix syntax error --- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 5903d395a2..ac59fe3860 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1494,7 +1494,7 @@ namespace Emby.Server.Implementations.Session user.Username, sessionsCount, maxActiveSessions - ) + ); } var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName); -- cgit v1.2.3 From b130af1b0c869fa6a35be4a53510e0f0fa4ad0d3 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" <joshua@boniface.me> Date: Sun, 4 Oct 2020 13:29:18 -0400 Subject: Remove variables from SecurityException --- Emby.Server.Implementations/Session/SessionManager.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index ac59fe3860..46fa29919a 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1489,12 +1489,7 @@ namespace Emby.Server.Implementations.Session _logger.LogDebug("Current/Max sessions for user {User}: {Sessions}/{Max}", user.Username, sessionsCount, maxActiveSessions); if (maxActiveSessions >= 0 && sessionsCount >= maxActiveSessions) { - throw new SecurityException( - "User {User} is at their maximum number of sessions ({Sessions}/{Max}).", - user.Username, - sessionsCount, - maxActiveSessions - ); + throw new SecurityException("User is at their maximum number of sessions."); } var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName); -- cgit v1.2.3 From 6e1d1eed23442976ac0d9b2a2af44b7a6804ea09 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" <joshua@boniface.me> Date: Sun, 4 Oct 2020 13:30:21 -0400 Subject: Make log entry informational --- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 46fa29919a..cd40bda556 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1486,7 +1486,7 @@ namespace Emby.Server.Implementations.Session var sessionsCount = Sessions.Where(i => string.Equals(i.UserId, user.Id)).ToList().Count; int maxActiveSessions = user.MaxActiveSessions; - _logger.LogDebug("Current/Max sessions for user {User}: {Sessions}/{Max}", user.Username, sessionsCount, maxActiveSessions); + _logger.LogInformation("Current/Max sessions for user {User}: {Sessions}/{Max}", user.Username, sessionsCount, maxActiveSessions); if (maxActiveSessions >= 0 && sessionsCount >= maxActiveSessions) { throw new SecurityException("User is at their maximum number of sessions."); -- cgit v1.2.3 From d0ec6872f3418a3bc6bacff6ba068526f45fc167 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" <joshua@boniface.me> Date: Sun, 4 Oct 2020 14:06:20 -0400 Subject: Increase count check to 1 There's another way to disable a user anyways. --- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index cd40bda556..90363e5603 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1487,7 +1487,7 @@ namespace Emby.Server.Implementations.Session var sessionsCount = Sessions.Where(i => string.Equals(i.UserId, user.Id)).ToList().Count; int maxActiveSessions = user.MaxActiveSessions; _logger.LogInformation("Current/Max sessions for user {User}: {Sessions}/{Max}", user.Username, sessionsCount, maxActiveSessions); - if (maxActiveSessions >= 0 && sessionsCount >= maxActiveSessions) + if (maxActiveSessions >= 1 && sessionsCount >= maxActiveSessions) { throw new SecurityException("User is at their maximum number of sessions."); } -- cgit v1.2.3 From 8dfa2015d0e804227fe9db09caf21315da7495e4 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" <joshua@boniface.me> Date: Sun, 4 Oct 2020 14:14:42 -0400 Subject: Make the count an int for cleanliness --- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 90363e5603..04becff49c 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1484,7 +1484,7 @@ namespace Emby.Server.Implementations.Session throw new SecurityException("User is not allowed access from this device."); } - var sessionsCount = Sessions.Where(i => string.Equals(i.UserId, user.Id)).ToList().Count; + int sessionsCount = Sessions.Where(i => string.Equals(i.UserId, user.Id)).ToList().Count; int maxActiveSessions = user.MaxActiveSessions; _logger.LogInformation("Current/Max sessions for user {User}: {Sessions}/{Max}", user.Username, sessionsCount, maxActiveSessions); if (maxActiveSessions >= 1 && sessionsCount >= maxActiveSessions) -- cgit v1.2.3 From 5aa519fd399d8b09a360f6448acc992225f8c219 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" <joshua@boniface.me> Date: Sun, 4 Oct 2020 15:46:57 -0400 Subject: Implement better count method --- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 04becff49c..d283ee8d8b 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1484,7 +1484,7 @@ namespace Emby.Server.Implementations.Session throw new SecurityException("User is not allowed access from this device."); } - int sessionsCount = Sessions.Where(i => string.Equals(i.UserId, user.Id)).ToList().Count; + int sessionsCount = Sessions.Count(i => string.Equals(i.UserId, user.Id, StringComparison.OrdinalIgnoreCase)); int maxActiveSessions = user.MaxActiveSessions; _logger.LogInformation("Current/Max sessions for user {User}: {Sessions}/{Max}", user.Username, sessionsCount, maxActiveSessions); if (maxActiveSessions >= 1 && sessionsCount >= maxActiveSessions) -- cgit v1.2.3 From df177b47524c0f7c5df21f32ebeaba5576d3f8a4 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" <joshua@boniface.me> Date: Sun, 4 Oct 2020 15:52:39 -0400 Subject: Revert "Implement better count method" This reverts commit 5aa519fd399d8b09a360f6448acc992225f8c219. --- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index d283ee8d8b..04becff49c 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1484,7 +1484,7 @@ namespace Emby.Server.Implementations.Session throw new SecurityException("User is not allowed access from this device."); } - int sessionsCount = Sessions.Count(i => string.Equals(i.UserId, user.Id, StringComparison.OrdinalIgnoreCase)); + int sessionsCount = Sessions.Where(i => string.Equals(i.UserId, user.Id)).ToList().Count; int maxActiveSessions = user.MaxActiveSessions; _logger.LogInformation("Current/Max sessions for user {User}: {Sessions}/{Max}", user.Username, sessionsCount, maxActiveSessions); if (maxActiveSessions >= 1 && sessionsCount >= maxActiveSessions) -- cgit v1.2.3 From 67e89dd5255a8ae38e0f0f489595d290e718b7a1 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" <joshua@boniface.me> Date: Sun, 4 Oct 2020 16:57:48 -0400 Subject: Use nicer count comparitor --- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 04becff49c..fa4caeb18c 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1484,7 +1484,7 @@ namespace Emby.Server.Implementations.Session throw new SecurityException("User is not allowed access from this device."); } - int sessionsCount = Sessions.Where(i => string.Equals(i.UserId, user.Id)).ToList().Count; + int sessionsCount = Sessions.Count(i => i.UserId.Equals(user.Id)); int maxActiveSessions = user.MaxActiveSessions; _logger.LogInformation("Current/Max sessions for user {User}: {Sessions}/{Max}", user.Username, sessionsCount, maxActiveSessions); if (maxActiveSessions >= 1 && sessionsCount >= maxActiveSessions) -- cgit v1.2.3 From 3d69bcd19090a3e467594375db3ee25f39e3def9 Mon Sep 17 00:00:00 2001 From: cvium <clausvium@gmail.com> Date: Tue, 6 Oct 2020 08:14:56 +0200 Subject: Fix playbackstart not triggering in the new eventmanager --- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index b1ab20da22..607b322f2e 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -666,7 +666,7 @@ namespace Emby.Server.Implementations.Session } } - var eventArgs = new PlaybackProgressEventArgs + var eventArgs = new PlaybackStartEventArgs { Item = libraryItem, Users = users, -- cgit v1.2.3 From 8819a9d478e6fc11dbfdcff80d9a2dc175953373 Mon Sep 17 00:00:00 2001 From: Ionut Andrei Oanca <oancaionutandrei@gmail.com> Date: Thu, 24 Sep 2020 23:04:21 +0200 Subject: Add playlist-sync and group-wait to SyncPlay --- .../Session/SessionManager.cs | 6 +- .../SyncPlay/GroupController.cs | 681 +++++++++++++++++++++ .../SyncPlay/GroupStates/AbstractGroupState.cs | 218 +++++++ .../SyncPlay/GroupStates/IdleGroupState.cs | 121 ++++ .../SyncPlay/GroupStates/PausedGroupState.cs | 193 +++--- .../SyncPlay/GroupStates/PlayingGroupState.cs | 133 +++- .../SyncPlay/GroupStates/WaitingGroupState.cs | 653 ++++++++++++++++++++ .../SyncPlay/SyncPlayController.cs | 282 --------- .../SyncPlay/SyncPlayManager.cs | 129 ++-- Jellyfin.Api/Controllers/SyncPlayController.cs | 300 ++++++++- MediaBrowser.Api/SyncPlay/SyncPlayService.cs | 267 -------- MediaBrowser.Controller/Session/ISessionManager.cs | 12 +- MediaBrowser.Controller/SyncPlay/GroupInfo.cs | 154 ----- MediaBrowser.Controller/SyncPlay/GroupMember.cs | 20 +- .../SyncPlay/IPlaybackGroupRequest.cs | 7 +- .../SyncPlay/ISyncPlayController.cs | 40 +- .../SyncPlay/ISyncPlayManager.cs | 12 +- MediaBrowser.Controller/SyncPlay/ISyncPlayState.cs | 165 ++++- .../SyncPlay/ISyncPlayStateContext.cs | 160 ++++- .../SyncPlay/PlaybackRequest/BufferGroupRequest.cs | 18 +- .../PlaybackRequest/IgnoreWaitGroupRequest.cs | 30 + .../MovePlaylistItemGroupRequest.cs | 36 ++ .../PlaybackRequest/NextTrackGroupRequest.cs | 30 + .../SyncPlay/PlaybackRequest/PauseGroupRequest.cs | 6 +- .../SyncPlay/PlaybackRequest/PingGroupRequest.cs | 7 +- .../SyncPlay/PlaybackRequest/PlayGroupRequest.cs | 25 +- .../PlaybackRequest/PreviousTrackGroupRequest.cs | 30 + .../SyncPlay/PlaybackRequest/QueueGroupRequest.cs | 37 ++ .../SyncPlay/PlaybackRequest/ReadyGroupRequest.cs | 18 +- .../RemoveFromPlaylistGroupRequest.cs | 30 + .../SyncPlay/PlaybackRequest/SeekGroupRequest.cs | 6 +- .../PlaybackRequest/SetCurrentItemGroupRequest.cs | 30 + .../PlaybackRequest/SetRepeatModeGroupRequest.cs | 30 + .../PlaybackRequest/SetShuffleModeGroupRequest.cs | 30 + .../SyncPlay/PlaybackRequest/StopGroupRequest.cs | 24 + .../PlaybackRequest/UnpauseGroupRequest.cs | 24 + .../SyncPlay/Queue/PlayQueueManager.cs | 596 ++++++++++++++++++ .../SyncPlay/SyncPlayAbstractState.cs | 65 -- MediaBrowser.Model/SyncPlay/GroupInfoDto.cs | 26 +- MediaBrowser.Model/SyncPlay/GroupRepeatMode.cs | 23 + MediaBrowser.Model/SyncPlay/GroupShuffleMode.cs | 18 + MediaBrowser.Model/SyncPlay/GroupStateUpdate.cs | 22 + MediaBrowser.Model/SyncPlay/GroupUpdateType.cs | 8 +- MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs | 4 +- MediaBrowser.Model/SyncPlay/NewGroupRequest.cs | 16 + MediaBrowser.Model/SyncPlay/PlayQueueUpdate.cs | 52 ++ .../SyncPlay/PlayQueueUpdateReason.cs | 58 ++ MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs | 70 ++- MediaBrowser.Model/SyncPlay/QueueItem.cs | 24 + MediaBrowser.Model/SyncPlay/SendCommand.cs | 6 + MediaBrowser.Model/SyncPlay/SendCommandType.cs | 11 +- 51 files changed, 3842 insertions(+), 1121 deletions(-) create mode 100644 Emby.Server.Implementations/SyncPlay/GroupController.cs create mode 100644 Emby.Server.Implementations/SyncPlay/GroupStates/AbstractGroupState.cs create mode 100644 Emby.Server.Implementations/SyncPlay/GroupStates/IdleGroupState.cs create mode 100644 Emby.Server.Implementations/SyncPlay/GroupStates/WaitingGroupState.cs delete mode 100644 Emby.Server.Implementations/SyncPlay/SyncPlayController.cs delete mode 100644 MediaBrowser.Api/SyncPlay/SyncPlayService.cs delete mode 100644 MediaBrowser.Controller/SyncPlay/GroupInfo.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/IgnoreWaitGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/MovePlaylistItemGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/NextTrackGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/PreviousTrackGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/QueueGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/RemoveFromPlaylistGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetCurrentItemGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetRepeatModeGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetShuffleModeGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/StopGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/PlaybackRequest/UnpauseGroupRequest.cs create mode 100644 MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs delete mode 100644 MediaBrowser.Controller/SyncPlay/SyncPlayAbstractState.cs create mode 100644 MediaBrowser.Model/SyncPlay/GroupRepeatMode.cs create mode 100644 MediaBrowser.Model/SyncPlay/GroupShuffleMode.cs create mode 100644 MediaBrowser.Model/SyncPlay/GroupStateUpdate.cs create mode 100644 MediaBrowser.Model/SyncPlay/NewGroupRequest.cs create mode 100644 MediaBrowser.Model/SyncPlay/PlayQueueUpdate.cs create mode 100644 MediaBrowser.Model/SyncPlay/PlayQueueUpdateReason.cs create mode 100644 MediaBrowser.Model/SyncPlay/QueueItem.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 607b322f2e..9a94d05eb0 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1182,18 +1182,16 @@ namespace Emby.Server.Implementations.Session } /// <inheritdoc /> - public async Task SendSyncPlayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken) + public async Task SendSyncPlayCommand(SessionInfo session, SendCommand command, CancellationToken cancellationToken) { CheckDisposed(); - var session = GetSessionToRemoteControl(sessionId); await SendMessageToSession(session, SessionMessageType.SyncPlayCommand, command, cancellationToken).ConfigureAwait(false); } /// <inheritdoc /> - public async Task SendSyncPlayGroupUpdate<T>(string sessionId, GroupUpdate<T> command, CancellationToken cancellationToken) + public async Task SendSyncPlayGroupUpdate<T>(SessionInfo session, GroupUpdate<T> command, CancellationToken cancellationToken) { CheckDisposed(); - var session = GetSessionToRemoteControl(sessionId); await SendMessageToSession(session, SessionMessageType.SyncPlayGroupUpdate, command, cancellationToken).ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/SyncPlay/GroupController.cs b/Emby.Server.Implementations/SyncPlay/GroupController.cs new file mode 100644 index 0000000000..ee2e9eb8f1 --- /dev/null +++ b/Emby.Server.Implementations/SyncPlay/GroupController.cs @@ -0,0 +1,681 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.SyncPlay; +using MediaBrowser.Model.SyncPlay; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.SyncPlay +{ + /// <summary> + /// Class SyncPlayGroupController. + /// </summary> + /// <remarks> + /// Class is not thread-safe, external locking is required when accessing methods. + /// </remarks> + public class SyncPlayGroupController : ISyncPlayGroupController, ISyncPlayStateContext + { + /// <summary> + /// Gets the default ping value used for sessions. + /// </summary> + public long DefaultPing { get; } = 500; + + /// <summary> + /// The logger. + /// </summary> + private readonly ILogger _logger; + + /// <summary> + /// The user manager. + /// </summary> + private readonly IUserManager _userManager; + + /// <summary> + /// The session manager. + /// </summary> + private readonly ISessionManager _sessionManager; + + /// <summary> + /// The library manager. + /// </summary> + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// The SyncPlay manager. + /// </summary> + private readonly ISyncPlayManager _syncPlayManager; + + /// <summary> + /// Internal group state. + /// </summary> + /// <value>The group's state.</value> + private ISyncPlayState State; + + /// <summary> + /// Gets the group identifier. + /// </summary> + /// <value>The group identifier.</value> + public Guid GroupId { get; } = Guid.NewGuid(); + + /// <summary> + /// Gets the group name. + /// </summary> + /// <value>The group name.</value> + public string GroupName { get; private set; } + + /// <summary> + /// Gets the group identifier. + /// </summary> + /// <value>The group identifier.</value> + public PlayQueueManager PlayQueue { get; } = new PlayQueueManager(); + + /// <summary> + /// Gets or sets the runtime ticks of current playing item. + /// </summary> + /// <value>The runtime ticks of current playing item.</value> + public long RunTimeTicks { get; private set; } + + /// <summary> + /// Gets or sets the position ticks. + /// </summary> + /// <value>The position ticks.</value> + public long PositionTicks { get; set; } + + /// <summary> + /// Gets or sets the last activity. + /// </summary> + /// <value>The last activity.</value> + public DateTime LastActivity { get; set; } + + /// <summary> + /// Gets the participants. + /// </summary> + /// <value>The participants, or members of the group.</value> + public Dictionary<string, GroupMember> Participants { get; } = + new Dictionary<string, GroupMember>(StringComparer.OrdinalIgnoreCase); + + /// <summary> + /// Initializes a new instance of the <see cref="SyncPlayGroupController" /> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="userManager">The user manager.</param> + /// <param name="sessionManager">The session manager.</param> + /// <param name="libraryManager">The library manager.</param> + /// <param name="syncPlayManager">The SyncPlay manager.</param> + public SyncPlayGroupController( + ILogger logger, + IUserManager userManager, + ISessionManager sessionManager, + ILibraryManager libraryManager, + ISyncPlayManager syncPlayManager) + { + _logger = logger; + _userManager = userManager; + _sessionManager = sessionManager; + _libraryManager = libraryManager; + _syncPlayManager = syncPlayManager; + + State = new IdleGroupState(_logger); + } + + /// <summary> + /// Checks if a session is in this group. + /// </summary> + /// <param name="sessionId">The session id to check.</param> + /// <returns><c>true</c> if the session is in this group; <c>false</c> otherwise.</returns> + private bool ContainsSession(string sessionId) + { + return Participants.ContainsKey(sessionId); + } + + /// <summary> + /// Adds the session to the group. + /// </summary> + /// <param name="session">The session.</param> + private void AddSession(SessionInfo session) + { + Participants.TryAdd( + session.Id, + new GroupMember + { + Session = session, + Ping = DefaultPing, + IsBuffering = false + }); + } + + /// <summary> + /// Removes the session from the group. + /// </summary> + /// <param name="session">The session.</param> + private void RemoveSession(SessionInfo session) + { + Participants.Remove(session.Id); + } + + /// <summary> + /// Filters sessions of this group. + /// </summary> + /// <param name="from">The current session.</param> + /// <param name="type">The filtering type.</param> + /// <returns>The array of sessions matching the filter.</returns> + private SessionInfo[] FilterSessions(SessionInfo from, SyncPlayBroadcastType type) + { + switch (type) + { + case SyncPlayBroadcastType.CurrentSession: + return new SessionInfo[] { from }; + case SyncPlayBroadcastType.AllGroup: + return Participants.Values.Select( + session => session.Session).ToArray(); + case SyncPlayBroadcastType.AllExceptCurrentSession: + return Participants.Values.Select( + session => session.Session).Where( + session => !session.Id.Equals(from.Id)).ToArray(); + case SyncPlayBroadcastType.AllReady: + return Participants.Values.Where( + session => !session.IsBuffering).Select( + session => session.Session).ToArray(); + default: + return Array.Empty<SessionInfo>(); + } + } + + private bool HasAccessToItem(User user, BaseItem item) + { + var collections = _libraryManager.GetCollectionFolders(item) + .Select(folder => folder.Id.ToString("N", CultureInfo.InvariantCulture)); + return collections.Intersect(user.GetPreference(PreferenceKind.EnabledFolders)).Any(); + } + + private bool HasAccessToQueue(User user, Guid[] queue) + { + if (queue == null || queue.Length == 0) + { + return true; + } + + var items = queue.ToList() + .Select(item => _libraryManager.GetItemById(item)); + + // Find the highest rating value, which becomes the required minimum for the user + var MinParentalRatingAccessRequired = items + .Select(item => item.InheritedParentalRatingValue) + .Min(); + + // Check ParentalRating access, user must have the minimum required access level + var hasParentalRatingAccess = !user.MaxParentalAgeRating.HasValue + || MinParentalRatingAccessRequired <= user.MaxParentalAgeRating; + + // Check that user has access to all required folders + if (!user.HasPermission(PermissionKind.EnableAllFolders) && hasParentalRatingAccess) + { + // Get list of items that are not accessible + var blockedItems = items.Where(item => !HasAccessToItem(user, item)); + + // We need the user to be able to access all items + return !blockedItems.Any(); + } + + return hasParentalRatingAccess; + } + + private bool AllUsersHaveAccessToQueue(Guid[] queue) + { + if (queue == null || queue.Length == 0) + { + return true; + } + + // Get list of users + var users = Participants.Values + .Select(participant => _userManager.GetUserById(participant.Session.UserId)); + + // Find problematic users + var usersWithNoAccess = users.Where(user => !HasAccessToQueue(user, queue)); + + // All users must be able to access the queue + return !usersWithNoAccess.Any(); + } + + /// <inheritdoc /> + public bool IsGroupEmpty() => Participants.Count == 0; + + /// <inheritdoc /> + public void CreateGroup(SessionInfo session, NewGroupRequest request, CancellationToken cancellationToken) + { + GroupName = request.GroupName; + AddSession(session); + _syncPlayManager.AddSessionToGroup(session, this); + + var sessionIsPlayingAnItem = session.FullNowPlayingItem != null; + + RestartCurrentItem(); + + if (sessionIsPlayingAnItem) + { + var playlist = session.NowPlayingQueue.Select(item => item.Id).ToArray(); + PlayQueue.SetPlaylist(playlist); + PlayQueue.SetPlayingItemById(session.FullNowPlayingItem.Id); + RunTimeTicks = session.FullNowPlayingItem.RunTimeTicks ?? 0; + PositionTicks = session.PlayState.PositionTicks ?? 0; + + // Mantain playstate + var waitingState = new WaitingGroupState(_logger); + waitingState.ResumePlaying = !session.PlayState.IsPaused; + SetState(waitingState); + } + + var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, GetInfo()); + SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken); + + State.SessionJoined(this, State.GetGroupState(), session, cancellationToken); + + _logger.LogInformation("InitGroup: {0} created group {1}.", session.Id.ToString(), GroupId.ToString()); + } + + /// <inheritdoc /> + public void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken) + { + AddSession(session); + _syncPlayManager.AddSessionToGroup(session, this); + + var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, GetInfo()); + SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken); + + var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserJoined, session.UserName); + SendGroupUpdate(session, SyncPlayBroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); + + State.SessionJoined(this, State.GetGroupState(), session, cancellationToken); + + _logger.LogInformation("SessionJoin: {0} joined group {1}.", session.Id.ToString(), GroupId.ToString()); + } + + /// <inheritdoc /> + public void SessionRestore(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken) + { + var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, GetInfo()); + SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken); + + var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserJoined, session.UserName); + SendGroupUpdate(session, SyncPlayBroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); + + State.SessionJoined(this, State.GetGroupState(), session, cancellationToken); + + _logger.LogInformation("SessionRestore: {0} re-joined group {1}.", session.Id.ToString(), GroupId.ToString()); + } + + /// <inheritdoc /> + public void SessionLeave(SessionInfo session, CancellationToken cancellationToken) + { + State.SessionLeaving(this, State.GetGroupState(), session, cancellationToken); + + RemoveSession(session); + _syncPlayManager.RemoveSessionFromGroup(session, this); + + var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupLeft, GroupId.ToString()); + SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken); + + var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserLeft, session.UserName); + SendGroupUpdate(session, SyncPlayBroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); + + _logger.LogInformation("SessionLeave: {0} left group {1}.", session.Id.ToString(), GroupId.ToString()); + } + + /// <inheritdoc /> + public void HandleRequest(SessionInfo session, IPlaybackGroupRequest request, CancellationToken cancellationToken) + { + // The server's job is to maintain a consistent state for clients to reference + // and notify clients of state changes. The actual syncing of media playback + // happens client side. Clients are aware of the server's time and use it to sync. + _logger.LogInformation("HandleRequest: {0} requested {1}, group {2} in {3} state.", + session.Id.ToString(), request.GetRequestType(), GroupId.ToString(), State.GetGroupState()); + request.Apply(this, State, session, cancellationToken); + } + + /// <inheritdoc /> + public GroupInfoDto GetInfo() + { + return new GroupInfoDto() + { + GroupId = GroupId.ToString(), + GroupName = GroupName, + State = State.GetGroupState(), + Participants = Participants.Values.Select(session => session.Session.UserName).Distinct().ToList(), + LastUpdatedAt = DateToUTCString(DateTime.UtcNow) + }; + } + + /// <inheritdoc /> + public bool HasAccessToPlayQueue(User user) + { + var items = PlayQueue.GetPlaylist().Select(item => item.ItemId).ToArray(); + return HasAccessToQueue(user, items); + } + + /// <inheritdoc /> + public void SetIgnoreGroupWait(SessionInfo session, bool ignoreGroupWait) + { + if (!ContainsSession(session.Id)) + { + return; + } + + Participants[session.Id].IgnoreGroupWait = ignoreGroupWait; + } + + /// <inheritdoc /> + public void SetState(ISyncPlayState state) + { + _logger.LogInformation("SetState: {0} switching from {1} to {2}.", GroupId.ToString(), State.GetGroupState(), state.GetGroupState()); + this.State = state; + } + + /// <inheritdoc /> + public Task SendGroupUpdate<T>(SessionInfo from, SyncPlayBroadcastType type, GroupUpdate<T> message, CancellationToken cancellationToken) + { + IEnumerable<Task> GetTasks() + { + foreach (var session in FilterSessions(from, type)) + { + yield return _sessionManager.SendSyncPlayGroupUpdate(session, message, cancellationToken); + } + } + + return Task.WhenAll(GetTasks()); + } + + /// <inheritdoc /> + public Task SendCommand(SessionInfo from, SyncPlayBroadcastType type, SendCommand message, CancellationToken cancellationToken) + { + IEnumerable<Task> GetTasks() + { + foreach (var session in FilterSessions(from, type)) + { + yield return _sessionManager.SendSyncPlayCommand(session, message, cancellationToken); + } + } + + return Task.WhenAll(GetTasks()); + } + + /// <inheritdoc /> + public SendCommand NewSyncPlayCommand(SendCommandType type) + { + return new SendCommand() + { + GroupId = GroupId.ToString(), + PlaylistItemId = PlayQueue.GetPlayingItemPlaylistId(), + PositionTicks = PositionTicks, + Command = type, + When = DateToUTCString(LastActivity), + EmittedAt = DateToUTCString(DateTime.UtcNow) + }; + } + + /// <inheritdoc /> + public GroupUpdate<T> NewSyncPlayGroupUpdate<T>(GroupUpdateType type, T data) + { + return new GroupUpdate<T>() + { + GroupId = GroupId.ToString(), + Type = type, + Data = data + }; + } + + /// <inheritdoc /> + public string DateToUTCString(DateTime dateTime) + { + return dateTime.ToUniversalTime().ToString("o"); + } + + /// <inheritdoc /> + public long SanitizePositionTicks(long? positionTicks) + { + var ticks = positionTicks ?? 0; + ticks = ticks >= 0 ? ticks : 0; + ticks = ticks > RunTimeTicks ? RunTimeTicks : ticks; + return ticks; + } + + /// <inheritdoc /> + public void UpdatePing(SessionInfo session, long ping) + { + if (Participants.TryGetValue(session.Id, out GroupMember value)) + { + value.Ping = ping; + } + } + + /// <inheritdoc /> + public long GetHighestPing() + { + long max = long.MinValue; + foreach (var session in Participants.Values) + { + max = Math.Max(max, session.Ping); + } + + return max; + } + + /// <inheritdoc /> + public void SetBuffering(SessionInfo session, bool isBuffering) + { + if (Participants.TryGetValue(session.Id, out GroupMember value)) + { + value.IsBuffering = isBuffering; + } + } + + /// <inheritdoc /> + public void SetAllBuffering(bool isBuffering) + { + foreach (var session in Participants.Values) + { + session.IsBuffering = isBuffering; + } + } + + /// <inheritdoc /> + public bool IsBuffering() + { + foreach (var session in Participants.Values) + { + if (session.IsBuffering && !session.IgnoreGroupWait) + { + return true; + } + } + + return false; + } + + /// <inheritdoc /> + public bool SetPlayQueue(Guid[] playQueue, int playingItemPosition, long startPositionTicks) + { + // Ignore on empty queue or invalid item position + if (playQueue.Length < 1 || playingItemPosition >= playQueue.Length || playingItemPosition < 0) + { + return false; + } + + // Check is participants can access the new playing queue + if (!AllUsersHaveAccessToQueue(playQueue)) + { + return false; + } + + PlayQueue.SetPlaylist(playQueue); + PlayQueue.SetPlayingItemByIndex(playingItemPosition); + var item = _libraryManager.GetItemById(PlayQueue.GetPlayingItemId()); + RunTimeTicks = item.RunTimeTicks ?? 0; + PositionTicks = startPositionTicks; + LastActivity = DateTime.UtcNow; + + return true; + } + + /// <inheritdoc /> + public bool SetPlayingItem(string playlistItemId) + { + var itemFound = PlayQueue.SetPlayingItemByPlaylistId(playlistItemId); + + if (itemFound) + { + var item = _libraryManager.GetItemById(PlayQueue.GetPlayingItemId()); + RunTimeTicks = item.RunTimeTicks ?? 0; + } + else + { + RunTimeTicks = 0; + } + + RestartCurrentItem(); + + return itemFound; + } + + /// <inheritdoc /> + public bool RemoveFromPlayQueue(string[] playlistItemIds) + { + var playingItemRemoved = PlayQueue.RemoveFromPlaylist(playlistItemIds); + if (playingItemRemoved) + { + var itemId = PlayQueue.GetPlayingItemId(); + if (!itemId.Equals(Guid.Empty)) + { + var item = _libraryManager.GetItemById(itemId); + RunTimeTicks = item.RunTimeTicks ?? 0; + } + else + { + RunTimeTicks = 0; + } + + RestartCurrentItem(); + } + + return playingItemRemoved; + } + + /// <inheritdoc /> + public bool MoveItemInPlayQueue(string playlistItemId, int newIndex) + { + return PlayQueue.MovePlaylistItem(playlistItemId, newIndex); + } + + /// <inheritdoc /> + public bool AddToPlayQueue(Guid[] newItems, string mode) + { + // Ignore on empty list + if (newItems.Length < 1) + { + return false; + } + + // Check is participants can access the new playing queue + if (!AllUsersHaveAccessToQueue(newItems)) + { + return false; + } + + if (mode.Equals("next")) + { + PlayQueue.QueueNext(newItems); + } + else + { + PlayQueue.Queue(newItems); + } + + return true; + } + + /// <inheritdoc /> + public void RestartCurrentItem() + { + PositionTicks = 0; + LastActivity = DateTime.UtcNow; + } + + /// <inheritdoc /> + public bool NextItemInQueue() + { + var update = PlayQueue.Next(); + if (update) + { + var item = _libraryManager.GetItemById(PlayQueue.GetPlayingItemId()); + RunTimeTicks = item.RunTimeTicks ?? 0; + RestartCurrentItem(); + return true; + } + else + { + return false; + } + } + + /// <inheritdoc /> + public bool PreviousItemInQueue() + { + var update = PlayQueue.Previous(); + if (update) + { + var item = _libraryManager.GetItemById(PlayQueue.GetPlayingItemId()); + RunTimeTicks = item.RunTimeTicks ?? 0; + RestartCurrentItem(); + return true; + } + else + { + return false; + } + } + + /// <inheritdoc /> + public void SetRepeatMode(string mode) { + PlayQueue.SetRepeatMode(mode); + } + + /// <inheritdoc /> + public void SetShuffleMode(string mode) { + PlayQueue.SetShuffleMode(mode); + } + + /// <inheritdoc /> + public PlayQueueUpdate GetPlayQueueUpdate(PlayQueueUpdateReason reason) + { + var startPositionTicks = PositionTicks; + + if (State.GetGroupState().Equals(GroupState.Playing)) + { + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - LastActivity; + // Event may happen during the delay added to account for latency + startPositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; + } + + return new PlayQueueUpdate() + { + Reason = reason, + LastUpdate = DateToUTCString(PlayQueue.LastChange), + Playlist = PlayQueue.GetPlaylist(), + PlayingItemIndex = PlayQueue.PlayingItemIndex, + StartPositionTicks = startPositionTicks, + ShuffleMode = PlayQueue.ShuffleMode, + RepeatMode = PlayQueue.RepeatMode + }; + } + + } +} diff --git a/Emby.Server.Implementations/SyncPlay/GroupStates/AbstractGroupState.cs b/Emby.Server.Implementations/SyncPlay/GroupStates/AbstractGroupState.cs new file mode 100644 index 0000000000..1f0cb42870 --- /dev/null +++ b/Emby.Server.Implementations/SyncPlay/GroupStates/AbstractGroupState.cs @@ -0,0 +1,218 @@ +using System.Threading; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.SyncPlay; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// <summary> + /// Class AbstractGroupState. + /// </summary> + /// <remarks> + /// Class is not thread-safe, external locking is required when accessing methods. + /// </remarks> + public abstract class AbstractGroupState : ISyncPlayState + { + /// <summary> + /// The logger. + /// </summary> + protected readonly ILogger _logger; + + /// <summary> + /// Default constructor. + /// </summary> + public AbstractGroupState(ILogger logger) + { + _logger = logger; + } + + /// <summary> + /// Sends a group state update to all group. + /// </summary> + /// <param name="context">The context of the state.</param> + /// <param name="reason">The reason of the state change.</param> + /// <param name="session">The session.</param> + /// <param name="cancellationToken">The cancellation token.</param> + protected void SendGroupStateUpdate(ISyncPlayStateContext context, IPlaybackGroupRequest reason, SessionInfo session, CancellationToken cancellationToken) + { + // Notify relevant state change event + var stateUpdate = new GroupStateUpdate() + { + State = GetGroupState(), + Reason = reason.GetRequestType() + }; + var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.StateUpdate, stateUpdate); + context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken); + } + + /// <inheritdoc /> + public abstract GroupState GetGroupState(); + + /// <inheritdoc /> + public abstract void SessionJoined(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken); + + /// <inheritdoc /> + public abstract void SessionLeaving(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken); + + /// <inheritdoc /> + public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, IPlaybackGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + UnhandledRequest(request); + } + + /// <inheritdoc /> + public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + UnhandledRequest(request); + } + + /// <inheritdoc /> + public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SetPlaylistItemGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + var waitingState = new WaitingGroupState(_logger); + context.SetState(waitingState); + waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken); + } + + /// <inheritdoc /> + public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, RemoveFromPlaylistGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + var playingItemRemoved = context.RemoveFromPlayQueue(request.PlaylistItemIds); + + var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.RemoveItems); + var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate); + context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken); + + if (playingItemRemoved) + { + var PlayingItemIndex = context.PlayQueue.PlayingItemIndex; + if (context.PlayQueue.PlayingItemIndex == -1) + { + _logger.LogDebug("HandleRequest: {0} in group {1}, play queue is empty.", request.GetRequestType(), context.GroupId.ToString()); + + ISyncPlayState idleState = new IdleGroupState(_logger); + context.SetState(idleState); + var stopRequest = new StopGroupRequest(); + idleState.HandleRequest(context, GetGroupState(), stopRequest, session, cancellationToken); + } + } + } + + /// <inheritdoc /> + public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, MovePlaylistItemGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + var result = context.MoveItemInPlayQueue(request.PlaylistItemId, request.NewIndex); + + if (!result) + { + _logger.LogError("HandleRequest: {0} in group {1}, unable to move item in play queue.", request.GetRequestType(), context.GroupId.ToString()); + return; + } + + var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.MoveItem); + var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate); + context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken); + } + + /// <inheritdoc /> + public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, QueueGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + var result = context.AddToPlayQueue(request.ItemIds, request.Mode); + + if (!result) + { + _logger.LogError("HandleRequest: {0} in group {1}, unable to add items to play queue.", request.GetRequestType(), context.GroupId.ToString()); + return; + } + + var reason = request.Mode.Equals("next") ? PlayQueueUpdateReason.QueueNext : PlayQueueUpdateReason.Queue; + var playQueueUpdate = context.GetPlayQueueUpdate(reason); + var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate); + context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken); + } + + /// <inheritdoc /> + public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, UnpauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + UnhandledRequest(request); + } + + /// <inheritdoc /> + public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + UnhandledRequest(request); + } + + /// <inheritdoc /> + public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + UnhandledRequest(request); + } + + /// <inheritdoc /> + public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + UnhandledRequest(request); + } + + /// <inheritdoc /> + public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + UnhandledRequest(request); + } + + /// <inheritdoc /> + public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + UnhandledRequest(request); + } + + /// <inheritdoc /> + public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + UnhandledRequest(request); + } + + /// <inheritdoc /> + public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + UnhandledRequest(request); + } + + /// <inheritdoc /> + public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SetRepeatModeGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + context.SetRepeatMode(request.Mode); + var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.RepeatMode); + var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate); + context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken); + } + + /// <inheritdoc /> + public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SetShuffleModeGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + context.SetShuffleMode(request.Mode); + var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.ShuffleMode); + var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate); + context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken); + } + + /// <inheritdoc /> + public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PingGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + // Collected pings are used to account for network latency when unpausing playback + context.UpdatePing(session, request.Ping); + } + + /// <inheritdoc /> + public virtual void HandleRequest(ISyncPlayStateContext context, GroupState prevState, IgnoreWaitGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + context.SetIgnoreGroupWait(session, request.IgnoreWait); + } + + private void UnhandledRequest(IPlaybackGroupRequest request) + { + _logger.LogWarning("HandleRequest: unhandled {0} request for {1} state.", request.GetRequestType(), this.GetGroupState()); + } + } +} diff --git a/Emby.Server.Implementations/SyncPlay/GroupStates/IdleGroupState.cs b/Emby.Server.Implementations/SyncPlay/GroupStates/IdleGroupState.cs new file mode 100644 index 0000000000..d6b981c584 --- /dev/null +++ b/Emby.Server.Implementations/SyncPlay/GroupStates/IdleGroupState.cs @@ -0,0 +1,121 @@ +using System.Threading; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.SyncPlay; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// <summary> + /// Class IdleGroupState. + /// </summary> + /// <remarks> + /// Class is not thread-safe, external locking is required when accessing methods. + /// </remarks> + public class IdleGroupState : AbstractGroupState + { + /// <summary> + /// Default constructor. + /// </summary> + public IdleGroupState(ILogger logger) : base(logger) + { + // Do nothing + } + + /// <inheritdoc /> + public override GroupState GetGroupState() + { + return GroupState.Idle; + } + + /// <inheritdoc /> + public override void SessionJoined(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken) + { + SendStopCommand(context, GetGroupState(), session, cancellationToken); + } + + /// <inheritdoc /> + public override void SessionLeaving(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken) + { + // Do nothing + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + // Change state + var waitingState = new WaitingGroupState(_logger); + context.SetState(waitingState); + waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken); + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, UnpauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + // Change state + var waitingState = new WaitingGroupState(_logger); + context.SetState(waitingState); + waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken); + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + SendStopCommand(context, prevState, session, cancellationToken); + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + SendStopCommand(context, prevState, session, cancellationToken); + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + SendStopCommand(context, prevState, session, cancellationToken); + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + SendStopCommand(context, prevState, session, cancellationToken); + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + SendStopCommand(context, prevState, session, cancellationToken); + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + // Change state + var waitingState = new WaitingGroupState(_logger); + context.SetState(waitingState); + waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken); + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + // Change state + var waitingState = new WaitingGroupState(_logger); + context.SetState(waitingState); + waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken); + } + + private void SendStopCommand(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken) + { + var command = context.NewSyncPlayCommand(SendCommandType.Stop); + if (!prevState.Equals(GetGroupState())) + { + context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken); + } + else + { + context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken); + } + } + } +} diff --git a/Emby.Server.Implementations/SyncPlay/GroupStates/PausedGroupState.cs b/Emby.Server.Implementations/SyncPlay/GroupStates/PausedGroupState.cs index d3bf24f747..39c0511d9f 100644 --- a/Emby.Server.Implementations/SyncPlay/GroupStates/PausedGroupState.cs +++ b/Emby.Server.Implementations/SyncPlay/GroupStates/PausedGroupState.cs @@ -1,11 +1,8 @@ -using System.Linq; using System; using System.Threading; -using System.Collections.Generic; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Session; using MediaBrowser.Model.SyncPlay; +using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.SyncPlay { @@ -15,8 +12,16 @@ namespace MediaBrowser.Controller.SyncPlay /// <remarks> /// Class is not thread-safe, external locking is required when accessing methods. /// </remarks> - public class PausedGroupState : SyncPlayAbstractState + public class PausedGroupState : AbstractGroupState { + /// <summary> + /// Default constructor. + /// </summary> + public PausedGroupState(ILogger logger) : base(logger) + { + // Do nothing + } + /// <inheritdoc /> public override GroupState GetGroupState() { @@ -24,31 +29,56 @@ namespace MediaBrowser.Controller.SyncPlay } /// <inheritdoc /> - public override bool HandleRequest(ISyncPlayStateContext context, bool newState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + public override void SessionJoined(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken) + { + // Wait for session to be ready + var waitingState = new WaitingGroupState(_logger); + context.SetState(waitingState); + waitingState.SessionJoined(context, GetGroupState(), session, cancellationToken); + } + + /// <inheritdoc /> + public override void SessionLeaving(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken) + { + // Do nothing + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + // Change state + var waitingState = new WaitingGroupState(_logger); + context.SetState(waitingState); + waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken); + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, UnpauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { // Change state - var playingState = new PlayingGroupState(); + var playingState = new PlayingGroupState(_logger); context.SetState(playingState); - return playingState.HandleRequest(context, true, request, session, cancellationToken); + playingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken); } /// <inheritdoc /> - public override bool HandleRequest(ISyncPlayStateContext context, bool newState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { - if (newState) + if (!prevState.Equals(GetGroupState())) { - GroupInfo group = context.GetGroup(); - // Pause group and compute the media playback position var currentTime = DateTime.UtcNow; - var elapsedTime = currentTime - group.LastActivity; - group.LastActivity = currentTime; + var elapsedTime = currentTime - context.LastActivity; + context.LastActivity = currentTime; // Seek only if playback actually started // Pause request may be issued during the delay added to account for latency - group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; + context.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; var command = context.NewSyncPlayCommand(SendCommandType.Pause); context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken); + + // Notify relevant state change event + SendGroupStateUpdate(context, request, session, cancellationToken); } else { @@ -56,116 +86,71 @@ namespace MediaBrowser.Controller.SyncPlay var command = context.NewSyncPlayCommand(SendCommandType.Pause); context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken); } - - return true; } /// <inheritdoc /> - public override bool HandleRequest(ISyncPlayStateContext context, bool newState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { - GroupInfo group = context.GetGroup(); - - // Sanitize PositionTicks - var ticks = context.SanitizePositionTicks(request.PositionTicks); - - // Seek - group.PositionTicks = ticks; - group.LastActivity = DateTime.UtcNow; - - var command = context.NewSyncPlayCommand(SendCommandType.Seek); - context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken); - - return true; + // Change state + var idleState = new IdleGroupState(_logger); + context.SetState(idleState); + idleState.HandleRequest(context, GetGroupState(), request, session, cancellationToken); } /// <inheritdoc /> - public override bool HandleRequest(ISyncPlayStateContext context, bool newState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { - GroupInfo group = context.GetGroup(); - - if (newState) - { - // Pause group and compute the media playback position - var currentTime = DateTime.UtcNow; - var elapsedTime = currentTime - group.LastActivity; - group.LastActivity = currentTime; - group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; - - group.SetBuffering(session, true); + // Change state + var waitingState = new WaitingGroupState(_logger); + context.SetState(waitingState); + waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken); + } - // Send pause command to all non-buffering sessions - var command = context.NewSyncPlayCommand(SendCommandType.Pause); - context.SendCommand(session, SyncPlayBroadcastType.AllReady, command, cancellationToken); + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + // Change state + var waitingState = new WaitingGroupState(_logger); + context.SetState(waitingState); + waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken); + } - var updateOthers = context.NewSyncPlayGroupUpdate(GroupUpdateType.GroupWait, session.UserName); - context.SendGroupUpdate(session, SyncPlayBroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); - } - else + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + if (prevState.Equals(GetGroupState())) { - // TODO: no idea? - // group.SetBuffering(session, true); - // Client got lost, sending current state var command = context.NewSyncPlayCommand(SendCommandType.Pause); context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken); } + else if (prevState.Equals(GroupState.Waiting)) + { + // Sending current state to all clients + var command = context.NewSyncPlayCommand(SendCommandType.Pause); + context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken); - return true; + // Notify relevant state change event + SendGroupStateUpdate(context, request, session, cancellationToken); + } } /// <inheritdoc /> - public override bool HandleRequest(ISyncPlayStateContext context, bool newState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { - GroupInfo group = context.GetGroup(); - - group.SetBuffering(session, false); - - var requestTicks = context.SanitizePositionTicks(request.PositionTicks); - - var currentTime = DateTime.UtcNow; - var elapsedTime = currentTime - request.When; - var clientPosition = TimeSpan.FromTicks(requestTicks) + elapsedTime; - var delay = group.PositionTicks - clientPosition.Ticks; - - if (group.IsBuffering()) - { - // Others are still buffering, tell this client to pause when ready - var command = context.NewSyncPlayCommand(SendCommandType.Pause); - var pauseAtTime = currentTime.AddMilliseconds(delay); - command.When = context.DateToUTCString(pauseAtTime); - context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken); - } - else - { - // Let other clients resume as soon as the buffering client catches up - if (delay > group.GetHighestPing() * 2) - { - // Client that was buffering is recovering, notifying others to resume - group.LastActivity = currentTime.AddMilliseconds( - delay - ); - var command = context.NewSyncPlayCommand(SendCommandType.Play); - context.SendCommand(session, SyncPlayBroadcastType.AllExceptCurrentSession, command, cancellationToken); - } - else - { - // Client, that was buffering, resumed playback but did not update others in time - delay = Math.Max(group.GetHighestPing() * 2, group.DefaultPing); - - group.LastActivity = currentTime.AddMilliseconds( - delay - ); - - var command = context.NewSyncPlayCommand(SendCommandType.Play); - context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken); - } - - // Change state - var playingState = new PlayingGroupState(); - context.SetState(playingState); - } + // Change state + var waitingState = new WaitingGroupState(_logger); + context.SetState(waitingState); + waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken); + } - return true; + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + // Change state + var waitingState = new WaitingGroupState(_logger); + context.SetState(waitingState); + waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken); } } } diff --git a/Emby.Server.Implementations/SyncPlay/GroupStates/PlayingGroupState.cs b/Emby.Server.Implementations/SyncPlay/GroupStates/PlayingGroupState.cs index 42c7779c19..e2909ff91c 100644 --- a/Emby.Server.Implementations/SyncPlay/GroupStates/PlayingGroupState.cs +++ b/Emby.Server.Implementations/SyncPlay/GroupStates/PlayingGroupState.cs @@ -1,11 +1,8 @@ -using System.Linq; using System; using System.Threading; -using System.Collections.Generic; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Session; using MediaBrowser.Model.SyncPlay; +using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.SyncPlay { @@ -15,8 +12,21 @@ namespace MediaBrowser.Controller.SyncPlay /// <remarks> /// Class is not thread-safe, external locking is required when accessing methods. /// </remarks> - public class PlayingGroupState : SyncPlayAbstractState + public class PlayingGroupState : AbstractGroupState { + /// <summary> + /// Ignore requests for buffering. + /// </summary> + public bool IgnoreBuffering { get; set; } + + /// <summary> + /// Default constructor. + /// </summary> + public PlayingGroupState(ILogger logger) : base(logger) + { + // Do nothing + } + /// <inheritdoc /> public override GroupState GetGroupState() { @@ -24,71 +34,132 @@ namespace MediaBrowser.Controller.SyncPlay } /// <inheritdoc /> - public override bool HandleRequest(ISyncPlayStateContext context, bool newState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + public override void SessionJoined(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken) { - GroupInfo group = context.GetGroup(); + // Wait for session to be ready + var waitingState = new WaitingGroupState(_logger); + context.SetState(waitingState); + waitingState.SessionJoined(context, GetGroupState(), session, cancellationToken); + } - if (newState) + /// <inheritdoc /> + public override void SessionLeaving(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken) + { + // Do nothing + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + // Change state + var waitingState = new WaitingGroupState(_logger); + context.SetState(waitingState); + waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken); + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, UnpauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + if (!prevState.Equals(GetGroupState())) { // Pick a suitable time that accounts for latency - var delay = Math.Max(group.GetHighestPing() * 2, group.DefaultPing); + var delayMillis = Math.Max(context.GetHighestPing() * 2, context.DefaultPing); // Unpause group and set starting point in future // Clients will start playback at LastActivity (datetime) from PositionTicks (playback position) // The added delay does not guarantee, of course, that the command will be received in time // Playback synchronization will mainly happen client side - group.LastActivity = DateTime.UtcNow.AddMilliseconds( - delay + context.LastActivity = DateTime.UtcNow.AddMilliseconds( + delayMillis ); - var command = context.NewSyncPlayCommand(SendCommandType.Play); + var command = context.NewSyncPlayCommand(SendCommandType.Unpause); context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken); + + // Notify relevant state change event + SendGroupStateUpdate(context, request, session, cancellationToken); } else { // Client got lost, sending current state - var command = context.NewSyncPlayCommand(SendCommandType.Play); + var command = context.NewSyncPlayCommand(SendCommandType.Unpause); context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken); } - - return true; } /// <inheritdoc /> - public override bool HandleRequest(ISyncPlayStateContext context, bool newState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { // Change state - var pausedState = new PausedGroupState(); + var pausedState = new PausedGroupState(_logger); context.SetState(pausedState); - return pausedState.HandleRequest(context, true, request, session, cancellationToken); + pausedState.HandleRequest(context, GetGroupState(), request, session, cancellationToken); } /// <inheritdoc /> - public override bool HandleRequest(ISyncPlayStateContext context, bool newState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { // Change state - var pausedState = new PausedGroupState(); - context.SetState(pausedState); - return pausedState.HandleRequest(context, true, request, session, cancellationToken); + var idleState = new IdleGroupState(_logger); + context.SetState(idleState); + idleState.HandleRequest(context, GetGroupState(), request, session, cancellationToken); } /// <inheritdoc /> - public override bool HandleRequest(ISyncPlayStateContext context, bool newState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { // Change state - var pausedState = new PausedGroupState(); - context.SetState(pausedState); - return pausedState.HandleRequest(context, true, request, session, cancellationToken); + var waitingState = new WaitingGroupState(_logger); + context.SetState(waitingState); + waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken); + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + if (IgnoreBuffering) + { + return; + } + + // Change state + var waitingState = new WaitingGroupState(_logger); + context.SetState(waitingState); + waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken); } /// <inheritdoc /> - public override bool HandleRequest(ISyncPlayStateContext context, bool newState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken) { - // Group was not waiting, make sure client has latest state - var command = context.NewSyncPlayCommand(SendCommandType.Play); - context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken); + if (prevState.Equals(GetGroupState())) + { + // Group was not waiting, make sure client has latest state + var command = context.NewSyncPlayCommand(SendCommandType.Unpause); + context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken); + } + else if (prevState.Equals(GroupState.Waiting)) + { + // Notify relevant state change event + SendGroupStateUpdate(context, request, session, cancellationToken); + } + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + // Change state + var waitingState = new WaitingGroupState(_logger); + context.SetState(waitingState); + waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken); + } - return true; + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + // Change state + var waitingState = new WaitingGroupState(_logger); + context.SetState(waitingState); + waitingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken); } } } diff --git a/Emby.Server.Implementations/SyncPlay/GroupStates/WaitingGroupState.cs b/Emby.Server.Implementations/SyncPlay/GroupStates/WaitingGroupState.cs new file mode 100644 index 0000000000..9d839b268b --- /dev/null +++ b/Emby.Server.Implementations/SyncPlay/GroupStates/WaitingGroupState.cs @@ -0,0 +1,653 @@ +using System; +using System.Threading; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.SyncPlay; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// <summary> + /// Class WaitingGroupState. + /// </summary> + /// <remarks> + /// Class is not thread-safe, external locking is required when accessing methods. + /// </remarks> + public class WaitingGroupState : AbstractGroupState + { + /// <summary> + /// Tells the state to switch to after buffering is done. + /// </summary> + public bool ResumePlaying { get; set; } = false; + + /// <summary> + /// Whether the initial state has been set. + /// </summary> + private bool InitialStateSet { get; set; } = false; + + /// <summary> + /// The group state before the first ever event. + /// </summary> + private GroupState InitialState { get; set; } + + /// <summary> + /// Default constructor. + /// </summary> + public WaitingGroupState(ILogger logger) : base(logger) + { + // Do nothing + } + + /// <inheritdoc /> + public override GroupState GetGroupState() + { + return GroupState.Waiting; + } + + /// <inheritdoc /> + public override void SessionJoined(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken) + { + // Save state if first event + if (!InitialStateSet) + { + InitialState = prevState; + InitialStateSet = true; + } + + if (prevState.Equals(GroupState.Playing)) { + ResumePlaying = true; + // Pause group and compute the media playback position + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - context.LastActivity; + context.LastActivity = currentTime; + // Seek only if playback actually started + // Event may happen during the delay added to account for latency + context.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; + } + + // Prepare new session + var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NewPlaylist); + var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate); + context.SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, update, cancellationToken); + + context.SetBuffering(session, true); + + // Send pause command to all non-buffering sessions + var command = context.NewSyncPlayCommand(SendCommandType.Pause); + context.SendCommand(session, SyncPlayBroadcastType.AllReady, command, cancellationToken); + } + + /// <inheritdoc /> + public override void SessionLeaving(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken) + { + // Save state if first event + if (!InitialStateSet) + { + InitialState = prevState; + InitialStateSet = true; + } + + context.SetBuffering(session, false); + + if (!context.IsBuffering()) + { + if (ResumePlaying) + { + // Client, that was buffering, left the group + var playingState = new PlayingGroupState(_logger); + context.SetState(playingState); + var unpauseRequest = new UnpauseGroupRequest(); + playingState.HandleRequest(context, GetGroupState(), unpauseRequest, session, cancellationToken); + + _logger.LogDebug("SessionLeaving: {0} left the group {1}, notifying others to resume.", session.Id.ToString(), context.GroupId.ToString()); + } + else + { + // Group is ready, returning to previous state + var pausedState = new PausedGroupState(_logger); + context.SetState(pausedState); + + _logger.LogDebug("SessionLeaving: {0} left the group {1}, returning to previous state.", session.Id.ToString(), context.GroupId.ToString()); + } + } + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + // Save state if first event + if (!InitialStateSet) + { + InitialState = prevState; + InitialStateSet = true; + } + + ResumePlaying = true; + + var setQueueStatus = context.SetPlayQueue(request.PlayingQueue, request.PlayingItemPosition, request.StartPositionTicks); + if (!setQueueStatus) + { + _logger.LogError("HandleRequest: {0} in group {1}, unable to set playing queue.", request.GetRequestType(), context.GroupId.ToString()); + + // Ignore request and return to previous state + ISyncPlayState newState; + switch (prevState) + { + case GroupState.Playing: + newState = new PlayingGroupState(_logger); + break; + case GroupState.Paused: + newState = new PausedGroupState(_logger); + break; + default: + newState = new IdleGroupState(_logger); + break; + } + + context.SetState(newState); + return; + } + + var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NewPlaylist); + var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate); + context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken); + + // Reset status of sessions and await for all Ready events before sending Play command + context.SetAllBuffering(true); + + _logger.LogDebug("HandleRequest: {0} in group {1}, {2} set a new play queue.", request.GetRequestType(), context.GroupId.ToString(), session.Id.ToString()); + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SetPlaylistItemGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + // Save state if first event + if (!InitialStateSet) + { + InitialState = prevState; + InitialStateSet = true; + } + + ResumePlaying = true; + + var result = context.SetPlayingItem(request.PlaylistItemId); + if (result) + { + var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.SetCurrentItem); + var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate); + context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken); + + // Reset status of sessions and await for all Ready events before sending Play command + context.SetAllBuffering(true); + } + else + { + // Return to old state + ISyncPlayState newState; + switch (prevState) + { + case GroupState.Playing: + newState = new PlayingGroupState(_logger); + break; + case GroupState.Paused: + newState = new PausedGroupState(_logger); + break; + default: + newState = new IdleGroupState(_logger); + break; + } + + context.SetState(newState); + + _logger.LogDebug("HandleRequest: {0} in group {1}, unable to change current playing item.", request.GetRequestType(), context.GroupId.ToString()); + } + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, UnpauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + // Save state if first event + if (!InitialStateSet) + { + InitialState = prevState; + InitialStateSet = true; + } + + if (prevState.Equals(GroupState.Idle)) + { + ResumePlaying = true; + context.RestartCurrentItem(); + + var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NewPlaylist); + var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate); + context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken); + + // Reset status of sessions and await for all Ready events before sending Play command + context.SetAllBuffering(true); + + _logger.LogDebug("HandleRequest: {0} in group {1}, waiting for all ready events.", request.GetRequestType(), context.GroupId.ToString()); + } + else + { + if (ResumePlaying) + { + _logger.LogDebug("HandleRequest: {0} in group {1}, ignoring sessions that are not ready and forcing the playback to start.", request.GetRequestType(), context.GroupId.ToString()); + + // An Unpause request is forcing the playback to start, ignoring sessions that are not ready + context.SetAllBuffering(false); + + // Change state + var playingState = new PlayingGroupState(_logger); + playingState.IgnoreBuffering = true; + context.SetState(playingState); + playingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken); + } + else + { + // Group would have gone to paused state, now will go to playing state when ready + ResumePlaying = true; + + // Notify relevant state change event + SendGroupStateUpdate(context, request, session, cancellationToken); + } + } + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + // Save state if first event + if (!InitialStateSet) + { + InitialState = prevState; + InitialStateSet = true; + } + + // Wait for sessions to be ready, then switch to paused state + ResumePlaying = false; + + // Notify relevant state change event + SendGroupStateUpdate(context, request, session, cancellationToken); + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + // Save state if first event + if (!InitialStateSet) + { + InitialState = prevState; + InitialStateSet = true; + } + + // Change state + var idleState = new IdleGroupState(_logger); + context.SetState(idleState); + idleState.HandleRequest(context, GetGroupState(), request, session, cancellationToken); + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + // Save state if first event + if (!InitialStateSet) + { + InitialState = prevState; + InitialStateSet = true; + } + + if (prevState.Equals(GroupState.Playing)) + { + ResumePlaying = true; + } + else if(prevState.Equals(GroupState.Paused)) + { + ResumePlaying = false; + } + + // Sanitize PositionTicks + var ticks = context.SanitizePositionTicks(request.PositionTicks); + + // Seek + context.PositionTicks = ticks; + context.LastActivity = DateTime.UtcNow; + + var command = context.NewSyncPlayCommand(SendCommandType.Seek); + context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken); + + // Reset status of sessions and await for all Ready events before sending Play command + context.SetAllBuffering(true); + + // Notify relevant state change event + SendGroupStateUpdate(context, request, session, cancellationToken); + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + // Save state if first event + if (!InitialStateSet) + { + InitialState = prevState; + InitialStateSet = true; + } + + // Make sure the client is playing the correct item + if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId())) + { + _logger.LogDebug("HandleRequest: {0} in group {1}, {2} has wrong playlist item.", request.GetRequestType(), context.GroupId.ToString(), session.Id.ToString()); + + var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.SetCurrentItem); + var updateSession = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate); + context.SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken); + context.SetBuffering(session, true); + + return; + } + + if (prevState.Equals(GroupState.Playing)) + { + // Resume playback when all ready + ResumePlaying = true; + + context.SetBuffering(session, true); + + // Pause group and compute the media playback position + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - context.LastActivity; + context.LastActivity = currentTime; + context.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; + + // Send pause command to all non-buffering sessions + var command = context.NewSyncPlayCommand(SendCommandType.Pause); + context.SendCommand(session, SyncPlayBroadcastType.AllReady, command, cancellationToken); + } + else if (prevState.Equals(GroupState.Paused)) + { + // Don't resume playback when all ready + ResumePlaying = false; + + context.SetBuffering(session, true); + + // Send pause command to buffering session + var command = context.NewSyncPlayCommand(SendCommandType.Pause); + context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken); + } + else if (prevState.Equals(GroupState.Waiting)) + { + // Another session is now buffering + context.SetBuffering(session, true); + + if (!ResumePlaying) + { + // Force update for this session that should be paused + var command = context.NewSyncPlayCommand(SendCommandType.Pause); + context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken); + } + } + + // Notify relevant state change event + SendGroupStateUpdate(context, request, session, cancellationToken); + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + // Save state if first event + if (!InitialStateSet) + { + InitialState = prevState; + InitialStateSet = true; + } + + // Make sure the client is playing the correct item + if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId())) + { + _logger.LogDebug("HandleRequest: {0} in group {1}, {2} has wrong playlist item.", request.GetRequestType(), context.GroupId.ToString(), session.Id.ToString()); + + var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.SetCurrentItem); + var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate); + context.SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, update, cancellationToken); + context.SetBuffering(session, true); + + return; + } + + var requestTicks = context.SanitizePositionTicks(request.PositionTicks); + var currentTime = DateTime.UtcNow; + var elapsedTime = currentTime - request.When; + if (!request.IsPlaying) + { + elapsedTime = TimeSpan.Zero; + } + + var clientPosition = TimeSpan.FromTicks(requestTicks) + elapsedTime; + var delayTicks = context.PositionTicks - clientPosition.Ticks; + + if (delayTicks > TimeSpan.FromSeconds(5).Ticks) + { + // The client is really behind, other participants will have to wait a lot of time... + _logger.LogWarning("HandleRequest: {0} in group {1}, {2} got lost in time.", request.GetRequestType(), context.GroupId.ToString(), session.Id.ToString()); + } + + if (ResumePlaying) + { + // Handle case where session reported as ready but in reality + // it has no clue of the real position nor the playback state + if (!request.IsPlaying && Math.Abs(context.PositionTicks - requestTicks) > TimeSpan.FromSeconds(0.5).Ticks) { + // Session not ready at all + context.SetBuffering(session, true); + + // Correcting session's position + var command = context.NewSyncPlayCommand(SendCommandType.Seek); + context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken); + + // Notify relevant state change event + SendGroupStateUpdate(context, request, session, cancellationToken); + + _logger.LogDebug("HandleRequest: {0} in group {1}, {2} got lost in time, correcting.", request.GetRequestType(), context.GroupId.ToString(), session.Id.ToString()); + return; + } + + // Session is ready + context.SetBuffering(session, false); + + if (context.IsBuffering()) + { + // Others are still buffering, tell this client to pause when ready + var command = context.NewSyncPlayCommand(SendCommandType.Pause); + var pauseAtTime = currentTime.AddTicks(delayTicks); + command.When = context.DateToUTCString(pauseAtTime); + context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken); + + _logger.LogDebug("HandleRequest: {0} in group {1}, others still buffering, {2} will pause when ready.", request.GetRequestType(), context.GroupId.ToString(), session.Id.ToString()); + } + else + { + // If all ready, then start playback + // Let other clients resume as soon as the buffering client catches up + if (delayTicks > context.GetHighestPing() * 2 * TimeSpan.TicksPerMillisecond) + { + // Client that was buffering is recovering, notifying others to resume + context.LastActivity = currentTime.AddTicks(delayTicks); + var command = context.NewSyncPlayCommand(SendCommandType.Unpause); + var filter = SyncPlayBroadcastType.AllExceptCurrentSession; + if (!request.IsPlaying) + { + filter = SyncPlayBroadcastType.AllGroup; + } + + context.SendCommand(session, filter, command, cancellationToken); + + _logger.LogDebug("HandleRequest: {0} in group {1}, {2} is recovering, notifying others to resume.", request.GetRequestType(), context.GroupId.ToString(), session.Id.ToString()); + } + else + { + // Client, that was buffering, resumed playback but did not update others in time + delayTicks = context.GetHighestPing() * 2 * TimeSpan.TicksPerMillisecond; + delayTicks = delayTicks < context.DefaultPing ? context.DefaultPing : delayTicks; + + context.LastActivity = currentTime.AddTicks(delayTicks); + + var command = context.NewSyncPlayCommand(SendCommandType.Unpause); + context.SendCommand(session, SyncPlayBroadcastType.AllGroup, command, cancellationToken); + + _logger.LogDebug("HandleRequest: {0} in group {1}, {2} resumed playback but did not update others in time.", request.GetRequestType(), context.GroupId.ToString(), session.Id.ToString()); + } + + // Change state + var playingState = new PlayingGroupState(_logger); + context.SetState(playingState); + playingState.HandleRequest(context, GetGroupState(), request, session, cancellationToken); + } + } + else + { + // Check that session is really ready, tollerate half second difference to account for player imperfections + if (Math.Abs(context.PositionTicks - requestTicks) > TimeSpan.FromSeconds(0.5).Ticks) + { + // Session still not ready + context.SetBuffering(session, true); + + // Session is seeking to wrong position, correcting + var command = context.NewSyncPlayCommand(SendCommandType.Seek); + context.SendCommand(session, SyncPlayBroadcastType.CurrentSession, command, cancellationToken); + + // Notify relevant state change event + SendGroupStateUpdate(context, request, session, cancellationToken); + + _logger.LogDebug("HandleRequest: {0} in group {1}, {2} was seeking to wrong position, correcting.", request.GetRequestType(), context.GroupId.ToString(), session.Id.ToString()); + return; + } else { + // Session is ready + context.SetBuffering(session, false); + } + + if (!context.IsBuffering()) + { + // Group is ready, returning to previous state + var pausedState = new PausedGroupState(_logger); + context.SetState(pausedState); + + if (InitialState.Equals(GroupState.Playing)) + { + // Group went from playing to waiting state and a pause request occured while waiting + var pauserequest = new PauseGroupRequest(); + pausedState.HandleRequest(context, GetGroupState(), pauserequest, session, cancellationToken); + } + else if (InitialState.Equals(GroupState.Paused)) + { + pausedState.HandleRequest(context, GetGroupState(), request, session, cancellationToken); + } + + _logger.LogDebug("HandleRequest: {0} in group {1}, {2} is ready, returning to previous state.", request.GetRequestType(), context.GroupId.ToString(), session.Id.ToString()); + } + } + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + // Save state if first event + if (!InitialStateSet) + { + InitialState = prevState; + InitialStateSet = true; + } + + ResumePlaying = true; + + // Make sure the client knows the playing item, to avoid duplicate requests + if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId())) + { + _logger.LogDebug("HandleRequest: {0} in group {1}, client provided the wrong playlist id.", request.GetRequestType(), context.GroupId.ToString()); + return; + } + + var newItem = context.NextItemInQueue(); + if (newItem) + { + // Send playing-queue update + var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.NextTrack); + var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate); + context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken); + + // Reset status of sessions and await for all Ready events before sending Play command + context.SetAllBuffering(true); + } + else + { + // Return to old state + ISyncPlayState newState; + switch (prevState) + { + case GroupState.Playing: + newState = new PlayingGroupState(_logger); + break; + case GroupState.Paused: + newState = new PausedGroupState(_logger); + break; + default: + newState = new IdleGroupState(_logger); + break; + } + + context.SetState(newState); + + _logger.LogDebug("HandleRequest: {0} in group {1}, no next track available.", request.GetRequestType(), context.GroupId.ToString()); + } + } + + /// <inheritdoc /> + public override void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken) + { + // Save state if first event + if (!InitialStateSet) + { + InitialState = prevState; + InitialStateSet = true; + } + + ResumePlaying = true; + + // Make sure the client knows the playing item, to avoid duplicate requests + if (!request.PlaylistItemId.Equals(context.PlayQueue.GetPlayingItemPlaylistId())) + { + _logger.LogDebug("HandleRequest: {0} in group {1}, client provided the wrong playlist id.", request.GetRequestType(), context.GroupId.ToString()); + return; + } + + var newItem = context.PreviousItemInQueue(); + if (newItem) + { + // Send playing-queue update + var playQueueUpdate = context.GetPlayQueueUpdate(PlayQueueUpdateReason.PreviousTrack); + var update = context.NewSyncPlayGroupUpdate(GroupUpdateType.PlayQueue, playQueueUpdate); + context.SendGroupUpdate(session, SyncPlayBroadcastType.AllGroup, update, cancellationToken); + + // Reset status of sessions and await for all Ready events before sending Play command + context.SetAllBuffering(true); + } + else + { + // Return to old state + ISyncPlayState newState; + switch (prevState) + { + case GroupState.Playing: + newState = new PlayingGroupState(_logger); + break; + case GroupState.Paused: + newState = new PausedGroupState(_logger); + break; + default: + newState = new IdleGroupState(_logger); + break; + } + + context.SetState(newState); + + _logger.LogDebug("HandleRequest: {0} in group {1}, no previous track available.", request.GetRequestType(), context.GroupId.ToString()); + } + } + } +} diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs deleted file mode 100644 index 225be7430d..0000000000 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs +++ /dev/null @@ -1,282 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Session; -using MediaBrowser.Controller.SyncPlay; -using MediaBrowser.Model.Session; -using MediaBrowser.Model.SyncPlay; -using Microsoft.Extensions.Logging; - -namespace Emby.Server.Implementations.SyncPlay -{ - /// <summary> - /// Class SyncPlayController. - /// </summary> - /// <remarks> - /// Class is not thread-safe, external locking is required when accessing methods. - /// </remarks> - public class SyncPlayController : ISyncPlayController, ISyncPlayStateContext - { - /// <summary> - /// The session manager. - /// </summary> - private readonly ISessionManager _sessionManager; - - /// <summary> - /// The SyncPlay manager. - /// </summary> - private readonly ISyncPlayManager _syncPlayManager; - - /// <summary> - /// The logger. - /// </summary> - private readonly ILogger _logger; - - /// <summary> - /// The group to manage. - /// </summary> - private readonly GroupInfo _group = new GroupInfo(); - - /// <summary> - /// Internal group state. - /// </summary> - /// <value>The group's state.</value> - private ISyncPlayState State = new PausedGroupState(); - - /// <inheritdoc /> - public GroupInfo GetGroup() - { - return _group; - } - - /// <inheritdoc /> - public void SetState(ISyncPlayState state) - { - _logger.LogInformation("SetState: {0} -> {1}.", State.GetGroupState(), state.GetGroupState()); - this.State = state; - } - - /// <inheritdoc /> - public Guid GetGroupId() => _group.GroupId; - - /// <inheritdoc /> - public Guid GetPlayingItemId() => _group.PlayingItem.Id; - - /// <inheritdoc /> - public bool IsGroupEmpty() => _group.IsEmpty(); - - /// <summary> - /// Initializes a new instance of the <see cref="SyncPlayController" /> class. - /// </summary> - /// <param name="sessionManager">The session manager.</param> - /// <param name="syncPlayManager">The SyncPlay manager.</param> - public SyncPlayController( - ISessionManager sessionManager, - ISyncPlayManager syncPlayManager, - ILogger logger) - { - _sessionManager = sessionManager; - _syncPlayManager = syncPlayManager; - _logger = logger; - } - - /// <summary> - /// Filters sessions of this group. - /// </summary> - /// <param name="from">The current session.</param> - /// <param name="type">The filtering type.</param> - /// <value>The array of sessions matching the filter.</value> - private SessionInfo[] FilterSessions(SessionInfo from, SyncPlayBroadcastType type) - { - switch (type) - { - case SyncPlayBroadcastType.CurrentSession: - return new SessionInfo[] { from }; - case SyncPlayBroadcastType.AllGroup: - return _group.Participants.Values.Select( - session => session.Session).ToArray(); - case SyncPlayBroadcastType.AllExceptCurrentSession: - return _group.Participants.Values.Select( - session => session.Session).Where( - session => !session.Id.Equals(from.Id)).ToArray(); - case SyncPlayBroadcastType.AllReady: - return _group.Participants.Values.Where( - session => !session.IsBuffering).Select( - session => session.Session).ToArray(); - default: - return Array.Empty<SessionInfo>(); - } - } - - /// <inheritdoc /> - public Task SendGroupUpdate<T>(SessionInfo from, SyncPlayBroadcastType type, GroupUpdate<T> message, CancellationToken cancellationToken) - { - IEnumerable<Task> GetTasks() - { - foreach (var session in FilterSessions(from, type)) - { - yield return _sessionManager.SendSyncPlayGroupUpdate(session.Id, message, cancellationToken); - } - } - - return Task.WhenAll(GetTasks()); - } - - /// <inheritdoc /> - public Task SendCommand(SessionInfo from, SyncPlayBroadcastType type, SendCommand message, CancellationToken cancellationToken) - { - IEnumerable<Task> GetTasks() - { - foreach (var session in FilterSessions(from, type)) - { - yield return _sessionManager.SendSyncPlayCommand(session.Id, message, cancellationToken); - } - } - - return Task.WhenAll(GetTasks()); - } - - /// <inheritdoc /> - public SendCommand NewSyncPlayCommand(SendCommandType type) - { - return new SendCommand() - { - GroupId = _group.GroupId.ToString(), - Command = type, - PositionTicks = _group.PositionTicks, - When = DateToUTCString(_group.LastActivity), - EmittedAt = DateToUTCString(DateTime.UtcNow) - }; - } - - /// <inheritdoc /> - public GroupUpdate<T> NewSyncPlayGroupUpdate<T>(GroupUpdateType type, T data) - { - return new GroupUpdate<T>() - { - GroupId = _group.GroupId.ToString(), - Type = type, - Data = data - }; - } - - /// <inheritdoc /> - public string DateToUTCString(DateTime _date) - { - return _date.ToUniversalTime().ToString("o"); - } - - /// <inheritdoc /> - public long SanitizePositionTicks(long? positionTicks) - { - var ticks = positionTicks ?? 0; - ticks = ticks >= 0 ? ticks : 0; - if (_group.PlayingItem != null) - { - var runTimeTicks = _group.PlayingItem.RunTimeTicks ?? 0; - ticks = ticks > runTimeTicks ? runTimeTicks : ticks; - } - - return ticks; - } - - /// <inheritdoc /> - public void CreateGroup(SessionInfo session, CancellationToken cancellationToken) - { - _group.AddSession(session); - _syncPlayManager.AddSessionToGroup(session, this); - - State = new PausedGroupState(); - - _group.PlayingItem = session.FullNowPlayingItem; - // TODO: looks like new groups should mantain playstate (and not force to pause) - // _group.IsPaused = session.PlayState.IsPaused; - _group.PositionTicks = session.PlayState.PositionTicks ?? 0; - _group.LastActivity = DateTime.UtcNow; - - var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow)); - SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken); - // TODO: looks like new groups should mantain playstate (and not force to pause) - var pauseCommand = NewSyncPlayCommand(SendCommandType.Pause); - SendCommand(session, SyncPlayBroadcastType.CurrentSession, pauseCommand, cancellationToken); - } - - /// <inheritdoc /> - public void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken) - { - if (session.NowPlayingItem?.Id == _group.PlayingItem.Id) - { - _group.AddSession(session); - _syncPlayManager.AddSessionToGroup(session, this); - - var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow)); - SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken); - - var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserJoined, session.UserName); - SendGroupUpdate(session, SyncPlayBroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); - - // Syncing will happen client-side - if (State.GetGroupState().Equals(GroupState.Playing)) - { - var playCommand = NewSyncPlayCommand(SendCommandType.Play); - SendCommand(session, SyncPlayBroadcastType.CurrentSession, playCommand, cancellationToken); - } - else - { - var pauseCommand = NewSyncPlayCommand(SendCommandType.Pause); - SendCommand(session, SyncPlayBroadcastType.CurrentSession, pauseCommand, cancellationToken); - } - } - else - { - var playRequest = new PlayRequest - { - ItemIds = new Guid[] { _group.PlayingItem.Id }, - StartPositionTicks = _group.PositionTicks - }; - var update = NewSyncPlayGroupUpdate(GroupUpdateType.PrepareSession, playRequest); - SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, update, cancellationToken); - } - } - - /// <inheritdoc /> - public void SessionLeave(SessionInfo session, CancellationToken cancellationToken) - { - _group.RemoveSession(session); - _syncPlayManager.RemoveSessionFromGroup(session, this); - - var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupLeft, _group.PositionTicks); - SendGroupUpdate(session, SyncPlayBroadcastType.CurrentSession, updateSession, cancellationToken); - - var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserLeft, session.UserName); - SendGroupUpdate(session, SyncPlayBroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken); - } - - /// <inheritdoc /> - public void HandleRequest(SessionInfo session, IPlaybackGroupRequest request, CancellationToken cancellationToken) - { - // The server's job is to maintain a consistent state for clients to reference - // and notify clients of state changes. The actual syncing of media playback - // happens client side. Clients are aware of the server's time and use it to sync. - _logger.LogInformation("HandleRequest: {0}:{1}.", request.GetType(), State.GetGroupState()); - _ = request.Apply(this, State, session, cancellationToken); - // TODO: do something with returned value - } - - /// <inheritdoc /> - public GroupInfoDto GetInfo() - { - return new GroupInfoDto() - { - GroupId = GetGroupId().ToString(), - PlayingItemName = _group.PlayingItem.Name, - PlayingItemId = _group.PlayingItem.Id.ToString(), - PositionTicks = _group.PositionTicks, - Participants = _group.Participants.Values.Select(session => session.Session.UserName).Distinct().ToList() - }; - } - } -} diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index b85f3c1496..a8e30a9eca 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Threading; -using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Session; @@ -41,14 +39,14 @@ namespace Emby.Server.Implementations.SyncPlay /// <summary> /// The map between sessions and groups. /// </summary> - private readonly Dictionary<string, ISyncPlayController> _sessionToGroupMap = - new Dictionary<string, ISyncPlayController>(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary<string, ISyncPlayGroupController> _sessionToGroupMap = + new Dictionary<string, ISyncPlayGroupController>(StringComparer.OrdinalIgnoreCase); /// <summary> /// The groups. /// </summary> - private readonly Dictionary<Guid, ISyncPlayController> _groups = - new Dictionary<Guid, ISyncPlayController>(); + private readonly Dictionary<Guid, ISyncPlayGroupController> _groups = + new Dictionary<Guid, ISyncPlayGroupController>(); /// <summary> /// Lock used for accesing any group. @@ -75,7 +73,9 @@ namespace Emby.Server.Implementations.SyncPlay _sessionManager = sessionManager; _libraryManager = libraryManager; + _sessionManager.SessionStarted += OnSessionManagerSessionStarted; _sessionManager.SessionEnded += OnSessionManagerSessionEnded; + _sessionManager.PlaybackStart += OnSessionManagerPlaybackStart; _sessionManager.PlaybackStopped += OnSessionManagerPlaybackStopped; } @@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.SyncPlay /// Gets all groups. /// </summary> /// <value>All groups.</value> - public IEnumerable<ISyncPlayController> Groups => _groups.Values; + public IEnumerable<ISyncPlayGroupController> Groups => _groups.Values; /// <inheritdoc /> public void Dispose() @@ -103,13 +103,15 @@ namespace Emby.Server.Implementations.SyncPlay return; } + _sessionManager.SessionStarted -= OnSessionManagerSessionStarted; _sessionManager.SessionEnded -= OnSessionManagerSessionEnded; + _sessionManager.PlaybackStart -= OnSessionManagerPlaybackStart; _sessionManager.PlaybackStopped -= OnSessionManagerPlaybackStopped; _disposed = true; } - private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e) + private void OnSessionManagerSessionStarted(object sender, SessionEventArgs e) { var session = e.SessionInfo; if (!IsSessionInGroup(session)) @@ -117,52 +119,60 @@ namespace Emby.Server.Implementations.SyncPlay return; } - LeaveGroup(session, CancellationToken.None); + var groupId = GetSessionGroup(session) ?? Guid.Empty; + var request = new JoinGroupRequest() + { + GroupId = groupId + }; + JoinGroup(session, groupId, request, CancellationToken.None); } - private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e) + private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e) { - var session = e.Session; + var session = e.SessionInfo; if (!IsSessionInGroup(session)) { return; } - LeaveGroup(session, CancellationToken.None); + // TODO: probably remove this event, not used at the moment } - private bool IsSessionInGroup(SessionInfo session) + private void OnSessionManagerPlaybackStart(object sender, PlaybackProgressEventArgs e) { - return _sessionToGroupMap.ContainsKey(session.Id); + var session = e.Session; + if (!IsSessionInGroup(session)) + { + return; + } + + // TODO: probably remove this event, not used at the moment } - private bool HasAccessToItem(User user, Guid itemId) + private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e) { - var item = _libraryManager.GetItemById(itemId); - - // Check ParentalRating access - var hasParentalRatingAccess = !user.MaxParentalAgeRating.HasValue - || item.InheritedParentalRatingValue <= user.MaxParentalAgeRating; - - if (!user.HasPermission(PermissionKind.EnableAllFolders) && hasParentalRatingAccess) + var session = e.Session; + if (!IsSessionInGroup(session)) { - var collections = _libraryManager.GetCollectionFolders(item).Select( - folder => folder.Id.ToString("N", CultureInfo.InvariantCulture)); - - return collections.Intersect(user.GetPreference(PreferenceKind.EnabledFolders)).Any(); + return; } - return hasParentalRatingAccess; + // TODO: probably remove this event, not used at the moment + } + + private bool IsSessionInGroup(SessionInfo session) + { + return _sessionToGroupMap.ContainsKey(session.Id); } private Guid? GetSessionGroup(SessionInfo session) { _sessionToGroupMap.TryGetValue(session.Id, out var group); - return group?.GetGroupId(); + return group?.GroupId; } /// <inheritdoc /> - public void NewGroup(SessionInfo session, CancellationToken cancellationToken) + public void NewGroup(SessionInfo session, NewGroupRequest request, CancellationToken cancellationToken) { var user = _userManager.GetUserById(session.UserId); @@ -174,8 +184,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.CreateGroupDenied }; - - _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); return; } @@ -186,10 +195,10 @@ namespace Emby.Server.Implementations.SyncPlay LeaveGroup(session, cancellationToken); } - var group = new SyncPlayController(_sessionManager, this, _logger); - _groups[group.GetGroupId()] = group; + var group = new SyncPlayGroupController(_logger, _userManager, _sessionManager, _libraryManager, this); + _groups[group.GroupId] = group; - group.CreateGroup(session, cancellationToken); + group.CreateGroup(session, request, cancellationToken); } } @@ -206,14 +215,13 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.JoinGroupDenied }; - - _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); return; } lock (_groupsLock) { - ISyncPlayController group; + ISyncPlayGroupController group; _groups.TryGetValue(groupId, out group); if (group == null) @@ -224,20 +232,20 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.GroupDoesNotExist }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); return; } - if (!HasAccessToItem(user, group.GetPlayingItemId())) + if (!group.HasAccessToPlayQueue(user)) { - _logger.LogWarning("JoinGroup: {0} does not have access to {1}.", session.Id, group.GetPlayingItemId()); + _logger.LogWarning("JoinGroup: {0} does not have access to some content from the playing queue of group {1}.", session.Id, group.GroupId.ToString()); var error = new GroupUpdate<string>() { - GroupId = group.GetGroupId().ToString(), + GroupId = group.GroupId.ToString(), Type = GroupUpdateType.LibraryAccessDenied }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); return; } @@ -245,6 +253,7 @@ namespace Emby.Server.Implementations.SyncPlay { if (GetSessionGroup(session).Equals(groupId)) { + group.SessionRestore(session, request, cancellationToken); return; } @@ -271,7 +280,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.NotInGroup }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); return; } @@ -279,14 +288,14 @@ namespace Emby.Server.Implementations.SyncPlay if (group.IsGroupEmpty()) { - _logger.LogInformation("LeaveGroup: removing empty group {0}.", group.GetGroupId()); - _groups.Remove(group.GetGroupId(), out _); + _logger.LogInformation("LeaveGroup: removing empty group {0}.", group.GroupId); + _groups.Remove(group.GroupId, out _); } } } /// <inheritdoc /> - public List<GroupInfoDto> ListGroups(SessionInfo session, Guid filterItemId) + public List<GroupInfoDto> ListGroups(SessionInfo session) { var user = _userManager.GetUserById(session.UserId); @@ -295,20 +304,9 @@ namespace Emby.Server.Implementations.SyncPlay return new List<GroupInfoDto>(); } - // Filter by item if requested - if (!filterItemId.Equals(Guid.Empty)) - { - return _groups.Values.Where( - group => group.GetPlayingItemId().Equals(filterItemId) && HasAccessToItem(user, group.GetPlayingItemId())).Select( - group => group.GetInfo()).ToList(); - } - else - { - // Otherwise show all available groups - return _groups.Values.Where( - group => HasAccessToItem(user, group.GetPlayingItemId())).Select( - group => group.GetInfo()).ToList(); - } + return _groups.Values.Where( + group => group.HasAccessToPlayQueue(user)).Select( + group => group.GetInfo()).ToList(); } /// <inheritdoc /> @@ -324,8 +322,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.JoinGroupDenied }; - - _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); return; } @@ -341,7 +338,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.NotInGroup }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); return; } @@ -350,7 +347,7 @@ namespace Emby.Server.Implementations.SyncPlay } /// <inheritdoc /> - public void AddSessionToGroup(SessionInfo session, ISyncPlayController group) + public void AddSessionToGroup(SessionInfo session, ISyncPlayGroupController group) { if (IsSessionInGroup(session)) { @@ -361,7 +358,7 @@ namespace Emby.Server.Implementations.SyncPlay } /// <inheritdoc /> - public void RemoveSessionFromGroup(SessionInfo session, ISyncPlayController group) + public void RemoveSessionFromGroup(SessionInfo session, ISyncPlayGroupController group) { if (!IsSessionInGroup(session)) { @@ -369,7 +366,7 @@ namespace Emby.Server.Implementations.SyncPlay } _sessionToGroupMap.Remove(session.Id, out var tempGroup); - if (!tempGroup.GetGroupId().Equals(group.GetGroupId())) + if (!tempGroup.GroupId.Equals(group.GroupId)) { throw new InvalidOperationException("Session was in wrong group!"); } diff --git a/Jellyfin.Api/Controllers/SyncPlayController.cs b/Jellyfin.Api/Controllers/SyncPlayController.cs index e16a10ba4d..847c3ab117 100644 --- a/Jellyfin.Api/Controllers/SyncPlayController.cs +++ b/Jellyfin.Api/Controllers/SyncPlayController.cs @@ -43,14 +43,20 @@ namespace Jellyfin.Api.Controllers /// <summary> /// Create a new SyncPlay group. /// </summary> + /// <param name="groupName">The name of the new group.</param> /// <response code="204">New group created.</response> /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> [HttpPost("New")] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult SyncPlayCreateGroup() + public ActionResult SyncPlayCreateGroup( + [FromQuery, Required] string groupName) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); - _syncPlayManager.NewGroup(currentSession, CancellationToken.None); + var newGroupRequest = new NewGroupRequest() + { + GroupName = groupName + }; + _syncPlayManager.NewGroup(currentSession, newGroupRequest, CancellationToken.None); return NoContent(); } @@ -62,15 +68,14 @@ namespace Jellyfin.Api.Controllers /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> [HttpPost("Join")] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult SyncPlayJoinGroup([FromQuery, Required] Guid groupId) + public ActionResult SyncPlayJoinGroup( + [FromQuery, Required] Guid groupId) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); - var joinRequest = new JoinGroupRequest() { GroupId = groupId }; - _syncPlayManager.JoinGroup(currentSession, groupId, joinRequest, CancellationToken.None); return NoContent(); } @@ -92,35 +97,143 @@ namespace Jellyfin.Api.Controllers /// <summary> /// Gets all SyncPlay groups. /// </summary> - /// <param name="filterItemId">Optional. Filter by item id.</param> /// <response code="200">Groups returned.</response> /// <returns>An <see cref="IEnumerable{GroupInfoView}"/> containing the available SyncPlay groups.</returns> [HttpGet("List")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<IEnumerable<GroupInfoView>> SyncPlayGetGroups([FromQuery] Guid? filterItemId) + public ActionResult<IEnumerable<GroupInfoDto>> SyncPlayGetGroups() { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); - return Ok(_syncPlayManager.ListGroups(currentSession, filterItemId.HasValue ? filterItemId.Value : Guid.Empty)); + return Ok(_syncPlayManager.ListGroups(currentSession)); } /// <summary> /// Request play in SyncPlay group. /// </summary> + /// <param name="playingQueue">The playing queue. Item ids in the playing queue, comma delimited.</param> + /// <param name="playingItemPosition">The playing item position from the queue.</param> + /// <param name="startPositionTicks">The start position ticks.</param> /// <response code="204">Play request sent to all group members.</response> /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> [HttpPost("Play")] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult SyncPlayPlay() + public ActionResult SyncPlayPlay( + [FromQuery, Required] string playingQueue, + [FromQuery, Required] int playingItemPosition, + [FromQuery, Required] long startPositionTicks) + { + var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var syncPlayRequest = new PlayGroupRequest() + { + PlayingQueue = RequestHelpers.GetGuids(playingQueue), + PlayingItemPosition = playingItemPosition, + StartPositionTicks = startPositionTicks + }; + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Request to change playlist item in SyncPlay group. + /// </summary> + /// <param name="playlistItemId">The playlist id of the item.</param> + /// <response code="204">Queue update request sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("SetPlaylistItem")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult SyncPlaySetPlaylistItem( + [FromQuery, Required] string playlistItemId) + { + var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var syncPlayRequest = new SetPlaylistItemGroupRequest() + { + PlaylistItemId = playlistItemId + }; + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Request to remove items from the playlist in SyncPlay group. + /// </summary> + /// <param name="playlistItemIds">The playlist ids of the items to remove.</param> + /// <response code="204">Queue update request sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("RemoveFromPlaylist")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult SyncPlayRemoveFromPlaylist( + [FromQuery, Required] string[] playlistItemIds) + { + var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var syncPlayRequest = new RemoveFromPlaylistGroupRequest() + { + PlaylistItemIds = playlistItemIds + }; + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Request to move an item in the playlist in SyncPlay group. + /// </summary> + /// <param name="playlistItemId">The playlist id of the item to move.</param> + /// <param name="newIndex">The new position.</param> + /// <response code="204">Queue update request sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("MovePlaylistItem")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult SyncPlayMovePlaylistItem( + [FromQuery, Required] string playlistItemId, + [FromQuery, Required] int newIndex) + { + var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var syncPlayRequest = new MovePlaylistItemGroupRequest() + { + PlaylistItemId = playlistItemId, + NewIndex = newIndex + }; + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Request to queue items to the playlist of a SyncPlay group. + /// </summary> + /// <param name="itemIds">The items to add. Item ids, comma delimited.</param> + /// <param name="mode">The mode in which to queue items.</param> + /// <response code="204">Queue update request sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("Queue")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult SyncPlayQueue( + [FromQuery, Required] string itemIds, + [FromQuery, Required] string mode) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); - var syncPlayRequest = new PlaybackRequest() + var syncPlayRequest = new QueueGroupRequest() { - Type = PlaybackRequestType.Play + ItemIds = RequestHelpers.GetGuids(itemIds), + Mode = mode }; _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } + /// <summary> + /// Request unpause in SyncPlay group. + /// </summary> + /// <response code="204">Unpause request sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("Unpause")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult SyncPlayUnpause() + { + var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var syncPlayRequest = new UnpauseGroupRequest(); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + /// <summary> /// Request pause in SyncPlay group. /// </summary> @@ -131,10 +244,22 @@ namespace Jellyfin.Api.Controllers public ActionResult SyncPlayPause() { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); - var syncPlayRequest = new PlaybackRequest() - { - Type = PlaybackRequestType.Pause - }; + var syncPlayRequest = new PauseGroupRequest(); + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Request stop in SyncPlay group. + /// </summary> + /// <response code="204">Stop request sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("Stop")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult SyncPlayStop() + { + var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var syncPlayRequest = new StopGroupRequest(); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); } @@ -147,12 +272,12 @@ namespace Jellyfin.Api.Controllers /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> [HttpPost("Seek")] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult SyncPlaySeek([FromQuery] long positionTicks) + public ActionResult SyncPlaySeek( + [FromQuery, Required] long positionTicks) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); - var syncPlayRequest = new PlaybackRequest() + var syncPlayRequest = new SeekGroupRequest() { - Type = PlaybackRequestType.Seek, PositionTicks = positionTicks }; _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); @@ -164,19 +289,142 @@ namespace Jellyfin.Api.Controllers /// </summary> /// <param name="when">When the request has been made by the client.</param> /// <param name="positionTicks">The playback position in ticks.</param> + /// <param name="isPlaying">Whether the client's playback is playing or not.</param> + /// <param name="playlistItemId">The playlist item id.</param> /// <param name="bufferingDone">Whether the buffering is done.</param> /// <response code="204">Buffering request sent to all group members.</response> /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> [HttpPost("Buffering")] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult SyncPlayBuffering([FromQuery] DateTime when, [FromQuery] long positionTicks, [FromQuery] bool bufferingDone) + public ActionResult SyncPlayBuffering( + [FromQuery, Required] DateTime when, + [FromQuery, Required] long positionTicks, + [FromQuery, Required] bool isPlaying, + [FromQuery, Required] string playlistItemId, + [FromQuery, Required] bool bufferingDone) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); - var syncPlayRequest = new PlaybackRequest() + IPlaybackGroupRequest syncPlayRequest; + if (!bufferingDone) { - Type = bufferingDone ? PlaybackRequestType.Ready : PlaybackRequestType.Buffer, - When = when, - PositionTicks = positionTicks + syncPlayRequest = new BufferGroupRequest() + { + When = when, + PositionTicks = positionTicks, + IsPlaying = isPlaying, + PlaylistItemId = playlistItemId + }; + } + else + { + syncPlayRequest = new ReadyGroupRequest() + { + When = when, + PositionTicks = positionTicks, + IsPlaying = isPlaying, + PlaylistItemId = playlistItemId + }; + } + + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Request SyncPlay group to ignore member during group-wait. + /// </summary> + /// <param name="ignoreWait">Whether to ignore the member.</param> + /// <response code="204">Member state updated.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("SetIgnoreWait")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult SyncPlaySetIgnoreWait( + [FromQuery, Required] bool ignoreWait) + { + var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var syncPlayRequest = new IgnoreWaitGroupRequest() + { + IgnoreWait = ignoreWait + }; + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Request next track in SyncPlay group. + /// </summary> + /// <param name="playlistItemId">The playing item id.</param> + /// <response code="204">Next track request sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("NextTrack")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult SyncPlayNextTrack( + [FromQuery, Required] string playlistItemId) + { + var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var syncPlayRequest = new NextTrackGroupRequest() + { + PlaylistItemId = playlistItemId + }; + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Request previous track in SyncPlay group. + /// </summary> + /// <param name="playlistItemId">The playing item id.</param> + /// <response code="204">Previous track request sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("PreviousTrack")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult SyncPlayPreviousTrack( + [FromQuery, Required] string playlistItemId) + { + var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var syncPlayRequest = new PreviousTrackGroupRequest() + { + PlaylistItemId = playlistItemId + }; + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Request to set repeat mode in SyncPlay group. + /// </summary> + /// <param name="mode">The repeat mode.</param> + /// <response code="204">Play queue update sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("SetRepeatMode")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult SyncPlaySetRepeatMode( + [FromQuery, Required] string mode) + { + var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var syncPlayRequest = new SetRepeatModeGroupRequest() + { + Mode = mode + }; + _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); + return NoContent(); + } + + /// <summary> + /// Request to set shuffle mode in SyncPlay group. + /// </summary> + /// <param name="mode">The shuffle mode.</param> + /// <response code="204">Play queue update sent to all group members.</response> + /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> + [HttpPost("SetShuffleMode")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult SyncPlaySetShuffleMode( + [FromQuery, Required] string mode) + { + var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var syncPlayRequest = new SetShuffleModeGroupRequest() + { + Mode = mode }; _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -190,12 +438,12 @@ namespace Jellyfin.Api.Controllers /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> [HttpPost("Ping")] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult SyncPlayPing([FromQuery] double ping) + public ActionResult SyncPlayPing( + [FromQuery, Required] double ping) { var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); - var syncPlayRequest = new PlaybackRequest() + var syncPlayRequest = new PingGroupRequest() { - Type = PlaybackRequestType.Ping, Ping = Convert.ToInt64(ping) }; _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); diff --git a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs deleted file mode 100644 index bd9670f07a..0000000000 --- a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs +++ /dev/null @@ -1,267 +0,0 @@ -using System.Threading; -using System; -using System.Collections.Generic; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Session; -using MediaBrowser.Controller.SyncPlay; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.SyncPlay; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.SyncPlay -{ - [Route("/SyncPlay/New", "POST", Summary = "Create a new SyncPlay group")] - [Authenticated] - public class SyncPlayNew : IReturnVoid - { - } - - [Route("/SyncPlay/Join", "POST", Summary = "Join an existing SyncPlay group")] - [Authenticated] - public class SyncPlayJoin : IReturnVoid - { - /// <summary> - /// Gets or sets the Group id. - /// </summary> - /// <value>The Group id to join.</value> - [ApiMember(Name = "GroupId", Description = "Group Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string GroupId { get; set; } - } - - [Route("/SyncPlay/Leave", "POST", Summary = "Leave joined SyncPlay group")] - [Authenticated] - public class SyncPlayLeave : IReturnVoid - { - } - - [Route("/SyncPlay/List", "GET", Summary = "List SyncPlay groups")] - [Authenticated] - public class SyncPlayList : IReturnVoid - { - /// <summary> - /// Gets or sets the filter item id. - /// </summary> - /// <value>The filter item id.</value> - [ApiMember(Name = "FilterItemId", Description = "Filter by item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string FilterItemId { get; set; } - } - - [Route("/SyncPlay/Play", "POST", Summary = "Request play in SyncPlay group")] - [Authenticated] - public class SyncPlayPlay : IReturnVoid - { - } - - [Route("/SyncPlay/Pause", "POST", Summary = "Request pause in SyncPlay group")] - [Authenticated] - public class SyncPlayPause : IReturnVoid - { - } - - [Route("/SyncPlay/Seek", "POST", Summary = "Request seek in SyncPlay group")] - [Authenticated] - public class SyncPlaySeek : IReturnVoid - { - [ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")] - public long PositionTicks { get; set; } - } - - [Route("/SyncPlay/Buffering", "POST", Summary = "Request group wait in SyncPlay group while buffering")] - [Authenticated] - public class SyncPlayBuffering : IReturnVoid - { - /// <summary> - /// Gets or sets the date used to pin PositionTicks in time. - /// </summary> - /// <value>The date related to PositionTicks.</value> - [ApiMember(Name = "When", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string When { get; set; } - - [ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")] - public long PositionTicks { get; set; } - - /// <summary> - /// Gets or sets whether this is a buffering or a ready request. - /// </summary> - /// <value><c>true</c> if buffering is complete; <c>false</c> otherwise.</value> - [ApiMember(Name = "BufferingDone", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool BufferingDone { get; set; } - } - - [Route("/SyncPlay/Ping", "POST", Summary = "Update session ping")] - [Authenticated] - public class SyncPlayPing : IReturnVoid - { - [ApiMember(Name = "Ping", IsRequired = true, DataType = "double", ParameterType = "query", Verb = "POST")] - public double Ping { get; set; } - } - - /// <summary> - /// Class SyncPlayService. - /// </summary> - public class SyncPlayService : BaseApiService - { - /// <summary> - /// The session context. - /// </summary> - private readonly ISessionContext _sessionContext; - - /// <summary> - /// The SyncPlay manager. - /// </summary> - private readonly ISyncPlayManager _syncPlayManager; - - public SyncPlayService( - ILogger<SyncPlayService> logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - ISessionContext sessionContext, - ISyncPlayManager syncPlayManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _sessionContext = sessionContext; - _syncPlayManager = syncPlayManager; - } - - /// <summary> - /// Handles the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(SyncPlayNew request) - { - var currentSession = GetSession(_sessionContext); - _syncPlayManager.NewGroup(currentSession, CancellationToken.None); - } - - /// <summary> - /// Handles the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(SyncPlayJoin request) - { - var currentSession = GetSession(_sessionContext); - - Guid groupId; - if (!Guid.TryParse(request.GroupId, out groupId)) - { - Logger.LogError("JoinGroup: {0} is not a valid format for GroupId. Ignoring request.", request.GroupId); - return; - } - - var joinRequest = new JoinGroupRequest() - { - GroupId = groupId - }; - - _syncPlayManager.JoinGroup(currentSession, groupId, joinRequest, CancellationToken.None); - } - - /// <summary> - /// Handles the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(SyncPlayLeave request) - { - var currentSession = GetSession(_sessionContext); - _syncPlayManager.LeaveGroup(currentSession, CancellationToken.None); - } - - /// <summary> - /// Handles the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <value>The requested list of groups.</value> - public List<GroupInfoDto> Get(SyncPlayList request) - { - var currentSession = GetSession(_sessionContext); - var filterItemId = Guid.Empty; - - if (!string.IsNullOrEmpty(request.FilterItemId) && !Guid.TryParse(request.FilterItemId, out filterItemId)) - { - Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); - } - - return _syncPlayManager.ListGroups(currentSession, filterItemId); - } - - /// <summary> - /// Handles the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(SyncPlayPlay request) - { - var currentSession = GetSession(_sessionContext); - var syncPlayRequest = new PlayGroupRequest(); - _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); - } - - /// <summary> - /// Handles the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(SyncPlayPause request) - { - var currentSession = GetSession(_sessionContext); - var syncPlayRequest = new PauseGroupRequest(); - _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); - } - - /// <summary> - /// Handles the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(SyncPlaySeek request) - { - var currentSession = GetSession(_sessionContext); - var syncPlayRequest = new SeekGroupRequest() - { - PositionTicks = request.PositionTicks - }; - _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); - } - - /// <summary> - /// Handles the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(SyncPlayBuffering request) - { - var currentSession = GetSession(_sessionContext); - - IPlaybackGroupRequest syncPlayRequest; - if (!request.BufferingDone) - { - syncPlayRequest = new BufferGroupRequest() - { - When = DateTime.Parse(request.When), - PositionTicks = request.PositionTicks - }; - } - else - { - syncPlayRequest = new ReadyGroupRequest() - { - When = DateTime.Parse(request.When), - PositionTicks = request.PositionTicks - }; - } - - _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); - } - - /// <summary> - /// Handles the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Post(SyncPlayPing request) - { - var currentSession = GetSession(_sessionContext); - var syncPlayRequest = new PingGroupRequest() - { - Ping = Convert.ToInt64(request.Ping) - }; - _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); - } - } -} diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 04c3004ee6..9ad8557ce6 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -143,22 +143,22 @@ namespace MediaBrowser.Controller.Session Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken); /// <summary> - /// Sends the SyncPlayCommand. + /// Sends a SyncPlayCommand to a session. /// </summary> - /// <param name="sessionId">The session id.</param> + /// <param name="session">The session.</param> /// <param name="command">The command.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task SendSyncPlayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken); + Task SendSyncPlayCommand(SessionInfo session, SendCommand command, CancellationToken cancellationToken); /// <summary> - /// Sends the SyncPlayGroupUpdate. + /// Sends a SyncPlayGroupUpdate to a session. /// </summary> - /// <param name="sessionId">The session id.</param> + /// <param name="session">The session.</param> /// <param name="command">The group update.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task SendSyncPlayGroupUpdate<T>(string sessionId, GroupUpdate<T> command, CancellationToken cancellationToken); + Task SendSyncPlayGroupUpdate<T>(SessionInfo session, GroupUpdate<T> command, CancellationToken cancellationToken); /// <summary> /// Sends the browse command. diff --git a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs deleted file mode 100644 index cdd24d0b59..0000000000 --- a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs +++ /dev/null @@ -1,154 +0,0 @@ -using System; -using System.Collections.Generic; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Session; - -namespace MediaBrowser.Controller.SyncPlay -{ - /// <summary> - /// Class GroupInfo. - /// </summary> - /// <remarks> - /// Class is not thread-safe, external locking is required when accessing methods. - /// </remarks> - public class GroupInfo - { - /// <summary> - /// The default ping value used for sessions. - /// </summary> - public const long DefaultPing = 500; - - /// <summary> - /// Gets the group identifier. - /// </summary> - /// <value>The group identifier.</value> - public Guid GroupId { get; } = Guid.NewGuid(); - - /// <summary> - /// Gets or sets the playing item. - /// </summary> - /// <value>The playing item.</value> - public BaseItem PlayingItem { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether there are position ticks. - /// </summary> - /// <value>The position ticks.</value> - public long PositionTicks { get; set; } - - /// <summary> - /// Gets or sets the last activity. - /// </summary> - /// <value>The last activity.</value> - public DateTime LastActivity { get; set; } - - /// <summary> - /// Gets the participants. - /// </summary> - /// <value>The participants, or members of the group.</value> - public Dictionary<string, GroupMember> Participants { get; } = - new Dictionary<string, GroupMember>(StringComparer.OrdinalIgnoreCase); - - /// <summary> - /// Checks if a session is in this group. - /// </summary> - /// <param name="sessionId">The session id to check.</param> - /// <returns><c>true</c> if the session is in this group; <c>false</c> otherwise.</returns> - public bool ContainsSession(string sessionId) - { - return Participants.ContainsKey(sessionId); - } - - /// <summary> - /// Adds the session to the group. - /// </summary> - /// <param name="session">The session.</param> - public void AddSession(SessionInfo session) - { - Participants.TryAdd( - session.Id, - new GroupMember - { - Session = session, - Ping = DefaultPing, - IsBuffering = false - }); - } - - /// <summary> - /// Removes the session from the group. - /// </summary> - /// <param name="session">The session.</param> - public void RemoveSession(SessionInfo session) - { - Participants.Remove(session.Id); - } - - /// <summary> - /// Updates the ping of a session. - /// </summary> - /// <param name="session">The session.</param> - /// <param name="ping">The ping.</param> - public void UpdatePing(SessionInfo session, long ping) - { - if (Participants.TryGetValue(session.Id, out GroupMember value)) - { - value.Ping = ping; - } - } - - /// <summary> - /// Gets the highest ping in the group. - /// </summary> - /// <returns>The highest ping in the group.</returns> - public long GetHighestPing() - { - long max = long.MinValue; - foreach (var session in Participants.Values) - { - max = Math.Max(max, session.Ping); - } - - return max; - } - - /// <summary> - /// Sets the session's buffering state. - /// </summary> - /// <param name="session">The session.</param> - /// <param name="isBuffering">The state.</param> - public void SetBuffering(SessionInfo session, bool isBuffering) - { - if (Participants.TryGetValue(session.Id, out GroupMember value)) - { - value.IsBuffering = isBuffering; - } - } - - /// <summary> - /// Gets the group buffering state. - /// </summary> - /// <returns><c>true</c> if there is a session buffering in the group; <c>false</c> otherwise.</returns> - public bool IsBuffering() - { - foreach (var session in Participants.Values) - { - if (session.IsBuffering) - { - return true; - } - } - - return false; - } - - /// <summary> - /// Checks if the group is empty. - /// </summary> - /// <returns><c>true</c> if the group is empty; <c>false</c> otherwise.</returns> - public bool IsEmpty() - { - return Participants.Count == 0; - } - } -} diff --git a/MediaBrowser.Controller/SyncPlay/GroupMember.cs b/MediaBrowser.Controller/SyncPlay/GroupMember.cs index cde6f8e8ce..9a9d30277f 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupMember.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupMember.cs @@ -7,12 +7,6 @@ namespace MediaBrowser.Controller.SyncPlay /// </summary> public class GroupMember { - /// <summary> - /// Gets or sets a value indicating whether this member is buffering. - /// </summary> - /// <value><c>true</c> if member is buffering; <c>false</c> otherwise.</value> - public bool IsBuffering { get; set; } - /// <summary> /// Gets or sets the session. /// </summary> @@ -20,9 +14,21 @@ namespace MediaBrowser.Controller.SyncPlay public SessionInfo Session { get; set; } /// <summary> - /// Gets or sets the ping. + /// Gets or sets the ping, in milliseconds. /// </summary> /// <value>The ping.</value> public long Ping { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this member is buffering. + /// </summary> + /// <value><c>true</c> if member is buffering; <c>false</c> otherwise.</value> + public bool IsBuffering { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this member is following group playback. + /// </summary> + /// <value><c>true</c> to ignore member on group wait; <c>false</c> if they're following group playback.</value> + public bool IgnoreGroupWait { get; set; } } } diff --git a/MediaBrowser.Controller/SyncPlay/IPlaybackGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/IPlaybackGroupRequest.cs index a6e87a007f..35ca64c8df 100644 --- a/MediaBrowser.Controller/SyncPlay/IPlaybackGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/IPlaybackGroupRequest.cs @@ -12,13 +12,12 @@ namespace MediaBrowser.Controller.SyncPlay /// <summary> /// Gets the playback request type. /// </summary> - /// <value>The playback request type.</value> - PlaybackRequestType Type(); + /// <returns>The playback request type.</returns> + PlaybackRequestType GetRequestType(); /// <summary> /// Applies the request to a group. /// </summary> - /// <value>The operation completion status.</value> - bool Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken); + void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs index 5ac2aeb247..9a4e1ee1ee 100644 --- a/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs @@ -1,39 +1,41 @@ using System; using System.Threading; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Session; using MediaBrowser.Model.SyncPlay; namespace MediaBrowser.Controller.SyncPlay { /// <summary> - /// Interface ISyncPlayController. + /// Interface ISyncPlayGroupController. /// </summary> - public interface ISyncPlayController + public interface ISyncPlayGroupController { /// <summary> - /// Gets the group id. + /// Gets the group identifier. /// </summary> - /// <value>The group id.</value> - Guid GetGroupId(); + /// <value>The group identifier.</value> + Guid GroupId { get; } /// <summary> - /// Gets the playing item id. + /// Gets the play queue. /// </summary> - /// <value>The playing item id.</value> - Guid GetPlayingItemId(); + /// <value>The play queue.</value> + PlayQueueManager PlayQueue { get; } /// <summary> /// Checks if the group is empty. /// </summary> - /// <value>If the group is empty.</value> + /// <returns>If the group is empty.</returns> bool IsGroupEmpty(); /// <summary> /// Initializes the group with the session's info. /// </summary> /// <param name="session">The session.</param> + /// <param name="request">The request.</param> /// <param name="cancellationToken">The cancellation token.</param> - void CreateGroup(SessionInfo session, CancellationToken cancellationToken); + void CreateGroup(SessionInfo session, NewGroupRequest request, CancellationToken cancellationToken); /// <summary> /// Adds the session to the group. @@ -43,6 +45,14 @@ namespace MediaBrowser.Controller.SyncPlay /// <param name="cancellationToken">The cancellation token.</param> void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken); + /// <summary> + /// Restores the state of a session that already joined the group. + /// </summary> + /// <param name="session">The session.</param> + /// <param name="request">The request.</param> + /// <param name="cancellationToken">The cancellation token.</param> + void SessionRestore(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken); + /// <summary> /// Removes the session from the group. /// </summary> @@ -61,7 +71,15 @@ namespace MediaBrowser.Controller.SyncPlay /// <summary> /// Gets the info about the group for the clients. /// </summary> - /// <value>The group info for the clients.</value> + /// <returns>The group info for the clients.</returns> GroupInfoDto GetInfo(); + + /// <summary> + /// Checks if a user has access to all content in the play queue. + /// </summary> + /// <param name="user">The user.</param> + /// <returns><c>true</c> if the user can access the play queue; <c>false</c> otherwise.</returns> + bool HasAccessToPlayQueue(User user); + } } diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs index 6fa94e2ce4..9bef3f5593 100644 --- a/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayManager.cs @@ -15,8 +15,9 @@ namespace MediaBrowser.Controller.SyncPlay /// Creates a new group. /// </summary> /// <param name="session">The session that's creating the group.</param> + /// <param name="request">The request.</param> /// <param name="cancellationToken">The cancellation token.</param> - void NewGroup(SessionInfo session, CancellationToken cancellationToken); + void NewGroup(SessionInfo session, NewGroupRequest request, CancellationToken cancellationToken); /// <summary> /// Adds the session to a group. @@ -38,9 +39,8 @@ namespace MediaBrowser.Controller.SyncPlay /// Gets list of available groups for a session. /// </summary> /// <param name="session">The session.</param> - /// <param name="filterItemId">The item id to filter by.</param> - /// <value>The list of available groups.</value> - List<GroupInfoDto> ListGroups(SessionInfo session, Guid filterItemId); + /// <returns>The list of available groups.</returns> + List<GroupInfoDto> ListGroups(SessionInfo session); /// <summary> /// Handle a request by a session in a group. @@ -56,7 +56,7 @@ namespace MediaBrowser.Controller.SyncPlay /// <param name="session">The session.</param> /// <param name="group">The group.</param> /// <exception cref="InvalidOperationException"></exception> - void AddSessionToGroup(SessionInfo session, ISyncPlayController group); + void AddSessionToGroup(SessionInfo session, ISyncPlayGroupController group); /// <summary> /// Unmaps a session from a group. @@ -64,6 +64,6 @@ namespace MediaBrowser.Controller.SyncPlay /// <param name="session">The session.</param> /// <param name="group">The group.</param> /// <exception cref="InvalidOperationException"></exception> - void RemoveSessionFromGroup(SessionInfo session, ISyncPlayController group); + void RemoveSessionFromGroup(SessionInfo session, ISyncPlayGroupController group); } } diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayState.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayState.cs index 55c9ee938f..290576e30f 100644 --- a/MediaBrowser.Controller/SyncPlay/ISyncPlayState.cs +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayState.cs @@ -15,81 +15,202 @@ namespace MediaBrowser.Controller.SyncPlay /// <value>The group state.</value> GroupState GetGroupState(); + /// <summary> + /// Handle a session that joined the group. + /// </summary> + /// <param name="context">The context of the state.</param> + /// <param name="prevState">The previous state.</param> + /// <param name="session">The session.</param> + /// <param name="cancellationToken">The cancellation token.</param> + void SessionJoined(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken); + + /// <summary> + /// Handle a session that is leaving the group. + /// </summary> + /// <param name="context">The context of the state.</param> + /// <param name="prevState">The previous state.</param> + /// <param name="session">The session.</param> + /// <param name="cancellationToken">The cancellation token.</param> + void SessionLeaving(ISyncPlayStateContext context, GroupState prevState, SessionInfo session, CancellationToken cancellationToken); + /// <summary> /// Generic handle. Context's state can change. /// </summary> /// <param name="context">The context of the state.</param> - /// <param name="newState">Whether the state has been just set.</param> - /// <param name="request">The play action.</param> + /// <param name="prevState">The previous state.</param> + /// <param name="request">The generic action.</param> /// <param name="session">The session.</param> /// <param name="cancellationToken">The cancellation token.</param> - /// <value>The operation completion status.</value> - bool HandleRequest(ISyncPlayStateContext context, bool newState, IPlaybackGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, IPlaybackGroupRequest request, SessionInfo session, CancellationToken cancellationToken); /// <summary> /// Handles a play action requested by a session. Context's state can change. /// </summary> /// <param name="context">The context of the state.</param> - /// <param name="newState">Whether the state has been just set.</param> + /// <param name="prevState">The previous state.</param> /// <param name="request">The play action.</param> /// <param name="session">The session.</param> /// <param name="cancellationToken">The cancellation token.</param> - /// <value>The operation completion status.</value> - bool HandleRequest(ISyncPlayStateContext context, bool newState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// <summary> + /// Handles a playlist-item change requested by a session. Context's state can change. + /// </summary> + /// <param name="context">The context of the state.</param> + /// <param name="prevState">The previous state.</param> + /// <param name="request">The playlist-item change action.</param> + /// <param name="session">The session.</param> + /// <param name="cancellationToken">The cancellation token.</param> + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SetPlaylistItemGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// <summary> + /// Handles a remove-items change requested by a session. Context's state can change. + /// </summary> + /// <param name="context">The context of the state.</param> + /// <param name="prevState">The previous state.</param> + /// <param name="request">The remove-items change action.</param> + /// <param name="session">The session.</param> + /// <param name="cancellationToken">The cancellation token.</param> + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, RemoveFromPlaylistGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// <summary> + /// Handles a move-item change requested by a session. Context's state should not change. + /// </summary> + /// <param name="context">The context of the state.</param> + /// <param name="prevState">The previous state.</param> + /// <param name="request">The move-item change action.</param> + /// <param name="session">The session.</param> + /// <param name="cancellationToken">The cancellation token.</param> + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, MovePlaylistItemGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// <summary> + /// Handles a queue change requested by a session. Context's state should not change. + /// </summary> + /// <param name="context">The context of the state.</param> + /// <param name="prevState">The previous state.</param> + /// <param name="request">The queue action.</param> + /// <param name="session">The session.</param> + /// <param name="cancellationToken">The cancellation token.</param> + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, QueueGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// <summary> + /// Handles an unpause action requested by a session. Context's state can change. + /// </summary> + /// <param name="context">The context of the state.</param> + /// <param name="prevState">The previous state.</param> + /// <param name="request">The unpause action.</param> + /// <param name="session">The session.</param> + /// <param name="cancellationToken">The cancellation token.</param> + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, UnpauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken); /// <summary> /// Handles a pause action requested by a session. Context's state can change. /// </summary> /// <param name="context">The context of the state.</param> - /// <param name="newState">Whether the state has been just set.</param> + /// <param name="prevState">The previous state.</param> /// <param name="request">The pause action.</param> /// <param name="session">The session.</param> /// <param name="cancellationToken">The cancellation token.</param> - /// <value>The operation completion status.</value> - bool HandleRequest(ISyncPlayStateContext context, bool newState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// <summary> + /// Handles a stop action requested by a session. Context's state can change. + /// </summary> + /// <param name="context">The context of the state.</param> + /// <param name="prevState">The previous state.</param> + /// <param name="request">The stop action.</param> + /// <param name="session">The session.</param> + /// <param name="cancellationToken">The cancellation token.</param> + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, StopGroupRequest request, SessionInfo session, CancellationToken cancellationToken); /// <summary> /// Handles a seek action requested by a session. Context's state can change. /// </summary> /// <param name="context">The context of the state.</param> - /// <param name="newState">Whether the state has been just set.</param> + /// <param name="prevState">The previous state.</param> /// <param name="request">The seek action.</param> /// <param name="session">The session.</param> /// <param name="cancellationToken">The cancellation token.</param> - /// <value>The operation completion status.</value> - bool HandleRequest(ISyncPlayStateContext context, bool newState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken); /// <summary> /// Handles a buffering action requested by a session. Context's state can change. /// </summary> /// <param name="context">The context of the state.</param> - /// <param name="newState">Whether the state has been just set.</param> + /// <param name="prevState">The previous state.</param> /// <param name="request">The buffering action.</param> /// <param name="session">The session.</param> /// <param name="cancellationToken">The cancellation token.</param> - /// <value>The operation completion status.</value> - bool HandleRequest(ISyncPlayStateContext context, bool newState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken); /// <summary> /// Handles a buffering-done action requested by a session. Context's state can change. /// </summary> /// <param name="context">The context of the state.</param> - /// <param name="newState">Whether the state has been just set.</param> + /// <param name="prevState">The previous state.</param> /// <param name="request">The buffering-done action.</param> /// <param name="session">The session.</param> /// <param name="cancellationToken">The cancellation token.</param> - /// <value>The operation completion status.</value> - bool HandleRequest(ISyncPlayStateContext context, bool newState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// <summary> + /// Handles a next-track action requested by a session. Context's state can change. + /// </summary> + /// <param name="context">The context of the state.</param> + /// <param name="prevState">The previous state.</param> + /// <param name="request">The next-track action.</param> + /// <param name="session">The session.</param> + /// <param name="cancellationToken">The cancellation token.</param> + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, NextTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// <summary> + /// Handles a previous-track action requested by a session. Context's state can change. + /// </summary> + /// <param name="context">The context of the state.</param> + /// <param name="prevState">The previous state.</param> + /// <param name="request">The previous-track action.</param> + /// <param name="session">The session.</param> + /// <param name="cancellationToken">The cancellation token.</param> + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PreviousTrackGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// <summary> + /// Handles a repeat-mode change requested by a session. Context's state should not change. + /// </summary> + /// <param name="context">The context of the state.</param> + /// <param name="prevState">The previous state.</param> + /// <param name="request">The repeat-mode action.</param> + /// <param name="session">The session.</param> + /// <param name="cancellationToken">The cancellation token.</param> + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SetRepeatModeGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// <summary> + /// Handles a shuffle-mode change requested by a session. Context's state should not change. + /// </summary> + /// <param name="context">The context of the state.</param> + /// <param name="prevState">The previous state.</param> + /// <param name="request">The shuffle-mode action.</param> + /// <param name="session">The session.</param> + /// <param name="cancellationToken">The cancellation token.</param> + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, SetShuffleModeGroupRequest request, SessionInfo session, CancellationToken cancellationToken); /// <summary> /// Updates ping of a session. Context's state should not change. /// </summary> /// <param name="context">The context of the state.</param> - /// <param name="newState">Whether the state has been just set.</param> + /// <param name="prevState">The previous state.</param> /// <param name="request">The buffering-done action.</param> /// <param name="session">The session.</param> /// <param name="cancellationToken">The cancellation token.</param> - /// <value>The operation completion status.</value> - bool HandleRequest(ISyncPlayStateContext context, bool newState, PingGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, PingGroupRequest request, SessionInfo session, CancellationToken cancellationToken); + + /// <summary> + /// Updates whether the session should be considered during group wait. Context's state should not change. + /// </summary> + /// <param name="context">The context of the state.</param> + /// <param name="prevState">The previous state.</param> + /// <param name="request">The ignore-wait action.</param> + /// <param name="session">The session.</param> + /// <param name="cancellationToken">The cancellation token.</param> + void HandleRequest(ISyncPlayStateContext context, GroupState prevState, IgnoreWaitGroupRequest request, SessionInfo session, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayStateContext.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayStateContext.cs index 9bdb1ace6c..18a6857491 100644 --- a/MediaBrowser.Controller/SyncPlay/ISyncPlayStateContext.cs +++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayStateContext.cs @@ -12,10 +12,34 @@ namespace MediaBrowser.Controller.SyncPlay public interface ISyncPlayStateContext { /// <summary> - /// Gets the context's group. + /// Gets the default ping value used for sessions, in milliseconds. /// </summary> - /// <value>The group.</value> - GroupInfo GetGroup(); + /// <value>The default ping value used for sessions, in milliseconds.</value> + long DefaultPing { get; } + + /// <summary> + /// Gets the group identifier. + /// </summary> + /// <value>The group identifier.</value> + Guid GroupId { get; } + + /// <summary> + /// Gets or sets the position ticks. + /// </summary> + /// <value>The position ticks.</value> + long PositionTicks { get; set; } + + /// <summary> + /// Gets or sets the last activity. + /// </summary> + /// <value>The last activity.</value> + DateTime LastActivity { get; set; } + + /// <summary> + /// Gets the play queue. + /// </summary> + /// <value>The play queue.</value> + PlayQueueManager PlayQueue { get; } /// <summary> /// Sets a new state. @@ -30,7 +54,7 @@ namespace MediaBrowser.Controller.SyncPlay /// <param name="type">The filtering type.</param> /// <param name="message">The message to send.</param> /// <param name="cancellationToken">The cancellation token.</param> - /// <value>The task.</value> + /// <returns>The task.</returns> Task SendGroupUpdate<T>(SessionInfo from, SyncPlayBroadcastType type, GroupUpdate<T> message, CancellationToken cancellationToken); /// <summary> @@ -40,14 +64,14 @@ namespace MediaBrowser.Controller.SyncPlay /// <param name="type">The filtering type.</param> /// <param name="message">The message to send.</param> /// <param name="cancellationToken">The cancellation token.</param> - /// <value>The task.</value> + /// <returns>The task.</returns> Task SendCommand(SessionInfo from, SyncPlayBroadcastType type, SendCommand message, CancellationToken cancellationToken); /// <summary> /// Builds a new playback command with some default values. /// </summary> /// <param name="type">The command type.</param> - /// <value>The SendCommand.</value> + /// <returns>The SendCommand.</returns> SendCommand NewSyncPlayCommand(SendCommandType type); /// <summary> @@ -55,21 +79,135 @@ namespace MediaBrowser.Controller.SyncPlay /// </summary> /// <param name="type">The update type.</param> /// <param name="data">The data to send.</param> - /// <value>The GroupUpdate.</value> + /// <returns>The GroupUpdate.</returns> GroupUpdate<T> NewSyncPlayGroupUpdate<T>(GroupUpdateType type, T data); /// <summary> /// Converts DateTime to UTC string. /// </summary> - /// <param name="date">The date to convert.</param> - /// <value>The UTC string.</value> - string DateToUTCString(DateTime date); + /// <param name="dateTime">The date to convert.</param> + /// <returns>The UTC string.</returns> + string DateToUTCString(DateTime dateTime); /// <summary> /// Sanitizes the PositionTicks, considers the current playing item when available. /// </summary> /// <param name="positionTicks">The PositionTicks.</param> - /// <value>The sanitized PositionTicks.</value> + /// <returns>The sanitized PositionTicks.</returns> long SanitizePositionTicks(long? positionTicks); + + /// <summary> + /// Updates the ping of a session, in milliseconds. + /// </summary> + /// <param name="session">The session.</param> + /// <param name="ping">The ping, in milliseconds.</param> + void UpdatePing(SessionInfo session, long ping); + + /// <summary> + /// Gets the highest ping in the group, in milliseconds. + /// </summary> + /// <returns>The highest ping in the group.</returns> + long GetHighestPing(); + + /// <summary> + /// Sets the session's buffering state. + /// </summary> + /// <param name="session">The session.</param> + /// <param name="isBuffering">The state.</param> + void SetBuffering(SessionInfo session, bool isBuffering); + + /// <summary> + /// Sets the buffering state of all the sessions. + /// </summary> + /// <param name="isBuffering">The state.</param> + void SetAllBuffering(bool isBuffering); + + /// <summary> + /// Gets the group buffering state. + /// </summary> + /// <returns><c>true</c> if there is a session buffering in the group; <c>false</c> otherwise.</returns> + bool IsBuffering(); + + /// <summary> + /// Sets the session's group wait state. + /// </summary> + /// <param name="session">The session.</param> + /// <param name="ignoreGroupWait">The state.</param> + void SetIgnoreGroupWait(SessionInfo session, bool ignoreGroupWait); + + /// <summary> + /// Sets a new play queue. + /// </summary> + /// <param name="playQueue">The new play queue.</param> + /// <param name="playingItemPosition">The playing item position in the play queue.</param> + /// <param name="startPositionTicks">The start position ticks.</param> + /// <returns><c>true</c> if the play queue has been changed; <c>false</c> is something went wrong.</returns> + bool SetPlayQueue(Guid[] playQueue, int playingItemPosition, long startPositionTicks); + + /// <summary> + /// Sets the playing item. + /// </summary> + /// <param name="playlistItemId">The new playing item id.</param> + /// <returns><c>true</c> if the play queue has been changed; <c>false</c> is something went wrong.</returns> + bool SetPlayingItem(string playlistItemId); + + /// <summary> + /// Removes items from the play queue. + /// </summary> + /// <param name="playlistItemIds">The items to remove.</param> + /// <returns><c>true</c> if playing item got removed; <c>false</c> otherwise.</returns> + bool RemoveFromPlayQueue(string[] playlistItemIds); + + /// <summary> + /// Moves an item in the play queue. + /// </summary> + /// <param name="playlistItemId">The playlist id of the item to move.</param> + /// <param name="newIndex">The new position.</param> + /// <returns><c>true</c> if item has been moved; <c>false</c> is something went wrong.</returns> + bool MoveItemInPlayQueue(string playlistItemId, int newIndex); + + /// <summary> + /// Updates the play queue. + /// </summary> + /// <param name="newItems">The new items to add to the play queue.</param> + /// <param name="mode">The mode with which the items will be added.</param> + /// <returns><c>true</c> if the play queue has been changed; <c>false</c> is something went wrong.</returns> + bool AddToPlayQueue(Guid[] newItems, string mode); + + /// <summary> + /// Restarts current item in play queue. + /// </summary> + void RestartCurrentItem(); + + /// <summary> + /// Picks next item in play queue. + /// </summary> + /// <returns><c>true</c> if the item changed; <c>false</c> otherwise.</returns> + bool NextItemInQueue(); + + /// <summary> + /// Picks previous item in play queue. + /// </summary> + /// <returns><c>true</c> if the item changed; <c>false</c> otherwise.</returns> + bool PreviousItemInQueue(); + + /// <summary> + /// Sets the repeat mode. + /// </summary> + /// <param name="mode">The new mode.</param> + void SetRepeatMode(string mode); + + /// <summary> + /// Sets the shuffle mode. + /// </summary> + /// <param name="mode">The new mode.</param> + void SetShuffleMode(string mode); + + /// <summary> + /// Creates a play queue update. + /// </summary> + /// <param name="reason">The reason for the update.</param> + /// <returns>The play queue update.</returns> + PlayQueueUpdate GetPlayQueueUpdate(PlayQueueUpdateReason reason); } } diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/BufferGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/BufferGroupRequest.cs index 21dae8e4e6..0815dd79b2 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/BufferGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/BufferGroupRequest.cs @@ -23,21 +23,27 @@ namespace MediaBrowser.Controller.SyncPlay public long PositionTicks { get; set; } /// <summary> - /// Gets or sets the playing item id. + /// Gets or sets the client playback status. /// </summary> - /// <value>The playing item id.</value> - public Guid PlayingItemId { get; set; } + /// <value>The client playback status.</value> + public bool IsPlaying { get; set; } + + /// <summary> + /// Gets or sets the playlist item id of the playing item. + /// </summary> + /// <value>The playlist item id.</value> + public string PlaylistItemId { get; set; } /// <inheritdoc /> - public PlaybackRequestType Type() + public PlaybackRequestType GetRequestType() { return PlaybackRequestType.Buffer; } /// <inheritdoc /> - public bool Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) { - return state.HandleRequest(context, false, this, session, cancellationToken); + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); } } } diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/IgnoreWaitGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/IgnoreWaitGroupRequest.cs new file mode 100644 index 0000000000..5466cbe2f7 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/IgnoreWaitGroupRequest.cs @@ -0,0 +1,30 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// <summary> + /// Class IgnoreWaitGroupRequest. + /// </summary> + public class IgnoreWaitGroupRequest : IPlaybackGroupRequest + { + /// <summary> + /// Gets or sets the client group-wait status. + /// </summary> + /// <value>The client group-wait status.</value> + public bool IgnoreWait { get; set; } + + /// <inheritdoc /> + public PlaybackRequestType GetRequestType() + { + return PlaybackRequestType.IgnoreWait; + } + + /// <inheritdoc /> + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/MovePlaylistItemGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/MovePlaylistItemGroupRequest.cs new file mode 100644 index 0000000000..7a293c02fd --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/MovePlaylistItemGroupRequest.cs @@ -0,0 +1,36 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// <summary> + /// Class MovePlaylistItemGroupRequest. + /// </summary> + public class MovePlaylistItemGroupRequest : IPlaybackGroupRequest + { + /// <summary> + /// Gets or sets the playlist id of the item. + /// </summary> + /// <value>The playlist id of the item.</value> + public string PlaylistItemId { get; set; } + + /// <summary> + /// Gets or sets the new position. + /// </summary> + /// <value>The new position.</value> + public int NewIndex { get; set; } + + /// <inheritdoc /> + public PlaybackRequestType GetRequestType() + { + return PlaybackRequestType.Queue; + } + + /// <inheritdoc /> + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/NextTrackGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/NextTrackGroupRequest.cs new file mode 100644 index 0000000000..d19df2c6a0 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/NextTrackGroupRequest.cs @@ -0,0 +1,30 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// <summary> + /// Class NextTrackGroupRequest. + /// </summary> + public class NextTrackGroupRequest : IPlaybackGroupRequest + { + /// <summary> + /// Gets or sets the playing item id. + /// </summary> + /// <value>The playing item id.</value> + public string PlaylistItemId { get; set; } + + /// <inheritdoc /> + public PlaybackRequestType GetRequestType() + { + return PlaybackRequestType.NextTrack; + } + + /// <inheritdoc /> + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PauseGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PauseGroupRequest.cs index 21a46add8c..facb25155c 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PauseGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PauseGroupRequest.cs @@ -10,15 +10,15 @@ namespace MediaBrowser.Controller.SyncPlay public class PauseGroupRequest : IPlaybackGroupRequest { /// <inheritdoc /> - public PlaybackRequestType Type() + public PlaybackRequestType GetRequestType() { return PlaybackRequestType.Pause; } /// <inheritdoc /> - public bool Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) { - return state.HandleRequest(context, false, this, session, cancellationToken); + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); } } } diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PingGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PingGroupRequest.cs index 2f78edfc56..631bf245b1 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PingGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PingGroupRequest.cs @@ -2,7 +2,6 @@ using System.Threading; using MediaBrowser.Model.SyncPlay; using MediaBrowser.Controller.Session; -// FIXME: not really group related, can be moved up to SyncPlayController maybe? namespace MediaBrowser.Controller.SyncPlay { /// <summary> @@ -17,15 +16,15 @@ namespace MediaBrowser.Controller.SyncPlay public long Ping { get; set; } /// <inheritdoc /> - public PlaybackRequestType Type() + public PlaybackRequestType GetRequestType() { return PlaybackRequestType.Ping; } /// <inheritdoc /> - public bool Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) { - return state.HandleRequest(context, false, this, session, cancellationToken); + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); } } } diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PlayGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PlayGroupRequest.cs index 942229a775..f3dd769e46 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PlayGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PlayGroupRequest.cs @@ -1,3 +1,4 @@ +using System; using System.Threading; using MediaBrowser.Model.SyncPlay; using MediaBrowser.Controller.Session; @@ -9,16 +10,34 @@ namespace MediaBrowser.Controller.SyncPlay /// </summary> public class PlayGroupRequest : IPlaybackGroupRequest { + /// <summary> + /// Gets or sets the playing queue. + /// </summary> + /// <value>The playing queue.</value> + public Guid[] PlayingQueue { get; set; } + + /// <summary> + /// Gets or sets the playing item from the queue. + /// </summary> + /// <value>The playing item.</value> + public int PlayingItemPosition { get; set; } + + /// <summary> + /// Gets or sets the start position ticks. + /// </summary> + /// <value>The start position ticks.</value> + public long StartPositionTicks { get; set; } + /// <inheritdoc /> - public PlaybackRequestType Type() + public PlaybackRequestType GetRequestType() { return PlaybackRequestType.Play; } /// <inheritdoc /> - public bool Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) { - return state.HandleRequest(context, false, this, session, cancellationToken); + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); } } } diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PreviousTrackGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PreviousTrackGroupRequest.cs new file mode 100644 index 0000000000..663011b429 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/PreviousTrackGroupRequest.cs @@ -0,0 +1,30 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// <summary> + /// Class PreviousTrackGroupRequest. + /// </summary> + public class PreviousTrackGroupRequest : IPlaybackGroupRequest + { + /// <summary> + /// Gets or sets the playing item id. + /// </summary> + /// <value>The playing item id.</value> + public string PlaylistItemId { get; set; } + + /// <inheritdoc /> + public PlaybackRequestType GetRequestType() + { + return PlaybackRequestType.PreviousTrack; + } + + /// <inheritdoc /> + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/QueueGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/QueueGroupRequest.cs new file mode 100644 index 0000000000..01c08cc860 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/QueueGroupRequest.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// <summary> + /// Class QueueGroupRequest. + /// </summary> + public class QueueGroupRequest : IPlaybackGroupRequest + { + /// <summary> + /// Gets or sets the items to queue. + /// </summary> + /// <value>The items to queue.</value> + public Guid[] ItemIds { get; set; } + + /// <summary> + /// Gets or sets the mode in which to add the new items. + /// </summary> + /// <value>The mode.</value> + public string Mode { get; set; } + + /// <inheritdoc /> + public PlaybackRequestType GetRequestType() + { + return PlaybackRequestType.Queue; + } + + /// <inheritdoc /> + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/ReadyGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/ReadyGroupRequest.cs index ee88ddddbb..16bc67c617 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/ReadyGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/ReadyGroupRequest.cs @@ -23,21 +23,27 @@ namespace MediaBrowser.Controller.SyncPlay public long PositionTicks { get; set; } /// <summary> - /// Gets or sets the playing item id. + /// Gets or sets the client playback status. /// </summary> - /// <value>The playing item id.</value> - public Guid PlayingItemId { get; set; } + /// <value>The client playback status.</value> + public bool IsPlaying { get; set; } + + /// <summary> + /// Gets or sets the playlist item id of the playing item. + /// </summary> + /// <value>The playlist item id.</value> + public string PlaylistItemId { get; set; } /// <inheritdoc /> - public PlaybackRequestType Type() + public PlaybackRequestType GetRequestType() { return PlaybackRequestType.Ready; } /// <inheritdoc /> - public bool Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) { - return state.HandleRequest(context, false, this, session, cancellationToken); + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); } } } diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/RemoveFromPlaylistGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/RemoveFromPlaylistGroupRequest.cs new file mode 100644 index 0000000000..3fc77f6771 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/RemoveFromPlaylistGroupRequest.cs @@ -0,0 +1,30 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// <summary> + /// Class RemoveFromPlaylistGroupRequest. + /// </summary> + public class RemoveFromPlaylistGroupRequest : IPlaybackGroupRequest + { + /// <summary> + /// Gets or sets the playlist ids ot the items. + /// </summary> + /// <value>The playlist ids ot the items.</value> + public string[] PlaylistItemIds { get; set; } + + /// <inheritdoc /> + public PlaybackRequestType GetRequestType() + { + return PlaybackRequestType.Queue; + } + + /// <inheritdoc /> + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SeekGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SeekGroupRequest.cs index bb5e7a343e..24d9be5073 100644 --- a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SeekGroupRequest.cs +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SeekGroupRequest.cs @@ -16,15 +16,15 @@ namespace MediaBrowser.Controller.SyncPlay public long PositionTicks { get; set; } /// <inheritdoc /> - public PlaybackRequestType Type() + public PlaybackRequestType GetRequestType() { return PlaybackRequestType.Seek; } /// <inheritdoc /> - public bool Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) { - return state.HandleRequest(context, false, this, session, cancellationToken); + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); } } } diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetCurrentItemGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetCurrentItemGroupRequest.cs new file mode 100644 index 0000000000..d70559899a --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetCurrentItemGroupRequest.cs @@ -0,0 +1,30 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// <summary> + /// Class SetPlaylistItemGroupRequest. + /// </summary> + public class SetPlaylistItemGroupRequest : IPlaybackGroupRequest + { + /// <summary> + /// Gets or sets the playlist id of the playing item. + /// </summary> + /// <value>The playlist id of the playing item.</value> + public string PlaylistItemId { get; set; } + + /// <inheritdoc /> + public PlaybackRequestType GetRequestType() + { + return PlaybackRequestType.SetPlaylistItem; + } + + /// <inheritdoc /> + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetRepeatModeGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetRepeatModeGroupRequest.cs new file mode 100644 index 0000000000..5f36f60e45 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetRepeatModeGroupRequest.cs @@ -0,0 +1,30 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// <summary> + /// Class SetRepeatModeGroupRequest. + /// </summary> + public class SetRepeatModeGroupRequest : IPlaybackGroupRequest + { + /// <summary> + /// Gets or sets the repeat mode. + /// </summary> + /// <value>The repeat mode.</value> + public string Mode { get; set; } + + /// <inheritdoc /> + public PlaybackRequestType GetRequestType() + { + return PlaybackRequestType.SetRepeatMode; + } + + /// <inheritdoc /> + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetShuffleModeGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetShuffleModeGroupRequest.cs new file mode 100644 index 0000000000..472455fd3b --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/SetShuffleModeGroupRequest.cs @@ -0,0 +1,30 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// <summary> + /// Class SetShuffleModeGroupRequest. + /// </summary> + public class SetShuffleModeGroupRequest : IPlaybackGroupRequest + { + /// <summary> + /// Gets or sets the shuffle mode. + /// </summary> + /// <value>The shuffle mode.</value> + public string Mode { get; set; } + + /// <inheritdoc /> + public PlaybackRequestType GetRequestType() + { + return PlaybackRequestType.SetShuffleMode; + } + + /// <inheritdoc /> + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/StopGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/StopGroupRequest.cs new file mode 100644 index 0000000000..f1581c98d9 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/StopGroupRequest.cs @@ -0,0 +1,24 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// <summary> + /// Class StopGroupRequest. + /// </summary> + public class StopGroupRequest : IPlaybackGroupRequest + { + /// <inheritdoc /> + public PlaybackRequestType GetRequestType() + { + return PlaybackRequestType.Stop; + } + + /// <inheritdoc /> + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/PlaybackRequest/UnpauseGroupRequest.cs b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/UnpauseGroupRequest.cs new file mode 100644 index 0000000000..1072952081 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/PlaybackRequest/UnpauseGroupRequest.cs @@ -0,0 +1,24 @@ +using System.Threading; +using MediaBrowser.Model.SyncPlay; +using MediaBrowser.Controller.Session; + +namespace MediaBrowser.Controller.SyncPlay +{ + /// <summary> + /// Class UnpauseGroupRequest. + /// </summary> + public class UnpauseGroupRequest : IPlaybackGroupRequest + { + /// <inheritdoc /> + public PlaybackRequestType GetRequestType() + { + return PlaybackRequestType.Unpause; + } + + /// <inheritdoc /> + public void Apply(ISyncPlayStateContext context, ISyncPlayState state, SessionInfo session, CancellationToken cancellationToken) + { + state.HandleRequest(context, state.GetGroupState(), this, session, cancellationToken); + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs new file mode 100644 index 0000000000..701982cc00 --- /dev/null +++ b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs @@ -0,0 +1,596 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Model.SyncPlay; + +namespace MediaBrowser.Controller.SyncPlay +{ + static class ListShuffleExtension + { + private static Random rng = new Random(); + public static void Shuffle<T>(this IList<T> list) + { + int n = list.Count; + while (n > 1) + { + n--; + int k = rng.Next(n + 1); + T value = list[k]; + list[k] = list[n]; + list[n] = value; + } + } + } + + /// <summary> + /// Class PlayQueueManager. + /// </summary> + public class PlayQueueManager : IDisposable + { + /// <summary> + /// Gets or sets the playing item index. + /// </summary> + /// <value>The playing item index.</value> + public int PlayingItemIndex { get; private set; } + + /// <summary> + /// Gets or sets the last time the queue has been changed. + /// </summary> + /// <value>The last time the queue has been changed.</value> + public DateTime LastChange { get; private set; } + + /// <summary> + /// Gets the sorted playlist. + /// </summary> + /// <value>The sorted playlist, or play queue of the group.</value> + private List<QueueItem> SortedPlaylist { get; set; } = new List<QueueItem>(); + + /// <summary> + /// Gets the shuffled playlist. + /// </summary> + /// <value>The shuffled playlist, or play queue of the group.</value> + private List<QueueItem> ShuffledPlaylist { get; set; } = new List<QueueItem>(); + + /// <summary> + /// Gets or sets the shuffle mode. + /// </summary> + /// <value>The shuffle mode.</value> + public GroupShuffleMode ShuffleMode { get; private set; } = GroupShuffleMode.Sorted; + + /// <summary> + /// Gets or sets the repeat mode. + /// </summary> + /// <value>The repeat mode.</value> + public GroupRepeatMode RepeatMode { get; private set; } = GroupRepeatMode.RepeatNone; + + /// <summary> + /// Gets or sets the progressive id counter. + /// </summary> + /// <value>The progressive id.</value> + private int ProgressiveId { get; set; } = 0; + + private bool _disposed = false; + + /// <summary> + /// Initializes a new instance of the <see cref="PlayQueueManager" /> class. + /// </summary> + public PlayQueueManager() + { + Reset(); + } + + /// <inheritdoc /> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases unmanaged and optionally managed resources. + /// </summary> + /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + _disposed = true; + } + + /// <summary> + /// Gets the next available id. + /// </summary> + /// <returns>The next available id.</returns> + private int GetNextProgressiveId() { + return ProgressiveId++; + } + + /// <summary> + /// Creates a list from the array of items. Each item is given an unique playlist id. + /// </summary> + /// <returns>The list of queue items.</returns> + private List<QueueItem> CreateQueueItemsFromArray(Guid[] items) + { + return items.ToList() + .Select(item => new QueueItem() + { + ItemId = item, + PlaylistItemId = "syncPlayItem" + GetNextProgressiveId() + }) + .ToList(); + } + + /// <summary> + /// Gets the current playlist, depending on the shuffle mode. + /// </summary> + /// <returns>The playlist.</returns> + private List<QueueItem> GetPlaylistAsList() + { + if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) + { + return ShuffledPlaylist; + } + else + { + return SortedPlaylist; + } + } + + /// <summary> + /// Gets the current playlist as an array, depending on the shuffle mode. + /// </summary> + /// <returns>The array of items in the playlist.</returns> + public QueueItem[] GetPlaylist() { + if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) + { + return ShuffledPlaylist.ToArray(); + } + else + { + return SortedPlaylist.ToArray(); + } + } + + /// <summary> + /// Sets a new playlist. Playing item is set to none. Resets shuffle mode and repeat mode as well. + /// </summary> + /// <param name="items">The new items of the playlist.</param> + public void SetPlaylist(Guid[] items) + { + SortedPlaylist = CreateQueueItemsFromArray(items); + PlayingItemIndex = -1; + ShuffleMode = GroupShuffleMode.Sorted; + RepeatMode = GroupRepeatMode.RepeatNone; + LastChange = DateTime.UtcNow; + } + + /// <summary> + /// Appends new items to the playlist. The specified order is mantained for the sorted playlist, whereas items get shuffled for the shuffled playlist. + /// </summary> + /// <param name="items">The items to add to the playlist.</param> + public void Queue(Guid[] items) + { + var newItems = CreateQueueItemsFromArray(items); + SortedPlaylist.AddRange(newItems); + + if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) + { + newItems.Shuffle(); + ShuffledPlaylist.AddRange(newItems); + } + + LastChange = DateTime.UtcNow; + } + + /// <summary> + /// Shuffles the playlist. Shuffle mode is changed. + /// </summary> + public void ShufflePlaylist() + { + if (SortedPlaylist.Count() == 0) + { + return; + } + + if (PlayingItemIndex < 0) { + ShuffledPlaylist = SortedPlaylist.ToList(); + ShuffledPlaylist.Shuffle(); + } + else + { + var playingItem = SortedPlaylist[PlayingItemIndex]; + ShuffledPlaylist = SortedPlaylist.ToList(); + ShuffledPlaylist.RemoveAt(PlayingItemIndex); + ShuffledPlaylist.Shuffle(); + ShuffledPlaylist = ShuffledPlaylist.Prepend(playingItem).ToList(); + PlayingItemIndex = 0; + } + + ShuffleMode = GroupShuffleMode.Shuffle; + LastChange = DateTime.UtcNow; + } + + /// <summary> + /// Resets the playlist to sorted mode. Shuffle mode is changed. + /// </summary> + public void SortShuffledPlaylist() + { + if (PlayingItemIndex >= 0) + { + var playingItem = ShuffledPlaylist[PlayingItemIndex]; + PlayingItemIndex = SortedPlaylist.IndexOf(playingItem); + } + + ShuffledPlaylist.Clear(); + + ShuffleMode = GroupShuffleMode.Sorted; + LastChange = DateTime.UtcNow; + } + + /// <summary> + /// Clears the playlist. + /// </summary> + /// <param name="clearPlayingItem">Whether to remove the playing item as well.</param> + public void ClearPlaylist(bool clearPlayingItem) + { + var playingItem = SortedPlaylist[PlayingItemIndex]; + SortedPlaylist.Clear(); + ShuffledPlaylist.Clear(); + LastChange = DateTime.UtcNow; + + if (!clearPlayingItem && playingItem != null) + { + SortedPlaylist.Add(playingItem); + if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) + { + SortedPlaylist.Add(playingItem); + } + } + } + + /// <summary> + /// Adds new items to the playlist right after the playing item. The specified order is mantained for the sorted playlist, whereas items get shuffled for the shuffled playlist. + /// </summary> + /// <param name="items">The items to add to the playlist.</param> + public void QueueNext(Guid[] items) + { + var newItems = CreateQueueItemsFromArray(items); + + if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) + { + // Append items to sorted playlist as they are + SortedPlaylist.AddRange(newItems); + // Shuffle items before adding to shuffled playlist + newItems.Shuffle(); + ShuffledPlaylist.InsertRange(PlayingItemIndex + 1, newItems); + } + else + { + SortedPlaylist.InsertRange(PlayingItemIndex + 1, newItems); + } + + LastChange = DateTime.UtcNow; + } + + /// <summary> + /// Gets playlist id of the playing item, if any. + /// </summary> + /// <returns>The playlist id of the playing item.</returns> + public string GetPlayingItemPlaylistId() + { + if (PlayingItemIndex < 0) + { + return null; + } + + var list = GetPlaylistAsList(); + + if (list.Count() > 0) + { + return list[PlayingItemIndex].PlaylistItemId; + } + else + { + return null; + } + } + + /// <summary> + /// Gets the playing item id, if any. + /// </summary> + /// <returns>The playing item id.</returns> + public Guid GetPlayingItemId() + { + if (PlayingItemIndex < 0) + { + return Guid.Empty; + } + + var list = GetPlaylistAsList(); + + if (list.Count() > 0) + { + return list[PlayingItemIndex].ItemId; + } + else + { + return Guid.Empty; + } + } + + /// <summary> + /// Sets the playing item using its id. If not in the playlist, the playing item is reset. + /// </summary> + /// <param name="itemId">The new playing item id.</param> + public void SetPlayingItemById(Guid itemId) + { + var itemIds = GetPlaylistAsList().Select(queueItem => queueItem.ItemId).ToList(); + PlayingItemIndex = itemIds.IndexOf(itemId); + LastChange = DateTime.UtcNow; + } + + /// <summary> + /// Sets the playing item using its playlist id. If not in the playlist, the playing item is reset. + /// </summary> + /// <param name="playlistItemId">The new playing item id.</param> + /// <returns><c>true</c> if playing item has been set; <c>false</c> if item is not in the playlist.</returns> + public bool SetPlayingItemByPlaylistId(string playlistItemId) + { + var playlistIds = GetPlaylistAsList().Select(queueItem => queueItem.PlaylistItemId).ToList(); + PlayingItemIndex = playlistIds.IndexOf(playlistItemId); + LastChange = DateTime.UtcNow; + return PlayingItemIndex != -1; + } + + /// <summary> + /// Sets the playing item using its position. If not in range, the playing item is reset. + /// </summary> + /// <param name="playlistIndex">The new playing item index.</param> + public void SetPlayingItemByIndex(int playlistIndex) + { + var list = GetPlaylistAsList(); + if (playlistIndex < 0 || playlistIndex > list.Count()) + { + PlayingItemIndex = -1; + } + else + { + PlayingItemIndex = playlistIndex; + } + + LastChange = DateTime.UtcNow; + } + + /// <summary> + /// Removes items from the playlist. If not removed, the playing item is preserved. + /// </summary> + /// <param name="playlistItemIds">The items to remove.</param> + /// <returns><c>true</c> if playing item got removed; <c>false</c> otherwise.</returns> + public bool RemoveFromPlaylist(string[] playlistItemIds) + { + var playingItem = SortedPlaylist[PlayingItemIndex]; + if (ShuffleMode.Equals(GroupShuffleMode.Shuffle)) + { + playingItem = ShuffledPlaylist[PlayingItemIndex]; + } + + var playlistItemIdsList = playlistItemIds.ToList(); + SortedPlaylist.RemoveAll(item => playlistItemIdsList.Contains(item.PlaylistItemId)); + ShuffledPlaylist.RemoveAll(item => playlistItemIdsList.Contains(item.PlaylistItemId)); + + LastChange = DateTime.UtcNow; + + if (playingItem != null) + { + if (playlistItemIds.Contains(playingItem.PlaylistItemId)) + { + // Playing item has been removed, picking previous item + PlayingItemIndex--; + if (PlayingItemIndex < 0) + { + // Was first element, picking next if available + PlayingItemIndex = SortedPlaylist.Count() > 0 ? 0 : -1; + } + + return true; + } + else + { + // Restoring playing item + SetPlayingItemByPlaylistId(playingItem.PlaylistItemId); + return false; + } + } + else + { + return false; + } + } + + /// <summary> + /// Moves an item in the playlist to another position. + /// </summary> + /// <param name="playlistItemId">The item to move.</param> + /// <param name="newIndex">The new position.</param> + /// <returns><c>true</c> if the item has been moved; <c>false</c> otherwise.</returns> + public bool MovePlaylistItem(string playlistItemId, int newIndex) + { + var list = GetPlaylistAsList(); + var playingItem = list[PlayingItemIndex]; + + var playlistIds = list.Select(queueItem => queueItem.PlaylistItemId).ToList(); + var oldIndex = playlistIds.IndexOf(playlistItemId); + if (oldIndex < 0) { + return false; + } + + var queueItem = list[oldIndex]; + list.RemoveAt(oldIndex); + newIndex = newIndex > list.Count() ? list.Count() : newIndex; + newIndex = newIndex < 0 ? 0 : newIndex; + list.Insert(newIndex, queueItem); + + LastChange = DateTime.UtcNow; + PlayingItemIndex = list.IndexOf(playingItem); + return true; + } + + /// <summary> + /// Resets the playlist to its initial state. + /// </summary> + public void Reset() + { + ProgressiveId = 0; + SortedPlaylist.Clear(); + ShuffledPlaylist.Clear(); + PlayingItemIndex = -1; + ShuffleMode = GroupShuffleMode.Sorted; + RepeatMode = GroupRepeatMode.RepeatNone; + LastChange = DateTime.UtcNow; + } + + /// <summary> + /// Sets the repeat mode. + /// </summary> + /// <param name="mode">The new mode.</param> + public void SetRepeatMode(string mode) + { + switch (mode) + { + case "RepeatOne": + RepeatMode = GroupRepeatMode.RepeatOne; + break; + case "RepeatAll": + RepeatMode = GroupRepeatMode.RepeatAll; + break; + default: + RepeatMode = GroupRepeatMode.RepeatNone; + break; + } + + LastChange = DateTime.UtcNow; + } + + /// <summary> + /// Sets the shuffle mode. + /// </summary> + /// <param name="mode">The new mode.</param> + public void SetShuffleMode(string mode) + { + switch (mode) + { + case "Shuffle": + ShufflePlaylist(); + break; + default: + SortShuffledPlaylist(); + break; + } + } + + /// <summary> + /// Toggles the shuffle mode between sorted and shuffled. + /// </summary> + public void ToggleShuffleMode() + { + SetShuffleMode(ShuffleMode.Equals(GroupShuffleMode.Shuffle) ? "Shuffle" : ""); + } + + /// <summary> + /// Gets the next item in the playlist considering repeat mode and shuffle mode. + /// </summary> + /// <returns>The next item in the playlist.</returns> + public QueueItem GetNextItemPlaylistId() + { + int newIndex; + var playlist = GetPlaylistAsList(); + + switch (RepeatMode) + { + case GroupRepeatMode.RepeatOne: + newIndex = PlayingItemIndex; + break; + case GroupRepeatMode.RepeatAll: + newIndex = PlayingItemIndex + 1; + if (newIndex >= playlist.Count()) + { + newIndex = 0; + } + break; + default: + newIndex = PlayingItemIndex + 1; + break; + } + + if (newIndex < 0 || newIndex >= playlist.Count()) + { + return null; + } + + return playlist[newIndex]; + } + + /// <summary> + /// Sets the next item in the queue as playing item. + /// </summary> + /// <returns><c>true</c> if the playing item changed; <c>false</c> otherwise.</returns> + public bool Next() + { + if (RepeatMode.Equals(GroupRepeatMode.RepeatOne)) + { + LastChange = DateTime.UtcNow; + return true; + } + + PlayingItemIndex++; + if (PlayingItemIndex >= SortedPlaylist.Count()) + { + if (RepeatMode.Equals(GroupRepeatMode.RepeatAll)) + { + PlayingItemIndex = 0; + } + else + { + PlayingItemIndex--; + return false; + } + } + + LastChange = DateTime.UtcNow; + return true; + } + + /// <summary> + /// Sets the previous item in the queue as playing item. + /// </summary> + /// <returns><c>true</c> if the playing item changed; <c>false</c> otherwise.</returns> + public bool Previous() + { + if (RepeatMode.Equals(GroupRepeatMode.RepeatOne)) + { + LastChange = DateTime.UtcNow; + return true; + } + + PlayingItemIndex--; + if (PlayingItemIndex < 0) + { + if (RepeatMode.Equals(GroupRepeatMode.RepeatAll)) + { + PlayingItemIndex = SortedPlaylist.Count() - 1; + } + else + { + PlayingItemIndex++; + return false; + } + } + + LastChange = DateTime.UtcNow; + return true; + } + } +} diff --git a/MediaBrowser.Controller/SyncPlay/SyncPlayAbstractState.cs b/MediaBrowser.Controller/SyncPlay/SyncPlayAbstractState.cs deleted file mode 100644 index 0b72d16686..0000000000 --- a/MediaBrowser.Controller/SyncPlay/SyncPlayAbstractState.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Threading; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.SyncPlay; - -namespace MediaBrowser.Controller.SyncPlay -{ - /// <summary> - /// Class SyncPlayAbstractState. - /// </summary> - /// <remarks> - /// Class is not thread-safe, external locking is required when accessing methods. - /// </remarks> - public abstract class SyncPlayAbstractState : ISyncPlayState - { - /// <inheritdoc /> - public abstract GroupState GetGroupState(); - - /// <inheritdoc /> - public virtual bool HandleRequest(ISyncPlayStateContext context, bool newState, IPlaybackGroupRequest request, SessionInfo session, CancellationToken cancellationToken) - { - return true; - } - - /// <inheritdoc /> - public virtual bool HandleRequest(ISyncPlayStateContext context, bool newState, PlayGroupRequest request, SessionInfo session, CancellationToken cancellationToken) - { - return true; - } - - /// <inheritdoc /> - public virtual bool HandleRequest(ISyncPlayStateContext context, bool newState, PauseGroupRequest request, SessionInfo session, CancellationToken cancellationToken) - { - return true; - } - - /// <inheritdoc /> - public virtual bool HandleRequest(ISyncPlayStateContext context, bool newState, SeekGroupRequest request, SessionInfo session, CancellationToken cancellationToken) - { - return true; - } - - /// <inheritdoc /> - public virtual bool HandleRequest(ISyncPlayStateContext context, bool newState, BufferGroupRequest request, SessionInfo session, CancellationToken cancellationToken) - { - return true; - } - - /// <inheritdoc /> - public virtual bool HandleRequest(ISyncPlayStateContext context, bool newState, ReadyGroupRequest request, SessionInfo session, CancellationToken cancellationToken) - { - return true; - } - - /// <inheritdoc /> - public virtual bool HandleRequest(ISyncPlayStateContext context, bool newState, PingGroupRequest request, SessionInfo session, CancellationToken cancellationToken) - { - GroupInfo group = context.GetGroup(); - - // Collected pings are used to account for network latency when unpausing playback - group.UpdatePing(session, request.Ping); - - return true; - } - } -} diff --git a/MediaBrowser.Model/SyncPlay/GroupInfoDto.cs b/MediaBrowser.Model/SyncPlay/GroupInfoDto.cs index ac84a26dc3..255f6812bd 100644 --- a/MediaBrowser.Model/SyncPlay/GroupInfoDto.cs +++ b/MediaBrowser.Model/SyncPlay/GroupInfoDto.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; namespace MediaBrowser.Model.SyncPlay { /// <summary> - /// Class GroupInfoView. + /// Class GroupInfoDto. /// </summary> public class GroupInfoDto { @@ -16,27 +16,27 @@ namespace MediaBrowser.Model.SyncPlay public string GroupId { get; set; } /// <summary> - /// Gets or sets the playing item id. + /// Gets or sets the group name. /// </summary> - /// <value>The playing item id.</value> - public string PlayingItemId { get; set; } + /// <value>The group name.</value> + public string GroupName { get; set; } /// <summary> - /// Gets or sets the playing item name. + /// Gets or sets the group state. /// </summary> - /// <value>The playing item name.</value> - public string PlayingItemName { get; set; } - - /// <summary> - /// Gets or sets the position ticks. - /// </summary> - /// <value>The position ticks.</value> - public long PositionTicks { get; set; } + /// <value>The group state.</value> + public GroupState State { get; set; } /// <summary> /// Gets or sets the participants. /// </summary> /// <value>The participants.</value> public IReadOnlyList<string> Participants { get; set; } + + /// <summary> + /// Gets or sets the date when this dto has been updated. + /// </summary> + /// <value>The date when this dto has been updated.</value> + public string LastUpdatedAt { get; set; } } } diff --git a/MediaBrowser.Model/SyncPlay/GroupRepeatMode.cs b/MediaBrowser.Model/SyncPlay/GroupRepeatMode.cs new file mode 100644 index 0000000000..4895e57b7f --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/GroupRepeatMode.cs @@ -0,0 +1,23 @@ +namespace MediaBrowser.Model.SyncPlay +{ + /// <summary> + /// Enum GroupRepeatMode. + /// </summary> + public enum GroupRepeatMode + { + /// <summary> + /// Repeat one item only. + /// </summary> + RepeatOne = 0, + + /// <summary> + /// Cycle the playlist. + /// </summary> + RepeatAll = 1, + + /// <summary> + /// Do not repeat. + /// </summary> + RepeatNone = 2 + } +} diff --git a/MediaBrowser.Model/SyncPlay/GroupShuffleMode.cs b/MediaBrowser.Model/SyncPlay/GroupShuffleMode.cs new file mode 100644 index 0000000000..de860883c0 --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/GroupShuffleMode.cs @@ -0,0 +1,18 @@ +namespace MediaBrowser.Model.SyncPlay +{ + /// <summary> + /// Enum GroupShuffleMode. + /// </summary> + public enum GroupShuffleMode + { + /// <summary> + /// Sorted playlist. + /// </summary> + Sorted = 0, + + /// <summary> + /// Shuffled playlist. + /// </summary> + Shuffle = 1 + } +} diff --git a/MediaBrowser.Model/SyncPlay/GroupStateUpdate.cs b/MediaBrowser.Model/SyncPlay/GroupStateUpdate.cs new file mode 100644 index 0000000000..7c7b267e6f --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/GroupStateUpdate.cs @@ -0,0 +1,22 @@ +#nullable disable + +namespace MediaBrowser.Model.SyncPlay +{ + /// <summary> + /// Class GroupStateUpdate. + /// </summary> + public class GroupStateUpdate + { + /// <summary> + /// Gets or sets the state of the group. + /// </summary> + /// <value>The state of the group.</value> + public GroupState State { get; set; } + + /// <summary> + /// Gets or sets the reason of the state change. + /// </summary> + /// <value>The reason of the state change.</value> + public PlaybackRequestType Reason { get; set; } + } +} diff --git a/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs b/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs index c749f7b13a..7423bff117 100644 --- a/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs +++ b/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs @@ -26,14 +26,14 @@ namespace MediaBrowser.Model.SyncPlay GroupLeft, /// <summary> - /// The group-wait update. Tells members of the group that a user is buffering. + /// The group-state update. Tells members of the group that the state changed. /// </summary> - GroupWait, + StateUpdate, /// <summary> - /// The prepare-session update. Tells a user to load some content. + /// The play-queue update. Tells a user what's the playing queue of the group. /// </summary> - PrepareSession, + PlayQueue, /// <summary> /// The not-in-group error. Tells a user that they don't belong to a group. diff --git a/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs b/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs index 0c77a61322..04f3a73b17 100644 --- a/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs +++ b/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs @@ -8,9 +8,9 @@ namespace MediaBrowser.Model.SyncPlay public class JoinGroupRequest { /// <summary> - /// Gets or sets the Group id. + /// Gets or sets the group id. /// </summary> - /// <value>The Group id to join.</value> + /// <value>The id of the group to join.</value> public Guid GroupId { get; set; } } } diff --git a/MediaBrowser.Model/SyncPlay/NewGroupRequest.cs b/MediaBrowser.Model/SyncPlay/NewGroupRequest.cs new file mode 100644 index 0000000000..ccab5313f7 --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/NewGroupRequest.cs @@ -0,0 +1,16 @@ +#nullable disable + +namespace MediaBrowser.Model.SyncPlay +{ + /// <summary> + /// Class NewGroupRequest. + /// </summary> + public class NewGroupRequest + { + /// <summary> + /// Gets or sets the group name. + /// </summary> + /// <value>The name of the new group.</value> + public string GroupName { get; set; } + } +} diff --git a/MediaBrowser.Model/SyncPlay/PlayQueueUpdate.cs b/MediaBrowser.Model/SyncPlay/PlayQueueUpdate.cs new file mode 100644 index 0000000000..5e2740a892 --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/PlayQueueUpdate.cs @@ -0,0 +1,52 @@ +#nullable disable + +namespace MediaBrowser.Model.SyncPlay +{ + /// <summary> + /// Class PlayQueueUpdate. + /// </summary> + public class PlayQueueUpdate + { + /// <summary> + /// Gets or sets the request type that originated this update. + /// </summary> + /// <value>The reason for the update.</value> + public PlayQueueUpdateReason Reason { get; set; } + + /// <summary> + /// Gets or sets the UTC time of the last change to the playing queue. + /// </summary> + /// <value>The UTC time of the last change to the playing queue.</value> + public string LastUpdate { get; set; } + + /// <summary> + /// Gets or sets the playlist. + /// </summary> + /// <value>The playlist.</value> + public QueueItem[] Playlist { get; set; } + + /// <summary> + /// Gets or sets the playing item index in the playlist. + /// </summary> + /// <value>The playing item index in the playlist.</value> + public int PlayingItemIndex { get; set; } + + /// <summary> + /// Gets or sets the start position ticks. + /// </summary> + /// <value>The start position ticks.</value> + public long StartPositionTicks { get; set; } + + /// <summary> + /// Gets or sets the shuffle mode. + /// </summary> + /// <value>The shuffle mode.</value> + public GroupShuffleMode ShuffleMode { get; set; } + + /// <summary> + /// Gets or sets the repeat mode. + /// </summary> + /// <value>The repeat mode.</value> + public GroupRepeatMode RepeatMode { get; set; } + } +} diff --git a/MediaBrowser.Model/SyncPlay/PlayQueueUpdateReason.cs b/MediaBrowser.Model/SyncPlay/PlayQueueUpdateReason.cs new file mode 100644 index 0000000000..4b3f6eb4d6 --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/PlayQueueUpdateReason.cs @@ -0,0 +1,58 @@ +namespace MediaBrowser.Model.SyncPlay +{ + /// <summary> + /// Enum PlayQueueUpdateReason. + /// </summary> + public enum PlayQueueUpdateReason + { + /// <summary> + /// A user is requesting to play a new playlist. + /// </summary> + NewPlaylist = 0, + + /// <summary> + /// A user is changing the playing item. + /// </summary> + SetCurrentItem = 1, + + /// <summary> + /// A user is removing items from the playlist. + /// </summary> + RemoveItems = 2, + + /// <summary> + /// A user is moving an item in the playlist. + /// </summary> + MoveItem = 3, + + /// <summary> + /// A user is making changes to the queue. + /// </summary> + Queue = 4, + + /// <summary> + /// A user is making changes to the queue. + /// </summary> + QueueNext = 5, + + /// <summary> + /// A user is requesting the next item in queue. + /// </summary> + NextTrack = 6, + + /// <summary> + /// A user is requesting the previous item in queue. + /// </summary> + PreviousTrack = 7, + + /// <summary> + /// A user is changing repeat mode. + /// </summary> + RepeatMode = 8, + + /// <summary> + /// A user is changing shuffle mode. + /// </summary> + ShuffleMode = 9 + } +} diff --git a/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs b/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs index e89efeed8a..0d0f48ea97 100644 --- a/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs +++ b/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs @@ -6,33 +6,87 @@ namespace MediaBrowser.Model.SyncPlay public enum PlaybackRequestType { /// <summary> - /// A user is requesting a play command for the group. + /// A user is setting a new playlist. /// </summary> Play = 0, + /// <summary> + /// A user is changing the playlist item. + /// </summary> + SetPlaylistItem = 1, + + /// <summary> + /// A user is removing items from the playlist. + /// </summary> + RemoveFromPlaylist = 2, + + /// <summary> + /// A user is moving an item in the playlist. + /// </summary> + MovePlaylistItem = 3, + + /// <summary> + /// A user is adding items to the playlist. + /// </summary> + Queue = 4, + + /// <summary> + /// A user is requesting an unpause command for the group. + /// </summary> + Unpause = 5, + /// <summary> /// A user is requesting a pause command for the group. /// </summary> - Pause = 1, + Pause = 6, /// <summary> - /// A user is requesting a seek command for the group. + /// A user is requesting a stop command for the group. /// </summary> - Seek = 2, + Stop = 7, /// <summary> + /// A user is requesting a seek command for the group. + /// </summary> + Seek = 8, + + /// <summary> /// A user is signaling that playback is buffering. /// </summary> - Buffer = 3, + Buffer = 9, /// <summary> /// A user is signaling that playback resumed. /// </summary> - Ready = 4, + Ready = 10, + + /// <summary> + /// A user is requesting next track in playlist. + /// </summary> + NextTrack = 11, + + /// <summary> + /// A user is requesting previous track in playlist. + /// </summary> + PreviousTrack = 12, + /// <summary> + /// A user is setting the repeat mode. + /// </summary> + SetRepeatMode = 13, + + /// <summary> + /// A user is setting the shuffle mode. + /// </summary> + SetShuffleMode = 14, + + /// <summary> + /// A user is reporting their ping. + /// </summary> + Ping = 15, /// <summary> - /// A user is reporting its ping. + /// A user is requesting to be ignored on group wait. /// </summary> - Ping = 5 + IgnoreWait = 16 } } diff --git a/MediaBrowser.Model/SyncPlay/QueueItem.cs b/MediaBrowser.Model/SyncPlay/QueueItem.cs new file mode 100644 index 0000000000..dc9cfbc229 --- /dev/null +++ b/MediaBrowser.Model/SyncPlay/QueueItem.cs @@ -0,0 +1,24 @@ +#nullable disable + +using System; + +namespace MediaBrowser.Model.SyncPlay +{ + /// <summary> + /// Class QueueItem. + /// </summary> + public class QueueItem + { + /// <summary> + /// Gets or sets the item id. + /// </summary> + /// <value>The item id.</value> + public Guid ItemId { get; set; } + + /// <summary> + /// Gets or sets the playlist id of the item. + /// </summary> + /// <value>The playlist id of the item.</value> + public string PlaylistItemId { get; set; } + } +} diff --git a/MediaBrowser.Model/SyncPlay/SendCommand.cs b/MediaBrowser.Model/SyncPlay/SendCommand.cs index 0f0be0152d..779f711af0 100644 --- a/MediaBrowser.Model/SyncPlay/SendCommand.cs +++ b/MediaBrowser.Model/SyncPlay/SendCommand.cs @@ -13,6 +13,12 @@ namespace MediaBrowser.Model.SyncPlay /// <value>The group identifier.</value> public string GroupId { get; set; } + /// <summary> + /// Gets or sets the playlist id of the playing item. + /// </summary> + /// <value>The playlist id of the playing item.</value> + public string PlaylistItemId { get; set; } + /// <summary> /// Gets or sets the UTC time when to execute the command. /// </summary> diff --git a/MediaBrowser.Model/SyncPlay/SendCommandType.cs b/MediaBrowser.Model/SyncPlay/SendCommandType.cs index 86dec9e900..e6b17c60ae 100644 --- a/MediaBrowser.Model/SyncPlay/SendCommandType.cs +++ b/MediaBrowser.Model/SyncPlay/SendCommandType.cs @@ -6,18 +6,23 @@ namespace MediaBrowser.Model.SyncPlay public enum SendCommandType { /// <summary> - /// The play command. Instructs users to start playback. + /// The unpause command. Instructs users to unpause playback. /// </summary> - Play = 0, + Unpause = 0, /// <summary> /// The pause command. Instructs users to pause playback. /// </summary> Pause = 1, + /// <summary> + /// The stop command. Instructs users to stop playback. + /// </summary> + Stop = 2, + /// <summary> /// The seek command. Instructs users to seek to a specified time. /// </summary> - Seek = 2 + Seek = 3 } } -- cgit v1.2.3 From 7bf320922c095b67293c05b8d2e0ed79f1db78d7 Mon Sep 17 00:00:00 2001 From: crobibero <cody@robibe.ro> Date: Fri, 13 Nov 2020 09:41:18 -0700 Subject: Fix nullability errors in Emby.Server.Implementations --- Emby.Server.Implementations/AppBase/ConfigurationHelper.cs | 10 ++++++++-- .../Cryptography/CryptographyProvider.cs | 5 +++++ Emby.Server.Implementations/Session/WebSocketController.cs | 7 ++++++- 3 files changed, 19 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs index 4c9ab33a7c..e19ce5edf8 100644 --- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs +++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs @@ -35,7 +35,8 @@ namespace Emby.Server.Implementations.AppBase } catch (Exception) { - configuration = Activator.CreateInstance(type); + var instanceConfiguration = Activator.CreateInstance(type); + configuration = instanceConfiguration ?? throw new NullReferenceException(nameof(instanceConfiguration)); } using var stream = new MemoryStream(buffer?.Length ?? 0); @@ -48,8 +49,13 @@ namespace Emby.Server.Implementations.AppBase // If the file didn't exist before, or if something has changed, re-save if (buffer == null || !newBytes.AsSpan(0, newBytesLen).SequenceEqual(buffer)) { - Directory.CreateDirectory(Path.GetDirectoryName(path)); + var directory = Path.GetDirectoryName(path); + if (directory == null) + { + throw new NullReferenceException(nameof(directory)); + } + Directory.CreateDirectory(directory); // Save it after load in case we got new items using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read)) { diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index fd302d1365..a48b6f3563 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -81,6 +81,11 @@ namespace Emby.Server.Implementations.Cryptography } using var h = HashAlgorithm.Create(hashMethod); + if (h == null) + { + throw new NullReferenceException(nameof(h)); + } + if (salt.Length == 0) { return h.ComputeHash(bytes); diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index b986ffa1cd..7559ccb782 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -55,8 +55,13 @@ namespace Emby.Server.Implementations.Session connection.Closed += OnConnectionClosed; } - private void OnConnectionClosed(object sender, EventArgs e) + private void OnConnectionClosed(object? sender, EventArgs e) { + if (sender == null) + { + throw new NullReferenceException(nameof(sender)); + } + var connection = (IWebSocketConnection)sender; _logger.LogDebug("Removing websocket from session {Session}", _session.Id); _sockets.Remove(connection); -- cgit v1.2.3 From 5f52a58e785fa4ae06fa07a93b81c1e7547ac9f3 Mon Sep 17 00:00:00 2001 From: crobibero <cody@robibe.ro> Date: Fri, 13 Nov 2020 11:14:44 -0700 Subject: Convert NullReferenceException to ResourceNotFoundException --- Emby.Server.Implementations/AppBase/ConfigurationHelper.cs | 5 +++-- Emby.Server.Implementations/Cryptography/CryptographyProvider.cs | 3 ++- Emby.Server.Implementations/Session/WebSocketController.cs | 3 ++- Jellyfin.Api/Controllers/DynamicHlsController.cs | 5 +++-- Jellyfin.Api/Controllers/EnvironmentController.cs | 3 ++- Jellyfin.Api/Controllers/HlsSegmentController.cs | 3 ++- Jellyfin.Api/Controllers/ItemLookupController.cs | 6 +++--- Jellyfin.Api/Controllers/RemoteImageController.cs | 6 +++--- Jellyfin.Api/Controllers/VideoHlsController.cs | 3 ++- Jellyfin.Api/Helpers/AudioHelper.cs | 3 ++- Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 2 +- Jellyfin.Api/Helpers/HlsHelpers.cs | 3 ++- Jellyfin.Api/Helpers/ProgressiveFileCopier.cs | 3 ++- Jellyfin.Api/Helpers/StreamingHelpers.cs | 2 +- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 5 +++-- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 7 ++++--- .../Users/DefaultPasswordResetProvider.cs | 2 +- MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs | 3 ++- 18 files changed, 40 insertions(+), 27 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs index e19ce5edf8..8cca5cc779 100644 --- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs +++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs @@ -3,6 +3,7 @@ using System; using System.IO; using System.Linq; +using MediaBrowser.Common.Extensions; using MediaBrowser.Model.Serialization; namespace Emby.Server.Implementations.AppBase @@ -36,7 +37,7 @@ namespace Emby.Server.Implementations.AppBase catch (Exception) { var instanceConfiguration = Activator.CreateInstance(type); - configuration = instanceConfiguration ?? throw new NullReferenceException(nameof(instanceConfiguration)); + configuration = instanceConfiguration ?? throw new ResourceNotFoundException(nameof(instanceConfiguration)); } using var stream = new MemoryStream(buffer?.Length ?? 0); @@ -52,7 +53,7 @@ namespace Emby.Server.Implementations.AppBase var directory = Path.GetDirectoryName(path); if (directory == null) { - throw new NullReferenceException(nameof(directory)); + throw new ResourceNotFoundException(nameof(directory)); } Directory.CreateDirectory(directory); diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index a48b6f3563..8d7f73b3cf 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Security.Cryptography; +using MediaBrowser.Common.Extensions; using MediaBrowser.Model.Cryptography; using static MediaBrowser.Common.Cryptography.Constants; @@ -83,7 +84,7 @@ namespace Emby.Server.Implementations.Cryptography using var h = HashAlgorithm.Create(hashMethod); if (h == null) { - throw new NullReferenceException(nameof(h)); + throw new ResourceNotFoundException(nameof(h)); } if (salt.Length == 0) diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index 7559ccb782..78f83e337f 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Net; @@ -59,7 +60,7 @@ namespace Emby.Server.Implementations.Session { if (sender == null) { - throw new NullReferenceException(nameof(sender)); + throw new ResourceNotFoundException(nameof(sender)); } var connection = (IWebSocketConnection)sender; diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index b0d5a7cd84..64bea999fa 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -14,6 +14,7 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.PlaybackDtos; using Jellyfin.Api.Models.StreamingDtos; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; @@ -1350,7 +1351,7 @@ namespace Jellyfin.Api.Controllers var directory = Path.GetDirectoryName(outputPath); if (directory == null) { - throw new NullReferenceException(nameof(directory)); + throw new ResourceNotFoundException(nameof(directory)); } var outputTsArg = Path.Combine(directory, Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request.SegmentContainer); @@ -1574,7 +1575,7 @@ namespace Jellyfin.Api.Controllers var folder = Path.GetDirectoryName(playlist); if (folder == null) { - throw new NullReferenceException(nameof(folder)); + throw new ResourceNotFoundException(nameof(folder)); } var filename = Path.GetFileNameWithoutExtension(playlist); diff --git a/Jellyfin.Api/Controllers/EnvironmentController.cs b/Jellyfin.Api/Controllers/EnvironmentController.cs index 8de217bbfa..6dd5362547 100644 --- a/Jellyfin.Api/Controllers/EnvironmentController.cs +++ b/Jellyfin.Api/Controllers/EnvironmentController.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Models.EnvironmentDtos; +using MediaBrowser.Common.Extensions; using MediaBrowser.Model.IO; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -105,7 +106,7 @@ namespace Jellyfin.Api.Controllers { if (validatePathDto.Path == null) { - throw new NullReferenceException(nameof(validatePathDto.Path)); + throw new ResourceNotFoundException(nameof(validatePathDto.Path)); } var file = Path.Combine(validatePathDto.Path, Guid.NewGuid().ToString()); diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs index 2cee0e9ef2..fe1ffacb01 100644 --- a/Jellyfin.Api/Controllers/HlsSegmentController.cs +++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs @@ -8,6 +8,7 @@ using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; using Jellyfin.Api.Helpers; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.IO; @@ -138,7 +139,7 @@ namespace Jellyfin.Api.Controllers if (playlistPath == null) { - throw new NullReferenceException(nameof(playlistPath)); + throw new ResourceNotFoundException(nameof(playlistPath)); } return GetFileResult(file, playlistPath); diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs index b6cb797167..b14840f80c 100644 --- a/Jellyfin.Api/Controllers/ItemLookupController.cs +++ b/Jellyfin.Api/Controllers/ItemLookupController.cs @@ -336,7 +336,7 @@ namespace Jellyfin.Api.Controllers using var result = await _providerManager.GetSearchImage(providerName, url, CancellationToken.None).ConfigureAwait(false); if (result.Content.Headers.ContentType?.MediaType == null) { - throw new NullReferenceException(nameof(result.Content.Headers.ContentType)); + throw new ResourceNotFoundException(nameof(result.Content.Headers.ContentType)); } var ext = result.Content.Headers.ContentType.MediaType.Split('/')[^1]; @@ -345,7 +345,7 @@ namespace Jellyfin.Api.Controllers var directory = Path.GetDirectoryName(fullCachePath); if (directory == null) { - throw new NullReferenceException(nameof(directory)); + throw new ResourceNotFoundException(nameof(directory)); } Directory.CreateDirectory(directory); @@ -365,7 +365,7 @@ namespace Jellyfin.Api.Controllers var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath); if (pointerCacheDirectory == null) { - throw new NullReferenceException(nameof(pointerCacheDirectory)); + throw new ResourceNotFoundException(nameof(pointerCacheDirectory)); } Directory.CreateDirectory(pointerCacheDirectory); diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs index ad76b39847..2566f574c4 100644 --- a/Jellyfin.Api/Controllers/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/RemoteImageController.cs @@ -251,7 +251,7 @@ namespace Jellyfin.Api.Controllers using var response = await httpClient.GetAsync(url).ConfigureAwait(false); if (response.Content.Headers.ContentType?.MediaType == null) { - throw new NullReferenceException(nameof(response.Content.Headers.ContentType)); + throw new ResourceNotFoundException(nameof(response.Content.Headers.ContentType)); } var ext = response.Content.Headers.ContentType.MediaType.Split('/').Last(); @@ -260,7 +260,7 @@ namespace Jellyfin.Api.Controllers var fullCacheDirectory = Path.GetDirectoryName(fullCachePath); if (fullCacheDirectory == null) { - throw new NullReferenceException(nameof(fullCacheDirectory)); + throw new ResourceNotFoundException(nameof(fullCacheDirectory)); } Directory.CreateDirectory(fullCacheDirectory); @@ -270,7 +270,7 @@ namespace Jellyfin.Api.Controllers var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath); if (pointerCacheDirectory == null) { - throw new NullReferenceException(nameof(pointerCacheDirectory)); + throw new ResourceNotFoundException(nameof(pointerCacheDirectory)); } Directory.CreateDirectory(pointerCacheDirectory); diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs index 517239966f..c47876beba 100644 --- a/Jellyfin.Api/Controllers/VideoHlsController.cs +++ b/Jellyfin.Api/Controllers/VideoHlsController.cs @@ -11,6 +11,7 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.PlaybackDtos; using Jellyfin.Api.Models.StreamingDtos; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; @@ -364,7 +365,7 @@ namespace Jellyfin.Api.Controllers var directory = Path.GetDirectoryName(outputPath); if (directory == null) { - throw new NullReferenceException(nameof(directory)); + throw new ResourceNotFoundException(nameof(directory)); } var outputTsArg = Path.Combine(directory, Path.GetFileNameWithoutExtension(outputPath)) + "%d" + format; diff --git a/Jellyfin.Api/Helpers/AudioHelper.cs b/Jellyfin.Api/Helpers/AudioHelper.cs index c6d577b193..21ec2d32f9 100644 --- a/Jellyfin.Api/Helpers/AudioHelper.cs +++ b/Jellyfin.Api/Helpers/AudioHelper.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Models.StreamingDtos; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; @@ -101,7 +102,7 @@ namespace Jellyfin.Api.Helpers { if (_httpContextAccessor.HttpContext == null) { - throw new NullReferenceException(nameof(_httpContextAccessor.HttpContext)); + throw new ResourceNotFoundException(nameof(_httpContextAccessor.HttpContext)); } bool isHeadRequest = _httpContextAccessor.HttpContext.Request.Method == System.Net.WebRequestMethods.Http.Head; diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index 20bca731fd..e7fac50c64 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -132,7 +132,7 @@ namespace Jellyfin.Api.Helpers { if (_httpContextAccessor.HttpContext == null) { - throw new NullReferenceException(nameof(_httpContextAccessor.HttpContext)); + throw new ResourceNotFoundException(nameof(_httpContextAccessor.HttpContext)); } using var state = await StreamingHelpers.GetStreamingState( diff --git a/Jellyfin.Api/Helpers/HlsHelpers.cs b/Jellyfin.Api/Helpers/HlsHelpers.cs index 16fbac7ae2..707d1cd104 100644 --- a/Jellyfin.Api/Helpers/HlsHelpers.cs +++ b/Jellyfin.Api/Helpers/HlsHelpers.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; @@ -47,7 +48,7 @@ namespace Jellyfin.Api.Helpers var line = await reader.ReadLineAsync().ConfigureAwait(false); if (line == null) { - throw new NullReferenceException(nameof(line)); + throw new ResourceNotFoundException(nameof(line)); } if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1) diff --git a/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs b/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs index 65c03c710b..8bddf00d5f 100644 --- a/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs +++ b/Jellyfin.Api/Helpers/ProgressiveFileCopier.cs @@ -5,6 +5,7 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Models.PlaybackDtos; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Model.IO; @@ -92,7 +93,7 @@ namespace Jellyfin.Api.Helpers if (_path == null) { - throw new NullReferenceException(nameof(_path)); + throw new ResourceNotFoundException(nameof(_path)); } await using var inputStream = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, fileOptions); diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 1d102071c2..04b1077277 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -85,7 +85,7 @@ namespace Jellyfin.Api.Helpers streamingRequest.StreamOptions = ParseStreamOptions(httpRequest.Query); if (httpRequest.Path.Value == null) { - throw new NullReferenceException(nameof(httpRequest.Path)); + throw new ResourceNotFoundException(nameof(httpRequest.Path)); } var url = httpRequest.Path.Value.Split('.').Last(); diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index b72e33af38..4ec0c576aa 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -12,6 +12,7 @@ using Jellyfin.Api.Models.PlaybackDtos; using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; @@ -198,7 +199,7 @@ namespace Jellyfin.Api.Helpers var job = (TranscodingJobDto?)state; if (job == null) { - throw new NullReferenceException(nameof(job)); + throw new ResourceNotFoundException(nameof(job)); } if (!job.HasExited && job.Type != TranscodingJobType.Progressive) @@ -496,7 +497,7 @@ namespace Jellyfin.Api.Helpers var directory = Path.GetDirectoryName(outputPath); if (directory == null) { - throw new NullReferenceException(nameof(directory)); + throw new ResourceNotFoundException(nameof(directory)); } Directory.CreateDirectory(directory); diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index ad066c524a..1570d247b8 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using BlurHashSharp.SkiaSharp; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Extensions; using MediaBrowser.Model.Drawing; @@ -230,7 +231,7 @@ namespace Jellyfin.Drawing.Skia var directory = Path.GetDirectoryName(tempPath); if (directory == null) { - throw new NullReferenceException(nameof(directory)); + throw new ResourceNotFoundException(nameof(directory)); } Directory.CreateDirectory(directory); @@ -501,7 +502,7 @@ namespace Jellyfin.Drawing.Skia var outputDirectory = Path.GetDirectoryName(outputPath); if (outputDirectory == null) { - throw new NullReferenceException(nameof(outputDirectory)); + throw new ResourceNotFoundException(nameof(outputDirectory)); } Directory.CreateDirectory(outputDirectory); @@ -554,7 +555,7 @@ namespace Jellyfin.Drawing.Skia var directory = Path.GetDirectoryName(outputPath); if (directory == null) { - throw new NullReferenceException(nameof(directory)); + throw new ResourceNotFoundException(nameof(directory)); } Directory.CreateDirectory(directory); diff --git a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs index ced25524ee..465fb09cd8 100644 --- a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs @@ -62,7 +62,7 @@ namespace Jellyfin.Server.Implementations.Users if (spr == null) { - throw new NullReferenceException(nameof(spr)); + throw new ResourceNotFoundException(nameof(spr)); } if (spr.ExpirationDate < DateTime.UtcNow) diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index 11ad69d914..da04203e03 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using System.Threading; using System.Xml; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; @@ -130,7 +131,7 @@ namespace MediaBrowser.LocalMetadata.Savers var directory = Path.GetDirectoryName(path); if (directory == null) { - throw new NullReferenceException(nameof(directory)); + throw new ResourceNotFoundException(nameof(directory)); } Directory.CreateDirectory(directory); -- cgit v1.2.3 From 95ebb9a55ace6c544fa7fa15d0a255a5cee752a8 Mon Sep 17 00:00:00 2001 From: crobibero <cody@robibe.ro> Date: Fri, 13 Nov 2020 11:24:46 -0700 Subject: Use null coalescing when possible --- .../AppBase/ConfigurationHelper.cs | 6 +----- .../Cryptography/CryptographyProvider.cs | 7 +------ .../Session/WebSocketController.cs | 7 +------ Jellyfin.Api/Controllers/DynamicHlsController.cs | 13 ++----------- Jellyfin.Api/Controllers/HlsSegmentController.cs | 8 ++------ Jellyfin.Api/Controllers/ItemLookupController.cs | 13 ++----------- Jellyfin.Api/Controllers/RemoteImageController.cs | 14 ++------------ Jellyfin.Api/Controllers/VideoHlsController.cs | 7 +------ Jellyfin.Api/Helpers/HlsHelpers.cs | 7 ++----- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 14 ++------------ Jellyfin.Drawing.Skia/SkiaEncoder.cs | 21 +++------------------ .../Users/DefaultPasswordResetProvider.cs | 8 ++------ MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs | 7 +------ 13 files changed, 22 insertions(+), 110 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs index 8cca5cc779..b0a14f43d8 100644 --- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs +++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs @@ -50,11 +50,7 @@ namespace Emby.Server.Implementations.AppBase // If the file didn't exist before, or if something has changed, re-save if (buffer == null || !newBytes.AsSpan(0, newBytesLen).SequenceEqual(buffer)) { - var directory = Path.GetDirectoryName(path); - if (directory == null) - { - throw new ResourceNotFoundException(nameof(directory)); - } + var directory = Path.GetDirectoryName(path) ?? throw new ResourceNotFoundException(nameof(path)); Directory.CreateDirectory(directory); // Save it after load in case we got new items diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 8d7f73b3cf..42db183963 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -81,12 +81,7 @@ namespace Emby.Server.Implementations.Cryptography throw new CryptographicException($"Requested hash method is not supported: {hashMethod}"); } - using var h = HashAlgorithm.Create(hashMethod); - if (h == null) - { - throw new ResourceNotFoundException(nameof(h)); - } - + using var h = HashAlgorithm.Create(hashMethod) ?? throw new ResourceNotFoundException(nameof(hashMethod)); if (salt.Length == 0) { return h.ComputeHash(bytes); diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index 78f83e337f..5268ea1b93 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -58,12 +58,7 @@ namespace Emby.Server.Implementations.Session private void OnConnectionClosed(object? sender, EventArgs e) { - if (sender == null) - { - throw new ResourceNotFoundException(nameof(sender)); - } - - var connection = (IWebSocketConnection)sender; + var connection = sender as IWebSocketConnection ?? throw new ResourceNotFoundException(nameof(sender)); _logger.LogDebug("Removing websocket from session {Session}", _session.Id); _sockets.Remove(connection); connection.Closed -= OnConnectionClosed; diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 64bea999fa..783deebdce 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1348,11 +1348,7 @@ namespace Jellyfin.Api.Controllers var mapArgs = state.IsOutputVideo ? _encodingHelper.GetMapArgs(state) : string.Empty; - var directory = Path.GetDirectoryName(outputPath); - if (directory == null) - { - throw new ResourceNotFoundException(nameof(directory)); - } + var directory = Path.GetDirectoryName(outputPath) ?? throw new ResourceNotFoundException(nameof(outputPath)); var outputTsArg = Path.Combine(directory, Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request.SegmentContainer); @@ -1572,12 +1568,7 @@ namespace Jellyfin.Api.Controllers private string GetSegmentPath(StreamState state, string playlist, int index) { - var folder = Path.GetDirectoryName(playlist); - if (folder == null) - { - throw new ResourceNotFoundException(nameof(folder)); - } - + var folder = Path.GetDirectoryName(playlist) ?? throw new ResourceNotFoundException(nameof(playlist)); var filename = Path.GetFileNameWithoutExtension(playlist); return Path.Combine(folder, filename + index.ToString(CultureInfo.InvariantCulture) + GetSegmentFileExtension(state.Request.SegmentContainer)); diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs index fe1ffacb01..b9adcd3806 100644 --- a/Jellyfin.Api/Controllers/HlsSegmentController.cs +++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs @@ -135,12 +135,8 @@ namespace Jellyfin.Api.Controllers var playlistPath = _fileSystem.GetFilePaths(transcodeFolderPath) .FirstOrDefault(i => string.Equals(Path.GetExtension(i), ".m3u8", StringComparison.OrdinalIgnoreCase) - && i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1); - - if (playlistPath == null) - { - throw new ResourceNotFoundException(nameof(playlistPath)); - } + && i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1) + ?? throw new ResourceNotFoundException(nameof(transcodeFolderPath)); return GetFileResult(file, playlistPath); } diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs index b14840f80c..a0d9bfb543 100644 --- a/Jellyfin.Api/Controllers/ItemLookupController.cs +++ b/Jellyfin.Api/Controllers/ItemLookupController.cs @@ -342,12 +342,7 @@ namespace Jellyfin.Api.Controllers var ext = result.Content.Headers.ContentType.MediaType.Split('/')[^1]; var fullCachePath = GetFullCachePath(urlHash + "." + ext); - var directory = Path.GetDirectoryName(fullCachePath); - if (directory == null) - { - throw new ResourceNotFoundException(nameof(directory)); - } - + var directory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException(nameof(fullCachePath)); Directory.CreateDirectory(directory); using (var stream = result.Content) { @@ -362,11 +357,7 @@ namespace Jellyfin.Api.Controllers await stream.CopyToAsync(fileStream).ConfigureAwait(false); } - var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath); - if (pointerCacheDirectory == null) - { - throw new ResourceNotFoundException(nameof(pointerCacheDirectory)); - } + var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ResourceNotFoundException(nameof(pointerCachePath)); Directory.CreateDirectory(pointerCacheDirectory); await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath).ConfigureAwait(false); diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs index 2566f574c4..b4a9e5582d 100644 --- a/Jellyfin.Api/Controllers/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/RemoteImageController.cs @@ -257,22 +257,12 @@ namespace Jellyfin.Api.Controllers var ext = response.Content.Headers.ContentType.MediaType.Split('/').Last(); var fullCachePath = GetFullCachePath(urlHash + "." + ext); - var fullCacheDirectory = Path.GetDirectoryName(fullCachePath); - if (fullCacheDirectory == null) - { - throw new ResourceNotFoundException(nameof(fullCacheDirectory)); - } - + var fullCacheDirectory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException(nameof(fullCachePath)); Directory.CreateDirectory(fullCacheDirectory); await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); await response.Content.CopyToAsync(fileStream).ConfigureAwait(false); - var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath); - if (pointerCacheDirectory == null) - { - throw new ResourceNotFoundException(nameof(pointerCacheDirectory)); - } - + var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ResourceNotFoundException(nameof(pointerCachePath)); Directory.CreateDirectory(pointerCacheDirectory); await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath, CancellationToken.None) .ConfigureAwait(false); diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs index c47876beba..cc538f15e2 100644 --- a/Jellyfin.Api/Controllers/VideoHlsController.cs +++ b/Jellyfin.Api/Controllers/VideoHlsController.cs @@ -362,12 +362,7 @@ namespace Jellyfin.Api.Controllers var threads = _encodingHelper.GetNumberOfThreads(state, _encodingOptions, videoCodec); var inputModifier = _encodingHelper.GetInputModifier(state, _encodingOptions); var format = !string.IsNullOrWhiteSpace(state.Request.SegmentContainer) ? "." + state.Request.SegmentContainer : ".ts"; - var directory = Path.GetDirectoryName(outputPath); - if (directory == null) - { - throw new ResourceNotFoundException(nameof(directory)); - } - + var directory = Path.GetDirectoryName(outputPath) ?? throw new ResourceNotFoundException(nameof(outputPath)); var outputTsArg = Path.Combine(directory, Path.GetFileNameWithoutExtension(outputPath)) + "%d" + format; var segmentFormat = format.TrimStart('.'); diff --git a/Jellyfin.Api/Helpers/HlsHelpers.cs b/Jellyfin.Api/Helpers/HlsHelpers.cs index 707d1cd104..bcf0da319c 100644 --- a/Jellyfin.Api/Helpers/HlsHelpers.cs +++ b/Jellyfin.Api/Helpers/HlsHelpers.cs @@ -45,11 +45,8 @@ namespace Jellyfin.Api.Helpers while (!reader.EndOfStream) { - var line = await reader.ReadLineAsync().ConfigureAwait(false); - if (line == null) - { - throw new ResourceNotFoundException(nameof(line)); - } + var line = await reader.ReadLineAsync().ConfigureAwait(false) + ?? throw new ResourceNotFoundException(nameof(reader)); if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1) { diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 4ec0c576aa..8466241838 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -196,12 +196,7 @@ namespace Jellyfin.Api.Helpers /// <param name="state">The state.</param> private async void OnTranscodeKillTimerStopped(object? state) { - var job = (TranscodingJobDto?)state; - if (job == null) - { - throw new ResourceNotFoundException(nameof(job)); - } - + var job = state as TranscodingJobDto ?? throw new ResourceNotFoundException(nameof(state)); if (!job.HasExited && job.Type != TranscodingJobType.Progressive) { var timeSinceLastPing = (DateTime.UtcNow - job.LastPingDate).TotalMilliseconds; @@ -494,12 +489,7 @@ namespace Jellyfin.Api.Helpers CancellationTokenSource cancellationTokenSource, string? workingDirectory = null) { - var directory = Path.GetDirectoryName(outputPath); - if (directory == null) - { - throw new ResourceNotFoundException(nameof(directory)); - } - + var directory = Path.GetDirectoryName(outputPath) ?? throw new ResourceNotFoundException(nameof(outputPath)); Directory.CreateDirectory(directory); await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false); diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 1570d247b8..0c90d04a75 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -228,12 +228,7 @@ namespace Jellyfin.Drawing.Skia } var tempPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + Path.GetExtension(path)); - var directory = Path.GetDirectoryName(tempPath); - if (directory == null) - { - throw new ResourceNotFoundException(nameof(directory)); - } - + var directory = Path.GetDirectoryName(tempPath) ?? throw new ResourceNotFoundException(nameof(tempPath)); Directory.CreateDirectory(directory); File.Copy(path, tempPath, true); @@ -499,12 +494,7 @@ namespace Jellyfin.Drawing.Skia // If all we're doing is resizing then we can stop now if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator) { - var outputDirectory = Path.GetDirectoryName(outputPath); - if (outputDirectory == null) - { - throw new ResourceNotFoundException(nameof(outputDirectory)); - } - + var outputDirectory = Path.GetDirectoryName(outputPath) ?? throw new ResourceNotFoundException(nameof(outputPath)); Directory.CreateDirectory(outputDirectory); using var outputStream = new SKFileWStream(outputPath); using var pixmap = new SKPixmap(new SKImageInfo(width, height), resizedBitmap.GetPixels()); @@ -552,12 +542,7 @@ namespace Jellyfin.Drawing.Skia DrawIndicator(canvas, width, height, options); } - var directory = Path.GetDirectoryName(outputPath); - if (directory == null) - { - throw new ResourceNotFoundException(nameof(directory)); - } - + var directory = Path.GetDirectoryName(outputPath) ?? throw new ResourceNotFoundException(nameof(outputPath)); Directory.CreateDirectory(directory); using (var outputStream = new SKFileWStream(outputPath)) { diff --git a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs index 465fb09cd8..7f24904049 100644 --- a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs @@ -57,12 +57,8 @@ namespace Jellyfin.Server.Implementations.Users SerializablePasswordReset? spr; await using (var str = File.OpenRead(resetFile)) { - spr = await JsonSerializer.DeserializeAsync<SerializablePasswordReset>(str).ConfigureAwait(false); - } - - if (spr == null) - { - throw new ResourceNotFoundException(nameof(spr)); + spr = await JsonSerializer.DeserializeAsync<SerializablePasswordReset>(str).ConfigureAwait(false) + ?? throw new ResourceNotFoundException(nameof(spr)); } if (spr.ExpirationDate < DateTime.UtcNow) diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index da04203e03..2c9c9ccaa1 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs @@ -128,12 +128,7 @@ namespace MediaBrowser.LocalMetadata.Savers private void SaveToFile(Stream stream, string path) { - var directory = Path.GetDirectoryName(path); - if (directory == null) - { - throw new ResourceNotFoundException(nameof(directory)); - } - + var directory = Path.GetDirectoryName(path) ?? throw new ResourceNotFoundException(nameof(path)); Directory.CreateDirectory(directory); // On Windows, savint the file will fail if the file is hidden or readonly FileSystem.SetAttributes(path, false, false); -- cgit v1.2.3 From 73d2cb1c2a3a7cd1a30840a2b52921a3b81c6809 Mon Sep 17 00:00:00 2001 From: crobibero <cody@robibe.ro> Date: Fri, 13 Nov 2020 18:04:06 -0700 Subject: Updated based on review feedback --- Emby.Server.Implementations/AppBase/ConfigurationHelper.cs | 4 ++-- Emby.Server.Implementations/Cryptography/CryptographyProvider.cs | 2 +- Emby.Server.Implementations/Session/WebSocketController.cs | 2 +- Jellyfin.Api/Controllers/DynamicHlsController.cs | 4 ++-- Jellyfin.Api/Controllers/HlsSegmentController.cs | 2 +- Jellyfin.Api/Controllers/ItemLookupController.cs | 4 ++-- Jellyfin.Api/Controllers/RemoteImageController.cs | 4 ++-- Jellyfin.Api/Controllers/VideoHlsController.cs | 2 +- Jellyfin.Api/Helpers/HlsHelpers.cs | 9 ++++++--- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 4 ++-- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 6 +++--- .../Users/DefaultPasswordResetProvider.cs | 2 +- MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs | 2 +- 13 files changed, 25 insertions(+), 22 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs index 6c425c7fb4..77819c7649 100644 --- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs +++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs @@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.AppBase } catch (Exception) { - configuration = Activator.CreateInstance(type) ?? throw new ResourceNotFoundException(nameof(type)); + configuration = Activator.CreateInstance(type) ?? throw new ArgumentException($"Provided path ({type}) is not valid.", nameof(type)); } using var stream = new MemoryStream(buffer?.Length ?? 0); @@ -49,7 +49,7 @@ namespace Emby.Server.Implementations.AppBase // If the file didn't exist before, or if something has changed, re-save if (buffer == null || !newBytes.AsSpan(0, newBytesLen).SequenceEqual(buffer)) { - var directory = Path.GetDirectoryName(path) ?? throw new ResourceNotFoundException(nameof(path)); + var directory = Path.GetDirectoryName(path) ?? throw new ArgumentException($"Provided path ({path}) is not valid.", nameof(path)); Directory.CreateDirectory(directory); // Save it after load in case we got new items diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 42db183963..12a9e44e70 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -81,7 +81,7 @@ namespace Emby.Server.Implementations.Cryptography throw new CryptographicException($"Requested hash method is not supported: {hashMethod}"); } - using var h = HashAlgorithm.Create(hashMethod) ?? throw new ResourceNotFoundException(nameof(hashMethod)); + using var h = HashAlgorithm.Create(hashMethod) ?? throw new ResourceNotFoundException($"Unknown hash method: {hashMethod}."); if (salt.Length == 0) { return h.ComputeHash(bytes); diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index 5268ea1b93..f9c6a13c69 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -58,7 +58,7 @@ namespace Emby.Server.Implementations.Session private void OnConnectionClosed(object? sender, EventArgs e) { - var connection = sender as IWebSocketConnection ?? throw new ResourceNotFoundException(nameof(sender)); + var connection = sender as IWebSocketConnection ?? throw new ArgumentException($"{nameof(sender)} is not of type {nameof(IWebSocketConnection)}", nameof(sender)); _logger.LogDebug("Removing websocket from session {Session}", _session.Id); _sockets.Remove(connection); connection.Closed -= OnConnectionClosed; diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 783deebdce..6e59da798c 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1348,7 +1348,7 @@ namespace Jellyfin.Api.Controllers var mapArgs = state.IsOutputVideo ? _encodingHelper.GetMapArgs(state) : string.Empty; - var directory = Path.GetDirectoryName(outputPath) ?? throw new ResourceNotFoundException(nameof(outputPath)); + var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath)); var outputTsArg = Path.Combine(directory, Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request.SegmentContainer); @@ -1568,7 +1568,7 @@ namespace Jellyfin.Api.Controllers private string GetSegmentPath(StreamState state, string playlist, int index) { - var folder = Path.GetDirectoryName(playlist) ?? throw new ResourceNotFoundException(nameof(playlist)); + var folder = Path.GetDirectoryName(playlist) ?? throw new ArgumentException($"Provided path ({playlist}) is not valid.", nameof(playlist)); var filename = Path.GetFileNameWithoutExtension(playlist); return Path.Combine(folder, filename + index.ToString(CultureInfo.InvariantCulture) + GetSegmentFileExtension(state.Request.SegmentContainer)); diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs index b9adcd3806..3b75e8d431 100644 --- a/Jellyfin.Api/Controllers/HlsSegmentController.cs +++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs @@ -136,7 +136,7 @@ namespace Jellyfin.Api.Controllers .FirstOrDefault(i => string.Equals(Path.GetExtension(i), ".m3u8", StringComparison.OrdinalIgnoreCase) && i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1) - ?? throw new ResourceNotFoundException(nameof(transcodeFolderPath)); + ?? throw new ResourceNotFoundException($"Provided path ({transcodeFolderPath}) is not valid."); return GetFileResult(file, playlistPath); } diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs index a0d9bfb543..a7c1a63882 100644 --- a/Jellyfin.Api/Controllers/ItemLookupController.cs +++ b/Jellyfin.Api/Controllers/ItemLookupController.cs @@ -342,7 +342,7 @@ namespace Jellyfin.Api.Controllers var ext = result.Content.Headers.ContentType.MediaType.Split('/')[^1]; var fullCachePath = GetFullCachePath(urlHash + "." + ext); - var directory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException(nameof(fullCachePath)); + var directory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid."); Directory.CreateDirectory(directory); using (var stream = result.Content) { @@ -357,7 +357,7 @@ namespace Jellyfin.Api.Controllers await stream.CopyToAsync(fileStream).ConfigureAwait(false); } - var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ResourceNotFoundException(nameof(pointerCachePath)); + var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath)); Directory.CreateDirectory(pointerCacheDirectory); await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath).ConfigureAwait(false); diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs index b4a9e5582d..c2bfab175a 100644 --- a/Jellyfin.Api/Controllers/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/RemoteImageController.cs @@ -257,12 +257,12 @@ namespace Jellyfin.Api.Controllers var ext = response.Content.Headers.ContentType.MediaType.Split('/').Last(); var fullCachePath = GetFullCachePath(urlHash + "." + ext); - var fullCacheDirectory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException(nameof(fullCachePath)); + var fullCacheDirectory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid."); Directory.CreateDirectory(fullCacheDirectory); await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); await response.Content.CopyToAsync(fileStream).ConfigureAwait(false); - var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ResourceNotFoundException(nameof(pointerCachePath)); + var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath)); Directory.CreateDirectory(pointerCacheDirectory); await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath, CancellationToken.None) .ConfigureAwait(false); diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs index cc538f15e2..389dc8a088 100644 --- a/Jellyfin.Api/Controllers/VideoHlsController.cs +++ b/Jellyfin.Api/Controllers/VideoHlsController.cs @@ -362,7 +362,7 @@ namespace Jellyfin.Api.Controllers var threads = _encodingHelper.GetNumberOfThreads(state, _encodingOptions, videoCodec); var inputModifier = _encodingHelper.GetInputModifier(state, _encodingOptions); var format = !string.IsNullOrWhiteSpace(state.Request.SegmentContainer) ? "." + state.Request.SegmentContainer : ".ts"; - var directory = Path.GetDirectoryName(outputPath) ?? throw new ResourceNotFoundException(nameof(outputPath)); + var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath)); var outputTsArg = Path.Combine(directory, Path.GetFileNameWithoutExtension(outputPath)) + "%d" + format; var segmentFormat = format.TrimStart('.'); diff --git a/Jellyfin.Api/Helpers/HlsHelpers.cs b/Jellyfin.Api/Helpers/HlsHelpers.cs index bcf0da319c..7fd784806e 100644 --- a/Jellyfin.Api/Helpers/HlsHelpers.cs +++ b/Jellyfin.Api/Helpers/HlsHelpers.cs @@ -3,7 +3,6 @@ using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; @@ -45,8 +44,12 @@ namespace Jellyfin.Api.Helpers while (!reader.EndOfStream) { - var line = await reader.ReadLineAsync().ConfigureAwait(false) - ?? throw new ResourceNotFoundException(nameof(reader)); + var line = await reader.ReadLineAsync().ConfigureAwait(false); + if (line == null) + { + // Nothing currently in buffer. + continue; + } if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1) { diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 8466241838..168dc27a83 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -196,7 +196,7 @@ namespace Jellyfin.Api.Helpers /// <param name="state">The state.</param> private async void OnTranscodeKillTimerStopped(object? state) { - var job = state as TranscodingJobDto ?? throw new ResourceNotFoundException(nameof(state)); + var job = state as TranscodingJobDto ?? throw new ArgumentException($"{nameof(state)} is not of type {nameof(TranscodingJobDto)}", nameof(state)); if (!job.HasExited && job.Type != TranscodingJobType.Progressive) { var timeSinceLastPing = (DateTime.UtcNow - job.LastPingDate).TotalMilliseconds; @@ -489,7 +489,7 @@ namespace Jellyfin.Api.Helpers CancellationTokenSource cancellationTokenSource, string? workingDirectory = null) { - var directory = Path.GetDirectoryName(outputPath) ?? throw new ResourceNotFoundException(nameof(outputPath)); + var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath)); Directory.CreateDirectory(directory); await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false); diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 0c90d04a75..ee60748c71 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -228,7 +228,7 @@ namespace Jellyfin.Drawing.Skia } var tempPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + Path.GetExtension(path)); - var directory = Path.GetDirectoryName(tempPath) ?? throw new ResourceNotFoundException(nameof(tempPath)); + var directory = Path.GetDirectoryName(tempPath) ?? throw new ResourceNotFoundException($"Provided path ({tempPath}) is not valid."); Directory.CreateDirectory(directory); File.Copy(path, tempPath, true); @@ -494,7 +494,7 @@ namespace Jellyfin.Drawing.Skia // If all we're doing is resizing then we can stop now if (!hasBackgroundColor && !hasForegroundColor && blur == 0 && !hasIndicator) { - var outputDirectory = Path.GetDirectoryName(outputPath) ?? throw new ResourceNotFoundException(nameof(outputPath)); + var outputDirectory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath)); Directory.CreateDirectory(outputDirectory); using var outputStream = new SKFileWStream(outputPath); using var pixmap = new SKPixmap(new SKImageInfo(width, height), resizedBitmap.GetPixels()); @@ -542,7 +542,7 @@ namespace Jellyfin.Drawing.Skia DrawIndicator(canvas, width, height, options); } - var directory = Path.GetDirectoryName(outputPath) ?? throw new ResourceNotFoundException(nameof(outputPath)); + var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath)); Directory.CreateDirectory(directory); using (var outputStream = new SKFileWStream(outputPath)) { diff --git a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs index 7f24904049..e71d419e0c 100644 --- a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs @@ -58,7 +58,7 @@ namespace Jellyfin.Server.Implementations.Users await using (var str = File.OpenRead(resetFile)) { spr = await JsonSerializer.DeserializeAsync<SerializablePasswordReset>(str).ConfigureAwait(false) - ?? throw new ResourceNotFoundException(nameof(spr)); + ?? throw new ResourceNotFoundException($"Provided path ({resetFile}) is not valid."); } if (spr.ExpirationDate < DateTime.UtcNow) diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index 2c9c9ccaa1..396206658d 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs @@ -128,7 +128,7 @@ namespace MediaBrowser.LocalMetadata.Savers private void SaveToFile(Stream stream, string path) { - var directory = Path.GetDirectoryName(path) ?? throw new ResourceNotFoundException(nameof(path)); + var directory = Path.GetDirectoryName(path) ?? throw new ArgumentException($"Provided path ({path}) is not valid.", nameof(path)); Directory.CreateDirectory(directory); // On Windows, savint the file will fail if the file is hidden or readonly FileSystem.SetAttributes(path, false, false); -- cgit v1.2.3 From 5790db05ba9c28d3aba83bf437d29820f3d02be8 Mon Sep 17 00:00:00 2001 From: Patrick Barron <barronpm@gmail.com> Date: Thu, 19 Nov 2020 09:38:54 -0500 Subject: Clean up DeviceManager and don't store capabilities on disk --- .../Devices/DeviceManager.cs | 71 +++------------------- .../Session/SessionManager.cs | 24 +------- 2 files changed, 12 insertions(+), 83 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index f98c694c46..da5047d244 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -1,61 +1,38 @@ #pragma warning disable CS1591 using System; +using System.Collections.Concurrent; using System.Collections.Generic; -using System.Globalization; -using System.IO; using System.Linq; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using Jellyfin.Data.Events; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Security; using MediaBrowser.Model.Devices; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; -using Microsoft.Extensions.Caching.Memory; namespace Emby.Server.Implementations.Devices { public class DeviceManager : IDeviceManager { - private readonly IMemoryCache _memoryCache; - private readonly IJsonSerializer _json; private readonly IUserManager _userManager; - private readonly IServerConfigurationManager _config; private readonly IAuthenticationRepository _authRepo; - private readonly object _capabilitiesSyncLock = new object(); + private readonly ConcurrentDictionary<string, ClientCapabilities> _capabilitiesMap = new (); - public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated; - - public DeviceManager( - IAuthenticationRepository authRepo, - IJsonSerializer json, - IUserManager userManager, - IServerConfigurationManager config, - IMemoryCache memoryCache) + public DeviceManager(IAuthenticationRepository authRepo, IUserManager userManager) { - _json = json; _userManager = userManager; - _config = config; - _memoryCache = memoryCache; _authRepo = authRepo; } + public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated; + public void SaveCapabilities(string deviceId, ClientCapabilities capabilities) { - var path = Path.Combine(GetDevicePath(deviceId), "capabilities.json"); - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - lock (_capabilitiesSyncLock) - { - _memoryCache.Set(deviceId, capabilities); - _json.SerializeToFile(capabilities, path); - } + _capabilitiesMap[deviceId] = capabilities; } public void UpdateDeviceOptions(string deviceId, DeviceOptions options) @@ -72,32 +49,12 @@ namespace Emby.Server.Implementations.Devices public ClientCapabilities GetCapabilities(string id) { - if (_memoryCache.TryGetValue(id, out ClientCapabilities result)) - { - return result; - } - - lock (_capabilitiesSyncLock) - { - var path = Path.Combine(GetDevicePath(id), "capabilities.json"); - try - { - return _json.DeserializeFromFile<ClientCapabilities>(path) ?? new ClientCapabilities(); - } - catch - { - } - } - - return new ClientCapabilities(); + return _capabilitiesMap.TryGetValue(id, out ClientCapabilities result) + ? result + : new ClientCapabilities(); } public DeviceInfo GetDevice(string id) - { - return GetDevice(id, true); - } - - private DeviceInfo GetDevice(string id, bool includeCapabilities) { var session = _authRepo.Get(new AuthenticationInfoQuery { @@ -154,16 +111,6 @@ namespace Emby.Server.Implementations.Devices }; } - private string GetDevicesPath() - { - return Path.Combine(_config.ApplicationPaths.DataPath, "devices"); - } - - private string GetDevicePath(string id) - { - return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N", CultureInfo.InvariantCulture)); - } - public bool CanAccessDevice(User user, string deviceId) { if (user == null) diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 607b322f2e..afddfa856b 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -58,8 +58,7 @@ namespace Emby.Server.Implementations.Session /// <summary> /// The active connections. /// </summary> - private readonly ConcurrentDictionary<string, SessionInfo> _activeConnections = - new ConcurrentDictionary<string, SessionInfo>(StringComparer.OrdinalIgnoreCase); + private readonly ConcurrentDictionary<string, SessionInfo> _activeConnections = new (StringComparer.OrdinalIgnoreCase); private Timer _idleTimer; @@ -196,7 +195,7 @@ namespace Emby.Server.Implementations.Session { if (!string.IsNullOrEmpty(info.DeviceId)) { - var capabilities = GetSavedCapabilities(info.DeviceId); + var capabilities = _deviceManager.GetCapabilities(info.DeviceId); if (capabilities != null) { @@ -1677,27 +1676,10 @@ namespace Emby.Server.Implementations.Session SessionInfo = session }); - try - { - SaveCapabilities(session.DeviceId, capabilities); - } - catch (Exception ex) - { - _logger.LogError("Error saving device capabilities", ex); - } + _deviceManager.SaveCapabilities(session.DeviceId, capabilities); } } - private ClientCapabilities GetSavedCapabilities(string deviceId) - { - return _deviceManager.GetCapabilities(deviceId); - } - - private void SaveCapabilities(string deviceId, ClientCapabilities capabilities) - { - _deviceManager.SaveCapabilities(deviceId, capabilities); - } - /// <summary> /// Converts a BaseItem to a BaseItemInfo. /// </summary> -- cgit v1.2.3 From 5cd5a7d4cebe134d8256d4a1b6eadff760fbb2a7 Mon Sep 17 00:00:00 2001 From: Greenback <jimcartlidge@yahoo.co.uk> Date: Sat, 28 Nov 2020 00:25:20 +0000 Subject: Removed Lazy implementation. --- .../HttpServer/WebSocketManager.cs | 31 ++++++++++------------ .../Session/ISessionWebSocketListener.cs | 30 +++++++++++++++++++++ .../Session/SessionWebSocketListener.cs | 27 +++++++------------ .../ActivityLogWebSocketListener.cs | 2 +- .../IActivityLogWebSocketListener.cs | 10 +++++++ .../IScheduledTasksWebSocketListener.cs | 10 +++++++ .../ISessionInfoWebSocketListener.cs | 10 +++++++ .../ScheduledTasksWebSocketListener.cs | 8 +++--- .../SessionInfoWebSocketListener.cs | 2 +- Jellyfin.Server/CoreAppHost.cs | 13 +++------ .../Middleware/WebSocketHandlerMiddleware.cs | 11 +------- MediaBrowser.Controller/Net/IWebSocketManager.cs | 5 ---- 12 files changed, 95 insertions(+), 64 deletions(-) create mode 100644 Emby.Server.Implementations/Session/ISessionWebSocketListener.cs create mode 100644 Jellyfin.Api/WebSocketListeners/IActivityLogWebSocketListener.cs create mode 100644 Jellyfin.Api/WebSocketListeners/IScheduledTasksWebSocketListener.cs create mode 100644 Jellyfin.Api/WebSocketListeners/ISessionInfoWebSocketListener.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs index 71ece80a75..5518fb95aa 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs @@ -4,7 +4,9 @@ using System; using System.Collections.Generic; using System.Net.WebSockets; using System.Threading.Tasks; -using Jellyfin.Data.Events; +using Emby.Server.Implementations.Session; +using Jellyfin.Api.WebSocketListeners; +using MediaBrowser.Controller; using MediaBrowser.Controller.Net; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -13,24 +15,21 @@ namespace Emby.Server.Implementations.HttpServer { public class WebSocketManager : IWebSocketManager { - private readonly Lazy<IEnumerable<IWebSocketListener>> _webSocketListeners; + private readonly IServerApplicationHost _appHost; private readonly ILogger<WebSocketManager> _logger; private readonly ILoggerFactory _loggerFactory; - private bool _disposed = false; public WebSocketManager( - Lazy<IEnumerable<IWebSocketListener>> webSocketListeners, + IServerApplicationHost appHost, ILogger<WebSocketManager> logger, ILoggerFactory loggerFactory) { - _webSocketListeners = webSocketListeners; + _appHost = appHost; _logger = logger; _loggerFactory = loggerFactory; } - public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected; - /// <inheritdoc /> public async Task WebSocketRequestHandler(HttpContext context) { @@ -39,6 +38,8 @@ namespace Emby.Server.Implementations.HttpServer return; } + var listener = _appHost.Resolve<ISessionWebSocketListener>(); + try { _logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress); @@ -54,7 +55,7 @@ namespace Emby.Server.Implementations.HttpServer OnReceive = ProcessWebSocketMessageReceived }; - WebSocketConnected?.Invoke(this, new GenericEventArgs<IWebSocketConnection>(connection)); + listener?.ProcessWebSocketConnected(connection); await connection.ProcessAsync().ConfigureAwait(false); _logger.LogInformation("WS {IP} closed", context.Connection.RemoteIpAddress); @@ -80,16 +81,12 @@ namespace Emby.Server.Implementations.HttpServer return Task.CompletedTask; } - IEnumerable<Task> GetTasks() - { - var listeners = _webSocketListeners.Value; - foreach (var x in listeners) - { - yield return x.ProcessMessageAsync(result); - } - } + Parallel.Invoke( + () => _appHost.Resolve<IActivityLogWebSocketListener>(), + () => _appHost.Resolve<IScheduledTasksWebSocketListener>(), + () => _appHost.Resolve<ISessionInfoWebSocketListener>()); - return Task.WhenAll(GetTasks()); + return Task.CompletedTask; } } } diff --git a/Emby.Server.Implementations/Session/ISessionWebSocketListener.cs b/Emby.Server.Implementations/Session/ISessionWebSocketListener.cs new file mode 100644 index 0000000000..9b0b28e6ee --- /dev/null +++ b/Emby.Server.Implementations/Session/ISessionWebSocketListener.cs @@ -0,0 +1,30 @@ +namespace Emby.Server.Implementations.Session +{ + using System.Threading.Tasks; + using Jellyfin.Data.Events; + using MediaBrowser.Controller.Net; + + /// <summary> + /// Defines the <see cref="ISessionWebSocketListener" />. + /// </summary> + public interface ISessionWebSocketListener + { + /// <summary> + /// Runs processes due to a WebSocket connection event. + /// </summary> + /// <param name="websocketConnection">The <see cref="IWebSocketConnection"/> instance.</param> + void ProcessWebSocketConnected(IWebSocketConnection websocketConnection); + + /// <summary> + /// Disposes the object. + /// </summary> + void Dispose(); + + /// <summary> + /// Processes a message. + /// </summary> + /// <param name="message">The <see cref="WebSocketMessageInfo"/>.</param> + /// <returns>A <see cref="Task"/>.</returns> + Task ProcessMessageAsync(WebSocketMessageInfo message); + } +} diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index a5f8479537..8f81ee1948 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Session /// <summary> /// Class SessionWebSocketListener. /// </summary> - public sealed class SessionWebSocketListener : IWebSocketListener, IDisposable + public sealed class SessionWebSocketListener : ISessionWebSocketListener, IDisposable { /// <summary> /// The timeout in seconds after which a WebSocket is considered to be lost. @@ -45,15 +45,13 @@ namespace Emby.Server.Implementations.Session private readonly ILogger<SessionWebSocketListener> _logger; private readonly ILoggerFactory _loggerFactory; - private readonly IWebSocketManager _webSocketManager; - /// <summary> /// The KeepAlive cancellation token. /// </summary> private CancellationTokenSource _keepAliveCancellationToken; /// <summary> - /// Lock used for accesing the KeepAlive cancellation token. + /// Lock used for accessing the KeepAlive cancellation token. /// </summary> private readonly object _keepAliveLock = new object(); @@ -63,7 +61,7 @@ namespace Emby.Server.Implementations.Session private readonly HashSet<IWebSocketConnection> _webSockets = new HashSet<IWebSocketConnection>(); /// <summary> - /// Lock used for accesing the WebSockets watchlist. + /// Lock used for accessing the WebSockets watchlist. /// </summary> private readonly object _webSocketsLock = new object(); @@ -73,32 +71,28 @@ namespace Emby.Server.Implementations.Session /// <param name="logger">The logger.</param> /// <param name="sessionManager">The session manager.</param> /// <param name="loggerFactory">The logger factory.</param> - /// <param name="webSocketManager">The HTTP server.</param> public SessionWebSocketListener( ILogger<SessionWebSocketListener> logger, ISessionManager sessionManager, - ILoggerFactory loggerFactory, - IWebSocketManager webSocketManager) + ILoggerFactory loggerFactory) { _logger = logger; _sessionManager = sessionManager; _loggerFactory = loggerFactory; - _webSocketManager = webSocketManager; - - webSocketManager.WebSocketConnected += OnServerManagerWebSocketConnected; } - private async void OnServerManagerWebSocketConnected(object sender, GenericEventArgs<IWebSocketConnection> e) + /// <inheritdoc/> + public async void ProcessWebSocketConnected(IWebSocketConnection websocketConnection) { - var session = GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint.ToString()); + var session = GetSession(websocketConnection.QueryString, websocketConnection.RemoteEndPoint.ToString()); if (session != null) { - EnsureController(session, e.Argument); - await KeepAliveWebSocket(e.Argument).ConfigureAwait(false); + EnsureController(session, websocketConnection); + await KeepAliveWebSocket(websocketConnection).ConfigureAwait(false); } else { - _logger.LogWarning("Unable to determine session based on query string: {0}", e.Argument.QueryString); + _logger.LogWarning("Unable to determine session based on query string: {Querystring}", websocketConnection.QueryString); } } @@ -122,7 +116,6 @@ namespace Emby.Server.Implementations.Session /// <inheritdoc /> public void Dispose() { - _webSocketManager.WebSocketConnected -= OnServerManagerWebSocketConnected; StopKeepAlive(); } diff --git a/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs index 288e03fcff..3dc45b1459 100644 --- a/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs +++ b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs @@ -11,7 +11,7 @@ namespace Jellyfin.Api.WebSocketListeners /// <summary> /// Class SessionInfoWebSocketListener. /// </summary> - public class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<ActivityLogEntry[], WebSocketListenerState> + public class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<ActivityLogEntry[], WebSocketListenerState>, IActivityLogWebSocketListener { /// <summary> /// The _kernel. diff --git a/Jellyfin.Api/WebSocketListeners/IActivityLogWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/IActivityLogWebSocketListener.cs new file mode 100644 index 0000000000..b23022e71d --- /dev/null +++ b/Jellyfin.Api/WebSocketListeners/IActivityLogWebSocketListener.cs @@ -0,0 +1,10 @@ +#pragma warning disable CA1040 // Avoid empty interfaces +namespace Jellyfin.Api.WebSocketListeners +{ + /// <summary> + /// Defines the <see cref="IActivityLogWebSocketListener" />. + /// </summary> + public interface IActivityLogWebSocketListener + { + } +} diff --git a/Jellyfin.Api/WebSocketListeners/IScheduledTasksWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/IScheduledTasksWebSocketListener.cs new file mode 100644 index 0000000000..425d61dfa4 --- /dev/null +++ b/Jellyfin.Api/WebSocketListeners/IScheduledTasksWebSocketListener.cs @@ -0,0 +1,10 @@ +#pragma warning disable CA1040 // Avoid empty interfaces +namespace Jellyfin.Api.WebSocketListeners +{ + /// <summary> + /// Defines the <see cref="IScheduledTasksWebSocketListener" />. + /// </summary> + public interface IScheduledTasksWebSocketListener + { + } +} diff --git a/Jellyfin.Api/WebSocketListeners/ISessionInfoWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/ISessionInfoWebSocketListener.cs new file mode 100644 index 0000000000..33636dc06d --- /dev/null +++ b/Jellyfin.Api/WebSocketListeners/ISessionInfoWebSocketListener.cs @@ -0,0 +1,10 @@ +#pragma warning disable CA1040 // Avoid empty interfaces +namespace Jellyfin.Api.WebSocketListeners +{ + /// <summary> + /// Defines the <see cref="ISessionInfoWebSocketListener" />. + /// </summary> + public interface ISessionInfoWebSocketListener + { + } +} diff --git a/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs index 94df23e569..eafa9c71f0 100644 --- a/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs +++ b/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs @@ -12,7 +12,7 @@ namespace Jellyfin.Api.WebSocketListeners /// <summary> /// Class ScheduledTasksWebSocketListener. /// </summary> - public class ScheduledTasksWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<TaskInfo>, WebSocketListenerState> + public class ScheduledTasksWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<TaskInfo>, WebSocketListenerState>, IScheduledTasksWebSocketListener { /// <summary> /// Gets or sets the task manager. @@ -66,19 +66,19 @@ namespace Jellyfin.Api.WebSocketListeners private void OnTaskCompleted(object? sender, TaskCompletionEventArgs e) { - SendData(true); + SendData(true).GetAwaiter().GetResult(); e.Task.TaskProgress -= OnTaskProgress; } private void OnTaskExecuting(object? sender, GenericEventArgs<IScheduledTaskWorker> e) { - SendData(true); + SendData(true).GetAwaiter().GetResult(); e.Argument.TaskProgress += OnTaskProgress; } private void OnTaskProgress(object? sender, GenericEventArgs<double> e) { - SendData(false); + SendData(false).GetAwaiter().GetResult(); } } } diff --git a/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs index d996ac69f9..be701be45e 100644 --- a/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs +++ b/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs @@ -11,7 +11,7 @@ namespace Jellyfin.Api.WebSocketListeners /// <summary> /// Class SessionInfoWebSocketListener. /// </summary> - public class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<SessionInfo>, WebSocketListenerState> + public class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<SessionInfo>, WebSocketListenerState>, ISessionInfoWebSocketListener { private readonly ISessionManager _sessionManager; diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 4b44537a59..100f3619a6 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -11,7 +11,6 @@ using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations.Activity; using Jellyfin.Server.Implementations.Events; using Jellyfin.Server.Implementations.Users; -using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.BaseItemManager; using MediaBrowser.Controller.Drawing; @@ -82,14 +81,10 @@ namespace Jellyfin.Server ServiceCollection.AddSingleton<IUserManager, UserManager>(); ServiceCollection.AddSingleton<IDisplayPreferencesManager, DisplayPreferencesManager>(); - ServiceCollection.AddScoped<IWebSocketListener, ActivityLogWebSocketListener>(); - ServiceCollection.AddScoped<IWebSocketListener, ScheduledTasksWebSocketListener>(); - ServiceCollection.AddScoped<IWebSocketListener, SessionInfoWebSocketListener>(); - // This one has to be last as DI will select it for parameterization. - ServiceCollection.AddScoped<IWebSocketListener, SessionWebSocketListener>(); - - // TODO fix circular dependency on IWebSocketManager - ServiceCollection.AddScoped(serviceProvider => new Lazy<IEnumerable<IWebSocketListener>>(serviceProvider.GetRequiredService<IEnumerable<IWebSocketListener>>)); + ServiceCollection.AddScoped<IActivityLogWebSocketListener, ActivityLogWebSocketListener>(); + ServiceCollection.AddScoped<IScheduledTasksWebSocketListener, ScheduledTasksWebSocketListener>(); + ServiceCollection.AddScoped<ISessionInfoWebSocketListener, SessionInfoWebSocketListener>(); + ServiceCollection.AddScoped<ISessionWebSocketListener, SessionWebSocketListener>(); base.RegisterServices(); } diff --git a/Jellyfin.Server/Middleware/WebSocketHandlerMiddleware.cs b/Jellyfin.Server/Middleware/WebSocketHandlerMiddleware.cs index 53f32bebd1..ba0abfbdd4 100644 --- a/Jellyfin.Server/Middleware/WebSocketHandlerMiddleware.cs +++ b/Jellyfin.Server/Middleware/WebSocketHandlerMiddleware.cs @@ -25,19 +25,10 @@ namespace Jellyfin.Server.Middleware /// </summary> /// <param name="httpContext">The current HTTP context.</param> /// <param name="webSocketManager">The WebSocket connection manager.</param> - /// <param name="websocketListener">Session manager instance.</param> /// <returns>The async task.</returns> public async Task Invoke( HttpContext httpContext, - IWebSocketManager webSocketManager, -#pragma warning disable CA1801 -#pragma warning disable IDE0060 - // TODO: Workaround. see https://github.com/jellyfin/jellyfin/pull/3194 - // Do not remove this parameter. It uses DI to create a SessionWebSocketListener which is - // required for webSocketManager events. - IWebSocketListener websocketListener) -#pragma warning restore IDE0060 // Remove unused parameter -#pragma warning restore CA1801 + IWebSocketManager webSocketManager) { if (!httpContext.WebSockets.IsWebSocketRequest) { diff --git a/MediaBrowser.Controller/Net/IWebSocketManager.cs b/MediaBrowser.Controller/Net/IWebSocketManager.cs index ce74173e70..d3f31de7c8 100644 --- a/MediaBrowser.Controller/Net/IWebSocketManager.cs +++ b/MediaBrowser.Controller/Net/IWebSocketManager.cs @@ -11,11 +11,6 @@ namespace MediaBrowser.Controller.Net /// </summary> public interface IWebSocketManager { - /// <summary> - /// Occurs when [web socket connected]. - /// </summary> - event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected; - /// <summary> /// The HTTP request handler. /// </summary> -- cgit v1.2.3 From 1a0d8aef80a066fdefec4d757d5dcba0dfe0a03c Mon Sep 17 00:00:00 2001 From: cvium <clausvium@gmail.com> Date: Sat, 28 Nov 2020 09:50:16 +0100 Subject: Revert "Removed Lazy implementation." --- .../HttpServer/WebSocketManager.cs | 31 ++++++++++++---------- .../Session/ISessionWebSocketListener.cs | 30 --------------------- .../Session/SessionWebSocketListener.cs | 27 ++++++++++++------- .../ActivityLogWebSocketListener.cs | 2 +- .../IActivityLogWebSocketListener.cs | 10 ------- .../IScheduledTasksWebSocketListener.cs | 10 ------- .../ISessionInfoWebSocketListener.cs | 10 ------- .../ScheduledTasksWebSocketListener.cs | 8 +++--- .../SessionInfoWebSocketListener.cs | 2 +- Jellyfin.Server/CoreAppHost.cs | 13 ++++++--- .../Middleware/WebSocketHandlerMiddleware.cs | 11 +++++++- MediaBrowser.Controller/Net/IWebSocketManager.cs | 5 ++++ 12 files changed, 64 insertions(+), 95 deletions(-) delete mode 100644 Emby.Server.Implementations/Session/ISessionWebSocketListener.cs delete mode 100644 Jellyfin.Api/WebSocketListeners/IActivityLogWebSocketListener.cs delete mode 100644 Jellyfin.Api/WebSocketListeners/IScheduledTasksWebSocketListener.cs delete mode 100644 Jellyfin.Api/WebSocketListeners/ISessionInfoWebSocketListener.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs index 5518fb95aa..71ece80a75 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs @@ -4,9 +4,7 @@ using System; using System.Collections.Generic; using System.Net.WebSockets; using System.Threading.Tasks; -using Emby.Server.Implementations.Session; -using Jellyfin.Api.WebSocketListeners; -using MediaBrowser.Controller; +using Jellyfin.Data.Events; using MediaBrowser.Controller.Net; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -15,21 +13,24 @@ namespace Emby.Server.Implementations.HttpServer { public class WebSocketManager : IWebSocketManager { - private readonly IServerApplicationHost _appHost; + private readonly Lazy<IEnumerable<IWebSocketListener>> _webSocketListeners; private readonly ILogger<WebSocketManager> _logger; private readonly ILoggerFactory _loggerFactory; + private bool _disposed = false; public WebSocketManager( - IServerApplicationHost appHost, + Lazy<IEnumerable<IWebSocketListener>> webSocketListeners, ILogger<WebSocketManager> logger, ILoggerFactory loggerFactory) { - _appHost = appHost; + _webSocketListeners = webSocketListeners; _logger = logger; _loggerFactory = loggerFactory; } + public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected; + /// <inheritdoc /> public async Task WebSocketRequestHandler(HttpContext context) { @@ -38,8 +39,6 @@ namespace Emby.Server.Implementations.HttpServer return; } - var listener = _appHost.Resolve<ISessionWebSocketListener>(); - try { _logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress); @@ -55,7 +54,7 @@ namespace Emby.Server.Implementations.HttpServer OnReceive = ProcessWebSocketMessageReceived }; - listener?.ProcessWebSocketConnected(connection); + WebSocketConnected?.Invoke(this, new GenericEventArgs<IWebSocketConnection>(connection)); await connection.ProcessAsync().ConfigureAwait(false); _logger.LogInformation("WS {IP} closed", context.Connection.RemoteIpAddress); @@ -81,12 +80,16 @@ namespace Emby.Server.Implementations.HttpServer return Task.CompletedTask; } - Parallel.Invoke( - () => _appHost.Resolve<IActivityLogWebSocketListener>(), - () => _appHost.Resolve<IScheduledTasksWebSocketListener>(), - () => _appHost.Resolve<ISessionInfoWebSocketListener>()); + IEnumerable<Task> GetTasks() + { + var listeners = _webSocketListeners.Value; + foreach (var x in listeners) + { + yield return x.ProcessMessageAsync(result); + } + } - return Task.CompletedTask; + return Task.WhenAll(GetTasks()); } } } diff --git a/Emby.Server.Implementations/Session/ISessionWebSocketListener.cs b/Emby.Server.Implementations/Session/ISessionWebSocketListener.cs deleted file mode 100644 index 9b0b28e6ee..0000000000 --- a/Emby.Server.Implementations/Session/ISessionWebSocketListener.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace Emby.Server.Implementations.Session -{ - using System.Threading.Tasks; - using Jellyfin.Data.Events; - using MediaBrowser.Controller.Net; - - /// <summary> - /// Defines the <see cref="ISessionWebSocketListener" />. - /// </summary> - public interface ISessionWebSocketListener - { - /// <summary> - /// Runs processes due to a WebSocket connection event. - /// </summary> - /// <param name="websocketConnection">The <see cref="IWebSocketConnection"/> instance.</param> - void ProcessWebSocketConnected(IWebSocketConnection websocketConnection); - - /// <summary> - /// Disposes the object. - /// </summary> - void Dispose(); - - /// <summary> - /// Processes a message. - /// </summary> - /// <param name="message">The <see cref="WebSocketMessageInfo"/>.</param> - /// <returns>A <see cref="Task"/>.</returns> - Task ProcessMessageAsync(WebSocketMessageInfo message); - } -} diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 8f81ee1948..a5f8479537 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Session /// <summary> /// Class SessionWebSocketListener. /// </summary> - public sealed class SessionWebSocketListener : ISessionWebSocketListener, IDisposable + public sealed class SessionWebSocketListener : IWebSocketListener, IDisposable { /// <summary> /// The timeout in seconds after which a WebSocket is considered to be lost. @@ -45,13 +45,15 @@ namespace Emby.Server.Implementations.Session private readonly ILogger<SessionWebSocketListener> _logger; private readonly ILoggerFactory _loggerFactory; + private readonly IWebSocketManager _webSocketManager; + /// <summary> /// The KeepAlive cancellation token. /// </summary> private CancellationTokenSource _keepAliveCancellationToken; /// <summary> - /// Lock used for accessing the KeepAlive cancellation token. + /// Lock used for accesing the KeepAlive cancellation token. /// </summary> private readonly object _keepAliveLock = new object(); @@ -61,7 +63,7 @@ namespace Emby.Server.Implementations.Session private readonly HashSet<IWebSocketConnection> _webSockets = new HashSet<IWebSocketConnection>(); /// <summary> - /// Lock used for accessing the WebSockets watchlist. + /// Lock used for accesing the WebSockets watchlist. /// </summary> private readonly object _webSocketsLock = new object(); @@ -71,28 +73,32 @@ namespace Emby.Server.Implementations.Session /// <param name="logger">The logger.</param> /// <param name="sessionManager">The session manager.</param> /// <param name="loggerFactory">The logger factory.</param> + /// <param name="webSocketManager">The HTTP server.</param> public SessionWebSocketListener( ILogger<SessionWebSocketListener> logger, ISessionManager sessionManager, - ILoggerFactory loggerFactory) + ILoggerFactory loggerFactory, + IWebSocketManager webSocketManager) { _logger = logger; _sessionManager = sessionManager; _loggerFactory = loggerFactory; + _webSocketManager = webSocketManager; + + webSocketManager.WebSocketConnected += OnServerManagerWebSocketConnected; } - /// <inheritdoc/> - public async void ProcessWebSocketConnected(IWebSocketConnection websocketConnection) + private async void OnServerManagerWebSocketConnected(object sender, GenericEventArgs<IWebSocketConnection> e) { - var session = GetSession(websocketConnection.QueryString, websocketConnection.RemoteEndPoint.ToString()); + var session = GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint.ToString()); if (session != null) { - EnsureController(session, websocketConnection); - await KeepAliveWebSocket(websocketConnection).ConfigureAwait(false); + EnsureController(session, e.Argument); + await KeepAliveWebSocket(e.Argument).ConfigureAwait(false); } else { - _logger.LogWarning("Unable to determine session based on query string: {Querystring}", websocketConnection.QueryString); + _logger.LogWarning("Unable to determine session based on query string: {0}", e.Argument.QueryString); } } @@ -116,6 +122,7 @@ namespace Emby.Server.Implementations.Session /// <inheritdoc /> public void Dispose() { + _webSocketManager.WebSocketConnected -= OnServerManagerWebSocketConnected; StopKeepAlive(); } diff --git a/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs index 3dc45b1459..288e03fcff 100644 --- a/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs +++ b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs @@ -11,7 +11,7 @@ namespace Jellyfin.Api.WebSocketListeners /// <summary> /// Class SessionInfoWebSocketListener. /// </summary> - public class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<ActivityLogEntry[], WebSocketListenerState>, IActivityLogWebSocketListener + public class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<ActivityLogEntry[], WebSocketListenerState> { /// <summary> /// The _kernel. diff --git a/Jellyfin.Api/WebSocketListeners/IActivityLogWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/IActivityLogWebSocketListener.cs deleted file mode 100644 index b23022e71d..0000000000 --- a/Jellyfin.Api/WebSocketListeners/IActivityLogWebSocketListener.cs +++ /dev/null @@ -1,10 +0,0 @@ -#pragma warning disable CA1040 // Avoid empty interfaces -namespace Jellyfin.Api.WebSocketListeners -{ - /// <summary> - /// Defines the <see cref="IActivityLogWebSocketListener" />. - /// </summary> - public interface IActivityLogWebSocketListener - { - } -} diff --git a/Jellyfin.Api/WebSocketListeners/IScheduledTasksWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/IScheduledTasksWebSocketListener.cs deleted file mode 100644 index 425d61dfa4..0000000000 --- a/Jellyfin.Api/WebSocketListeners/IScheduledTasksWebSocketListener.cs +++ /dev/null @@ -1,10 +0,0 @@ -#pragma warning disable CA1040 // Avoid empty interfaces -namespace Jellyfin.Api.WebSocketListeners -{ - /// <summary> - /// Defines the <see cref="IScheduledTasksWebSocketListener" />. - /// </summary> - public interface IScheduledTasksWebSocketListener - { - } -} diff --git a/Jellyfin.Api/WebSocketListeners/ISessionInfoWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/ISessionInfoWebSocketListener.cs deleted file mode 100644 index 33636dc06d..0000000000 --- a/Jellyfin.Api/WebSocketListeners/ISessionInfoWebSocketListener.cs +++ /dev/null @@ -1,10 +0,0 @@ -#pragma warning disable CA1040 // Avoid empty interfaces -namespace Jellyfin.Api.WebSocketListeners -{ - /// <summary> - /// Defines the <see cref="ISessionInfoWebSocketListener" />. - /// </summary> - public interface ISessionInfoWebSocketListener - { - } -} diff --git a/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs index eafa9c71f0..94df23e569 100644 --- a/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs +++ b/Jellyfin.Api/WebSocketListeners/ScheduledTasksWebSocketListener.cs @@ -12,7 +12,7 @@ namespace Jellyfin.Api.WebSocketListeners /// <summary> /// Class ScheduledTasksWebSocketListener. /// </summary> - public class ScheduledTasksWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<TaskInfo>, WebSocketListenerState>, IScheduledTasksWebSocketListener + public class ScheduledTasksWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<TaskInfo>, WebSocketListenerState> { /// <summary> /// Gets or sets the task manager. @@ -66,19 +66,19 @@ namespace Jellyfin.Api.WebSocketListeners private void OnTaskCompleted(object? sender, TaskCompletionEventArgs e) { - SendData(true).GetAwaiter().GetResult(); + SendData(true); e.Task.TaskProgress -= OnTaskProgress; } private void OnTaskExecuting(object? sender, GenericEventArgs<IScheduledTaskWorker> e) { - SendData(true).GetAwaiter().GetResult(); + SendData(true); e.Argument.TaskProgress += OnTaskProgress; } private void OnTaskProgress(object? sender, GenericEventArgs<double> e) { - SendData(false).GetAwaiter().GetResult(); + SendData(false); } } } diff --git a/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs index be701be45e..d996ac69f9 100644 --- a/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs +++ b/Jellyfin.Api/WebSocketListeners/SessionInfoWebSocketListener.cs @@ -11,7 +11,7 @@ namespace Jellyfin.Api.WebSocketListeners /// <summary> /// Class SessionInfoWebSocketListener. /// </summary> - public class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<SessionInfo>, WebSocketListenerState>, ISessionInfoWebSocketListener + public class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnumerable<SessionInfo>, WebSocketListenerState> { private readonly ISessionManager _sessionManager; diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 100f3619a6..4b44537a59 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -11,6 +11,7 @@ using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations.Activity; using Jellyfin.Server.Implementations.Events; using Jellyfin.Server.Implementations.Users; +using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.BaseItemManager; using MediaBrowser.Controller.Drawing; @@ -81,10 +82,14 @@ namespace Jellyfin.Server ServiceCollection.AddSingleton<IUserManager, UserManager>(); ServiceCollection.AddSingleton<IDisplayPreferencesManager, DisplayPreferencesManager>(); - ServiceCollection.AddScoped<IActivityLogWebSocketListener, ActivityLogWebSocketListener>(); - ServiceCollection.AddScoped<IScheduledTasksWebSocketListener, ScheduledTasksWebSocketListener>(); - ServiceCollection.AddScoped<ISessionInfoWebSocketListener, SessionInfoWebSocketListener>(); - ServiceCollection.AddScoped<ISessionWebSocketListener, SessionWebSocketListener>(); + ServiceCollection.AddScoped<IWebSocketListener, ActivityLogWebSocketListener>(); + ServiceCollection.AddScoped<IWebSocketListener, ScheduledTasksWebSocketListener>(); + ServiceCollection.AddScoped<IWebSocketListener, SessionInfoWebSocketListener>(); + // This one has to be last as DI will select it for parameterization. + ServiceCollection.AddScoped<IWebSocketListener, SessionWebSocketListener>(); + + // TODO fix circular dependency on IWebSocketManager + ServiceCollection.AddScoped(serviceProvider => new Lazy<IEnumerable<IWebSocketListener>>(serviceProvider.GetRequiredService<IEnumerable<IWebSocketListener>>)); base.RegisterServices(); } diff --git a/Jellyfin.Server/Middleware/WebSocketHandlerMiddleware.cs b/Jellyfin.Server/Middleware/WebSocketHandlerMiddleware.cs index ba0abfbdd4..53f32bebd1 100644 --- a/Jellyfin.Server/Middleware/WebSocketHandlerMiddleware.cs +++ b/Jellyfin.Server/Middleware/WebSocketHandlerMiddleware.cs @@ -25,10 +25,19 @@ namespace Jellyfin.Server.Middleware /// </summary> /// <param name="httpContext">The current HTTP context.</param> /// <param name="webSocketManager">The WebSocket connection manager.</param> + /// <param name="websocketListener">Session manager instance.</param> /// <returns>The async task.</returns> public async Task Invoke( HttpContext httpContext, - IWebSocketManager webSocketManager) + IWebSocketManager webSocketManager, +#pragma warning disable CA1801 +#pragma warning disable IDE0060 + // TODO: Workaround. see https://github.com/jellyfin/jellyfin/pull/3194 + // Do not remove this parameter. It uses DI to create a SessionWebSocketListener which is + // required for webSocketManager events. + IWebSocketListener websocketListener) +#pragma warning restore IDE0060 // Remove unused parameter +#pragma warning restore CA1801 { if (!httpContext.WebSockets.IsWebSocketRequest) { diff --git a/MediaBrowser.Controller/Net/IWebSocketManager.cs b/MediaBrowser.Controller/Net/IWebSocketManager.cs index d3f31de7c8..ce74173e70 100644 --- a/MediaBrowser.Controller/Net/IWebSocketManager.cs +++ b/MediaBrowser.Controller/Net/IWebSocketManager.cs @@ -11,6 +11,11 @@ namespace MediaBrowser.Controller.Net /// </summary> public interface IWebSocketManager { + /// <summary> + /// Occurs when [web socket connected]. + /// </summary> + event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected; + /// <summary> /// The HTTP request handler. /// </summary> -- cgit v1.2.3 From 65e6211c035c2269584220f1a3dcc0bb37374e01 Mon Sep 17 00:00:00 2001 From: cvium <clausvium@gmail.com> Date: Sat, 28 Nov 2020 11:21:53 +0100 Subject: Remove circular dependency between websocket listeners and manager --- .../HttpServer/WebSocketManager.cs | 41 ++++------- .../Session/SessionWebSocketListener.cs | 80 ++++++++++------------ .../ActivityLogWebSocketListener.cs | 2 +- Jellyfin.Server/CoreAppHost.cs | 12 ++-- .../Net/BasePeriodicWebSocketListener.cs | 2 + MediaBrowser.Controller/Net/IWebSocketListener.cs | 9 ++- MediaBrowser.Controller/Net/IWebSocketManager.cs | 8 --- 7 files changed, 67 insertions(+), 87 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs index 71ece80a75..d6cf6233e4 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs @@ -2,9 +2,9 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net.WebSockets; using System.Threading.Tasks; -using Jellyfin.Data.Events; using MediaBrowser.Controller.Net; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -13,32 +13,23 @@ namespace Emby.Server.Implementations.HttpServer { public class WebSocketManager : IWebSocketManager { - private readonly Lazy<IEnumerable<IWebSocketListener>> _webSocketListeners; + private readonly IWebSocketListener[] _webSocketListeners; private readonly ILogger<WebSocketManager> _logger; private readonly ILoggerFactory _loggerFactory; - private bool _disposed = false; - public WebSocketManager( - Lazy<IEnumerable<IWebSocketListener>> webSocketListeners, + IEnumerable<IWebSocketListener> webSocketListeners, ILogger<WebSocketManager> logger, ILoggerFactory loggerFactory) { - _webSocketListeners = webSocketListeners; + _webSocketListeners = webSocketListeners.ToArray(); _logger = logger; _loggerFactory = loggerFactory; } - public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected; - /// <inheritdoc /> public async Task WebSocketRequestHandler(HttpContext context) { - if (_disposed) - { - return; - } - try { _logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress); @@ -54,7 +45,13 @@ namespace Emby.Server.Implementations.HttpServer OnReceive = ProcessWebSocketMessageReceived }; - WebSocketConnected?.Invoke(this, new GenericEventArgs<IWebSocketConnection>(connection)); + var tasks = new Task[_webSocketListeners.Length]; + for (var i = 0; i < _webSocketListeners.Length; ++i) + { + tasks[i] = _webSocketListeners[i].ProcessWebSocketConnectedAsync(connection); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); await connection.ProcessAsync().ConfigureAwait(false); _logger.LogInformation("WS {IP} closed", context.Connection.RemoteIpAddress); @@ -75,21 +72,13 @@ namespace Emby.Server.Implementations.HttpServer /// <param name="result">The result.</param> private Task ProcessWebSocketMessageReceived(WebSocketMessageInfo result) { - if (_disposed) - { - return Task.CompletedTask; - } - - IEnumerable<Task> GetTasks() + var tasks = new Task[_webSocketListeners.Length]; + for (var i = 0; i < _webSocketListeners.Length; ++i) { - var listeners = _webSocketListeners.Value; - foreach (var x in listeners) - { - yield return x.ProcessMessageAsync(result); - } + tasks[i] = _webSocketListeners[i].ProcessMessageAsync(result); } - return Task.WhenAll(GetTasks()); + return Task.WhenAll(tasks); } } } diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index a5f8479537..169eaefd8b 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Data.Events; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Net; @@ -22,50 +21,48 @@ namespace Emby.Server.Implementations.Session /// <summary> /// The timeout in seconds after which a WebSocket is considered to be lost. /// </summary> - public const int WebSocketLostTimeout = 60; + private const int WebSocketLostTimeout = 60; /// <summary> /// The keep-alive interval factor; controls how often the watcher will check on the status of the WebSockets. /// </summary> - public const float IntervalFactor = 0.2f; + private const float IntervalFactor = 0.2f; /// <summary> /// The ForceKeepAlive factor; controls when a ForceKeepAlive is sent. /// </summary> - public const float ForceKeepAliveFactor = 0.75f; + private const float ForceKeepAliveFactor = 0.75f; /// <summary> - /// The _session manager. + /// Lock used for accesing the KeepAlive cancellation token. /// </summary> - private readonly ISessionManager _sessionManager; + private readonly object _keepAliveLock = new object(); /// <summary> - /// The _logger. + /// The WebSocket watchlist. /// </summary> - private readonly ILogger<SessionWebSocketListener> _logger; - private readonly ILoggerFactory _loggerFactory; - - private readonly IWebSocketManager _webSocketManager; + private readonly HashSet<IWebSocketConnection> _webSockets = new HashSet<IWebSocketConnection>(); /// <summary> - /// The KeepAlive cancellation token. + /// Lock used for accessing the WebSockets watchlist. /// </summary> - private CancellationTokenSource _keepAliveCancellationToken; + private readonly object _webSocketsLock = new object(); /// <summary> - /// Lock used for accesing the KeepAlive cancellation token. + /// The _session manager. /// </summary> - private readonly object _keepAliveLock = new object(); + private readonly ISessionManager _sessionManager; /// <summary> - /// The WebSocket watchlist. + /// The _logger. /// </summary> - private readonly HashSet<IWebSocketConnection> _webSockets = new HashSet<IWebSocketConnection>(); + private readonly ILogger<SessionWebSocketListener> _logger; + private readonly ILoggerFactory _loggerFactory; /// <summary> - /// Lock used for accesing the WebSockets watchlist. + /// The KeepAlive cancellation token. /// </summary> - private readonly object _webSocketsLock = new object(); + private CancellationTokenSource _keepAliveCancellationToken; /// <summary> /// Initializes a new instance of the <see cref="SessionWebSocketListener" /> class. @@ -73,32 +70,42 @@ namespace Emby.Server.Implementations.Session /// <param name="logger">The logger.</param> /// <param name="sessionManager">The session manager.</param> /// <param name="loggerFactory">The logger factory.</param> - /// <param name="webSocketManager">The HTTP server.</param> public SessionWebSocketListener( ILogger<SessionWebSocketListener> logger, ISessionManager sessionManager, - ILoggerFactory loggerFactory, - IWebSocketManager webSocketManager) + ILoggerFactory loggerFactory) { _logger = logger; _sessionManager = sessionManager; _loggerFactory = loggerFactory; - _webSocketManager = webSocketManager; + } - webSocketManager.WebSocketConnected += OnServerManagerWebSocketConnected; + /// <inheritdoc /> + public void Dispose() + { + StopKeepAlive(); } - private async void OnServerManagerWebSocketConnected(object sender, GenericEventArgs<IWebSocketConnection> e) + /// <summary> + /// Processes the message. + /// </summary> + /// <param name="message">The message.</param> + /// <returns>Task.</returns> + public Task ProcessMessageAsync(WebSocketMessageInfo message) + => Task.CompletedTask; + + /// <inheritdoc /> + public async Task ProcessWebSocketConnectedAsync(IWebSocketConnection connection) { - var session = GetSession(e.Argument.QueryString, e.Argument.RemoteEndPoint.ToString()); + var session = GetSession(connection.QueryString, connection.RemoteEndPoint.ToString()); if (session != null) { - EnsureController(session, e.Argument); - await KeepAliveWebSocket(e.Argument).ConfigureAwait(false); + EnsureController(session, connection); + await KeepAliveWebSocket(connection).ConfigureAwait(false); } else { - _logger.LogWarning("Unable to determine session based on query string: {0}", e.Argument.QueryString); + _logger.LogWarning("Unable to determine session based on query string: {0}", connection.QueryString); } } @@ -119,21 +126,6 @@ namespace Emby.Server.Implementations.Session return _sessionManager.GetSessionByAuthenticationToken(token, deviceId, remoteEndpoint); } - /// <inheritdoc /> - public void Dispose() - { - _webSocketManager.WebSocketConnected -= OnServerManagerWebSocketConnected; - StopKeepAlive(); - } - - /// <summary> - /// Processes the message. - /// </summary> - /// <param name="message">The message.</param> - /// <returns>Task.</returns> - public Task ProcessMessageAsync(WebSocketMessageInfo message) - => Task.CompletedTask; - private void EnsureController(SessionInfo session, IWebSocketConnection connection) { var controllerInfo = session.EnsureController<WebSocketController>( diff --git a/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs index ce54651166..288e03fcff 100644 --- a/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs +++ b/Jellyfin.Api/WebSocketListeners/ActivityLogWebSocketListener.cs @@ -58,7 +58,7 @@ namespace Jellyfin.Api.WebSocketListeners private void OnEntryCreated(object? sender, GenericEventArgs<ActivityLogEntry> e) { - SendData(true); + SendData(true).GetAwaiter().GetResult(); } } } diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 78f596a5c9..b76aa5e141 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -82,13 +82,11 @@ namespace Jellyfin.Server ServiceCollection.AddSingleton<IUserManager, UserManager>(); ServiceCollection.AddSingleton<IDisplayPreferencesManager, DisplayPreferencesManager>(); - ServiceCollection.AddScoped<IWebSocketListener, SessionWebSocketListener>(); - ServiceCollection.AddScoped<IWebSocketListener, ActivityLogWebSocketListener>(); - ServiceCollection.AddScoped<IWebSocketListener, ScheduledTasksWebSocketListener>(); - ServiceCollection.AddScoped<IWebSocketListener, SessionInfoWebSocketListener>(); - - // TODO fix circular dependency on IWebSocketManager - ServiceCollection.AddScoped(serviceProvider => new Lazy<IEnumerable<IWebSocketListener>>(serviceProvider.GetRequiredService<IEnumerable<IWebSocketListener>>)); + // TODO search the assemblies instead of adding them manually? + ServiceCollection.AddSingleton<IWebSocketListener, SessionWebSocketListener>(); + ServiceCollection.AddSingleton<IWebSocketListener, ActivityLogWebSocketListener>(); + ServiceCollection.AddSingleton<IWebSocketListener, ScheduledTasksWebSocketListener>(); + ServiceCollection.AddSingleton<IWebSocketListener, SessionInfoWebSocketListener>(); base.RegisterServices(); } diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index 28227603b2..bbcfe77753 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -92,6 +92,8 @@ namespace MediaBrowser.Controller.Net return Task.CompletedTask; } + public Task ProcessWebSocketConnectedAsync(IWebSocketConnection connection) => Task.CompletedTask; + /// <summary> /// Starts sending messages over a web socket. /// </summary> diff --git a/MediaBrowser.Controller/Net/IWebSocketListener.cs b/MediaBrowser.Controller/Net/IWebSocketListener.cs index 7250a57b0a..f1a75d5180 100644 --- a/MediaBrowser.Controller/Net/IWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/IWebSocketListener.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Controller.Net { /// <summary> - ///This is an interface for listening to messages coming through a web socket connection. + /// Interface for listening to messages coming through a web socket connection. /// </summary> public interface IWebSocketListener { @@ -13,5 +13,12 @@ namespace MediaBrowser.Controller.Net /// <param name="message">The message.</param> /// <returns>Task.</returns> Task ProcessMessageAsync(WebSocketMessageInfo message); + + /// <summary> + /// Processes a new web socket connection. + /// </summary> + /// <param name="connection">An instance of the <see cref="IWebSocketConnection"/> interface.</param> + /// <returns>Task.</returns> + Task ProcessWebSocketConnectedAsync(IWebSocketConnection connection); } } diff --git a/MediaBrowser.Controller/Net/IWebSocketManager.cs b/MediaBrowser.Controller/Net/IWebSocketManager.cs index ce74173e70..bb0ae83bea 100644 --- a/MediaBrowser.Controller/Net/IWebSocketManager.cs +++ b/MediaBrowser.Controller/Net/IWebSocketManager.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; using System.Threading.Tasks; -using Jellyfin.Data.Events; using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net @@ -11,11 +8,6 @@ namespace MediaBrowser.Controller.Net /// </summary> public interface IWebSocketManager { - /// <summary> - /// Occurs when [web socket connected]. - /// </summary> - event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected; - /// <summary> /// The HTTP request handler. /// </summary> -- cgit v1.2.3 From 0825ce687decbe1ad6bbc3b3e796d3763f3c4447 Mon Sep 17 00:00:00 2001 From: Ionut Andrei Oanca <oancaionutandrei@gmail.com> Date: Mon, 7 Dec 2020 01:04:48 +0100 Subject: Add SessionControllerConnected event --- Emby.Server.Implementations/Session/SessionManager.cs | 16 ++++++++++++++++ .../Session/SessionWebSocketListener.cs | 2 ++ Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs | 6 +++--- MediaBrowser.Controller/Session/ISessionManager.cs | 11 +++++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index b3965fccad..885f65c64e 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -128,6 +128,9 @@ namespace Emby.Server.Implementations.Session /// <inheritdoc /> public event EventHandler<SessionEventArgs> SessionActivity; + /// <inheritdoc /> + public event EventHandler<SessionEventArgs> SessionControllerConnected; + /// <summary> /// Gets all connections. /// </summary> @@ -312,6 +315,19 @@ namespace Emby.Server.Implementations.Session return session; } + /// <inheritdoc /> + public void OnSessionControllerConnected(SessionInfo info) + { + EventHelper.QueueEventIfNotNull( + SessionControllerConnected, + this, + new SessionEventArgs + { + SessionInfo = info + }, + _logger); + } + /// <inheritdoc /> public void CloseIfNeeded(SessionInfo session) { diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 169eaefd8b..39c369a01d 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -133,6 +133,8 @@ namespace Emby.Server.Implementations.Session var controller = (WebSocketController)controllerInfo.Item1; controller.AddWebSocket(connection); + + _sessionManager.OnSessionControllerConnected(session); } /// <summary> diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index 348213ee15..1d87036a27 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -81,7 +81,7 @@ namespace Emby.Server.Implementations.SyncPlay _sessionManager = sessionManager; _libraryManager = libraryManager; _logger = loggerFactory.CreateLogger<SyncPlayManager>(); - _sessionManager.SessionStarted += OnSessionManagerSessionStarted; + _sessionManager.SessionControllerConnected += OnSessionControllerConnected; } /// <inheritdoc /> @@ -329,11 +329,11 @@ namespace Emby.Server.Implementations.SyncPlay return; } - _sessionManager.SessionStarted -= OnSessionManagerSessionStarted; + _sessionManager.SessionControllerConnected -= OnSessionControllerConnected; _disposed = true; } - private void OnSessionManagerSessionStarted(object sender, SessionEventArgs e) + private void OnSessionControllerConnected(object sender, SessionEventArgs e) { var session = e.SessionInfo; diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 9ad8557ce6..6c06dcad58 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -46,6 +46,11 @@ namespace MediaBrowser.Controller.Session event EventHandler<SessionEventArgs> SessionActivity; + /// <summary> + /// Occurs when [session controller connected]. + /// </summary> + event EventHandler<SessionEventArgs> SessionControllerConnected; + /// <summary> /// Occurs when [capabilities changed]. /// </summary> @@ -78,6 +83,12 @@ namespace MediaBrowser.Controller.Session /// <param name="user">The user.</param> SessionInfo LogSessionActivity(string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, Jellyfin.Data.Entities.User user); + /// <summary> + /// Used to report that a session controller has connected. + /// </summary> + /// <param name="session">The session.</param> + void OnSessionControllerConnected(SessionInfo session); + void UpdateDeviceName(string sessionId, string reportedDeviceName); /// <summary> -- cgit v1.2.3 From 8dd83327b5b9b77912b3b69ec00dc31898a86bc7 Mon Sep 17 00:00:00 2001 From: Matt Montgomery <33811686+ConfusedPolarBear@users.noreply.github.com> Date: Fri, 1 Jan 2021 17:26:31 -0600 Subject: Remove quick connect tokens after usage --- Emby.Server.Implementations/Session/SessionManager.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 885f65c64e..92cbb08129 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1456,7 +1456,12 @@ namespace Emby.Server.Implementations.Session throw new SecurityException("Unknown quick connect token"); } - request.UserId = result.Items[0].UserId; + var info = result.Items[0]; + request.UserId = info.UserId; + + // There's no need to keep the quick connect token in the database, as AuthenticateNewSessionInternal() issues a long lived token. + _authRepo.Delete(info); + return AuthenticateNewSessionInternal(request, false); } -- cgit v1.2.3 From cfca27e99a40204bc8e4fff963ef1a67aeed1876 Mon Sep 17 00:00:00 2001 From: Bill Thornton <billt2006@gmail.com> Date: Tue, 5 Jan 2021 10:06:55 -0500 Subject: Fix capitalization of Playstate message --- Emby.Dlna/PlayTo/PlayToController.cs | 2 +- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- MediaBrowser.Model/Session/SessionMessageType.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 4861093044..311fae2400 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -826,7 +826,7 @@ namespace Emby.Dlna.PlayTo return SendPlayCommand(data as PlayRequest, cancellationToken); } - if (name == SessionMessageType.PlayState) + if (name == SessionMessageType.Playstate) { return SendPlaystateCommand(data as PlaystateRequest, cancellationToken); } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 885f65c64e..4e026a0e6a 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1310,7 +1310,7 @@ namespace Emby.Server.Implementations.Session } } - return SendMessageToSession(session, SessionMessageType.PlayState, command, cancellationToken); + return SendMessageToSession(session, SessionMessageType.Playstate, command, cancellationToken); } private static void AssertCanControl(SessionInfo session, SessionInfo controllingSession) diff --git a/MediaBrowser.Model/Session/SessionMessageType.cs b/MediaBrowser.Model/Session/SessionMessageType.cs index 23c41026dc..84f4716b4d 100644 --- a/MediaBrowser.Model/Session/SessionMessageType.cs +++ b/MediaBrowser.Model/Session/SessionMessageType.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Model.Session Play, SyncPlayCommand, SyncPlayGroupUpdate, - PlayState, + Playstate, RestartRequired, ServerShuttingDown, ServerRestarting, -- cgit v1.2.3 From eba859e33e7bf611e5b4d63acd4df81038154be8 Mon Sep 17 00:00:00 2001 From: Bond_009 <bond.009@outlook.com> Date: Sun, 21 Feb 2021 02:49:52 +0100 Subject: Minor improvements --- .../LiveTv/Listings/SchedulesDirect.cs | 1 - .../Session/WebSocketController.cs | 1 - Jellyfin.Api/Extensions/DtoExtensions.cs | 9 ----- Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 11 ++++-- Jellyfin.Api/Helpers/StreamingHelpers.cs | 2 +- MediaBrowser.Common/Extensions/StringExtensions.cs | 37 ------------------- MediaBrowser.Common/Net/NetworkExtensions.cs | 5 --- tests/Jellyfin.Api.Tests/ParseNetworkTests.cs | 16 ++++---- .../Extensions/StringExtensionsTests.cs | 43 ---------------------- 9 files changed, 16 insertions(+), 109 deletions(-) delete mode 100644 MediaBrowser.Common/Extensions/StringExtensions.cs delete mode 100644 tests/Jellyfin.Common.Tests/Extensions/StringExtensionsTests.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index c641b760b6..6d7c5ac6ee 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -10,7 +10,6 @@ using System.Net.Http; using System.Net.Mime; using System.Text; using System.Text.Json; -using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index f9c6a13c69..a653b58c2b 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Net; diff --git a/Jellyfin.Api/Extensions/DtoExtensions.cs b/Jellyfin.Api/Extensions/DtoExtensions.cs index f2abd515d3..e0c744325f 100644 --- a/Jellyfin.Api/Extensions/DtoExtensions.cs +++ b/Jellyfin.Api/Extensions/DtoExtensions.cs @@ -113,14 +113,5 @@ namespace Jellyfin.Api.Extensions return dtoOptions; } - - /// <summary> - /// Check if DtoOptions contains field. - /// </summary> - /// <param name="dtoOptions">DtoOptions object.</param> - /// <param name="field">Field to check.</param> - /// <returns>Field existence.</returns> - internal static bool ContainsField(this DtoOptions dtoOptions, ItemFields field) - => dtoOptions.Fields != null && dtoOptions.Fields.Contains(field); } } diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index 8a47f7461d..16380f0bba 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; +using System.Net.Mime; using System.Security.Claims; using System.Text; using System.Threading; @@ -171,13 +172,15 @@ namespace Jellyfin.Api.Helpers var queryString = _httpContextAccessor.HttpContext.Request.QueryString.ToString(); // from universal audio service - if (queryString.IndexOf("SegmentContainer", StringComparison.OrdinalIgnoreCase) == -1 && !string.IsNullOrWhiteSpace(state.Request.SegmentContainer)) + if (!string.IsNullOrWhiteSpace(state.Request.SegmentContainer) + && !queryString.Contains("SegmentContainer", StringComparison.OrdinalIgnoreCase)) { queryString += "&SegmentContainer=" + state.Request.SegmentContainer; } // from universal audio service - if (!string.IsNullOrWhiteSpace(state.Request.TranscodeReasons) && queryString.IndexOf("TranscodeReasons=", StringComparison.OrdinalIgnoreCase) == -1) + if (!string.IsNullOrWhiteSpace(state.Request.TranscodeReasons) + && !queryString.Contains("TranscodeReasons=", StringComparison.OrdinalIgnoreCase)) { queryString += "&TranscodeReasons=" + state.Request.TranscodeReasons; } @@ -560,13 +563,13 @@ namespace Jellyfin.Api.Helpers profileString = state.GetRequestedProfiles(codec).FirstOrDefault() ?? string.Empty; if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase)) { - profileString = profileString ?? "high"; + profileString ??= "high"; } if (string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase) || string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)) { - profileString = profileString ?? "main"; + profileString ??= "main"; } } diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 4957ee8b8d..153ec31759 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -245,7 +245,7 @@ namespace Jellyfin.Api.Helpers var ext = string.IsNullOrWhiteSpace(state.OutputContainer) ? GetOutputFileExtension(state) - : ('.' + state.OutputContainer); + : ("." + state.OutputContainer); state.OutputFilePath = GetOutputFilePath(state, ext!, serverConfigurationManager, streamingRequest.DeviceId, streamingRequest.PlaySessionId); diff --git a/MediaBrowser.Common/Extensions/StringExtensions.cs b/MediaBrowser.Common/Extensions/StringExtensions.cs deleted file mode 100644 index 7643017412..0000000000 --- a/MediaBrowser.Common/Extensions/StringExtensions.cs +++ /dev/null @@ -1,37 +0,0 @@ -#nullable enable - -using System; - -namespace MediaBrowser.Common.Extensions -{ - /// <summary> - /// Extensions methods to simplify string operations. - /// </summary> - public static class StringExtensions - { - /// <summary> - /// Returns the part on the left of the <c>needle</c>. - /// </summary> - /// <param name="haystack">The string to seek.</param> - /// <param name="needle">The needle to find.</param> - /// <returns>The part left of the <paramref name="needle" />.</returns> - public static ReadOnlySpan<char> LeftPart(this ReadOnlySpan<char> haystack, char needle) - { - var pos = haystack.IndexOf(needle); - return pos == -1 ? haystack : haystack[..pos]; - } - - /// <summary> - /// Returns the part on the left of the <c>needle</c>. - /// </summary> - /// <param name="haystack">The string to seek.</param> - /// <param name="needle">The needle to find.</param> - /// <param name="stringComparison">One of the enumeration values that specifies the rules for the search.</param> - /// <returns>The part left of the <c>needle</c>.</returns> - public static ReadOnlySpan<char> LeftPart(this ReadOnlySpan<char> haystack, ReadOnlySpan<char> needle, StringComparison stringComparison = default) - { - var pos = haystack.IndexOf(needle, stringComparison); - return pos == -1 ? haystack : haystack[..pos]; - } - } -} diff --git a/MediaBrowser.Common/Net/NetworkExtensions.cs b/MediaBrowser.Common/Net/NetworkExtensions.cs index d07bba249b..9c1a0cf495 100644 --- a/MediaBrowser.Common/Net/NetworkExtensions.cs +++ b/MediaBrowser.Common/Net/NetworkExtensions.cs @@ -1,11 +1,6 @@ -#pragma warning disable CA1062 // Validate arguments of public methods using System; -using System.Collections; -using System.Collections.Generic; using System.Collections.ObjectModel; using System.Net; -using System.Runtime.CompilerServices; -using System.Text; namespace MediaBrowser.Common.Net { diff --git a/tests/Jellyfin.Api.Tests/ParseNetworkTests.cs b/tests/Jellyfin.Api.Tests/ParseNetworkTests.cs index 6c3fd0ee10..3984407ee9 100644 --- a/tests/Jellyfin.Api.Tests/ParseNetworkTests.cs +++ b/tests/Jellyfin.Api.Tests/ParseNetworkTests.cs @@ -37,28 +37,28 @@ namespace Jellyfin.Api.Tests EnableIPV6 = ip6 }; - var result = match + ','; + var result = match + ","; ForwardedHeadersOptions options = new ForwardedHeadersOptions(); // Need this here as ::1 and 127.0.0.1 are in them by default. options.KnownProxies.Clear(); options.KnownNetworks.Clear(); - ApiServiceCollectionExtensions.AddProxyAddresses(settings, hostList.Split(","), options); + ApiServiceCollectionExtensions.AddProxyAddresses(settings, hostList.Split(','), options); var sb = new StringBuilder(); foreach (var item in options.KnownProxies) { - sb.Append(item); - sb.Append(','); + sb.Append(item) + .Append(','); } foreach (var item in options.KnownNetworks) { - sb.Append(item.Prefix); - sb.Append('/'); - sb.Append(item.PrefixLength.ToString(CultureInfo.InvariantCulture)); - sb.Append(','); + sb.Append(item.Prefix) + .Append('/') + .Append(item.PrefixLength.ToString(CultureInfo.InvariantCulture)) + .Append(','); } Assert.Equal(sb.ToString(), result); diff --git a/tests/Jellyfin.Common.Tests/Extensions/StringExtensionsTests.cs b/tests/Jellyfin.Common.Tests/Extensions/StringExtensionsTests.cs deleted file mode 100644 index 8bf613f05f..0000000000 --- a/tests/Jellyfin.Common.Tests/Extensions/StringExtensionsTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using MediaBrowser.Common.Extensions; -using Xunit; - -namespace Jellyfin.Common.Tests.Extensions -{ - public class StringExtensionsTests - { - [Theory] - [InlineData("", 'q', "")] - [InlineData("Banana split", ' ', "Banana")] - [InlineData("Banana split", 'q', "Banana split")] - public void LeftPart_ValidArgsCharNeedle_Correct(string str, char needle, string expectedResult) - { - var result = str.AsSpan().LeftPart(needle).ToString(); - Assert.Equal(expectedResult, result); - } - - [Theory] - [InlineData("", "", "")] - [InlineData("", "q", "")] - [InlineData("Banana split", "", "")] - [InlineData("Banana split", " ", "Banana")] - [InlineData("Banana split test", " split", "Banana")] - public void LeftPart_ValidArgsWithoutStringComparison_Correct(string str, string needle, string expectedResult) - { - var result = str.AsSpan().LeftPart(needle).ToString(); - Assert.Equal(expectedResult, result); - } - - [Theory] - [InlineData("", "", StringComparison.Ordinal, "")] - [InlineData("Banana split", " ", StringComparison.Ordinal, "Banana")] - [InlineData("Banana split test", " split", StringComparison.Ordinal, "Banana")] - [InlineData("Banana split test", " Split", StringComparison.Ordinal, "Banana split test")] - [InlineData("Banana split test", " Splït", StringComparison.InvariantCultureIgnoreCase, "Banana split test")] - public void LeftPart_ValidArgs_Correct(string str, string needle, StringComparison stringComparison, string expectedResult) - { - var result = str.AsSpan().LeftPart(needle, stringComparison).ToString(); - Assert.Equal(expectedResult, result); - } - } -} -- cgit v1.2.3 From 9eb740ba57c7d8dfbffac9e18541c969eb0af880 Mon Sep 17 00:00:00 2001 From: Ionut Andrei Oanca <oancaionutandrei@gmail.com> Date: Sun, 28 Mar 2021 13:25:40 +0200 Subject: Fix storing outdated sessions in SyncPlay --- .../Session/SessionManager.cs | 6 +++-- Emby.Server.Implementations/SyncPlay/Group.cs | 30 +++++++++++----------- .../SyncPlay/SyncPlayManager.cs | 8 +++--- MediaBrowser.Controller/Session/ISessionManager.cs | 8 +++--- MediaBrowser.Controller/SyncPlay/GroupMember.cs | 23 ++++++++++++++--- 5 files changed, 46 insertions(+), 29 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 10e28c33a5..0dc87c8448 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1197,16 +1197,18 @@ namespace Emby.Server.Implementations.Session } /// <inheritdoc /> - public async Task SendSyncPlayCommand(SessionInfo session, SendCommand command, CancellationToken cancellationToken) + public async Task SendSyncPlayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken) { CheckDisposed(); + var session = GetSession(sessionId); await SendMessageToSession(session, SessionMessageType.SyncPlayCommand, command, cancellationToken).ConfigureAwait(false); } /// <inheritdoc /> - public async Task SendSyncPlayGroupUpdate<T>(SessionInfo session, GroupUpdate<T> command, CancellationToken cancellationToken) + public async Task SendSyncPlayGroupUpdate<T>(string sessionId, GroupUpdate<T> command, CancellationToken cancellationToken) { CheckDisposed(); + var session = GetSession(sessionId); await SendMessageToSession(session, SessionMessageType.SyncPlayGroupUpdate, command, cancellationToken).ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/SyncPlay/Group.cs b/Emby.Server.Implementations/SyncPlay/Group.cs index 7c2ad2477a..2fd971c9d7 100644 --- a/Emby.Server.Implementations/SyncPlay/Group.cs +++ b/Emby.Server.Implementations/SyncPlay/Group.cs @@ -162,26 +162,26 @@ namespace Emby.Server.Implementations.SyncPlay /// <summary> /// Filters sessions of this group. /// </summary> - /// <param name="from">The current session.</param> + /// <param name="fromId">The current session identifier.</param> /// <param name="type">The filtering type.</param> /// <returns>The list of sessions matching the filter.</returns> - private IEnumerable<SessionInfo> FilterSessions(SessionInfo from, SyncPlayBroadcastType type) + private IEnumerable<string> FilterSessions(string fromId, SyncPlayBroadcastType type) { return type switch { - SyncPlayBroadcastType.CurrentSession => new SessionInfo[] { from }, + SyncPlayBroadcastType.CurrentSession => new string[] { fromId }, SyncPlayBroadcastType.AllGroup => _participants .Values - .Select(session => session.Session), + .Select(member => member.SessionId), SyncPlayBroadcastType.AllExceptCurrentSession => _participants .Values - .Select(session => session.Session) - .Where(session => !session.Id.Equals(from.Id, StringComparison.OrdinalIgnoreCase)), + .Select(member => member.SessionId) + .Where(sessionId => !sessionId.Equals(fromId, StringComparison.OrdinalIgnoreCase)), SyncPlayBroadcastType.AllReady => _participants .Values - .Where(session => !session.IsBuffering) - .Select(session => session.Session), - _ => Enumerable.Empty<SessionInfo>() + .Where(member => !member.IsBuffering) + .Select(member => member.SessionId), + _ => Enumerable.Empty<string>() }; } @@ -223,7 +223,7 @@ namespace Emby.Server.Implementations.SyncPlay // Get list of users. var users = _participants .Values - .Select(participant => _userManager.GetUserById(participant.Session.UserId)); + .Select(participant => _userManager.GetUserById(participant.UserId)); // Find problematic users. var usersWithNoAccess = users.Where(user => !HasAccessToQueue(user, queue)); @@ -351,7 +351,7 @@ namespace Emby.Server.Implementations.SyncPlay /// <returns>The group info for the clients.</returns> public GroupInfoDto GetInfo() { - var participants = _participants.Values.Select(session => session.Session.UserName).Distinct().ToList(); + var participants = _participants.Values.Select(session => session.UserName).Distinct().ToList(); return new GroupInfoDto(GroupId, GroupName, _state.Type, participants, DateTime.UtcNow); } @@ -387,9 +387,9 @@ namespace Emby.Server.Implementations.SyncPlay { IEnumerable<Task> GetTasks() { - foreach (var session in FilterSessions(from, type)) + foreach (var sessionId in FilterSessions(from.Id, type)) { - yield return _sessionManager.SendSyncPlayGroupUpdate(session, message, cancellationToken); + yield return _sessionManager.SendSyncPlayGroupUpdate(sessionId, message, cancellationToken); } } @@ -401,9 +401,9 @@ namespace Emby.Server.Implementations.SyncPlay { IEnumerable<Task> GetTasks() { - foreach (var session in FilterSessions(from, type)) + foreach (var sessionId in FilterSessions(from.Id, type)) { - yield return _sessionManager.SendSyncPlayCommand(session, message, cancellationToken); + yield return _sessionManager.SendSyncPlayCommand(sessionId, message, cancellationToken); } } diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index aee959c53c..3ebdbcf9ae 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -158,7 +158,7 @@ namespace Emby.Server.Implementations.SyncPlay _logger.LogWarning("Session {SessionId} tried to join group {GroupId} that does not exist.", session.Id, request.GroupId); var error = new GroupUpdate<string>(Guid.Empty, GroupUpdateType.GroupDoesNotExist, string.Empty); - _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -170,7 +170,7 @@ namespace Emby.Server.Implementations.SyncPlay _logger.LogWarning("Session {SessionId} tried to join group {GroupId} but does not have access to some content of the playing queue.", session.Id, group.GroupId.ToString()); var error = new GroupUpdate<string>(group.GroupId, GroupUpdateType.LibraryAccessDenied, string.Empty); - _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -247,7 +247,7 @@ namespace Emby.Server.Implementations.SyncPlay _logger.LogWarning("Session {SessionId} does not belong to any group.", session.Id); var error = new GroupUpdate<string>(Guid.Empty, GroupUpdateType.NotInGroup, string.Empty); - _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } } @@ -324,7 +324,7 @@ namespace Emby.Server.Implementations.SyncPlay _logger.LogWarning("Session {SessionId} does not belong to any group.", session.Id); var error = new GroupUpdate<string>(Guid.Empty, GroupUpdateType.NotInGroup, string.Empty); - _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); } } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 6c06dcad58..d22c9c6cfe 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -156,20 +156,20 @@ namespace MediaBrowser.Controller.Session /// <summary> /// Sends a SyncPlayCommand to a session. /// </summary> - /// <param name="session">The session.</param> + /// <param name="sessionId">The identifier of the session.</param> /// <param name="command">The command.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task SendSyncPlayCommand(SessionInfo session, SendCommand command, CancellationToken cancellationToken); + Task SendSyncPlayCommand(string sessionId, SendCommand command, CancellationToken cancellationToken); /// <summary> /// Sends a SyncPlayGroupUpdate to a session. /// </summary> - /// <param name="session">The session.</param> + /// <param name="sessionId">The identifier of the session.</param> /// <param name="command">The group update.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task SendSyncPlayGroupUpdate<T>(SessionInfo session, GroupUpdate<T> command, CancellationToken cancellationToken); + Task SendSyncPlayGroupUpdate<T>(string sessionId, GroupUpdate<T> command, CancellationToken cancellationToken); /// <summary> /// Sends the browse command. diff --git a/MediaBrowser.Controller/SyncPlay/GroupMember.cs b/MediaBrowser.Controller/SyncPlay/GroupMember.cs index 5fb982e85a..361c91d530 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupMember.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupMember.cs @@ -1,3 +1,4 @@ +using System; using MediaBrowser.Controller.Session; namespace MediaBrowser.Controller.SyncPlay @@ -13,14 +14,28 @@ namespace MediaBrowser.Controller.SyncPlay /// <param name="session">The session.</param> public GroupMember(SessionInfo session) { - Session = session; + SessionId = session.Id; + UserId = session.UserId; + UserName = session.UserName; } /// <summary> - /// Gets the session. + /// Gets the identifier of the session. /// </summary> - /// <value>The session.</value> - public SessionInfo Session { get; } + /// <value>The session identifier.</value> + public string SessionId { get; } + + /// <summary> + /// Gets the identifier of the user. + /// </summary> + /// <value>The user identifier.</value> + public Guid UserId { get; } + + /// <summary> + /// Gets the username. + /// </summary> + /// <value>The username.</value> + public string UserName { get; } /// <summary> /// Gets or sets the ping, in milliseconds. -- cgit v1.2.3 From 16ca8c753618395af2e186744570768e7a76bafc Mon Sep 17 00:00:00 2001 From: Patrick Barron <patrick@yobo.dev> Date: Thu, 1 Apr 2021 10:42:00 -0400 Subject: Remove unused SessionManager methods --- Emby.Server.Implementations/Session/SessionManager.cs | 16 +--------------- MediaBrowser.Controller/Session/ISessionManager.cs | 15 --------------- 2 files changed, 1 insertion(+), 30 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 10e28c33a5..e06e48ca67 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1437,11 +1437,6 @@ namespace Emby.Server.Implementations.Session return AuthenticateNewSessionInternal(request, true); } - public Task<AuthenticationResult> CreateNewSession(AuthenticationRequest request) - { - return AuthenticateNewSessionInternal(request, false); - } - public Task<AuthenticationResult> AuthenticateQuickConnect(AuthenticationRequest request, string token) { var result = _authRepo.Get(new AuthenticationInfoQuery() @@ -1784,18 +1779,9 @@ namespace Emby.Server.Implementations.Session } var item = _libraryManager.GetItemById(new Guid(itemId)); - - var info = GetItemInfo(item, null); - - ReportNowViewingItem(sessionId, info); - } - - /// <inheritdoc /> - public void ReportNowViewingItem(string sessionId, BaseItemDto item) - { var session = GetSession(sessionId); - session.NowViewingItem = item; + session.NowViewingItem = GetItemInfo(item, null); } /// <inheritdoc /> diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 6c06dcad58..9eb486534c 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -8,7 +8,6 @@ using Jellyfin.Data.Events; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Security; -using MediaBrowser.Model.Dto; using MediaBrowser.Model.Session; using MediaBrowser.Model.SyncPlay; @@ -263,13 +262,6 @@ namespace MediaBrowser.Controller.Session /// <param name="itemId">The item identifier.</param> void ReportNowViewingItem(string sessionId, string itemId); - /// <summary> - /// Reports the now viewing item. - /// </summary> - /// <param name="sessionId">The session identifier.</param> - /// <param name="item">The item.</param> - void ReportNowViewingItem(string sessionId, BaseItemDto item); - /// <summary> /// Authenticates the new session. /// </summary> @@ -285,13 +277,6 @@ namespace MediaBrowser.Controller.Session /// <returns>Task{SessionInfo}.</returns> Task<AuthenticationResult> AuthenticateQuickConnect(AuthenticationRequest request, string token); - /// <summary> - /// Creates the new session. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>Task<AuthenticationResult>.</returns> - Task<AuthenticationResult> CreateNewSession(AuthenticationRequest request); - /// <summary> /// Reports the capabilities. /// </summary> -- cgit v1.2.3 From 44e71774b17942034691d6a2c630cd687b23bceb Mon Sep 17 00:00:00 2001 From: Patrick Barron <barronpm@gmail.com> Date: Sat, 10 Apr 2021 16:17:36 -0400 Subject: Rewrite device manager using EF Core --- Emby.Server.Implementations/ApplicationHost.cs | 2 - .../Devices/DeviceManager.cs | 144 ------------------- .../Security/AuthenticationRepository.cs | 4 +- .../Session/SessionManager.cs | 2 +- Jellyfin.Api/Controllers/DevicesController.cs | 14 +- .../Devices/DeviceManager.cs | 158 +++++++++++++++++++++ Jellyfin.Server/CoreAppHost.cs | 3 + MediaBrowser.Controller/Devices/IDeviceManager.cs | 9 +- .../Security/IAuthenticationRepository.cs | 2 +- MediaBrowser.Model/Devices/DeviceOptions.cs | 9 -- 10 files changed, 178 insertions(+), 169 deletions(-) delete mode 100644 Emby.Server.Implementations/Devices/DeviceManager.cs create mode 100644 Jellyfin.Server.Implementations/Devices/DeviceManager.cs delete mode 100644 MediaBrowser.Model/Devices/DeviceOptions.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 3846de5fd4..f2ed20fbcb 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -644,8 +644,6 @@ namespace Emby.Server.Implementations ServiceCollection.AddSingleton<ITVSeriesManager, TVSeriesManager>(); - ServiceCollection.AddSingleton<IDeviceManager, DeviceManager>(); - ServiceCollection.AddSingleton<IMediaSourceManager, MediaSourceManager>(); ServiceCollection.AddSingleton<ISubtitleManager, SubtitleManager>(); diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs deleted file mode 100644 index da5047d244..0000000000 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ /dev/null @@ -1,144 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using Jellyfin.Data.Entities; -using Jellyfin.Data.Enums; -using Jellyfin.Data.Events; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Security; -using MediaBrowser.Model.Devices; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Session; - -namespace Emby.Server.Implementations.Devices -{ - public class DeviceManager : IDeviceManager - { - private readonly IUserManager _userManager; - private readonly IAuthenticationRepository _authRepo; - private readonly ConcurrentDictionary<string, ClientCapabilities> _capabilitiesMap = new (); - - public DeviceManager(IAuthenticationRepository authRepo, IUserManager userManager) - { - _userManager = userManager; - _authRepo = authRepo; - } - - public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated; - - public void SaveCapabilities(string deviceId, ClientCapabilities capabilities) - { - _capabilitiesMap[deviceId] = capabilities; - } - - public void UpdateDeviceOptions(string deviceId, DeviceOptions options) - { - _authRepo.UpdateDeviceOptions(deviceId, options); - - DeviceOptionsUpdated?.Invoke(this, new GenericEventArgs<Tuple<string, DeviceOptions>>(new Tuple<string, DeviceOptions>(deviceId, options))); - } - - public DeviceOptions GetDeviceOptions(string deviceId) - { - return _authRepo.GetDeviceOptions(deviceId); - } - - public ClientCapabilities GetCapabilities(string id) - { - return _capabilitiesMap.TryGetValue(id, out ClientCapabilities result) - ? result - : new ClientCapabilities(); - } - - public DeviceInfo GetDevice(string id) - { - var session = _authRepo.Get(new AuthenticationInfoQuery - { - DeviceId = id - }).Items.FirstOrDefault(); - - var device = session == null ? null : ToDeviceInfo(session); - - return device; - } - - public QueryResult<DeviceInfo> GetDevices(DeviceQuery query) - { - IEnumerable<AuthenticationInfo> sessions = _authRepo.Get(new AuthenticationInfoQuery - { - // UserId = query.UserId - HasUser = true - }).Items; - - // TODO: DeviceQuery doesn't seem to be used from client. Not even Swagger. - if (query.SupportsSync.HasValue) - { - var val = query.SupportsSync.Value; - - sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == val); - } - - if (!query.UserId.Equals(Guid.Empty)) - { - var user = _userManager.GetUserById(query.UserId); - - sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId)); - } - - var array = sessions.Select(ToDeviceInfo).ToArray(); - - return new QueryResult<DeviceInfo>(array); - } - - private DeviceInfo ToDeviceInfo(AuthenticationInfo authInfo) - { - var caps = GetCapabilities(authInfo.DeviceId); - - return new DeviceInfo - { - AppName = authInfo.AppName, - AppVersion = authInfo.AppVersion, - Id = authInfo.DeviceId, - LastUserId = authInfo.UserId, - LastUserName = authInfo.UserName, - Name = authInfo.DeviceName, - DateLastActivity = authInfo.DateLastActivity, - IconUrl = caps?.IconUrl - }; - } - - public bool CanAccessDevice(User user, string deviceId) - { - if (user == null) - { - throw new ArgumentException("user not found"); - } - - if (string.IsNullOrEmpty(deviceId)) - { - throw new ArgumentNullException(nameof(deviceId)); - } - - if (user.HasPermission(PermissionKind.EnableAllDevices) || user.HasPermission(PermissionKind.IsAdministrator)) - { - return true; - } - - if (!user.GetPreference(PreferenceKind.EnabledDevices).Contains(deviceId, StringComparer.OrdinalIgnoreCase)) - { - var capabilities = GetCapabilities(deviceId); - - if (capabilities != null && capabilities.SupportsPersistentIdentifier) - { - return false; - } - } - - return true; - } - } -} diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 4bc12f44a4..0d0a2b1df7 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -6,9 +6,9 @@ using System.Globalization; using System.IO; using System.Linq; using Emby.Server.Implementations.Data; +using Jellyfin.Data.Entities.Security; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Security; -using MediaBrowser.Model.Devices; using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; using SQLitePCL.pretty; @@ -357,7 +357,7 @@ namespace Emby.Server.Implementations.Security { statement.TryBind("@DeviceId", deviceId); - var result = new DeviceOptions(); + var result = new DeviceOptions(deviceId); foreach (var row in statement.ExecuteQuery()) { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index e06e48ca67..a47a1f56f8 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Enums; using Jellyfin.Data.Events; using MediaBrowser.Common.Events; @@ -24,7 +25,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Devices; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Library; diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs index b3e3490c2a..3ca6488d91 100644 --- a/Jellyfin.Api/Controllers/DevicesController.cs +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -1,6 +1,8 @@ using System; using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; using Jellyfin.Api.Constants; +using Jellyfin.Data.Entities.Security; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; @@ -47,10 +49,10 @@ namespace Jellyfin.Api.Controllers /// <returns>An <see cref="OkResult"/> containing the list of devices.</returns> [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<QueryResult<DeviceInfo>> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId) + public async Task<ActionResult<QueryResult<DeviceInfo>>> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId) { var deviceQuery = new DeviceQuery { SupportsSync = supportsSync, UserId = userId ?? Guid.Empty }; - return _deviceManager.GetDevices(deviceQuery); + return await _deviceManager.GetDevices(deviceQuery); } /// <summary> @@ -63,9 +65,9 @@ namespace Jellyfin.Api.Controllers [HttpGet("Info")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult<DeviceInfo> GetDeviceInfo([FromQuery, Required] string id) + public async Task<ActionResult<DeviceInfo>> GetDeviceInfo([FromQuery, Required] string id) { - var deviceInfo = _deviceManager.GetDevice(id); + var deviceInfo = await _deviceManager.GetDevice(id).ConfigureAwait(false); if (deviceInfo == null) { return NotFound(); @@ -106,7 +108,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("Options")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult UpdateDeviceOptions( + public async Task<ActionResult> UpdateDeviceOptions( [FromQuery, Required] string id, [FromBody, Required] DeviceOptions deviceOptions) { @@ -116,7 +118,7 @@ namespace Jellyfin.Api.Controllers return NotFound(); } - _deviceManager.UpdateDeviceOptions(id, deviceOptions); + await _deviceManager.UpdateDeviceOptions(id, deviceOptions).ConfigureAwait(false); return NoContent(); } diff --git a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs new file mode 100644 index 0000000000..c942678d97 --- /dev/null +++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Entities.Security; +using Jellyfin.Data.Enums; +using Jellyfin.Data.Events; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Devices; +using MediaBrowser.Model.Querying; +using MediaBrowser.Model.Session; +using Microsoft.EntityFrameworkCore; + +namespace Jellyfin.Server.Implementations.Devices +{ + public class DeviceManager : IDeviceManager + { + private readonly JellyfinDbProvider _dbProvider; + private readonly IUserManager _userManager; + private readonly ConcurrentDictionary<string, ClientCapabilities> _capabilitiesMap = new (); + + /// <summary> + /// Initializes a new instance of the <see cref="DeviceManager"/> class. + /// </summary> + /// <param name="dbProvider">The database provider.</param> + /// <param name="userManager">The user manager.</param> + public DeviceManager(JellyfinDbProvider dbProvider, IUserManager userManager) + { + _dbProvider = dbProvider; + _userManager = userManager; + } + + /// <inheritdoc /> + public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>>? DeviceOptionsUpdated; + + /// <inheritdoc /> + public void SaveCapabilities(string deviceId, ClientCapabilities capabilities) + { + _capabilitiesMap[deviceId] = capabilities; + } + + /// <inheritdoc /> + public async Task UpdateDeviceOptions(string deviceId, DeviceOptions options) + { + await using var dbContext = _dbProvider.CreateContext(); + await dbContext.Database + .ExecuteSqlRawAsync($"UPDATE [DeviceOptions] SET [CustomName] = ${options.CustomName}") + .ConfigureAwait(false); + + DeviceOptionsUpdated?.Invoke(this, new GenericEventArgs<Tuple<string, DeviceOptions>>(new Tuple<string, DeviceOptions>(deviceId, options))); + } + + /// <inheritdoc /> + public DeviceOptions? GetDeviceOptions(string deviceId) + { + using var dbContext = _dbProvider.CreateContext(); + return dbContext.DeviceOptions + .AsQueryable() + .FirstOrDefault(d => d.DeviceId == deviceId); + } + + /// <inheritdoc /> + public ClientCapabilities GetCapabilities(string id) + { + return _capabilitiesMap.TryGetValue(id, out ClientCapabilities? result) + ? result + : new ClientCapabilities(); + } + + /// <inheritdoc /> + public async Task<DeviceInfo?> GetDevice(string id) + { + await using var dbContext = _dbProvider.CreateContext(); + var device = await dbContext.Devices + .AsQueryable() + .Where(d => d.DeviceId == id) + .OrderByDescending(d => d.DateLastActivity) + .Include(d => d.User) + .FirstOrDefaultAsync() + .ConfigureAwait(false); + + var deviceInfo = device == null ? null : ToDeviceInfo(device); + + return deviceInfo; + } + + /// <inheritdoc /> + public async Task<QueryResult<DeviceInfo>> GetDevices(DeviceQuery query) + { + await using var dbContext = _dbProvider.CreateContext(); + var sessions = dbContext.Devices + .AsQueryable() + .OrderBy(d => d.DeviceId) + .ThenByDescending(d => d.DateLastActivity) + .AsAsyncEnumerable(); + + // TODO: DeviceQuery doesn't seem to be used from client. Not even Swagger. + if (query.SupportsSync.HasValue) + { + var val = query.SupportsSync.Value; + + sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == val); + } + + if (!query.UserId.Equals(Guid.Empty)) + { + var user = _userManager.GetUserById(query.UserId); + + sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId)); + } + + var array = await sessions.Select(ToDeviceInfo).ToArrayAsync(); + + return new QueryResult<DeviceInfo>(array); + } + + /// <inheritdoc /> + public bool CanAccessDevice(User user, string deviceId) + { + if (user == null) + { + throw new ArgumentException("user not found"); + } + + if (string.IsNullOrEmpty(deviceId)) + { + throw new ArgumentNullException(nameof(deviceId)); + } + + if (user.HasPermission(PermissionKind.EnableAllDevices) || user.HasPermission(PermissionKind.IsAdministrator)) + { + return true; + } + + return user.GetPreference(PreferenceKind.EnabledDevices).Contains(deviceId, StringComparer.OrdinalIgnoreCase) + || !GetCapabilities(deviceId).SupportsPersistentIdentifier; + } + + private DeviceInfo ToDeviceInfo(Device authInfo) + { + var caps = GetCapabilities(authInfo.DeviceId); + + return new DeviceInfo + { + AppName = authInfo.AppName, + AppVersion = authInfo.AppVersion, + Id = authInfo.DeviceId, + LastUserId = authInfo.UserId, + LastUserName = authInfo.User.Username, + Name = authInfo.DeviceName, + DateLastActivity = authInfo.DateLastActivity, + IconUrl = caps.IconUrl + }; + } + } +} diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 94c3ca4a95..b20acae32e 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -9,10 +9,12 @@ using Jellyfin.Api.WebSocketListeners; using Jellyfin.Drawing.Skia; using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations.Activity; +using Jellyfin.Server.Implementations.Devices; using Jellyfin.Server.Implementations.Events; using Jellyfin.Server.Implementations.Users; using MediaBrowser.Controller; using MediaBrowser.Controller.BaseItemManager; +using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Library; @@ -84,6 +86,7 @@ namespace Jellyfin.Server ServiceCollection.AddSingleton<IActivityManager, ActivityManager>(); ServiceCollection.AddSingleton<IUserManager, UserManager>(); ServiceCollection.AddSingleton<IDisplayPreferencesManager, DisplayPreferencesManager>(); + ServiceCollection.AddSingleton<IDeviceManager, DeviceManager>(); // TODO search the assemblies instead of adding them manually? ServiceCollection.AddSingleton<IWebSocketListener, SessionWebSocketListener>(); diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs index 8f0872dba9..aa05ead8fd 100644 --- a/MediaBrowser.Controller/Devices/IDeviceManager.cs +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -1,7 +1,9 @@ #pragma warning disable CS1591 using System; +using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Events; using MediaBrowser.Model.Devices; using MediaBrowser.Model.Querying; @@ -18,7 +20,6 @@ namespace MediaBrowser.Controller.Devices /// </summary> /// <param name="reportedId">The reported identifier.</param> /// <param name="capabilities">The capabilities.</param> - /// <returns>Task.</returns> void SaveCapabilities(string reportedId, ClientCapabilities capabilities); /// <summary> @@ -33,21 +34,21 @@ namespace MediaBrowser.Controller.Devices /// </summary> /// <param name="id">The identifier.</param> /// <returns>DeviceInfo.</returns> - DeviceInfo GetDevice(string id); + Task<DeviceInfo> GetDevice(string id); /// <summary> /// Gets the devices. /// </summary> /// <param name="query">The query.</param> /// <returns>IEnumerable<DeviceInfo>.</returns> - QueryResult<DeviceInfo> GetDevices(DeviceQuery query); + Task<QueryResult<DeviceInfo>> GetDevices(DeviceQuery query); /// <summary> /// Determines whether this instance [can access device] the specified user identifier. /// </summary> bool CanAccessDevice(User user, string deviceId); - void UpdateDeviceOptions(string deviceId, DeviceOptions options); + Task UpdateDeviceOptions(string deviceId, DeviceOptions options); DeviceOptions GetDeviceOptions(string deviceId); } diff --git a/MediaBrowser.Controller/Security/IAuthenticationRepository.cs b/MediaBrowser.Controller/Security/IAuthenticationRepository.cs index 883b74165c..27f281b71f 100644 --- a/MediaBrowser.Controller/Security/IAuthenticationRepository.cs +++ b/MediaBrowser.Controller/Security/IAuthenticationRepository.cs @@ -1,6 +1,6 @@ #pragma warning disable CS1591 -using MediaBrowser.Model.Devices; +using Jellyfin.Data.Entities.Security; using MediaBrowser.Model.Querying; namespace MediaBrowser.Controller.Security diff --git a/MediaBrowser.Model/Devices/DeviceOptions.cs b/MediaBrowser.Model/Devices/DeviceOptions.cs deleted file mode 100644 index 037ffeb5e8..0000000000 --- a/MediaBrowser.Model/Devices/DeviceOptions.cs +++ /dev/null @@ -1,9 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.Model.Devices -{ - public class DeviceOptions - { - public string? CustomName { get; set; } - } -} -- cgit v1.2.3 From 8607b5254142662e79dbf826d43375ce60727cfe Mon Sep 17 00:00:00 2001 From: Patrick Barron <barronpm@gmail.com> Date: Sat, 10 Apr 2021 16:57:25 -0400 Subject: Make device/session code async --- Emby.Dlna/PlayTo/PlayToManager.cs | 4 +- .../HttpServer/Security/SessionContext.cs | 11 +-- .../Session/SessionManager.cs | 26 ++++--- .../Session/SessionWebSocketListener.cs | 4 +- Jellyfin.Api/Controllers/DevicesController.cs | 4 +- Jellyfin.Api/Controllers/LiveTvController.cs | 26 +++---- Jellyfin.Api/Controllers/PlaystateController.cs | 20 ++--- Jellyfin.Api/Controllers/SessionController.cs | 72 ++++++++++-------- Jellyfin.Api/Controllers/SyncPlayController.cs | 85 +++++++++++----------- Jellyfin.Api/Helpers/RequestHelpers.cs | 14 +++- .../Devices/DeviceManager.cs | 9 ++- MediaBrowser.Controller/Devices/IDeviceManager.cs | 2 +- MediaBrowser.Controller/Net/ISessionContext.cs | 9 ++- MediaBrowser.Controller/Session/ISessionManager.cs | 6 +- 14 files changed, 160 insertions(+), 132 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index 8272e505a0..236ea4d57d 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -171,7 +171,9 @@ namespace Emby.Dlna.PlayTo uuid = uri.ToString().GetMD5().ToString("N", CultureInfo.InvariantCulture); } - var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersionString, uuid, null, uri.OriginalString, null); + var sessionInfo = await _sessionManager + .LogSessionActivity("DLNA", _appHost.ApplicationVersionString, uuid, null, uri.OriginalString, null) + .ConfigureAwait(false); var controller = sessionInfo.SessionControllers.OfType<PlayToController>().FirstOrDefault(); diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index 040b6b9e4e..1b295a92d8 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System; +using System.Threading.Tasks; using Jellyfin.Data.Entities; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Library; @@ -23,7 +24,7 @@ namespace Emby.Server.Implementations.HttpServer.Security _sessionManager = sessionManager; } - public SessionInfo GetSession(HttpContext requestContext) + public Task<SessionInfo> GetSession(HttpContext requestContext) { var authorization = _authContext.GetAuthorizationInfo(requestContext); @@ -31,19 +32,19 @@ namespace Emby.Server.Implementations.HttpServer.Security return _sessionManager.LogSessionActivity(authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, requestContext.GetNormalizedRemoteIp(), user); } - public SessionInfo GetSession(object requestContext) + public Task<SessionInfo> GetSession(object requestContext) { return GetSession((HttpContext)requestContext); } - public User GetUser(HttpContext requestContext) + public async Task<User> GetUser(HttpContext requestContext) { - var session = GetSession(requestContext); + var session = await GetSession(requestContext).ConfigureAwait(false); return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId); } - public User GetUser(object requestContext) + public Task<User> GetUser(object requestContext) { return GetUser(((HttpRequest)requestContext).HttpContext); } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index a47a1f56f8..678a27665b 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -254,7 +254,7 @@ namespace Emby.Server.Implementations.Session /// <param name="remoteEndPoint">The remote end point.</param> /// <param name="user">The user.</param> /// <returns>SessionInfo.</returns> - public SessionInfo LogSessionActivity( + public async Task<SessionInfo> LogSessionActivity( string appName, string appVersion, string deviceId, @@ -280,7 +280,7 @@ namespace Emby.Server.Implementations.Session } var activityDate = DateTime.UtcNow; - var session = GetSessionInfo(appName, appVersion, deviceId, deviceName, remoteEndPoint, user); + var session = await GetSessionInfo(appName, appVersion, deviceId, deviceName, remoteEndPoint, user).ConfigureAwait(false); var lastActivityDate = session.LastActivityDate; session.LastActivityDate = activityDate; @@ -458,7 +458,7 @@ namespace Emby.Server.Implementations.Session /// <param name="remoteEndPoint">The remote end point.</param> /// <param name="user">The user.</param> /// <returns>SessionInfo.</returns> - private SessionInfo GetSessionInfo( + private async Task<SessionInfo> GetSessionInfo( string appName, string appVersion, string deviceId, @@ -477,9 +477,11 @@ namespace Emby.Server.Implementations.Session CheckDisposed(); - var sessionInfo = _activeConnections.GetOrAdd( - key, - k => CreateSession(k, appName, appVersion, deviceId, deviceName, remoteEndPoint, user)); + if (!_activeConnections.TryGetValue(key, out var sessionInfo)) + { + _activeConnections[key] = await CreateSession(key, appName, appVersion, deviceId, deviceName, remoteEndPoint, user).ConfigureAwait(false); + sessionInfo = _activeConnections[key]; + } sessionInfo.UserId = user?.Id ?? Guid.Empty; sessionInfo.UserName = user?.Username; @@ -502,7 +504,7 @@ namespace Emby.Server.Implementations.Session return sessionInfo; } - private SessionInfo CreateSession( + private async Task<SessionInfo> CreateSession( string key, string appName, string appVersion, @@ -532,7 +534,7 @@ namespace Emby.Server.Implementations.Session deviceName = "Network Device"; } - var deviceOptions = _deviceManager.GetDeviceOptions(deviceId); + var deviceOptions = await _deviceManager.GetDeviceOptions(deviceId).ConfigureAwait(false); if (string.IsNullOrEmpty(deviceOptions.CustomName)) { sessionInfo.DeviceName = deviceName; @@ -1507,13 +1509,13 @@ namespace Emby.Server.Implementations.Session var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName); - var session = LogSessionActivity( + var session = await LogSessionActivity( request.App, request.AppVersion, request.DeviceId, request.DeviceName, request.RemoteEndPoint, - user); + user).ConfigureAwait(false); var returnResult = new AuthenticationResult { @@ -1811,7 +1813,7 @@ namespace Emby.Server.Implementations.Session } /// <inheritdoc /> - public SessionInfo GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion) + public Task<SessionInfo> GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion) { if (info == null) { @@ -1844,7 +1846,7 @@ namespace Emby.Server.Implementations.Session } /// <inheritdoc /> - public SessionInfo GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint) + public Task<SessionInfo> GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint) { var items = _authRepo.Get(new AuthenticationInfoQuery { diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 39c369a01d..54e64cfebc 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -97,7 +97,7 @@ namespace Emby.Server.Implementations.Session /// <inheritdoc /> public async Task ProcessWebSocketConnectedAsync(IWebSocketConnection connection) { - var session = GetSession(connection.QueryString, connection.RemoteEndPoint.ToString()); + var session = await GetSession(connection.QueryString, connection.RemoteEndPoint.ToString()).ConfigureAwait(false); if (session != null) { EnsureController(session, connection); @@ -109,7 +109,7 @@ namespace Emby.Server.Implementations.Session } } - private SessionInfo GetSession(IQueryCollection queryString, string remoteEndpoint) + private Task<SessionInfo> GetSession(IQueryCollection queryString, string remoteEndpoint) { if (queryString == null) { diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs index 3ca6488d91..99f8ede3ae 100644 --- a/Jellyfin.Api/Controllers/DevicesController.cs +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -86,9 +86,9 @@ namespace Jellyfin.Api.Controllers [HttpGet("Options")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult<DeviceOptions> GetDeviceOptions([FromQuery, Required] string id) + public async Task<ActionResult<DeviceOptions>> GetDeviceOptions([FromQuery, Required] string id) { - var deviceInfo = _deviceManager.GetDeviceOptions(id); + var deviceInfo = await _deviceManager.GetDeviceOptions(id).ConfigureAwait(false); if (deviceInfo == null) { return NotFound(); diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 24ee833ef7..3e9b8bfa4d 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -429,10 +429,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("Tuners/{tunerId}/Reset")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.DefaultAuthorization)] - public ActionResult ResetTuner([FromRoute, Required] string tunerId) + public async Task<ActionResult> ResetTuner([FromRoute, Required] string tunerId) { - AssertUserCanManageLiveTv(); - _liveTvManager.ResetTuner(tunerId, CancellationToken.None); + await AssertUserCanManageLiveTv().ConfigureAwait(false); + await _liveTvManager.ResetTuner(tunerId, CancellationToken.None).ConfigureAwait(false); return NoContent(); } @@ -761,9 +761,9 @@ namespace Jellyfin.Api.Controllers [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult DeleteRecording([FromRoute, Required] Guid recordingId) + public async Task<ActionResult> DeleteRecording([FromRoute, Required] Guid recordingId) { - AssertUserCanManageLiveTv(); + await AssertUserCanManageLiveTv().ConfigureAwait(false); var item = _libraryManager.GetItemById(recordingId); if (item == null) @@ -790,7 +790,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task<ActionResult> CancelTimer([FromRoute, Required] string timerId) { - AssertUserCanManageLiveTv(); + await AssertUserCanManageLiveTv().ConfigureAwait(false); await _liveTvManager.CancelTimer(timerId).ConfigureAwait(false); return NoContent(); } @@ -808,7 +808,7 @@ namespace Jellyfin.Api.Controllers [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")] public async Task<ActionResult> UpdateTimer([FromRoute, Required] string timerId, [FromBody] TimerInfoDto timerInfo) { - AssertUserCanManageLiveTv(); + await AssertUserCanManageLiveTv().ConfigureAwait(false); await _liveTvManager.UpdateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false); return NoContent(); } @@ -824,7 +824,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task<ActionResult> CreateTimer([FromBody] TimerInfoDto timerInfo) { - AssertUserCanManageLiveTv(); + await AssertUserCanManageLiveTv().ConfigureAwait(false); await _liveTvManager.CreateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false); return NoContent(); } @@ -882,7 +882,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task<ActionResult> CancelSeriesTimer([FromRoute, Required] string timerId) { - AssertUserCanManageLiveTv(); + await AssertUserCanManageLiveTv().ConfigureAwait(false); await _liveTvManager.CancelSeriesTimer(timerId).ConfigureAwait(false); return NoContent(); } @@ -900,7 +900,7 @@ namespace Jellyfin.Api.Controllers [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")] public async Task<ActionResult> UpdateSeriesTimer([FromRoute, Required] string timerId, [FromBody] SeriesTimerInfoDto seriesTimerInfo) { - AssertUserCanManageLiveTv(); + await AssertUserCanManageLiveTv().ConfigureAwait(false); await _liveTvManager.UpdateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false); return NoContent(); } @@ -916,7 +916,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task<ActionResult> CreateSeriesTimer([FromBody] SeriesTimerInfoDto seriesTimerInfo) { - AssertUserCanManageLiveTv(); + await AssertUserCanManageLiveTv().ConfigureAwait(false); await _liveTvManager.CreateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false); return NoContent(); } @@ -1215,9 +1215,9 @@ namespace Jellyfin.Api.Controllers return new FileStreamResult(liveStream, MimeTypes.GetMimeType("file." + container)); } - private void AssertUserCanManageLiveTv() + private async Task AssertUserCanManageLiveTv() { - var user = _sessionContext.GetUser(Request); + var user = await _sessionContext.GetUser(Request).ConfigureAwait(false); if (user == null) { diff --git a/Jellyfin.Api/Controllers/PlaystateController.cs b/Jellyfin.Api/Controllers/PlaystateController.cs index f256c8c25c..cc8c630b35 100644 --- a/Jellyfin.Api/Controllers/PlaystateController.cs +++ b/Jellyfin.Api/Controllers/PlaystateController.cs @@ -72,13 +72,13 @@ namespace Jellyfin.Api.Controllers /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns> [HttpPost("Users/{userId}/PlayedItems/{itemId}")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<UserItemDataDto> MarkPlayedItem( + public async Task<ActionResult<UserItemDataDto>> MarkPlayedItem( [FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId, [FromQuery, ModelBinder(typeof(LegacyDateTimeModelBinder))] DateTime? datePlayed) { var user = _userManager.GetUserById(userId); - var session = RequestHelpers.GetSession(_sessionManager, _authContext, Request); + var session = await RequestHelpers.GetSession(_sessionManager, _authContext, Request).ConfigureAwait(false); var dto = UpdatePlayedStatus(user, itemId, true, datePlayed); foreach (var additionalUserInfo in session.AdditionalUsers) { @@ -98,10 +98,10 @@ namespace Jellyfin.Api.Controllers /// <returns>A <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns> [HttpDelete("Users/{userId}/PlayedItems/{itemId}")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<UserItemDataDto> MarkUnplayedItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) + public async Task<ActionResult<UserItemDataDto>> MarkUnplayedItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) { var user = _userManager.GetUserById(userId); - var session = RequestHelpers.GetSession(_sessionManager, _authContext, Request); + var session = await RequestHelpers.GetSession(_sessionManager, _authContext, Request).ConfigureAwait(false); var dto = UpdatePlayedStatus(user, itemId, false, null); foreach (var additionalUserInfo in session.AdditionalUsers) { @@ -123,7 +123,7 @@ namespace Jellyfin.Api.Controllers public async Task<ActionResult> ReportPlaybackStart([FromBody] PlaybackStartInfo playbackStartInfo) { playbackStartInfo.PlayMethod = ValidatePlayMethod(playbackStartInfo.PlayMethod, playbackStartInfo.PlaySessionId); - playbackStartInfo.SessionId = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id; + playbackStartInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false); await _sessionManager.OnPlaybackStart(playbackStartInfo).ConfigureAwait(false); return NoContent(); } @@ -139,7 +139,7 @@ namespace Jellyfin.Api.Controllers public async Task<ActionResult> ReportPlaybackProgress([FromBody] PlaybackProgressInfo playbackProgressInfo) { playbackProgressInfo.PlayMethod = ValidatePlayMethod(playbackProgressInfo.PlayMethod, playbackProgressInfo.PlaySessionId); - playbackProgressInfo.SessionId = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id; + playbackProgressInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false); await _sessionManager.OnPlaybackProgress(playbackProgressInfo).ConfigureAwait(false); return NoContent(); } @@ -174,7 +174,7 @@ namespace Jellyfin.Api.Controllers await _transcodingJobHelper.KillTranscodingJobs(_authContext.GetAuthorizationInfo(Request).DeviceId, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false); } - playbackStopInfo.SessionId = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id; + playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false); await _sessionManager.OnPlaybackStopped(playbackStopInfo).ConfigureAwait(false); return NoContent(); } @@ -220,7 +220,7 @@ namespace Jellyfin.Api.Controllers }; playbackStartInfo.PlayMethod = ValidatePlayMethod(playbackStartInfo.PlayMethod, playbackStartInfo.PlaySessionId); - playbackStartInfo.SessionId = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id; + playbackStartInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false); await _sessionManager.OnPlaybackStart(playbackStartInfo).ConfigureAwait(false); return NoContent(); } @@ -278,7 +278,7 @@ namespace Jellyfin.Api.Controllers }; playbackProgressInfo.PlayMethod = ValidatePlayMethod(playbackProgressInfo.PlayMethod, playbackProgressInfo.PlaySessionId); - playbackProgressInfo.SessionId = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id; + playbackProgressInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false); await _sessionManager.OnPlaybackProgress(playbackProgressInfo).ConfigureAwait(false); return NoContent(); } @@ -323,7 +323,7 @@ namespace Jellyfin.Api.Controllers await _transcodingJobHelper.KillTranscodingJobs(_authContext.GetAuthorizationInfo(Request).DeviceId, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false); } - playbackStopInfo.SessionId = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id; + playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false); await _sessionManager.OnPlaybackStopped(playbackStopInfo).ConfigureAwait(false); return NoContent(); } diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index e2269a2ce2..14ce75514b 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Jellyfin.Api.Constants; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; @@ -124,7 +125,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("Sessions/{sessionId}/Viewing")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult DisplayContent( + public async Task<ActionResult> DisplayContent( [FromRoute, Required] string sessionId, [FromQuery, Required] string itemType, [FromQuery, Required] string itemId, @@ -137,11 +138,12 @@ namespace Jellyfin.Api.Controllers ItemType = itemType }; - _sessionManager.SendBrowseCommand( - RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, + await _sessionManager.SendBrowseCommand( + await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false), sessionId, command, - CancellationToken.None); + CancellationToken.None) + .ConfigureAwait(false); return NoContent(); } @@ -158,7 +160,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("Sessions/{sessionId}/Playing")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult Play( + public async Task<ActionResult> Play( [FromRoute, Required] string sessionId, [FromQuery, Required] PlayCommand playCommand, [FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] itemIds, @@ -171,11 +173,12 @@ namespace Jellyfin.Api.Controllers PlayCommand = playCommand }; - _sessionManager.SendPlayCommand( - RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, + await _sessionManager.SendPlayCommand( + await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false), sessionId, playRequest, - CancellationToken.None); + CancellationToken.None) + .ConfigureAwait(false); return NoContent(); } @@ -192,14 +195,14 @@ namespace Jellyfin.Api.Controllers [HttpPost("Sessions/{sessionId}/Playing/{command}")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult SendPlaystateCommand( + public async Task<ActionResult> SendPlaystateCommand( [FromRoute, Required] string sessionId, [FromRoute, Required] PlaystateCommand command, [FromQuery] long? seekPositionTicks, [FromQuery] string? controllingUserId) { - _sessionManager.SendPlaystateCommand( - RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, + await _sessionManager.SendPlaystateCommand( + await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false), sessionId, new PlaystateRequest() { @@ -207,7 +210,8 @@ namespace Jellyfin.Api.Controllers ControllingUserId = controllingUserId, SeekPositionTicks = seekPositionTicks, }, - CancellationToken.None); + CancellationToken.None) + .ConfigureAwait(false); return NoContent(); } @@ -222,18 +226,18 @@ namespace Jellyfin.Api.Controllers [HttpPost("Sessions/{sessionId}/System/{command}")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult SendSystemCommand( + public async Task<ActionResult> SendSystemCommand( [FromRoute, Required] string sessionId, [FromRoute, Required] GeneralCommandType command) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authContext, Request).ConfigureAwait(false); var generalCommand = new GeneralCommand { Name = command, ControllingUserId = currentSession.UserId }; - _sessionManager.SendGeneralCommand(currentSession.Id, sessionId, generalCommand, CancellationToken.None); + await _sessionManager.SendGeneralCommand(currentSession.Id, sessionId, generalCommand, CancellationToken.None); return NoContent(); } @@ -248,11 +252,11 @@ namespace Jellyfin.Api.Controllers [HttpPost("Sessions/{sessionId}/Command/{command}")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult SendGeneralCommand( + public async Task<ActionResult> SendGeneralCommand( [FromRoute, Required] string sessionId, [FromRoute, Required] GeneralCommandType command) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authContext, Request).ConfigureAwait(false); var generalCommand = new GeneralCommand { @@ -260,7 +264,8 @@ namespace Jellyfin.Api.Controllers ControllingUserId = currentSession.UserId }; - _sessionManager.SendGeneralCommand(currentSession.Id, sessionId, generalCommand, CancellationToken.None); + await _sessionManager.SendGeneralCommand(currentSession.Id, sessionId, generalCommand, CancellationToken.None) + .ConfigureAwait(false); return NoContent(); } @@ -275,11 +280,12 @@ namespace Jellyfin.Api.Controllers [HttpPost("Sessions/{sessionId}/Command")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult SendFullGeneralCommand( + public async Task<ActionResult> SendFullGeneralCommand( [FromRoute, Required] string sessionId, [FromBody, Required] GeneralCommand command) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authContext, Request) + .ConfigureAwait(false); if (command == null) { @@ -288,11 +294,12 @@ namespace Jellyfin.Api.Controllers command.ControllingUserId = currentSession.UserId; - _sessionManager.SendGeneralCommand( + await _sessionManager.SendGeneralCommand( currentSession.Id, sessionId, command, - CancellationToken.None); + CancellationToken.None) + .ConfigureAwait(false); return NoContent(); } @@ -309,7 +316,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("Sessions/{sessionId}/Message")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult SendMessageCommand( + public async Task<ActionResult> SendMessageCommand( [FromRoute, Required] string sessionId, [FromQuery, Required] string text, [FromQuery] string? header, @@ -322,7 +329,12 @@ namespace Jellyfin.Api.Controllers Text = text }; - _sessionManager.SendMessageCommand(RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, sessionId, command, CancellationToken.None); + await _sessionManager.SendMessageCommand( + await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false), + sessionId, + command, + CancellationToken.None) + .ConfigureAwait(false); return NoContent(); } @@ -377,7 +389,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("Sessions/Capabilities")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult PostCapabilities( + public async Task<ActionResult> PostCapabilities( [FromQuery] string? id, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] playableMediaTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] GeneralCommandType[] supportedCommands, @@ -387,7 +399,7 @@ namespace Jellyfin.Api.Controllers { if (string.IsNullOrWhiteSpace(id)) { - id = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id; + id = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false); } _sessionManager.ReportCapabilities(id, new ClientCapabilities @@ -411,13 +423,13 @@ namespace Jellyfin.Api.Controllers [HttpPost("Sessions/Capabilities/Full")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult PostFullCapabilities( + public async Task<ActionResult> PostFullCapabilities( [FromQuery] string? id, [FromBody, Required] ClientCapabilitiesDto capabilities) { if (string.IsNullOrWhiteSpace(id)) { - id = RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id; + id = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false); } _sessionManager.ReportCapabilities(id, capabilities.ToClientCapabilities()); @@ -435,11 +447,11 @@ namespace Jellyfin.Api.Controllers [HttpPost("Sessions/Viewing")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult ReportViewing( + public async Task<ActionResult> ReportViewing( [FromQuery] string? sessionId, [FromQuery, Required] string? itemId) { - string session = sessionId ?? RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id; + string session = sessionId ?? await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false); _sessionManager.ReportNowViewingItem(session, itemId); return NoContent(); diff --git a/Jellyfin.Api/Controllers/SyncPlayController.cs b/Jellyfin.Api/Controllers/SyncPlayController.cs index f878f2329c..1b3248c0c6 100644 --- a/Jellyfin.Api/Controllers/SyncPlayController.cs +++ b/Jellyfin.Api/Controllers/SyncPlayController.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Threading; +using System.Threading.Tasks; using Jellyfin.Api.Constants; using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.SyncPlayDtos; @@ -51,10 +52,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("New")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayCreateGroup)] - public ActionResult SyncPlayCreateGroup( + public async Task<ActionResult> SyncPlayCreateGroup( [FromBody, Required] NewGroupRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new NewGroupRequest(requestData.GroupName); _syncPlayManager.NewGroup(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -69,10 +70,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("Join")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayJoinGroup)] - public ActionResult SyncPlayJoinGroup( + public async Task<ActionResult> SyncPlayJoinGroup( [FromBody, Required] JoinGroupRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new JoinGroupRequest(requestData.GroupId); _syncPlayManager.JoinGroup(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -86,9 +87,9 @@ namespace Jellyfin.Api.Controllers [HttpPost("Leave")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlayLeaveGroup() + public async Task<ActionResult> SyncPlayLeaveGroup() { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new LeaveGroupRequest(); _syncPlayManager.LeaveGroup(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -102,9 +103,9 @@ namespace Jellyfin.Api.Controllers [HttpGet("List")] [ProducesResponseType(StatusCodes.Status200OK)] [Authorize(Policy = Policies.SyncPlayJoinGroup)] - public ActionResult<IEnumerable<GroupInfoDto>> SyncPlayGetGroups() + public async Task<ActionResult<IEnumerable<GroupInfoDto>>> SyncPlayGetGroups() { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new ListGroupsRequest(); return Ok(_syncPlayManager.ListGroups(currentSession, syncPlayRequest)); } @@ -118,10 +119,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("SetNewQueue")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlaySetNewQueue( + public async Task<ActionResult> SyncPlaySetNewQueue( [FromBody, Required] PlayRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new PlayGroupRequest( requestData.PlayingQueue, requestData.PlayingItemPosition, @@ -139,10 +140,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("SetPlaylistItem")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlaySetPlaylistItem( + public async Task<ActionResult> SyncPlaySetPlaylistItem( [FromBody, Required] SetPlaylistItemRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new SetPlaylistItemGroupRequest(requestData.PlaylistItemId); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -157,10 +158,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("RemoveFromPlaylist")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlayRemoveFromPlaylist( + public async Task<ActionResult> SyncPlayRemoveFromPlaylist( [FromBody, Required] RemoveFromPlaylistRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new RemoveFromPlaylistGroupRequest(requestData.PlaylistItemIds); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -175,10 +176,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("MovePlaylistItem")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlayMovePlaylistItem( + public async Task<ActionResult> SyncPlayMovePlaylistItem( [FromBody, Required] MovePlaylistItemRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new MovePlaylistItemGroupRequest(requestData.PlaylistItemId, requestData.NewIndex); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -193,10 +194,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("Queue")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlayQueue( + public async Task<ActionResult> SyncPlayQueue( [FromBody, Required] QueueRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new QueueGroupRequest(requestData.ItemIds, requestData.Mode); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -210,9 +211,9 @@ namespace Jellyfin.Api.Controllers [HttpPost("Unpause")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlayUnpause() + public async Task<ActionResult> SyncPlayUnpause() { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new UnpauseGroupRequest(); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -226,9 +227,9 @@ namespace Jellyfin.Api.Controllers [HttpPost("Pause")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlayPause() + public async Task<ActionResult> SyncPlayPause() { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new PauseGroupRequest(); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -242,9 +243,9 @@ namespace Jellyfin.Api.Controllers [HttpPost("Stop")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlayStop() + public async Task<ActionResult> SyncPlayStop() { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new StopGroupRequest(); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -259,10 +260,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("Seek")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlaySeek( + public async Task<ActionResult> SyncPlaySeek( [FromBody, Required] SeekRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new SeekGroupRequest(requestData.PositionTicks); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -277,10 +278,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("Buffering")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlayBuffering( + public async Task<ActionResult> SyncPlayBuffering( [FromBody, Required] BufferRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new BufferGroupRequest( requestData.When, requestData.PositionTicks, @@ -299,10 +300,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("Ready")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlayReady( + public async Task<ActionResult> SyncPlayReady( [FromBody, Required] ReadyRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new ReadyGroupRequest( requestData.When, requestData.PositionTicks, @@ -321,10 +322,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("SetIgnoreWait")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlaySetIgnoreWait( + public async Task<ActionResult> SyncPlaySetIgnoreWait( [FromBody, Required] IgnoreWaitRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new IgnoreWaitGroupRequest(requestData.IgnoreWait); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -339,10 +340,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("NextItem")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlayNextItem( + public async Task<ActionResult> SyncPlayNextItem( [FromBody, Required] NextItemRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new NextItemGroupRequest(requestData.PlaylistItemId); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -357,10 +358,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("PreviousItem")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlayPreviousItem( + public async Task<ActionResult> SyncPlayPreviousItem( [FromBody, Required] PreviousItemRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new PreviousItemGroupRequest(requestData.PlaylistItemId); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -375,10 +376,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("SetRepeatMode")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlaySetRepeatMode( + public async Task<ActionResult> SyncPlaySetRepeatMode( [FromBody, Required] SetRepeatModeRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new SetRepeatModeGroupRequest(requestData.Mode); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -393,10 +394,10 @@ namespace Jellyfin.Api.Controllers [HttpPost("SetShuffleMode")] [ProducesResponseType(StatusCodes.Status204NoContent)] [Authorize(Policy = Policies.SyncPlayIsInGroup)] - public ActionResult SyncPlaySetShuffleMode( + public async Task<ActionResult> SyncPlaySetShuffleMode( [FromBody, Required] SetShuffleModeRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new SetShuffleModeGroupRequest(requestData.Mode); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); @@ -410,10 +411,10 @@ namespace Jellyfin.Api.Controllers /// <returns>A <see cref="NoContentResult"/> indicating success.</returns> [HttpPost("Ping")] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult SyncPlayPing( + public async Task<ActionResult> SyncPlayPing( [FromBody, Required] PingRequestDto requestData) { - var currentSession = RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request); + var currentSession = await RequestHelpers.GetSession(_sessionManager, _authorizationContext, Request).ConfigureAwait(false); var syncPlayRequest = new PingGroupRequest(requestData.Ping); _syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None); return NoContent(); diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index 94856e03ed..72b60ee570 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; @@ -75,17 +76,17 @@ namespace Jellyfin.Api.Helpers return true; } - internal static SessionInfo GetSession(ISessionManager sessionManager, IAuthorizationContext authContext, HttpRequest request) + internal static async Task<SessionInfo> GetSession(ISessionManager sessionManager, IAuthorizationContext authContext, HttpRequest request) { var authorization = authContext.GetAuthorizationInfo(request); var user = authorization.User; - var session = sessionManager.LogSessionActivity( + var session = await sessionManager.LogSessionActivity( authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, request.HttpContext.GetNormalizedRemoteIp(), - user); + user).ConfigureAwait(false); if (session == null) { @@ -95,6 +96,13 @@ namespace Jellyfin.Api.Helpers return session; } + internal static async Task<string> GetSessionId(ISessionManager sessionManager, IAuthorizationContext authContext, HttpRequest request) + { + var session = await GetSession(sessionManager, authContext, request).ConfigureAwait(false); + + return session.Id; + } + internal static QueryResult<BaseItemDto> CreateQueryResult( QueryResult<(BaseItem, ItemCounts)> result, DtoOptions dtoOptions, diff --git a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs index c942678d97..0d93ee2bf1 100644 --- a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs +++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs @@ -53,12 +53,13 @@ namespace Jellyfin.Server.Implementations.Devices } /// <inheritdoc /> - public DeviceOptions? GetDeviceOptions(string deviceId) + public async Task<DeviceOptions?> GetDeviceOptions(string deviceId) { - using var dbContext = _dbProvider.CreateContext(); - return dbContext.DeviceOptions + await using var dbContext = _dbProvider.CreateContext(); + return await dbContext.DeviceOptions .AsQueryable() - .FirstOrDefault(d => d.DeviceId == deviceId); + .FirstOrDefaultAsync(d => d.DeviceId == deviceId) + .ConfigureAwait(false); } /// <inheritdoc /> diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs index aa05ead8fd..0df0407942 100644 --- a/MediaBrowser.Controller/Devices/IDeviceManager.cs +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -50,6 +50,6 @@ namespace MediaBrowser.Controller.Devices Task UpdateDeviceOptions(string deviceId, DeviceOptions options); - DeviceOptions GetDeviceOptions(string deviceId); + Task<DeviceOptions> GetDeviceOptions(string deviceId); } } diff --git a/MediaBrowser.Controller/Net/ISessionContext.cs b/MediaBrowser.Controller/Net/ISessionContext.cs index a60dc2ea19..57938da1f4 100644 --- a/MediaBrowser.Controller/Net/ISessionContext.cs +++ b/MediaBrowser.Controller/Net/ISessionContext.cs @@ -1,5 +1,6 @@ #pragma warning disable CS1591 +using System.Threading.Tasks; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Session; using Microsoft.AspNetCore.Http; @@ -8,12 +9,12 @@ namespace MediaBrowser.Controller.Net { public interface ISessionContext { - SessionInfo GetSession(object requestContext); + Task<SessionInfo> GetSession(object requestContext); - User GetUser(object requestContext); + Task<User> GetUser(object requestContext); - SessionInfo GetSession(HttpContext requestContext); + Task<SessionInfo> GetSession(HttpContext requestContext); - User GetUser(HttpContext requestContext); + Task<User> GetUser(HttpContext requestContext); } } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 9eb486534c..630037570e 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -80,7 +80,7 @@ namespace MediaBrowser.Controller.Session /// <param name="deviceName">Name of the device.</param> /// <param name="remoteEndPoint">The remote end point.</param> /// <param name="user">The user.</param> - SessionInfo LogSessionActivity(string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, Jellyfin.Data.Entities.User user); + Task<SessionInfo> LogSessionActivity(string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, Jellyfin.Data.Entities.User user); /// <summary> /// Used to report that a session controller has connected. @@ -313,7 +313,7 @@ namespace MediaBrowser.Controller.Session /// <param name="deviceId">The device identifier.</param> /// <param name="remoteEndpoint">The remote endpoint.</param> /// <returns>SessionInfo.</returns> - SessionInfo GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint); + Task<SessionInfo> GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint); /// <summary> /// Gets the session by authentication token. @@ -323,7 +323,7 @@ namespace MediaBrowser.Controller.Session /// <param name="remoteEndpoint">The remote endpoint.</param> /// <param name="appVersion">The application version.</param> /// <returns>Task<SessionInfo>.</returns> - SessionInfo GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion); + Task<SessionInfo> GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion); /// <summary> /// Logouts the specified access token. -- cgit v1.2.3 From 3ebc0474343eb07defb6dd6f0e8bed707471e0a0 Mon Sep 17 00:00:00 2001 From: Patrick Barron <barronpm@gmail.com> Date: Sat, 10 Apr 2021 16:59:41 -0400 Subject: Convert UpdateUser to solely async --- .../LiveTv/Listings/SchedulesDirect.cs | 4 ++-- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- Jellyfin.Server.Implementations/Users/UserManager.cs | 17 ++++------------- MediaBrowser.Controller/Library/IUserManager.cs | 16 ++++------------ 4 files changed, 11 insertions(+), 28 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 1926e738f2..dd58ca8997 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -457,10 +457,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings } StringBuilder str = new StringBuilder("[", 1 + (programIds.Count * 13)); - foreach (ReadOnlySpan<char> i in programIds) + foreach (string i in programIds) { str.Append('"') - .Append(i.Slice(0, 10)) + .Append(i[..10]) .Append("\","); } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 678a27665b..50156d2b6f 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -293,7 +293,7 @@ namespace Emby.Server.Implementations.Session try { user.LastActivityDate = activityDate; - _userManager.UpdateUser(user); + await _userManager.UpdateUserAsync(user); } catch (DbUpdateConcurrencyException e) { diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index b6b45e69b5..4d847ec959 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -163,15 +163,6 @@ namespace Jellyfin.Server.Implementations.Users OnUserUpdated?.Invoke(this, new GenericEventArgs<User>(user)); } - /// <inheritdoc/> - public void UpdateUser(User user) - { - using var dbContext = _dbProvider.CreateContext(); - dbContext.Users.Update(user); - _users[user.Id] = user; - dbContext.SaveChanges(); - } - /// <inheritdoc/> public async Task UpdateUserAsync(User user) { @@ -271,9 +262,9 @@ namespace Jellyfin.Server.Implementations.Users } /// <inheritdoc/> - public void ResetEasyPassword(User user) + public Task ResetEasyPassword(User user) { - ChangeEasyPassword(user, string.Empty, null); + return ChangeEasyPassword(user, string.Empty, null); } /// <inheritdoc/> @@ -291,7 +282,7 @@ namespace Jellyfin.Server.Implementations.Users } /// <inheritdoc/> - public void ChangeEasyPassword(User user, string newPassword, string? newPasswordSha1) + public async Task ChangeEasyPassword(User user, string newPassword, string? newPasswordSha1) { if (newPassword != null) { @@ -304,7 +295,7 @@ namespace Jellyfin.Server.Implementations.Users } user.EasyPassword = newPasswordSha1; - UpdateUser(user); + await UpdateUserAsync(user); _eventManager.Publish(new UserPasswordChangedEventArgs(user)); } diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 6e267834b0..41dfe1b967 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -63,14 +63,6 @@ namespace MediaBrowser.Controller.Library /// <exception cref="ArgumentException"></exception> Task RenameUser(User user, string newName); - /// <summary> - /// Updates the user. - /// </summary> - /// <param name="user">The user.</param> - /// <exception cref="ArgumentNullException">user</exception> - /// <exception cref="ArgumentException"></exception> - void UpdateUser(User user); - /// <summary> /// Updates the user. /// </summary> @@ -108,7 +100,7 @@ namespace MediaBrowser.Controller.Library /// </summary> /// <param name="user">The user.</param> /// <returns>Task.</returns> - void ResetEasyPassword(User user); + Task ResetEasyPassword(User user); /// <summary> /// Changes the password. @@ -118,7 +110,7 @@ namespace MediaBrowser.Controller.Library /// <summary> /// Changes the easy password. /// </summary> - void ChangeEasyPassword(User user, string newPassword, string newPasswordSha1); + Task ChangeEasyPassword(User user, string newPassword, string newPasswordSha1); /// <summary> /// Gets the user dto. @@ -155,7 +147,7 @@ namespace MediaBrowser.Controller.Library /// <summary> /// This method updates the user's configuration. /// This is only included as a stopgap until the new API, using this internally is not recommended. - /// Instead, modify the user object directly, then call <see cref="UpdateUser"/>. + /// Instead, modify the user object directly, then call <see cref="UpdateUserAsync"/>. /// </summary> /// <param name="userId">The user's Id.</param> /// <param name="config">The request containing the new user configuration.</param> @@ -165,7 +157,7 @@ namespace MediaBrowser.Controller.Library /// <summary> /// This method updates the user's policy. /// This is only included as a stopgap until the new API, using this internally is not recommended. - /// Instead, modify the user object directly, then call <see cref="UpdateUser"/>. + /// Instead, modify the user object directly, then call <see cref="UpdateUserAsync"/>. /// </summary> /// <param name="userId">The user's Id.</param> /// <param name="policy">The request containing the new user policy.</param> -- cgit v1.2.3 From 75df6965a046967dca94912c9490fd4b4af9f0a6 Mon Sep 17 00:00:00 2001 From: Patrick Barron <barronpm@gmail.com> Date: Tue, 13 Apr 2021 20:01:21 -0400 Subject: Don't use database for QuickConnect --- .../QuickConnect/QuickConnectManager.cs | 71 +++++++++------------- .../Session/SessionManager.cs | 18 ------ .../QuickConnect/IQuickConnect.cs | 8 +++ 3 files changed, 38 insertions(+), 59 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs index 7bed06de36..9f639138a9 100644 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs @@ -1,16 +1,15 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Security.Cryptography; -using MediaBrowser.Common; using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.QuickConnect; -using MediaBrowser.Controller.Security; +using MediaBrowser.Controller.Session; using MediaBrowser.Model.QuickConnect; using Microsoft.Extensions.Logging; @@ -21,36 +20,26 @@ namespace Emby.Server.Implementations.QuickConnect /// </summary> public class QuickConnectManager : IQuickConnect, IDisposable { - private readonly RNGCryptoServiceProvider _rng = new RNGCryptoServiceProvider(); - private readonly ConcurrentDictionary<string, QuickConnectResult> _currentRequests = new ConcurrentDictionary<string, QuickConnectResult>(); + private readonly RNGCryptoServiceProvider _rng = new (); + private readonly ConcurrentDictionary<string, QuickConnectResult> _currentRequests = new (); + private readonly ConcurrentDictionary<string, (string, Guid)> _quickConnectTokens = new (); private readonly IServerConfigurationManager _config; private readonly ILogger<QuickConnectManager> _logger; - private readonly IAuthenticationRepository _authenticationRepository; - private readonly IAuthorizationContext _authContext; - private readonly IServerApplicationHost _appHost; + private readonly ISessionManager _sessionManager; /// <summary> /// Initializes a new instance of the <see cref="QuickConnectManager"/> class. /// Should only be called at server startup when a singleton is created. /// </summary> - /// <param name="config">Configuration.</param> - /// <param name="logger">Logger.</param> - /// <param name="appHost">Application host.</param> - /// <param name="authContext">Authentication context.</param> - /// <param name="authenticationRepository">Authentication repository.</param> - public QuickConnectManager( - IServerConfigurationManager config, - ILogger<QuickConnectManager> logger, - IServerApplicationHost appHost, - IAuthorizationContext authContext, - IAuthenticationRepository authenticationRepository) + /// <param name="config">The server configuration manager.</param> + /// <param name="logger">The logger.</param> + /// <param name="sessionManager">The session manager.</param> + public QuickConnectManager(IServerConfigurationManager config, ILogger<QuickConnectManager> logger, ISessionManager sessionManager) { _config = config; _logger = logger; - _appHost = appHost; - _authContext = authContext; - _authenticationRepository = authenticationRepository; + _sessionManager = sessionManager; ReloadConfiguration(); } @@ -138,6 +127,19 @@ namespace Emby.Server.Implementations.QuickConnect return result; } + public void AuthenticateRequest(AuthenticationRequest request, string token) + { + if (!_quickConnectTokens.TryGetValue(token, out var entry)) + { + throw new SecurityException("Unknown quick connect token"); + } + + request.UserId = entry.Item2; + _quickConnectTokens.Remove(token, out _); + + _sessionManager.AuthenticateQuickConnect(request, token); + } + /// <inheritdoc/> public string GenerateCode() { @@ -179,16 +181,7 @@ namespace Emby.Server.Implementations.QuickConnect var added = result.DateAdded ?? DateTime.UtcNow.Subtract(TimeSpan.FromMinutes(Timeout)); result.DateAdded = added.Subtract(TimeSpan.FromMinutes(Timeout - 1)); - _authenticationRepository.Create(new AuthenticationInfo - { - AppName = TokenName, - AccessToken = result.Authentication, - DateCreated = DateTime.UtcNow, - DeviceId = _appHost.SystemId, - DeviceName = _appHost.FriendlyName, - AppVersion = _appHost.ApplicationVersionString, - UserId = userId - }); + _quickConnectTokens[result.Authentication] = (TokenName, userId); _logger.LogDebug("Authorizing device with code {Code} to login as user {userId}", code, userId); @@ -198,19 +191,15 @@ namespace Emby.Server.Implementations.QuickConnect /// <inheritdoc/> public int DeleteAllDevices(Guid user) { - var raw = _authenticationRepository.Get(new AuthenticationInfoQuery() - { - DeviceId = _appHost.SystemId, - UserId = user - }); - - var tokens = raw.Items.Where(x => x.AppName.StartsWith(TokenName, StringComparison.Ordinal)); + var tokens = _quickConnectTokens + .Where(entry => entry.Value.Item1.StartsWith(TokenName, StringComparison.Ordinal) && entry.Value.Item2 == user) + .ToList(); var removed = 0; foreach (var token in tokens) { - _authenticationRepository.Delete(token); - _logger.LogDebug("Deleted token {AccessToken}", token.AccessToken); + _quickConnectTokens.Remove(token.Key, out _); + _logger.LogDebug("Deleted token {AccessToken}", token.Key); removed++; } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 50156d2b6f..99d2947f0e 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1441,24 +1441,6 @@ namespace Emby.Server.Implementations.Session public Task<AuthenticationResult> AuthenticateQuickConnect(AuthenticationRequest request, string token) { - var result = _authRepo.Get(new AuthenticationInfoQuery() - { - AccessToken = token, - DeviceId = _appHost.SystemId, - Limit = 1 - }); - - if (result.TotalRecordCount == 0) - { - throw new SecurityException("Unknown quick connect token"); - } - - var info = result.Items[0]; - request.UserId = info.UserId; - - // There's no need to keep the quick connect token in the database, as AuthenticateNewSessionInternal() issues a long lived token. - _authRepo.Delete(info); - return AuthenticateNewSessionInternal(request, false); } diff --git a/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs b/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs index 959a2d7712..ce8999e490 100644 --- a/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs +++ b/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs @@ -1,4 +1,5 @@ using System; +using MediaBrowser.Controller.Session; using MediaBrowser.Model.QuickConnect; namespace MediaBrowser.Controller.QuickConnect @@ -57,6 +58,13 @@ namespace MediaBrowser.Controller.QuickConnect /// <returns>Quick connect result.</returns> QuickConnectResult CheckRequestStatus(string secret); + /// <summary> + /// Authenticates a QuickConnect request. + /// </summary> + /// <param name="request">The request.</param> + /// <param name="token">The token.</param> + void AuthenticateRequest(AuthenticationRequest request, string token); + /// <summary> /// Authorizes a quick connect request to connect as the calling user. /// </summary> -- cgit v1.2.3 From 940c30081ef8b9290d9f3c24b5529b2e593f924c Mon Sep 17 00:00:00 2001 From: crobibero <cody@robibe.ro> Date: Thu, 22 Apr 2021 06:49:54 -0600 Subject: Mark PasswordSha1 as obsolete --- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- MediaBrowser.Controller/Session/AuthenticationRequest.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 10e28c33a5..6f21ec31e5 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1485,7 +1485,7 @@ namespace Emby.Server.Implementations.Session user = await _userManager.AuthenticateUser( request.Username, request.Password, - request.PasswordSha1, + null, request.RemoteEndPoint, true).ConfigureAwait(false); } diff --git a/MediaBrowser.Controller/Session/AuthenticationRequest.cs b/MediaBrowser.Controller/Session/AuthenticationRequest.cs index cc321fd22e..8c3ac58f2b 100644 --- a/MediaBrowser.Controller/Session/AuthenticationRequest.cs +++ b/MediaBrowser.Controller/Session/AuthenticationRequest.cs @@ -12,6 +12,7 @@ namespace MediaBrowser.Controller.Session public string Password { get; set; } + [Obsolete("Send full password in Password field")] public string PasswordSha1 { get; set; } public string App { get; set; } -- cgit v1.2.3 From 2e98de90628e9a4e42fb182f2d5a2a296acfd827 Mon Sep 17 00:00:00 2001 From: BaronGreenback <jimcartlidge@yahoo.co.uk> Date: Wed, 5 May 2021 12:51:14 +0100 Subject: Code Clean up: Convert to null-coalescing operator ?? (#5845) Co-authored-by: Cody Robibero <cody@robibe.ro> Co-authored-by: Patrick Barron <18354464+barronpm@users.noreply.github.com> --- Emby.Server.Implementations/ApplicationHost.cs | 10 +---- Emby.Server.Implementations/Dto/DtoService.cs | 15 ++------ .../Library/LibraryManager.cs | 5 +-- .../Library/Resolvers/TV/EpisodeResolver.cs | 14 ++----- .../LiveTv/EmbyTV/EmbyTV.cs | 10 ++--- .../LiveTv/Listings/SchedulesDirect.cs | 7 +--- .../LiveTv/LiveTvManager.cs | 15 ++------ .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 5 +-- .../Plugins/PluginManager.cs | 13 +------ .../ScheduledTasks/ScheduledTaskWorker.cs | 7 +--- .../Session/SessionManager.cs | 5 +-- Jellyfin.Api/Controllers/PluginsController.cs | 7 +--- Jellyfin.Api/Controllers/SearchController.cs | 5 +-- Jellyfin.Api/Helpers/StreamingHelpers.cs | 5 +-- MediaBrowser.Common/Net/IPHost.cs | 5 +-- MediaBrowser.Common/Plugins/BasePluginOfT.cs | 5 +-- MediaBrowser.Controller/Entities/BaseItem.cs | 26 ++++--------- MediaBrowser.Controller/Entities/UserView.cs | 5 +-- MediaBrowser.Controller/Library/ItemResolveArgs.cs | 5 +-- MediaBrowser.Controller/Playlists/Playlist.cs | 5 +-- .../Providers/MetadataRefreshOptions.cs | 5 +-- .../Providers/MetadataResult.cs | 16 +++----- .../Probing/ProbeResultNormalizer.cs | 45 ++++++---------------- .../Entities/ProviderIdsExtensions.cs | 5 +-- .../MediaInfo/FFProbeVideoInfo.cs | 5 +-- .../Subtitles/SubtitleManager.cs | 5 +-- RSSDP/SsdpCommunicationsServer.cs | 5 +-- 27 files changed, 60 insertions(+), 200 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 703f8d20da..75d8fc113d 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -335,10 +335,7 @@ namespace Emby.Server.Implementations { get { - if (_deviceId == null) - { - _deviceId = new DeviceId(ApplicationPaths, LoggerFactory); - } + _deviceId ??= new DeviceId(ApplicationPaths, LoggerFactory); return _deviceId.Value; } @@ -370,10 +367,7 @@ namespace Emby.Server.Implementations /// <returns>System.Object.</returns> protected object CreateInstanceSafe(Type type) { - if (_creatingInstances == null) - { - _creatingInstances = new List<Type>(); - } + _creatingInstances ??= new List<Type>(); if (_creatingInstances.IndexOf(type) != -1) { diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 54b18a8c80..4ae35039ab 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -665,10 +665,7 @@ namespace Emby.Server.Implementations.Dto var tag = GetImageCacheTag(item, image); if (!string.IsNullOrEmpty(image.BlurHash)) { - if (dto.ImageBlurHashes == null) - { - dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>(); - } + dto.ImageBlurHashes ??= new Dictionary<ImageType, Dictionary<string, string>>(); if (!dto.ImageBlurHashes.ContainsKey(image.Type)) { @@ -702,10 +699,7 @@ namespace Emby.Server.Implementations.Dto if (hashes.Count > 0) { - if (dto.ImageBlurHashes == null) - { - dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>(); - } + dto.ImageBlurHashes ??= new Dictionary<ImageType, Dictionary<string, string>>(); dto.ImageBlurHashes[imageType] = hashes; } @@ -898,10 +892,7 @@ namespace Emby.Server.Implementations.Dto dto.Taglines = new string[] { item.Tagline }; } - if (dto.Taglines == null) - { - dto.Taglines = Array.Empty<string>(); - } + dto.Taglines ??= Array.Empty<string>(); } dto.Type = item.GetBaseItemKind(); diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index a44edad161..4d207471a2 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -176,10 +176,7 @@ namespace Emby.Server.Implementations.Library { lock (_rootFolderSyncLock) { - if (_rootFolder == null) - { - _rootFolder = CreateRootFolder(); - } + _rootFolder ??= CreateRootFolder(); } } diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index 9b4cd7a3df..6f29bc6494 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -35,14 +35,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV return null; } - var season = parent as Season; - // Just in case the user decided to nest episodes. // Not officially supported but in some cases we can handle it. - if (season == null) - { - season = parent.GetParents().OfType<Season>().FirstOrDefault(); - } + + var season = parent as Season ?? parent.GetParents().OfType<Season>().FirstOrDefault(); // If the parent is a Season or Series and the parent is not an extras folder, then this is an Episode if the VideoResolver returns something // Also handle flat tv folders @@ -55,11 +51,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV if (episode != null) { - var series = parent as Series; - if (series == null) - { - series = parent.GetParents().OfType<Series>().FirstOrDefault(); - } + var series = parent as Series ?? parent.GetParents().OfType<Series>().FirstOrDefault(); if (series != null) { diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 665fbfa0fe..28a2095e16 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -2237,14 +2237,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var enabledTimersForSeries = new List<TimerInfo>(); foreach (var timer in allTimers) { - var existingTimer = _timerProvider.GetTimer(timer.Id); - - if (existingTimer == null) - { - existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId) + var existingTimer = _timerProvider.GetTimer(timer.Id) + ?? (string.IsNullOrWhiteSpace(timer.ProgramId) ? null - : _timerProvider.GetTimerByProgramId(timer.ProgramId); - } + : _timerProvider.GetTimerByProgramId(timer.ProgramId)); if (existingTimer == null) { diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 1926e738f2..9af65cabba 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -787,14 +787,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings { var channelNumber = GetChannelNumber(channel); - var station = allStations.Find(item => string.Equals(item.stationID, channel.stationID, StringComparison.OrdinalIgnoreCase)); - if (station == null) - { - station = new ScheduleDirect.Station + var station = allStations.Find(item => string.Equals(item.stationID, channel.stationID, StringComparison.OrdinalIgnoreCase)) + ?? new ScheduleDirect.Station { stationID = channel.stationID }; - } var channelInfo = new ChannelInfo { diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 63a3146aa9..1145d8aa1e 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -987,10 +987,7 @@ namespace Emby.Server.Implementations.LiveTv var externalProgramId = programTuple.Item2; string externalSeriesId = programTuple.Item3; - if (timerList == null) - { - timerList = (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items; - } + timerList ??= (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items; var timer = timerList.FirstOrDefault(i => string.Equals(i.ProgramId, externalProgramId, StringComparison.OrdinalIgnoreCase)); var foundSeriesTimer = false; @@ -1018,10 +1015,7 @@ namespace Emby.Server.Implementations.LiveTv continue; } - if (seriesTimerList == null) - { - seriesTimerList = (await GetSeriesTimersInternal(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false)).Items; - } + seriesTimerList ??= (await GetSeriesTimersInternal(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false)).Items; var seriesTimer = seriesTimerList.FirstOrDefault(i => string.Equals(i.SeriesId, externalSeriesId, StringComparison.OrdinalIgnoreCase)); @@ -1974,10 +1968,7 @@ namespace Emby.Server.Implementations.LiveTv }; } - if (service == null) - { - service = _services[0]; - } + service ??= _services[0]; var info = await service.GetNewTimerDefaultsAsync(cancellationToken, programInfo).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index c32ca2fb69..4aa5832b18 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -421,10 +421,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun string audioCodec = channelInfo.AudioCodec; - if (!videoBitrate.HasValue) - { - videoBitrate = isHd ? 15000000 : 2000000; - } + videoBitrate ??= isHd ? 15000000 : 2000000; int? audioBitrate = isHd ? 448000 : 192000; diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index fd2ee6b7a7..14df20936c 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -44,12 +44,7 @@ namespace Emby.Server.Implementations.Plugins { get { - if (_httpClientFactory == null) - { - _httpClientFactory = _appHost.Resolve<IHttpClientFactory>(); - } - - return _httpClientFactory; + return _httpClientFactory ?? (_httpClientFactory = _appHost.Resolve<IHttpClientFactory>()); } } @@ -276,11 +271,7 @@ namespace Emby.Server.Implementations.Plugins // If no version is given, return the current instance. var plugins = _plugins.Where(p => p.Id.Equals(id)).ToList(); - plugin = plugins.FirstOrDefault(p => p.Instance != null); - if (plugin == null) - { - plugin = plugins.OrderByDescending(p => p.Version).FirstOrDefault(); - } + plugin = plugins.FirstOrDefault(p => p.Instance != null) ?? plugins.OrderByDescending(p => p.Version).FirstOrDefault(); } else { diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 61dccaa19f..101d9b5377 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -301,12 +301,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { get { - if (_id == null) - { - _id = ScheduledTask.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture); - } - - return _id; + return _id ??= ScheduledTask.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture); } } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 6f21ec31e5..6844152ea5 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1475,10 +1475,7 @@ namespace Emby.Server.Implementations.Session user = _userManager.GetUserById(request.UserId); } - if (user == null) - { - user = _userManager.GetUserByName(request.Username); - } + user ??= _userManager.GetUserByName(request.Username); if (enforcePassword) { diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index adec86a102..7a61307195 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -207,12 +207,7 @@ namespace Jellyfin.Api.Controllers var plugins = _pluginManager.Plugins.Where(p => p.Id.Equals(pluginId)); // Select the un-instanced one first. - var plugin = plugins.FirstOrDefault(p => p.Instance == null); - if (plugin == null) - { - // Then by the status. - plugin = plugins.OrderBy(p => p.Manifest.Status).FirstOrDefault(); - } + var plugin = plugins.FirstOrDefault(p => p.Instance == null) ?? plugins.OrderBy(p => p.Manifest.Status).FirstOrDefault(); if (plugin != null) { diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs index 6c22050a79..73bdf90182 100644 --- a/Jellyfin.Api/Controllers/SearchController.cs +++ b/Jellyfin.Api/Controllers/SearchController.cs @@ -228,10 +228,7 @@ namespace Jellyfin.Api.Controllers itemWithImage = GetParentWithImage<Series>(item, ImageType.Thumb); } - if (itemWithImage == null) - { - itemWithImage = GetParentWithImage<BaseItem>(item, ImageType.Thumb); - } + itemWithImage ??= GetParentWithImage<BaseItem>(item, ImageType.Thumb); if (itemWithImage != null) { diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 583e613b4c..8cffe9c4c9 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -292,10 +292,7 @@ namespace Jellyfin.Api.Helpers } } - if (profile == null) - { - profile = dlnaManager.GetDefaultProfile(); - } + profile ??= dlnaManager.GetDefaultProfile(); var audioCodec = state.ActualOutputAudioCodec; diff --git a/MediaBrowser.Common/Net/IPHost.cs b/MediaBrowser.Common/Net/IPHost.cs index fb3ef9b12b..7156ce6188 100644 --- a/MediaBrowser.Common/Net/IPHost.cs +++ b/MediaBrowser.Common/Net/IPHost.cs @@ -400,10 +400,7 @@ namespace MediaBrowser.Common.Net private bool ResolveHost() { // When was the last time we resolved? - if (_lastResolved == null) - { - _lastResolved = DateTime.UtcNow; - } + _lastResolved ??= DateTime.UtcNow; // If we haven't resolved before, or our timer has run out... if ((_addresses.Length == 0 && !Resolved) || (DateTime.UtcNow > _lastResolved.Value.AddMinutes(Timeout))) diff --git a/MediaBrowser.Common/Plugins/BasePluginOfT.cs b/MediaBrowser.Common/Plugins/BasePluginOfT.cs index 99c226f509..e074cc6a01 100644 --- a/MediaBrowser.Common/Plugins/BasePluginOfT.cs +++ b/MediaBrowser.Common/Plugins/BasePluginOfT.cs @@ -105,10 +105,7 @@ namespace MediaBrowser.Common.Plugins { lock (_configurationSyncLock) { - if (_configuration == null) - { - _configuration = LoadConfiguration(); - } + _configuration ??= LoadConfiguration(); } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 1b69c66469..32ae154983 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -106,15 +106,10 @@ namespace MediaBrowser.Controller.Entities { get { - if (_themeSongIds == null) - { - _themeSongIds = GetExtras() - .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeSong) - .Select(song => song.Id) - .ToArray(); - } - - return _themeSongIds; + return _themeSongIds ??= GetExtras() + .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeSong) + .Select(song => song.Id) + .ToArray(); } private set @@ -128,15 +123,10 @@ namespace MediaBrowser.Controller.Entities { get { - if (_themeVideoIds == null) - { - _themeVideoIds = GetExtras() - .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeVideo) - .Select(song => song.Id) - .ToArray(); - } - - return _themeVideoIds; + return _themeVideoIds ??= GetExtras() + .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeVideo) + .Select(song => song.Id) + .ToArray(); } private set diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index b1da4d64cb..fec83dd941 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -75,10 +75,7 @@ namespace MediaBrowser.Controller.Entities public override List<BaseItem> GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query) { - if (query == null) - { - query = new InternalItemsQuery(user); - } + query ??= new InternalItemsQuery(user); query.EnableTotalRecordCount = false; var result = GetItemList(query); diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs index f86f7df25e..f9086066d7 100644 --- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs +++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs @@ -147,10 +147,7 @@ namespace MediaBrowser.Controller.Library throw new ArgumentException("The path was empty or null.", nameof(path)); } - if (AdditionalLocations == null) - { - AdditionalLocations = new List<string>(); - } + AdditionalLocations ??= new List<string>(); AdditionalLocations.Add(path); } diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index c9c168c4c6..3c93cfc79c 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -126,10 +126,7 @@ namespace MediaBrowser.Controller.Playlists private List<BaseItem> GetPlayableItems(User user, InternalItemsQuery query) { - if (query == null) - { - query = new InternalItemsQuery(user); - } + query ??= new InternalItemsQuery(user); query.IsFolder = false; diff --git a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs index b92b837012..db0ef70728 100644 --- a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs +++ b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs @@ -45,10 +45,7 @@ namespace MediaBrowser.Controller.Providers if (copy.RefreshPaths != null && copy.RefreshPaths.Length > 0) { - if (RefreshPaths == null) - { - RefreshPaths = Array.Empty<string>(); - } + RefreshPaths ??= Array.Empty<string>(); RefreshPaths = copy.RefreshPaths.ToArray(); } diff --git a/MediaBrowser.Controller/Providers/MetadataResult.cs b/MediaBrowser.Controller/Providers/MetadataResult.cs index 864cb30506..98c7eadfe0 100644 --- a/MediaBrowser.Controller/Providers/MetadataResult.cs +++ b/MediaBrowser.Controller/Providers/MetadataResult.cs @@ -37,10 +37,7 @@ namespace MediaBrowser.Controller.Providers public void AddPerson(PersonInfo p) { - if (People == null) - { - People = new List<PersonInfo>(); - } + People ??= new List<PersonInfo>(); PeopleHelper.AddPerson(People, p); } @@ -54,16 +51,15 @@ namespace MediaBrowser.Controller.Providers { People = new List<PersonInfo>(); } - - People.Clear(); + else + { + People.Clear(); + } } public UserItemData GetOrAddUserData(string userId) { - if (UserDataList == null) - { - UserDataList = new List<UserItemData>(); - } + UserDataList ??= new List<UserItemData>(); UserItemData userData = null; diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index ee2e5fcdee..2e96f8cb0f 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -1187,43 +1187,28 @@ namespace MediaBrowser.MediaEncoding.Probing FetchStudios(audio, tags, "label"); // These support mulitple values, but for now we only store the first. - var mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Artist Id")); - if (mb == null) - { - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMARTISTID")); - } + var mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Artist Id")) + ?? GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMARTISTID")); audio.SetProviderId(MetadataProvider.MusicBrainzAlbumArtist, mb); - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Artist Id")); - if (mb == null) - { - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ARTISTID")); - } + mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Artist Id")) + ?? GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ARTISTID")); audio.SetProviderId(MetadataProvider.MusicBrainzArtist, mb); - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Id")); - if (mb == null) - { - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMID")); - } + mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Album Id")) + ?? GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_ALBUMID")); audio.SetProviderId(MetadataProvider.MusicBrainzAlbum, mb); - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Group Id")); - if (mb == null) - { - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASEGROUPID")); - } + mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Group Id")) + ?? GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASEGROUPID")); audio.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, mb); - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Track Id")); - if (mb == null) - { - mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASETRACKID")); - } + mb = GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MusicBrainz Release Track Id")) + ?? GetMultipleMusicBrainzId(FFProbeHelpers.GetDictionaryValue(tags, "MUSICBRAINZ_RELEASETRACKID")); audio.SetProviderId(MetadataProvider.MusicBrainzTrack, mb); } @@ -1290,15 +1275,7 @@ namespace MediaBrowser.MediaEncoding.Probing private IEnumerable<string> GetSplitWhitelist() { - if (_splitWhiteList == null) - { - _splitWhiteList = new List<string> - { - "AC/DC" - }; - } - - return _splitWhiteList; + return _splitWhiteList ??= new List<string> { "AC/DC" }; } /// <summary> diff --git a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs index 09d14dc6a4..ce4b0ec92e 100644 --- a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs +++ b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs @@ -123,10 +123,7 @@ namespace MediaBrowser.Model.Entities else { // Ensure it exists - if (instance.ProviderIds == null) - { - instance.ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - } + instance.ProviderIds ??= new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); instance.ProviderIds[name] = value; } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 74849a5221..f049cc81ff 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -111,10 +111,7 @@ namespace MediaBrowser.Providers.MediaInfo } } - if (streamFileNames == null) - { - streamFileNames = Array.Empty<string>(); - } + streamFileNames ??= Array.Empty<string>(); mediaInfoResult = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 1f3d9acff2..8d62343cb1 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -256,10 +256,7 @@ namespace MediaBrowser.Providers.Subtitles } catch (Exception ex) { - if (exceptionToThrow == null) - { - exceptionToThrow = ex; - } + exceptionToThrow ??= ex; } finally { diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index 8f1f0fa613..f448ad38bb 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -415,10 +415,7 @@ namespace Rssdp.Infrastructure { lock (_SendSocketSynchroniser) { - if (_sendSockets == null) - { - _sendSockets = CreateSocketAndListenForResponsesAsync(); - } + _sendSockets ??= CreateSocketAndListenForResponsesAsync(); } } } -- cgit v1.2.3 From f53aa55bdbcdf83c527648a9fc3c89191039ecd1 Mon Sep 17 00:00:00 2001 From: crobibero <cody@robibe.ro> Date: Wed, 12 May 2021 13:32:54 -0600 Subject: Don't logout if deviceId is null. --- .../Session/SessionManager.cs | 31 ++++++++++++---------- 1 file changed, 17 insertions(+), 14 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 6844152ea5..a1c8fae6f6 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1540,23 +1540,26 @@ namespace Emby.Server.Implementations.Session Limit = 1 }).Items.FirstOrDefault(); - var allExistingForDevice = _authRepo.Get( - new AuthenticationInfoQuery - { - DeviceId = deviceId - }).Items; - - foreach (var auth in allExistingForDevice) + if (!string.IsNullOrEmpty(deviceId)) { - if (existing == null || !string.Equals(auth.AccessToken, existing.AccessToken, StringComparison.Ordinal)) - { - try + var allExistingForDevice = _authRepo.Get( + new AuthenticationInfoQuery { - Logout(auth); - } - catch (Exception ex) + DeviceId = deviceId + }).Items; + + foreach (var auth in allExistingForDevice) + { + if (existing == null || !string.Equals(auth.AccessToken, existing.AccessToken, StringComparison.Ordinal)) { - _logger.LogError(ex, "Error while logging out."); + try + { + Logout(auth); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error while logging out."); + } } } } -- cgit v1.2.3 From 7e8428e588b3f0a0574da44081098c64fe1a47d7 Mon Sep 17 00:00:00 2001 From: Bond_009 <bond.009@outlook.com> Date: Thu, 20 May 2021 21:28:18 +0200 Subject: Enable nullable reference types for Emby.Server.Implementations --- .../AppBase/BaseApplicationPaths.cs | 8 ++------ .../AppBase/BaseConfigurationManager.cs | 2 ++ .../AppBase/ConfigurationHelper.cs | 2 -- Emby.Server.Implementations/ApplicationHost.cs | 2 ++ .../Channels/ChannelManager.cs | 2 ++ .../Collections/CollectionImageProvider.cs | 4 ++-- .../Collections/CollectionManager.cs | 2 ++ .../Configuration/ServerConfigurationManager.cs | 2 ++ .../Cryptography/CryptographyProvider.cs | 2 -- .../Data/BaseSqliteRepository.cs | 2 ++ .../Data/ManagedConnection.cs | 2 +- Emby.Server.Implementations/Data/SqliteExtensions.cs | 1 + .../Data/SqliteItemRepository.cs | 2 ++ .../Data/SqliteUserDataRepository.cs | 2 ++ Emby.Server.Implementations/Data/TypeMapper.cs | 6 +++--- Emby.Server.Implementations/Devices/DeviceId.cs | 2 ++ Emby.Server.Implementations/Devices/DeviceManager.cs | 2 ++ Emby.Server.Implementations/Dto/DtoService.cs | 2 ++ .../Emby.Server.Implementations.csproj | 1 + .../EntryPoints/ExternalPortForwarding.cs | 2 ++ .../EntryPoints/LibraryChangedNotifier.cs | 2 ++ .../EntryPoints/RecordingNotifier.cs | 2 ++ .../EntryPoints/UdpServerEntryPoint.cs | 2 -- .../EntryPoints/UserDataChangeNotifier.cs | 2 ++ .../HttpServer/Security/AuthorizationContext.cs | 20 ++++++++++---------- .../HttpServer/Security/SessionContext.cs | 4 ++-- .../HttpServer/WebSocketConnection.cs | 2 -- .../HttpServer/WebSocketManager.cs | 2 ++ Emby.Server.Implementations/IO/FileRefresher.cs | 2 ++ Emby.Server.Implementations/IO/LibraryMonitor.cs | 2 ++ Emby.Server.Implementations/IO/ManagedFileSystem.cs | 6 +++--- .../IO/MbLinkShortcutHandler.cs | 2 +- Emby.Server.Implementations/IO/StreamHelper.cs | 2 +- Emby.Server.Implementations/IStartupOptions.cs | 1 - .../Images/BaseDynamicImageProvider.cs | 2 ++ .../Images/CollectionFolderImageProvider.cs | 2 ++ .../Images/DynamicImageProvider.cs | 2 ++ .../Images/FolderImageProvider.cs | 2 ++ .../Images/GenreImageProvider.cs | 2 ++ .../Images/PlaylistImageProvider.cs | 2 ++ .../Library/ExclusiveLiveStream.cs | 2 ++ .../Library/IgnorePatterns.cs | 2 -- .../Library/LibraryManager.cs | 2 ++ .../Library/LiveStreamHelper.cs | 2 ++ .../Library/MediaSourceManager.cs | 2 ++ .../Library/MediaStreamSelector.cs | 2 ++ Emby.Server.Implementations/Library/MusicManager.cs | 2 ++ .../Library/PathExtensions.cs | 2 -- .../Library/ResolverHelper.cs | 2 -- .../Library/Resolvers/Audio/AudioResolver.cs | 2 ++ .../Library/Resolvers/Audio/MusicAlbumResolver.cs | 2 ++ .../Library/Resolvers/Audio/MusicArtistResolver.cs | 2 ++ .../Library/Resolvers/BaseVideoResolver.cs | 2 ++ .../Library/Resolvers/Books/BookResolver.cs | 2 ++ .../Library/Resolvers/FolderResolver.cs | 2 ++ .../Library/Resolvers/ItemResolver.cs | 2 ++ .../Library/Resolvers/Movies/BoxSetResolver.cs | 2 ++ .../Library/Resolvers/Movies/MovieResolver.cs | 2 ++ .../Library/Resolvers/PhotoAlbumResolver.cs | 2 ++ .../Library/Resolvers/PhotoResolver.cs | 2 ++ .../Library/Resolvers/PlaylistResolver.cs | 2 ++ .../Library/Resolvers/SpecialFolderResolver.cs | 2 ++ .../Library/Resolvers/TV/EpisodeResolver.cs | 2 ++ .../Library/Resolvers/TV/SeasonResolver.cs | 2 ++ .../Library/Resolvers/TV/SeriesResolver.cs | 2 ++ .../Library/Resolvers/VideoResolver.cs | 2 ++ Emby.Server.Implementations/Library/SearchEngine.cs | 2 ++ .../Library/UserDataManager.cs | 2 ++ .../Library/UserViewManager.cs | 2 ++ .../LiveTv/EmbyTV/DirectRecorder.cs | 2 ++ Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs | 2 ++ .../LiveTv/EmbyTV/EncodedRecorder.cs | 2 ++ .../LiveTv/EmbyTV/EpgChannelData.cs | 7 +++---- .../LiveTv/EmbyTV/ItemDataProvider.cs | 2 ++ .../LiveTv/EmbyTV/RecordingHelper.cs | 2 ++ .../LiveTv/EmbyTV/TimerManager.cs | 2 ++ .../LiveTv/Listings/SchedulesDirect.cs | 2 ++ .../LiveTv/Listings/XmlTvListingsProvider.cs | 2 ++ .../LiveTv/LiveTvDtoService.cs | 2 ++ Emby.Server.Implementations/LiveTv/LiveTvManager.cs | 2 ++ .../LiveTv/LiveTvMediaSourceProvider.cs | 2 ++ .../LiveTv/TunerHosts/BaseTunerHost.cs | 2 ++ .../LiveTv/TunerHosts/HdHomerun/Channels.cs | 2 ++ .../LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs | 2 ++ .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 2 ++ .../LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs | 2 ++ .../TunerHosts/HdHomerun/HdHomerunUdpStream.cs | 2 ++ .../LiveTv/TunerHosts/LiveStream.cs | 2 ++ .../LiveTv/TunerHosts/M3UTunerHost.cs | 2 ++ .../LiveTv/TunerHosts/M3uParser.cs | 2 ++ .../LiveTv/TunerHosts/SharedHttpStream.cs | 2 ++ .../Localization/LocalizationManager.cs | 2 ++ .../MediaEncoder/EncodingManager.cs | 2 ++ Emby.Server.Implementations/Net/SocketFactory.cs | 2 ++ Emby.Server.Implementations/Net/UdpSocket.cs | 2 ++ .../Playlists/PlaylistManager.cs | 2 ++ Emby.Server.Implementations/Plugins/PluginManager.cs | 2 -- .../QuickConnect/QuickConnectManager.cs | 2 ++ .../ScheduledTasks/ScheduledTaskWorker.cs | 2 ++ .../ScheduledTasks/TaskManager.cs | 2 ++ .../ScheduledTasks/Tasks/ChapterImagesTask.cs | 6 ++++-- .../ScheduledTasks/Tasks/CleanActivityLogTask.cs | 4 ++-- .../ScheduledTasks/Triggers/DailyTrigger.cs | 2 ++ .../ScheduledTasks/Triggers/IntervalTrigger.cs | 2 ++ .../ScheduledTasks/Triggers/StartupTrigger.cs | 2 ++ .../ScheduledTasks/Triggers/WeeklyTrigger.cs | 2 ++ .../Security/AuthenticationRepository.cs | 2 ++ .../Serialization/MyXmlSerializer.cs | 10 ++++++---- .../Session/SessionManager.cs | 2 ++ .../Session/SessionWebSocketListener.cs | 2 ++ .../Session/WebSocketController.cs | 1 - .../Sorting/AiredEpisodeOrderComparer.cs | 2 +- .../Sorting/AlbumArtistComparer.cs | 4 ++-- Emby.Server.Implementations/Sorting/AlbumComparer.cs | 4 ++-- .../Sorting/ArtistComparer.cs | 4 ++-- .../Sorting/CommunityRatingComparer.cs | 2 +- .../Sorting/CriticRatingComparer.cs | 6 +++--- .../Sorting/DateCreatedComparer.cs | 2 +- .../Sorting/DateLastMediaAddedComparer.cs | 1 + .../Sorting/DatePlayedComparer.cs | 2 ++ .../Sorting/IsFavoriteOrLikeComparer.cs | 1 + .../Sorting/IsFolderComparer.cs | 6 +++--- .../Sorting/IsPlayedComparer.cs | 2 ++ .../Sorting/IsUnplayedComparer.cs | 2 ++ Emby.Server.Implementations/Sorting/NameComparer.cs | 2 +- .../Sorting/OfficialRatingComparer.cs | 2 +- .../Sorting/PlayCountComparer.cs | 2 ++ .../Sorting/PremiereDateComparer.cs | 9 +++++++-- .../Sorting/ProductionYearComparer.cs | 9 +++++++-- .../Sorting/RandomComparer.cs | 2 +- .../Sorting/RuntimeComparer.cs | 2 ++ .../Sorting/SeriesSortNameComparer.cs | 2 ++ .../Sorting/SortNameComparer.cs | 2 ++ .../Sorting/StartDateComparer.cs | 2 ++ .../Sorting/StudioComparer.cs | 2 ++ Emby.Server.Implementations/SyncPlay/Group.cs | 2 ++ .../SyncPlay/SyncPlayManager.cs | 2 ++ Emby.Server.Implementations/TV/TVSeriesManager.cs | 2 ++ Emby.Server.Implementations/Udp/UdpServer.cs | 2 ++ .../Updates/InstallationManager.cs | 2 ++ Jellyfin.Api/Controllers/DynamicHlsController.cs | 4 ++-- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 4 +++- .../Models/PlaybackDtos/TranscodingThrottler.cs | 3 ++- Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs | 11 +++++++---- .../Extensions/StringExtensions.cs | 1 + MediaBrowser.Controller/Net/ISessionContext.cs | 4 ++-- MediaBrowser.Controller/Sorting/IBaseItemComparer.cs | 2 +- MediaBrowser.Model/IO/IFileSystem.cs | 7 +++---- MediaBrowser.Model/IO/IShortcutHandler.cs | 2 +- MediaBrowser.Model/IO/IStreamHelper.cs | 2 +- MediaBrowser.Model/Tasks/ITaskTrigger.cs | 2 +- 151 files changed, 300 insertions(+), 99 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index 660bbb2deb..6edfad575a 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.AppBase CachePath = cacheDirectoryPath; WebPath = webDirectoryPath; - DataPath = Path.Combine(ProgramDataPath, "data"); + _dataPath = Directory.CreateDirectory(Path.Combine(ProgramDataPath, "data")).FullName; } /// <summary> @@ -55,11 +55,7 @@ namespace Emby.Server.Implementations.AppBase /// Gets the folder path to the data directory. /// </summary> /// <value>The data directory.</value> - public string DataPath - { - get => _dataPath; - private set => _dataPath = Directory.CreateDirectory(value).FullName; - } + public string DataPath => _dataPath; /// <inheritdoc /> public string VirtualDataPath => "%AppDataPath%"; diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs index 4f72c8ce15..8c919db431 100644 --- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs +++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Concurrent; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs index 29bac66340..de770f59ec 100644 --- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs +++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.IO; using System.Linq; diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 75d8fc113d..82995deb30 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 7324b0ee9f..448f124034 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Globalization; diff --git a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs index c69a07e83e..ca84094024 100644 --- a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs +++ b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs @@ -82,9 +82,9 @@ namespace Emby.Server.Implementations.Collections return null; }) .Where(i => i != null) - .GroupBy(x => x.Id) + .GroupBy(x => x!.Id) // We removed the null values .Select(x => x.First()) - .ToList(); + .ToList()!; // Again... the list doesn't contain any null values } /// <inheritdoc /> diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index c56f334481..82d80fc83c 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs index 7a8ed8c29f..ff5602f243 100644 --- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Globalization; using System.IO; diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 12a9e44e70..4a9b280852 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Collections.Generic; using System.Security.Cryptography; diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index c331a61124..6f23a0888e 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Data/ManagedConnection.cs b/Emby.Server.Implementations/Data/ManagedConnection.cs index 5c094ddd2d..10c6f837e6 100644 --- a/Emby.Server.Implementations/Data/ManagedConnection.cs +++ b/Emby.Server.Implementations/Data/ManagedConnection.cs @@ -9,7 +9,7 @@ namespace Emby.Server.Implementations.Data { public class ManagedConnection : IDisposable { - private SQLiteDatabaseConnection _db; + private SQLiteDatabaseConnection? _db; private readonly SemaphoreSlim _writeLock; private bool _disposed = false; diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs index a8f3feb589..e532825af5 100644 --- a/Emby.Server.Implementations/Data/SqliteExtensions.cs +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index b3d8860a9e..5b4bbb3395 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index e0ebd0a6cf..1756bcae08 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Data/TypeMapper.cs b/Emby.Server.Implementations/Data/TypeMapper.cs index 7044b1d194..7f1306d15a 100644 --- a/Emby.Server.Implementations/Data/TypeMapper.cs +++ b/Emby.Server.Implementations/Data/TypeMapper.cs @@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.Data /// This holds all the types in the running assemblies /// so that we can de-serialize properly when we don't have strong types. /// </summary> - private readonly ConcurrentDictionary<string, Type> _typeMap = new ConcurrentDictionary<string, Type>(); + private readonly ConcurrentDictionary<string, Type?> _typeMap = new ConcurrentDictionary<string, Type?>(); /// <summary> /// Gets the type. @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Data /// <param name="typeName">Name of the type.</param> /// <returns>Type.</returns> /// <exception cref="ArgumentNullException"><c>typeName</c> is null.</exception> - public Type GetType(string typeName) + public Type? GetType(string typeName) { if (string.IsNullOrEmpty(typeName)) { @@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Data /// </summary> /// <param name="typeName">Name of the type.</param> /// <returns>Type.</returns> - private Type LookupType(string typeName) + private Type? LookupType(string typeName) { return AppDomain.CurrentDomain.GetAssemblies() .Select(a => a.GetType(typeName)) diff --git a/Emby.Server.Implementations/Devices/DeviceId.cs b/Emby.Server.Implementations/Devices/DeviceId.cs index fa6ac95fd3..3d15b3e768 100644 --- a/Emby.Server.Implementations/Devices/DeviceId.cs +++ b/Emby.Server.Implementations/Devices/DeviceId.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index da5047d244..2637addce7 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 4ae35039ab..7411239a1e 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 14f6f565c4..113863519b 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -44,6 +44,7 @@ <GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateDocumentationFile>true</GenerateDocumentationFile> <TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'">true</TreatWarningsAsErrors> + <Nullable>enable</Nullable> <!-- https://github.com/microsoft/ApplicationInsights-dotnet/issues/2047 --> <NoWarn>AD0001</NoWarn> <AnalysisMode Condition=" '$(Configuration)' == 'Debug' ">AllEnabledByDefault</AnalysisMode> diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index 14201ead29..cc3e4a2c24 100644 --- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index ae1b51b4c3..5bb4100ba9 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index 824bb85f44..e0ca02d986 100644 --- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index 3624e079f5..211941f443 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Net.Sockets; using System.Threading; diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index 1989e9ed25..332fb33858 100644 --- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index fbf9254d16..c87f7dbbde 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.HttpServer.Security { if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached)) { - return (AuthorizationInfo)cached; + return (AuthorizationInfo)cached!; // Cache should never contain null } return GetAuthorization(requestContext); @@ -55,15 +55,15 @@ namespace Emby.Server.Implementations.HttpServer.Security } private AuthorizationInfo GetAuthorizationInfoFromDictionary( - in Dictionary<string, string> auth, + in Dictionary<string, string>? auth, in IHeaderDictionary headers, in IQueryCollection queryString) { - string deviceId = null; - string device = null; - string client = null; - string version = null; - string token = null; + string? deviceId = null; + string? device = null; + string? client = null; + string? version = null; + string? token = null; if (auth != null) { @@ -206,7 +206,7 @@ namespace Emby.Server.Implementations.HttpServer.Security /// </summary> /// <param name="httpReq">The HTTP req.</param> /// <returns>Dictionary{System.StringSystem.String}.</returns> - private Dictionary<string, string> GetAuthorizationDictionary(HttpContext httpReq) + private Dictionary<string, string>? GetAuthorizationDictionary(HttpContext httpReq) { var auth = httpReq.Request.Headers["X-Emby-Authorization"]; @@ -223,7 +223,7 @@ namespace Emby.Server.Implementations.HttpServer.Security /// </summary> /// <param name="httpReq">The HTTP req.</param> /// <returns>Dictionary{System.StringSystem.String}.</returns> - private Dictionary<string, string> GetAuthorizationDictionary(HttpRequest httpReq) + private Dictionary<string, string>? GetAuthorizationDictionary(HttpRequest httpReq) { var auth = httpReq.Headers["X-Emby-Authorization"]; @@ -240,7 +240,7 @@ namespace Emby.Server.Implementations.HttpServer.Security /// </summary> /// <param name="authorizationHeader">The authorization header.</param> /// <returns>Dictionary{System.StringSystem.String}.</returns> - private Dictionary<string, string> GetAuthorization(ReadOnlySpan<char> authorizationHeader) + private Dictionary<string, string>? GetAuthorization(ReadOnlySpan<char> authorizationHeader) { if (authorizationHeader == null) { diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index dd77b45d89..c375f36ce4 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -36,14 +36,14 @@ namespace Emby.Server.Implementations.HttpServer.Security return GetSession((HttpContext)requestContext); } - public User GetUser(HttpContext requestContext) + public User? GetUser(HttpContext requestContext) { var session = GetSession(requestContext); return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId); } - public User GetUser(object requestContext) + public User? GetUser(object requestContext) { return GetUser(((HttpRequest)requestContext).HttpContext); } diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 06acb56061..8f7d606692 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Buffers; using System.IO.Pipelines; diff --git a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs index 1bee1ac310..861c0a95e3 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs index 7435e9d0bf..47a83d77ce 100644 --- a/Emby.Server.Implementations/IO/FileRefresher.cs +++ b/Emby.Server.Implementations/IO/FileRefresher.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index 3353fae9d8..aa80bccd72 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 27096ed334..6a554e68ab 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.IO /// <param name="filename">The filename.</param> /// <returns>System.String.</returns> /// <exception cref="ArgumentNullException">filename</exception> - public virtual string ResolveShortcut(string filename) + public virtual string? ResolveShortcut(string filename) { if (string.IsNullOrEmpty(filename)) { @@ -601,7 +601,7 @@ namespace Emby.Server.Implementations.IO return GetFiles(path, null, false, recursive); } - public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string> extensions, bool enableCaseSensitiveExtensions, bool recursive = false) + public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string>? extensions, bool enableCaseSensitiveExtensions, bool recursive = false) { var enumerationOptions = GetEnumerationOptions(recursive); @@ -655,7 +655,7 @@ namespace Emby.Server.Implementations.IO return GetFilePaths(path, null, false, recursive); } - public virtual IEnumerable<string> GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false) + public virtual IEnumerable<string> GetFilePaths(string path, string[]? extensions, bool enableCaseSensitiveExtensions, bool recursive = false) { var enumerationOptions = GetEnumerationOptions(recursive); diff --git a/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs index e6696b8c4c..76c58d5dcd 100644 --- a/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs +++ b/Emby.Server.Implementations/IO/MbLinkShortcutHandler.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.IO public string Extension => ".mblink"; - public string Resolve(string shortcutPath) + public string? Resolve(string shortcutPath) { if (string.IsNullOrEmpty(shortcutPath)) { diff --git a/Emby.Server.Implementations/IO/StreamHelper.cs b/Emby.Server.Implementations/IO/StreamHelper.cs index c16ebd61b7..e4f5f4cf0b 100644 --- a/Emby.Server.Implementations/IO/StreamHelper.cs +++ b/Emby.Server.Implementations/IO/StreamHelper.cs @@ -11,7 +11,7 @@ namespace Emby.Server.Implementations.IO { public class StreamHelper : IStreamHelper { - public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken) + public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action? onStarted, CancellationToken cancellationToken) { byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize); try diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index f719dc5f89..a430b9e720 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#nullable enable namespace Emby.Server.Implementations { diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 6fa3c1c618..833fb0b7a1 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs index 161b4c4528..ff5f26ce09 100644 --- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Images/DynamicImageProvider.cs b/Emby.Server.Implementations/Images/DynamicImageProvider.cs index 50c5314820..900b3fd9c6 100644 --- a/Emby.Server.Implementations/Images/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/DynamicImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Images/FolderImageProvider.cs b/Emby.Server.Implementations/Images/FolderImageProvider.cs index 0224ab32a0..859017f869 100644 --- a/Emby.Server.Implementations/Images/FolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/FolderImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Images/GenreImageProvider.cs b/Emby.Server.Implementations/Images/GenreImageProvider.cs index 3817882312..6da431c68e 100644 --- a/Emby.Server.Implementations/Images/GenreImageProvider.cs +++ b/Emby.Server.Implementations/Images/GenreImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Images/PlaylistImageProvider.cs b/Emby.Server.Implementations/Images/PlaylistImageProvider.cs index a4c106e879..b8f0f0d65c 100644 --- a/Emby.Server.Implementations/Images/PlaylistImageProvider.cs +++ b/Emby.Server.Implementations/Images/PlaylistImageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs index 236453e805..6c65b58999 100644 --- a/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs +++ b/Emby.Server.Implementations/Library/ExclusiveLiveStream.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/IgnorePatterns.cs b/Emby.Server.Implementations/Library/IgnorePatterns.cs index e30a675931..5384c04b3b 100644 --- a/Emby.Server.Implementations/Library/IgnorePatterns.cs +++ b/Emby.Server.Implementations/Library/IgnorePatterns.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Linq; using DotNet.Globbing; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 4d207471a2..f8d8197d46 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs index c2951dd155..4ef7923db3 100644 --- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs +++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 85d6d3043e..38e81d14c4 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs index 28fa062396..b833122ea0 100644 --- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index f8bae4fd1a..06300adebc 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs index 0de4edb7e7..86b8039fab 100644 --- a/Emby.Server.Implementations/Library/PathExtensions.cs +++ b/Emby.Server.Implementations/Library/PathExtensions.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Diagnostics.CodeAnalysis; using MediaBrowser.Common.Providers; diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs index 1d9b448741..ac75e5d3a1 100644 --- a/Emby.Server.Implementations/Library/ResolverHelper.cs +++ b/Emby.Server.Implementations/Library/ResolverHelper.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.IO; using System.Linq; diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs index 4ad84579d8..e893d63350 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index bf32381ebf..8e1eccb10a 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs index 60f82806fb..3d2ae95d24 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using System.Threading.Tasks; diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index 16050185fb..a3dcdc9441 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs index 0525c7e307..68076730b3 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs index 7dbce7a6ef..7aaee017dd 100644 --- a/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/FolderResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; diff --git a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs index 92fb2a753a..fa45ccf840 100644 --- a/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/ItemResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs index 295e9e120b..69d71d0d9a 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.IO; using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 16bf4dc4a0..02c5287646 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs index 204c8a62e7..534bc80ddc 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs index 3cb6542cf7..57bf40e9ec 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs index 5f051321f6..ecd44be477 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs index 99f3041909..7b4e143348 100644 --- a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs index 6f29bc6494..d6ae910565 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs index 768e2e4f5b..7d707df182 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Globalization; using Emby.Naming.TV; using MediaBrowser.Controller.Entities.TV; diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 8fc3e3e75c..a1562abd31 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs index 62268fce90..9599faea4b 100644 --- a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index bcdf854ca3..26e615fa08 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index 827e3c64b3..667e466133 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index ac041bcf6c..e2da672a3c 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index 7a6b1d8b61..3fcadf5b1b 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 28a2095e16..7970631201 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 9372b0f6c3..26e4ef1ed9 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs index 8c27ca76e3..0ec52a9598 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EpgChannelData.cs @@ -6,7 +6,6 @@ using MediaBrowser.Controller.LiveTv; namespace Emby.Server.Implementations.LiveTv.EmbyTV { - internal class EpgChannelData { @@ -39,13 +38,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - public ChannelInfo GetChannelById(string id) + public ChannelInfo? GetChannelById(string id) => _channelsById.GetValueOrDefault(id); - public ChannelInfo GetChannelByNumber(string number) + public ChannelInfo? GetChannelByNumber(string number) => _channelsByNumber.GetValueOrDefault(number); - public ChannelInfo GetChannelByName(string name) + public ChannelInfo? GetChannelByName(string name) => _channelsByName.GetValueOrDefault(name); public static string NormalizeName(string value) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs index 1cac9cb963..bdab8c3e4b 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index 32245f899e..1088638691 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index 1efa90e256..6c52a9a73d 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 9af65cabba..00d02873c5 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index 6824aa4423..ebad4eddf1 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs index 6af49dd45e..21e1409ac0 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 1145d8aa1e..1f16289002 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs index 3a738fd5d2..ecd28097d3 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index fbcd4ef372..00a37bb022 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/Channels.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/Channels.cs index 740cbb66ee..0f04531896 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/Channels.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/Channels.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { internal class Channels diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs index 09d77f8382..42068cd340 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/DiscoverResponse.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index bbac6e0551..c5700db715 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index a7fda1d72e..3016eeda24 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index b16ccc561a..50a2d9abb3 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index f8baf55da7..96a678c1d3 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 4b170b2e4e..69035dac96 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 84d4161499..48a0c3cd37 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index eeb2426f4f..137ed27e20 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 220e423bf5..dd5dee1d16 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Concurrent; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index 031b5d2e72..8aaa1f7bbe 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs index 0781a0e333..1377286168 100644 --- a/Emby.Server.Implementations/Net/SocketFactory.cs +++ b/Emby.Server.Implementations/Net/SocketFactory.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Net/UdpSocket.cs b/Emby.Server.Implementations/Net/UdpSocket.cs index 4e25768cf6..a8b18d2925 100644 --- a/Emby.Server.Implementations/Net/UdpSocket.cs +++ b/Emby.Server.Implementations/Net/UdpSocket.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 2d1a559f16..9a1ca99467 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 14df20936c..48281b75f6 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -1,5 +1,3 @@ -#nullable enable - using System; using System.Collections.Generic; using System.Globalization; diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs index 0259dc436b..7cfd1fced9 100644 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Concurrent; using System.Globalization; diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 101d9b5377..ccbd4289ea 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index af316e1083..4f0df75bf8 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index 2312c85d97..baeb86a221 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -140,8 +140,10 @@ namespace Emby.Server.Implementations.ScheduledTasks previouslyFailedImages.Add(key); var parentPath = Path.GetDirectoryName(failHistoryPath); - - Directory.CreateDirectory(parentPath); + if (parentPath != null) + { + Directory.CreateDirectory(parentPath); + } string text = string.Join('|', previouslyFailedImages); File.WriteAllText(failHistoryPath, text); diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs index 4abbf784b2..50ba9bc899 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -75,4 +75,4 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks return Enumerable.Empty<TaskTriggerInfo>(); } } -} \ No newline at end of file +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs index 3b40320ab8..3b63536a42 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Model.Tasks; diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs index b04fd7c7e2..e13782fe02 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using System.Threading; diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs index 7cd5493da8..ced14195bb 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs index 0c0ebec082..a67f940b75 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading; using MediaBrowser.Model.Tasks; diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 76f863c951..30823ab8f5 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs index 27024e4e1c..8d8b82f0a4 100644 --- a/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs +++ b/Emby.Server.Implementations/Serialization/MyXmlSerializer.cs @@ -19,7 +19,9 @@ namespace Emby.Server.Implementations.Serialization new ConcurrentDictionary<string, XmlSerializer>(); private static XmlSerializer GetSerializer(Type type) - => _serializers.GetOrAdd(type.FullName, _ => new XmlSerializer(type)); + => _serializers.GetOrAdd( + type.FullName ?? throw new ArgumentException($"Invalid type {type}."), + _ => new XmlSerializer(type)); /// <summary> /// Serializes to writer. @@ -38,7 +40,7 @@ namespace Emby.Server.Implementations.Serialization /// <param name="type">The type.</param> /// <param name="stream">The stream.</param> /// <returns>System.Object.</returns> - public object DeserializeFromStream(Type type, Stream stream) + public object? DeserializeFromStream(Type type, Stream stream) { using (var reader = XmlReader.Create(stream)) { @@ -81,7 +83,7 @@ namespace Emby.Server.Implementations.Serialization /// <param name="type">The type.</param> /// <param name="file">The file.</param> /// <returns>System.Object.</returns> - public object DeserializeFromFile(Type type, string file) + public object? DeserializeFromFile(Type type, string file) { using (var stream = File.OpenRead(file)) { @@ -95,7 +97,7 @@ namespace Emby.Server.Implementations.Serialization /// <param name="type">The type.</param> /// <param name="buffer">The buffer.</param> /// <returns>System.Object.</returns> - public object DeserializeFromBytes(Type type, byte[] buffer) + public object? DeserializeFromBytes(Type type, byte[] buffer) { using (var stream = new MemoryStream(buffer, 0, buffer.Length, false, true)) { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 6844152ea5..ef467da7ed 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 39c369a01d..e9e3ca7f4b 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index a653b58c2b..ed1dfca592 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 #pragma warning disable SA1600 -#nullable enable using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs index 60698e803d..2b0ab536f9 100644 --- a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs +++ b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs index 7657cc74e1..42e644970c 100644 --- a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs +++ b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs @@ -18,7 +18,7 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); } @@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.Sorting /// </summary> /// <param name="x">The x.</param> /// <returns>System.String.</returns> - private static string GetValue(BaseItem x) + private static string? GetValue(BaseItem? x) { var audio = x as IHasAlbumArtist; diff --git a/Emby.Server.Implementations/Sorting/AlbumComparer.cs b/Emby.Server.Implementations/Sorting/AlbumComparer.cs index 7dfdd9ecff..1db3f5e9ca 100644 --- a/Emby.Server.Implementations/Sorting/AlbumComparer.cs +++ b/Emby.Server.Implementations/Sorting/AlbumComparer.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); } @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Sorting /// </summary> /// <param name="x">The x.</param> /// <returns>System.String.</returns> - private static string GetValue(BaseItem x) + private static string? GetValue(BaseItem? x) { var audio = x as Audio; diff --git a/Emby.Server.Implementations/Sorting/ArtistComparer.cs b/Emby.Server.Implementations/Sorting/ArtistComparer.cs index 756d3c5b6c..98bee3fd9f 100644 --- a/Emby.Server.Implementations/Sorting/ArtistComparer.cs +++ b/Emby.Server.Implementations/Sorting/ArtistComparer.cs @@ -15,7 +15,7 @@ namespace Emby.Server.Implementations.Sorting public string Name => ItemSortBy.Artist; /// <inheritdoc /> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); } @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Sorting /// </summary> /// <param name="x">The x.</param> /// <returns>System.String.</returns> - private static string GetValue(BaseItem x) + private static string? GetValue(BaseItem? x) { if (!(x is Audio audio)) { diff --git a/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs b/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs index 980954ba03..5f142fa4bb 100644 --- a/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/CommunityRatingComparer.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs b/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs index fa136c36d0..d20dedc2d4 100644 --- a/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/CriticRatingComparer.cs @@ -15,14 +15,14 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return GetValue(x).CompareTo(GetValue(y)); } - private static float GetValue(BaseItem x) + private static float GetValue(BaseItem? x) { - return x.CriticRating ?? 0; + return x?.CriticRating ?? 0; } /// <summary> diff --git a/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs b/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs index cbca300d2f..d3f10f78cb 100644 --- a/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DateCreatedComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs index 03ff19d21c..b1cb123ce1 100644 --- a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs index 16bd2aff86..08a44319f2 100644 --- a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; diff --git a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs index 0c4e82d011..73e628cf75 100644 --- a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using Jellyfin.Data.Entities; diff --git a/Emby.Server.Implementations/Sorting/IsFolderComparer.cs b/Emby.Server.Implementations/Sorting/IsFolderComparer.cs index a35192eff8..3c5ddeefaa 100644 --- a/Emby.Server.Implementations/Sorting/IsFolderComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsFolderComparer.cs @@ -20,7 +20,7 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return GetValue(x).CompareTo(GetValue(y)); } @@ -30,9 +30,9 @@ namespace Emby.Server.Implementations.Sorting /// </summary> /// <param name="x">The x.</param> /// <returns>System.String.</returns> - private static int GetValue(BaseItem x) + private static int GetValue(BaseItem? x) { - return x.IsFolder ? 0 : 1; + return x?.IsFolder ?? true ? 0 : 1; } } } diff --git a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs index d95948406f..7d77a8bc5a 100644 --- a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using Jellyfin.Data.Entities; diff --git a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs index 1632c5a7a9..926835f906 100644 --- a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using Jellyfin.Data.Entities; diff --git a/Emby.Server.Implementations/Sorting/NameComparer.cs b/Emby.Server.Implementations/Sorting/NameComparer.cs index da020d8d8e..4de81a69e3 100644 --- a/Emby.Server.Implementations/Sorting/NameComparer.cs +++ b/Emby.Server.Implementations/Sorting/NameComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs index 76bb798b5f..a81f78ebfc 100644 --- a/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs +++ b/Emby.Server.Implementations/Sorting/OfficialRatingComparer.cs @@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { if (x == null) { diff --git a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs index 5c28303229..04e4865cbf 100644 --- a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs +++ b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; diff --git a/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs index 92ac04dc66..c98f97bf1e 100644 --- a/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs +++ b/Emby.Server.Implementations/Sorting/PremiereDateComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return GetDate(x).CompareTo(GetDate(y)); } @@ -26,8 +26,13 @@ namespace Emby.Server.Implementations.Sorting /// </summary> /// <param name="x">The x.</param> /// <returns>DateTime.</returns> - private static DateTime GetDate(BaseItem x) + private static DateTime GetDate(BaseItem? x) { + if (x == null) + { + return DateTime.MinValue; + } + if (x.PremiereDate.HasValue) { return x.PremiereDate.Value; diff --git a/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs b/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs index e2857df0b9..df9f9957d6 100644 --- a/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs +++ b/Emby.Server.Implementations/Sorting/ProductionYearComparer.cs @@ -15,7 +15,7 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return GetValue(x).CompareTo(GetValue(y)); } @@ -25,8 +25,13 @@ namespace Emby.Server.Implementations.Sorting /// </summary> /// <param name="x">The x.</param> /// <returns>DateTime.</returns> - private static int GetValue(BaseItem x) + private static int GetValue(BaseItem? x) { + if (x == null) + { + return 0; + } + if (x.ProductionYear.HasValue) { return x.ProductionYear.Value; diff --git a/Emby.Server.Implementations/Sorting/RandomComparer.cs b/Emby.Server.Implementations/Sorting/RandomComparer.cs index 7739d04182..af3bc27508 100644 --- a/Emby.Server.Implementations/Sorting/RandomComparer.cs +++ b/Emby.Server.Implementations/Sorting/RandomComparer.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// <param name="x">The x.</param> /// <param name="y">The y.</param> /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) + public int Compare(BaseItem? x, BaseItem? y) { return Guid.NewGuid().CompareTo(Guid.NewGuid()); } diff --git a/Emby.Server.Implementations/Sorting/RuntimeComparer.cs b/Emby.Server.Implementations/Sorting/RuntimeComparer.cs index dde44333d5..1293153031 100644 --- a/Emby.Server.Implementations/Sorting/RuntimeComparer.cs +++ b/Emby.Server.Implementations/Sorting/RuntimeComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Sorting; diff --git a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs index b9205ee076..4123a59f8b 100644 --- a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Sorting/SortNameComparer.cs b/Emby.Server.Implementations/Sorting/SortNameComparer.cs index f745e193b4..8d30716d39 100644 --- a/Emby.Server.Implementations/Sorting/SortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SortNameComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Sorting; diff --git a/Emby.Server.Implementations/Sorting/StartDateComparer.cs b/Emby.Server.Implementations/Sorting/StartDateComparer.cs index 558a3d3513..c3df7c47e6 100644 --- a/Emby.Server.Implementations/Sorting/StartDateComparer.cs +++ b/Emby.Server.Implementations/Sorting/StartDateComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Sorting/StudioComparer.cs b/Emby.Server.Implementations/Sorting/StudioComparer.cs index 5766dc542b..01445c5254 100644 --- a/Emby.Server.Implementations/Sorting/StudioComparer.cs +++ b/Emby.Server.Implementations/Sorting/StudioComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/SyncPlay/Group.cs b/Emby.Server.Implementations/SyncPlay/Group.cs index 7c2ad2477a..12efff261b 100644 --- a/Emby.Server.Implementations/SyncPlay/Group.cs +++ b/Emby.Server.Implementations/SyncPlay/Group.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index 72c0a838e2..993456196d 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Concurrent; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index 829df64bfb..a837f09cac 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index db5265e79d..750f001680 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Net; using System.Net.Sockets; diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 653b1381b9..2351b7d8c6 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -1,3 +1,5 @@ +#nullable disable + #nullable enable using System; diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index b4154b361e..45559fce9a 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1762,9 +1762,9 @@ namespace Jellyfin.Api.Controllers private static FileSystemMetadata? GetLastTranscodingFile(string playlist, string segmentExtension, IFileSystem fileSystem) { - var folder = Path.GetDirectoryName(playlist); + var folder = Path.GetDirectoryName(playlist) ?? throw new ArgumentException("Path can't be a root directory.", nameof(playlist)); - var filePrefix = Path.GetFileNameWithoutExtension(playlist) ?? string.Empty; + var filePrefix = Path.GetFileNameWithoutExtension(playlist); try { diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 0879cbd18f..7cb0159936 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -380,7 +380,9 @@ namespace Jellyfin.Api.Helpers /// <param name="outputFilePath">The output file path.</param> private void DeleteHlsPartialStreamFiles(string outputFilePath) { - var directory = Path.GetDirectoryName(outputFilePath); + var directory = Path.GetDirectoryName(outputFilePath) + ?? throw new ArgumentException("Path can't be a root directory.", nameof(outputFilePath)); + var name = Path.GetFileNameWithoutExtension(outputFilePath); var filesToDelete = _fileSystem.GetFilePaths(directory) diff --git a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs index e33e552edb..7b32d76ba7 100644 --- a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs +++ b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs @@ -145,7 +145,8 @@ namespace Jellyfin.Api.Models.PlaybackDtos var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0; var downloadPositionTicks = job.DownloadPositionTicks ?? 0; - var path = job.Path; + var path = job.Path ?? throw new ArgumentException("Path can't be null."); + var gapLengthInTicks = TimeSpan.FromSeconds(thresholdSeconds).Ticks; if (downloadPositionTicks > 0 && transcodingPositionTicks > 0) diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index a15a381772..96bd2ccc40 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -82,11 +82,14 @@ namespace Jellyfin.Server.Migrations.Routines var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, mockup.Name); - var config = File.Exists(Path.Combine(userDataDir, "config.xml")) - ? (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), Path.Combine(userDataDir, "config.xml")) + var configPath = Path.Combine(userDataDir, "config.xml"); + var config = File.Exists(configPath) + ? (UserConfiguration?)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), configPath) ?? new UserConfiguration() : new UserConfiguration(); - var policy = File.Exists(Path.Combine(userDataDir, "policy.xml")) - ? (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), Path.Combine(userDataDir, "policy.xml")) + + var policyPath = Path.Combine(userDataDir, "policy.xml"); + var policy = File.Exists(policyPath) + ? (UserPolicy?)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), policyPath) ?? new UserPolicy() : new UserPolicy(); policy.AuthenticationProviderId = policy.AuthenticationProviderId?.Replace( "Emby.Server.Implementations.Library", diff --git a/MediaBrowser.Controller/Extensions/StringExtensions.cs b/MediaBrowser.Controller/Extensions/StringExtensions.cs index 48bd9522a8..1853896eec 100644 --- a/MediaBrowser.Controller/Extensions/StringExtensions.cs +++ b/MediaBrowser.Controller/Extensions/StringExtensions.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; diff --git a/MediaBrowser.Controller/Net/ISessionContext.cs b/MediaBrowser.Controller/Net/ISessionContext.cs index a60dc2ea19..6b896b41f4 100644 --- a/MediaBrowser.Controller/Net/ISessionContext.cs +++ b/MediaBrowser.Controller/Net/ISessionContext.cs @@ -10,10 +10,10 @@ namespace MediaBrowser.Controller.Net { SessionInfo GetSession(object requestContext); - User GetUser(object requestContext); + User? GetUser(object requestContext); SessionInfo GetSession(HttpContext requestContext); - User GetUser(HttpContext requestContext); + User? GetUser(HttpContext requestContext); } } diff --git a/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs b/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs index 727cbe639c..07fe1ea8a9 100644 --- a/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs +++ b/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs @@ -6,7 +6,7 @@ namespace MediaBrowser.Controller.Sorting /// <summary> /// Interface IBaseItemComparer. /// </summary> - public interface IBaseItemComparer : IComparer<BaseItem> + public interface IBaseItemComparer : IComparer<BaseItem?> { /// <summary> /// Gets the name. diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs index e5c26430a8..be4f1e16b5 100644 --- a/MediaBrowser.Model/IO/IFileSystem.cs +++ b/MediaBrowser.Model/IO/IFileSystem.cs @@ -1,4 +1,3 @@ -#nullable disable #pragma warning disable CS1591 using System; @@ -25,7 +24,7 @@ namespace MediaBrowser.Model.IO /// </summary> /// <param name="filename">The filename.</param> /// <returns>System.String.</returns> - string ResolveShortcut(string filename); + string? ResolveShortcut(string filename); /// <summary> /// Creates the shortcut. @@ -160,7 +159,7 @@ namespace MediaBrowser.Model.IO /// <returns>All found files.</returns> IEnumerable<FileSystemMetadata> GetFiles(string path, bool recursive = false); - IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string> extensions, bool enableCaseSensitiveExtensions, bool recursive); + IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string>? extensions, bool enableCaseSensitiveExtensions, bool recursive); /// <summary> /// Gets the file system entries. @@ -186,7 +185,7 @@ namespace MediaBrowser.Model.IO /// <returns>IEnumerable<System.String>.</returns> IEnumerable<string> GetFilePaths(string path, bool recursive = false); - IEnumerable<string> GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive); + IEnumerable<string> GetFilePaths(string path, string[]? extensions, bool enableCaseSensitiveExtensions, bool recursive); /// <summary> /// Gets the file system entry paths. diff --git a/MediaBrowser.Model/IO/IShortcutHandler.cs b/MediaBrowser.Model/IO/IShortcutHandler.cs index 14d5c4b62f..2c364a962f 100644 --- a/MediaBrowser.Model/IO/IShortcutHandler.cs +++ b/MediaBrowser.Model/IO/IShortcutHandler.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Model.IO /// </summary> /// <param name="shortcutPath">The shortcut path.</param> /// <returns>System.String.</returns> - string Resolve(string shortcutPath); + string? Resolve(string shortcutPath); /// <summary> /// Creates the specified shortcut path. diff --git a/MediaBrowser.Model/IO/IStreamHelper.cs b/MediaBrowser.Model/IO/IStreamHelper.cs index 0e09db16e8..f900da5567 100644 --- a/MediaBrowser.Model/IO/IStreamHelper.cs +++ b/MediaBrowser.Model/IO/IStreamHelper.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.Model.IO { public interface IStreamHelper { - Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken); + Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action? onStarted, CancellationToken cancellationToken); Task CopyToAsync(Stream source, Stream destination, int bufferSize, int emptyReadLimit, CancellationToken cancellationToken); diff --git a/MediaBrowser.Model/Tasks/ITaskTrigger.cs b/MediaBrowser.Model/Tasks/ITaskTrigger.cs index cbd60cca18..db9fba6964 100644 --- a/MediaBrowser.Model/Tasks/ITaskTrigger.cs +++ b/MediaBrowser.Model/Tasks/ITaskTrigger.cs @@ -11,7 +11,7 @@ namespace MediaBrowser.Model.Tasks /// <summary> /// Fires when the trigger condition is satisfied and the task should run. /// </summary> - event EventHandler<EventArgs> Triggered; + event EventHandler<EventArgs>? Triggered; /// <summary> /// Gets or sets the options of this task. -- cgit v1.2.3 From a0c6f7276211ac0429877fafa400368aba1430a9 Mon Sep 17 00:00:00 2001 From: Patrick Barron <barronpm@gmail.com> Date: Thu, 20 May 2021 23:56:59 -0400 Subject: Migrate authentication db to EF Core --- Emby.Server.Implementations/ApplicationHost.cs | 10 +- .../HttpServer/Security/AuthService.cs | 5 +- .../HttpServer/Security/AuthorizationContext.cs | 291 ---------- .../HttpServer/Security/SessionContext.cs | 12 +- .../HttpServer/WebSocketManager.cs | 2 +- .../Security/AuthenticationRepository.cs | 407 ------------- .../Session/SessionManager.cs | 80 +-- Jellyfin.Api/Auth/CustomAuthenticationHandler.cs | 10 +- Jellyfin.Api/Controllers/CollectionController.cs | 2 +- Jellyfin.Api/Controllers/DevicesController.cs | 18 +- Jellyfin.Api/Controllers/ImageController.cs | 8 +- Jellyfin.Api/Controllers/LibraryController.cs | 10 +- Jellyfin.Api/Controllers/MediaInfoController.cs | 2 +- Jellyfin.Api/Controllers/PlaystateController.cs | 6 +- Jellyfin.Api/Controllers/SessionController.cs | 6 +- Jellyfin.Api/Controllers/SubtitleController.cs | 2 +- .../Controllers/UniversalAudioController.cs | 4 +- Jellyfin.Api/Controllers/UserController.cs | 42 +- Jellyfin.Api/Controllers/UserViewsController.cs | 5 +- Jellyfin.Api/Helpers/MediaInfoHelper.cs | 2 +- Jellyfin.Api/Helpers/RequestHelpers.cs | 6 +- Jellyfin.Api/Helpers/StreamingHelpers.cs | 4 +- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 2 +- .../Devices/DeviceManager.cs | 73 +++ Jellyfin.Server.Implementations/JellyfinDb.cs | 9 + .../20210521032224_AddDevices.Designer.cs | 645 +++++++++++++++++++++ .../Migrations/20210521032224_AddDevices.cs | 126 ++++ .../Migrations/JellyfinDbModelSnapshot.cs | 114 +++- .../Security/AuthorizationContext.cs | 283 +++++++++ .../Users/DeviceAccessEntryPoint.cs | 22 +- Jellyfin.Server/CoreAppHost.cs | 3 + MediaBrowser.Controller/Devices/IDeviceManager.cs | 19 + MediaBrowser.Controller/Net/IAuthService.cs | 3 +- .../Net/IAuthorizationContext.cs | 9 +- .../Security/AuthenticationInfoQuery.cs | 53 -- .../Security/IAuthenticationRepository.cs | 39 -- MediaBrowser.Controller/Session/ISessionManager.cs | 19 +- MediaBrowser.Model/Devices/DeviceInfo.cs | 5 + .../Auth/CustomAuthenticationHandlerTests.cs | 2 +- 39 files changed, 1408 insertions(+), 952 deletions(-) delete mode 100644 Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs delete mode 100644 Emby.Server.Implementations/Security/AuthenticationRepository.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20210521032224_AddDevices.Designer.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20210521032224_AddDevices.cs create mode 100644 Jellyfin.Server.Implementations/Security/AuthorizationContext.cs delete mode 100644 MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs delete mode 100644 MediaBrowser.Controller/Security/IAuthenticationRepository.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 213890c679..a69c4d0353 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -36,7 +36,6 @@ using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.Plugins; using Emby.Server.Implementations.QuickConnect; using Emby.Server.Implementations.ScheduledTasks; -using Emby.Server.Implementations.Security; using Emby.Server.Implementations.Serialization; using Emby.Server.Implementations.Session; using Emby.Server.Implementations.SyncPlay; @@ -57,7 +56,6 @@ using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; @@ -73,7 +71,6 @@ using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.QuickConnect; using MediaBrowser.Controller.Resolvers; -using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Subtitles; @@ -599,8 +596,6 @@ namespace Emby.Server.Implementations ServiceCollection.AddSingleton<IItemRepository, SqliteItemRepository>(); - ServiceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>(); - ServiceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(); ServiceCollection.AddSingleton<EncodingHelper>(); @@ -657,8 +652,7 @@ namespace Emby.Server.Implementations ServiceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>(); - ServiceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>(); - ServiceCollection.AddSingleton<ISessionContext, SessionContext>(); + ServiceCollection.AddScoped<ISessionContext, SessionContext>(); ServiceCollection.AddSingleton<IAuthService, AuthService>(); ServiceCollection.AddSingleton<IQuickConnect, QuickConnectManager>(); @@ -687,8 +681,6 @@ namespace Emby.Server.Implementations _mediaEncoder = Resolve<IMediaEncoder>(); _sessionManager = Resolve<ISessionManager>(); - ((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize(); - SetStaticProperties(); var userDataRepo = (SqliteUserDataRepository)Resolve<IUserDataRepository>(); diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 9afabf5272..e2ad07177e 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -1,5 +1,6 @@ #pragma warning disable CS1591 +using System.Threading.Tasks; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Net; @@ -17,9 +18,9 @@ namespace Emby.Server.Implementations.HttpServer.Security _authorizationContext = authorizationContext; } - public AuthorizationInfo Authenticate(HttpRequest request) + public async Task<AuthorizationInfo> Authenticate(HttpRequest request) { - var auth = _authorizationContext.GetAuthorizationInfo(request); + var auth = await _authorizationContext.GetAuthorizationInfo(request).ConfigureAwait(false); if (!auth.HasToken) { diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs deleted file mode 100644 index 024404ceb0..0000000000 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ /dev/null @@ -1,291 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Security; -using Microsoft.AspNetCore.Http; -using Microsoft.Net.Http.Headers; - -namespace Emby.Server.Implementations.HttpServer.Security -{ - public class AuthorizationContext : IAuthorizationContext - { - private readonly IAuthenticationRepository _authRepo; - private readonly IUserManager _userManager; - - public AuthorizationContext(IAuthenticationRepository authRepo, IUserManager userManager) - { - _authRepo = authRepo; - _userManager = userManager; - } - - public AuthorizationInfo GetAuthorizationInfo(HttpContext requestContext) - { - if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached)) - { - return (AuthorizationInfo)cached; - } - - return GetAuthorization(requestContext); - } - - public AuthorizationInfo GetAuthorizationInfo(HttpRequest requestContext) - { - var auth = GetAuthorizationDictionary(requestContext); - var authInfo = GetAuthorizationInfoFromDictionary(auth, requestContext.Headers, requestContext.Query); - return authInfo; - } - - /// <summary> - /// Gets the authorization. - /// </summary> - /// <param name="httpReq">The HTTP req.</param> - /// <returns>Dictionary{System.StringSystem.String}.</returns> - private AuthorizationInfo GetAuthorization(HttpContext httpReq) - { - var auth = GetAuthorizationDictionary(httpReq); - var authInfo = GetAuthorizationInfoFromDictionary(auth, httpReq.Request.Headers, httpReq.Request.Query); - - httpReq.Request.HttpContext.Items["AuthorizationInfo"] = authInfo; - return authInfo; - } - - private AuthorizationInfo GetAuthorizationInfoFromDictionary( - in Dictionary<string, string> auth, - in IHeaderDictionary headers, - in IQueryCollection queryString) - { - string deviceId = null; - string device = null; - string client = null; - string version = null; - string token = null; - - if (auth != null) - { - auth.TryGetValue("DeviceId", out deviceId); - auth.TryGetValue("Device", out device); - auth.TryGetValue("Client", out client); - auth.TryGetValue("Version", out version); - auth.TryGetValue("Token", out token); - } - - if (string.IsNullOrEmpty(token)) - { - token = headers["X-Emby-Token"]; - } - - if (string.IsNullOrEmpty(token)) - { - token = headers["X-MediaBrowser-Token"]; - } - - if (string.IsNullOrEmpty(token)) - { - token = queryString["ApiKey"]; - } - - // TODO deprecate this query parameter. - if (string.IsNullOrEmpty(token)) - { - token = queryString["api_key"]; - } - - var authInfo = new AuthorizationInfo - { - Client = client, - Device = device, - DeviceId = deviceId, - Version = version, - Token = token, - IsAuthenticated = false, - HasToken = false - }; - - if (string.IsNullOrWhiteSpace(token)) - { - // Request doesn't contain a token. - return authInfo; - } - - authInfo.HasToken = true; - var result = _authRepo.Get(new AuthenticationInfoQuery - { - AccessToken = token - }); - - if (result.Items.Count > 0) - { - authInfo.IsAuthenticated = true; - } - - var originalAuthenticationInfo = result.Items.Count > 0 ? result.Items[0] : null; - - if (originalAuthenticationInfo != null) - { - var updateToken = false; - - // TODO: Remove these checks for IsNullOrWhiteSpace - if (string.IsNullOrWhiteSpace(authInfo.Client)) - { - authInfo.Client = originalAuthenticationInfo.AppName; - } - - if (string.IsNullOrWhiteSpace(authInfo.DeviceId)) - { - authInfo.DeviceId = originalAuthenticationInfo.DeviceId; - } - - // Temporary. TODO - allow clients to specify that the token has been shared with a casting device - var allowTokenInfoUpdate = authInfo.Client == null || authInfo.Client.IndexOf("chromecast", StringComparison.OrdinalIgnoreCase) == -1; - - if (string.IsNullOrWhiteSpace(authInfo.Device)) - { - authInfo.Device = originalAuthenticationInfo.DeviceName; - } - else if (!string.Equals(authInfo.Device, originalAuthenticationInfo.DeviceName, StringComparison.OrdinalIgnoreCase)) - { - if (allowTokenInfoUpdate) - { - updateToken = true; - originalAuthenticationInfo.DeviceName = authInfo.Device; - } - } - - if (string.IsNullOrWhiteSpace(authInfo.Version)) - { - authInfo.Version = originalAuthenticationInfo.AppVersion; - } - else if (!string.Equals(authInfo.Version, originalAuthenticationInfo.AppVersion, StringComparison.OrdinalIgnoreCase)) - { - if (allowTokenInfoUpdate) - { - updateToken = true; - originalAuthenticationInfo.AppVersion = authInfo.Version; - } - } - - if ((DateTime.UtcNow - originalAuthenticationInfo.DateLastActivity).TotalMinutes > 3) - { - originalAuthenticationInfo.DateLastActivity = DateTime.UtcNow; - updateToken = true; - } - - if (!originalAuthenticationInfo.UserId.Equals(Guid.Empty)) - { - authInfo.User = _userManager.GetUserById(originalAuthenticationInfo.UserId); - - if (authInfo.User != null && !string.Equals(authInfo.User.Username, originalAuthenticationInfo.UserName, StringComparison.OrdinalIgnoreCase)) - { - originalAuthenticationInfo.UserName = authInfo.User.Username; - updateToken = true; - } - - authInfo.IsApiKey = false; - } - else - { - authInfo.IsApiKey = true; - } - - if (updateToken) - { - _authRepo.Update(originalAuthenticationInfo); - } - } - - return authInfo; - } - - /// <summary> - /// Gets the auth. - /// </summary> - /// <param name="httpReq">The HTTP req.</param> - /// <returns>Dictionary{System.StringSystem.String}.</returns> - private Dictionary<string, string> GetAuthorizationDictionary(HttpContext httpReq) - { - var auth = httpReq.Request.Headers["X-Emby-Authorization"]; - - if (string.IsNullOrEmpty(auth)) - { - auth = httpReq.Request.Headers[HeaderNames.Authorization]; - } - - return GetAuthorization(auth); - } - - /// <summary> - /// Gets the auth. - /// </summary> - /// <param name="httpReq">The HTTP req.</param> - /// <returns>Dictionary{System.StringSystem.String}.</returns> - private Dictionary<string, string> GetAuthorizationDictionary(HttpRequest httpReq) - { - var auth = httpReq.Headers["X-Emby-Authorization"]; - - if (string.IsNullOrEmpty(auth)) - { - auth = httpReq.Headers[HeaderNames.Authorization]; - } - - return GetAuthorization(auth); - } - - /// <summary> - /// Gets the authorization. - /// </summary> - /// <param name="authorizationHeader">The authorization header.</param> - /// <returns>Dictionary{System.StringSystem.String}.</returns> - private Dictionary<string, string> GetAuthorization(string authorizationHeader) - { - if (authorizationHeader == null) - { - return null; - } - - var parts = authorizationHeader.Split(' ', 2); - - // There should be at least to parts - if (parts.Length != 2) - { - return null; - } - - var acceptedNames = new[] { "MediaBrowser", "Emby" }; - - // It has to be a digest request - if (!acceptedNames.Contains(parts[0], StringComparer.OrdinalIgnoreCase)) - { - return null; - } - - // Remove uptil the first space - authorizationHeader = parts[1]; - parts = authorizationHeader.Split(','); - - var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - - foreach (var item in parts) - { - var param = item.Trim().Split('=', 2); - - if (param.Length == 2) - { - var value = NormalizeValue(param[1].Trim('"')); - result[param[0]] = value; - } - } - - return result; - } - - private static string NormalizeValue(string value) - { - return string.IsNullOrEmpty(value) ? value : WebUtility.HtmlEncode(value); - } - } -} diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index 414ba7ca01..cd1b9cba01 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -24,12 +24,18 @@ namespace Emby.Server.Implementations.HttpServer.Security _sessionManager = sessionManager; } - public Task<SessionInfo> GetSession(HttpContext requestContext) + public async Task<SessionInfo> GetSession(HttpContext requestContext) { - var authorization = _authContext.GetAuthorizationInfo(requestContext); + var authorization = await _authContext.GetAuthorizationInfo(requestContext).ConfigureAwait(false); var user = authorization.User; - return _sessionManager.LogSessionActivity(authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, requestContext.GetNormalizedRemoteIp().ToString(), user); + return await _sessionManager.LogSessionActivity( + authorization.Client, + authorization.Version, + authorization.DeviceId, + authorization.Device, + requestContext.GetNormalizedRemoteIp().ToString(), + user).ConfigureAwait(false); } public Task<SessionInfo> GetSession(object requestContext) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs index 1bee1ac310..b71ffdaee5 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketManager.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketManager.cs @@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.HttpServer /// <inheritdoc /> public async Task WebSocketRequestHandler(HttpContext context) { - _ = _authService.Authenticate(context.Request); + _ = await _authService.Authenticate(context.Request).ConfigureAwait(false); try { _logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress); diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs deleted file mode 100644 index 0d0a2b1df7..0000000000 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ /dev/null @@ -1,407 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using Emby.Server.Implementations.Data; -using Jellyfin.Data.Entities.Security; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Security; -using MediaBrowser.Model.Querying; -using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; - -namespace Emby.Server.Implementations.Security -{ - public class AuthenticationRepository : BaseSqliteRepository, IAuthenticationRepository - { - public AuthenticationRepository(ILogger<AuthenticationRepository> logger, IServerConfigurationManager config) - : base(logger) - { - DbFilePath = Path.Combine(config.ApplicationPaths.DataPath, "authentication.db"); - } - - public void Initialize() - { - string[] queries = - { - "create table if not exists Tokens (Id INTEGER PRIMARY KEY, AccessToken TEXT NOT NULL, DeviceId TEXT NOT NULL, AppName TEXT NOT NULL, AppVersion TEXT NOT NULL, DeviceName TEXT NOT NULL, UserId TEXT, UserName TEXT, IsActive BIT NOT NULL, DateCreated DATETIME NOT NULL, DateLastActivity DATETIME NOT NULL)", - "create table if not exists Devices (Id TEXT NOT NULL PRIMARY KEY, CustomName TEXT, Capabilities TEXT)", - "drop index if exists idx_AccessTokens", - "drop index if exists Tokens1", - "drop index if exists Tokens2", - - "create index if not exists Tokens3 on Tokens (AccessToken, DateLastActivity)", - "create index if not exists Tokens4 on Tokens (Id, DateLastActivity)", - "create index if not exists Devices1 on Devices (Id)" - }; - - using (var connection = GetConnection()) - { - var tableNewlyCreated = !TableExists(connection, "Tokens"); - - connection.RunQueries(queries); - - TryMigrate(connection, tableNewlyCreated); - } - } - - private void TryMigrate(ManagedConnection connection, bool tableNewlyCreated) - { - try - { - if (tableNewlyCreated && TableExists(connection, "AccessTokens")) - { - connection.RunInTransaction( - db => - { - var existingColumnNames = GetColumnNames(db, "AccessTokens"); - - AddColumn(db, "AccessTokens", "UserName", "TEXT", existingColumnNames); - AddColumn(db, "AccessTokens", "DateLastActivity", "DATETIME", existingColumnNames); - AddColumn(db, "AccessTokens", "AppVersion", "TEXT", existingColumnNames); - }, TransactionMode); - - connection.RunQueries(new[] - { - "update accesstokens set DateLastActivity=DateCreated where DateLastActivity is null", - "update accesstokens set DeviceName='Unknown' where DeviceName is null", - "update accesstokens set AppName='Unknown' where AppName is null", - "update accesstokens set AppVersion='1' where AppVersion is null", - "INSERT INTO Tokens (AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, IsActive, DateCreated, DateLastActivity) SELECT AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, IsActive, DateCreated, DateLastActivity FROM AccessTokens where deviceid not null and devicename not null and appname not null and isactive=1" - }); - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Error migrating authentication database"); - } - } - - public void Create(AuthenticationInfo info) - { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } - - using (var connection = GetConnection()) - { - connection.RunInTransaction( - db => - { - using (var statement = db.PrepareStatement("insert into Tokens (AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, IsActive, DateCreated, DateLastActivity) values (@AccessToken, @DeviceId, @AppName, @AppVersion, @DeviceName, @UserId, @UserName, @IsActive, @DateCreated, @DateLastActivity)")) - { - statement.TryBind("@AccessToken", info.AccessToken); - - statement.TryBind("@DeviceId", info.DeviceId); - statement.TryBind("@AppName", info.AppName); - statement.TryBind("@AppVersion", info.AppVersion); - statement.TryBind("@DeviceName", info.DeviceName); - statement.TryBind("@UserId", info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N", CultureInfo.InvariantCulture)); - statement.TryBind("@UserName", info.UserName); - statement.TryBind("@IsActive", true); - statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue()); - statement.TryBind("@DateLastActivity", info.DateLastActivity.ToDateTimeParamValue()); - - statement.MoveNext(); - } - }, TransactionMode); - } - } - - public void Update(AuthenticationInfo info) - { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } - - using (var connection = GetConnection()) - { - connection.RunInTransaction( - db => - { - using (var statement = db.PrepareStatement("Update Tokens set AccessToken=@AccessToken, DeviceId=@DeviceId, AppName=@AppName, AppVersion=@AppVersion, DeviceName=@DeviceName, UserId=@UserId, UserName=@UserName, DateCreated=@DateCreated, DateLastActivity=@DateLastActivity where Id=@Id")) - { - statement.TryBind("@Id", info.Id); - - statement.TryBind("@AccessToken", info.AccessToken); - - statement.TryBind("@DeviceId", info.DeviceId); - statement.TryBind("@AppName", info.AppName); - statement.TryBind("@AppVersion", info.AppVersion); - statement.TryBind("@DeviceName", info.DeviceName); - statement.TryBind("@UserId", info.UserId.Equals(Guid.Empty) ? null : info.UserId.ToString("N", CultureInfo.InvariantCulture)); - statement.TryBind("@UserName", info.UserName); - statement.TryBind("@DateCreated", info.DateCreated.ToDateTimeParamValue()); - statement.TryBind("@DateLastActivity", info.DateLastActivity.ToDateTimeParamValue()); - - statement.MoveNext(); - } - }, TransactionMode); - } - } - - public void Delete(AuthenticationInfo info) - { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } - - using (var connection = GetConnection()) - { - connection.RunInTransaction( - db => - { - using (var statement = db.PrepareStatement("Delete from Tokens where Id=@Id")) - { - statement.TryBind("@Id", info.Id); - - statement.MoveNext(); - } - }, TransactionMode); - } - } - - private const string BaseSelectText = "select Tokens.Id, AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, DateCreated, DateLastActivity, Devices.CustomName from Tokens left join Devices on Tokens.DeviceId=Devices.Id"; - - private static void BindAuthenticationQueryParams(AuthenticationInfoQuery query, IStatement statement) - { - if (!string.IsNullOrEmpty(query.AccessToken)) - { - statement.TryBind("@AccessToken", query.AccessToken); - } - - if (!query.UserId.Equals(Guid.Empty)) - { - statement.TryBind("@UserId", query.UserId.ToString("N", CultureInfo.InvariantCulture)); - } - - if (!string.IsNullOrEmpty(query.DeviceId)) - { - statement.TryBind("@DeviceId", query.DeviceId); - } - } - - public QueryResult<AuthenticationInfo> Get(AuthenticationInfoQuery query) - { - if (query == null) - { - throw new ArgumentNullException(nameof(query)); - } - - var commandText = BaseSelectText; - - var whereClauses = new List<string>(); - - if (!string.IsNullOrEmpty(query.AccessToken)) - { - whereClauses.Add("AccessToken=@AccessToken"); - } - - if (!string.IsNullOrEmpty(query.DeviceId)) - { - whereClauses.Add("DeviceId=@DeviceId"); - } - - if (!query.UserId.Equals(Guid.Empty)) - { - whereClauses.Add("UserId=@UserId"); - } - - if (query.HasUser.HasValue) - { - if (query.HasUser.Value) - { - whereClauses.Add("UserId not null"); - } - else - { - whereClauses.Add("UserId is null"); - } - } - - var whereTextWithoutPaging = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); - - commandText += whereTextWithoutPaging; - - commandText += " ORDER BY DateLastActivity desc"; - - if (query.Limit.HasValue || query.StartIndex.HasValue) - { - var offset = query.StartIndex ?? 0; - - if (query.Limit.HasValue || offset > 0) - { - commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture); - } - - if (offset > 0) - { - commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture); - } - } - - var statementTexts = new[] - { - commandText, - "select count (Id) from Tokens" + whereTextWithoutPaging - }; - - var list = new List<AuthenticationInfo>(); - var result = new QueryResult<AuthenticationInfo>(); - using (var connection = GetConnection(true)) - { - connection.RunInTransaction( - db => - { - var statements = PrepareAll(db, statementTexts); - - using (var statement = statements[0]) - { - BindAuthenticationQueryParams(query, statement); - - foreach (var row in statement.ExecuteQuery()) - { - list.Add(Get(row)); - } - - using (var totalCountStatement = statements[1]) - { - BindAuthenticationQueryParams(query, totalCountStatement); - - result.TotalRecordCount = totalCountStatement.ExecuteQuery() - .SelectScalarInt() - .First(); - } - } - }, - ReadTransactionMode); - } - - result.Items = list; - return result; - } - - private static AuthenticationInfo Get(IReadOnlyList<IResultSetValue> reader) - { - var info = new AuthenticationInfo - { - Id = reader[0].ToInt64(), - AccessToken = reader[1].ToString() - }; - - if (reader[2].SQLiteType != SQLiteType.Null) - { - info.DeviceId = reader[2].ToString(); - } - - if (reader[3].SQLiteType != SQLiteType.Null) - { - info.AppName = reader[3].ToString(); - } - - if (reader[4].SQLiteType != SQLiteType.Null) - { - info.AppVersion = reader[4].ToString(); - } - - if (reader[5].SQLiteType != SQLiteType.Null) - { - info.DeviceName = reader[5].ToString(); - } - - if (reader[6].SQLiteType != SQLiteType.Null) - { - info.UserId = new Guid(reader[6].ToString()); - } - - if (reader[7].SQLiteType != SQLiteType.Null) - { - info.UserName = reader[7].ToString(); - } - - info.DateCreated = reader[8].ReadDateTime(); - - if (reader[9].SQLiteType != SQLiteType.Null) - { - info.DateLastActivity = reader[9].ReadDateTime(); - } - else - { - info.DateLastActivity = info.DateCreated; - } - - if (reader[10].SQLiteType != SQLiteType.Null) - { - info.DeviceName = reader[10].ToString(); - } - - return info; - } - - public DeviceOptions GetDeviceOptions(string deviceId) - { - using (var connection = GetConnection(true)) - { - return connection.RunInTransaction( - db => - { - using (var statement = base.PrepareStatement(db, "select CustomName from Devices where Id=@DeviceId")) - { - statement.TryBind("@DeviceId", deviceId); - - var result = new DeviceOptions(deviceId); - - foreach (var row in statement.ExecuteQuery()) - { - if (row[0].SQLiteType != SQLiteType.Null) - { - result.CustomName = row[0].ToString(); - } - } - - return result; - } - }, ReadTransactionMode); - } - } - - public void UpdateDeviceOptions(string deviceId, DeviceOptions options) - { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - using (var connection = GetConnection()) - { - connection.RunInTransaction( - db => - { - using (var statement = db.PrepareStatement("replace into devices (Id, CustomName, Capabilities) VALUES (@Id, @CustomName, (Select Capabilities from Devices where Id=@Id))")) - { - statement.TryBind("@Id", deviceId); - - if (string.IsNullOrWhiteSpace(options.CustomName)) - { - statement.TryBindNull("@CustomName"); - } - else - { - statement.TryBind("@CustomName", options.CustomName); - } - - statement.MoveNext(); - } - }, TransactionMode); - } - } - } -} diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 2fb5040f9c..92ef65bf11 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -11,6 +11,7 @@ using Jellyfin.Data.Entities; using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Enums; using Jellyfin.Data.Events; +using Jellyfin.Data.Queries; using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; @@ -23,7 +24,6 @@ using MediaBrowser.Controller.Events; using MediaBrowser.Controller.Events.Session; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -52,7 +52,6 @@ namespace Emby.Server.Implementations.Session private readonly IImageProcessor _imageProcessor; private readonly IMediaSourceManager _mediaSourceManager; private readonly IServerApplicationHost _appHost; - private readonly IAuthenticationRepository _authRepo; private readonly IDeviceManager _deviceManager; /// <summary> @@ -75,7 +74,6 @@ namespace Emby.Server.Implementations.Session IDtoService dtoService, IImageProcessor imageProcessor, IServerApplicationHost appHost, - IAuthenticationRepository authRepo, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager) { @@ -88,7 +86,6 @@ namespace Emby.Server.Implementations.Session _dtoService = dtoService; _imageProcessor = imageProcessor; _appHost = appHost; - _authRepo = authRepo; _deviceManager = deviceManager; _mediaSourceManager = mediaSourceManager; @@ -1486,7 +1483,7 @@ namespace Emby.Server.Implementations.Session throw new SecurityException("User is at their maximum number of sessions."); } - var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName); + var token = await GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName).ConfigureAwait(false); var session = await LogSessionActivity( request.App, @@ -1509,21 +1506,21 @@ namespace Emby.Server.Implementations.Session return returnResult; } - private string GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName) + private async Task<string> GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName) { - var existing = _authRepo.Get( - new AuthenticationInfoQuery + var existing = (await _deviceManager.GetDevices( + new DeviceQuery { DeviceId = deviceId, UserId = user.Id, Limit = 1 - }).Items.FirstOrDefault(); + }).ConfigureAwait(false)).Items.FirstOrDefault(); - var allExistingForDevice = _authRepo.Get( - new AuthenticationInfoQuery + var allExistingForDevice = (await _deviceManager.GetDevices( + new DeviceQuery { DeviceId = deviceId - }).Items; + }).ConfigureAwait(false)).Items; foreach (var auth in allExistingForDevice) { @@ -1531,7 +1528,7 @@ namespace Emby.Server.Implementations.Session { try { - Logout(auth); + await Logout(auth).ConfigureAwait(false); } catch (Exception ex) { @@ -1546,29 +1543,14 @@ namespace Emby.Server.Implementations.Session return existing.AccessToken; } - var now = DateTime.UtcNow; - - var newToken = new AuthenticationInfo - { - AppName = app, - AppVersion = appVersion, - DateCreated = now, - DateLastActivity = now, - DeviceId = deviceId, - DeviceName = deviceName, - UserId = user.Id, - AccessToken = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture), - UserName = user.Username - }; - _logger.LogInformation("Creating new access token for user {0}", user.Id); - _authRepo.Create(newToken); + var device = await _deviceManager.CreateDevice(new Device(user.Id, app, appVersion, deviceName, deviceId)).ConfigureAwait(false); - return newToken.AccessToken; + return device.AccessToken; } /// <inheritdoc /> - public void Logout(string accessToken) + public async Task Logout(string accessToken) { CheckDisposed(); @@ -1577,27 +1559,27 @@ namespace Emby.Server.Implementations.Session throw new ArgumentNullException(nameof(accessToken)); } - var existing = _authRepo.Get( - new AuthenticationInfoQuery + var existing = (await _deviceManager.GetDevices( + new DeviceQuery { Limit = 1, AccessToken = accessToken - }).Items; + }).ConfigureAwait(false)).Items; if (existing.Count > 0) { - Logout(existing[0]); + await Logout(existing[0]).ConfigureAwait(false); } } /// <inheritdoc /> - public void Logout(AuthenticationInfo existing) + public async Task Logout(Device existing) { CheckDisposed(); _logger.LogInformation("Logging out access token {0}", existing.AccessToken); - _authRepo.Delete(existing); + await _deviceManager.DeleteDevice(existing).ConfigureAwait(false); var sessions = Sessions .Where(i => string.Equals(i.DeviceId, existing.DeviceId, StringComparison.OrdinalIgnoreCase)) @@ -1617,30 +1599,24 @@ namespace Emby.Server.Implementations.Session } /// <inheritdoc /> - public void RevokeUserTokens(Guid userId, string currentAccessToken) + public async Task RevokeUserTokens(Guid userId, string currentAccessToken) { CheckDisposed(); - var existing = _authRepo.Get(new AuthenticationInfoQuery + var existing = await _deviceManager.GetDevices(new DeviceQuery { UserId = userId - }); + }).ConfigureAwait(false); foreach (var info in existing.Items) { if (!string.Equals(currentAccessToken, info.AccessToken, StringComparison.OrdinalIgnoreCase)) { - Logout(info); + await Logout(info).ConfigureAwait(false); } } } - /// <inheritdoc /> - public void RevokeToken(string token) - { - Logout(token); - } - /// <summary> /// Reports the capabilities. /// </summary> @@ -1792,7 +1768,7 @@ namespace Emby.Server.Implementations.Session } /// <inheritdoc /> - public Task<SessionInfo> GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion) + public Task<SessionInfo> GetSessionByAuthenticationToken(Device info, string deviceId, string remoteEndpoint, string appVersion) { if (info == null) { @@ -1825,20 +1801,20 @@ namespace Emby.Server.Implementations.Session } /// <inheritdoc /> - public Task<SessionInfo> GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint) + public async Task<SessionInfo> GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint) { - var items = _authRepo.Get(new AuthenticationInfoQuery + var items = (await _deviceManager.GetDevices(new DeviceQuery { AccessToken = token, Limit = 1 - }).Items; + }).ConfigureAwait(false)).Items; if (items.Count == 0) { return null; } - return GetSessionByAuthenticationToken(items[0], deviceId, remoteEndpoint, null); + return await GetSessionByAuthenticationToken(items[0], deviceId, remoteEndpoint, null).ConfigureAwait(false); } /// <inheritdoc /> diff --git a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs index c56233794a..369e846aef 100644 --- a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs +++ b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs @@ -40,11 +40,11 @@ namespace Jellyfin.Api.Auth } /// <inheritdoc /> - protected override Task<AuthenticateResult> HandleAuthenticateAsync() + protected override async Task<AuthenticateResult> HandleAuthenticateAsync() { try { - var authorizationInfo = _authService.Authenticate(Request); + var authorizationInfo = await _authService.Authenticate(Request).ConfigureAwait(false); var role = UserRoles.User; if (authorizationInfo.IsApiKey || authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator)) { @@ -68,16 +68,16 @@ namespace Jellyfin.Api.Auth var principal = new ClaimsPrincipal(identity); var ticket = new AuthenticationTicket(principal, Scheme.Name); - return Task.FromResult(AuthenticateResult.Success(ticket)); + return AuthenticateResult.Success(ticket); } catch (AuthenticationException ex) { _logger.LogDebug(ex, "Error authenticating with {Handler}", nameof(CustomAuthenticationHandler)); - return Task.FromResult(AuthenticateResult.NoResult()); + return AuthenticateResult.NoResult(); } catch (SecurityException ex) { - return Task.FromResult(AuthenticateResult.Fail(ex)); + return AuthenticateResult.Fail(ex); } } } diff --git a/Jellyfin.Api/Controllers/CollectionController.cs b/Jellyfin.Api/Controllers/CollectionController.cs index 852d1e9cbd..8a98d856c4 100644 --- a/Jellyfin.Api/Controllers/CollectionController.cs +++ b/Jellyfin.Api/Controllers/CollectionController.cs @@ -58,7 +58,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] Guid? parentId, [FromQuery] bool isLocked = false) { - var userId = _authContext.GetAuthorizationInfo(Request).UserId; + var userId = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).UserId; var item = await _collectionManager.CreateCollectionAsync(new CollectionCreationOptions { diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs index 4cfae568ad..8af7b8f734 100644 --- a/Jellyfin.Api/Controllers/DevicesController.cs +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -3,8 +3,8 @@ using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Jellyfin.Api.Constants; using Jellyfin.Data.Entities.Security; +using Jellyfin.Data.Queries; using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Devices; using MediaBrowser.Model.Querying; @@ -21,22 +21,18 @@ namespace Jellyfin.Api.Controllers public class DevicesController : BaseJellyfinApiController { private readonly IDeviceManager _deviceManager; - private readonly IAuthenticationRepository _authenticationRepository; private readonly ISessionManager _sessionManager; /// <summary> /// Initializes a new instance of the <see cref="DevicesController"/> class. /// </summary> /// <param name="deviceManager">Instance of <see cref="IDeviceManager"/> interface.</param> - /// <param name="authenticationRepository">Instance of <see cref="IAuthenticationRepository"/> interface.</param> /// <param name="sessionManager">Instance of <see cref="ISessionManager"/> interface.</param> public DevicesController( IDeviceManager deviceManager, - IAuthenticationRepository authenticationRepository, ISessionManager sessionManager) { _deviceManager = deviceManager; - _authenticationRepository = authenticationRepository; _sessionManager = sessionManager; } @@ -111,7 +107,7 @@ namespace Jellyfin.Api.Controllers [FromQuery, Required] string id, [FromBody, Required] DeviceOptions deviceOptions) { - var existingDeviceOptions = _deviceManager.GetDeviceOptions(id); + var existingDeviceOptions = await _deviceManager.GetDeviceOptions(id).ConfigureAwait(false); if (existingDeviceOptions == null) { return NotFound(); @@ -131,19 +127,19 @@ namespace Jellyfin.Api.Controllers [HttpDelete] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult DeleteDevice([FromQuery, Required] string id) + public async Task<ActionResult> DeleteDevice([FromQuery, Required] string id) { - var existingDevice = _deviceManager.GetDevice(id); + var existingDevice = await _deviceManager.GetDevice(id).ConfigureAwait(false); if (existingDevice == null) { return NotFound(); } - var sessions = _authenticationRepository.Get(new AuthenticationInfoQuery { DeviceId = id }).Items; + var sessions = await _deviceManager.GetDevices(new DeviceQuery { DeviceId = id }).ConfigureAwait(false); - foreach (var session in sessions) + foreach (var session in sessions.Items) { - _sessionManager.Logout(session); + await _sessionManager.Logout(session).ConfigureAwait(false); } return NoContent(); diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index 8f7500ac69..9dc280e138 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -97,7 +97,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] ImageType imageType, [FromQuery] int? index = null) { - if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true)) + if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false)) { return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image."); } @@ -144,7 +144,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] ImageType imageType, [FromRoute] int index) { - if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true)) + if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false)) { return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image."); } @@ -190,7 +190,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] ImageType imageType, [FromQuery] int? index = null) { - if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true)) + if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false)) { return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to delete the image."); } @@ -234,7 +234,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] ImageType imageType, [FromRoute] int index) { - if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true)) + if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false)) { return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to delete the image."); } diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 4ed15e1d51..504f587906 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -331,10 +331,10 @@ namespace Jellyfin.Api.Controllers [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public ActionResult DeleteItem(Guid itemId) + public async Task<ActionResult> DeleteItem(Guid itemId) { var item = _libraryManager.GetItemById(itemId); - var auth = _authContext.GetAuthorizationInfo(Request); + var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); var user = auth.User; if (!item.CanDelete(user)) @@ -361,7 +361,7 @@ namespace Jellyfin.Api.Controllers [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public ActionResult DeleteItems([FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids) + public async Task<ActionResult> DeleteItems([FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids) { if (ids.Length == 0) { @@ -371,7 +371,7 @@ namespace Jellyfin.Api.Controllers foreach (var i in ids) { var item = _libraryManager.GetItemById(i); - var auth = _authContext.GetAuthorizationInfo(Request); + var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); var user = auth.User; if (!item.CanDelete(user)) @@ -627,7 +627,7 @@ namespace Jellyfin.Api.Controllers return NotFound(); } - var auth = _authContext.GetAuthorizationInfo(Request); + var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); var user = auth.User; diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs index e330f02b61..6c29e6bb84 100644 --- a/Jellyfin.Api/Controllers/MediaInfoController.cs +++ b/Jellyfin.Api/Controllers/MediaInfoController.cs @@ -123,7 +123,7 @@ namespace Jellyfin.Api.Controllers [FromQuery, ParameterObsolete] bool? allowAudioStreamCopy, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] PlaybackInfoDto? playbackInfoDto) { - var authInfo = _authContext.GetAuthorizationInfo(Request); + var authInfo = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); var profile = playbackInfoDto?.DeviceProfile; _logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", profile); diff --git a/Jellyfin.Api/Controllers/PlaystateController.cs b/Jellyfin.Api/Controllers/PlaystateController.cs index cc8c630b35..6dee1c2192 100644 --- a/Jellyfin.Api/Controllers/PlaystateController.cs +++ b/Jellyfin.Api/Controllers/PlaystateController.cs @@ -171,7 +171,8 @@ namespace Jellyfin.Api.Controllers _logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", playbackStopInfo.PlaySessionId ?? string.Empty); if (!string.IsNullOrWhiteSpace(playbackStopInfo.PlaySessionId)) { - await _transcodingJobHelper.KillTranscodingJobs(_authContext.GetAuthorizationInfo(Request).DeviceId, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false); + var authInfo = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); + await _transcodingJobHelper.KillTranscodingJobs(authInfo.DeviceId, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false); } playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false); @@ -320,7 +321,8 @@ namespace Jellyfin.Api.Controllers _logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", playbackStopInfo.PlaySessionId ?? string.Empty); if (!string.IsNullOrWhiteSpace(playbackStopInfo.PlaySessionId)) { - await _transcodingJobHelper.KillTranscodingJobs(_authContext.GetAuthorizationInfo(Request).DeviceId, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false); + var authInfo = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); + await _transcodingJobHelper.KillTranscodingJobs(authInfo.DeviceId, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false); } playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _authContext, Request).ConfigureAwait(false); diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs index 116d669e40..3a04cb3a4a 100644 --- a/Jellyfin.Api/Controllers/SessionController.cs +++ b/Jellyfin.Api/Controllers/SessionController.cs @@ -471,11 +471,11 @@ namespace Jellyfin.Api.Controllers [HttpPost("Sessions/Logout")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] - public ActionResult ReportSessionEnded() + public async Task<ActionResult> ReportSessionEnded() { - AuthorizationInfo auth = _authContext.GetAuthorizationInfo(Request); + AuthorizationInfo auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); - _sessionManager.Logout(auth.Token); + await _sessionManager.Logout(auth.Token).ConfigureAwait(false); return NoContent(); } diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs index 1669a659dc..2d5339b166 100644 --- a/Jellyfin.Api/Controllers/SubtitleController.cs +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -361,7 +361,7 @@ namespace Jellyfin.Api.Controllers long positionTicks = 0; - var accessToken = _authContext.GetAuthorizationInfo(Request).Token; + var accessToken = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).Token; while (positionTicks < runtime) { diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index 679f055bc2..20a02bf4a9 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -116,9 +116,9 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool enableRedirection = true) { var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels); - _authorizationContext.GetAuthorizationInfo(Request).DeviceId = deviceId; + (await _authorizationContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).DeviceId = deviceId; - var authInfo = _authorizationContext.GetAuthorizationInfo(Request); + var authInfo = await _authorizationContext.GetAuthorizationInfo(Request).ConfigureAwait(false); _logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", deviceProfile); diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index b13db4baa9..8e2298bb7d 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -77,11 +77,11 @@ namespace Jellyfin.Api.Controllers [HttpGet] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<IEnumerable<UserDto>> GetUsers( + public async Task<ActionResult<IEnumerable<UserDto>>> GetUsers( [FromQuery] bool? isHidden, [FromQuery] bool? isDisabled) { - var users = Get(isHidden, isDisabled, false, false); + var users = await Get(isHidden, isDisabled, false, false).ConfigureAwait(false); return Ok(users); } @@ -92,15 +92,15 @@ namespace Jellyfin.Api.Controllers /// <returns>An <see cref="IEnumerable{UserDto}"/> containing the public users.</returns> [HttpGet("Public")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<IEnumerable<UserDto>> GetPublicUsers() + public async Task<ActionResult<IEnumerable<UserDto>>> GetPublicUsers() { // If the startup wizard hasn't been completed then just return all users if (!_config.Configuration.IsStartupWizardCompleted) { - return Ok(Get(false, false, false, false)); + return Ok(await Get(false, false, false, false).ConfigureAwait(false)); } - return Ok(Get(false, false, true, true)); + return Ok(await Get(false, false, true, true).ConfigureAwait(false)); } /// <summary> @@ -141,7 +141,7 @@ namespace Jellyfin.Api.Controllers public async Task<ActionResult> DeleteUser([FromRoute, Required] Guid userId) { var user = _userManager.GetUserById(userId); - _sessionManager.RevokeUserTokens(user.Id, null); + await _sessionManager.RevokeUserTokens(user.Id, null).ConfigureAwait(false); await _userManager.DeleteUserAsync(userId).ConfigureAwait(false); return NoContent(); } @@ -195,7 +195,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] public async Task<ActionResult<AuthenticationResult>> AuthenticateUserByName([FromBody, Required] AuthenticateUserByName request) { - var auth = _authContext.GetAuthorizationInfo(Request); + var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); try { @@ -230,7 +230,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] public async Task<ActionResult<AuthenticationResult>> AuthenticateWithQuickConnect([FromBody, Required] QuickConnectDto request) { - var auth = _authContext.GetAuthorizationInfo(Request); + var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); try { @@ -271,7 +271,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] Guid userId, [FromBody, Required] UpdateUserPassword request) { - if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true)) + if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false)) { return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the password."); } @@ -303,9 +303,9 @@ namespace Jellyfin.Api.Controllers await _userManager.ChangePassword(user, request.NewPw).ConfigureAwait(false); - var currentToken = _authContext.GetAuthorizationInfo(Request).Token; + var currentToken = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).Token; - _sessionManager.RevokeUserTokens(user.Id, currentToken); + await _sessionManager.RevokeUserTokens(user.Id, currentToken).ConfigureAwait(false); } return NoContent(); @@ -325,11 +325,11 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult UpdateUserEasyPassword( + public async Task<ActionResult> UpdateUserEasyPassword( [FromRoute, Required] Guid userId, [FromBody, Required] UpdateUserEasyPassword request) { - if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true)) + if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true).ConfigureAwait(false)) { return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the easy password."); } @@ -343,11 +343,11 @@ namespace Jellyfin.Api.Controllers if (request.ResetPassword) { - _userManager.ResetEasyPassword(user); + await _userManager.ResetEasyPassword(user).ConfigureAwait(false); } else { - _userManager.ChangeEasyPassword(user, request.NewPw, request.NewPassword); + await _userManager.ChangeEasyPassword(user, request.NewPw, request.NewPassword).ConfigureAwait(false); } return NoContent(); @@ -371,7 +371,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] Guid userId, [FromBody, Required] UserDto updateUser) { - if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, false)) + if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, false).ConfigureAwait(false)) { return StatusCode(StatusCodes.Status403Forbidden, "User update not allowed."); } @@ -431,8 +431,8 @@ namespace Jellyfin.Api.Controllers return StatusCode(StatusCodes.Status403Forbidden, "There must be at least one enabled user in the system."); } - var currentToken = _authContext.GetAuthorizationInfo(Request).Token; - _sessionManager.RevokeUserTokens(user.Id, currentToken); + var currentToken = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).Token; + await _sessionManager.RevokeUserTokens(user.Id, currentToken).ConfigureAwait(false); } await _userManager.UpdatePolicyAsync(userId, newPolicy).ConfigureAwait(false); @@ -456,7 +456,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] Guid userId, [FromBody, Required] UserConfiguration userConfig) { - if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, false)) + if (!await RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, false).ConfigureAwait(false)) { return StatusCode(StatusCodes.Status403Forbidden, "User configuration update not allowed"); } @@ -555,7 +555,7 @@ namespace Jellyfin.Api.Controllers return _userManager.GetUserDto(user); } - private IEnumerable<UserDto> Get(bool? isHidden, bool? isDisabled, bool filterByDevice, bool filterByNetwork) + private async Task<IEnumerable<UserDto>> Get(bool? isHidden, bool? isDisabled, bool filterByDevice, bool filterByNetwork) { var users = _userManager.Users; @@ -571,7 +571,7 @@ namespace Jellyfin.Api.Controllers if (filterByDevice) { - var deviceId = _authContext.GetAuthorizationInfo(Request).DeviceId; + var deviceId = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).DeviceId; if (!string.IsNullOrWhiteSpace(deviceId)) { diff --git a/Jellyfin.Api/Controllers/UserViewsController.cs b/Jellyfin.Api/Controllers/UserViewsController.cs index 7bc5ecdf1d..3d27371f63 100644 --- a/Jellyfin.Api/Controllers/UserViewsController.cs +++ b/Jellyfin.Api/Controllers/UserViewsController.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Linq; +using System.Threading.Tasks; using Jellyfin.Api.Extensions; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.UserViewDtos; @@ -64,7 +65,7 @@ namespace Jellyfin.Api.Controllers /// <returns>An <see cref="OkResult"/> containing the user views.</returns> [HttpGet("Users/{userId}/Views")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<QueryResult<BaseItemDto>> GetUserViews( + public async Task<ActionResult<QueryResult<BaseItemDto>>> GetUserViews( [FromRoute, Required] Guid userId, [FromQuery] bool? includeExternalContent, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] presetViews, @@ -86,7 +87,7 @@ namespace Jellyfin.Api.Controllers query.PresetViews = presetViews; } - var app = _authContext.GetAuthorizationInfo(Request).Client ?? string.Empty; + var app = (await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).Client ?? string.Empty; if (app.IndexOf("emby rt", StringComparison.OrdinalIgnoreCase) != -1) { query.PresetViews = new[] { CollectionType.Movies, CollectionType.TvShows }; diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs index 295cfaf089..3b8dc7e316 100644 --- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs +++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs @@ -468,7 +468,7 @@ namespace Jellyfin.Api.Helpers /// <returns>A <see cref="Task"/> containing the <see cref="LiveStreamResponse"/>.</returns> public async Task<LiveStreamResponse> OpenMediaSource(HttpRequest httpRequest, LiveStreamRequest request) { - var authInfo = _authContext.GetAuthorizationInfo(httpRequest); + var authInfo = await _authContext.GetAuthorizationInfo(httpRequest).ConfigureAwait(false); var result = await _mediaSourceManager.OpenLiveStream(request, CancellationToken.None).ConfigureAwait(false); diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index 3810f74779..0efd3443b8 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -60,9 +60,9 @@ namespace Jellyfin.Api.Helpers /// <param name="userId">The user id.</param> /// <param name="restrictUserPreferences">Whether to restrict the user preferences.</param> /// <returns>A <see cref="bool"/> whether the user can update the entry.</returns> - internal static bool AssertCanUpdateUser(IAuthorizationContext authContext, HttpRequest requestContext, Guid userId, bool restrictUserPreferences) + internal static async Task<bool> AssertCanUpdateUser(IAuthorizationContext authContext, HttpRequest requestContext, Guid userId, bool restrictUserPreferences) { - var auth = authContext.GetAuthorizationInfo(requestContext); + var auth = await authContext.GetAuthorizationInfo(requestContext).ConfigureAwait(false); var authenticatedUser = auth.User; @@ -78,7 +78,7 @@ namespace Jellyfin.Api.Helpers internal static async Task<SessionInfo> GetSession(ISessionManager sessionManager, IAuthorizationContext authContext, HttpRequest request) { - var authorization = authContext.GetAuthorizationInfo(request); + var authorization = await authContext.GetAuthorizationInfo(request).ConfigureAwait(false); var user = authorization.User; var session = await sessionManager.LogSessionActivity( authorization.Client, diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 8cffe9c4c9..cecbd36c12 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -17,9 +17,7 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; @@ -101,7 +99,7 @@ namespace Jellyfin.Api.Helpers EnableDlnaHeaders = enableDlnaHeaders }; - var auth = authorizationContext.GetAuthorizationInfo(httpRequest); + var auth = await authorizationContext.GetAuthorizationInfo(httpRequest).ConfigureAwait(false); if (!auth.UserId.Equals(Guid.Empty)) { state.User = userManager.GetUserById(auth.UserId); diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 0879cbd18f..2420779633 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -489,7 +489,7 @@ namespace Jellyfin.Api.Helpers if (state.VideoRequest != null && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) { - var auth = _authorizationContext.GetAuthorizationInfo(request); + var auth = await _authorizationContext.GetAuthorizationInfo(request).ConfigureAwait(false); if (auth.User != null && !auth.User.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)) { this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state); diff --git a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs index bde8eb86ee..9fd2e5ad4c 100644 --- a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs +++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs @@ -6,6 +6,7 @@ using Jellyfin.Data.Entities; using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Enums; using Jellyfin.Data.Events; +using Jellyfin.Data.Queries; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Devices; @@ -55,6 +56,17 @@ namespace Jellyfin.Server.Implementations.Devices DeviceOptionsUpdated?.Invoke(this, new GenericEventArgs<Tuple<string, DeviceOptions>>(new Tuple<string, DeviceOptions>(deviceId, options))); } + /// <inheritdoc /> + public async Task<Device> CreateDevice(Device device) + { + await using var dbContext = _dbProvider.CreateContext(); + + dbContext.Devices.Add(device); + + await dbContext.SaveChangesAsync().ConfigureAwait(false); + return device; + } + /// <inheritdoc /> public async Task<DeviceOptions?> GetDeviceOptions(string deviceId) { @@ -90,6 +102,61 @@ namespace Jellyfin.Server.Implementations.Devices return deviceInfo; } + /// <inheritdoc /> + public async Task<QueryResult<Device>> GetDevices(DeviceQuery query) + { + await using var dbContext = _dbProvider.CreateContext(); + + var devices = dbContext.Devices.AsQueryable(); + + if (query.UserId.HasValue) + { + devices = devices.Where(device => device.UserId == query.UserId.Value); + } + + if (query.DeviceId != null) + { + devices = devices.Where(device => device.DeviceId == query.DeviceId); + } + + if (query.AccessToken != null) + { + devices = devices.Where(device => device.AccessToken == query.AccessToken); + } + + if (query.Skip.HasValue) + { + devices = devices.Skip(query.Skip.Value); + } + + var count = await devices.CountAsync().ConfigureAwait(false); + + if (query.Limit.HasValue) + { + devices = devices.Take(query.Limit.Value); + } + + return new QueryResult<Device> + { + Items = await devices.ToListAsync().ConfigureAwait(false), + StartIndex = query.Skip ?? 0, + TotalRecordCount = count + }; + } + + /// <inheritdoc /> + public async Task<QueryResult<DeviceInfo>> GetDeviceInfos(DeviceQuery query) + { + var devices = await GetDevices(query).ConfigureAwait(false); + + return new QueryResult<DeviceInfo> + { + Items = devices.Items.Select(ToDeviceInfo).ToList(), + StartIndex = devices.StartIndex, + TotalRecordCount = devices.TotalRecordCount + }; + } + /// <inheritdoc /> public async Task<QueryResult<DeviceInfo>> GetDevicesForUser(Guid? userId, bool? supportsSync) { @@ -117,6 +184,12 @@ namespace Jellyfin.Server.Implementations.Devices return new QueryResult<DeviceInfo>(array); } + /// <inheritdoc /> + public async Task DeleteDevice(Device device) + { + await using var dbContext = _dbProvider.CreateContext(); + } + /// <inheritdoc /> public bool CanAccessDevice(User user, string deviceId) { diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 059e884e5f..6f35a2c1c2 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -214,6 +214,15 @@ namespace Jellyfin.Server.Implementations modelBuilder.Entity<Device>() .HasIndex(entity => new { entity.DeviceId, entity.DateLastActivity }); + modelBuilder.Entity<Device>() + .HasIndex(entity => new { entity.AccessToken, entity.DateLastActivity }); + + modelBuilder.Entity<Device>() + .HasIndex(entity => new { entity.UserId, entity.DeviceId }); + + modelBuilder.Entity<Device>() + .HasIndex(entity => entity.DeviceId); + modelBuilder.Entity<DeviceOptions>() .HasIndex(entity => entity.DeviceId) .IsUnique(); diff --git a/Jellyfin.Server.Implementations/Migrations/20210521032224_AddDevices.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20210521032224_AddDevices.Designer.cs new file mode 100644 index 0000000000..e1faef7a2f --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20210521032224_AddDevices.Designer.cs @@ -0,0 +1,645 @@ +#pragma warning disable CS1591 +// <auto-generated /> +using System; +using Jellyfin.Server.Implementations; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Jellyfin.Server.Implementations.Migrations +{ + [DbContext(typeof(JellyfinDb))] + [Migration("20210521032224_AddDevices")] + partial class AddDevices + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("jellyfin") + .HasAnnotation("ProductVersion", "5.0.5"); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<int>("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property<double>("EndHour") + .HasColumnType("REAL"); + + b.Property<double>("StartHour") + .HasColumnType("REAL"); + + b.Property<Guid>("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AccessSchedules"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<DateTime>("DateCreated") + .HasColumnType("TEXT"); + + b.Property<string>("ItemId") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property<int>("LogSeverity") + .HasColumnType("INTEGER"); + + b.Property<string>("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property<string>("Overview") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property<uint>("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property<string>("ShortOverview") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property<string>("Type") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property<Guid>("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ActivityLogs"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<string>("Client") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property<Guid>("ItemId") + .HasColumnType("TEXT"); + + b.Property<string>("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property<Guid>("UserId") + .HasColumnType("TEXT"); + + b.Property<string>("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "ItemId", "Client", "Key") + .IsUnique(); + + b.ToTable("CustomItemDisplayPreferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<int>("ChromecastVersion") + .HasColumnType("INTEGER"); + + b.Property<string>("Client") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property<string>("DashboardTheme") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property<bool>("EnableNextVideoInfoOverlay") + .HasColumnType("INTEGER"); + + b.Property<int?>("IndexBy") + .HasColumnType("INTEGER"); + + b.Property<Guid>("ItemId") + .HasColumnType("TEXT"); + + b.Property<int>("ScrollDirection") + .HasColumnType("INTEGER"); + + b.Property<bool>("ShowBackdrop") + .HasColumnType("INTEGER"); + + b.Property<bool>("ShowSidebar") + .HasColumnType("INTEGER"); + + b.Property<int>("SkipBackwardLength") + .HasColumnType("INTEGER"); + + b.Property<int>("SkipForwardLength") + .HasColumnType("INTEGER"); + + b.Property<string>("TvHome") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property<Guid>("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "ItemId", "Client") + .IsUnique(); + + b.ToTable("DisplayPreferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<int>("DisplayPreferencesId") + .HasColumnType("INTEGER"); + + b.Property<int>("Order") + .HasColumnType("INTEGER"); + + b.Property<int>("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DisplayPreferencesId"); + + b.ToTable("HomeSection"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<DateTime>("LastModified") + .HasColumnType("TEXT"); + + b.Property<string>("Path") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property<Guid?>("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("ImageInfos"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<string>("Client") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property<int?>("IndexBy") + .HasColumnType("INTEGER"); + + b.Property<Guid>("ItemId") + .HasColumnType("TEXT"); + + b.Property<bool>("RememberIndexing") + .HasColumnType("INTEGER"); + + b.Property<bool>("RememberSorting") + .HasColumnType("INTEGER"); + + b.Property<string>("SortBy") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property<int>("SortOrder") + .HasColumnType("INTEGER"); + + b.Property<Guid>("UserId") + .HasColumnType("TEXT"); + + b.Property<int>("ViewType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ItemDisplayPreferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<int>("Kind") + .HasColumnType("INTEGER"); + + b.Property<Guid?>("Permission_Permissions_Guid") + .HasColumnType("TEXT"); + + b.Property<uint>("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property<Guid?>("UserId") + .HasColumnType("TEXT"); + + b.Property<bool>("Value") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "Kind") + .IsUnique() + .HasFilter("[UserId] IS NOT NULL"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<int>("Kind") + .HasColumnType("INTEGER"); + + b.Property<Guid?>("Preference_Preferences_Guid") + .HasColumnType("TEXT"); + + b.Property<uint>("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property<Guid?>("UserId") + .HasColumnType("TEXT"); + + b.Property<string>("Value") + .IsRequired() + .HasMaxLength(65535) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "Kind") + .IsUnique() + .HasFilter("[UserId] IS NOT NULL"); + + b.ToTable("Preferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Security.ApiKey", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<Guid>("AccessToken") + .HasColumnType("TEXT"); + + b.Property<DateTime>("DateCreated") + .HasColumnType("TEXT"); + + b.Property<string>("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AccessToken") + .IsUnique(); + + b.ToTable("ApiKeys"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<string>("AccessToken") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property<string>("AppName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property<string>("AppVersion") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property<DateTime>("DateCreated") + .HasColumnType("TEXT"); + + b.Property<DateTime>("DateLastActivity") + .HasColumnType("TEXT"); + + b.Property<string>("DeviceId") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property<string>("DeviceName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property<bool>("IsActive") + .HasColumnType("INTEGER"); + + b.Property<Guid>("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.HasIndex("AccessToken", "DateLastActivity"); + + b.HasIndex("DeviceId", "DateLastActivity"); + + b.HasIndex("UserId", "DeviceId"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Security.DeviceOptions", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<string>("CustomName") + .HasColumnType("TEXT"); + + b.Property<string>("DeviceId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId") + .IsUnique(); + + b.ToTable("DeviceOptions"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.User", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property<string>("AudioLanguagePreference") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property<string>("AuthenticationProviderId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property<bool>("DisplayCollectionsView") + .HasColumnType("INTEGER"); + + b.Property<bool>("DisplayMissingEpisodes") + .HasColumnType("INTEGER"); + + b.Property<string>("EasyPassword") + .HasMaxLength(65535) + .HasColumnType("TEXT"); + + b.Property<bool>("EnableAutoLogin") + .HasColumnType("INTEGER"); + + b.Property<bool>("EnableLocalPassword") + .HasColumnType("INTEGER"); + + b.Property<bool>("EnableNextEpisodeAutoPlay") + .HasColumnType("INTEGER"); + + b.Property<bool>("EnableUserPreferenceAccess") + .HasColumnType("INTEGER"); + + b.Property<bool>("HidePlayedInLatest") + .HasColumnType("INTEGER"); + + b.Property<long>("InternalId") + .HasColumnType("INTEGER"); + + b.Property<int>("InvalidLoginAttemptCount") + .HasColumnType("INTEGER"); + + b.Property<DateTime?>("LastActivityDate") + .HasColumnType("TEXT"); + + b.Property<DateTime?>("LastLoginDate") + .HasColumnType("TEXT"); + + b.Property<int?>("LoginAttemptsBeforeLockout") + .HasColumnType("INTEGER"); + + b.Property<int>("MaxActiveSessions") + .HasColumnType("INTEGER"); + + b.Property<int?>("MaxParentalAgeRating") + .HasColumnType("INTEGER"); + + b.Property<bool>("MustUpdatePassword") + .HasColumnType("INTEGER"); + + b.Property<string>("Password") + .HasMaxLength(65535) + .HasColumnType("TEXT"); + + b.Property<string>("PasswordResetProviderId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property<bool>("PlayDefaultAudioTrack") + .HasColumnType("INTEGER"); + + b.Property<bool>("RememberAudioSelections") + .HasColumnType("INTEGER"); + + b.Property<bool>("RememberSubtitleSelections") + .HasColumnType("INTEGER"); + + b.Property<int?>("RemoteClientBitrateLimit") + .HasColumnType("INTEGER"); + + b.Property<uint>("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property<string>("SubtitleLanguagePreference") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property<int>("SubtitleMode") + .HasColumnType("INTEGER"); + + b.Property<int>("SyncPlayAccess") + .HasColumnType("INTEGER"); + + b.Property<string>("Username") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("TEXT") + .UseCollation("NOCASE"); + + b.HasKey("Id"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("AccessSchedules") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("DisplayPreferences") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b => + { + b.HasOne("Jellyfin.Data.Entities.DisplayPreferences", null) + .WithMany("HomeSections") + .HasForeignKey("DisplayPreferencesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithOne("ProfileImage") + .HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("ItemDisplayPreferences") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Permissions") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Preferences") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b => + { + b.HasOne("Jellyfin.Data.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => + { + b.Navigation("HomeSections"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.User", b => + { + b.Navigation("AccessSchedules"); + + b.Navigation("DisplayPreferences"); + + b.Navigation("ItemDisplayPreferences"); + + b.Navigation("Permissions"); + + b.Navigation("Preferences"); + + b.Navigation("ProfileImage"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/20210521032224_AddDevices.cs b/Jellyfin.Server.Implementations/Migrations/20210521032224_AddDevices.cs new file mode 100644 index 0000000000..2da8d37881 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20210521032224_AddDevices.cs @@ -0,0 +1,126 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1601 +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Jellyfin.Server.Implementations.Migrations +{ + public partial class AddDevices : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ApiKeys", + schema: "jellyfin", + columns: table => new + { + Id = table.Column<int>(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DateCreated = table.Column<DateTime>(type: "TEXT", nullable: false), + Name = table.Column<string>(type: "TEXT", maxLength: 64, nullable: false), + AccessToken = table.Column<Guid>(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiKeys", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "DeviceOptions", + schema: "jellyfin", + columns: table => new + { + Id = table.Column<int>(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DeviceId = table.Column<string>(type: "TEXT", nullable: false), + CustomName = table.Column<string>(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_DeviceOptions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Devices", + schema: "jellyfin", + columns: table => new + { + Id = table.Column<int>(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column<Guid>(type: "TEXT", nullable: false), + AccessToken = table.Column<string>(type: "TEXT", nullable: false), + AppName = table.Column<string>(type: "TEXT", maxLength: 64, nullable: false), + AppVersion = table.Column<string>(type: "TEXT", maxLength: 32, nullable: false), + DeviceName = table.Column<string>(type: "TEXT", maxLength: 64, nullable: false), + DeviceId = table.Column<string>(type: "TEXT", maxLength: 256, nullable: false), + IsActive = table.Column<bool>(type: "INTEGER", nullable: false), + DateCreated = table.Column<DateTime>(type: "TEXT", nullable: false), + DateLastActivity = table.Column<DateTime>(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Devices", x => x.Id); + table.ForeignKey( + name: "FK_Devices_Users_UserId", + column: x => x.UserId, + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ApiKeys_AccessToken", + schema: "jellyfin", + table: "ApiKeys", + column: "AccessToken", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_DeviceOptions_DeviceId", + schema: "jellyfin", + table: "DeviceOptions", + column: "DeviceId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Devices_AccessToken_DateLastActivity", + schema: "jellyfin", + table: "Devices", + columns: new[] { "AccessToken", "DateLastActivity" }); + + migrationBuilder.CreateIndex( + name: "IX_Devices_DeviceId", + schema: "jellyfin", + table: "Devices", + column: "DeviceId"); + + migrationBuilder.CreateIndex( + name: "IX_Devices_DeviceId_DateLastActivity", + schema: "jellyfin", + table: "Devices", + columns: new[] { "DeviceId", "DateLastActivity" }); + + migrationBuilder.CreateIndex( + name: "IX_Devices_UserId_DeviceId", + schema: "jellyfin", + table: "Devices", + columns: new[] { "UserId", "DeviceId" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ApiKeys", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "DeviceOptions", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Devices", + schema: "jellyfin"); + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs index 286eb7468a..8a1ae16f84 100644 --- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs +++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs @@ -15,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("jellyfin") - .HasAnnotation("ProductVersion", "5.0.3"); + .HasAnnotation("ProductVersion", "5.0.5"); modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => { @@ -332,6 +332,107 @@ namespace Jellyfin.Server.Implementations.Migrations b.ToTable("Preferences"); }); + modelBuilder.Entity("Jellyfin.Data.Entities.Security.ApiKey", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<Guid>("AccessToken") + .HasColumnType("TEXT"); + + b.Property<DateTime>("DateCreated") + .HasColumnType("TEXT"); + + b.Property<string>("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AccessToken") + .IsUnique(); + + b.ToTable("ApiKeys"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<string>("AccessToken") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property<string>("AppName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property<string>("AppVersion") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property<DateTime>("DateCreated") + .HasColumnType("TEXT"); + + b.Property<DateTime>("DateLastActivity") + .HasColumnType("TEXT"); + + b.Property<string>("DeviceId") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property<string>("DeviceName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property<bool>("IsActive") + .HasColumnType("INTEGER"); + + b.Property<Guid>("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.HasIndex("AccessToken", "DateLastActivity"); + + b.HasIndex("DeviceId", "DateLastActivity"); + + b.HasIndex("UserId", "DeviceId"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Security.DeviceOptions", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<string>("CustomName") + .HasColumnType("TEXT"); + + b.Property<string>("DeviceId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId") + .IsUnique(); + + b.ToTable("DeviceOptions"); + }); + modelBuilder.Entity("Jellyfin.Data.Entities.User", b => { b.Property<Guid>("Id") @@ -505,6 +606,17 @@ namespace Jellyfin.Server.Implementations.Migrations .OnDelete(DeleteBehavior.Cascade); }); + modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b => + { + b.HasOne("Jellyfin.Data.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b => { b.Navigation("HomeSections"); diff --git a/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs b/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs new file mode 100644 index 0000000000..775edafc2d --- /dev/null +++ b/Jellyfin.Server.Implementations/Security/AuthorizationContext.cs @@ -0,0 +1,283 @@ +#pragma warning disable CS1591 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using Microsoft.AspNetCore.Http; +using Microsoft.Net.Http.Headers; + +namespace Jellyfin.Server.Implementations.Security +{ + public class AuthorizationContext : IAuthorizationContext + { + private readonly JellyfinDb _jellyfinDb; + private readonly IUserManager _userManager; + + public AuthorizationContext(JellyfinDb jellyfinDb, IUserManager userManager) + { + _jellyfinDb = jellyfinDb; + _userManager = userManager; + } + + public Task<AuthorizationInfo> GetAuthorizationInfo(HttpContext requestContext) + { + if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached) && cached != null) + { + return Task.FromResult((AuthorizationInfo)cached); + } + + return GetAuthorization(requestContext); + } + + public async Task<AuthorizationInfo> GetAuthorizationInfo(HttpRequest requestContext) + { + var auth = GetAuthorizationDictionary(requestContext); + var authInfo = await GetAuthorizationInfoFromDictionary(auth, requestContext.Headers, requestContext.Query).ConfigureAwait(false); + return authInfo; + } + + /// <summary> + /// Gets the authorization. + /// </summary> + /// <param name="httpReq">The HTTP req.</param> + /// <returns>Dictionary{System.StringSystem.String}.</returns> + private async Task<AuthorizationInfo> GetAuthorization(HttpContext httpReq) + { + var auth = GetAuthorizationDictionary(httpReq); + var authInfo = await GetAuthorizationInfoFromDictionary(auth, httpReq.Request.Headers, httpReq.Request.Query).ConfigureAwait(false); + + httpReq.Request.HttpContext.Items["AuthorizationInfo"] = authInfo; + return authInfo; + } + + private async Task<AuthorizationInfo> GetAuthorizationInfoFromDictionary( + IReadOnlyDictionary<string, string>? auth, + IHeaderDictionary headers, + IQueryCollection queryString) + { + string? deviceId = null; + string? deviceName = null; + string? client = null; + string? version = null; + string? token = null; + + if (auth != null) + { + auth.TryGetValue("DeviceId", out deviceId); + auth.TryGetValue("Device", out deviceName); + auth.TryGetValue("Client", out client); + auth.TryGetValue("Version", out version); + auth.TryGetValue("Token", out token); + } + +#pragma warning disable CA1508 + // headers can return StringValues.Empty + if (string.IsNullOrEmpty(token)) + { + token = headers["X-Emby-Token"]; + } + + if (string.IsNullOrEmpty(token)) + { + token = headers["X-MediaBrowser-Token"]; + } + + if (string.IsNullOrEmpty(token)) + { + token = queryString["ApiKey"]; + } + + // TODO deprecate this query parameter. + if (string.IsNullOrEmpty(token)) + { + token = queryString["api_key"]; + } + + var authInfo = new AuthorizationInfo + { + Client = client, + Device = deviceName, + DeviceId = deviceId, + Version = version, + Token = token, + IsAuthenticated = false, + HasToken = false + }; + + if (string.IsNullOrWhiteSpace(token)) + { + // Request doesn't contain a token. + return authInfo; + } +#pragma warning restore CA1508 + + authInfo.HasToken = true; + var device = await _jellyfinDb.Devices.FirstOrDefaultAsync(d => d.AccessToken == token).ConfigureAwait(false); + + if (device != null) + { + authInfo.IsAuthenticated = true; + } + + if (device != null) + { + var updateToken = false; + + // TODO: Remove these checks for IsNullOrWhiteSpace + if (string.IsNullOrWhiteSpace(authInfo.Client)) + { + authInfo.Client = device.AppName; + } + + if (string.IsNullOrWhiteSpace(authInfo.DeviceId)) + { + authInfo.DeviceId = device.DeviceId; + } + + // Temporary. TODO - allow clients to specify that the token has been shared with a casting device + var allowTokenInfoUpdate = !authInfo.Client.Contains("chromecast", StringComparison.OrdinalIgnoreCase); + + if (string.IsNullOrWhiteSpace(authInfo.Device)) + { + authInfo.Device = device.DeviceName; + } + else if (!string.Equals(authInfo.Device, device.DeviceName, StringComparison.OrdinalIgnoreCase)) + { + if (allowTokenInfoUpdate) + { + updateToken = true; + device.DeviceName = authInfo.Device; + } + } + + if (string.IsNullOrWhiteSpace(authInfo.Version)) + { + authInfo.Version = device.AppVersion; + } + else if (!string.Equals(authInfo.Version, device.AppVersion, StringComparison.OrdinalIgnoreCase)) + { + if (allowTokenInfoUpdate) + { + updateToken = true; + device.AppVersion = authInfo.Version; + } + } + + if ((DateTime.UtcNow - device.DateLastActivity).TotalMinutes > 3) + { + device.DateLastActivity = DateTime.UtcNow; + updateToken = true; + } + + if (!device.UserId.Equals(Guid.Empty)) + { + authInfo.User = _userManager.GetUserById(device.UserId); + authInfo.IsApiKey = false; + } + else + { + authInfo.IsApiKey = true; + } + + if (updateToken) + { + _jellyfinDb.Devices.Update(device); + await _jellyfinDb.SaveChangesAsync().ConfigureAwait(false); + } + } + + return authInfo; + } + + /// <summary> + /// Gets the auth. + /// </summary> + /// <param name="httpReq">The HTTP req.</param> + /// <returns>Dictionary{System.StringSystem.String}.</returns> + private Dictionary<string, string>? GetAuthorizationDictionary(HttpContext httpReq) + { + var auth = httpReq.Request.Headers["X-Emby-Authorization"]; + + if (string.IsNullOrEmpty(auth)) + { + auth = httpReq.Request.Headers[HeaderNames.Authorization]; + } + + return GetAuthorization(auth); + } + + /// <summary> + /// Gets the auth. + /// </summary> + /// <param name="httpReq">The HTTP req.</param> + /// <returns>Dictionary{System.StringSystem.String}.</returns> + private Dictionary<string, string>? GetAuthorizationDictionary(HttpRequest httpReq) + { + var auth = httpReq.Headers["X-Emby-Authorization"]; + + if (string.IsNullOrEmpty(auth)) + { + auth = httpReq.Headers[HeaderNames.Authorization]; + } + + return GetAuthorization(auth); + } + + /// <summary> + /// Gets the authorization. + /// </summary> + /// <param name="authorizationHeader">The authorization header.</param> + /// <returns>Dictionary{System.StringSystem.String}.</returns> + private Dictionary<string, string>? GetAuthorization(string? authorizationHeader) + { + if (authorizationHeader == null) + { + return null; + } + + var parts = authorizationHeader.Split(' ', 2); + + // There should be at least to parts + if (parts.Length != 2) + { + return null; + } + + var acceptedNames = new[] { "MediaBrowser", "Emby" }; + + // It has to be a digest request + if (!acceptedNames.Contains(parts[0], StringComparer.OrdinalIgnoreCase)) + { + return null; + } + + // Remove uptil the first space + authorizationHeader = parts[1]; + parts = authorizationHeader.Split(','); + + var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + + foreach (var item in parts) + { + var param = item.Trim().Split('=', 2); + + if (param.Length == 2) + { + var value = NormalizeValue(param[1].Trim('"')); + result[param[0]] = value; + } + } + + return result; + } + + private static string NormalizeValue(string value) + { + return string.IsNullOrEmpty(value) ? value : WebUtility.HtmlEncode(value); + } + } +} diff --git a/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs b/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs index dbba80c210..a471ea1d50 100644 --- a/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs +++ b/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs @@ -4,10 +4,10 @@ using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using Jellyfin.Data.Events; +using Jellyfin.Data.Queries; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; -using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; namespace Jellyfin.Server.Implementations.Users @@ -15,14 +15,12 @@ namespace Jellyfin.Server.Implementations.Users public sealed class DeviceAccessEntryPoint : IServerEntryPoint { private readonly IUserManager _userManager; - private readonly IAuthenticationRepository _authRepo; private readonly IDeviceManager _deviceManager; private readonly ISessionManager _sessionManager; - public DeviceAccessEntryPoint(IUserManager userManager, IAuthenticationRepository authRepo, IDeviceManager deviceManager, ISessionManager sessionManager) + public DeviceAccessEntryPoint(IUserManager userManager, IDeviceManager deviceManager, ISessionManager sessionManager) { _userManager = userManager; - _authRepo = authRepo; _deviceManager = deviceManager; _sessionManager = sessionManager; } @@ -38,27 +36,27 @@ namespace Jellyfin.Server.Implementations.Users { } - private void OnUserUpdated(object? sender, GenericEventArgs<User> e) + private async void OnUserUpdated(object? sender, GenericEventArgs<User> e) { var user = e.Argument; if (!user.HasPermission(PermissionKind.EnableAllDevices)) { - UpdateDeviceAccess(user); + await UpdateDeviceAccess(user).ConfigureAwait(false); } } - private void UpdateDeviceAccess(User user) + private async Task UpdateDeviceAccess(User user) { - var existing = _authRepo.Get(new AuthenticationInfoQuery + var existing = (await _deviceManager.GetDevices(new DeviceQuery { UserId = user.Id - }).Items; + }).ConfigureAwait(false)).Items; - foreach (var authInfo in existing) + foreach (var device in existing) { - if (!string.IsNullOrEmpty(authInfo.DeviceId) && !_deviceManager.CanAccessDevice(user, authInfo.DeviceId)) + if (!string.IsNullOrEmpty(device.DeviceId) && !_deviceManager.CanAccessDevice(user, device.DeviceId)) { - _sessionManager.Logout(authInfo); + await _sessionManager.Logout(device).ConfigureAwait(false); } } } diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index b20acae32e..362be85316 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -11,6 +11,7 @@ using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations.Activity; using Jellyfin.Server.Implementations.Devices; using Jellyfin.Server.Implementations.Events; +using Jellyfin.Server.Implementations.Security; using Jellyfin.Server.Implementations.Users; using MediaBrowser.Controller; using MediaBrowser.Controller.BaseItemManager; @@ -94,6 +95,8 @@ namespace Jellyfin.Server ServiceCollection.AddSingleton<IWebSocketListener, ScheduledTasksWebSocketListener>(); ServiceCollection.AddSingleton<IWebSocketListener, SessionInfoWebSocketListener>(); + ServiceCollection.AddScoped<IAuthorizationContext, AuthorizationContext>(); + base.RegisterServices(); } diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs index 28612cea33..6ff4422d21 100644 --- a/MediaBrowser.Controller/Devices/IDeviceManager.cs +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Events; +using Jellyfin.Data.Queries; using MediaBrowser.Model.Devices; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; @@ -17,6 +18,13 @@ namespace MediaBrowser.Controller.Devices { event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated; + /// <summary> + /// Creates a new device. + /// </summary> + /// <param name="device">The device to create.</param> + /// <returns>A <see cref="Task{Device}"/> representing the creation of the device.</returns> + Task<Device> CreateDevice(Device device); + /// <summary> /// Saves the capabilities. /// </summary> @@ -38,6 +46,15 @@ namespace MediaBrowser.Controller.Devices /// <returns>DeviceInfo.</returns> Task<DeviceInfo> GetDevice(string id); + /// <summary> + /// Gets devices based on the provided query. + /// </summary> + /// <param name="query">The device query.</param> + /// <returns>A <see cref="Task{QueryResult}"/> representing the retrieval of the devices.</returns> + Task<QueryResult<Device>> GetDevices(DeviceQuery query); + + Task<QueryResult<DeviceInfo>> GetDeviceInfos(DeviceQuery query); + /// <summary> /// Gets the devices. /// </summary> @@ -46,6 +63,8 @@ namespace MediaBrowser.Controller.Devices /// <returns>IEnumerable<DeviceInfo>.</returns> Task<QueryResult<DeviceInfo>> GetDevicesForUser(Guid? userId, bool? supportsSync); + Task DeleteDevice(Device device); + /// <summary> /// Determines whether this instance [can access device] the specified user identifier. /// </summary> diff --git a/MediaBrowser.Controller/Net/IAuthService.cs b/MediaBrowser.Controller/Net/IAuthService.cs index d15c6d3183..a7da740e05 100644 --- a/MediaBrowser.Controller/Net/IAuthService.cs +++ b/MediaBrowser.Controller/Net/IAuthService.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net @@ -12,6 +13,6 @@ namespace MediaBrowser.Controller.Net /// </summary> /// <param name="request">The request.</param> /// <returns>Authorization information. Null if unauthenticated.</returns> - AuthorizationInfo Authenticate(HttpRequest request); + Task<AuthorizationInfo> Authenticate(HttpRequest request); } } diff --git a/MediaBrowser.Controller/Net/IAuthorizationContext.cs b/MediaBrowser.Controller/Net/IAuthorizationContext.cs index 0d310548dc..5c6ca43d1e 100644 --- a/MediaBrowser.Controller/Net/IAuthorizationContext.cs +++ b/MediaBrowser.Controller/Net/IAuthorizationContext.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net @@ -11,14 +12,14 @@ namespace MediaBrowser.Controller.Net /// Gets the authorization information. /// </summary> /// <param name="requestContext">The request context.</param> - /// <returns>AuthorizationInfo.</returns> - AuthorizationInfo GetAuthorizationInfo(HttpContext requestContext); + /// <returns>A task containing the authorization info.</returns> + Task<AuthorizationInfo> GetAuthorizationInfo(HttpContext requestContext); /// <summary> /// Gets the authorization information. /// </summary> /// <param name="requestContext">The request context.</param> - /// <returns>AuthorizationInfo.</returns> - AuthorizationInfo GetAuthorizationInfo(HttpRequest requestContext); + /// <returns>A <see cref="Task"/> containing the authorization info.</returns> + Task<AuthorizationInfo> GetAuthorizationInfo(HttpRequest requestContext); } } diff --git a/MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs b/MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs deleted file mode 100644 index 3af6a525c7..0000000000 --- a/MediaBrowser.Controller/Security/AuthenticationInfoQuery.cs +++ /dev/null @@ -1,53 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using System; - -namespace MediaBrowser.Controller.Security -{ - public class AuthenticationInfoQuery - { - /// <summary> - /// Gets or sets the device identifier. - /// </summary> - /// <value>The device identifier.</value> - public string DeviceId { get; set; } - - /// <summary> - /// Gets or sets the user identifier. - /// </summary> - /// <value>The user identifier.</value> - public Guid UserId { get; set; } - - /// <summary> - /// Gets or sets the access token. - /// </summary> - /// <value>The access token.</value> - public string AccessToken { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this instance is active. - /// </summary> - /// <value><c>null</c> if [is active] contains no value, <c>true</c> if [is active]; otherwise, <c>false</c>.</value> - public bool? IsActive { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether this instance has user. - /// </summary> - /// <value><c>null</c> if [has user] contains no value, <c>true</c> if [has user]; otherwise, <c>false</c>.</value> - public bool? HasUser { get; set; } - - /// <summary> - /// Gets or sets the start index. - /// </summary> - /// <value>The start index.</value> - public int? StartIndex { get; set; } - - /// <summary> - /// Gets or sets the limit. - /// </summary> - /// <value>The limit.</value> - public int? Limit { get; set; } - } -} diff --git a/MediaBrowser.Controller/Security/IAuthenticationRepository.cs b/MediaBrowser.Controller/Security/IAuthenticationRepository.cs deleted file mode 100644 index 9685005ba3..0000000000 --- a/MediaBrowser.Controller/Security/IAuthenticationRepository.cs +++ /dev/null @@ -1,39 +0,0 @@ -#nullable disable - -#pragma warning disable CS1591 - -using Jellyfin.Data.Entities.Security; -using MediaBrowser.Model.Querying; - -namespace MediaBrowser.Controller.Security -{ - public interface IAuthenticationRepository - { - /// <summary> - /// Creates the specified information. - /// </summary> - /// <param name="info">The information.</param> - /// <returns>Task.</returns> - void Create(AuthenticationInfo info); - - /// <summary> - /// Updates the specified information. - /// </summary> - /// <param name="info">The information.</param> - /// <returns>Task.</returns> - void Update(AuthenticationInfo info); - - /// <summary> - /// Gets the specified query. - /// </summary> - /// <param name="query">The query.</param> - /// <returns>QueryResult{AuthenticationInfo}.</returns> - QueryResult<AuthenticationInfo> Get(AuthenticationInfoQuery query); - - void Delete(AuthenticationInfo info); - - DeviceOptions GetDeviceOptions(string deviceId); - - void UpdateDeviceOptions(string deviceId, DeviceOptions options); - } -} diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 30a83d6e73..1000da2473 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -6,10 +6,12 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Events; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Security; +using MediaBrowser.Model.Devices; using MediaBrowser.Model.Session; using MediaBrowser.Model.SyncPlay; @@ -325,26 +327,23 @@ namespace MediaBrowser.Controller.Session /// <param name="remoteEndpoint">The remote endpoint.</param> /// <param name="appVersion">The application version.</param> /// <returns>Task<SessionInfo>.</returns> - Task<SessionInfo> GetSessionByAuthenticationToken(AuthenticationInfo info, string deviceId, string remoteEndpoint, string appVersion); + Task<SessionInfo> GetSessionByAuthenticationToken(Device info, string deviceId, string remoteEndpoint, string appVersion); /// <summary> /// Logouts the specified access token. /// </summary> /// <param name="accessToken">The access token.</param> - void Logout(string accessToken); + /// <returns>A <see cref="Task"/> representing the log out process.</returns> + Task Logout(string accessToken); - void Logout(AuthenticationInfo accessToken); + Task Logout(Device accessToken); /// <summary> /// Revokes the user tokens. /// </summary> - void RevokeUserTokens(Guid userId, string currentAccessToken); - - /// <summary> - /// Revokes the token. - /// </summary> - /// <param name="id">The identifier.</param> - void RevokeToken(string id); + /// <param name="userId">The user's id.</param> + /// <param name="currentAccessToken">The current access token.</param> + Task RevokeUserTokens(Guid userId, string currentAccessToken); void CloseIfNeeded(SessionInfo session); } diff --git a/MediaBrowser.Model/Devices/DeviceInfo.cs b/MediaBrowser.Model/Devices/DeviceInfo.cs index 0cccf931c4..7a1c7a7382 100644 --- a/MediaBrowser.Model/Devices/DeviceInfo.cs +++ b/MediaBrowser.Model/Devices/DeviceInfo.cs @@ -15,6 +15,11 @@ namespace MediaBrowser.Model.Devices public string Name { get; set; } + /// <summary> + /// Gets or sets the access token. + /// </summary> + public string AccessToken { get; set; } + /// <summary> /// Gets or sets the identifier. /// </summary> diff --git a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs index de03aa5f5b..cd03958b66 100644 --- a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs +++ b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs @@ -136,7 +136,7 @@ namespace Jellyfin.Api.Tests.Auth _jellyfinAuthServiceMock.Setup( a => a.Authenticate( It.IsAny<HttpRequest>())) - .Returns(authorizationInfo); + .Returns(Task.FromResult(authorizationInfo)); return authorizationInfo; } -- cgit v1.2.3 From 0bc06014427e36a770adeda66392d08147658ea8 Mon Sep 17 00:00:00 2001 From: Bond_009 <bond.009@outlook.com> Date: Fri, 28 May 2021 14:33:54 +0200 Subject: Fix some warnings --- .../AppBase/BaseConfigurationManager.cs | 10 +-- .../AppBase/ConfigurationHelper.cs | 3 +- .../EntryPoints/UdpServerEntryPoint.cs | 4 +- .../EntryPoints/UserDataChangeNotifier.cs | 10 ++- .../LiveTv/EmbyTV/RecordingHelper.cs | 2 - .../LiveTv/LiveTvConfigurationFactory.cs | 10 +-- .../LiveTv/LiveTvManager.cs | 8 +-- .../LiveTv/RefreshChannelsScheduledTask.cs | 63 ------------------ .../LiveTv/RefreshGuideScheduledTask.cs | 75 ++++++++++++++++++++++ .../LiveTv/TunerHosts/BaseTunerHost.cs | 1 + .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 2 - .../TunerHosts/HdHomerun/HdHomerunUdpStream.cs | 6 +- .../LiveTv/TunerHosts/M3UTunerHost.cs | 20 +++--- .../LiveTv/TunerHosts/M3uParser.cs | 8 +-- .../LiveTv/TunerHosts/SharedHttpStream.cs | 4 +- .../Plugins/PluginManager.cs | 3 +- .../ScheduledTasks/ScheduledTaskWorker.cs | 21 ++---- .../ScheduledTasks/Triggers/DailyTrigger.cs | 41 ++++++------ .../ScheduledTasks/Triggers/IntervalTrigger.cs | 40 ++++++------ .../ScheduledTasks/Triggers/StartupTrigger.cs | 28 ++++---- .../ScheduledTasks/Triggers/WeeklyTrigger.cs | 54 +++++++--------- .../Session/WebSocketController.cs | 1 - Emby.Server.Implementations/Udp/UdpServer.cs | 67 ++++++++++--------- .../Events/EventManager.cs | 7 +- MediaBrowser.Common/IApplicationHost.cs | 8 +-- .../Subtitles/ParserValues.cs | 9 --- MediaBrowser.Model/Tasks/ITaskTrigger.cs | 4 +- jellyfin.ruleset | 3 + 28 files changed, 247 insertions(+), 265 deletions(-) delete mode 100644 Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs create mode 100644 Emby.Server.Implementations/LiveTv/RefreshGuideScheduledTask.cs delete mode 100644 MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs index 8c919db431..4c442a4736 100644 --- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs +++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs @@ -25,6 +25,11 @@ namespace Emby.Server.Implementations.AppBase private readonly ConcurrentDictionary<string, object> _configurations = new ConcurrentDictionary<string, object>(); + /// <summary> + /// The _configuration sync lock. + /// </summary> + private readonly object _configurationSyncLock = new object(); + private ConfigurationStore[] _configurationStores = Array.Empty<ConfigurationStore>(); private IConfigurationFactory[] _configurationFactories = Array.Empty<IConfigurationFactory>(); @@ -33,11 +38,6 @@ namespace Emby.Server.Implementations.AppBase /// </summary> private bool _configurationLoaded; - /// <summary> - /// The _configuration sync lock. - /// </summary> - private readonly object _configurationSyncLock = new object(); - /// <summary> /// The _configuration. /// </summary> diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs index de770f59ec..0308a68e42 100644 --- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs +++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs @@ -33,7 +33,8 @@ namespace Emby.Server.Implementations.AppBase } catch (Exception) { - configuration = Activator.CreateInstance(type) ?? throw new ArgumentException($"Provided path ({type}) is not valid.", nameof(type)); + // Note: CreateInstance returns null for Nullable<T>, e.g. CreateInstance(typeof(int?)) returns null. + configuration = Activator.CreateInstance(type)!; } using var stream = new MemoryStream(buffer?.Length ?? 0); diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index 211941f443..2e72b18f57 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -54,8 +54,8 @@ namespace Emby.Server.Implementations.EntryPoints try { - _udpServer = new UdpServer(_logger, _appHost, _config); - _udpServer.Start(PortNumber, _cancellationTokenSource.Token); + _udpServer = new UdpServer(_logger, _appHost, _config, PortNumber); + _udpServer.Start(_cancellationTokenSource.Token); } catch (SocketException ex) { diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index 332fb33858..d3bcd5e13e 100644 --- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -1,5 +1,3 @@ -#nullable disable - #pragma warning disable CS1591 using System; @@ -28,7 +26,7 @@ namespace Emby.Server.Implementations.EntryPoints private readonly Dictionary<Guid, List<BaseItem>> _changedItems = new Dictionary<Guid, List<BaseItem>>(); private readonly object _syncLock = new object(); - private Timer _updateTimer; + private Timer? _updateTimer; public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, IUserManager userManager) { @@ -44,7 +42,7 @@ namespace Emby.Server.Implementations.EntryPoints return Task.CompletedTask; } - void OnUserDataManagerUserDataSaved(object sender, UserDataSaveEventArgs e) + private void OnUserDataManagerUserDataSaved(object? sender, UserDataSaveEventArgs e) { if (e.SaveReason == UserDataSaveReason.PlaybackProgress) { @@ -66,7 +64,7 @@ namespace Emby.Server.Implementations.EntryPoints _updateTimer.Change(UpdateDuration, Timeout.Infinite); } - if (!_changedItems.TryGetValue(e.UserId, out List<BaseItem> keys)) + if (!_changedItems.TryGetValue(e.UserId, out List<BaseItem>? keys)) { keys = new List<BaseItem>(); _changedItems[e.UserId] = keys; @@ -89,7 +87,7 @@ namespace Emby.Server.Implementations.EntryPoints } } - private void UpdateTimerCallback(object state) + private void UpdateTimerCallback(object? state) { lock (_syncLock) { diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index 1088638691..32245f899e 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - #pragma warning disable CS1591 using System; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvConfigurationFactory.cs b/Emby.Server.Implementations/LiveTv/LiveTvConfigurationFactory.cs index ba916af389..098f193fba 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvConfigurationFactory.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvConfigurationFactory.cs @@ -1,21 +1,23 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; using MediaBrowser.Common.Configuration; using MediaBrowser.Model.LiveTv; namespace Emby.Server.Implementations.LiveTv { + /// <summary> + /// <see cref="IConfigurationFactory" /> implementation for <see cref="LiveTvOptions" />. + /// </summary> public class LiveTvConfigurationFactory : IConfigurationFactory { + /// <inheritdoc /> public IEnumerable<ConfigurationStore> GetConfigurations() { return new ConfigurationStore[] { new ConfigurationStore { - ConfigurationType = typeof(LiveTvOptions), - Key = "livetv" + ConfigurationType = typeof(LiveTvOptions), + Key = "livetv" } }; } diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 1f16289002..d964769b59 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -2266,7 +2266,7 @@ namespace Emby.Server.Implementations.LiveTv if (dataSourceChanged) { - _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>(); + _taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>(); } return info; @@ -2309,7 +2309,7 @@ namespace Emby.Server.Implementations.LiveTv _config.SaveConfiguration("livetv", config); - _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>(); + _taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>(); return info; } @@ -2321,7 +2321,7 @@ namespace Emby.Server.Implementations.LiveTv config.ListingProviders = config.ListingProviders.Where(i => !string.Equals(id, i.Id, StringComparison.OrdinalIgnoreCase)).ToArray(); _config.SaveConfiguration("livetv", config); - _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>(); + _taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>(); } public async Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelId, string providerChannelId) @@ -2355,7 +2355,7 @@ namespace Emby.Server.Implementations.LiveTv var tunerChannelMappings = tunerChannels.Select(i => GetTunerChannelMapping(i, mappings, providerChannels)).ToList(); - _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>(); + _taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>(); return tunerChannelMappings.First(i => string.Equals(i.Id, tunerChannelId, StringComparison.OrdinalIgnoreCase)); } diff --git a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs deleted file mode 100644 index 582b649235..0000000000 --- a/Emby.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs +++ /dev/null @@ -1,63 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Model.LiveTv; -using MediaBrowser.Model.Tasks; - -namespace Emby.Server.Implementations.LiveTv -{ - public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask - { - private readonly ILiveTvManager _liveTvManager; - private readonly IConfigurationManager _config; - - public RefreshChannelsScheduledTask(ILiveTvManager liveTvManager, IConfigurationManager config) - { - _liveTvManager = liveTvManager; - _config = config; - } - - public string Name => "Refresh Guide"; - - public string Description => "Downloads channel information from live tv services."; - - public string Category => "Live TV"; - - public Task Execute(System.Threading.CancellationToken cancellationToken, IProgress<double> progress) - { - var manager = (LiveTvManager)_liveTvManager; - - return manager.RefreshChannels(progress, cancellationToken); - } - - /// <summary> - /// Creates the triggers that define when the task will run. - /// </summary> - /// <returns>IEnumerable{BaseTaskTrigger}.</returns> - public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() - { - return new[] - { - // Every so often - new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks } - }; - } - - private LiveTvOptions GetConfiguration() - { - return _config.GetConfiguration<LiveTvOptions>("livetv"); - } - - public bool IsHidden => _liveTvManager.Services.Count == 1 && GetConfiguration().TunerHosts.Length == 0; - - public bool IsEnabled => true; - - public bool IsLogged => true; - - public string Key => "RefreshGuide"; - } -} diff --git a/Emby.Server.Implementations/LiveTv/RefreshGuideScheduledTask.cs b/Emby.Server.Implementations/LiveTv/RefreshGuideScheduledTask.cs new file mode 100644 index 0000000000..15df0dcf13 --- /dev/null +++ b/Emby.Server.Implementations/LiveTv/RefreshGuideScheduledTask.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Model.LiveTv; +using MediaBrowser.Model.Tasks; + +namespace Emby.Server.Implementations.LiveTv +{ + /// <summary> + /// The "Refresh Guide" scheduled task. + /// </summary> + public class RefreshGuideScheduledTask : IScheduledTask, IConfigurableScheduledTask + { + private readonly ILiveTvManager _liveTvManager; + private readonly IConfigurationManager _config; + + /// <summary> + /// Initializes a new instance of the <see cref="RefreshGuideScheduledTask"/> class. + /// </summary> + /// <param name="liveTvManager">The live tv manager.</param> + /// <param name="config">The configuration manager.</param> + public RefreshGuideScheduledTask(ILiveTvManager liveTvManager, IConfigurationManager config) + { + _liveTvManager = liveTvManager; + _config = config; + } + + /// <inheritdoc /> + public string Name => "Refresh Guide"; + + /// <inheritdoc /> + public string Description => "Downloads channel information from live tv services."; + + /// <inheritdoc /> + public string Category => "Live TV"; + + /// <inheritdoc /> + public bool IsHidden => _liveTvManager.Services.Count == 1 && GetConfiguration().TunerHosts.Length == 0; + + /// <inheritdoc /> + public bool IsEnabled => true; + + /// <inheritdoc /> + public bool IsLogged => true; + + /// <inheritdoc /> + public string Key => "RefreshGuide"; + + /// <inheritdoc /> + public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + { + var manager = (LiveTvManager)_liveTvManager; + + return manager.RefreshChannels(progress, cancellationToken); + } + + /// <inheritdoc /> + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() + { + return new[] + { + // Every so often + new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks } + }; + } + + private LiveTvOptions GetConfiguration() + { + return _config.GetConfiguration<LiveTvOptions>("livetv"); + } + } +} diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index 00a37bb022..5941613cf9 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -40,6 +40,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public virtual bool IsSupported => true; protected abstract Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken); + public abstract string Type { get; } public async Task<List<ChannelInfo>> GetChannels(TunerHostInfo tuner, bool enableCache, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index c5700db715..54de841fef 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -583,7 +583,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun Logger, Config, _appHost, - _networkManager, _streamHelper); } @@ -624,7 +623,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun Logger, Config, _appHost, - _networkManager, _streamHelper); } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 50a2d9abb3..58e0c7448a 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -12,7 +12,6 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; @@ -30,7 +29,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun private readonly IServerApplicationHost _appHost; private readonly IHdHomerunChannelCommands _channelCommands; private readonly int _numTuners; - private readonly INetworkManager _networkManager; public HdHomerunUdpStream( MediaSourceInfo mediaSource, @@ -42,12 +40,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun ILogger logger, IConfigurationManager configurationManager, IServerApplicationHost appHost, - INetworkManager networkManager, IStreamHelper streamHelper) : base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper) { _appHost = appHost; - _networkManager = networkManager; OriginalStreamId = originalStreamId; _channelCommands = channelCommands; _numTuners = numTuners; @@ -128,7 +124,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun using (udpClient) using (hdHomerunManager) { - if (!(ex is OperationCanceledException)) + if (ex is not OperationCanceledException) { Logger.LogError(ex, "Error opening live stream:"); } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 69035dac96..8fa6f5ad68 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -29,6 +29,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost { + private static readonly string[] _disallowedSharedStreamExtensions = + { + ".mkv", + ".mp4", + ".m3u8", + ".mpd" + }; + private readonly IHttpClientFactory _httpClientFactory; private readonly IServerApplicationHost _appHost; private readonly INetworkManager _networkManager; @@ -67,7 +75,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { var channelIdPrefix = GetFullChannelIdPrefix(info); - return await new M3uParser(Logger, _httpClientFactory, _appHost) + return await new M3uParser(Logger, _httpClientFactory) .Parse(info, channelIdPrefix, cancellationToken) .ConfigureAwait(false); } @@ -88,14 +96,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return Task.FromResult(list); } - private static readonly string[] _disallowedSharedStreamExtensions = - { - ".mkv", - ".mp4", - ".m3u8", - ".mpd" - }; - protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo info, ChannelInfo channelInfo, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken) { var tunerCount = info.TunerCount; @@ -130,7 +130,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public async Task Validate(TunerHostInfo info) { - using (var stream = await new M3uParser(Logger, _httpClientFactory, _appHost).GetListingsStream(info, CancellationToken.None).ConfigureAwait(false)) + using (var stream = await new M3uParser(Logger, _httpClientFactory).GetListingsStream(info, CancellationToken.None).ConfigureAwait(false)) { } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 48a0c3cd37..40a1628902 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -21,15 +21,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { public class M3uParser { + private const string ExtInfPrefix = "#EXTINF:"; + private readonly ILogger _logger; private readonly IHttpClientFactory _httpClientFactory; - private readonly IServerApplicationHost _appHost; - public M3uParser(ILogger logger, IHttpClientFactory httpClientFactory, IServerApplicationHost appHost) + public M3uParser(ILogger logger, IHttpClientFactory httpClientFactory) { _logger = logger; _httpClientFactory = httpClientFactory; - _appHost = appHost; } public async Task<List<ChannelInfo>> Parse(TunerHostInfo info, string channelIdPrefix, CancellationToken cancellationToken) @@ -61,8 +61,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return File.OpenRead(info.Url); } - private const string ExtInfPrefix = "#EXTINF:"; - private async Task<List<ChannelInfo>> GetChannelsAsync(TextReader reader, string channelIdPrefix, string tunerHostId) { var channels = new List<ChannelInfo>(); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index 137ed27e20..f572151b8a 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -91,8 +91,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts var taskCompletionSource = new TaskCompletionSource<bool>(); - var now = DateTime.UtcNow; - _ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token); // OpenedMediaSource.Protocol = MediaProtocol.File; @@ -120,7 +118,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts if (!taskCompletionSource.Task.Result) { Logger.LogWarning("Zero bytes copied from stream {0} to {1} but no exception raised", GetType().Name, TempFilePath); - throw new EndOfStreamException(String.Format(CultureInfo.InvariantCulture, "Zero bytes copied from stream {0}", GetType().Name)); + throw new EndOfStreamException(string.Format(CultureInfo.InvariantCulture, "Zero bytes copied from stream {0}", GetType().Name)); } } diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 48281b75f6..16a2bd615b 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -455,7 +455,8 @@ namespace Emby.Server.Implementations.Plugins try { _logger.LogDebug("Creating instance of {Type}", type); - var instance = (IPlugin)ActivatorUtilities.CreateInstance(_appHost.ServiceProvider, type); + // _appHost.ServiceProvider is already assigned when we create the plugins + var instance = (IPlugin)ActivatorUtilities.CreateInstance(_appHost.ServiceProvider!, type); if (plugin == null) { // Create a dummy record for the providers. diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index ccbd4289ea..d7e3207549 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -711,11 +711,7 @@ namespace Emby.Server.Implementations.ScheduledTasks throw new ArgumentException("Info did not contain a TimeOfDayTicks.", nameof(info)); } - return new DailyTrigger - { - TimeOfDay = TimeSpan.FromTicks(info.TimeOfDayTicks.Value), - TaskOptions = options - }; + return new DailyTrigger(TimeSpan.FromTicks(info.TimeOfDayTicks.Value), options); } if (info.Type.Equals(nameof(WeeklyTrigger), StringComparison.OrdinalIgnoreCase)) @@ -730,12 +726,7 @@ namespace Emby.Server.Implementations.ScheduledTasks throw new ArgumentException("Info did not contain a DayOfWeek.", nameof(info)); } - return new WeeklyTrigger - { - TimeOfDay = TimeSpan.FromTicks(info.TimeOfDayTicks.Value), - DayOfWeek = info.DayOfWeek.Value, - TaskOptions = options - }; + return new WeeklyTrigger(TimeSpan.FromTicks(info.TimeOfDayTicks.Value), info.DayOfWeek.Value, options); } if (info.Type.Equals(nameof(IntervalTrigger), StringComparison.OrdinalIgnoreCase)) @@ -745,16 +736,12 @@ namespace Emby.Server.Implementations.ScheduledTasks throw new ArgumentException("Info did not contain a IntervalTicks.", nameof(info)); } - return new IntervalTrigger - { - Interval = TimeSpan.FromTicks(info.IntervalTicks.Value), - TaskOptions = options - }; + return new IntervalTrigger(TimeSpan.FromTicks(info.IntervalTicks.Value), options); } if (info.Type.Equals(nameof(StartupTrigger), StringComparison.OrdinalIgnoreCase)) { - return new StartupTrigger(); + return new StartupTrigger(options); } throw new ArgumentException("Unrecognized trigger type: " + info.Type); diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs index 3b63536a42..29ab6a73dc 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Threading; using MediaBrowser.Model.Tasks; @@ -10,29 +8,31 @@ namespace Emby.Server.Implementations.ScheduledTasks /// <summary> /// Represents a task trigger that fires everyday. /// </summary> - public class DailyTrigger : ITaskTrigger + public sealed class DailyTrigger : ITaskTrigger { - /// <summary> - /// Occurs when [triggered]. - /// </summary> - public event EventHandler<EventArgs> Triggered; + private readonly TimeSpan _timeOfDay; + private Timer? _timer; /// <summary> - /// Gets or sets the time of day to trigger the task to run. + /// Initializes a new instance of the <see cref="DailyTrigger"/> class. /// </summary> - /// <value>The time of day.</value> - public TimeSpan TimeOfDay { get; set; } + /// <param name="timeofDay">The time of day to trigger the task to run.</param> + /// <param name="taskOptions">The options of this task.</param> + public DailyTrigger(TimeSpan timeofDay, TaskOptions taskOptions) + { + _timeOfDay = timeofDay; + TaskOptions = taskOptions; + } /// <summary> - /// Gets or sets the options of this task. + /// Occurs when [triggered]. /// </summary> - public TaskOptions TaskOptions { get; set; } + public event EventHandler<EventArgs>? Triggered; /// <summary> - /// Gets or sets the timer. + /// Gets the options of this task. /// </summary> - /// <value>The timer.</value> - private Timer Timer { get; set; } + public TaskOptions TaskOptions { get; } /// <summary> /// Stars waiting for the trigger action. @@ -47,14 +47,14 @@ namespace Emby.Server.Implementations.ScheduledTasks var now = DateTime.Now; - var triggerDate = now.TimeOfDay > TimeOfDay ? now.Date.AddDays(1) : now.Date; - triggerDate = triggerDate.Add(TimeOfDay); + var triggerDate = now.TimeOfDay > _timeOfDay ? now.Date.AddDays(1) : now.Date; + triggerDate = triggerDate.Add(_timeOfDay); var dueTime = triggerDate - now; logger.LogInformation("Daily trigger for {Task} set to fire at {TriggerDate:yyyy-MM-dd HH:mm:ss.fff zzz}, which is {DueTime:c} from now.", taskName, triggerDate, dueTime); - Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1)); + _timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1)); } /// <summary> @@ -70,10 +70,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// </summary> private void DisposeTimer() { - if (Timer != null) - { - Timer.Dispose(); - } + _timer?.Dispose(); } /// <summary> diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs index e13782fe02..30568e8092 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Linq; using System.Threading; @@ -11,31 +9,32 @@ namespace Emby.Server.Implementations.ScheduledTasks /// <summary> /// Represents a task trigger that runs repeatedly on an interval. /// </summary> - public class IntervalTrigger : ITaskTrigger + public sealed class IntervalTrigger : ITaskTrigger { + private readonly TimeSpan _interval; private DateTime _lastStartDate; + private Timer? _timer; /// <summary> - /// Occurs when [triggered]. - /// </summary> - public event EventHandler<EventArgs> Triggered; - - /// <summary> - /// Gets or sets the interval. + /// Initializes a new instance of the <see cref="IntervalTrigger"/> class. /// </summary> - /// <value>The interval.</value> - public TimeSpan Interval { get; set; } + /// <param name="interval">The interval.</param> + /// <param name="taskOptions">The options of this task.</param> + public IntervalTrigger(TimeSpan interval, TaskOptions taskOptions) + { + _interval = interval; + TaskOptions = taskOptions; + } /// <summary> - /// Gets or sets the options of this task. + /// Occurs when [triggered]. /// </summary> - public TaskOptions TaskOptions { get; set; } + public event EventHandler<EventArgs>? Triggered; /// <summary> - /// Gets or sets the timer. + /// Gets the options of this task. /// </summary> - /// <value>The timer.</value> - private Timer Timer { get; set; } + public TaskOptions TaskOptions { get; } /// <summary> /// Stars waiting for the trigger action. @@ -57,7 +56,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } else { - triggerDate = new[] { lastResult.EndTimeUtc, _lastStartDate }.Max().Add(Interval); + triggerDate = new[] { lastResult.EndTimeUtc, _lastStartDate }.Max().Add(_interval); } if (DateTime.UtcNow > triggerDate) @@ -73,7 +72,7 @@ namespace Emby.Server.Implementations.ScheduledTasks dueTime = maxDueTime; } - Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1)); + _timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1)); } /// <summary> @@ -89,10 +88,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// </summary> private void DisposeTimer() { - if (Timer != null) - { - Timer.Dispose(); - } + _timer?.Dispose(); } /// <summary> diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs index ced14195bb..18b9a8b75a 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs @@ -1,5 +1,3 @@ -#nullable disable - #pragma warning disable CS1591 using System; @@ -12,24 +10,28 @@ namespace Emby.Server.Implementations.ScheduledTasks /// <summary> /// Class StartupTaskTrigger. /// </summary> - public class StartupTrigger : ITaskTrigger + public sealed class StartupTrigger : ITaskTrigger { + public const int DelayMs = 3000; + /// <summary> - /// Occurs when [triggered]. + /// Initializes a new instance of the <see cref="StartupTrigger"/> class. /// </summary> - public event EventHandler<EventArgs> Triggered; - - public int DelayMs { get; set; } + /// <param name="taskOptions">The options of this task.</param> + public StartupTrigger(TaskOptions taskOptions) + { + TaskOptions = taskOptions; + } /// <summary> - /// Gets or sets the options of this task. + /// Occurs when [triggered]. /// </summary> - public TaskOptions TaskOptions { get; set; } + public event EventHandler<EventArgs>? Triggered; - public StartupTrigger() - { - DelayMs = 3000; - } + /// <summary> + /// Gets the options of this task. + /// </summary> + public TaskOptions TaskOptions { get; } /// <summary> /// Stars waiting for the trigger action. diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs index a67f940b75..36ae190b05 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Threading; using MediaBrowser.Model.Tasks; @@ -10,35 +8,34 @@ namespace Emby.Server.Implementations.ScheduledTasks /// <summary> /// Represents a task trigger that fires on a weekly basis. /// </summary> - public class WeeklyTrigger : ITaskTrigger + public sealed class WeeklyTrigger : ITaskTrigger { - /// <summary> - /// Occurs when [triggered]. - /// </summary> - public event EventHandler<EventArgs> Triggered; - - /// <summary> - /// Gets or sets the time of day to trigger the task to run. - /// </summary> - /// <value>The time of day.</value> - public TimeSpan TimeOfDay { get; set; } + private readonly TimeSpan _timeOfDay; + private readonly DayOfWeek _dayOfWeek; + private Timer? _timer; /// <summary> - /// Gets or sets the day of week. + /// Initializes a new instance of the <see cref="WeeklyTrigger"/> class. /// </summary> - /// <value>The day of week.</value> - public DayOfWeek DayOfWeek { get; set; } + /// <param name="timeofDay">The time of day to trigger the task to run.</param> + /// <param name="dayOfWeek">The day of week.</param> + /// <param name="taskOptions">The options of this task.</param> + public WeeklyTrigger(TimeSpan timeofDay, DayOfWeek dayOfWeek, TaskOptions taskOptions) + { + _timeOfDay = timeofDay; + _dayOfWeek = dayOfWeek; + TaskOptions = taskOptions; + } /// <summary> - /// Gets or sets the options of this task. + /// Occurs when [triggered]. /// </summary> - public TaskOptions TaskOptions { get; set; } + public event EventHandler<EventArgs>? Triggered; /// <summary> - /// Gets or sets the timer. + /// Gets the options of this task. /// </summary> - /// <value>The timer.</value> - private Timer Timer { get; set; } + public TaskOptions TaskOptions { get; } /// <summary> /// Stars waiting for the trigger action. @@ -53,7 +50,7 @@ namespace Emby.Server.Implementations.ScheduledTasks var triggerDate = GetNextTriggerDateTime(); - Timer = new Timer(state => OnTriggered(), null, triggerDate - DateTime.Now, TimeSpan.FromMilliseconds(-1)); + _timer = new Timer(state => OnTriggered(), null, triggerDate - DateTime.Now, TimeSpan.FromMilliseconds(-1)); } /// <summary> @@ -65,22 +62,22 @@ namespace Emby.Server.Implementations.ScheduledTasks var now = DateTime.Now; // If it's on the same day - if (now.DayOfWeek == DayOfWeek) + if (now.DayOfWeek == _dayOfWeek) { // It's either later today, or a week from now - return now.TimeOfDay < TimeOfDay ? now.Date.Add(TimeOfDay) : now.Date.AddDays(7).Add(TimeOfDay); + return now.TimeOfDay < _timeOfDay ? now.Date.Add(_timeOfDay) : now.Date.AddDays(7).Add(_timeOfDay); } var triggerDate = now.Date; // Walk the date forward until we get to the trigger day - while (triggerDate.DayOfWeek != DayOfWeek) + while (triggerDate.DayOfWeek != _dayOfWeek) { triggerDate = triggerDate.AddDays(1); } // Return the trigger date plus the time offset - return triggerDate.Add(TimeOfDay); + return triggerDate.Add(_timeOfDay); } /// <summary> @@ -96,10 +93,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// </summary> private void DisposeTimer() { - if (Timer != null) - { - Timer.Dispose(); - } + _timer?.Dispose(); } /// <summary> diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index ed1dfca592..9fa92a53a1 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -1,5 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1600 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index 750f001680..8179e26c5e 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Net; using System.Net.Sockets; @@ -19,6 +17,11 @@ namespace Emby.Server.Implementations.Udp /// </summary> public sealed class UdpServer : IDisposable { + /// <summary> + /// Address Override Configuration Key. + /// </summary> + public const string AddressOverrideConfigKey = "PublishedServerUrl"; + /// <summary> /// The _logger. /// </summary> @@ -26,11 +29,6 @@ namespace Emby.Server.Implementations.Udp private readonly IServerApplicationHost _appHost; private readonly IConfiguration _config; - /// <summary> - /// Address Override Configuration Key. - /// </summary> - public const string AddressOverrideConfigKey = "PublishedServerUrl"; - private Socket _udpSocket; private IPEndPoint _endpoint; private readonly byte[] _receiveBuffer = new byte[8192]; @@ -40,49 +38,58 @@ namespace Emby.Server.Implementations.Udp /// <summary> /// Initializes a new instance of the <see cref="UdpServer" /> class. /// </summary> - public UdpServer(ILogger logger, IServerApplicationHost appHost, IConfiguration configuration) + /// <param name="logger">The logger.</param> + /// <param name="appHost">The application host.</param> + /// <param name="configuration">The configuration manager.</param> + /// <param name="port">The port.</param> + public UdpServer( + ILogger logger, + IServerApplicationHost appHost, + IConfiguration configuration, + int port) { _logger = logger; _appHost = appHost; _config = configuration; + + _endpoint = new IPEndPoint(IPAddress.Any, port); + + _udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + _udpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); } private async Task RespondToV2Message(string messageText, EndPoint endpoint, CancellationToken cancellationToken) { - string localUrl = !string.IsNullOrEmpty(_config[AddressOverrideConfigKey]) - ? _config[AddressOverrideConfigKey] - : _appHost.GetSmartApiUrl(((IPEndPoint)endpoint).Address); + string? localUrl = _config[AddressOverrideConfigKey]; + if (string.IsNullOrEmpty(localUrl)) + { + localUrl = _appHost.GetSmartApiUrl(((IPEndPoint)endpoint).Address); + } - if (!string.IsNullOrEmpty(localUrl)) + if (string.IsNullOrEmpty(localUrl)) { - var response = new ServerDiscoveryInfo(localUrl, _appHost.SystemId, _appHost.FriendlyName); + _logger.LogWarning("Unable to respond to udp request because the local ip address could not be determined."); + return; + } - try - { - await _udpSocket.SendToAsync(JsonSerializer.SerializeToUtf8Bytes(response), SocketFlags.None, endpoint).ConfigureAwait(false); - } - catch (SocketException ex) - { - _logger.LogError(ex, "Error sending response message"); - } + var response = new ServerDiscoveryInfo(localUrl, _appHost.SystemId, _appHost.FriendlyName); + + try + { + await _udpSocket.SendToAsync(JsonSerializer.SerializeToUtf8Bytes(response), SocketFlags.None, endpoint).ConfigureAwait(false); } - else + catch (SocketException ex) { - _logger.LogWarning("Unable to respond to udp request because the local ip address could not be determined."); + _logger.LogError(ex, "Error sending response message"); } } /// <summary> /// Starts the specified port. /// </summary> - /// <param name="port">The port.</param> /// <param name="cancellationToken">The cancellation token to cancel operation.</param> - public void Start(int port, CancellationToken cancellationToken) + public void Start(CancellationToken cancellationToken) { - _endpoint = new IPEndPoint(IPAddress.Any, port); - - _udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - _udpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); _udpSocket.Bind(_endpoint); _ = Task.Run(async () => await BeginReceiveAsync(cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); @@ -90,9 +97,9 @@ namespace Emby.Server.Implementations.Udp private async Task BeginReceiveAsync(CancellationToken cancellationToken) { + var infiniteTask = Task.Delay(-1, cancellationToken); while (!cancellationToken.IsCancellationRequested) { - var infiniteTask = Task.Delay(-1, cancellationToken); try { var task = _udpSocket.ReceiveFromAsync(_receiveBuffer, SocketFlags.None, _endpoint); diff --git a/Jellyfin.Server.Implementations/Events/EventManager.cs b/Jellyfin.Server.Implementations/Events/EventManager.cs index 7070024422..c5e66112d1 100644 --- a/Jellyfin.Server.Implementations/Events/EventManager.cs +++ b/Jellyfin.Server.Implementations/Events/EventManager.cs @@ -43,7 +43,12 @@ namespace Jellyfin.Server.Implementations.Events private async Task PublishInternal<T>(T eventArgs) where T : EventArgs { - using var scope = _appHost.ServiceProvider.CreateScope(); + using var scope = _appHost.ServiceProvider?.CreateScope(); + if (scope == null) + { + return; + } + foreach (var service in scope.ServiceProvider.GetServices<IEventConsumer<T>>()) { try diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index 46d93e4940..192a776115 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Reflection; @@ -12,7 +10,7 @@ namespace MediaBrowser.Common /// </summary> /// <param name="type">Type to create.</param> /// <returns>New instance of type <param>type</param>.</returns> - public delegate object CreationDelegateFactory(Type type); + public delegate object? CreationDelegateFactory(Type type); /// <summary> /// An interface to be implemented by the applications hosting a kernel. @@ -22,7 +20,7 @@ namespace MediaBrowser.Common /// <summary> /// Occurs when [has pending restart changed]. /// </summary> - event EventHandler HasPendingRestartChanged; + event EventHandler? HasPendingRestartChanged; /// <summary> /// Gets the name. @@ -63,7 +61,7 @@ namespace MediaBrowser.Common /// <summary> /// Gets or sets the service provider. /// </summary> - IServiceProvider ServiceProvider { get; set; } + IServiceProvider? ServiceProvider { get; set; } /// <summary> /// Gets the application version. diff --git a/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs b/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs deleted file mode 100644 index cec1aaf080..0000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/ParserValues.cs +++ /dev/null @@ -1,9 +0,0 @@ -#pragma warning disable CS1591 - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public static class ParserValues - { - public const string NewLine = "\r\n"; - } -} diff --git a/MediaBrowser.Model/Tasks/ITaskTrigger.cs b/MediaBrowser.Model/Tasks/ITaskTrigger.cs index db9fba6964..999db9605d 100644 --- a/MediaBrowser.Model/Tasks/ITaskTrigger.cs +++ b/MediaBrowser.Model/Tasks/ITaskTrigger.cs @@ -14,9 +14,9 @@ namespace MediaBrowser.Model.Tasks event EventHandler<EventArgs>? Triggered; /// <summary> - /// Gets or sets the options of this task. + /// Gets the options of this task. /// </summary> - TaskOptions TaskOptions { get; set; } + TaskOptions TaskOptions { get; } /// <summary> /// Stars waiting for the trigger action. diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 1a9f2bf96c..44bc343690 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -1,6 +1,9 @@ <?xml version="1.0" encoding="utf-8"?> <RuleSet Name="Rules for Jellyfin.Server" Description="Code analysis rules for Jellyfin.Server.csproj" ToolsVersion="14.0"> <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers"> + <!-- disable warning CA1040: Avoid empty interfaces --> + <Rule Id="CA1040" Action="Info" /> + <!-- disable warning SA1009: Closing parenthesis should be followed by a space. --> <Rule Id="SA1009" Action="None" /> <!-- disable warning SA1011: Closing square bracket should be followed by a space. --> -- cgit v1.2.3 From 373155a06324140fe8b0ff95d4f58c7a498db2e2 Mon Sep 17 00:00:00 2001 From: Patrick Barron <barronpm@gmail.com> Date: Fri, 18 Jun 2021 16:57:46 -0400 Subject: Correctly handle devices without custom names --- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 92ef65bf11..b8c778690b 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -532,7 +532,7 @@ namespace Emby.Server.Implementations.Session } var deviceOptions = await _deviceManager.GetDeviceOptions(deviceId).ConfigureAwait(false); - if (string.IsNullOrEmpty(deviceOptions.CustomName)) + if (string.IsNullOrEmpty(deviceOptions?.CustomName)) { sessionInfo.DeviceName = deviceName; } -- cgit v1.2.3 From 6f8ccab788e85e025eaa44b67a1487bf419afb53 Mon Sep 17 00:00:00 2001 From: Bond_009 <bond.009@outlook.com> Date: Sat, 19 Jun 2021 18:02:33 +0200 Subject: Move non-jellyfin extensions to separate project --- Emby.Dlna/DlnaManager.cs | 2 +- Emby.Naming/Audio/AudioFileParser.cs | 2 +- Emby.Naming/Video/VideoResolver.cs | 2 +- .../Channels/ChannelManager.cs | 2 +- .../Data/SqliteItemRepository.cs | 3 +- .../HttpServer/Security/AuthorizationContext.cs | 2 +- .../HttpServer/WebSocketConnection.cs | 2 +- .../IO/ManagedFileSystem.cs | 2 +- .../Library/LibraryManager.cs | 1 + .../Library/LiveStreamHelper.cs | 2 +- .../Library/MediaSourceManager.cs | 2 +- .../Library/Resolvers/Movies/MovieResolver.cs | 2 +- .../LiveTv/EmbyTV/EncodedRecorder.cs | 4 +- .../LiveTv/EmbyTV/ItemDataProvider.cs | 2 +- .../LiveTv/Listings/SchedulesDirect.cs | 4 +- .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 3 +- .../LiveTv/TunerHosts/M3uParser.cs | 1 + .../Localization/LocalizationManager.cs | 4 +- .../Plugins/PluginManager.cs | 4 +- .../ScheduledTasks/ScheduledTaskWorker.cs | 2 +- .../Session/SessionManager.cs | 1 + .../Sorting/StudioComparer.cs | 3 +- .../Updates/InstallationManager.cs | 2 +- Jellyfin.Api/BaseJellyfinApiController.cs | 2 +- .../Controllers/ConfigurationController.cs | 2 +- Jellyfin.Api/Controllers/PluginsController.cs | 2 +- Jellyfin.Api/Controllers/TvShowsController.cs | 2 +- Jellyfin.Api/Controllers/UserLibraryController.cs | 2 +- Jellyfin.Api/Extensions/DtoExtensions.cs | 2 +- Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs | 2 +- .../Models/PlaylistDtos/CreatePlaylistDto.cs | 2 +- .../Models/SessionDtos/ClientCapabilitiesDto.cs | 4 +- .../Extensions/ApiServiceCollectionExtensions.cs | 2 +- .../Formatters/CamelCaseJsonProfileFormatter.cs | 2 +- .../Formatters/PascalCaseJsonProfileFormatter.cs | 2 +- .../Middleware/UrlDecodeQueryFeature.cs | 2 +- .../Migrations/Routines/MigrateUserDb.cs | 2 +- Jellyfin.sln | 16 +++ MediaBrowser.Common/Extensions/CopyToExtensions.cs | 26 ---- .../Extensions/EnumerableExtensions.cs | 51 -------- .../Extensions/ShuffleExtensions.cs | 41 ------ .../Extensions/SplitStringExtensions.cs | 95 -------------- MediaBrowser.Common/Extensions/StreamExtensions.cs | 63 --------- .../Extensions/StringBuilderExtensions.cs | 35 ----- .../Json/Converters/JsonBoolNumberConverter.cs | 30 ----- .../Converters/JsonCommaDelimitedArrayConverter.cs | 24 ---- .../JsonCommaDelimitedArrayConverterFactory.cs | 28 ---- .../Json/Converters/JsonDateTimeConverter.cs | 34 ----- .../Json/Converters/JsonDelimitedArrayConverter.cs | 81 ------------ .../Json/Converters/JsonGuidConverter.cs | 26 ---- .../Json/Converters/JsonNullableGuidConverter.cs | 33 ----- .../Json/Converters/JsonNullableStructConverter.cs | 45 ------- .../JsonNullableStructConverterFactory.cs | 27 ---- .../JsonOmdbNotAvailableInt32Converter.cs | 44 ------- .../JsonOmdbNotAvailableStringConverter.cs | 41 ------ .../Converters/JsonPipeDelimitedArrayConverter.cs | 24 ---- .../JsonPipeDelimitedArrayConverterFactory.cs | 28 ---- .../Json/Converters/JsonStringConverter.cs | 39 ------ .../Json/Converters/JsonVersionConverter.cs | 23 ---- MediaBrowser.Common/Json/JsonDefaults.cs | 90 ------------- .../BaseItemManager/BaseItemManager.cs | 2 +- MediaBrowser.Controller/Entities/BaseItem.cs | 1 + .../Entities/CollectionFolder.cs | 2 +- MediaBrowser.Controller/Entities/Extensions.cs | 2 +- .../Extensions/StringExtensions.cs | 21 --- .../Sorting/AlphanumComparator.cs | 137 -------------------- MediaBrowser.Controller/Sorting/SortExtensions.cs | 5 +- .../Images/EpisodeLocalImageProvider.cs | 2 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 2 +- .../Subtitles/SubtitleEditParser.cs | 2 +- .../Entities/JsonLowerCaseConverter.cs | 29 ----- MediaBrowser.Model/Entities/VirtualFolderInfo.cs | 1 + MediaBrowser.Model/MediaBrowser.Model.csproj | 3 +- .../Plugins/AudioDb/AlbumImageProvider.cs | 2 +- .../Plugins/AudioDb/AlbumProvider.cs | 2 +- .../Plugins/AudioDb/ArtistImageProvider.cs | 2 +- .../Plugins/AudioDb/ArtistProvider.cs | 2 +- .../Omdb/JsonOmdbNotAvailableInt32Converter.cs | 46 +++++++ .../Omdb/JsonOmdbNotAvailableStringConverter.cs | 43 ++++++ .../Plugins/Omdb/OmdbItemProvider.cs | 4 +- .../Plugins/Omdb/OmdbProvider.cs | 4 +- MediaBrowser.Providers/Properties/AssemblyInfo.cs | 2 +- .../Studios/StudiosImageProvider.cs | 2 +- src/Jellyfin.Extensions/AlphanumericComparator.cs | 144 +++++++++++++++++++++ src/Jellyfin.Extensions/CopyToExtensions.cs | 26 ++++ src/Jellyfin.Extensions/EnumerableExtensions.cs | 51 ++++++++ src/Jellyfin.Extensions/Jellyfin.Extensions.csproj | 30 +++++ .../Json/Converters/JsonBoolNumberConverter.cs | 30 +++++ .../Converters/JsonCommaDelimitedArrayConverter.cs | 24 ++++ .../JsonCommaDelimitedArrayConverterFactory.cs | 28 ++++ .../Json/Converters/JsonDateTimeConverter.cs | 34 +++++ .../Json/Converters/JsonDelimitedArrayConverter.cs | 81 ++++++++++++ .../Json/Converters/JsonGuidConverter.cs | 26 ++++ .../Json/Converters/JsonLowerCaseConverter.cs | 25 ++++ .../Json/Converters/JsonNullableGuidConverter.cs | 33 +++++ .../Json/Converters/JsonNullableStructConverter.cs | 45 +++++++ .../JsonNullableStructConverterFactory.cs | 27 ++++ .../Converters/JsonPipeDelimitedArrayConverter.cs | 24 ++++ .../JsonPipeDelimitedArrayConverterFactory.cs | 28 ++++ .../Json/Converters/JsonStringConverter.cs | 39 ++++++ .../Json/Converters/JsonVersionConverter.cs | 23 ++++ src/Jellyfin.Extensions/Json/JsonDefaults.cs | 90 +++++++++++++ src/Jellyfin.Extensions/ShuffleExtensions.cs | 41 ++++++ src/Jellyfin.Extensions/SplitStringExtensions.cs | 115 ++++++++++++++++ src/Jellyfin.Extensions/StreamExtensions.cs | 63 +++++++++ src/Jellyfin.Extensions/StringBuilderExtensions.cs | 35 +++++ src/Jellyfin.Extensions/StringExtensions.cs | 31 +++++ .../Extensions/CopyToExtensionsTests.cs | 40 ------ .../Extensions/ShuffleExtensionsTests.cs | 22 ---- .../Json/JsonBoolNumberTests.cs | 45 ------- .../Json/JsonCommaDelimitedArrayTests.cs | 142 -------------------- .../Json/JsonCommaDelimitedIReadOnlyListTests.cs | 92 ------------- .../Json/JsonGuidConverterTests.cs | 68 ---------- .../Json/JsonNullableGuidConverterTests.cs | 80 ------------ .../Json/JsonOmdbConverterTests.cs | 87 ------------- .../Json/JsonStringConverterTests.cs | 38 ------ .../Json/JsonVersionConverterTests.cs | 36 ------ .../Models/GenericBodyArrayModel.cs | 20 --- .../Models/GenericBodyIReadOnlyListModel.cs | 19 --- .../AlphanumComparatorTests.cs | 30 ----- .../Extensions/StringExtensionsTests.cs | 19 --- .../AlphanumericComparatorTests.cs | 29 +++++ .../CopyToExtensionsTests.cs | 39 ++++++ .../Jellyfin.Extensions.Tests.csproj | 38 ++++++ .../Json/Converters/JsonBoolNumberTests.cs | 45 +++++++ .../Converters/JsonCommaDelimitedArrayTests.cs | 142 ++++++++++++++++++++ .../JsonCommaDelimitedIReadOnlyListTests.cs | 92 +++++++++++++ .../Json/Converters/JsonGuidConverterTests.cs | 68 ++++++++++ .../Json/Converters/JsonLowerCaseConverterTests.cs | 71 ++++++++++ .../Converters/JsonNullableGuidConverterTests.cs | 80 ++++++++++++ .../Json/Converters/JsonStringConverterTests.cs | 38 ++++++ .../Json/Converters/JsonVersionConverterTests.cs | 36 ++++++ .../Json/Models/GenericBodyArrayModel.cs | 20 +++ .../Json/Models/GenericBodyIReadOnlyListModel.cs | 19 +++ .../ShuffleExtensionsTests.cs | 21 +++ .../StringExtensionsTests.cs | 18 +++ .../FFprobeParserTests.cs | 2 +- .../Probing/ProbeResultNormalizerTests.cs | 2 +- .../Entities/JsonLowerCaseConverterTests.cs | 70 ---------- .../Omdb/JsonOmdbConverterTests.cs | 86 ++++++++++++ .../AuthHelper.cs | 2 +- .../Controllers/DashboardControllerTests.cs | 2 +- .../Controllers/StartupControllerTests.cs | 2 +- .../Controllers/UserControllerTests.cs | 2 +- 144 files changed, 2113 insertions(+), 1988 deletions(-) delete mode 100644 MediaBrowser.Common/Extensions/CopyToExtensions.cs delete mode 100644 MediaBrowser.Common/Extensions/EnumerableExtensions.cs delete mode 100644 MediaBrowser.Common/Extensions/ShuffleExtensions.cs delete mode 100644 MediaBrowser.Common/Extensions/SplitStringExtensions.cs delete mode 100644 MediaBrowser.Common/Extensions/StreamExtensions.cs delete mode 100644 MediaBrowser.Common/Extensions/StringBuilderExtensions.cs delete mode 100644 MediaBrowser.Common/Json/Converters/JsonBoolNumberConverter.cs delete mode 100644 MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs delete mode 100644 MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs delete mode 100644 MediaBrowser.Common/Json/Converters/JsonDateTimeConverter.cs delete mode 100644 MediaBrowser.Common/Json/Converters/JsonDelimitedArrayConverter.cs delete mode 100644 MediaBrowser.Common/Json/Converters/JsonGuidConverter.cs delete mode 100644 MediaBrowser.Common/Json/Converters/JsonNullableGuidConverter.cs delete mode 100644 MediaBrowser.Common/Json/Converters/JsonNullableStructConverter.cs delete mode 100644 MediaBrowser.Common/Json/Converters/JsonNullableStructConverterFactory.cs delete mode 100644 MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableInt32Converter.cs delete mode 100644 MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStringConverter.cs delete mode 100644 MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs delete mode 100644 MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverterFactory.cs delete mode 100644 MediaBrowser.Common/Json/Converters/JsonStringConverter.cs delete mode 100644 MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs delete mode 100644 MediaBrowser.Common/Json/JsonDefaults.cs delete mode 100644 MediaBrowser.Controller/Sorting/AlphanumComparator.cs delete mode 100644 MediaBrowser.Model/Entities/JsonLowerCaseConverter.cs create mode 100644 MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs create mode 100644 MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs create mode 100644 src/Jellyfin.Extensions/AlphanumericComparator.cs create mode 100644 src/Jellyfin.Extensions/CopyToExtensions.cs create mode 100644 src/Jellyfin.Extensions/EnumerableExtensions.cs create mode 100644 src/Jellyfin.Extensions/Jellyfin.Extensions.csproj create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonBoolNumberConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonCommaDelimitedArrayConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonDateTimeConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonGuidConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonLowerCaseConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonNullableGuidConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonNullableStructConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonNullableStructConverterFactory.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonPipeDelimitedArrayConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonPipeDelimitedArrayConverterFactory.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonStringConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/Converters/JsonVersionConverter.cs create mode 100644 src/Jellyfin.Extensions/Json/JsonDefaults.cs create mode 100644 src/Jellyfin.Extensions/ShuffleExtensions.cs create mode 100644 src/Jellyfin.Extensions/SplitStringExtensions.cs create mode 100644 src/Jellyfin.Extensions/StreamExtensions.cs create mode 100644 src/Jellyfin.Extensions/StringBuilderExtensions.cs create mode 100644 src/Jellyfin.Extensions/StringExtensions.cs delete mode 100644 tests/Jellyfin.Common.Tests/Extensions/CopyToExtensionsTests.cs delete mode 100644 tests/Jellyfin.Common.Tests/Extensions/ShuffleExtensionsTests.cs delete mode 100644 tests/Jellyfin.Common.Tests/Json/JsonBoolNumberTests.cs delete mode 100644 tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs delete mode 100644 tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedIReadOnlyListTests.cs delete mode 100644 tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs delete mode 100644 tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs delete mode 100644 tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs delete mode 100644 tests/Jellyfin.Common.Tests/Json/JsonStringConverterTests.cs delete mode 100644 tests/Jellyfin.Common.Tests/Json/JsonVersionConverterTests.cs delete mode 100644 tests/Jellyfin.Common.Tests/Models/GenericBodyArrayModel.cs delete mode 100644 tests/Jellyfin.Common.Tests/Models/GenericBodyIReadOnlyListModel.cs delete mode 100644 tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs delete mode 100644 tests/Jellyfin.Controller.Tests/Extensions/StringExtensionsTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/AlphanumericComparatorTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/CopyToExtensionsTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Converters/JsonBoolNumberTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedIReadOnlyListTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Converters/JsonGuidConverterTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Converters/JsonLowerCaseConverterTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Converters/JsonNullableGuidConverterTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Converters/JsonStringConverterTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Converters/JsonVersionConverterTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyArrayModel.cs create mode 100644 tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyIReadOnlyListModel.cs create mode 100644 tests/Jellyfin.Extensions.Tests/ShuffleExtensionsTests.cs create mode 100644 tests/Jellyfin.Extensions.Tests/StringExtensionsTests.cs delete mode 100644 tests/Jellyfin.Model.Tests/Entities/JsonLowerCaseConverterTests.cs create mode 100644 tests/Jellyfin.Providers.Tests/Omdb/JsonOmdbConverterTests.cs (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index a1b1067040..b08f7590d4 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -14,9 +14,9 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Emby.Dlna.Profiles; using Emby.Dlna.Server; +using Jellyfin.Extensions.Json; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Json; using MediaBrowser.Controller; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; diff --git a/Emby.Naming/Audio/AudioFileParser.cs b/Emby.Naming/Audio/AudioFileParser.cs index af4aa0059f..2b610ec796 100644 --- a/Emby.Naming/Audio/AudioFileParser.cs +++ b/Emby.Naming/Audio/AudioFileParser.cs @@ -1,7 +1,7 @@ using System; using System.IO; using Emby.Naming.Common; -using MediaBrowser.Common.Extensions; +using Jellyfin.Extensions; namespace Emby.Naming.Audio { diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs index c4ac5fdc60..3b1d906c64 100644 --- a/Emby.Naming/Video/VideoResolver.cs +++ b/Emby.Naming/Video/VideoResolver.cs @@ -2,7 +2,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; using Emby.Naming.Common; -using MediaBrowser.Common.Extensions; +using Jellyfin.Extensions; namespace Emby.Naming.Video { diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 448f124034..093607dd5e 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Json; +using Jellyfin.Extensions.Json; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 9b147b5d7a..35aa589a11 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -13,8 +13,9 @@ using System.Text.Json; using System.Threading; using Emby.Server.Implementations.Playlists; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; +using Jellyfin.Extensions.Json; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Json; using MediaBrowser.Controller; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index c87f7dbbde..488614609a 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Net; -using MediaBrowser.Common.Extensions; +using Jellyfin.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 8f7d606692..5d38ea0ca6 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -7,7 +7,7 @@ using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Json; +using Jellyfin.Extensions.Json; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Net; using MediaBrowser.Model.Session; diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 64d802457e..ca028a3ca2 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -6,8 +6,8 @@ using System.Globalization; using System.IO; using System.Linq; using System.Runtime.InteropServices; +using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; using MediaBrowser.Model.IO; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 0286735294..d806373329 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -21,6 +21,7 @@ using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.ScheduledTasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs index 4ef7923db3..8062691827 100644 --- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs +++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs @@ -12,7 +12,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Json; +using Jellyfin.Extensions.Json; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index b812b6b61b..91c9e61cf3 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -15,7 +15,7 @@ using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Json; +using Jellyfin.Extensions.Json; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index 97f96f7469..889e29a6ba 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -6,7 +6,7 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; using Emby.Naming.Video; -using MediaBrowser.Common.Extensions; +using Jellyfin.Extensions; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 26e4ef1ed9..93781cb7ba 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -11,9 +11,9 @@ using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions; +using Jellyfin.Extensions.Json; using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Json; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs index bdab8c3e4b..4a031e4752 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; -using MediaBrowser.Common.Json; +using Jellyfin.Extensions.Json; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.LiveTv.EmbyTV diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 00d02873c5..b7639a51ce 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -15,7 +15,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; -using MediaBrowser.Common.Json; +using Jellyfin.Extensions.Json; using MediaBrowser.Common.Net; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Cryptography; @@ -789,7 +789,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings { var channelNumber = GetChannelNumber(channel); - var station = allStations.Find(item => string.Equals(item.stationID, channel.stationID, StringComparison.OrdinalIgnoreCase)) + var station = allStations.Find(item => string.Equals(item.stationID, channel.stationID, StringComparison.OrdinalIgnoreCase)) ?? new ScheduleDirect.Station { stationID = channel.stationID diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 54de841fef..011748d1d6 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -12,8 +12,9 @@ using System.Net.Http; using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions; +using Jellyfin.Extensions.Json; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Json; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 40a1628902..c9657f6057 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -10,6 +10,7 @@ using System.Net.Http; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index b1ff28c2c5..a9e3bfdb0e 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -8,8 +8,8 @@ using System.IO; using System.Reflection; using System.Text.Json; using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Json; +using Jellyfin.Extensions; +using Jellyfin.Extensions.Json; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 8fd61f2bcb..fc0920edfa 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -10,8 +10,8 @@ using System.Text.Json; using System.Threading.Tasks; using MediaBrowser.Common; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Json; -using MediaBrowser.Common.Json.Converters; +using Jellyfin.Extensions.Json; +using Jellyfin.Extensions.Json.Converters; using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; using MediaBrowser.Model.Configuration; diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index d7e3207549..b343250419 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; using Jellyfin.Data.Events; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Json; +using Jellyfin.Extensions.Json; using MediaBrowser.Common.Progress; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 62df354fd4..c4b19f4171 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using Jellyfin.Data.Events; +using Jellyfin.Extensions; using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; diff --git a/Emby.Server.Implementations/Sorting/StudioComparer.cs b/Emby.Server.Implementations/Sorting/StudioComparer.cs index 01445c5254..6826aee3b1 100644 --- a/Emby.Server.Implementations/Sorting/StudioComparer.cs +++ b/Emby.Server.Implementations/Sorting/StudioComparer.cs @@ -4,6 +4,7 @@ using System; using System.Linq; +using Jellyfin.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Querying; @@ -30,7 +31,7 @@ namespace Emby.Server.Implementations.Sorting throw new ArgumentNullException(nameof(y)); } - return AlphanumComparator.CompareValues(x.Studios.FirstOrDefault() ?? string.Empty, y.Studios.FirstOrDefault() ?? string.Empty); + return AlphanumericComparator.CompareValues(x.Studios.FirstOrDefault(), y.Studios.FirstOrDefault()); } /// <summary> diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index b0921cbd82..7b0afa4e24 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -11,7 +11,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Events; using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Json; +using Jellyfin.Extensions.Json; using MediaBrowser.Common.Net; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; diff --git a/Jellyfin.Api/BaseJellyfinApiController.cs b/Jellyfin.Api/BaseJellyfinApiController.cs index 1c1fc71d79..59d6b75137 100644 --- a/Jellyfin.Api/BaseJellyfinApiController.cs +++ b/Jellyfin.Api/BaseJellyfinApiController.cs @@ -1,5 +1,5 @@ using System.Net.Mime; -using MediaBrowser.Common.Json; +using Jellyfin.Extensions.Json; using Microsoft.AspNetCore.Mvc; namespace Jellyfin.Api diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs index b6309baab0..60529e9904 100644 --- a/Jellyfin.Api/Controllers/ConfigurationController.cs +++ b/Jellyfin.Api/Controllers/ConfigurationController.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; using Jellyfin.Api.Models.ConfigurationDtos; -using MediaBrowser.Common.Json; +using Jellyfin.Extensions.Json; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Configuration; diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index 7a61307195..0ae6109bcc 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -8,8 +8,8 @@ using System.Threading.Tasks; using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; using Jellyfin.Api.Models.PluginDtos; +using Jellyfin.Extensions.Json; using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Json; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; using MediaBrowser.Model.Net; diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index ffb726fab7..51d40994e1 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -6,7 +6,7 @@ using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; -using MediaBrowser.Common.Extensions; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs index 1d70406acc..a33a0826c9 100644 --- a/Jellyfin.Api/Controllers/UserLibraryController.cs +++ b/Jellyfin.Api/Controllers/UserLibraryController.cs @@ -9,7 +9,7 @@ using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; -using MediaBrowser.Common.Extensions; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; diff --git a/Jellyfin.Api/Extensions/DtoExtensions.cs b/Jellyfin.Api/Extensions/DtoExtensions.cs index 06173315aa..5e338b67da 100644 --- a/Jellyfin.Api/Extensions/DtoExtensions.cs +++ b/Jellyfin.Api/Extensions/DtoExtensions.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using Jellyfin.Api.Helpers; -using MediaBrowser.Common.Extensions; +using Jellyfin.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; diff --git a/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs b/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs index 8913180e48..411e4c550c 100644 --- a/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs +++ b/Jellyfin.Api/Models/LiveTvDtos/GetProgramsDto.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text.Json.Serialization; using Jellyfin.Data.Enums; -using MediaBrowser.Common.Json.Converters; +using Jellyfin.Extensions.Json.Converters; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; diff --git a/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs b/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs index 65d4b644e4..0761b20855 100644 --- a/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs +++ b/Jellyfin.Api/Models/PlaylistDtos/CreatePlaylistDto.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Text.Json.Serialization; -using MediaBrowser.Common.Json.Converters; +using Jellyfin.Extensions.Json.Converters; namespace Jellyfin.Api.Models.PlaylistDtos { diff --git a/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs b/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs index e58095536a..fa62472e1e 100644 --- a/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs +++ b/Jellyfin.Api/Models/SessionDtos/ClientCapabilitiesDto.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Text.Json.Serialization; -using MediaBrowser.Common.Json.Converters; +using Jellyfin.Extensions.Json.Converters; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Session; @@ -85,4 +85,4 @@ namespace Jellyfin.Api.Models.SessionDtos }; } } -} \ No newline at end of file +} diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 924b250cec..15dc438561 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -21,11 +21,11 @@ using Jellyfin.Api.Constants; using Jellyfin.Api.Controllers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; +using Jellyfin.Extensions.Json; using Jellyfin.Networking.Configuration; using Jellyfin.Server.Configuration; using Jellyfin.Server.Filters; using Jellyfin.Server.Formatters; -using MediaBrowser.Common.Json; using MediaBrowser.Common.Net; using MediaBrowser.Model.Entities; using Microsoft.AspNetCore.Authentication; diff --git a/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs b/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs index c349e3dca2..ea8c5ecdb1 100644 --- a/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs +++ b/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs @@ -1,4 +1,4 @@ -using MediaBrowser.Common.Json; +using Jellyfin.Extensions.Json; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Net.Http.Headers; diff --git a/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs b/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs index 0480f5e0ec..03ca7dda72 100644 --- a/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs +++ b/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs @@ -1,5 +1,5 @@ using System.Net.Mime; -using MediaBrowser.Common.Json; +using Jellyfin.Extensions.Json; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Net.Http.Headers; diff --git a/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs b/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs index 310a3d31ad..c1f5b5dfaf 100644 --- a/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs +++ b/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Web; -using MediaBrowser.Common.Extensions; +using Jellyfin.Extensions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Primitives; diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index 96bd2ccc40..d9524645a1 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -4,9 +4,9 @@ using Emby.Server.Implementations.Data; using Emby.Server.Implementations.Serialization; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions.Json; using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations.Users; -using MediaBrowser.Common.Json; using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; diff --git a/Jellyfin.sln b/Jellyfin.sln index 9fbd9d266e..4626601c3d 100644 --- a/Jellyfin.sln +++ b/Jellyfin.sln @@ -83,6 +83,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Providers.Tests", "tests\Jellyfin.Providers.Tests\Jellyfin.Providers.Tests.csproj", "{A964008C-2136-4716-B6CB-B3426C22320A}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Extensions", "src\Jellyfin.Extensions\Jellyfin.Extensions.csproj", "{750B8757-BE3D-4F8C-941A-FBAD94904ADA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Extensions.Tests", "tests\Jellyfin.Extensions.Tests\Jellyfin.Extensions.Tests.csproj", "{332A5C7A-F907-47CA-910E-BE6F7371B9E0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -229,6 +235,14 @@ Global {A964008C-2136-4716-B6CB-B3426C22320A}.Debug|Any CPU.Build.0 = Debug|Any CPU {A964008C-2136-4716-B6CB-B3426C22320A}.Release|Any CPU.ActiveCfg = Release|Any CPU {A964008C-2136-4716-B6CB-B3426C22320A}.Release|Any CPU.Build.0 = Release|Any CPU + {750B8757-BE3D-4F8C-941A-FBAD94904ADA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {750B8757-BE3D-4F8C-941A-FBAD94904ADA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {750B8757-BE3D-4F8C-941A-FBAD94904ADA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {750B8757-BE3D-4F8C-941A-FBAD94904ADA}.Release|Any CPU.Build.0 = Release|Any CPU + {332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -247,6 +261,8 @@ Global {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {A964008C-2136-4716-B6CB-B3426C22320A} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {750B8757-BE3D-4F8C-941A-FBAD94904ADA} = {C9F0AB5D-F4D7-40C8-A353-3305C86D6D4C} + {332A5C7A-F907-47CA-910E-BE6F7371B9E0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} diff --git a/MediaBrowser.Common/Extensions/CopyToExtensions.cs b/MediaBrowser.Common/Extensions/CopyToExtensions.cs deleted file mode 100644 index 2ecbc6539b..0000000000 --- a/MediaBrowser.Common/Extensions/CopyToExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; - -namespace MediaBrowser.Common.Extensions -{ - /// <summary> - /// Provides <c>CopyTo</c> extensions methods for <see cref="IReadOnlyList{T}" />. - /// </summary> - public static class CopyToExtensions - { - /// <summary> - /// Copies all the elements of the current collection to the specified list - /// starting at the specified destination array index. The index is specified as a 32-bit integer. - /// </summary> - /// <param name="source">The current collection that is the source of the elements.</param> - /// <param name="destination">The list that is the destination of the elements copied from the current collection.</param> - /// <param name="index">A 32-bit integer that represents the index in <c>destination</c> at which copying begins.</param> - /// <typeparam name="T">The type of the array.</typeparam> - public static void CopyTo<T>(this IReadOnlyList<T> source, IList<T> destination, int index = 0) - { - for (int i = 0; i < source.Count; i++) - { - destination[index + i] = source[i]; - } - } - } -} diff --git a/MediaBrowser.Common/Extensions/EnumerableExtensions.cs b/MediaBrowser.Common/Extensions/EnumerableExtensions.cs deleted file mode 100644 index 2b8a6c395b..0000000000 --- a/MediaBrowser.Common/Extensions/EnumerableExtensions.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MediaBrowser.Common.Extensions -{ - /// <summary> - /// Static extensions for the <see cref="IEnumerable{T}"/> interface. - /// </summary> - public static class EnumerableExtensions - { - /// <summary> - /// Determines whether the value is contained in the source collection. - /// </summary> - /// <param name="source">An instance of the <see cref="IEnumerable{String}"/> interface.</param> - /// <param name="value">The value to look for in the collection.</param> - /// <param name="stringComparison">The string comparison.</param> - /// <returns>A value indicating whether the value is contained in the collection.</returns> - /// <exception cref="ArgumentNullException">The source is null.</exception> - public static bool Contains(this IEnumerable<string> source, ReadOnlySpan<char> value, StringComparison stringComparison) - { - if (source == null) - { - throw new ArgumentNullException(nameof(source)); - } - - if (source is IList<string> list) - { - int len = list.Count; - for (int i = 0; i < len; i++) - { - if (value.Equals(list[i], stringComparison)) - { - return true; - } - } - - return false; - } - - foreach (string element in source) - { - if (value.Equals(element, stringComparison)) - { - return true; - } - } - - return false; - } - } -} diff --git a/MediaBrowser.Common/Extensions/ShuffleExtensions.cs b/MediaBrowser.Common/Extensions/ShuffleExtensions.cs deleted file mode 100644 index 2604abf85c..0000000000 --- a/MediaBrowser.Common/Extensions/ShuffleExtensions.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MediaBrowser.Common.Extensions -{ - /// <summary> - /// Provides <c>Shuffle</c> extensions methods for <see cref="IList{T}" />. - /// </summary> - public static class ShuffleExtensions - { - private static readonly Random _rng = new Random(); - - /// <summary> - /// Shuffles the items in a list. - /// </summary> - /// <param name="list">The list that should get shuffled.</param> - /// <typeparam name="T">The type.</typeparam> - public static void Shuffle<T>(this IList<T> list) - { - list.Shuffle(_rng); - } - - /// <summary> - /// Shuffles the items in a list. - /// </summary> - /// <param name="list">The list that should get shuffled.</param> - /// <param name="rng">The random number generator to use.</param> - /// <typeparam name="T">The type.</typeparam> - public static void Shuffle<T>(this IList<T> list, Random rng) - { - int n = list.Count; - while (n > 1) - { - int k = rng.Next(n--); - T value = list[k]; - list[k] = list[n]; - list[n] = value; - } - } - } -} diff --git a/MediaBrowser.Common/Extensions/SplitStringExtensions.cs b/MediaBrowser.Common/Extensions/SplitStringExtensions.cs deleted file mode 100644 index 9c9108495f..0000000000 --- a/MediaBrowser.Common/Extensions/SplitStringExtensions.cs +++ /dev/null @@ -1,95 +0,0 @@ -/* -MIT License - -Copyright (c) 2019 Gérald Barré - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - */ - -#pragma warning disable CS1591 -#pragma warning disable CA1034 -using System; -using System.Diagnostics.Contracts; -using System.Runtime.InteropServices; - -namespace MediaBrowser.Common.Extensions -{ - /// <summary> - /// Extension class for splitting lines without unnecessary allocations. - /// </summary> - public static class SplitStringExtensions - { - /// <summary> - /// Creates a new string split enumerator. - /// </summary> - /// <param name="str">The string to split.</param> - /// <param name="separator">The separator to split on.</param> - /// <returns>The enumerator struct.</returns> - [Pure] - public static SplitEnumerator SpanSplit(this string str, char separator) => new (str.AsSpan(), separator); - - /// <summary> - /// Creates a new span split enumerator. - /// </summary> - /// <param name="str">The span to split.</param> - /// <param name="separator">The separator to split on.</param> - /// <returns>The enumerator struct.</returns> - [Pure] - public static SplitEnumerator Split(this ReadOnlySpan<char> str, char separator) => new (str, separator); - - [StructLayout(LayoutKind.Auto)] - public ref struct SplitEnumerator - { - private readonly char _separator; - private ReadOnlySpan<char> _str; - - public SplitEnumerator(ReadOnlySpan<char> str, char separator) - { - _str = str; - _separator = separator; - Current = default; - } - - public ReadOnlySpan<char> Current { get; private set; } - - public readonly SplitEnumerator GetEnumerator() => this; - - public bool MoveNext() - { - if (_str.Length == 0) - { - return false; - } - - var span = _str; - var index = span.IndexOf(_separator); - if (index == -1) - { - _str = ReadOnlySpan<char>.Empty; - Current = span; - return true; - } - - Current = span.Slice(0, index); - _str = span[(index + 1)..]; - return true; - } - } - } -} diff --git a/MediaBrowser.Common/Extensions/StreamExtensions.cs b/MediaBrowser.Common/Extensions/StreamExtensions.cs deleted file mode 100644 index 5cbf57d988..0000000000 --- a/MediaBrowser.Common/Extensions/StreamExtensions.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; - -namespace MediaBrowser.Common.Extensions -{ - /// <summary> - /// Class BaseExtensions. - /// </summary> - public static class StreamExtensions - { - /// <summary> - /// Reads all lines in the <see cref="Stream" />. - /// </summary> - /// <param name="stream">The <see cref="Stream" /> to read from.</param> - /// <returns>All lines in the stream.</returns> - public static string[] ReadAllLines(this Stream stream) - => ReadAllLines(stream, Encoding.UTF8); - - /// <summary> - /// Reads all lines in the <see cref="Stream" />. - /// </summary> - /// <param name="stream">The <see cref="Stream" /> to read from.</param> - /// <param name="encoding">The character encoding to use.</param> - /// <returns>All lines in the stream.</returns> - public static string[] ReadAllLines(this Stream stream, Encoding encoding) - { - using (StreamReader reader = new StreamReader(stream, encoding)) - { - return ReadAllLines(reader).ToArray(); - } - } - - /// <summary> - /// Reads all lines in the <see cref="TextReader" />. - /// </summary> - /// <param name="reader">The <see cref="TextReader" /> to read from.</param> - /// <returns>All lines in the stream.</returns> - public static IEnumerable<string> ReadAllLines(this TextReader reader) - { - string? line; - while ((line = reader.ReadLine()) != null) - { - yield return line; - } - } - - /// <summary> - /// Reads all lines in the <see cref="TextReader" />. - /// </summary> - /// <param name="reader">The <see cref="TextReader" /> to read from.</param> - /// <returns>All lines in the stream.</returns> - public static async IAsyncEnumerable<string> ReadAllLinesAsync(this TextReader reader) - { - string? line; - while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) != null) - { - yield return line; - } - } - } -} diff --git a/MediaBrowser.Common/Extensions/StringBuilderExtensions.cs b/MediaBrowser.Common/Extensions/StringBuilderExtensions.cs deleted file mode 100644 index 75d654f23b..0000000000 --- a/MediaBrowser.Common/Extensions/StringBuilderExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Collections.Generic; -using System.Text; - -namespace MediaBrowser.Common.Extensions -{ - /// <summary> - /// Extension methods for the <see cref="StringBuilder"/> class. - /// </summary> - public static class StringBuilderExtensions - { - /// <summary> - /// Concatenates and appends the members of a collection in single quotes using the specified delimiter. - /// </summary> - /// <param name="builder">The string builder.</param> - /// <param name="delimiter">The character delimiter.</param> - /// <param name="values">The collection of strings to concatenate.</param> - /// <returns>The updated string builder.</returns> - public static StringBuilder AppendJoinInSingleQuotes(this StringBuilder builder, char delimiter, IReadOnlyList<string> values) - { - var len = values.Count; - for (var i = 0; i < len; i++) - { - builder.Append('\'') - .Append(values[i]) - .Append('\'') - .Append(delimiter); - } - - // remove last , - builder.Length--; - - return builder; - } - } -} diff --git a/MediaBrowser.Common/Json/Converters/JsonBoolNumberConverter.cs b/MediaBrowser.Common/Json/Converters/JsonBoolNumberConverter.cs deleted file mode 100644 index b29e6a71a6..0000000000 --- a/MediaBrowser.Common/Json/Converters/JsonBoolNumberConverter.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace MediaBrowser.Common.Json.Converters -{ - /// <summary> - /// Converts a number to a boolean. - /// This is needed for HDHomerun. - /// </summary> - public class JsonBoolNumberConverter : JsonConverter<bool> - { - /// <inheritdoc /> - public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.Number) - { - return Convert.ToBoolean(reader.GetInt32()); - } - - return reader.GetBoolean(); - } - - /// <inheritdoc /> - public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) - { - writer.WriteBooleanValue(value); - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs deleted file mode 100644 index 127a41a06b..0000000000 --- a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.ComponentModel; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace MediaBrowser.Common.Json.Converters -{ - /// <summary> - /// Convert comma delimited string to array of type. - /// </summary> - /// <typeparam name="T">Type to convert to.</typeparam> - public sealed class JsonCommaDelimitedArrayConverter<T> : JsonDelimitedArrayConverter<T> - { - /// <summary> - /// Initializes a new instance of the <see cref="JsonCommaDelimitedArrayConverter{T}"/> class. - /// </summary> - public JsonCommaDelimitedArrayConverter() : base() - { - } - - /// <inheritdoc /> - protected override char Delimiter => ','; - } -} diff --git a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs deleted file mode 100644 index de41348dda..0000000000 --- a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace MediaBrowser.Common.Json.Converters -{ - /// <summary> - /// Json comma delimited array converter factory. - /// </summary> - /// <remarks> - /// This must be applied as an attribute, adding to the JsonConverter list causes stack overflow. - /// </remarks> - public class JsonCommaDelimitedArrayConverterFactory : JsonConverterFactory - { - /// <inheritdoc /> - public override bool CanConvert(Type typeToConvert) - { - return true; - } - - /// <inheritdoc /> - public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) - { - var structType = typeToConvert.GetElementType() ?? typeToConvert.GenericTypeArguments[0]; - return (JsonConverter?)Activator.CreateInstance(typeof(JsonCommaDelimitedArrayConverter<>).MakeGenericType(structType)); - } - } -} diff --git a/MediaBrowser.Common/Json/Converters/JsonDateTimeConverter.cs b/MediaBrowser.Common/Json/Converters/JsonDateTimeConverter.cs deleted file mode 100644 index 73e3a0493d..0000000000 --- a/MediaBrowser.Common/Json/Converters/JsonDateTimeConverter.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Globalization; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace MediaBrowser.Common.Json.Converters -{ - /// <summary> - /// Legacy DateTime converter. - /// Milliseconds aren't output if zero by default. - /// </summary> - public class JsonDateTimeConverter : JsonConverter<DateTime> - { - /// <inheritdoc /> - public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return reader.GetDateTime(); - } - - /// <inheritdoc /> - public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) - { - if (value.Millisecond == 0) - { - // Remaining ticks value will be 0, manually format. - writer.WriteStringValue(value.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffffZ", CultureInfo.InvariantCulture)); - } - else - { - writer.WriteStringValue(value); - } - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Common/Json/Converters/JsonDelimitedArrayConverter.cs b/MediaBrowser.Common/Json/Converters/JsonDelimitedArrayConverter.cs deleted file mode 100644 index b691798c98..0000000000 --- a/MediaBrowser.Common/Json/Converters/JsonDelimitedArrayConverter.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.ComponentModel; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace MediaBrowser.Common.Json.Converters -{ - /// <summary> - /// Convert delimited string to array of type. - /// </summary> - /// <typeparam name="T">Type to convert to.</typeparam> - public abstract class JsonDelimitedArrayConverter<T> : JsonConverter<T[]?> - { - private readonly TypeConverter _typeConverter; - - /// <summary> - /// Initializes a new instance of the <see cref="JsonDelimitedArrayConverter{T}"/> class. - /// </summary> - protected JsonDelimitedArrayConverter() - { - _typeConverter = TypeDescriptor.GetConverter(typeof(T)); - } - - /// <summary> - /// Gets the array delimiter. - /// </summary> - protected virtual char Delimiter { get; } - - /// <inheritdoc /> - public override T[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.String) - { - // GetString can't return null here because we already handled it above - var stringEntries = reader.GetString()?.Split(Delimiter, StringSplitOptions.RemoveEmptyEntries); - if (stringEntries == null || stringEntries.Length == 0) - { - return Array.Empty<T>(); - } - - var parsedValues = new object[stringEntries.Length]; - var convertedCount = 0; - for (var i = 0; i < stringEntries.Length; i++) - { - try - { - parsedValues[i] = _typeConverter.ConvertFrom(stringEntries[i].Trim()); - convertedCount++; - } - catch (FormatException) - { - // TODO log when upgraded to .Net6 - // https://github.com/dotnet/runtime/issues/42975 - // _logger.LogDebug(e, "Error converting value."); - } - } - - var typedValues = new T[convertedCount]; - var typedValueIndex = 0; - for (var i = 0; i < stringEntries.Length; i++) - { - if (parsedValues[i] != null) - { - typedValues.SetValue(parsedValues[i], typedValueIndex); - typedValueIndex++; - } - } - - return typedValues; - } - - return JsonSerializer.Deserialize<T[]>(ref reader, options); - } - - /// <inheritdoc /> - public override void Write(Utf8JsonWriter writer, T[]? value, JsonSerializerOptions options) - { - throw new NotImplementedException(); - } - } -} diff --git a/MediaBrowser.Common/Json/Converters/JsonGuidConverter.cs b/MediaBrowser.Common/Json/Converters/JsonGuidConverter.cs deleted file mode 100644 index bd96001105..0000000000 --- a/MediaBrowser.Common/Json/Converters/JsonGuidConverter.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Globalization; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace MediaBrowser.Common.Json.Converters -{ - /// <summary> - /// Converts a GUID object or value to/from JSON. - /// </summary> - public class JsonGuidConverter : JsonConverter<Guid> - { - /// <inheritdoc /> - public override Guid Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - var guidStr = reader.GetString(); - return guidStr == null ? Guid.Empty : new Guid(guidStr); - } - - /// <inheritdoc /> - public override void Write(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options) - { - writer.WriteStringValue(value.ToString("N", CultureInfo.InvariantCulture)); - } - } -} diff --git a/MediaBrowser.Common/Json/Converters/JsonNullableGuidConverter.cs b/MediaBrowser.Common/Json/Converters/JsonNullableGuidConverter.cs deleted file mode 100644 index 6d96d5496f..0000000000 --- a/MediaBrowser.Common/Json/Converters/JsonNullableGuidConverter.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Globalization; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace MediaBrowser.Common.Json.Converters -{ - /// <summary> - /// Converts a GUID object or value to/from JSON. - /// </summary> - public class JsonNullableGuidConverter : JsonConverter<Guid?> - { - /// <inheritdoc /> - public override Guid? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - var guidStr = reader.GetString(); - return guidStr == null ? null : new Guid(guidStr); - } - - /// <inheritdoc /> - public override void Write(Utf8JsonWriter writer, Guid? value, JsonSerializerOptions options) - { - if (value == null || value == Guid.Empty) - { - writer.WriteNullValue(); - } - else - { - writer.WriteStringValue(value.Value.ToString("N", CultureInfo.InvariantCulture)); - } - } - } -} diff --git a/MediaBrowser.Common/Json/Converters/JsonNullableStructConverter.cs b/MediaBrowser.Common/Json/Converters/JsonNullableStructConverter.cs deleted file mode 100644 index 0501f7b2a1..0000000000 --- a/MediaBrowser.Common/Json/Converters/JsonNullableStructConverter.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace MediaBrowser.Common.Json.Converters -{ - /// <summary> - /// Converts a nullable struct or value to/from JSON. - /// Required - some clients send an empty string. - /// </summary> - /// <typeparam name="TStruct">The struct type.</typeparam> - public class JsonNullableStructConverter<TStruct> : JsonConverter<TStruct?> - where TStruct : struct - { - /// <inheritdoc /> - public override TStruct? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.Null) - { - return null; - } - - // Token is empty string. - if (reader.TokenType == JsonTokenType.String && ((reader.HasValueSequence && reader.ValueSequence.IsEmpty) || reader.ValueSpan.IsEmpty)) - { - return null; - } - - return JsonSerializer.Deserialize<TStruct>(ref reader, options); - } - - /// <inheritdoc /> - public override void Write(Utf8JsonWriter writer, TStruct? value, JsonSerializerOptions options) - { - if (value.HasValue) - { - JsonSerializer.Serialize(writer, value.Value, options); - } - else - { - writer.WriteNullValue(); - } - } - } -} diff --git a/MediaBrowser.Common/Json/Converters/JsonNullableStructConverterFactory.cs b/MediaBrowser.Common/Json/Converters/JsonNullableStructConverterFactory.cs deleted file mode 100644 index e2a3d798a2..0000000000 --- a/MediaBrowser.Common/Json/Converters/JsonNullableStructConverterFactory.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace MediaBrowser.Common.Json.Converters -{ - /// <summary> - /// Json nullable struct converter factory. - /// </summary> - public class JsonNullableStructConverterFactory : JsonConverterFactory - { - /// <inheritdoc /> - public override bool CanConvert(Type typeToConvert) - { - return typeToConvert.IsGenericType - && typeToConvert.GetGenericTypeDefinition() == typeof(Nullable<>) - && typeToConvert.GenericTypeArguments[0].IsValueType; - } - - /// <inheritdoc /> - public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) - { - var structType = typeToConvert.GenericTypeArguments[0]; - return (JsonConverter?)Activator.CreateInstance(typeof(JsonNullableStructConverter<>).MakeGenericType(structType)); - } - } -} diff --git a/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableInt32Converter.cs b/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableInt32Converter.cs deleted file mode 100644 index 3d97a9de55..0000000000 --- a/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableInt32Converter.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.ComponentModel; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace MediaBrowser.Common.Json.Converters -{ - /// <summary> - /// Converts a string <c>N/A</c> to <c>string.Empty</c>. - /// </summary> - public class JsonOmdbNotAvailableInt32Converter : JsonConverter<int?> - { - /// <inheritdoc /> - public override int? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.String) - { - var str = reader.GetString(); - if (str != null && str.Equals("N/A", StringComparison.OrdinalIgnoreCase)) - { - return null; - } - - var converter = TypeDescriptor.GetConverter(typeToConvert); - return (int?)converter.ConvertFromString(str); - } - - return JsonSerializer.Deserialize<int>(ref reader, options); - } - - /// <inheritdoc /> - public override void Write(Utf8JsonWriter writer, int? value, JsonSerializerOptions options) - { - if (value.HasValue) - { - writer.WriteNumberValue(value.Value); - } - else - { - writer.WriteNullValue(); - } - } - } -} diff --git a/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStringConverter.cs b/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStringConverter.cs deleted file mode 100644 index 77cf46b706..0000000000 --- a/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStringConverter.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace MediaBrowser.Common.Json.Converters -{ - /// <summary> - /// Converts a string <c>N/A</c> to <c>string.Empty</c>. - /// </summary> - public class JsonOmdbNotAvailableStringConverter : JsonConverter<string?> - { - /// <inheritdoc /> - public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.Null) - { - return null; - } - - if (reader.TokenType == JsonTokenType.String) - { - // GetString can't return null here because we already handled it above - var str = reader.GetString()!; - if (str.Equals("N/A", StringComparison.OrdinalIgnoreCase)) - { - return null; - } - - return str; - } - - return JsonSerializer.Deserialize<string?>(ref reader, options); - } - - /// <inheritdoc /> - public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options) - { - writer.WriteStringValue(value); - } - } -} diff --git a/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs b/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs deleted file mode 100644 index a8f6cfbecf..0000000000 --- a/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.ComponentModel; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace MediaBrowser.Common.Json.Converters -{ - /// <summary> - /// Convert Pipe delimited string to array of type. - /// </summary> - /// <typeparam name="T">Type to convert to.</typeparam> - public sealed class JsonPipeDelimitedArrayConverter<T> : JsonDelimitedArrayConverter<T> - { - /// <summary> - /// Initializes a new instance of the <see cref="JsonPipeDelimitedArrayConverter{T}"/> class. - /// </summary> - public JsonPipeDelimitedArrayConverter() : base() - { - } - - /// <inheritdoc /> - protected override char Delimiter => '|'; - } -} diff --git a/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverterFactory.cs b/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverterFactory.cs deleted file mode 100644 index 1bebc49ecc..0000000000 --- a/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverterFactory.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace MediaBrowser.Common.Json.Converters -{ - /// <summary> - /// Json Pipe delimited array converter factory. - /// </summary> - /// <remarks> - /// This must be applied as an attribute, adding to the JsonConverter list causes stack overflow. - /// </remarks> - public class JsonPipeDelimitedArrayConverterFactory : JsonConverterFactory - { - /// <inheritdoc /> - public override bool CanConvert(Type typeToConvert) - { - return true; - } - - /// <inheritdoc /> - public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) - { - var structType = typeToConvert.GetElementType() ?? typeToConvert.GenericTypeArguments[0]; - return (JsonConverter?)Activator.CreateInstance(typeof(JsonPipeDelimitedArrayConverter<>).MakeGenericType(structType)); - } - } -} diff --git a/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs b/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs deleted file mode 100644 index 6cd980e48a..0000000000 --- a/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Buffers; -using System.Text; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace MediaBrowser.Common.Json.Converters -{ - /// <summary> - /// Converter to allow the serializer to read strings. - /// </summary> - public class JsonStringConverter : JsonConverter<string?> - { - /// <inheritdoc /> - public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return reader.TokenType switch - { - JsonTokenType.Null => null, - JsonTokenType.String => reader.GetString(), - _ => GetRawValue(reader) - }; - } - - /// <inheritdoc /> - public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options) - { - writer.WriteStringValue(value); - } - - private static string GetRawValue(Utf8JsonReader reader) - { - var utf8Bytes = reader.HasValueSequence - ? reader.ValueSequence.ToArray() - : reader.ValueSpan; - return Encoding.UTF8.GetString(utf8Bytes); - } - } -} diff --git a/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs b/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs deleted file mode 100644 index 81c093c541..0000000000 --- a/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace MediaBrowser.Common.Json.Converters -{ - /// <summary> - /// Converts a Version object or value to/from JSON. - /// </summary> - /// <remarks> - /// Required to send <see cref="Version"/> as a string instead of an object. - /// </remarks> - public class JsonVersionConverter : JsonConverter<Version> - { - /// <inheritdoc /> - public override Version Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - => new Version(reader.GetString()!); // Will throw ArgumentNullException on null - - /// <inheritdoc /> - public override void Write(Utf8JsonWriter writer, Version value, JsonSerializerOptions options) - => writer.WriteStringValue(value.ToString()); - } -} diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs deleted file mode 100644 index 405d6125f4..0000000000 --- a/MediaBrowser.Common/Json/JsonDefaults.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; -using MediaBrowser.Common.Json.Converters; - -namespace MediaBrowser.Common.Json -{ - /// <summary> - /// Helper class for having compatible JSON throughout the codebase. - /// </summary> - public static class JsonDefaults - { - /// <summary> - /// Pascal case json profile media type. - /// </summary> - public const string PascalCaseMediaType = "application/json; profile=\"PascalCase\""; - - /// <summary> - /// Camel case json profile media type. - /// </summary> - public const string CamelCaseMediaType = "application/json; profile=\"CamelCase\""; - - /// <summary> - /// When changing these options, update - /// Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs - /// -> AddJellyfinApi - /// -> AddJsonOptions. - /// </summary> - private static readonly JsonSerializerOptions _jsonSerializerOptions = new () - { - ReadCommentHandling = JsonCommentHandling.Disallow, - WriteIndented = false, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - NumberHandling = JsonNumberHandling.AllowReadingFromString, - Converters = - { - new JsonGuidConverter(), - new JsonNullableGuidConverter(), - new JsonVersionConverter(), - new JsonStringEnumConverter(), - new JsonNullableStructConverterFactory(), - new JsonBoolNumberConverter(), - new JsonDateTimeConverter(), - new JsonStringConverter() - } - }; - - private static readonly JsonSerializerOptions _pascalCaseJsonSerializerOptions = new (_jsonSerializerOptions) - { - PropertyNamingPolicy = null - }; - - private static readonly JsonSerializerOptions _camelCaseJsonSerializerOptions = new (_jsonSerializerOptions) - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - - /// <summary> - /// Gets the default <see cref="JsonSerializerOptions" /> options. - /// </summary> - /// <remarks> - /// The return value must not be modified. - /// If the defaults must be modified the author must use the copy constructor. - /// </remarks> - /// <returns>The default <see cref="JsonSerializerOptions" /> options.</returns> - public static JsonSerializerOptions Options - => _jsonSerializerOptions; - - /// <summary> - /// Gets camelCase json options. - /// </summary> - /// <remarks> - /// The return value must not be modified. - /// If the defaults must be modified the author must use the copy constructor. - /// </remarks> - /// <returns>The camelCase <see cref="JsonSerializerOptions" /> options.</returns> - public static JsonSerializerOptions CamelCaseOptions - => _camelCaseJsonSerializerOptions; - - /// <summary> - /// Gets PascalCase json options. - /// </summary> - /// <remarks> - /// The return value must not be modified. - /// If the defaults must be modified the author must use the copy constructor. - /// </remarks> - /// <returns>The PascalCase <see cref="JsonSerializerOptions" /> options.</returns> - public static JsonSerializerOptions PascalCaseOptions - => _pascalCaseJsonSerializerOptions; - } -} diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs index ffc274c5de..97f40b5372 100644 --- a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs @@ -3,7 +3,7 @@ using System; using System.Linq; using System.Threading; -using MediaBrowser.Common.Extensions; +using Jellyfin.Extensions; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 6137ddbf7e..a6c22c93df 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -13,6 +13,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 4a721ca44f..4f367fe2b5 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -10,7 +10,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Json; +using Jellyfin.Extensions.Json; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; diff --git a/MediaBrowser.Controller/Entities/Extensions.cs b/MediaBrowser.Controller/Entities/Extensions.cs index 244cc00bea..d8bc0069c7 100644 --- a/MediaBrowser.Controller/Entities/Extensions.cs +++ b/MediaBrowser.Controller/Entities/Extensions.cs @@ -2,7 +2,7 @@ using System; using System.Linq; -using MediaBrowser.Common.Extensions; +using Jellyfin.Extensions; using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Entities diff --git a/MediaBrowser.Controller/Extensions/StringExtensions.cs b/MediaBrowser.Controller/Extensions/StringExtensions.cs index f1af013451..48bd9522a8 100644 --- a/MediaBrowser.Controller/Extensions/StringExtensions.cs +++ b/MediaBrowser.Controller/Extensions/StringExtensions.cs @@ -21,27 +21,6 @@ namespace MediaBrowser.Controller.Extensions return Normalize(string.Concat(chars), NormalizationForm.FormC); } - /// <summary> - /// Counts the number of occurrences of [needle] in the string. - /// </summary> - /// <param name="value">The haystack to search in.</param> - /// <param name="needle">The character to search for.</param> - /// <returns>The number of occurrences of the [needle] character.</returns> - public static int Count(this ReadOnlySpan<char> value, char needle) - { - var count = 0; - var length = value.Length; - for (var i = 0; i < length; i++) - { - if (value[i] == needle) - { - count++; - } - } - - return count; - } - private static string Normalize(string text, NormalizationForm form, bool stripStringOnFailure = true) { if (stripStringOnFailure) diff --git a/MediaBrowser.Controller/Sorting/AlphanumComparator.cs b/MediaBrowser.Controller/Sorting/AlphanumComparator.cs deleted file mode 100644 index e00cadca21..0000000000 --- a/MediaBrowser.Controller/Sorting/AlphanumComparator.cs +++ /dev/null @@ -1,137 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; - -namespace MediaBrowser.Controller.Sorting -{ - public class AlphanumComparator : IComparer<string?> - { - public static int CompareValues(string? s1, string? s2) - { - if (s1 == null && s2 == null) - { - return 0; - } - else if (s1 == null) - { - return -1; - } - else if (s2 == null) - { - return 1; - } - - int len1 = s1.Length; - int len2 = s2.Length; - - // Early return for empty strings - if (len1 == 0 && len2 == 0) - { - return 0; - } - else if (len1 == 0) - { - return -1; - } - else if (len2 == 0) - { - return 1; - } - - int pos1 = 0; - int pos2 = 0; - - do - { - int start1 = pos1; - int start2 = pos2; - - bool isNum1 = char.IsDigit(s1[pos1++]); - bool isNum2 = char.IsDigit(s2[pos2++]); - - while (pos1 < len1 && char.IsDigit(s1[pos1]) == isNum1) - { - pos1++; - } - - while (pos2 < len2 && char.IsDigit(s2[pos2]) == isNum2) - { - pos2++; - } - - var span1 = s1.AsSpan(start1, pos1 - start1); - var span2 = s2.AsSpan(start2, pos2 - start2); - - if (isNum1 && isNum2) - { - // Trim leading zeros so we can compare the length - // of the strings to find the largest number - span1 = span1.TrimStart('0'); - span2 = span2.TrimStart('0'); - var span1Len = span1.Length; - var span2Len = span2.Length; - if (span1Len < span2Len) - { - return -1; - } - else if (span1Len > span2Len) - { - return 1; - } - else if (span1Len >= 20) // Number is probably too big for a ulong - { - // Trim all the first digits that are the same - int i = 0; - while (i < span1Len && span1[i] == span2[i]) - { - i++; - } - - // If there are no more digits it's the same number - if (i == span1Len) - { - continue; - } - - // Only need to compare the most significant digit - span1 = span1.Slice(i, 1); - span2 = span2.Slice(i, 1); - } - - if (!ulong.TryParse(span1, out var num1) - || !ulong.TryParse(span2, out var num2)) - { - return 0; - } - else if (num1 < num2) - { - return -1; - } - else if (num1 > num2) - { - return 1; - } - } - else - { - int result = span1.CompareTo(span2, StringComparison.InvariantCulture); - if (result != 0) - { - return result; - } - } -#pragma warning disable SA1500 // TODO remove with StyleCop.Analyzers v1.2.0 https://github.com/DotNetAnalyzers/StyleCopAnalyzers/pull/3196 - } while (pos1 < len1 && pos2 < len2); -#pragma warning restore SA1500 - - return len1 - len2; - } - - /// <inheritdoc /> - public int Compare(string? x, string? y) - { - return CompareValues(x, y); - } - } -} diff --git a/MediaBrowser.Controller/Sorting/SortExtensions.cs b/MediaBrowser.Controller/Sorting/SortExtensions.cs index aa6ec513f3..f9c0d39ddd 100644 --- a/MediaBrowser.Controller/Sorting/SortExtensions.cs +++ b/MediaBrowser.Controller/Sorting/SortExtensions.cs @@ -1,16 +1,15 @@ -#nullable disable - #pragma warning disable CS1591 using System; using System.Collections.Generic; using System.Linq; +using Jellyfin.Extensions; namespace MediaBrowser.Controller.Sorting { public static class SortExtensions { - private static readonly AlphanumComparator _comparer = new AlphanumComparator(); + private static readonly AlphanumericComparator _comparer = new AlphanumericComparator(); public static IEnumerable<T> OrderByString<T>(this IEnumerable<T> list, Func<T, string> getName) { diff --git a/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs index fdba64c4a5..dc13bf4f6d 100644 --- a/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/EpisodeLocalImageProvider.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using MediaBrowser.Common.Extensions; +using Jellyfin.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 3af618af85..412a953216 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -11,9 +11,9 @@ using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions.Json; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Json; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.MediaEncoding.Probing; diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs index 639a34d993..24ceb1b571 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEditParser.cs @@ -2,7 +2,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Threading; -using MediaBrowser.Common.Extensions; +using Jellyfin.Extensions; using MediaBrowser.Model.MediaInfo; using Microsoft.Extensions.Logging; using Nikse.SubtitleEdit.Core; diff --git a/MediaBrowser.Model/Entities/JsonLowerCaseConverter.cs b/MediaBrowser.Model/Entities/JsonLowerCaseConverter.cs deleted file mode 100644 index 7c627f0e39..0000000000 --- a/MediaBrowser.Model/Entities/JsonLowerCaseConverter.cs +++ /dev/null @@ -1,29 +0,0 @@ -#nullable disable -// THIS IS A HACK -// TODO: @bond Move to separate project - -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace MediaBrowser.Model.Entities -{ - /// <summary> - /// Converts an object to a lowercase string. - /// </summary> - /// <typeparam name="T">The object type.</typeparam> - public class JsonLowerCaseConverter<T> : JsonConverter<T> - { - /// <inheritdoc /> - public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return JsonSerializer.Deserialize<T>(ref reader, options); - } - - /// <inheritdoc /> - public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) - { - writer.WriteStringValue(value?.ToString().ToLowerInvariant()); - } - } -} diff --git a/MediaBrowser.Model/Entities/VirtualFolderInfo.cs b/MediaBrowser.Model/Entities/VirtualFolderInfo.cs index 8fed392b9d..2b2bda12c3 100644 --- a/MediaBrowser.Model/Entities/VirtualFolderInfo.cs +++ b/MediaBrowser.Model/Entities/VirtualFolderInfo.cs @@ -3,6 +3,7 @@ using System; using System.Text.Json.Serialization; +using Jellyfin.Extensions.Json.Converters; using MediaBrowser.Model.Configuration; namespace MediaBrowser.Model.Entities diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 4db99f0b08..c475d905a9 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -50,7 +50,8 @@ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\Jellyfin.Data\Jellyfin.Data.csproj" /> + <ProjectReference Include="../Jellyfin.Data/Jellyfin.Data.csproj" /> + <ProjectReference Include="../src/Jellyfin.Extensions/Jellyfin.Extensions.csproj" /> </ItemGroup> </Project> diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs index 85a28747f5..36d8eeb401 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs @@ -6,7 +6,7 @@ using System.Net.Http; using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Json; +using Jellyfin.Extensions.Json; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs index 25bb3f9ce3..9539c396d9 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs @@ -11,7 +11,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Json; +using Jellyfin.Extensions.Json; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.Audio; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs index db8536cc92..aa61a56f66 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs @@ -6,7 +6,7 @@ using System.Net.Http; using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Json; +using Jellyfin.Extensions.Json; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs index cbb61fa353..b2f05d76de 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs @@ -10,7 +10,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Json; +using Jellyfin.Extensions.Json; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.Audio; diff --git a/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs b/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs new file mode 100644 index 0000000000..268538815e --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs @@ -0,0 +1,46 @@ +#nullable enable + +using System; +using System.ComponentModel; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MediaBrowser.Providers.Plugins.Omdb +{ + /// <summary> + /// Converts a string <c>N/A</c> to <c>string.Empty</c>. + /// </summary> + public class JsonOmdbNotAvailableInt32Converter : JsonConverter<int?> + { + /// <inheritdoc /> + public override int? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + var str = reader.GetString(); + if (str != null && str.Equals("N/A", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + var converter = TypeDescriptor.GetConverter(typeToConvert); + return (int?)converter.ConvertFromString(str); + } + + return JsonSerializer.Deserialize<int>(ref reader, options); + } + + /// <inheritdoc /> + public override void Write(Utf8JsonWriter writer, int? value, JsonSerializerOptions options) + { + if (value.HasValue) + { + writer.WriteNumberValue(value.Value); + } + else + { + writer.WriteNullValue(); + } + } + } +} diff --git a/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs b/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs new file mode 100644 index 0000000000..c19589d45b --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs @@ -0,0 +1,43 @@ +#nullable enable + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MediaBrowser.Providers.Plugins.Omdb +{ + /// <summary> + /// Converts a string <c>N/A</c> to <c>string.Empty</c>. + /// </summary> + public class JsonOmdbNotAvailableStringConverter : JsonConverter<string?> + { + /// <inheritdoc /> + public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + + if (reader.TokenType == JsonTokenType.String) + { + // GetString can't return null here because we already handled it above + var str = reader.GetString()!; + if (str.Equals("N/A", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + return str; + } + + return JsonSerializer.Deserialize<string?>(ref reader, options); + } + + /// <inheritdoc /> + public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options) + { + writer.WriteStringValue(value); + } + } +} diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs index 428b0ded11..78eea02e03 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs @@ -10,8 +10,8 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; -using MediaBrowser.Common.Json; -using MediaBrowser.Common.Json.Converters; +using Jellyfin.Extensions.Json; +using Jellyfin.Extensions.Json.Converters; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs index 46d3038905..5d9fd36d35 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs @@ -10,8 +10,8 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; -using MediaBrowser.Common.Json; -using MediaBrowser.Common.Json.Converters; +using Jellyfin.Extensions.Json; +using Jellyfin.Extensions.Json.Converters; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; diff --git a/MediaBrowser.Providers/Properties/AssemblyInfo.cs b/MediaBrowser.Providers/Properties/AssemblyInfo.cs index fe4749c799..bd301b5f08 100644 --- a/MediaBrowser.Providers/Properties/AssemblyInfo.cs +++ b/MediaBrowser.Providers/Properties/AssemblyInfo.cs @@ -15,7 +15,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] -[assembly: InternalsVisibleTo("Jellyfin.Common.Tests")] +[assembly: InternalsVisibleTo("Jellyfin.Providers.Tests")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs index f6153dd532..63e78d15e8 100644 --- a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs +++ b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs @@ -8,7 +8,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; +using Jellyfin.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; diff --git a/src/Jellyfin.Extensions/AlphanumericComparator.cs b/src/Jellyfin.Extensions/AlphanumericComparator.cs new file mode 100644 index 0000000000..e3c81eba8c --- /dev/null +++ b/src/Jellyfin.Extensions/AlphanumericComparator.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; + +namespace Jellyfin.Extensions +{ + /// <summary> + /// Alphanumeric <see cref="IComparer{T}" />. + /// </summary> + public class AlphanumericComparator : IComparer<string?> + { + /// <summary> + /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. + /// </summary> + /// <param name="s1">The first object to compare.</param> + /// <param name="s2">The second object to compare.</param> + /// <returns>A signed integer that indicates the relative values of <c>x</c> and <c>y</c>.</returns> + public static int CompareValues(string? s1, string? s2) + { + if (s1 == null && s2 == null) + { + return 0; + } + else if (s1 == null) + { + return -1; + } + else if (s2 == null) + { + return 1; + } + + int len1 = s1.Length; + int len2 = s2.Length; + + // Early return for empty strings + if (len1 == 0 && len2 == 0) + { + return 0; + } + else if (len1 == 0) + { + return -1; + } + else if (len2 == 0) + { + return 1; + } + + int pos1 = 0; + int pos2 = 0; + + do + { + int start1 = pos1; + int start2 = pos2; + + bool isNum1 = char.IsDigit(s1[pos1++]); + bool isNum2 = char.IsDigit(s2[pos2++]); + + while (pos1 < len1 && char.IsDigit(s1[pos1]) == isNum1) + { + pos1++; + } + + while (pos2 < len2 && char.IsDigit(s2[pos2]) == isNum2) + { + pos2++; + } + + var span1 = s1.AsSpan(start1, pos1 - start1); + var span2 = s2.AsSpan(start2, pos2 - start2); + + if (isNum1 && isNum2) + { + // Trim leading zeros so we can compare the length + // of the strings to find the largest number + span1 = span1.TrimStart('0'); + span2 = span2.TrimStart('0'); + var span1Len = span1.Length; + var span2Len = span2.Length; + if (span1Len < span2Len) + { + return -1; + } + else if (span1Len > span2Len) + { + return 1; + } + else if (span1Len >= 20) // Number is probably too big for a ulong + { + // Trim all the first digits that are the same + int i = 0; + while (i < span1Len && span1[i] == span2[i]) + { + i++; + } + + // If there are no more digits it's the same number + if (i == span1Len) + { + continue; + } + + // Only need to compare the most significant digit + span1 = span1.Slice(i, 1); + span2 = span2.Slice(i, 1); + } + + if (!ulong.TryParse(span1, out var num1) + || !ulong.TryParse(span2, out var num2)) + { + return 0; + } + else if (num1 < num2) + { + return -1; + } + else if (num1 > num2) + { + return 1; + } + } + else + { + int result = span1.CompareTo(span2, StringComparison.InvariantCulture); + if (result != 0) + { + return result; + } + } +#pragma warning disable SA1500 // TODO remove with StyleCop.Analyzers v1.2.0 https://github.com/DotNetAnalyzers/StyleCopAnalyzers/pull/3196 + } while (pos1 < len1 && pos2 < len2); +#pragma warning restore SA1500 + + return len1 - len2; + } + + /// <inheritdoc /> + public int Compare(string? x, string? y) + { + return CompareValues(x, y); + } + } +} diff --git a/src/Jellyfin.Extensions/CopyToExtensions.cs b/src/Jellyfin.Extensions/CopyToExtensions.cs new file mode 100644 index 0000000000..72d37b5b6a --- /dev/null +++ b/src/Jellyfin.Extensions/CopyToExtensions.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace Jellyfin.Extensions +{ + /// <summary> + /// Provides <c>CopyTo</c> extensions methods for <see cref="IReadOnlyList{T}" />. + /// </summary> + public static class CopyToExtensions + { + /// <summary> + /// Copies all the elements of the current collection to the specified list + /// starting at the specified destination array index. The index is specified as a 32-bit integer. + /// </summary> + /// <param name="source">The current collection that is the source of the elements.</param> + /// <param name="destination">The list that is the destination of the elements copied from the current collection.</param> + /// <param name="index">A 32-bit integer that represents the index in <c>destination</c> at which copying begins.</param> + /// <typeparam name="T">The type of the array.</typeparam> + public static void CopyTo<T>(this IReadOnlyList<T> source, IList<T> destination, int index = 0) + { + for (int i = 0; i < source.Count; i++) + { + destination[index + i] = source[i]; + } + } + } +} diff --git a/src/Jellyfin.Extensions/EnumerableExtensions.cs b/src/Jellyfin.Extensions/EnumerableExtensions.cs new file mode 100644 index 0000000000..b5fe933571 --- /dev/null +++ b/src/Jellyfin.Extensions/EnumerableExtensions.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; + +namespace Jellyfin.Extensions +{ + /// <summary> + /// Static extensions for the <see cref="IEnumerable{T}"/> interface. + /// </summary> + public static class EnumerableExtensions + { + /// <summary> + /// Determines whether the value is contained in the source collection. + /// </summary> + /// <param name="source">An instance of the <see cref="IEnumerable{String}"/> interface.</param> + /// <param name="value">The value to look for in the collection.</param> + /// <param name="stringComparison">The string comparison.</param> + /// <returns>A value indicating whether the value is contained in the collection.</returns> + /// <exception cref="ArgumentNullException">The source is null.</exception> + public static bool Contains(this IEnumerable<string> source, ReadOnlySpan<char> value, StringComparison stringComparison) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (source is IList<string> list) + { + int len = list.Count; + for (int i = 0; i < len; i++) + { + if (value.Equals(list[i], stringComparison)) + { + return true; + } + } + + return false; + } + + foreach (string element in source) + { + if (value.Equals(element, stringComparison)) + { + return true; + } + } + + return false; + } + } +} diff --git a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj new file mode 100644 index 0000000000..c7b9a4ad0d --- /dev/null +++ b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj @@ -0,0 +1,30 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net5.0</TargetFramework> + <GenerateAssemblyInfo>false</GenerateAssemblyInfo> + <GenerateDocumentationFile>true</GenerateDocumentationFile> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <Nullable>enable</Nullable> + <AnalysisMode>AllEnabledByDefault</AnalysisMode> + <CodeAnalysisRuleSet>../../jellyfin.ruleset</CodeAnalysisRuleSet> + </PropertyGroup> + + <PropertyGroup> + <Authors>Jellyfin Contributors</Authors> + <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> + <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> + </PropertyGroup> + + <ItemGroup> + <Compile Include="../../SharedVersion.cs" /> + </ItemGroup> + + <!-- Code Analyzers--> + <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> + </ItemGroup> + +</Project> diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonBoolNumberConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonBoolNumberConverter.cs new file mode 100644 index 0000000000..c2543cf7ce --- /dev/null +++ b/src/Jellyfin.Extensions/Json/Converters/JsonBoolNumberConverter.cs @@ -0,0 +1,30 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Jellyfin.Extensions.Json.Converters +{ + /// <summary> + /// Converts a number to a boolean. + /// This is needed for HDHomerun. + /// </summary> + public class JsonBoolNumberConverter : JsonConverter<bool> + { + /// <inheritdoc /> + public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Number) + { + return Convert.ToBoolean(reader.GetInt32()); + } + + return reader.GetBoolean(); + } + + /// <inheritdoc /> + public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) + { + writer.WriteBooleanValue(value); + } + } +} diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonCommaDelimitedArrayConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonCommaDelimitedArrayConverter.cs new file mode 100644 index 0000000000..44980ec02f --- /dev/null +++ b/src/Jellyfin.Extensions/Json/Converters/JsonCommaDelimitedArrayConverter.cs @@ -0,0 +1,24 @@ +using System; +using System.ComponentModel; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Jellyfin.Extensions.Json.Converters +{ + /// <summary> + /// Convert comma delimited string to array of type. + /// </summary> + /// <typeparam name="T">Type to convert to.</typeparam> + public sealed class JsonCommaDelimitedArrayConverter<T> : JsonDelimitedArrayConverter<T> + { + /// <summary> + /// Initializes a new instance of the <see cref="JsonCommaDelimitedArrayConverter{T}"/> class. + /// </summary> + public JsonCommaDelimitedArrayConverter() : base() + { + } + + /// <inheritdoc /> + protected override char Delimiter => ','; + } +} diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs b/src/Jellyfin.Extensions/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs new file mode 100644 index 0000000000..cc9311a24c --- /dev/null +++ b/src/Jellyfin.Extensions/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs @@ -0,0 +1,28 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Jellyfin.Extensions.Json.Converters +{ + /// <summary> + /// Json comma delimited array converter factory. + /// </summary> + /// <remarks> + /// This must be applied as an attribute, adding to the JsonConverter list causes stack overflow. + /// </remarks> + public class JsonCommaDelimitedArrayConverterFactory : JsonConverterFactory + { + /// <inheritdoc /> + public override bool CanConvert(Type typeToConvert) + { + return true; + } + + /// <inheritdoc /> + public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + var structType = typeToConvert.GetElementType() ?? typeToConvert.GenericTypeArguments[0]; + return (JsonConverter?)Activator.CreateInstance(typeof(JsonCommaDelimitedArrayConverter<>).MakeGenericType(structType)); + } + } +} diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonDateTimeConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonDateTimeConverter.cs new file mode 100644 index 0000000000..8ae080b15a --- /dev/null +++ b/src/Jellyfin.Extensions/Json/Converters/JsonDateTimeConverter.cs @@ -0,0 +1,34 @@ +using System; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Jellyfin.Extensions.Json.Converters +{ + /// <summary> + /// Legacy DateTime converter. + /// Milliseconds aren't output if zero by default. + /// </summary> + public class JsonDateTimeConverter : JsonConverter<DateTime> + { + /// <inheritdoc /> + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDateTime(); + } + + /// <inheritdoc /> + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + if (value.Millisecond == 0) + { + // Remaining ticks value will be 0, manually format. + writer.WriteStringValue(value.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffffZ", CultureInfo.InvariantCulture)); + } + else + { + writer.WriteStringValue(value); + } + } + } +} diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs new file mode 100644 index 0000000000..c39805aa35 --- /dev/null +++ b/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs @@ -0,0 +1,81 @@ +using System; +using System.ComponentModel; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Jellyfin.Extensions.Json.Converters +{ + /// <summary> + /// Convert delimited string to array of type. + /// </summary> + /// <typeparam name="T">Type to convert to.</typeparam> + public abstract class JsonDelimitedArrayConverter<T> : JsonConverter<T[]?> + { + private readonly TypeConverter _typeConverter; + + /// <summary> + /// Initializes a new instance of the <see cref="JsonDelimitedArrayConverter{T}"/> class. + /// </summary> + protected JsonDelimitedArrayConverter() + { + _typeConverter = TypeDescriptor.GetConverter(typeof(T)); + } + + /// <summary> + /// Gets the array delimiter. + /// </summary> + protected virtual char Delimiter { get; } + + /// <inheritdoc /> + public override T[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + // GetString can't return null here because we already handled it above + var stringEntries = reader.GetString()?.Split(Delimiter, StringSplitOptions.RemoveEmptyEntries); + if (stringEntries == null || stringEntries.Length == 0) + { + return Array.Empty<T>(); + } + + var parsedValues = new object[stringEntries.Length]; + var convertedCount = 0; + for (var i = 0; i < stringEntries.Length; i++) + { + try + { + parsedValues[i] = _typeConverter.ConvertFrom(stringEntries[i].Trim()); + convertedCount++; + } + catch (FormatException) + { + // TODO log when upgraded to .Net6 + // https://github.com/dotnet/runtime/issues/42975 + // _logger.LogDebug(e, "Error converting value."); + } + } + + var typedValues = new T[convertedCount]; + var typedValueIndex = 0; + for (var i = 0; i < stringEntries.Length; i++) + { + if (parsedValues[i] != null) + { + typedValues.SetValue(parsedValues[i], typedValueIndex); + typedValueIndex++; + } + } + + return typedValues; + } + + return JsonSerializer.Deserialize<T[]>(ref reader, options); + } + + /// <inheritdoc /> + public override void Write(Utf8JsonWriter writer, T[]? value, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonGuidConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonGuidConverter.cs new file mode 100644 index 0000000000..be94dd5192 --- /dev/null +++ b/src/Jellyfin.Extensions/Json/Converters/JsonGuidConverter.cs @@ -0,0 +1,26 @@ +using System; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Jellyfin.Extensions.Json.Converters +{ + /// <summary> + /// Converts a GUID object or value to/from JSON. + /// </summary> + public class JsonGuidConverter : JsonConverter<Guid> + { + /// <inheritdoc /> + public override Guid Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var guidStr = reader.GetString(); + return guidStr == null ? Guid.Empty : new Guid(guidStr); + } + + /// <inheritdoc /> + public override void Write(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString("N", CultureInfo.InvariantCulture)); + } + } +} diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonLowerCaseConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonLowerCaseConverter.cs new file mode 100644 index 0000000000..cd582ced64 --- /dev/null +++ b/src/Jellyfin.Extensions/Json/Converters/JsonLowerCaseConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Jellyfin.Extensions.Json.Converters +{ + /// <summary> + /// Converts an object to a lowercase string. + /// </summary> + /// <typeparam name="T">The object type.</typeparam> + public class JsonLowerCaseConverter<T> : JsonConverter<T> + { + /// <inheritdoc /> + public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return JsonSerializer.Deserialize<T>(ref reader, options); + } + + /// <inheritdoc /> + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + writer.WriteStringValue(value?.ToString()?.ToLowerInvariant()); + } + } +} diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonNullableGuidConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonNullableGuidConverter.cs new file mode 100644 index 0000000000..6192d1598d --- /dev/null +++ b/src/Jellyfin.Extensions/Json/Converters/JsonNullableGuidConverter.cs @@ -0,0 +1,33 @@ +using System; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Jellyfin.Extensions.Json.Converters +{ + /// <summary> + /// Converts a GUID object or value to/from JSON. + /// </summary> + public class JsonNullableGuidConverter : JsonConverter<Guid?> + { + /// <inheritdoc /> + public override Guid? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var guidStr = reader.GetString(); + return guidStr == null ? null : new Guid(guidStr); + } + + /// <inheritdoc /> + public override void Write(Utf8JsonWriter writer, Guid? value, JsonSerializerOptions options) + { + if (value == null || value == Guid.Empty) + { + writer.WriteNullValue(); + } + else + { + writer.WriteStringValue(value.Value.ToString("N", CultureInfo.InvariantCulture)); + } + } + } +} diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonNullableStructConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonNullableStructConverter.cs new file mode 100644 index 0000000000..6de238b39e --- /dev/null +++ b/src/Jellyfin.Extensions/Json/Converters/JsonNullableStructConverter.cs @@ -0,0 +1,45 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Jellyfin.Extensions.Json.Converters +{ + /// <summary> + /// Converts a nullable struct or value to/from JSON. + /// Required - some clients send an empty string. + /// </summary> + /// <typeparam name="TStruct">The struct type.</typeparam> + public class JsonNullableStructConverter<TStruct> : JsonConverter<TStruct?> + where TStruct : struct + { + /// <inheritdoc /> + public override TStruct? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + + // Token is empty string. + if (reader.TokenType == JsonTokenType.String && ((reader.HasValueSequence && reader.ValueSequence.IsEmpty) || reader.ValueSpan.IsEmpty)) + { + return null; + } + + return JsonSerializer.Deserialize<TStruct>(ref reader, options); + } + + /// <inheritdoc /> + public override void Write(Utf8JsonWriter writer, TStruct? value, JsonSerializerOptions options) + { + if (value.HasValue) + { + JsonSerializer.Serialize(writer, value.Value, options); + } + else + { + writer.WriteNullValue(); + } + } + } +} diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonNullableStructConverterFactory.cs b/src/Jellyfin.Extensions/Json/Converters/JsonNullableStructConverterFactory.cs new file mode 100644 index 0000000000..e7749589ae --- /dev/null +++ b/src/Jellyfin.Extensions/Json/Converters/JsonNullableStructConverterFactory.cs @@ -0,0 +1,27 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Jellyfin.Extensions.Json.Converters +{ + /// <summary> + /// Json nullable struct converter factory. + /// </summary> + public class JsonNullableStructConverterFactory : JsonConverterFactory + { + /// <inheritdoc /> + public override bool CanConvert(Type typeToConvert) + { + return typeToConvert.IsGenericType + && typeToConvert.GetGenericTypeDefinition() == typeof(Nullable<>) + && typeToConvert.GenericTypeArguments[0].IsValueType; + } + + /// <inheritdoc /> + public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + var structType = typeToConvert.GenericTypeArguments[0]; + return (JsonConverter?)Activator.CreateInstance(typeof(JsonNullableStructConverter<>).MakeGenericType(structType)); + } + } +} diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonPipeDelimitedArrayConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonPipeDelimitedArrayConverter.cs new file mode 100644 index 0000000000..e3e492e24d --- /dev/null +++ b/src/Jellyfin.Extensions/Json/Converters/JsonPipeDelimitedArrayConverter.cs @@ -0,0 +1,24 @@ +using System; +using System.ComponentModel; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Jellyfin.Extensions.Json.Converters +{ + /// <summary> + /// Convert Pipe delimited string to array of type. + /// </summary> + /// <typeparam name="T">Type to convert to.</typeparam> + public sealed class JsonPipeDelimitedArrayConverter<T> : JsonDelimitedArrayConverter<T> + { + /// <summary> + /// Initializes a new instance of the <see cref="JsonPipeDelimitedArrayConverter{T}"/> class. + /// </summary> + public JsonPipeDelimitedArrayConverter() : base() + { + } + + /// <inheritdoc /> + protected override char Delimiter => '|'; + } +} diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonPipeDelimitedArrayConverterFactory.cs b/src/Jellyfin.Extensions/Json/Converters/JsonPipeDelimitedArrayConverterFactory.cs new file mode 100644 index 0000000000..579674f18c --- /dev/null +++ b/src/Jellyfin.Extensions/Json/Converters/JsonPipeDelimitedArrayConverterFactory.cs @@ -0,0 +1,28 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Jellyfin.Extensions.Json.Converters +{ + /// <summary> + /// Json Pipe delimited array converter factory. + /// </summary> + /// <remarks> + /// This must be applied as an attribute, adding to the JsonConverter list causes stack overflow. + /// </remarks> + public class JsonPipeDelimitedArrayConverterFactory : JsonConverterFactory + { + /// <inheritdoc /> + public override bool CanConvert(Type typeToConvert) + { + return true; + } + + /// <inheritdoc /> + public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + var structType = typeToConvert.GetElementType() ?? typeToConvert.GenericTypeArguments[0]; + return (JsonConverter?)Activator.CreateInstance(typeof(JsonPipeDelimitedArrayConverter<>).MakeGenericType(structType)); + } + } +} diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonStringConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonStringConverter.cs new file mode 100644 index 0000000000..1a7a8c4f55 --- /dev/null +++ b/src/Jellyfin.Extensions/Json/Converters/JsonStringConverter.cs @@ -0,0 +1,39 @@ +using System; +using System.Buffers; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Jellyfin.Extensions.Json.Converters +{ + /// <summary> + /// Converter to allow the serializer to read strings. + /// </summary> + public class JsonStringConverter : JsonConverter<string?> + { + /// <inheritdoc /> + public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.TokenType switch + { + JsonTokenType.Null => null, + JsonTokenType.String => reader.GetString(), + _ => GetRawValue(reader) + }; + } + + /// <inheritdoc /> + public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options) + { + writer.WriteStringValue(value); + } + + private static string GetRawValue(Utf8JsonReader reader) + { + var utf8Bytes = reader.HasValueSequence + ? reader.ValueSequence.ToArray() + : reader.ValueSpan; + return Encoding.UTF8.GetString(utf8Bytes); + } + } +} diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonVersionConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonVersionConverter.cs new file mode 100644 index 0000000000..51ffec1cbf --- /dev/null +++ b/src/Jellyfin.Extensions/Json/Converters/JsonVersionConverter.cs @@ -0,0 +1,23 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Jellyfin.Extensions.Json.Converters +{ + /// <summary> + /// Converts a Version object or value to/from JSON. + /// </summary> + /// <remarks> + /// Required to send <see cref="Version"/> as a string instead of an object. + /// </remarks> + public class JsonVersionConverter : JsonConverter<Version> + { + /// <inheritdoc /> + public override Version Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => new Version(reader.GetString()!); // Will throw ArgumentNullException on null + + /// <inheritdoc /> + public override void Write(Utf8JsonWriter writer, Version value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString()); + } +} diff --git a/src/Jellyfin.Extensions/Json/JsonDefaults.cs b/src/Jellyfin.Extensions/Json/JsonDefaults.cs new file mode 100644 index 0000000000..f4ec911231 --- /dev/null +++ b/src/Jellyfin.Extensions/Json/JsonDefaults.cs @@ -0,0 +1,90 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Jellyfin.Extensions.Json.Converters; + +namespace Jellyfin.Extensions.Json +{ + /// <summary> + /// Helper class for having compatible JSON throughout the codebase. + /// </summary> + public static class JsonDefaults + { + /// <summary> + /// Pascal case json profile media type. + /// </summary> + public const string PascalCaseMediaType = "application/json; profile=\"PascalCase\""; + + /// <summary> + /// Camel case json profile media type. + /// </summary> + public const string CamelCaseMediaType = "application/json; profile=\"CamelCase\""; + + /// <summary> + /// When changing these options, update + /// Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs + /// -> AddJellyfinApi + /// -> AddJsonOptions. + /// </summary> + private static readonly JsonSerializerOptions _jsonSerializerOptions = new () + { + ReadCommentHandling = JsonCommentHandling.Disallow, + WriteIndented = false, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + NumberHandling = JsonNumberHandling.AllowReadingFromString, + Converters = + { + new JsonGuidConverter(), + new JsonNullableGuidConverter(), + new JsonVersionConverter(), + new JsonStringEnumConverter(), + new JsonNullableStructConverterFactory(), + new JsonBoolNumberConverter(), + new JsonDateTimeConverter(), + new JsonStringConverter() + } + }; + + private static readonly JsonSerializerOptions _pascalCaseJsonSerializerOptions = new (_jsonSerializerOptions) + { + PropertyNamingPolicy = null + }; + + private static readonly JsonSerializerOptions _camelCaseJsonSerializerOptions = new (_jsonSerializerOptions) + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + /// <summary> + /// Gets the default <see cref="JsonSerializerOptions" /> options. + /// </summary> + /// <remarks> + /// The return value must not be modified. + /// If the defaults must be modified the author must use the copy constructor. + /// </remarks> + /// <returns>The default <see cref="JsonSerializerOptions" /> options.</returns> + public static JsonSerializerOptions Options + => _jsonSerializerOptions; + + /// <summary> + /// Gets camelCase json options. + /// </summary> + /// <remarks> + /// The return value must not be modified. + /// If the defaults must be modified the author must use the copy constructor. + /// </remarks> + /// <returns>The camelCase <see cref="JsonSerializerOptions" /> options.</returns> + public static JsonSerializerOptions CamelCaseOptions + => _camelCaseJsonSerializerOptions; + + /// <summary> + /// Gets PascalCase json options. + /// </summary> + /// <remarks> + /// The return value must not be modified. + /// If the defaults must be modified the author must use the copy constructor. + /// </remarks> + /// <returns>The PascalCase <see cref="JsonSerializerOptions" /> options.</returns> + public static JsonSerializerOptions PascalCaseOptions + => _pascalCaseJsonSerializerOptions; + } +} diff --git a/src/Jellyfin.Extensions/ShuffleExtensions.cs b/src/Jellyfin.Extensions/ShuffleExtensions.cs new file mode 100644 index 0000000000..4e481983f3 --- /dev/null +++ b/src/Jellyfin.Extensions/ShuffleExtensions.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; + +namespace Jellyfin.Extensions +{ + /// <summary> + /// Provides <c>Shuffle</c> extensions methods for <see cref="IList{T}" />. + /// </summary> + public static class ShuffleExtensions + { + private static readonly Random _rng = new Random(); + + /// <summary> + /// Shuffles the items in a list. + /// </summary> + /// <param name="list">The list that should get shuffled.</param> + /// <typeparam name="T">The type.</typeparam> + public static void Shuffle<T>(this IList<T> list) + { + list.Shuffle(_rng); + } + + /// <summary> + /// Shuffles the items in a list. + /// </summary> + /// <param name="list">The list that should get shuffled.</param> + /// <param name="rng">The random number generator to use.</param> + /// <typeparam name="T">The type.</typeparam> + public static void Shuffle<T>(this IList<T> list, Random rng) + { + int n = list.Count; + while (n > 1) + { + int k = rng.Next(n--); + T value = list[k]; + list[k] = list[n]; + list[n] = value; + } + } + } +} diff --git a/src/Jellyfin.Extensions/SplitStringExtensions.cs b/src/Jellyfin.Extensions/SplitStringExtensions.cs new file mode 100644 index 0000000000..5fa5c01235 --- /dev/null +++ b/src/Jellyfin.Extensions/SplitStringExtensions.cs @@ -0,0 +1,115 @@ +/* +MIT License + +Copyright (c) 2019 Gérald Barré + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +// TODO: remove when analyzer is fixed: https://github.com/dotnet/roslyn-analyzers/issues/5158 +#pragma warning disable CA1034 // Nested types should not be visible + +using System; +using System.Diagnostics.Contracts; +using System.Runtime.InteropServices; + +namespace Jellyfin.Extensions +{ + /// <summary> + /// Extension class for splitting lines without unnecessary allocations. + /// </summary> + public static class SplitStringExtensions + { + /// <summary> + /// Creates a new string split enumerator. + /// </summary> + /// <param name="str">The string to split.</param> + /// <param name="separator">The separator to split on.</param> + /// <returns>The enumerator struct.</returns> + [Pure] + public static Enumerator SpanSplit(this string str, char separator) => new (str.AsSpan(), separator); + + /// <summary> + /// Creates a new span split enumerator. + /// </summary> + /// <param name="str">The span to split.</param> + /// <param name="separator">The separator to split on.</param> + /// <returns>The enumerator struct.</returns> + [Pure] + public static Enumerator Split(this ReadOnlySpan<char> str, char separator) => new (str, separator); + + /// <summary> + /// Provides an enumerator for the substrings seperated by the separator. + /// </summary> + [StructLayout(LayoutKind.Auto)] + public ref struct Enumerator + { + private readonly char _separator; + private ReadOnlySpan<char> _str; + + /// <summary> + /// Initializes a new instance of the <see cref="Enumerator"/> struct. + /// </summary> + /// <param name="str">The span to split.</param> + /// <param name="separator">The separator to split on.</param> + public Enumerator(ReadOnlySpan<char> str, char separator) + { + _str = str; + _separator = separator; + Current = default; + } + + /// <summary> + /// Gets a reference to the item at the current position of the enumerator. + /// </summary> + public ReadOnlySpan<char> Current { get; private set; } + + /// <summary> + /// Returns <c>this</c>. + /// </summary> + /// <returns><c>this</c>.</returns> + public readonly Enumerator GetEnumerator() => this; + + /// <summary> + /// Advances the enumerator to the next item. + /// </summary> + /// <returns><c>true</c> if there is a next element; otherwise <c>false</c>.</returns> + public bool MoveNext() + { + if (_str.Length == 0) + { + return false; + } + + var span = _str; + var index = span.IndexOf(_separator); + if (index == -1) + { + _str = ReadOnlySpan<char>.Empty; + Current = span; + return true; + } + + Current = span.Slice(0, index); + _str = span[(index + 1)..]; + return true; + } + } + } +} diff --git a/src/Jellyfin.Extensions/StreamExtensions.cs b/src/Jellyfin.Extensions/StreamExtensions.cs new file mode 100644 index 0000000000..9751d9d42a --- /dev/null +++ b/src/Jellyfin.Extensions/StreamExtensions.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Jellyfin.Extensions +{ + /// <summary> + /// Class BaseExtensions. + /// </summary> + public static class StreamExtensions + { + /// <summary> + /// Reads all lines in the <see cref="Stream" />. + /// </summary> + /// <param name="stream">The <see cref="Stream" /> to read from.</param> + /// <returns>All lines in the stream.</returns> + public static string[] ReadAllLines(this Stream stream) + => ReadAllLines(stream, Encoding.UTF8); + + /// <summary> + /// Reads all lines in the <see cref="Stream" />. + /// </summary> + /// <param name="stream">The <see cref="Stream" /> to read from.</param> + /// <param name="encoding">The character encoding to use.</param> + /// <returns>All lines in the stream.</returns> + public static string[] ReadAllLines(this Stream stream, Encoding encoding) + { + using (StreamReader reader = new StreamReader(stream, encoding)) + { + return ReadAllLines(reader).ToArray(); + } + } + + /// <summary> + /// Reads all lines in the <see cref="TextReader" />. + /// </summary> + /// <param name="reader">The <see cref="TextReader" /> to read from.</param> + /// <returns>All lines in the stream.</returns> + public static IEnumerable<string> ReadAllLines(this TextReader reader) + { + string? line; + while ((line = reader.ReadLine()) != null) + { + yield return line; + } + } + + /// <summary> + /// Reads all lines in the <see cref="TextReader" />. + /// </summary> + /// <param name="reader">The <see cref="TextReader" /> to read from.</param> + /// <returns>All lines in the stream.</returns> + public static async IAsyncEnumerable<string> ReadAllLinesAsync(this TextReader reader) + { + string? line; + while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) != null) + { + yield return line; + } + } + } +} diff --git a/src/Jellyfin.Extensions/StringBuilderExtensions.cs b/src/Jellyfin.Extensions/StringBuilderExtensions.cs new file mode 100644 index 0000000000..02ff7cc1f9 --- /dev/null +++ b/src/Jellyfin.Extensions/StringBuilderExtensions.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Text; + +namespace Jellyfin.Extensions +{ + /// <summary> + /// Extension methods for the <see cref="StringBuilder"/> class. + /// </summary> + public static class StringBuilderExtensions + { + /// <summary> + /// Concatenates and appends the members of a collection in single quotes using the specified delimiter. + /// </summary> + /// <param name="builder">The string builder.</param> + /// <param name="delimiter">The character delimiter.</param> + /// <param name="values">The collection of strings to concatenate.</param> + /// <returns>The updated string builder.</returns> + public static StringBuilder AppendJoinInSingleQuotes(this StringBuilder builder, char delimiter, IReadOnlyList<string> values) + { + var len = values.Count; + for (var i = 0; i < len; i++) + { + builder.Append('\'') + .Append(values[i]) + .Append('\'') + .Append(delimiter); + } + + // remove last , + builder.Length--; + + return builder; + } + } +} diff --git a/src/Jellyfin.Extensions/StringExtensions.cs b/src/Jellyfin.Extensions/StringExtensions.cs new file mode 100644 index 0000000000..acc695ed2f --- /dev/null +++ b/src/Jellyfin.Extensions/StringExtensions.cs @@ -0,0 +1,31 @@ +using System; + +namespace Jellyfin.Extensions +{ + /// <summary> + /// Provides extensions methods for <see cref="string" />. + /// </summary> + public static class StringExtensions + { + /// <summary> + /// Counts the number of occurrences of [needle] in the string. + /// </summary> + /// <param name="value">The haystack to search in.</param> + /// <param name="needle">The character to search for.</param> + /// <returns>The number of occurrences of the [needle] character.</returns> + public static int Count(this ReadOnlySpan<char> value, char needle) + { + var count = 0; + var length = value.Length; + for (var i = 0; i < length; i++) + { + if (value[i] == needle) + { + count++; + } + } + + return count; + } + } +} diff --git a/tests/Jellyfin.Common.Tests/Extensions/CopyToExtensionsTests.cs b/tests/Jellyfin.Common.Tests/Extensions/CopyToExtensionsTests.cs deleted file mode 100644 index 9903409fa6..0000000000 --- a/tests/Jellyfin.Common.Tests/Extensions/CopyToExtensionsTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Collections.Generic; -using MediaBrowser.Common.Extensions; -using Xunit; - -namespace Jellyfin.Common.Tests.Extensions -{ - public static class CopyToExtensionsTests - { - public static IEnumerable<object[]> CopyTo_Valid_Correct_TestData() - { - yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, 0, new[] { 0, 1, 2, 3, 4, 5 } }; - yield return new object[] { new[] { 0, 1, 2 }, new[] { 5, 4, 3, 2, 1, 0 }, 2, new[] { 5, 4, 0, 1, 2, 0 } }; - } - - [Theory] - [MemberData(nameof(CopyTo_Valid_Correct_TestData))] - public static void CopyTo_Valid_Correct<T>(IReadOnlyList<T> source, IList<T> destination, int index, IList<T> expected) - { - source.CopyTo(destination, index); - Assert.Equal(expected, destination); - } - - public static IEnumerable<object[]> CopyTo_Invalid_ThrowsArgumentOutOfRangeException_TestData() - { - yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, -1 }; - yield return new object[] { new[] { 0, 1, 2 }, new[] { 5, 4, 3, 2, 1, 0 }, 6 }; - yield return new object[] { new[] { 0, 1, 2 }, Array.Empty<int>(), 0 }; - yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0 }, 0 }; - yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, 1 }; - } - - [Theory] - [MemberData(nameof(CopyTo_Invalid_ThrowsArgumentOutOfRangeException_TestData))] - public static void CopyTo_Invalid_ThrowsArgumentOutOfRangeException<T>(IReadOnlyList<T> source, IList<T> destination, int index) - { - Assert.Throws<ArgumentOutOfRangeException>(() => source.CopyTo(destination, index)); - } - } -} diff --git a/tests/Jellyfin.Common.Tests/Extensions/ShuffleExtensionsTests.cs b/tests/Jellyfin.Common.Tests/Extensions/ShuffleExtensionsTests.cs deleted file mode 100644 index cbdbcf112c..0000000000 --- a/tests/Jellyfin.Common.Tests/Extensions/ShuffleExtensionsTests.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using MediaBrowser.Common.Extensions; -using Xunit; - -namespace Jellyfin.Common.Tests.Extensions -{ - public static class ShuffleExtensionsTests - { - private static readonly Random _rng = new Random(); - - [Fact] - public static void Shuffle_Valid_Correct() - { - byte[] original = new byte[1 << 6]; - _rng.NextBytes(original); - byte[] shuffled = (byte[])original.Clone(); - shuffled.Shuffle(); - - Assert.NotEqual(original, shuffled); - } - } -} diff --git a/tests/Jellyfin.Common.Tests/Json/JsonBoolNumberTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonBoolNumberTests.cs deleted file mode 100644 index 7629d9912c..0000000000 --- a/tests/Jellyfin.Common.Tests/Json/JsonBoolNumberTests.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Globalization; -using System.Text.Json; -using FsCheck; -using FsCheck.Xunit; -using MediaBrowser.Common.Json.Converters; -using Xunit; - -namespace Jellyfin.Common.Tests.Json -{ - public class JsonBoolNumberTests - { - private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions() - { - Converters = - { - new JsonBoolNumberConverter() - } - }; - - [Theory] - [InlineData("1", true)] - [InlineData("0", false)] - [InlineData("2", true)] - [InlineData("true", true)] - [InlineData("false", false)] - public void Deserialize_Number_Valid_Success(string input, bool? output) - { - var value = JsonSerializer.Deserialize<bool>(input, _jsonOptions); - Assert.Equal(value, output); - } - - [Theory] - [InlineData(true, "true")] - [InlineData(false, "false")] - public void Serialize_Bool_Success(bool input, string output) - { - var value = JsonSerializer.Serialize(input, _jsonOptions); - Assert.Equal(value, output); - } - - [Property] - public Property Deserialize_NonZeroInt_True(NonZeroInt input) - => JsonSerializer.Deserialize<bool>(input.ToString(), _jsonOptions).ToProperty(); - } -} diff --git a/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs deleted file mode 100644 index ca300401da..0000000000 --- a/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using System.Text.Json; -using System.Text.Json.Serialization; -using Jellyfin.Common.Tests.Models; -using MediaBrowser.Model.Session; -using Xunit; - -namespace Jellyfin.Common.Tests.Json -{ - public static class JsonCommaDelimitedArrayTests - { - [Fact] - public static void Deserialize_String_Null_Success() - { - var options = new JsonSerializerOptions(); - var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": null }", options); - Assert.Null(value?.Value); - } - - [Fact] - public static void Deserialize_Empty_Success() - { - var desiredValue = new GenericBodyArrayModel<string> - { - Value = Array.Empty<string>() - }; - - var options = new JsonSerializerOptions(); - var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": """" }", options); - Assert.Equal(desiredValue.Value, value?.Value); - } - - [Fact] - public static void Deserialize_String_Valid_Success() - { - var desiredValue = new GenericBodyArrayModel<string> - { - Value = new[] { "a", "b", "c" } - }; - - var options = new JsonSerializerOptions(); - var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": ""a,b,c"" }", options); - Assert.Equal(desiredValue.Value, value?.Value); - } - - [Fact] - public static void Deserialize_String_Space_Valid_Success() - { - var desiredValue = new GenericBodyArrayModel<string> - { - Value = new[] { "a", "b", "c" } - }; - - var options = new JsonSerializerOptions(); - var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": ""a, b, c"" }", options); - Assert.Equal(desiredValue.Value, value?.Value); - } - - [Fact] - public static void Deserialize_GenericCommandType_Valid_Success() - { - var desiredValue = new GenericBodyArrayModel<GeneralCommandType> - { - Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } - }; - - var options = new JsonSerializerOptions(); - options.Converters.Add(new JsonStringEnumConverter()); - var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,MoveDown"" }", options); - Assert.Equal(desiredValue.Value, value?.Value); - } - - [Fact] - public static void Deserialize_GenericCommandType_EmptyEntry_Success() - { - var desiredValue = new GenericBodyArrayModel<GeneralCommandType> - { - Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } - }; - - var options = new JsonSerializerOptions(); - options.Converters.Add(new JsonStringEnumConverter()); - var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,,MoveDown"" }", options); - Assert.Equal(desiredValue.Value, value?.Value); - } - - [Fact] - public static void Deserialize_GenericCommandType_Invalid_Success() - { - var desiredValue = new GenericBodyArrayModel<GeneralCommandType> - { - Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } - }; - - var options = new JsonSerializerOptions(); - options.Converters.Add(new JsonStringEnumConverter()); - var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,TotallyNotAVallidCommand,MoveDown"" }", options); - Assert.Equal(desiredValue.Value, value?.Value); - } - - [Fact] - public static void Deserialize_GenericCommandType_Space_Valid_Success() - { - var desiredValue = new GenericBodyArrayModel<GeneralCommandType> - { - Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } - }; - - var options = new JsonSerializerOptions(); - options.Converters.Add(new JsonStringEnumConverter()); - var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp, MoveDown"" }", options); - Assert.Equal(desiredValue.Value, value?.Value); - } - - [Fact] - public static void Deserialize_String_Array_Valid_Success() - { - var desiredValue = new GenericBodyArrayModel<string> - { - Value = new[] { "a", "b", "c" } - }; - - var options = new JsonSerializerOptions(); - var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": [""a"",""b"",""c""] }", options); - Assert.Equal(desiredValue.Value, value?.Value); - } - - [Fact] - public static void Deserialize_GenericCommandType_Array_Valid_Success() - { - var desiredValue = new GenericBodyArrayModel<GeneralCommandType> - { - Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } - }; - - var options = new JsonSerializerOptions(); - options.Converters.Add(new JsonStringEnumConverter()); - var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", options); - Assert.Equal(desiredValue.Value, value?.Value); - } - } -} diff --git a/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedIReadOnlyListTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedIReadOnlyListTests.cs deleted file mode 100644 index 34ad9bac79..0000000000 --- a/tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedIReadOnlyListTests.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; -using Jellyfin.Common.Tests.Models; -using MediaBrowser.Model.Session; -using Xunit; - -namespace Jellyfin.Common.Tests.Json -{ - public static class JsonCommaDelimitedIReadOnlyListTests - { - [Fact] - public static void Deserialize_String_Valid_Success() - { - var desiredValue = new GenericBodyIReadOnlyListModel<string> - { - Value = new[] { "a", "b", "c" } - }; - - var options = new JsonSerializerOptions(); - var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": ""a,b,c"" }", options); - Assert.Equal(desiredValue.Value, value?.Value); - } - - [Fact] - public static void Deserialize_String_Space_Valid_Success() - { - var desiredValue = new GenericBodyIReadOnlyListModel<string> - { - Value = new[] { "a", "b", "c" } - }; - - var options = new JsonSerializerOptions(); - var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": ""a, b, c"" }", options); - Assert.Equal(desiredValue.Value, value?.Value); - } - - [Fact] - public static void Deserialize_GenericCommandType_Valid_Success() - { - var desiredValue = new GenericBodyIReadOnlyListModel<GeneralCommandType> - { - Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } - }; - - var options = new JsonSerializerOptions(); - options.Converters.Add(new JsonStringEnumConverter()); - var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,MoveDown"" }", options); - Assert.Equal(desiredValue.Value, value?.Value); - } - - [Fact] - public static void Deserialize_GenericCommandType_Space_Valid_Success() - { - var desiredValue = new GenericBodyIReadOnlyListModel<GeneralCommandType> - { - Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } - }; - - var options = new JsonSerializerOptions(); - options.Converters.Add(new JsonStringEnumConverter()); - var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp, MoveDown"" }", options); - Assert.Equal(desiredValue.Value, value?.Value); - } - - [Fact] - public static void Deserialize_String_Array_Valid_Success() - { - var desiredValue = new GenericBodyIReadOnlyListModel<string> - { - Value = new[] { "a", "b", "c" } - }; - - var options = new JsonSerializerOptions(); - var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": [""a"",""b"",""c""] }", options); - Assert.Equal(desiredValue.Value, value?.Value); - } - - [Fact] - public static void Deserialize_GenericCommandType_Array_Valid_Success() - { - var desiredValue = new GenericBodyIReadOnlyListModel<GeneralCommandType> - { - Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } - }; - - var options = new JsonSerializerOptions(); - options.Converters.Add(new JsonStringEnumConverter()); - var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", options); - Assert.Equal(desiredValue.Value, value?.Value); - } - } -} diff --git a/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs deleted file mode 100644 index dbfad3c2ff..0000000000 --- a/tests/Jellyfin.Common.Tests/Json/JsonGuidConverterTests.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Text.Json; -using MediaBrowser.Common.Json.Converters; -using Xunit; - -namespace Jellyfin.Common.Tests.Json -{ - public class JsonGuidConverterTests - { - private readonly JsonSerializerOptions _options; - - public JsonGuidConverterTests() - { - _options = new JsonSerializerOptions(); - _options.Converters.Add(new JsonGuidConverter()); - } - - [Fact] - public void Deserialize_Valid_Success() - { - Guid value = JsonSerializer.Deserialize<Guid>(@"""a852a27afe324084ae66db579ee3ee18""", _options); - Assert.Equal(new Guid("a852a27afe324084ae66db579ee3ee18"), value); - } - - [Fact] - public void Deserialize_ValidDashed_Success() - { - Guid value = JsonSerializer.Deserialize<Guid>(@"""e9b2dcaa-529c-426e-9433-5e9981f27f2e""", _options); - Assert.Equal(new Guid("e9b2dcaa-529c-426e-9433-5e9981f27f2e"), value); - } - - [Fact] - public void Roundtrip_Valid_Success() - { - Guid guid = new Guid("a852a27afe324084ae66db579ee3ee18"); - string value = JsonSerializer.Serialize(guid, _options); - Assert.Equal(guid, JsonSerializer.Deserialize<Guid>(value, _options)); - } - - [Fact] - public void Deserialize_Null_EmptyGuid() - { - Assert.Equal(Guid.Empty, JsonSerializer.Deserialize<Guid>("null", _options)); - } - - [Fact] - public void Serialize_EmptyGuid_EmptyGuid() - { - Assert.Equal($"\"{Guid.Empty:N}\"", JsonSerializer.Serialize(Guid.Empty, _options)); - } - - [Fact] - public void Serialize_Valid_NoDash_Success() - { - var guid = new Guid("531797E9-9457-40E0-88BC-B1D6D38752FA"); - var str = JsonSerializer.Serialize(guid, _options); - Assert.Equal($"\"{guid:N}\"", str); - } - - [Fact] - public void Serialize_Nullable_Success() - { - Guid? guid = new Guid("531797E9-9457-40E0-88BC-B1D6D38752FA"); - var str = JsonSerializer.Serialize(guid, _options); - Assert.Equal($"\"{guid:N}\"", str); - } - } -} diff --git a/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs deleted file mode 100644 index cb3b66c4c5..0000000000 --- a/tests/Jellyfin.Common.Tests/Json/JsonNullableGuidConverterTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Text.Json; -using MediaBrowser.Common.Json.Converters; -using Xunit; - -namespace Jellyfin.Common.Tests.Json -{ - public class JsonNullableGuidConverterTests - { - private readonly JsonSerializerOptions _options; - - public JsonNullableGuidConverterTests() - { - _options = new JsonSerializerOptions(); - _options.Converters.Add(new JsonNullableGuidConverter()); - } - - [Fact] - public void Deserialize_Valid_Success() - { - Guid? value = JsonSerializer.Deserialize<Guid?>(@"""a852a27afe324084ae66db579ee3ee18""", _options); - Assert.Equal(new Guid("a852a27afe324084ae66db579ee3ee18"), value); - } - - [Fact] - public void Deserialize_ValidDashed_Success() - { - Guid? value = JsonSerializer.Deserialize<Guid?>(@"""e9b2dcaa-529c-426e-9433-5e9981f27f2e""", _options); - Assert.Equal(new Guid("e9b2dcaa-529c-426e-9433-5e9981f27f2e"), value); - } - - [Fact] - public void Roundtrip_Valid_Success() - { - Guid guid = new Guid("a852a27afe324084ae66db579ee3ee18"); - string value = JsonSerializer.Serialize(guid, _options); - Assert.Equal(guid, JsonSerializer.Deserialize<Guid?>(value, _options)); - } - - [Fact] - public void Deserialize_Null_Null() - { - Assert.Null(JsonSerializer.Deserialize<Guid?>("null", _options)); - } - - [Fact] - public void Deserialize_EmptyGuid_EmptyGuid() - { - Assert.Equal(Guid.Empty, JsonSerializer.Deserialize<Guid?>(@"""00000000-0000-0000-0000-000000000000""", _options)); - } - - [Fact] - public void Serialize_EmptyGuid_Null() - { - Assert.Equal("null", JsonSerializer.Serialize((Guid?)Guid.Empty, _options)); - } - - [Fact] - public void Serialize_Null_Null() - { - Assert.Equal("null", JsonSerializer.Serialize((Guid?)null, _options)); - } - - [Fact] - public void Serialize_Valid_NoDash_Success() - { - var guid = (Guid?)new Guid("531797E9-9457-40E0-88BC-B1D6D38752FA"); - var str = JsonSerializer.Serialize(guid, _options); - Assert.Equal($"\"{guid:N}\"", str); - } - - [Fact] - public void Serialize_Nullable_Success() - { - Guid? guid = new Guid("531797E9-9457-40E0-88BC-B1D6D38752FA"); - var str = JsonSerializer.Serialize(guid, _options); - Assert.Equal($"\"{guid:N}\"", str); - } - } -} diff --git a/tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs deleted file mode 100644 index efe8063a07..0000000000 --- a/tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; -using MediaBrowser.Common.Json.Converters; -using MediaBrowser.Providers.Plugins.Omdb; -using Xunit; - -namespace Jellyfin.Common.Tests.Json -{ - public class JsonOmdbConverterTests - { - private readonly JsonSerializerOptions _options; - - public JsonOmdbConverterTests() - { - _options = new JsonSerializerOptions(); - _options.Converters.Add(new JsonOmdbNotAvailableStringConverter()); - _options.Converters.Add(new JsonOmdbNotAvailableInt32Converter()); - _options.NumberHandling = JsonNumberHandling.AllowReadingFromString; - } - - [Fact] - public void Deserialize_Omdb_Response_Not_Available_Success() - { - const string Input = "{\"Title\":\"Chapter 1\",\"Year\":\"2013\",\"Rated\":\"TV-MA\",\"Released\":\"01 Feb 2013\",\"Season\":\"N/A\",\"Episode\":\"N/A\",\"Runtime\":\"55 min\",\"Genre\":\"Drama\",\"Director\":\"David Fincher\",\"Writer\":\"Michael Dobbs (based on the novels by), Andrew Davies (based on the mini-series by), Beau Willimon (created for television by), Beau Willimon, Sam Forman (staff writer)\",\"Actors\":\"Kevin Spacey, Robin Wright, Kate Mara, Corey Stoll\",\"Plot\":\"Congressman Francis Underwood has been declined the chair for Secretary of State. He's now gathering his own team to plot his revenge. Zoe Barnes, a reporter for the Washington Herald, will do anything to get her big break.\",\"Language\":\"English\",\"Country\":\"USA\",\"Awards\":\"N/A\",\"Poster\":\"https://m.media-amazon.com/images/M/MV5BMTY5MTU4NDQzNV5BMl5BanBnXkFtZTgwMzk2ODcxMzE@._V1_SX300.jpg\",\"Ratings\":[{\"Source\":\"Internet Movie Database\",\"Value\":\"8.7/10\"}],\"Metascore\":\"N/A\",\"imdbRating\":\"8.7\",\"imdbVotes\":\"6736\",\"imdbID\":\"tt2161930\",\"seriesID\":\"N/A\",\"Type\":\"episode\",\"Response\":\"True\"}"; - var seasonRootObject = JsonSerializer.Deserialize<OmdbProvider.RootObject>(Input, _options); - Assert.NotNull(seasonRootObject); - Assert.Null(seasonRootObject?.Awards); - Assert.Null(seasonRootObject?.Episode); - Assert.Null(seasonRootObject?.Metascore); - } - - [Theory] - [InlineData("\"N/A\"")] - [InlineData("null")] - public void Deserialization_To_Nullable_Int_Shoud_Be_Null(string input) - { - var result = JsonSerializer.Deserialize<int?>(input, _options); - Assert.Null(result); - } - - [Theory] - [InlineData("\"8\"", 8)] - [InlineData("8", 8)] - public void Deserialize_NullableInt_Success(string input, int? expected) - { - var result = JsonSerializer.Deserialize<int?>(input, _options); - Assert.Equal(result, expected); - } - - [Theory] - [InlineData("\"N/A\"")] - [InlineData("null")] - public void Deserialization_To_Nullable_String_Shoud_Be_Null(string input) - { - var result = JsonSerializer.Deserialize<string?>(input, _options); - Assert.Null(result); - } - - [Theory] - [InlineData("\"Jellyfin\"", "Jellyfin")] - public void Deserialize_Normal_String_Success(string input, string expected) - { - var result = JsonSerializer.Deserialize<string?>(input, _options); - Assert.Equal(expected, result); - } - - [Fact] - public void Roundtrip_Valid_Success() - { - const string Input = "{\"Title\":\"Chapter 1\",\"Year\":\"2013\",\"Rated\":\"TV-MA\",\"Released\":\"01 Feb 2013\",\"Season\":\"N/A\",\"Episode\":\"N/A\",\"Runtime\":\"55 min\",\"Genre\":\"Drama\",\"Director\":\"David Fincher\",\"Writer\":\"Michael Dobbs (based on the novels by), Andrew Davies (based on the mini-series by), Beau Willimon (created for television by), Beau Willimon, Sam Forman (staff writer)\",\"Actors\":\"Kevin Spacey, Robin Wright, Kate Mara, Corey Stoll\",\"Plot\":\"Congressman Francis Underwood has been declined the chair for Secretary of State. He's now gathering his own team to plot his revenge. Zoe Barnes, a reporter for the Washington Herald, will do anything to get her big break.\",\"Language\":\"English\",\"Country\":\"USA\",\"Awards\":\"N/A\",\"Poster\":\"https://m.media-amazon.com/images/M/MV5BMTY5MTU4NDQzNV5BMl5BanBnXkFtZTgwMzk2ODcxMzE@._V1_SX300.jpg\",\"Ratings\":[{\"Source\":\"Internet Movie Database\",\"Value\":\"8.7/10\"}],\"Metascore\":\"N/A\",\"imdbRating\":\"8.7\",\"imdbVotes\":\"6736\",\"imdbID\":\"tt2161930\",\"seriesID\":\"N/A\",\"Type\":\"episode\",\"Response\":\"True\"}"; - var trip1 = JsonSerializer.Deserialize<OmdbProvider.RootObject>(Input, _options); - Assert.NotNull(trip1); - Assert.NotNull(trip1?.Title); - Assert.Null(trip1?.Awards); - Assert.Null(trip1?.Episode); - Assert.Null(trip1?.Metascore); - - var serializedTrip1 = JsonSerializer.Serialize(trip1!, _options); - var trip2 = JsonSerializer.Deserialize<OmdbProvider.RootObject>(serializedTrip1, _options); - Assert.NotNull(trip2); - Assert.NotNull(trip2?.Title); - Assert.Null(trip2?.Awards); - Assert.Null(trip2?.Episode); - Assert.Null(trip2?.Metascore); - } - } -} diff --git a/tests/Jellyfin.Common.Tests/Json/JsonStringConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonStringConverterTests.cs deleted file mode 100644 index 2b23c6705b..0000000000 --- a/tests/Jellyfin.Common.Tests/Json/JsonStringConverterTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Text.Json; -using MediaBrowser.Common.Json.Converters; -using Xunit; - -namespace Jellyfin.Common.Tests.Json -{ - public class JsonStringConverterTests - { - private readonly JsonSerializerOptions _jsonSerializerOptions = new () - { - Converters = - { - new JsonStringConverter() - } - }; - - [Theory] - [InlineData("\"test\"", "test")] - [InlineData("123", "123")] - [InlineData("123.45", "123.45")] - [InlineData("true", "true")] - [InlineData("false", "false")] - public void Deserialize_String_Valid_Success(string input, string output) - { - var deserialized = JsonSerializer.Deserialize<string>(input, _jsonSerializerOptions); - Assert.Equal(deserialized, output); - } - - [Fact] - public void Deserialize_Int32asInt32_Valid_Success() - { - const string? input = "123"; - const int output = 123; - var deserialized = JsonSerializer.Deserialize<int>(input, _jsonSerializerOptions); - Assert.Equal(deserialized, output); - } - } -} diff --git a/tests/Jellyfin.Common.Tests/Json/JsonVersionConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonVersionConverterTests.cs deleted file mode 100644 index f2cefdbf8e..0000000000 --- a/tests/Jellyfin.Common.Tests/Json/JsonVersionConverterTests.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Text.Json; -using MediaBrowser.Common.Json.Converters; -using Xunit; - -namespace Jellyfin.Common.Tests.Json -{ - public class JsonVersionConverterTests - { - private readonly JsonSerializerOptions _options; - - public JsonVersionConverterTests() - { - _options = new JsonSerializerOptions(); - _options.Converters.Add(new JsonVersionConverter()); - } - - [Fact] - public void Deserialize_Version_Success() - { - var input = "\"1.025.222\""; - var output = new Version(1, 25, 222); - var deserializedInput = JsonSerializer.Deserialize<Version>(input, _options); - Assert.Equal(output, deserializedInput); - } - - [Fact] - public void Serialize_Version_Success() - { - var input = new Version(1, 09, 59); - var output = "\"1.9.59\""; - var serializedInput = JsonSerializer.Serialize(input, _options); - Assert.Equal(output, serializedInput); - } - } -} diff --git a/tests/Jellyfin.Common.Tests/Models/GenericBodyArrayModel.cs b/tests/Jellyfin.Common.Tests/Models/GenericBodyArrayModel.cs deleted file mode 100644 index 276e1bfbe2..0000000000 --- a/tests/Jellyfin.Common.Tests/Models/GenericBodyArrayModel.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Text.Json.Serialization; -using MediaBrowser.Common.Json.Converters; - -namespace Jellyfin.Common.Tests.Models -{ - /// <summary> - /// The generic body model. - /// </summary> - /// <typeparam name="T">The value type.</typeparam> - public class GenericBodyArrayModel<T> - { - /// <summary> - /// Gets or sets the value. - /// </summary> - [SuppressMessage("Microsoft.Performance", "CA1819:Properties should not return arrays", MessageId = "Value", Justification = "Imported from ServiceStack")] - [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] - public T[] Value { get; set; } = default!; - } -} diff --git a/tests/Jellyfin.Common.Tests/Models/GenericBodyIReadOnlyListModel.cs b/tests/Jellyfin.Common.Tests/Models/GenericBodyIReadOnlyListModel.cs deleted file mode 100644 index 627454b25f..0000000000 --- a/tests/Jellyfin.Common.Tests/Models/GenericBodyIReadOnlyListModel.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; -using MediaBrowser.Common.Json.Converters; - -namespace Jellyfin.Common.Tests.Models -{ - /// <summary> - /// The generic body <c>IReadOnlyList</c> model. - /// </summary> - /// <typeparam name="T">The value type.</typeparam> - public class GenericBodyIReadOnlyListModel<T> - { - /// <summary> - /// Gets or sets the value. - /// </summary> - [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] - public IReadOnlyList<T> Value { get; set; } = default!; - } -} diff --git a/tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs b/tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs deleted file mode 100644 index 0adf098c33..0000000000 --- a/tests/Jellyfin.Controller.Tests/AlphanumComparatorTests.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Linq; -using MediaBrowser.Controller.Sorting; -using Xunit; - -namespace Jellyfin.Controller.Tests -{ - public class AlphanumComparatorTests - { - // InlineData is pre-sorted - [Theory] - [InlineData(null, "", "1", "9", "10", "a", "z")] - [InlineData("50F", "100F", "SR9", "SR100")] - [InlineData("image-1.jpg", "image-02.jpg", "image-4.jpg", "image-9.jpg", "image-10.jpg", "image-11.jpg", "image-22.jpg")] - [InlineData("Hard drive 2GB", "Hard drive 20GB")] - [InlineData("b", "e", "è", "ě", "f", "g", "k")] - [InlineData("123456789", "123456789a", "abc", "abcd")] - [InlineData("12345678912345678912345678913234567891", "123456789123456789123456789132345678912")] - [InlineData("12345678912345678912345678913234567891", "12345678912345678912345678913234567891")] - [InlineData("12345678912345678912345678913234567891", "12345678912345678912345678913234567892")] - [InlineData("12345678912345678912345678913234567891a", "12345678912345678912345678913234567891a")] - [InlineData("12345678912345678912345678913234567891a", "12345678912345678912345678913234567891b")] - public void AlphanumComparatorTest(params string?[] strings) - { - var copy = strings.Reverse().ToArray(); - Array.Sort(copy, new AlphanumComparator()); - Assert.True(strings.SequenceEqual(copy)); - } - } -} diff --git a/tests/Jellyfin.Controller.Tests/Extensions/StringExtensionsTests.cs b/tests/Jellyfin.Controller.Tests/Extensions/StringExtensionsTests.cs deleted file mode 100644 index 576c0a49b2..0000000000 --- a/tests/Jellyfin.Controller.Tests/Extensions/StringExtensionsTests.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using MediaBrowser.Controller.Extensions; -using Xunit; - -namespace Jellyfin.Controller.Extensions.Tests -{ - public class StringExtensionsTests - { - [Theory] - [InlineData("", '_', 0)] - [InlineData("___", '_', 3)] - [InlineData("test\x00", '\x00', 1)] - [InlineData("Imdb=tt0119567|Tmdb=330|TmdbCollection=328", '|', 2)] - public void ReadOnlySpan_Count_Success(string str, char needle, int count) - { - Assert.Equal(count, str.AsSpan().Count(needle)); - } - } -} diff --git a/tests/Jellyfin.Extensions.Tests/AlphanumericComparatorTests.cs b/tests/Jellyfin.Extensions.Tests/AlphanumericComparatorTests.cs new file mode 100644 index 0000000000..7730841a1c --- /dev/null +++ b/tests/Jellyfin.Extensions.Tests/AlphanumericComparatorTests.cs @@ -0,0 +1,29 @@ +using System; +using System.Linq; +using Xunit; + +namespace Jellyfin.Extensions.Tests +{ + public class AlphanumericComparatorTests + { + // InlineData is pre-sorted + [Theory] + [InlineData(null, "", "1", "9", "10", "a", "z")] + [InlineData("50F", "100F", "SR9", "SR100")] + [InlineData("image-1.jpg", "image-02.jpg", "image-4.jpg", "image-9.jpg", "image-10.jpg", "image-11.jpg", "image-22.jpg")] + [InlineData("Hard drive 2GB", "Hard drive 20GB")] + [InlineData("b", "e", "è", "ě", "f", "g", "k")] + [InlineData("123456789", "123456789a", "abc", "abcd")] + [InlineData("12345678912345678912345678913234567891", "123456789123456789123456789132345678912")] + [InlineData("12345678912345678912345678913234567891", "12345678912345678912345678913234567891")] + [InlineData("12345678912345678912345678913234567891", "12345678912345678912345678913234567892")] + [InlineData("12345678912345678912345678913234567891a", "12345678912345678912345678913234567891a")] + [InlineData("12345678912345678912345678913234567891a", "12345678912345678912345678913234567891b")] + public void AlphanumericComparatorTest(params string?[] strings) + { + var copy = strings.Reverse().ToArray(); + Array.Sort(copy, new AlphanumericComparator()); + Assert.True(strings.SequenceEqual(copy)); + } + } +} diff --git a/tests/Jellyfin.Extensions.Tests/CopyToExtensionsTests.cs b/tests/Jellyfin.Extensions.Tests/CopyToExtensionsTests.cs new file mode 100644 index 0000000000..6fdca46944 --- /dev/null +++ b/tests/Jellyfin.Extensions.Tests/CopyToExtensionsTests.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using Xunit; + +namespace Jellyfin.Extensions.Tests +{ + public static class CopyToExtensionsTests + { + public static IEnumerable<object[]> CopyTo_Valid_Correct_TestData() + { + yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, 0, new[] { 0, 1, 2, 3, 4, 5 } }; + yield return new object[] { new[] { 0, 1, 2 }, new[] { 5, 4, 3, 2, 1, 0 }, 2, new[] { 5, 4, 0, 1, 2, 0 } }; + } + + [Theory] + [MemberData(nameof(CopyTo_Valid_Correct_TestData))] + public static void CopyTo_Valid_Correct<T>(IReadOnlyList<T> source, IList<T> destination, int index, IList<T> expected) + { + source.CopyTo(destination, index); + Assert.Equal(expected, destination); + } + + public static IEnumerable<object[]> CopyTo_Invalid_ThrowsArgumentOutOfRangeException_TestData() + { + yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, -1 }; + yield return new object[] { new[] { 0, 1, 2 }, new[] { 5, 4, 3, 2, 1, 0 }, 6 }; + yield return new object[] { new[] { 0, 1, 2 }, Array.Empty<int>(), 0 }; + yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0 }, 0 }; + yield return new object[] { new[] { 0, 1, 2, 3, 4, 5 }, new[] { 0, 0, 0, 0, 0, 0 }, 1 }; + } + + [Theory] + [MemberData(nameof(CopyTo_Invalid_ThrowsArgumentOutOfRangeException_TestData))] + public static void CopyTo_Invalid_ThrowsArgumentOutOfRangeException<T>(IReadOnlyList<T> source, IList<T> destination, int index) + { + Assert.Throws<ArgumentOutOfRangeException>(() => source.CopyTo(destination, index)); + } + } +} diff --git a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj new file mode 100644 index 0000000000..4b6dca3777 --- /dev/null +++ b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj @@ -0,0 +1,38 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net5.0</TargetFramework> + <IsPackable>false</IsPackable> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <Nullable>enable</Nullable> + <AnalysisMode>AllEnabledByDefault</AnalysisMode> + <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" /> + <PackageReference Include="xunit" Version="2.4.1" /> + <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + <PrivateAssets>all</PrivateAssets> + </PackageReference> + <PackageReference Include="coverlet.collector" Version="1.3.0"> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + <PrivateAssets>all</PrivateAssets> + </PackageReference> + <PackageReference Include="FsCheck.Xunit" Version="2.15.3" /> + </ItemGroup> + + <!-- Code Analyzers --> + <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="../../MediaBrowser.Model/MediaBrowser.Model.csproj" /> + <ProjectReference Include="../../src/Jellyfin.Extensions/Jellyfin.Extensions.csproj" /> + </ItemGroup> + +</Project> diff --git a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonBoolNumberTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonBoolNumberTests.cs new file mode 100644 index 0000000000..d0e3e94568 --- /dev/null +++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonBoolNumberTests.cs @@ -0,0 +1,45 @@ +using System.Globalization; +using System.Text.Json; +using FsCheck; +using FsCheck.Xunit; +using Jellyfin.Extensions.Json.Converters; +using Xunit; + +namespace Jellyfin.Extensions.Tests.Json.Converters +{ + public class JsonBoolNumberTests + { + private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions() + { + Converters = + { + new JsonBoolNumberConverter() + } + }; + + [Theory] + [InlineData("1", true)] + [InlineData("0", false)] + [InlineData("2", true)] + [InlineData("true", true)] + [InlineData("false", false)] + public void Deserialize_Number_Valid_Success(string input, bool? output) + { + var value = JsonSerializer.Deserialize<bool>(input, _jsonOptions); + Assert.Equal(value, output); + } + + [Theory] + [InlineData(true, "true")] + [InlineData(false, "false")] + public void Serialize_Bool_Success(bool input, string output) + { + var value = JsonSerializer.Serialize(input, _jsonOptions); + Assert.Equal(value, output); + } + + [Property] + public Property Deserialize_NonZeroInt_True(NonZeroInt input) + => JsonSerializer.Deserialize<bool>(input.ToString(), _jsonOptions).ToProperty(); + } +} diff --git a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs new file mode 100644 index 0000000000..f2ca2ff081 --- /dev/null +++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedArrayTests.cs @@ -0,0 +1,142 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using Jellyfin.Extensions.Tests.Json.Models; +using MediaBrowser.Model.Session; +using Xunit; + +namespace Jellyfin.Extensions.Tests.Json.Converters +{ + public static class JsonCommaDelimitedArrayTests + { + [Fact] + public static void Deserialize_String_Null_Success() + { + var options = new JsonSerializerOptions(); + var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": null }", options); + Assert.Null(value?.Value); + } + + [Fact] + public static void Deserialize_Empty_Success() + { + var desiredValue = new GenericBodyArrayModel<string> + { + Value = Array.Empty<string>() + }; + + var options = new JsonSerializerOptions(); + var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": """" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public static void Deserialize_String_Valid_Success() + { + var desiredValue = new GenericBodyArrayModel<string> + { + Value = new[] { "a", "b", "c" } + }; + + var options = new JsonSerializerOptions(); + var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": ""a,b,c"" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public static void Deserialize_String_Space_Valid_Success() + { + var desiredValue = new GenericBodyArrayModel<string> + { + Value = new[] { "a", "b", "c" } + }; + + var options = new JsonSerializerOptions(); + var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": ""a, b, c"" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public static void Deserialize_GenericCommandType_Valid_Success() + { + var desiredValue = new GenericBodyArrayModel<GeneralCommandType> + { + Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + }; + + var options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,MoveDown"" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public static void Deserialize_GenericCommandType_EmptyEntry_Success() + { + var desiredValue = new GenericBodyArrayModel<GeneralCommandType> + { + Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + }; + + var options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,,MoveDown"" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public static void Deserialize_GenericCommandType_Invalid_Success() + { + var desiredValue = new GenericBodyArrayModel<GeneralCommandType> + { + Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + }; + + var options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,TotallyNotAVallidCommand,MoveDown"" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public static void Deserialize_GenericCommandType_Space_Valid_Success() + { + var desiredValue = new GenericBodyArrayModel<GeneralCommandType> + { + Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + }; + + var options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp, MoveDown"" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public static void Deserialize_String_Array_Valid_Success() + { + var desiredValue = new GenericBodyArrayModel<string> + { + Value = new[] { "a", "b", "c" } + }; + + var options = new JsonSerializerOptions(); + var value = JsonSerializer.Deserialize<GenericBodyArrayModel<string>>(@"{ ""Value"": [""a"",""b"",""c""] }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public static void Deserialize_GenericCommandType_Array_Valid_Success() + { + var desiredValue = new GenericBodyArrayModel<GeneralCommandType> + { + Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + }; + + var options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + var value = JsonSerializer.Deserialize<GenericBodyArrayModel<GeneralCommandType>>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + } +} diff --git a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedIReadOnlyListTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedIReadOnlyListTests.cs new file mode 100644 index 0000000000..92886dcd28 --- /dev/null +++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonCommaDelimitedIReadOnlyListTests.cs @@ -0,0 +1,92 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Jellyfin.Extensions.Tests.Json.Models; +using MediaBrowser.Model.Session; +using Xunit; + +namespace Jellyfin.Extensions.Tests.Json.Converters +{ + public static class JsonCommaDelimitedIReadOnlyListTests + { + [Fact] + public static void Deserialize_String_Valid_Success() + { + var desiredValue = new GenericBodyIReadOnlyListModel<string> + { + Value = new[] { "a", "b", "c" } + }; + + var options = new JsonSerializerOptions(); + var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": ""a,b,c"" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public static void Deserialize_String_Space_Valid_Success() + { + var desiredValue = new GenericBodyIReadOnlyListModel<string> + { + Value = new[] { "a", "b", "c" } + }; + + var options = new JsonSerializerOptions(); + var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": ""a, b, c"" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public static void Deserialize_GenericCommandType_Valid_Success() + { + var desiredValue = new GenericBodyIReadOnlyListModel<GeneralCommandType> + { + Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + }; + + var options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,MoveDown"" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public static void Deserialize_GenericCommandType_Space_Valid_Success() + { + var desiredValue = new GenericBodyIReadOnlyListModel<GeneralCommandType> + { + Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + }; + + var options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp, MoveDown"" }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public static void Deserialize_String_Array_Valid_Success() + { + var desiredValue = new GenericBodyIReadOnlyListModel<string> + { + Value = new[] { "a", "b", "c" } + }; + + var options = new JsonSerializerOptions(); + var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<string>>(@"{ ""Value"": [""a"",""b"",""c""] }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + + [Fact] + public static void Deserialize_GenericCommandType_Array_Valid_Success() + { + var desiredValue = new GenericBodyIReadOnlyListModel<GeneralCommandType> + { + Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown } + }; + + var options = new JsonSerializerOptions(); + options.Converters.Add(new JsonStringEnumConverter()); + var value = JsonSerializer.Deserialize<GenericBodyIReadOnlyListModel<GeneralCommandType>>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", options); + Assert.Equal(desiredValue.Value, value?.Value); + } + } +} diff --git a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonGuidConverterTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonGuidConverterTests.cs new file mode 100644 index 0000000000..8465d465ab --- /dev/null +++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonGuidConverterTests.cs @@ -0,0 +1,68 @@ +using System; +using System.Text.Json; +using Jellyfin.Extensions.Json.Converters; +using Xunit; + +namespace Jellyfin.Extensions.Tests.Json.Converters +{ + public class JsonGuidConverterTests + { + private readonly JsonSerializerOptions _options; + + public JsonGuidConverterTests() + { + _options = new JsonSerializerOptions(); + _options.Converters.Add(new JsonGuidConverter()); + } + + [Fact] + public void Deserialize_Valid_Success() + { + Guid value = JsonSerializer.Deserialize<Guid>(@"""a852a27afe324084ae66db579ee3ee18""", _options); + Assert.Equal(new Guid("a852a27afe324084ae66db579ee3ee18"), value); + } + + [Fact] + public void Deserialize_ValidDashed_Success() + { + Guid value = JsonSerializer.Deserialize<Guid>(@"""e9b2dcaa-529c-426e-9433-5e9981f27f2e""", _options); + Assert.Equal(new Guid("e9b2dcaa-529c-426e-9433-5e9981f27f2e"), value); + } + + [Fact] + public void Roundtrip_Valid_Success() + { + Guid guid = new Guid("a852a27afe324084ae66db579ee3ee18"); + string value = JsonSerializer.Serialize(guid, _options); + Assert.Equal(guid, JsonSerializer.Deserialize<Guid>(value, _options)); + } + + [Fact] + public void Deserialize_Null_EmptyGuid() + { + Assert.Equal(Guid.Empty, JsonSerializer.Deserialize<Guid>("null", _options)); + } + + [Fact] + public void Serialize_EmptyGuid_EmptyGuid() + { + Assert.Equal($"\"{Guid.Empty:N}\"", JsonSerializer.Serialize(Guid.Empty, _options)); + } + + [Fact] + public void Serialize_Valid_NoDash_Success() + { + var guid = new Guid("531797E9-9457-40E0-88BC-B1D6D38752FA"); + var str = JsonSerializer.Serialize(guid, _options); + Assert.Equal($"\"{guid:N}\"", str); + } + + [Fact] + public void Serialize_Nullable_Success() + { + Guid? guid = new Guid("531797E9-9457-40E0-88BC-B1D6D38752FA"); + var str = JsonSerializer.Serialize(guid, _options); + Assert.Equal($"\"{guid:N}\"", str); + } + } +} diff --git a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonLowerCaseConverterTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonLowerCaseConverterTests.cs new file mode 100644 index 0000000000..af9227de27 --- /dev/null +++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonLowerCaseConverterTests.cs @@ -0,0 +1,71 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Jellyfin.Extensions.Json.Converters; +using MediaBrowser.Model.Entities; +using Xunit; + +namespace Jellyfin.Extensions.Tests.Json.Converters +{ + public class JsonLowerCaseConverterTests + { + private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions() + { + Converters = + { + new JsonStringEnumConverter() + } + }; + + [Theory] + [InlineData(null, "{\"CollectionType\":null}")] + [InlineData(CollectionTypeOptions.Movies, "{\"CollectionType\":\"movies\"}")] + [InlineData(CollectionTypeOptions.MusicVideos, "{\"CollectionType\":\"musicvideos\"}")] + public void Serialize_CollectionTypeOptions_Correct(CollectionTypeOptions? collectionType, string expected) + { + Assert.Equal(expected, JsonSerializer.Serialize(new TestContainer(collectionType), _jsonOptions)); + } + + [Theory] + [InlineData("{\"CollectionType\":null}", null)] + [InlineData("{\"CollectionType\":\"movies\"}", CollectionTypeOptions.Movies)] + [InlineData("{\"CollectionType\":\"musicvideos\"}", CollectionTypeOptions.MusicVideos)] + public void Deserialize_CollectionTypeOptions_Correct(string json, CollectionTypeOptions? result) + { + var res = JsonSerializer.Deserialize<TestContainer>(json, _jsonOptions); + Assert.NotNull(res); + Assert.Equal(result, res!.CollectionType); + } + + [Theory] + [InlineData(null)] + [InlineData(CollectionTypeOptions.Movies)] + [InlineData(CollectionTypeOptions.MusicVideos)] + public void RoundTrip_CollectionTypeOptions_Correct(CollectionTypeOptions? value) + { + var res = JsonSerializer.Deserialize<TestContainer>(JsonSerializer.Serialize(new TestContainer(value), _jsonOptions), _jsonOptions); + Assert.NotNull(res); + Assert.Equal(value, res!.CollectionType); + } + + [Theory] + [InlineData("{\"CollectionType\":null}")] + [InlineData("{\"CollectionType\":\"movies\"}")] + [InlineData("{\"CollectionType\":\"musicvideos\"}")] + public void RoundTrip_String_Correct(string json) + { + var res = JsonSerializer.Serialize(JsonSerializer.Deserialize<TestContainer>(json, _jsonOptions), _jsonOptions); + Assert.Equal(json, res); + } + + private class TestContainer + { + public TestContainer(CollectionTypeOptions? collectionType) + { + CollectionType = collectionType; + } + + [JsonConverter(typeof(JsonLowerCaseConverter<CollectionTypeOptions?>))] + public CollectionTypeOptions? CollectionType { get; set; } + } + } +} diff --git a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonNullableGuidConverterTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonNullableGuidConverterTests.cs new file mode 100644 index 0000000000..b0dbc09e4f --- /dev/null +++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonNullableGuidConverterTests.cs @@ -0,0 +1,80 @@ +using System; +using System.Text.Json; +using Jellyfin.Extensions.Json.Converters; +using Xunit; + +namespace Jellyfin.Extensions.Tests.Json.Converters +{ + public class JsonNullableGuidConverterTests + { + private readonly JsonSerializerOptions _options; + + public JsonNullableGuidConverterTests() + { + _options = new JsonSerializerOptions(); + _options.Converters.Add(new JsonNullableGuidConverter()); + } + + [Fact] + public void Deserialize_Valid_Success() + { + Guid? value = JsonSerializer.Deserialize<Guid?>(@"""a852a27afe324084ae66db579ee3ee18""", _options); + Assert.Equal(new Guid("a852a27afe324084ae66db579ee3ee18"), value); + } + + [Fact] + public void Deserialize_ValidDashed_Success() + { + Guid? value = JsonSerializer.Deserialize<Guid?>(@"""e9b2dcaa-529c-426e-9433-5e9981f27f2e""", _options); + Assert.Equal(new Guid("e9b2dcaa-529c-426e-9433-5e9981f27f2e"), value); + } + + [Fact] + public void Roundtrip_Valid_Success() + { + Guid guid = new Guid("a852a27afe324084ae66db579ee3ee18"); + string value = JsonSerializer.Serialize(guid, _options); + Assert.Equal(guid, JsonSerializer.Deserialize<Guid?>(value, _options)); + } + + [Fact] + public void Deserialize_Null_Null() + { + Assert.Null(JsonSerializer.Deserialize<Guid?>("null", _options)); + } + + [Fact] + public void Deserialize_EmptyGuid_EmptyGuid() + { + Assert.Equal(Guid.Empty, JsonSerializer.Deserialize<Guid?>(@"""00000000-0000-0000-0000-000000000000""", _options)); + } + + [Fact] + public void Serialize_EmptyGuid_Null() + { + Assert.Equal("null", JsonSerializer.Serialize((Guid?)Guid.Empty, _options)); + } + + [Fact] + public void Serialize_Null_Null() + { + Assert.Equal("null", JsonSerializer.Serialize((Guid?)null, _options)); + } + + [Fact] + public void Serialize_Valid_NoDash_Success() + { + var guid = (Guid?)new Guid("531797E9-9457-40E0-88BC-B1D6D38752FA"); + var str = JsonSerializer.Serialize(guid, _options); + Assert.Equal($"\"{guid:N}\"", str); + } + + [Fact] + public void Serialize_Nullable_Success() + { + Guid? guid = new Guid("531797E9-9457-40E0-88BC-B1D6D38752FA"); + var str = JsonSerializer.Serialize(guid, _options); + Assert.Equal($"\"{guid:N}\"", str); + } + } +} diff --git a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonStringConverterTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonStringConverterTests.cs new file mode 100644 index 0000000000..655e070742 --- /dev/null +++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonStringConverterTests.cs @@ -0,0 +1,38 @@ +using System.Text.Json; +using Jellyfin.Extensions.Json.Converters; +using Xunit; + +namespace Jellyfin.Extensions.Tests.Json.Converters +{ + public class JsonStringConverterTests + { + private readonly JsonSerializerOptions _jsonSerializerOptions = new () + { + Converters = + { + new JsonStringConverter() + } + }; + + [Theory] + [InlineData("\"test\"", "test")] + [InlineData("123", "123")] + [InlineData("123.45", "123.45")] + [InlineData("true", "true")] + [InlineData("false", "false")] + public void Deserialize_String_Valid_Success(string input, string output) + { + var deserialized = JsonSerializer.Deserialize<string>(input, _jsonSerializerOptions); + Assert.Equal(deserialized, output); + } + + [Fact] + public void Deserialize_Int32asInt32_Valid_Success() + { + const string? input = "123"; + const int output = 123; + var deserialized = JsonSerializer.Deserialize<int>(input, _jsonSerializerOptions); + Assert.Equal(deserialized, output); + } + } +} diff --git a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonVersionConverterTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonVersionConverterTests.cs new file mode 100644 index 0000000000..5fbac7eab0 --- /dev/null +++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonVersionConverterTests.cs @@ -0,0 +1,36 @@ +using System; +using System.Text.Json; +using Jellyfin.Extensions.Json.Converters; +using Xunit; + +namespace Jellyfin.Extensions.Tests.Json.Converters +{ + public class JsonVersionConverterTests + { + private readonly JsonSerializerOptions _options; + + public JsonVersionConverterTests() + { + _options = new JsonSerializerOptions(); + _options.Converters.Add(new JsonVersionConverter()); + } + + [Fact] + public void Deserialize_Version_Success() + { + var input = "\"1.025.222\""; + var output = new Version(1, 25, 222); + var deserializedInput = JsonSerializer.Deserialize<Version>(input, _options); + Assert.Equal(output, deserializedInput); + } + + [Fact] + public void Serialize_Version_Success() + { + var input = new Version(1, 09, 59); + var output = "\"1.9.59\""; + var serializedInput = JsonSerializer.Serialize(input, _options); + Assert.Equal(output, serializedInput); + } + } +} diff --git a/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyArrayModel.cs b/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyArrayModel.cs new file mode 100644 index 0000000000..ef135278fd --- /dev/null +++ b/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyArrayModel.cs @@ -0,0 +1,20 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; +using Jellyfin.Extensions.Json.Converters; + +namespace Jellyfin.Extensions.Tests.Json.Models +{ + /// <summary> + /// The generic body model. + /// </summary> + /// <typeparam name="T">The value type.</typeparam> + public class GenericBodyArrayModel<T> + { + /// <summary> + /// Gets or sets the value. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1819:Properties should not return arrays", MessageId = "Value", Justification = "Imported from ServiceStack")] + [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + public T[] Value { get; set; } = default!; + } +} diff --git a/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyIReadOnlyListModel.cs b/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyIReadOnlyListModel.cs new file mode 100644 index 0000000000..8e7b5a35b4 --- /dev/null +++ b/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyIReadOnlyListModel.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Jellyfin.Extensions.Json.Converters; + +namespace Jellyfin.Extensions.Tests.Json.Models +{ + /// <summary> + /// The generic body <c>IReadOnlyList</c> model. + /// </summary> + /// <typeparam name="T">The value type.</typeparam> + public class GenericBodyIReadOnlyListModel<T> + { + /// <summary> + /// Gets or sets the value. + /// </summary> + [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))] + public IReadOnlyList<T> Value { get; set; } = default!; + } +} diff --git a/tests/Jellyfin.Extensions.Tests/ShuffleExtensionsTests.cs b/tests/Jellyfin.Extensions.Tests/ShuffleExtensionsTests.cs new file mode 100644 index 0000000000..c72216d94b --- /dev/null +++ b/tests/Jellyfin.Extensions.Tests/ShuffleExtensionsTests.cs @@ -0,0 +1,21 @@ +using System; +using Xunit; + +namespace Jellyfin.Extensions.Tests +{ + public static class ShuffleExtensionsTests + { + private static readonly Random _rng = new Random(); + + [Fact] + public static void Shuffle_Valid_Correct() + { + byte[] original = new byte[1 << 6]; + _rng.NextBytes(original); + byte[] shuffled = (byte[])original.Clone(); + shuffled.Shuffle(); + + Assert.NotEqual(original, shuffled); + } + } +} diff --git a/tests/Jellyfin.Extensions.Tests/StringExtensionsTests.cs b/tests/Jellyfin.Extensions.Tests/StringExtensionsTests.cs new file mode 100644 index 0000000000..d1aa2e4764 --- /dev/null +++ b/tests/Jellyfin.Extensions.Tests/StringExtensionsTests.cs @@ -0,0 +1,18 @@ +using System; +using Xunit; + +namespace Jellyfin.Extensions.Tests +{ + public class StringExtensionsTests + { + [Theory] + [InlineData("", '_', 0)] + [InlineData("___", '_', 3)] + [InlineData("test\x00", '\x00', 1)] + [InlineData("Imdb=tt0119567|Tmdb=330|TmdbCollection=328", '|', 2)] + public void ReadOnlySpan_Count_Success(string str, char needle, int count) + { + Assert.Equal(count, str.AsSpan().Count(needle)); + } + } +} diff --git a/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs index 415682e855..45808375f1 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs @@ -1,7 +1,7 @@ using System.IO; using System.Text.Json; using System.Threading.Tasks; -using MediaBrowser.Common.Json; +using Jellyfin.Extensions.Json; using MediaBrowser.MediaEncoding.Probing; using Xunit; diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs index 98fbb00d52..d8089eea2d 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs @@ -2,7 +2,7 @@ using System; using System.Globalization; using System.IO; using System.Text.Json; -using MediaBrowser.Common.Json; +using Jellyfin.Extensions.Json; using MediaBrowser.MediaEncoding.Probing; using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; diff --git a/tests/Jellyfin.Model.Tests/Entities/JsonLowerCaseConverterTests.cs b/tests/Jellyfin.Model.Tests/Entities/JsonLowerCaseConverterTests.cs deleted file mode 100644 index 955d296cc8..0000000000 --- a/tests/Jellyfin.Model.Tests/Entities/JsonLowerCaseConverterTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; -using MediaBrowser.Model.Entities; -using Xunit; - -namespace Jellyfin.Model.Tests.Entities -{ - public class JsonLowerCaseConverterTests - { - private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions() - { - Converters = - { - new JsonStringEnumConverter() - } - }; - - [Theory] - [InlineData(null, "{\"CollectionType\":null}")] - [InlineData(CollectionTypeOptions.Movies, "{\"CollectionType\":\"movies\"}")] - [InlineData(CollectionTypeOptions.MusicVideos, "{\"CollectionType\":\"musicvideos\"}")] - public void Serialize_CollectionTypeOptions_Correct(CollectionTypeOptions? collectionType, string expected) - { - Assert.Equal(expected, JsonSerializer.Serialize(new TestContainer(collectionType), _jsonOptions)); - } - - [Theory] - [InlineData("{\"CollectionType\":null}", null)] - [InlineData("{\"CollectionType\":\"movies\"}", CollectionTypeOptions.Movies)] - [InlineData("{\"CollectionType\":\"musicvideos\"}", CollectionTypeOptions.MusicVideos)] - public void Deserialize_CollectionTypeOptions_Correct(string json, CollectionTypeOptions? result) - { - var res = JsonSerializer.Deserialize<TestContainer>(json, _jsonOptions); - Assert.NotNull(res); - Assert.Equal(result, res!.CollectionType); - } - - [Theory] - [InlineData(null)] - [InlineData(CollectionTypeOptions.Movies)] - [InlineData(CollectionTypeOptions.MusicVideos)] - public void RoundTrip_CollectionTypeOptions_Correct(CollectionTypeOptions? value) - { - var res = JsonSerializer.Deserialize<TestContainer>(JsonSerializer.Serialize(new TestContainer(value), _jsonOptions), _jsonOptions); - Assert.NotNull(res); - Assert.Equal(value, res!.CollectionType); - } - - [Theory] - [InlineData("{\"CollectionType\":null}")] - [InlineData("{\"CollectionType\":\"movies\"}")] - [InlineData("{\"CollectionType\":\"musicvideos\"}")] - public void RoundTrip_String_Correct(string json) - { - var res = JsonSerializer.Serialize(JsonSerializer.Deserialize<TestContainer>(json, _jsonOptions), _jsonOptions); - Assert.Equal(json, res); - } - - private class TestContainer - { - public TestContainer(CollectionTypeOptions? collectionType) - { - CollectionType = collectionType; - } - - [JsonConverter(typeof(JsonLowerCaseConverter<CollectionTypeOptions?>))] - public CollectionTypeOptions? CollectionType { get; set; } - } - } -} diff --git a/tests/Jellyfin.Providers.Tests/Omdb/JsonOmdbConverterTests.cs b/tests/Jellyfin.Providers.Tests/Omdb/JsonOmdbConverterTests.cs new file mode 100644 index 0000000000..25900bc09e --- /dev/null +++ b/tests/Jellyfin.Providers.Tests/Omdb/JsonOmdbConverterTests.cs @@ -0,0 +1,86 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using MediaBrowser.Providers.Plugins.Omdb; +using Xunit; + +namespace Jellyfin.Providers.Tests.Omdb +{ + public class JsonOmdbConverterTests + { + private readonly JsonSerializerOptions _options; + + public JsonOmdbConverterTests() + { + _options = new JsonSerializerOptions(); + _options.Converters.Add(new JsonOmdbNotAvailableStringConverter()); + _options.Converters.Add(new JsonOmdbNotAvailableInt32Converter()); + _options.NumberHandling = JsonNumberHandling.AllowReadingFromString; + } + + [Fact] + public void Deserialize_Omdb_Response_Not_Available_Success() + { + const string Input = "{\"Title\":\"Chapter 1\",\"Year\":\"2013\",\"Rated\":\"TV-MA\",\"Released\":\"01 Feb 2013\",\"Season\":\"N/A\",\"Episode\":\"N/A\",\"Runtime\":\"55 min\",\"Genre\":\"Drama\",\"Director\":\"David Fincher\",\"Writer\":\"Michael Dobbs (based on the novels by), Andrew Davies (based on the mini-series by), Beau Willimon (created for television by), Beau Willimon, Sam Forman (staff writer)\",\"Actors\":\"Kevin Spacey, Robin Wright, Kate Mara, Corey Stoll\",\"Plot\":\"Congressman Francis Underwood has been declined the chair for Secretary of State. He's now gathering his own team to plot his revenge. Zoe Barnes, a reporter for the Washington Herald, will do anything to get her big break.\",\"Language\":\"English\",\"Country\":\"USA\",\"Awards\":\"N/A\",\"Poster\":\"https://m.media-amazon.com/images/M/MV5BMTY5MTU4NDQzNV5BMl5BanBnXkFtZTgwMzk2ODcxMzE@._V1_SX300.jpg\",\"Ratings\":[{\"Source\":\"Internet Movie Database\",\"Value\":\"8.7/10\"}],\"Metascore\":\"N/A\",\"imdbRating\":\"8.7\",\"imdbVotes\":\"6736\",\"imdbID\":\"tt2161930\",\"seriesID\":\"N/A\",\"Type\":\"episode\",\"Response\":\"True\"}"; + var seasonRootObject = JsonSerializer.Deserialize<OmdbProvider.RootObject>(Input, _options); + Assert.NotNull(seasonRootObject); + Assert.Null(seasonRootObject?.Awards); + Assert.Null(seasonRootObject?.Episode); + Assert.Null(seasonRootObject?.Metascore); + } + + [Theory] + [InlineData("\"N/A\"")] + [InlineData("null")] + public void Deserialization_To_Nullable_Int_Shoud_Be_Null(string input) + { + var result = JsonSerializer.Deserialize<int?>(input, _options); + Assert.Null(result); + } + + [Theory] + [InlineData("\"8\"", 8)] + [InlineData("8", 8)] + public void Deserialize_NullableInt_Success(string input, int? expected) + { + var result = JsonSerializer.Deserialize<int?>(input, _options); + Assert.Equal(result, expected); + } + + [Theory] + [InlineData("\"N/A\"")] + [InlineData("null")] + public void Deserialization_To_Nullable_String_Shoud_Be_Null(string input) + { + var result = JsonSerializer.Deserialize<string?>(input, _options); + Assert.Null(result); + } + + [Theory] + [InlineData("\"Jellyfin\"", "Jellyfin")] + public void Deserialize_Normal_String_Success(string input, string expected) + { + var result = JsonSerializer.Deserialize<string?>(input, _options); + Assert.Equal(expected, result); + } + + [Fact] + public void Roundtrip_Valid_Success() + { + const string Input = "{\"Title\":\"Chapter 1\",\"Year\":\"2013\",\"Rated\":\"TV-MA\",\"Released\":\"01 Feb 2013\",\"Season\":\"N/A\",\"Episode\":\"N/A\",\"Runtime\":\"55 min\",\"Genre\":\"Drama\",\"Director\":\"David Fincher\",\"Writer\":\"Michael Dobbs (based on the novels by), Andrew Davies (based on the mini-series by), Beau Willimon (created for television by), Beau Willimon, Sam Forman (staff writer)\",\"Actors\":\"Kevin Spacey, Robin Wright, Kate Mara, Corey Stoll\",\"Plot\":\"Congressman Francis Underwood has been declined the chair for Secretary of State. He's now gathering his own team to plot his revenge. Zoe Barnes, a reporter for the Washington Herald, will do anything to get her big break.\",\"Language\":\"English\",\"Country\":\"USA\",\"Awards\":\"N/A\",\"Poster\":\"https://m.media-amazon.com/images/M/MV5BMTY5MTU4NDQzNV5BMl5BanBnXkFtZTgwMzk2ODcxMzE@._V1_SX300.jpg\",\"Ratings\":[{\"Source\":\"Internet Movie Database\",\"Value\":\"8.7/10\"}],\"Metascore\":\"N/A\",\"imdbRating\":\"8.7\",\"imdbVotes\":\"6736\",\"imdbID\":\"tt2161930\",\"seriesID\":\"N/A\",\"Type\":\"episode\",\"Response\":\"True\"}"; + var trip1 = JsonSerializer.Deserialize<OmdbProvider.RootObject>(Input, _options); + Assert.NotNull(trip1); + Assert.NotNull(trip1?.Title); + Assert.Null(trip1?.Awards); + Assert.Null(trip1?.Episode); + Assert.Null(trip1?.Metascore); + + var serializedTrip1 = JsonSerializer.Serialize(trip1!, _options); + var trip2 = JsonSerializer.Deserialize<OmdbProvider.RootObject>(serializedTrip1, _options); + Assert.NotNull(trip2); + Assert.NotNull(trip2?.Title); + Assert.Null(trip2?.Awards); + Assert.Null(trip2?.Episode); + Assert.Null(trip2?.Metascore); + } + } +} diff --git a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs index ea6838682a..4ea05397dd 100644 --- a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs +++ b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs @@ -7,7 +7,7 @@ using System.Text.Json; using System.Threading.Tasks; using Jellyfin.Api.Models.StartupDtos; using Jellyfin.Api.Models.UserDtos; -using MediaBrowser.Common.Json; +using Jellyfin.Extensions.Json; using Xunit; namespace Jellyfin.Server.Integration.Tests diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs index f5411dcb8d..8273653637 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs @@ -5,7 +5,7 @@ using System.Text; using System.Text.Json; using System.Threading.Tasks; using Jellyfin.Api.Models; -using MediaBrowser.Common.Json; +using Jellyfin.Extensions.Json; using Xunit; namespace Jellyfin.Server.Integration.Tests.Controllers diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs index 169a5a6c52..9c0fc72f65 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs @@ -6,7 +6,7 @@ using System.Net.Mime; using System.Text.Json; using System.Threading.Tasks; using Jellyfin.Api.Models.StartupDtos; -using MediaBrowser.Common.Json; +using Jellyfin.Extensions.Json; using Xunit; using Xunit.Priority; diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs index 6584490de5..8866ab53cd 100644 --- a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs +++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs @@ -8,7 +8,7 @@ using System.Net.Mime; using System.Text.Json; using System.Threading.Tasks; using Jellyfin.Api.Models.UserDtos; -using MediaBrowser.Common.Json; +using Jellyfin.Extensions.Json; using MediaBrowser.Model.Dto; using Xunit; using Xunit.Priority; -- cgit v1.2.3 From 397868be95db2f705522cc975ac076e60decbf0f Mon Sep 17 00:00:00 2001 From: crobibero <cody@robibe.ro> Date: Wed, 23 Jun 2021 21:07:08 -0600 Subject: Fix issues with QuickConnect and AuthenticationDb --- .../QuickConnect/QuickConnectManager.cs | 85 +++++++++++++++++++--- .../Session/SessionManager.cs | 13 +++- Jellyfin.Api/Controllers/QuickConnectController.cs | 11 ++- Jellyfin.Api/Controllers/UserController.cs | 23 ++---- Jellyfin.Api/Models/UserDtos/QuickConnectDto.cs | 4 +- .../QuickConnect/IQuickConnect.cs | 13 +++- MediaBrowser.Controller/Session/ISessionManager.cs | 7 +- .../QuickConnect/QuickConnectResult.cs | 40 ++++++++-- 8 files changed, 148 insertions(+), 48 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs index afc08fc261..ae773c6589 100644 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.QuickConnect; using MediaBrowser.Controller.Session; using MediaBrowser.Model.QuickConnect; @@ -29,8 +30,9 @@ namespace Emby.Server.Implementations.QuickConnect /// </summary> private const int Timeout = 10; - private readonly RNGCryptoServiceProvider _rng = new(); - private readonly ConcurrentDictionary<string, QuickConnectResult> _currentRequests = new(); + private readonly RNGCryptoServiceProvider _rng = new (); + private readonly ConcurrentDictionary<string, QuickConnectResult> _currentRequests = new (); + private readonly ConcurrentDictionary<string, (DateTime Timestamp, AuthenticationResult AuthenticationResult)> _authorizedSecrets = new (); private readonly IServerConfigurationManager _config; private readonly ILogger<QuickConnectManager> _logger; @@ -68,14 +70,41 @@ namespace Emby.Server.Implementations.QuickConnect } /// <inheritdoc/> - public QuickConnectResult TryConnect() + public QuickConnectResult TryConnect(AuthorizationInfo authorizationInfo) { + if (string.IsNullOrEmpty(authorizationInfo.DeviceId)) + { + throw new ArgumentException(nameof(authorizationInfo.DeviceId) + " is required"); + } + + if (string.IsNullOrEmpty(authorizationInfo.Device)) + { + throw new ArgumentException(nameof(authorizationInfo.Device) + " is required"); + } + + if (string.IsNullOrEmpty(authorizationInfo.Client)) + { + throw new ArgumentException(nameof(authorizationInfo.Client) + " is required"); + } + + if (string.IsNullOrEmpty(authorizationInfo.Version)) + { + throw new ArgumentException(nameof(authorizationInfo.Version) + "is required"); + } + AssertActive(); ExpireRequests(); var secret = GenerateSecureRandom(); var code = GenerateCode(); - var result = new QuickConnectResult(secret, code, DateTime.UtcNow); + var result = new QuickConnectResult( + secret, + code, + DateTime.UtcNow, + authorizationInfo.DeviceId, + authorizationInfo.Device, + authorizationInfo.Client, + authorizationInfo.Version); _currentRequests[code] = result; return result; @@ -135,19 +164,41 @@ namespace Emby.Server.Implementations.QuickConnect throw new InvalidOperationException("Request is already authorized"); } - var token = Guid.NewGuid(); - result.Authentication = token; - // Change the time on the request so it expires one minute into the future. It can't expire immediately as otherwise some clients wouldn't ever see that they have been authenticated. - result.DateAdded = DateTime.Now.Add(TimeSpan.FromMinutes(1)); + result.DateAdded = DateTime.UtcNow.Add(TimeSpan.FromMinutes(1)); - await _sessionManager.AuthenticateQuickConnect(userId).ConfigureAwait(false); + var authenticationResult = await _sessionManager.AuthenticateDirect(new AuthenticationRequest + { + UserId = userId, + DeviceId = result.DeviceId, + DeviceName = result.DeviceName, + App = result.AppName, + AppVersion = result.AppVersion + }).ConfigureAwait(false); + + _authorizedSecrets[result.Secret] = (DateTime.UtcNow, authenticationResult); + result.Authenticated = true; + _currentRequests[code] = result; - _logger.LogDebug("Authorizing device with code {Code} to login as user {userId}", code, userId); + _logger.LogDebug("Authorizing device with code {Code} to login as user {UserId}", code, userId); return true; } + /// <inheritdoc/> + public AuthenticationResult GetAuthorizedRequest(string secret) + { + AssertActive(); + ExpireRequests(); + + if (!_authorizedSecrets.TryGetValue(secret, out var result)) + { + throw new ResourceNotFoundException("Unable to find request"); + } + + return result.AuthenticationResult; + } + /// <summary> /// Dispose. /// </summary> @@ -189,7 +240,7 @@ namespace Emby.Server.Implementations.QuickConnect // Expire stale connection requests foreach (var (_, currentRequest) in _currentRequests) { - if (expireAll || currentRequest.DateAdded > minTime) + if (expireAll || currentRequest.DateAdded < minTime) { var code = currentRequest.Code; _logger.LogDebug("Removing expired request {Code}", code); @@ -200,6 +251,18 @@ namespace Emby.Server.Implementations.QuickConnect } } } + + foreach (var (secret, (timestamp, _)) in _authorizedSecrets) + { + if (expireAll || timestamp < minTime) + { + _logger.LogDebug("Removing expired secret {Secret}", secret); + if (!_authorizedSecrets.TryRemove(secret, out _)) + { + _logger.LogWarning("Secret {Secret} already expired", secret); + } + } + } } } } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 29b545583a..40a346e95e 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1432,16 +1432,21 @@ namespace Emby.Server.Implementations.Session /// <summary> /// Authenticates the new session. /// </summary> - /// <param name="request">The request.</param> - /// <returns>Task{SessionInfo}.</returns> + /// <param name="request">The authenticationrequest.</param> + /// <returns>The authentication result.</returns> public Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request) { return AuthenticateNewSessionInternal(request, true); } - public Task<AuthenticationResult> AuthenticateQuickConnect(Guid userId) + /// <summary> + /// Directly authenticates the session without enforcing password. + /// </summary> + /// <param name="request">The authentication request.</param> + /// <returns>The authentication result.</returns> + public Task<AuthenticationResult> AuthenticateDirect(AuthenticationRequest request) { - return AuthenticateNewSessionInternal(new AuthenticationRequest { UserId = userId }, false); + return AuthenticateNewSessionInternal(request, false); } private async Task<AuthenticationResult> AuthenticateNewSessionInternal(AuthenticationRequest request, bool enforcePassword) diff --git a/Jellyfin.Api/Controllers/QuickConnectController.cs b/Jellyfin.Api/Controllers/QuickConnectController.cs index 56fef08a99..87b78fe93f 100644 --- a/Jellyfin.Api/Controllers/QuickConnectController.cs +++ b/Jellyfin.Api/Controllers/QuickConnectController.cs @@ -4,6 +4,7 @@ using Jellyfin.Api.Constants; using Jellyfin.Api.Helpers; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.QuickConnect; using MediaBrowser.Model.QuickConnect; using Microsoft.AspNetCore.Authorization; @@ -18,14 +19,17 @@ namespace Jellyfin.Api.Controllers public class QuickConnectController : BaseJellyfinApiController { private readonly IQuickConnect _quickConnect; + private readonly IAuthorizationContext _authContext; /// <summary> /// Initializes a new instance of the <see cref="QuickConnectController"/> class. /// </summary> /// <param name="quickConnect">Instance of the <see cref="IQuickConnect"/> interface.</param> - public QuickConnectController(IQuickConnect quickConnect) + /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param> + public QuickConnectController(IQuickConnect quickConnect, IAuthorizationContext authContext) { _quickConnect = quickConnect; + _authContext = authContext; } /// <summary> @@ -48,11 +52,12 @@ namespace Jellyfin.Api.Controllers /// <returns>A <see cref="QuickConnectResult"/> with a secret and code for future use or an error message.</returns> [HttpGet("Initiate")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult<QuickConnectResult> Initiate() + public async Task<ActionResult<QuickConnectResult>> Initiate() { try { - return _quickConnect.TryConnect(); + var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); + return _quickConnect.TryConnect(auth); } catch (AuthenticationException) { diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs index 8e2298bb7d..4263d4fe5f 100644 --- a/Jellyfin.Api/Controllers/UserController.cs +++ b/Jellyfin.Api/Controllers/UserController.cs @@ -14,6 +14,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.QuickConnect; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; @@ -38,6 +39,7 @@ namespace Jellyfin.Api.Controllers private readonly IAuthorizationContext _authContext; private readonly IServerConfigurationManager _config; private readonly ILogger _logger; + private readonly IQuickConnect _quickConnectManager; /// <summary> /// Initializes a new instance of the <see cref="UserController"/> class. @@ -49,6 +51,7 @@ namespace Jellyfin.Api.Controllers /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param> /// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="quickConnectManager">Instance of the <see cref="IQuickConnect"/> interface.</param> public UserController( IUserManager userManager, ISessionManager sessionManager, @@ -56,7 +59,8 @@ namespace Jellyfin.Api.Controllers IDeviceManager deviceManager, IAuthorizationContext authContext, IServerConfigurationManager config, - ILogger<UserController> logger) + ILogger<UserController> logger, + IQuickConnect quickConnectManager) { _userManager = userManager; _sessionManager = sessionManager; @@ -65,6 +69,7 @@ namespace Jellyfin.Api.Controllers _authContext = authContext; _config = config; _logger = logger; + _quickConnectManager = quickConnectManager; } /// <summary> @@ -228,23 +233,11 @@ namespace Jellyfin.Api.Controllers /// <returns>A <see cref="Task"/> containing an <see cref="AuthenticationRequest"/> with information about the new session.</returns> [HttpPost("AuthenticateWithQuickConnect")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task<ActionResult<AuthenticationResult>> AuthenticateWithQuickConnect([FromBody, Required] QuickConnectDto request) + public ActionResult<AuthenticationResult> AuthenticateWithQuickConnect([FromBody, Required] QuickConnectDto request) { - var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false); - try { - var authRequest = new AuthenticationRequest - { - App = auth.Client, - AppVersion = auth.Version, - DeviceId = auth.DeviceId, - DeviceName = auth.Device, - }; - - return await _sessionManager.AuthenticateQuickConnect( - authRequest, - request.Token).ConfigureAwait(false); + return _quickConnectManager.GetAuthorizedRequest(request.Secret); } catch (SecurityException e) { diff --git a/Jellyfin.Api/Models/UserDtos/QuickConnectDto.cs b/Jellyfin.Api/Models/UserDtos/QuickConnectDto.cs index c3a2d5cec2..9493c08c28 100644 --- a/Jellyfin.Api/Models/UserDtos/QuickConnectDto.cs +++ b/Jellyfin.Api/Models/UserDtos/QuickConnectDto.cs @@ -8,9 +8,9 @@ namespace Jellyfin.Api.Models.UserDtos public class QuickConnectDto { /// <summary> - /// Gets or sets the quick connect token. + /// Gets or sets the quick connect secret. /// </summary> [Required] - public string? Token { get; set; } + public string Secret { get; set; } = null!; } } diff --git a/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs b/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs index 616409533e..ec3706773a 100644 --- a/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs +++ b/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; -using MediaBrowser.Controller.Session; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.QuickConnect; namespace MediaBrowser.Controller.QuickConnect @@ -18,8 +19,9 @@ namespace MediaBrowser.Controller.QuickConnect /// <summary> /// Initiates a new quick connect request. /// </summary> + /// <param name="authorizationInfo">The initiator authorization info.</param> /// <returns>A quick connect result with tokens to proceed or throws an exception if not active.</returns> - QuickConnectResult TryConnect(); + QuickConnectResult TryConnect(AuthorizationInfo authorizationInfo); /// <summary> /// Checks the status of an individual request. @@ -35,5 +37,12 @@ namespace MediaBrowser.Controller.QuickConnect /// <param name="code">Identifying code for the request.</param> /// <returns>A boolean indicating if the authorization completed successfully.</returns> Task<bool> AuthorizeRequest(Guid userId, string code); + + /// <summary> + /// Gets the authorized request for the secret. + /// </summary> + /// <param name="secret">The secret.</param> + /// <returns>The authentication result.</returns> + AuthenticationResult GetAuthorizedRequest(string secret); } } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 8be9ff5219..88a905166f 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -273,12 +273,7 @@ namespace MediaBrowser.Controller.Session /// <returns>Task{SessionInfo}.</returns> Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request); - /// <summary> - /// Authenticates a new session with quick connect. - /// </summary> - /// <param name="userId">The user id.</param> - /// <returns>Task{SessionInfo}.</returns> - Task<AuthenticationResult> AuthenticateQuickConnect(Guid userId); + Task<AuthenticationResult> AuthenticateDirect(AuthenticationRequest request); /// <summary> /// Reports the capabilities. diff --git a/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs b/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs index d180d29860..35a82f47cd 100644 --- a/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs +++ b/MediaBrowser.Model/QuickConnect/QuickConnectResult.cs @@ -13,17 +13,32 @@ namespace MediaBrowser.Model.QuickConnect /// <param name="secret">The secret used to query the request state.</param> /// <param name="code">The code used to allow the request.</param> /// <param name="dateAdded">The time when the request was created.</param> - public QuickConnectResult(string secret, string code, DateTime dateAdded) + /// <param name="deviceId">The requesting device id.</param> + /// <param name="deviceName">The requesting device name.</param> + /// <param name="appName">The requesting app name.</param> + /// <param name="appVersion">The requesting app version.</param> + public QuickConnectResult( + string secret, + string code, + DateTime dateAdded, + string deviceId, + string deviceName, + string appName, + string appVersion) { Secret = secret; Code = code; DateAdded = dateAdded; + DeviceId = deviceId; + DeviceName = deviceName; + AppName = appName; + AppVersion = appVersion; } /// <summary> - /// Gets a value indicating whether this request is authorized. + /// Gets or sets a value indicating whether this request is authorized. /// </summary> - public bool Authenticated => Authentication != null; + public bool Authenticated { get; set; } /// <summary> /// Gets the secret value used to uniquely identify this request. Can be used to retrieve authentication information. @@ -36,9 +51,24 @@ namespace MediaBrowser.Model.QuickConnect public string Code { get; } /// <summary> - /// Gets or sets the private access token. + /// Gets the requesting device id. /// </summary> - public Guid? Authentication { get; set; } + public string DeviceId { get; } + + /// <summary> + /// Gets the requesting device name. + /// </summary> + public string DeviceName { get; } + + /// <summary> + /// Gets the requesting app name. + /// </summary> + public string AppName { get; } + + /// <summary> + /// Gets the requesting app version. + /// </summary> + public string AppVersion { get; } /// <summary> /// Gets or sets the DateTime that this request was created. -- cgit v1.2.3 From befedaf6fc70456557de71b9d1d223f669e5c22b Mon Sep 17 00:00:00 2001 From: Patrick Barron <18354464+barronpm@users.noreply.github.com> Date: Fri, 25 Jun 2021 21:13:38 -0400 Subject: Update Emby.Server.Implementations/Session/SessionManager.cs Co-authored-by: Cody Robibero <cody@robibe.ro> --- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 40a346e95e..9dada44cba 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -293,7 +293,7 @@ namespace Emby.Server.Implementations.Session try { user.LastActivityDate = activityDate; - await _userManager.UpdateUserAsync(user); + await _userManager.UpdateUserAsync(user).ConfigureAwait(false); } catch (DbUpdateConcurrencyException e) { -- cgit v1.2.3 From bbac9ff67e60d243dbd05be60abfcf13c295cd84 Mon Sep 17 00:00:00 2001 From: Patrick Barron <barronpm@gmail.com> Date: Sun, 27 Jun 2021 16:42:26 -0400 Subject: GetDeviceOptions always returns an instance of DeviceOptions --- Emby.Server.Implementations/Session/SessionManager.cs | 2 +- Jellyfin.Api/Controllers/DevicesController.cs | 6 ------ Jellyfin.Server.Implementations/Devices/DeviceManager.cs | 6 ++++-- 3 files changed, 5 insertions(+), 9 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 40a346e95e..ac730fa432 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -535,7 +535,7 @@ namespace Emby.Server.Implementations.Session } var deviceOptions = await _deviceManager.GetDeviceOptions(deviceId).ConfigureAwait(false); - if (string.IsNullOrEmpty(deviceOptions?.CustomName)) + if (string.IsNullOrEmpty(deviceOptions.CustomName)) { sessionInfo.DeviceName = deviceName; } diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs index d4c2dbba0f..26b9a854da 100644 --- a/Jellyfin.Api/Controllers/DevicesController.cs +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -107,12 +107,6 @@ namespace Jellyfin.Api.Controllers [FromQuery, Required] string id, [FromBody, Required] DeviceOptions deviceOptions) { - var existingDeviceOptions = await _deviceManager.GetDeviceOptions(id).ConfigureAwait(false); - if (existingDeviceOptions == null) - { - return NotFound(); - } - await _deviceManager.UpdateDeviceOptions(id, deviceOptions).ConfigureAwait(false); return NoContent(); } diff --git a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs index 484a53428c..b02ca4ef0a 100644 --- a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs +++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs @@ -74,13 +74,15 @@ namespace Jellyfin.Server.Implementations.Devices } /// <inheritdoc /> - public async Task<DeviceOptions?> GetDeviceOptions(string deviceId) + public async Task<DeviceOptions> GetDeviceOptions(string deviceId) { await using var dbContext = _dbProvider.CreateContext(); - return await dbContext.DeviceOptions + var deviceOptions = await dbContext.DeviceOptions .AsQueryable() .FirstOrDefaultAsync(d => d.DeviceId == deviceId) .ConfigureAwait(false); + + return deviceOptions ?? new DeviceOptions(deviceId); } /// <inheritdoc /> -- cgit v1.2.3 From 637e86478f5cca7c8ac5e17cf541dc4c6adac14e Mon Sep 17 00:00:00 2001 From: Bond_009 <bond.009@outlook.com> Date: Fri, 3 Sep 2021 18:46:34 +0200 Subject: Fix some warnings --- Emby.Server.Implementations/ApplicationHost.cs | 244 +++++++++------------ .../Collections/CollectionManager.cs | 4 +- .../Data/SqliteItemRepository.cs | 17 +- .../Data/SqliteUserDataRepository.cs | 47 ++-- Emby.Server.Implementations/Dto/DtoService.cs | 14 +- .../IO/ManagedFileSystem.cs | 6 +- .../Images/BaseDynamicImageProvider.cs | 2 +- .../Library/LibraryManager.cs | 22 +- .../Library/MusicManager.cs | 5 +- .../Library/Resolvers/BaseVideoResolver.cs | 4 +- .../Library/UserDataManager.cs | 4 +- .../LiveTv/EmbyTV/EmbyTV.cs | 26 +-- .../LiveTv/LiveTvManager.cs | 55 +++-- .../LiveTv/TunerHosts/BaseTunerHost.cs | 2 +- .../LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs | 64 +++--- .../LiveTv/TunerHosts/M3UTunerHost.cs | 22 +- Emby.Server.Implementations/Net/SocketFactory.cs | 20 +- Emby.Server.Implementations/Net/UdpSocket.cs | 4 +- .../ScheduledTasks/Tasks/DeleteCacheFileTask.cs | 4 + .../Session/SessionManager.cs | 18 +- Emby.Server.Implementations/TV/TVSeriesManager.cs | 22 +- .../Entities/CollectionFolder.cs | 3 +- MediaBrowser.Controller/IServerApplicationHost.cs | 7 - MediaBrowser.Controller/Library/ILibraryManager.cs | 6 +- .../Library/IUserDataManager.cs | 6 +- MediaBrowser.Controller/LiveTv/ILiveTvManager.cs | 2 +- MediaBrowser.Controller/LiveTv/ILiveTvService.cs | 4 +- .../Persistence/IItemRepository.cs | 8 +- MediaBrowser.Controller/Session/ISessionManager.cs | 2 +- MediaBrowser.Model/Dlna/StreamInfo.cs | 85 +++---- jellyfin.ruleset | 6 + 31 files changed, 325 insertions(+), 410 deletions(-) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 39e59a0739..3a504d2f43 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -114,6 +114,11 @@ namespace Emby.Server.Implementations /// </summary> private static readonly string[] _relevantEnvVarPrefixes = { "JELLYFIN_", "DOTNET_", "ASPNETCORE_" }; + /// <summary> + /// The disposable parts. + /// </summary> + private readonly List<IDisposable> _disposableParts = new List<IDisposable>(); + private readonly IFileSystem _fileSystemManager; private readonly IConfiguration _startupConfig; private readonly IXmlSerializer _xmlSerializer; @@ -125,6 +130,62 @@ namespace Emby.Server.Implementations private ISessionManager _sessionManager; private string[] _urlPrefixes; + /// <summary> + /// Gets or sets all concrete types. + /// </summary> + /// <value>All concrete types.</value> + private Type[] _allConcreteTypes; + + private DeviceId _deviceId; + + private bool _disposed = false; + + /// <summary> + /// Initializes a new instance of the <see cref="ApplicationHost"/> class. + /// </summary> + /// <param name="applicationPaths">Instance of the <see cref="IServerApplicationPaths"/> interface.</param> + /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param> + /// <param name="options">Instance of the <see cref="IStartupOptions"/> interface.</param> + /// <param name="startupConfig">The <see cref="IConfiguration" /> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="serviceCollection">Instance of the <see cref="IServiceCollection"/> interface.</param> + public ApplicationHost( + IServerApplicationPaths applicationPaths, + ILoggerFactory loggerFactory, + IStartupOptions options, + IConfiguration startupConfig, + IFileSystem fileSystem, + IServiceCollection serviceCollection) + { + ApplicationPaths = applicationPaths; + LoggerFactory = loggerFactory; + _startupOptions = options; + _startupConfig = startupConfig; + _fileSystemManager = fileSystem; + ServiceCollection = serviceCollection; + + Logger = LoggerFactory.CreateLogger<ApplicationHost>(); + fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); + + ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version; + ApplicationVersionString = ApplicationVersion.ToString(3); + ApplicationUserAgent = Name.Replace(' ', '-') + "/" + ApplicationVersionString; + + _xmlSerializer = new MyXmlSerializer(); + ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager); + _pluginManager = new PluginManager( + LoggerFactory.CreateLogger<PluginManager>(), + this, + ConfigurationManager.Configuration, + ApplicationPaths.PluginsPath, + ApplicationVersion); + } + + /// <summary> + /// Occurs when [has pending restart changed]. + /// </summary> + public event EventHandler HasPendingRestartChanged; + /// <summary> /// Gets a value indicating whether this instance can self restart. /// </summary> @@ -155,11 +216,6 @@ namespace Emby.Server.Implementations /// </summary> public INetworkManager NetManager { get; internal set; } - /// <summary> - /// Occurs when [has pending restart changed]. - /// </summary> - public event EventHandler HasPendingRestartChanged; - /// <summary> /// Gets a value indicating whether this instance has changes that require the entire application to restart. /// </summary> @@ -187,17 +243,6 @@ namespace Emby.Server.Implementations /// <value>The application paths.</value> protected IServerApplicationPaths ApplicationPaths { get; set; } - /// <summary> - /// Gets or sets all concrete types. - /// </summary> - /// <value>All concrete types.</value> - private Type[] _allConcreteTypes; - - /// <summary> - /// The disposable parts. - /// </summary> - private readonly List<IDisposable> _disposableParts = new List<IDisposable>(); - /// <summary> /// Gets or sets the configuration manager. /// </summary> @@ -224,47 +269,55 @@ namespace Emby.Server.Implementations /// </summary> public string PublishedServerUrl => _startupOptions.PublishedServerUrl ?? _startupConfig[UdpServer.AddressOverrideConfigKey]; + /// <inheritdoc /> + public Version ApplicationVersion { get; } + + /// <inheritdoc /> + public string ApplicationVersionString { get; } + /// <summary> - /// Initializes a new instance of the <see cref="ApplicationHost"/> class. + /// Gets the current application user agent. /// </summary> - /// <param name="applicationPaths">Instance of the <see cref="IServerApplicationPaths"/> interface.</param> - /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param> - /// <param name="options">Instance of the <see cref="IStartupOptions"/> interface.</param> - /// <param name="startupConfig">The <see cref="IConfiguration" /> interface.</param> - /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> - /// <param name="serviceCollection">Instance of the <see cref="IServiceCollection"/> interface.</param> - public ApplicationHost( - IServerApplicationPaths applicationPaths, - ILoggerFactory loggerFactory, - IStartupOptions options, - IConfiguration startupConfig, - IFileSystem fileSystem, - IServiceCollection serviceCollection) - { - ApplicationPaths = applicationPaths; - LoggerFactory = loggerFactory; - _startupOptions = options; - _startupConfig = startupConfig; - _fileSystemManager = fileSystem; - ServiceCollection = serviceCollection; + /// <value>The application user agent.</value> + public string ApplicationUserAgent { get; } - Logger = LoggerFactory.CreateLogger<ApplicationHost>(); - fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem)); + /// <summary> + /// Gets the email address for use within a comment section of a user agent field. + /// Presently used to provide contact information to MusicBrainz service. + /// </summary> + public string ApplicationUserAgentAddress => "team@jellyfin.org"; - ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version; - ApplicationVersionString = ApplicationVersion.ToString(3); - ApplicationUserAgent = Name.Replace(' ', '-') + "/" + ApplicationVersionString; + /// <summary> + /// Gets the current application name. + /// </summary> + /// <value>The application name.</value> + public string ApplicationProductName { get; } = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName; - _xmlSerializer = new MyXmlSerializer(); - ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, _xmlSerializer, _fileSystemManager); - _pluginManager = new PluginManager( - LoggerFactory.CreateLogger<PluginManager>(), - this, - ConfigurationManager.Configuration, - ApplicationPaths.PluginsPath, - ApplicationVersion); + public string SystemId + { + get + { + _deviceId ??= new DeviceId(ApplicationPaths, LoggerFactory); + + return _deviceId.Value; + } } + /// <inheritdoc/> + public string Name => ApplicationProductName; + + private CertificateInfo CertificateInfo { get; set; } + + public X509Certificate2 Certificate { get; private set; } + + /// <inheritdoc/> + public bool ListenWithHttps => Certificate != null && ConfigurationManager.GetNetworkConfiguration().EnableHttps; + + public string FriendlyName => + string.IsNullOrEmpty(ConfigurationManager.Configuration.ServerName) + ? Environment.MachineName + : ConfigurationManager.Configuration.ServerName; + /// <summary> /// Temporary function to migration network settings out of system.xml and into network.xml. /// TODO: remove at the point when a fixed migration path has been decided upon. @@ -297,45 +350,6 @@ namespace Emby.Server.Implementations .Replace(appPaths.InternalMetadataPath, appPaths.VirtualInternalMetadataPath, StringComparison.OrdinalIgnoreCase); } - /// <inheritdoc /> - public Version ApplicationVersion { get; } - - /// <inheritdoc /> - public string ApplicationVersionString { get; } - - /// <summary> - /// Gets the current application user agent. - /// </summary> - /// <value>The application user agent.</value> - public string ApplicationUserAgent { get; } - - /// <summary> - /// Gets the email address for use within a comment section of a user agent field. - /// Presently used to provide contact information to MusicBrainz service. - /// </summary> - public string ApplicationUserAgentAddress => "team@jellyfin.org"; - - /// <summary> - /// Gets the current application name. - /// </summary> - /// <value>The application name.</value> - public string ApplicationProductName { get; } = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName; - - private DeviceId _deviceId; - - public string SystemId - { - get - { - _deviceId ??= new DeviceId(ApplicationPaths, LoggerFactory); - - return _deviceId.Value; - } - } - - /// <inheritdoc/> - public string Name => ApplicationProductName; - /// <summary> /// Creates an instance of type and resolves all constructor dependencies. /// </summary> @@ -857,10 +871,6 @@ namespace Emby.Server.Implementations } } - private CertificateInfo CertificateInfo { get; set; } - - public X509Certificate2 Certificate { get; private set; } - private IEnumerable<string> GetUrlPrefixes() { var hosts = new[] { "+" }; @@ -1114,9 +1124,6 @@ namespace Emby.Server.Implementations }; } - /// <inheritdoc/> - public bool ListenWithHttps => Certificate != null && ConfigurationManager.GetNetworkConfiguration().EnableHttps; - /// <inheritdoc/> public string GetSmartApiUrl(IPAddress remoteAddr, int? port = null) { @@ -1203,14 +1210,7 @@ namespace Emby.Server.Implementations }.ToString().TrimEnd('/'); } - public string FriendlyName => - string.IsNullOrEmpty(ConfigurationManager.Configuration.ServerName) - ? Environment.MachineName - : ConfigurationManager.Configuration.ServerName; - - /// <summary> - /// Shuts down. - /// </summary> + /// <inheritdoc /> public async Task Shutdown() { if (IsShuttingDown) @@ -1248,41 +1248,7 @@ namespace Emby.Server.Implementations } } - public virtual void LaunchUrl(string url) - { - if (!CanLaunchWebBrowser) - { - throw new NotSupportedException(); - } - - var process = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = url, - UseShellExecute = true, - ErrorDialog = false - }, - EnableRaisingEvents = true - }; - process.Exited += (sender, args) => ((Process)sender).Dispose(); - - try - { - process.Start(); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error launching url: {url}", url); - throw; - } - } - - private bool _disposed = false; - - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> + /// <inheritdoc /> public void Dispose() { Dispose(true); diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index b00a519229..79ef70fffd 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -196,8 +196,8 @@ namespace Emby.Server.Implementations.Collections } /// <inheritdoc /> - public Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> ids) - => AddToCollectionAsync(collectionId, ids, true, new MetadataRefreshOptions(new DirectoryService(_fileSystem))); + public Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> itemIds) + => AddToCollectionAsync(collectionId, itemIds, true, new MetadataRefreshOptions(new DirectoryService(_fileSystem))); private async Task AddToCollectionAsync(Guid collectionId, IEnumerable<Guid> ids, bool fireEvent, MetadataRefreshOptions refreshOptions) { diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 0e6b7fb82d..30f88c7964 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1902,12 +1902,7 @@ namespace Emby.Server.Implementations.Data return result; } - /// <summary> - /// Gets chapters for an item. - /// </summary> - /// <param name="item">The item.</param> - /// <returns>IEnumerable{ChapterInfo}.</returns> - /// <exception cref="ArgumentNullException">id</exception> + /// <inheritdoc /> public List<ChapterInfo> GetChapters(BaseItem item) { CheckDisposed(); @@ -1930,13 +1925,7 @@ namespace Emby.Server.Implementations.Data } } - /// <summary> - /// Gets a single chapter for an item. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="index">The index.</param> - /// <returns>ChapterInfo.</returns> - /// <exception cref="ArgumentNullException">id</exception> + /// <inheritdoc /> public ChapterInfo GetChapter(BaseItem item, int index) { CheckDisposed(); @@ -2048,7 +2037,7 @@ namespace Emby.Server.Implementations.Data for (var i = startIndex; i < endIndex; i++) { - insertText.AppendFormat("(@ItemId, @ChapterIndex{0}, @StartPositionTicks{0}, @Name{0}, @ImagePath{0}, @ImageDateModified{0}),", i.ToString(CultureInfo.InvariantCulture)); + insertText.AppendFormat(CultureInfo.InvariantCulture, "(@ItemId, @ChapterIndex{0}, @StartPositionTicks{0}, @Name{0}, @ImagePath{0}, @ImageDateModified{0}),", i.ToString(CultureInfo.InvariantCulture)); } insertText.Length -= 1; // Remove last , diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 613d07d775..829f1de2f6 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -129,19 +129,17 @@ namespace Emby.Server.Implementations.Data return list; } - /// <summary> - /// Saves the user data. - /// </summary> - public void SaveUserData(long internalUserId, string key, UserItemData userData, CancellationToken cancellationToken) + /// <inheritdoc /> + public void SaveUserData(long userId, string key, UserItemData userData, CancellationToken cancellationToken) { if (userData == null) { throw new ArgumentNullException(nameof(userData)); } - if (internalUserId <= 0) + if (userId <= 0) { - throw new ArgumentNullException(nameof(internalUserId)); + throw new ArgumentNullException(nameof(userId)); } if (string.IsNullOrEmpty(key)) @@ -149,22 +147,23 @@ namespace Emby.Server.Implementations.Data throw new ArgumentNullException(nameof(key)); } - PersistUserData(internalUserId, key, userData, cancellationToken); + PersistUserData(userId, key, userData, cancellationToken); } - public void SaveAllUserData(long internalUserId, UserItemData[] userData, CancellationToken cancellationToken) + /// <inheritdoc /> + public void SaveAllUserData(long userId, UserItemData[] userData, CancellationToken cancellationToken) { if (userData == null) { throw new ArgumentNullException(nameof(userData)); } - if (internalUserId <= 0) + if (userId <= 0) { - throw new ArgumentNullException(nameof(internalUserId)); + throw new ArgumentNullException(nameof(userId)); } - PersistAllUserData(internalUserId, userData, cancellationToken); + PersistAllUserData(userId, userData, cancellationToken); } /// <summary> @@ -263,19 +262,19 @@ namespace Emby.Server.Implementations.Data /// <summary> /// Gets the user data. /// </summary> - /// <param name="internalUserId">The user id.</param> + /// <param name="userId">The user id.</param> /// <param name="key">The key.</param> /// <returns>Task{UserItemData}.</returns> /// <exception cref="ArgumentNullException"> /// userId /// or - /// key + /// key. /// </exception> - public UserItemData GetUserData(long internalUserId, string key) + public UserItemData GetUserData(long userId, string key) { - if (internalUserId <= 0) + if (userId <= 0) { - throw new ArgumentNullException(nameof(internalUserId)); + throw new ArgumentNullException(nameof(userId)); } if (string.IsNullOrEmpty(key)) @@ -287,7 +286,7 @@ namespace Emby.Server.Implementations.Data { using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where key =@Key and userId=@UserId")) { - statement.TryBind("@UserId", internalUserId); + statement.TryBind("@UserId", userId); statement.TryBind("@Key", key); foreach (var row in statement.ExecuteQuery()) @@ -300,7 +299,7 @@ namespace Emby.Server.Implementations.Data } } - public UserItemData GetUserData(long internalUserId, List<string> keys) + public UserItemData GetUserData(long userId, List<string> keys) { if (keys == null) { @@ -312,19 +311,19 @@ namespace Emby.Server.Implementations.Data return null; } - return GetUserData(internalUserId, keys[0]); + return GetUserData(userId, keys[0]); } /// <summary> /// Return all user-data associated with the given user. /// </summary> - /// <param name="internalUserId">The internal user id.</param> + /// <param name="userId">The internal user id.</param> /// <returns>The list of user item data.</returns> - public List<UserItemData> GetAllUserData(long internalUserId) + public List<UserItemData> GetAllUserData(long userId) { - if (internalUserId <= 0) + if (userId <= 0) { - throw new ArgumentNullException(nameof(internalUserId)); + throw new ArgumentNullException(nameof(userId)); } var list = new List<UserItemData>(); @@ -333,7 +332,7 @@ namespace Emby.Server.Implementations.Data { using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where userId=@UserId")) { - statement.TryBind("@UserId", internalUserId); + statement.TryBind("@UserId", userId); foreach (var row in statement.ExecuteQuery()) { diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 7c2d02037c..74400b5128 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -51,8 +51,6 @@ namespace Emby.Server.Implementations.Dto private readonly IMediaSourceManager _mediaSourceManager; private readonly Lazy<ILiveTvManager> _livetvManagerFactory; - private ILiveTvManager LivetvManager => _livetvManagerFactory.Value; - public DtoService( ILogger<DtoService> logger, ILibraryManager libraryManager, @@ -75,6 +73,8 @@ namespace Emby.Server.Implementations.Dto _livetvManagerFactory = livetvManagerFactory; } + private ILiveTvManager LivetvManager => _livetvManagerFactory.Value; + /// <inheritdoc /> public IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null) { @@ -507,7 +507,6 @@ namespace Emby.Server.Implementations.Dto /// </summary> /// <param name="dto">The dto.</param> /// <param name="item">The item.</param> - /// <returns>Task.</returns> private void AttachPeople(BaseItemDto dto, BaseItem item) { // Ordering by person type to ensure actors and artists are at the front. @@ -616,7 +615,6 @@ namespace Emby.Server.Implementations.Dto /// </summary> /// <param name="dto">The dto.</param> /// <param name="item">The item.</param> - /// <returns>Task.</returns> private void AttachStudios(BaseItemDto dto, BaseItem item) { dto.Studios = item.Studios @@ -1313,9 +1311,12 @@ namespace Emby.Server.Implementations.Dto var imageTags = dto.ImageTags; - while (((!(imageTags != null && imageTags.ContainsKey(ImageType.Logo)) && logoLimit > 0) || (!(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && artLimit > 0) || (!(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && thumbLimit > 0) || parent is Series) && - (parent ??= (isFirst ? GetImageDisplayParent(item, item) ?? owner : parent)) != null) + while ((!(imageTags != null && imageTags.ContainsKey(ImageType.Logo)) && logoLimit > 0) + || (!(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && artLimit > 0) + || (!(imageTags != null && imageTags.ContainsKey(ImageType.Thumb)) && thumbLimit > 0) + || parent is Series) { + parent ??= isFirst ? GetImageDisplayParent(item, item) ?? owner : parent; if (parent == null) { break; @@ -1395,7 +1396,6 @@ namespace Emby.Server.Implementations.Dto /// </summary> /// <param name="dto">The dto.</param> /// <param name="item">The item.</param> - /// <returns>Task.</returns> public void AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item) { dto.PrimaryImageAspectRatio = GetPrimaryImageAspectRatio(item); diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 7c3c7da230..2c722ff25a 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -423,7 +423,7 @@ namespace Emby.Server.Implementations.IO } } - public virtual void SetAttributes(string path, bool isHidden, bool isReadOnly) + public virtual void SetAttributes(string path, bool isHidden, bool readOnly) { if (!OperatingSystem.IsWindows()) { @@ -437,14 +437,14 @@ namespace Emby.Server.Implementations.IO return; } - if (info.IsReadOnly == isReadOnly && info.IsHidden == isHidden) + if (info.IsReadOnly == readOnly && info.IsHidden == isHidden) { return; } var attributes = File.GetAttributes(path); - if (isReadOnly) + if (readOnly) { attributes = attributes | FileAttributes.ReadOnly; } diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 833fb0b7a1..4a026fd218 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -51,7 +51,7 @@ namespace Emby.Server.Implementations.Images public int Order => 0; - protected virtual bool Supports(BaseItem _) => true; + protected virtual bool Supports(BaseItem item) => true; public async Task<ItemUpdateType> FetchAsync(T item, MetadataRefreshOptions options, CancellationToken cancellationToken) { diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index a0a6bb2929..8054beae3f 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1761,22 +1761,20 @@ namespace Emby.Server.Implementations.Library return orderedItems ?? items; } - public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<ValueTuple<string, SortOrder>> orderByList) + public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<ValueTuple<string, SortOrder>> orderBy) { var isFirst = true; IOrderedEnumerable<BaseItem> orderedItems = null; - foreach (var orderBy in orderByList) + foreach (var (name, sortOrder) in orderBy) { - var comparer = GetComparer(orderBy.Item1, user); + var comparer = GetComparer(name, user); if (comparer == null) { continue; } - var sortOrder = orderBy.Item2; - if (isFirst) { orderedItems = sortOrder == SortOrder.Descending ? items.OrderByDescending(i => i, comparer) : items.OrderBy(i => i, comparer); @@ -3076,9 +3074,9 @@ namespace Emby.Server.Implementations.Library }); } - public void AddMediaPath(string virtualFolderName, MediaPathInfo pathInfo) + public void AddMediaPath(string virtualFolderName, MediaPathInfo mediaPath) { - AddMediaPathInternal(virtualFolderName, pathInfo, true); + AddMediaPathInternal(virtualFolderName, mediaPath, true); } private void AddMediaPathInternal(string virtualFolderName, MediaPathInfo pathInfo, bool saveLibraryOptions) @@ -3131,11 +3129,11 @@ namespace Emby.Server.Implementations.Library } } - public void UpdateMediaPath(string virtualFolderName, MediaPathInfo pathInfo) + public void UpdateMediaPath(string virtualFolderName, MediaPathInfo mediaPath) { - if (pathInfo == null) + if (mediaPath == null) { - throw new ArgumentNullException(nameof(pathInfo)); + throw new ArgumentNullException(nameof(mediaPath)); } var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath; @@ -3148,9 +3146,9 @@ namespace Emby.Server.Implementations.Library var list = libraryOptions.PathInfos.ToList(); foreach (var originalPathInfo in list) { - if (string.Equals(pathInfo.Path, originalPathInfo.Path, StringComparison.Ordinal)) + if (string.Equals(mediaPath.Path, originalPathInfo.Path, StringComparison.Ordinal)) { - originalPathInfo.NetworkPath = pathInfo.NetworkPath; + originalPathInfo.NetworkPath = mediaPath.NetworkPath; break; } } diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index 06300adebc..e2f1fb0ade 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -36,9 +36,10 @@ namespace Emby.Server.Implementations.Library return list.Concat(GetInstantMixFromGenres(item.Genres, user, dtoOptions)).ToList(); } - public List<BaseItem> GetInstantMixFromArtist(MusicArtist item, User user, DtoOptions dtoOptions) + /// <inheritdoc /> + public List<BaseItem> GetInstantMixFromArtist(MusicArtist artist, User user, DtoOptions dtoOptions) { - return GetInstantMixFromGenres(item.Genres, user, dtoOptions); + return GetInstantMixFromGenres(artist.Genres, user, dtoOptions); } public List<BaseItem> GetInstantMixFromAlbum(MusicAlbum item, User user, DtoOptions dtoOptions) diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index 01e89302e9..b102b86cfb 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -21,13 +21,13 @@ namespace Emby.Server.Implementations.Library.Resolvers public abstract class BaseVideoResolver<T> : MediaBrowser.Controller.Resolvers.ItemResolver<T> where T : Video, new() { - protected readonly ILibraryManager LibraryManager; - protected BaseVideoResolver(ILibraryManager libraryManager) { LibraryManager = libraryManager; } + protected ILibraryManager LibraryManager { get; } + /// <summary> /// Resolves the specified args. /// </summary> diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index 8aa605a903..c4e230f211 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -177,6 +177,7 @@ namespace Emby.Server.Implementations.Library return dto; } + /// <inheritdoc /> public UserItemDataDto GetUserDataDto(BaseItem item, BaseItemDto itemDto, User user, DtoOptions options) { var userData = GetUserData(user, item); @@ -191,7 +192,7 @@ namespace Emby.Server.Implementations.Library /// </summary> /// <param name="data">The data.</param> /// <returns>DtoUserItemData.</returns> - /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="ArgumentNullException"><paramref name="data"/> is <c>null</c>.</exception> private UserItemDataDto GetUserItemDataDto(UserItemData data) { if (data == null) @@ -212,6 +213,7 @@ namespace Emby.Server.Implementations.Library }; } + /// <inheritdoc /> public bool UpdatePlayState(BaseItem item, UserItemData data, long? reportedPositionTicks) { var playedToCompletion = false; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index f2b9f3cb90..026b6bc0bb 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -610,11 +610,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV throw new NotImplementedException(); } - public Task<string> CreateTimer(TimerInfo timer, CancellationToken cancellationToken) + public Task<string> CreateTimer(TimerInfo info, CancellationToken cancellationToken) { - var existingTimer = string.IsNullOrWhiteSpace(timer.ProgramId) ? + var existingTimer = string.IsNullOrWhiteSpace(info.ProgramId) ? null : - _timerProvider.GetTimerByProgramId(timer.ProgramId); + _timerProvider.GetTimerByProgramId(info.ProgramId); if (existingTimer != null) { @@ -632,32 +632,32 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - timer.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); + info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); LiveTvProgram programInfo = null; - if (!string.IsNullOrWhiteSpace(timer.ProgramId)) + if (!string.IsNullOrWhiteSpace(info.ProgramId)) { - programInfo = GetProgramInfoFromCache(timer); + programInfo = GetProgramInfoFromCache(info); } if (programInfo == null) { - _logger.LogInformation("Unable to find program with Id {0}. Will search using start date", timer.ProgramId); - programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate); + _logger.LogInformation("Unable to find program with Id {0}. Will search using start date", info.ProgramId); + programInfo = GetProgramInfoFromCache(info.ChannelId, info.StartDate); } if (programInfo != null) { - CopyProgramInfoToTimerInfo(programInfo, timer); + CopyProgramInfoToTimerInfo(programInfo, info); } - timer.IsManual = true; - _timerProvider.Add(timer); + info.IsManual = true; + _timerProvider.Add(info); - TimerCreated?.Invoke(this, new GenericEventArgs<TimerInfo>(timer)); + TimerCreated?.Invoke(this, new GenericEventArgs<TimerInfo>(info)); - return Task.FromResult(timer.Id); + return Task.FromResult(info.Id); } public async Task<string> CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index ce585d0fb5..ea1a28fe86 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -65,6 +65,8 @@ namespace Emby.Server.Implementations.LiveTv private ITunerHost[] _tunerHosts = Array.Empty<ITunerHost>(); private IListingsProvider[] _listingProviders = Array.Empty<IListingsProvider>(); + private bool _disposed = false; + public LiveTvManager( IServerConfigurationManager config, ILogger<LiveTvManager> logger, @@ -520,7 +522,7 @@ namespace Emby.Server.Implementations.LiveTv return item; } - private Tuple<LiveTvProgram, bool, bool> GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel, ChannelType channelType, string serviceName, CancellationToken cancellationToken) + private (LiveTvProgram item, bool isNew, bool isUpdated) GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel) { var id = _tvDtoService.GetInternalProgramId(info.Id); @@ -559,8 +561,6 @@ namespace Emby.Server.Implementations.LiveTv item.ParentId = channel.Id; - // item.ChannelType = channelType; - item.Audio = info.Audio; item.ChannelId = channel.Id; item.CommunityRating ??= info.CommunityRating; @@ -772,7 +772,7 @@ namespace Emby.Server.Implementations.LiveTv item.OnMetadataChanged(); } - return new Tuple<LiveTvProgram, bool, bool>(item, isNew, isUpdated); + return (item, isNew, isUpdated); } public async Task<BaseItemDto> GetProgram(string id, CancellationToken cancellationToken, User user = null) @@ -1187,14 +1187,14 @@ namespace Emby.Server.Implementations.LiveTv foreach (var program in channelPrograms) { - var programTuple = GetProgram(program, existingPrograms, currentChannel, currentChannel.ChannelType, service.Name, cancellationToken); - var programItem = programTuple.Item1; + var programTuple = GetProgram(program, existingPrograms, currentChannel); + var programItem = programTuple.item; - if (programTuple.Item2) + if (programTuple.isNew) { newPrograms.Add(programItem); } - else if (programTuple.Item3) + else if (programTuple.isUpdated) { updatedPrograms.Add(programItem); } @@ -1385,10 +1385,10 @@ namespace Emby.Server.Implementations.LiveTv // var items = allActivePaths.Select(i => _libraryManager.FindByPath(i, false)).Where(i => i != null).ToArray(); // return new QueryResult<BaseItem> - //{ + // { // Items = items, // TotalRecordCount = items.Length - //}; + // }; dtoOptions.Fields = dtoOptions.Fields.Concat(new[] { ItemFields.Tags }).Distinct().ToArray(); } @@ -1425,16 +1425,15 @@ namespace Emby.Server.Implementations.LiveTv return result; } - public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> tuples, IReadOnlyList<ItemFields> fields, User user = null) + public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null) { var programTuples = new List<Tuple<BaseItemDto, string, string>>(); var hasChannelImage = fields.Contains(ItemFields.ChannelImage); var hasChannelInfo = fields.Contains(ItemFields.ChannelInfo); - foreach (var tuple in tuples) + foreach (var (item, dto) in programs) { - var program = (LiveTvProgram)tuple.Item1; - var dto = tuple.Item2; + var program = (LiveTvProgram)item; dto.StartDate = program.StartDate; dto.EpisodeTitle = program.EpisodeTitle; @@ -1871,11 +1870,11 @@ namespace Emby.Server.Implementations.LiveTv return _libraryManager.GetItemById(internalChannelId); } - public void AddChannelInfo(IReadOnlyCollection<(BaseItemDto, LiveTvChannel)> tuples, DtoOptions options, User user) + public void AddChannelInfo(IReadOnlyCollection<(BaseItemDto, LiveTvChannel)> items, DtoOptions options, User user) { var now = DateTime.UtcNow; - var channelIds = tuples.Select(i => i.Item2.Id).Distinct().ToArray(); + var channelIds = items.Select(i => i.Item2.Id).Distinct().ToArray(); var programs = options.AddCurrentProgram ? _libraryManager.GetItemList(new InternalItemsQuery(user) { @@ -1896,7 +1895,7 @@ namespace Emby.Server.Implementations.LiveTv var addCurrentProgram = options.AddCurrentProgram; - foreach (var tuple in tuples) + foreach (var tuple in items) { var dto = tuple.Item1; var channel = tuple.Item2; @@ -2118,17 +2117,13 @@ namespace Emby.Server.Implementations.LiveTv }; } - /// <summary> - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// </summary> + /// <inheritdoc /> public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } - private bool _disposed = false; - /// <summary> /// Releases unmanaged and - optionally - managed resources. /// </summary> @@ -2324,20 +2319,20 @@ namespace Emby.Server.Implementations.LiveTv _taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>(); } - public async Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelId, string providerChannelId) + public async Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelNumber, string providerChannelNumber) { var config = GetConfiguration(); var listingsProviderInfo = config.ListingProviders.First(i => string.Equals(providerId, i.Id, StringComparison.OrdinalIgnoreCase)); - listingsProviderInfo.ChannelMappings = listingsProviderInfo.ChannelMappings.Where(i => !string.Equals(i.Name, tunerChannelId, StringComparison.OrdinalIgnoreCase)).ToArray(); + listingsProviderInfo.ChannelMappings = listingsProviderInfo.ChannelMappings.Where(i => !string.Equals(i.Name, tunerChannelNumber, StringComparison.OrdinalIgnoreCase)).ToArray(); - if (!string.Equals(tunerChannelId, providerChannelId, StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(tunerChannelNumber, providerChannelNumber, StringComparison.OrdinalIgnoreCase)) { var list = listingsProviderInfo.ChannelMappings.ToList(); list.Add(new NameValuePair { - Name = tunerChannelId, - Value = providerChannelId + Name = tunerChannelNumber, + Value = providerChannelNumber }); listingsProviderInfo.ChannelMappings = list.ToArray(); } @@ -2357,10 +2352,10 @@ namespace Emby.Server.Implementations.LiveTv _taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>(); - return tunerChannelMappings.First(i => string.Equals(i.Id, tunerChannelId, StringComparison.OrdinalIgnoreCase)); + return tunerChannelMappings.First(i => string.Equals(i.Id, tunerChannelNumber, StringComparison.OrdinalIgnoreCase)); } - public TunerChannelMapping GetTunerChannelMapping(ChannelInfo tunerChannel, NameValuePair[] mappings, List<ChannelInfo> epgChannels) + public TunerChannelMapping GetTunerChannelMapping(ChannelInfo tunerChannel, NameValuePair[] mappings, List<ChannelInfo> providerChannels) { var result = new TunerChannelMapping { @@ -2373,7 +2368,7 @@ namespace Emby.Server.Implementations.LiveTv result.Name = tunerChannel.Number + " " + result.Name; } - var providerChannel = EmbyTV.EmbyTV.Current.GetEpgChannelFromTunerChannel(mappings, tunerChannel, epgChannels); + var providerChannel = EmbyTV.EmbyTV.Current.GetEpgChannelFromTunerChannel(mappings, tunerChannel, providerChannels); if (providerChannel != null) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index 5941613cf9..9bff0861bb 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -158,7 +158,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return new List<MediaSourceInfo>(); } - protected abstract Task<ILiveStream> GetChannelStream(TunerHostInfo tuner, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken); + protected abstract Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken); public async Task<ILiveStream> GetChannelStream(string channelId, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 011748d1d6..2bd12a9c8f 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -95,17 +95,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public bool IsLegacyTuner { get; set; } } - protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken) + protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken) { - var lineup = await GetLineup(info, cancellationToken).ConfigureAwait(false); + var lineup = await GetLineup(tuner, cancellationToken).ConfigureAwait(false); return lineup.Select(i => new HdHomerunChannelInfo { Name = i.GuideName, Number = i.GuideNumber, - Id = GetChannelId(info, i), + Id = GetChannelId(tuner, i), IsFavorite = i.Favorite, - TunerHostId = info.Id, + TunerHostId = tuner.Id, IsHD = i.HD, AudioCodec = i.AudioCodec, VideoCodec = i.VideoCodec, @@ -496,57 +496,53 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return mediaSource; } - protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, ChannelInfo channelInfo, CancellationToken cancellationToken) + protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo tuner, ChannelInfo channel, CancellationToken cancellationToken) { var list = new List<MediaSourceInfo>(); - var channelId = channelInfo.Id; + var channelId = channel.Id; var hdhrId = GetHdHrIdFromChannelId(channelId); - var hdHomerunChannelInfo = channelInfo as HdHomerunChannelInfo; - - var isLegacyTuner = hdHomerunChannelInfo != null && hdHomerunChannelInfo.IsLegacyTuner; - - if (isLegacyTuner) + if (channel is HdHomerunChannelInfo hdHomerunChannelInfo && hdHomerunChannelInfo.IsLegacyTuner) { - list.Add(GetMediaSource(info, hdhrId, channelInfo, "native")); + list.Add(GetMediaSource(tuner, hdhrId, channel, "native")); } else { - var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); + var modelInfo = await GetModelInfo(tuner, false, cancellationToken).ConfigureAwait(false); if (modelInfo != null && modelInfo.SupportsTranscoding) { - if (info.AllowHWTranscoding) + if (tuner.AllowHWTranscoding) { - list.Add(GetMediaSource(info, hdhrId, channelInfo, "heavy")); + list.Add(GetMediaSource(tuner, hdhrId, channel, "heavy")); - list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet540")); - list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet480")); - list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet360")); - list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet240")); - list.Add(GetMediaSource(info, hdhrId, channelInfo, "mobile")); + list.Add(GetMediaSource(tuner, hdhrId, channel, "internet540")); + list.Add(GetMediaSource(tuner, hdhrId, channel, "internet480")); + list.Add(GetMediaSource(tuner, hdhrId, channel, "internet360")); + list.Add(GetMediaSource(tuner, hdhrId, channel, "internet240")); + list.Add(GetMediaSource(tuner, hdhrId, channel, "mobile")); } - list.Add(GetMediaSource(info, hdhrId, channelInfo, "native")); + list.Add(GetMediaSource(tuner, hdhrId, channel, "native")); } if (list.Count == 0) { - list.Add(GetMediaSource(info, hdhrId, channelInfo, "native")); + list.Add(GetMediaSource(tuner, hdhrId, channel, "native")); } } return list; } - protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo info, ChannelInfo channelInfo, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken) + protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken) { - var tunerCount = info.TunerCount; + var tunerCount = tunerHost.TunerCount; if (tunerCount > 0) { - var tunerHostId = info.Id; + var tunerHostId = tunerHost.Id; var liveStreams = currentLiveStreams.Where(i => string.Equals(i.TunerHostId, tunerHostId, StringComparison.OrdinalIgnoreCase)); if (liveStreams.Count() >= tunerCount) @@ -557,26 +553,26 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun var profile = streamId.Split('_')[0]; - Logger.LogInformation("GetChannelStream: channel id: {0}. stream id: {1} profile: {2}", channelInfo.Id, streamId, profile); + Logger.LogInformation("GetChannelStream: channel id: {0}. stream id: {1} profile: {2}", channel.Id, streamId, profile); - var hdhrId = GetHdHrIdFromChannelId(channelInfo.Id); + var hdhrId = GetHdHrIdFromChannelId(channel.Id); - var hdhomerunChannel = channelInfo as HdHomerunChannelInfo; + var hdhomerunChannel = channel as HdHomerunChannelInfo; - var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); + var modelInfo = await GetModelInfo(tunerHost, false, cancellationToken).ConfigureAwait(false); if (!modelInfo.SupportsTranscoding) { profile = "native"; } - var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile); + var mediaSource = GetMediaSource(tunerHost, hdhrId, channel, profile); if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner) { return new HdHomerunUdpStream( mediaSource, - info, + tunerHost, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Path), modelInfo.TunerCount, @@ -592,7 +588,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun { mediaSource.Protocol = MediaProtocol.Http; - var httpUrl = channelInfo.Path; + var httpUrl = channel.Path; // If raw was used, the tuner doesn't support params if (!string.IsNullOrWhiteSpace(profile) && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase)) @@ -604,7 +600,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return new SharedHttpStream( mediaSource, - info, + tunerHost, streamId, FileSystem, _httpClientFactory, @@ -616,7 +612,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun return new HdHomerunUdpStream( mediaSource, - info, + tunerHost, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number, profile), modelInfo.TunerCount, diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 8fa6f5ad68..08b9260b98 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -71,12 +71,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return ChannelIdPrefix + info.Url.GetMD5().ToString("N", CultureInfo.InvariantCulture); } - protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken) + protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken) { - var channelIdPrefix = GetFullChannelIdPrefix(info); + var channelIdPrefix = GetFullChannelIdPrefix(tuner); return await new M3uParser(Logger, _httpClientFactory) - .Parse(info, channelIdPrefix, cancellationToken) + .Parse(tuner, channelIdPrefix, cancellationToken) .ConfigureAwait(false); } @@ -96,13 +96,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return Task.FromResult(list); } - protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo info, ChannelInfo channelInfo, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken) + protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo tunerHost, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken) { - var tunerCount = info.TunerCount; + var tunerCount = tunerHost.TunerCount; if (tunerCount > 0) { - var tunerHostId = info.Id; + var tunerHostId = tunerHost.Id; var liveStreams = currentLiveStreams.Where(i => string.Equals(i.TunerHostId, tunerHostId, StringComparison.OrdinalIgnoreCase)); if (liveStreams.Count() >= tunerCount) @@ -111,7 +111,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } } - var sources = await GetChannelStreamMediaSources(info, channelInfo, cancellationToken).ConfigureAwait(false); + var sources = await GetChannelStreamMediaSources(tunerHost, channel, cancellationToken).ConfigureAwait(false); var mediaSource = sources[0]; @@ -121,11 +121,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) { - return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper); + return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper); } } - return new LiveStream(mediaSource, info, FileSystem, Logger, Config, _streamHelper); + return new LiveStream(mediaSource, tunerHost, FileSystem, Logger, Config, _streamHelper); } public async Task Validate(TunerHostInfo info) @@ -135,9 +135,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } } - protected override Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, ChannelInfo channelInfo, CancellationToken cancellationToken) + protected override Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo tuner, ChannelInfo channel, CancellationToken cancellationToken) { - return Task.FromResult(new List<MediaSourceInfo> { CreateMediaSourceInfo(info, channelInfo) }); + return Task.FromResult(new List<MediaSourceInfo> { CreateMediaSourceInfo(tuner, channel) }); } protected virtual MediaSourceInfo CreateMediaSourceInfo(TunerHostInfo info, ChannelInfo channel) diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs index 1377286168..6d0c8731e9 100644 --- a/Emby.Server.Implementations/Net/SocketFactory.cs +++ b/Emby.Server.Implementations/Net/SocketFactory.cs @@ -11,6 +11,7 @@ namespace Emby.Server.Implementations.Net { public class SocketFactory : ISocketFactory { + /// <inheritdoc /> public ISocket CreateUdpBroadcastSocket(int localPort) { if (localPort < 0) @@ -35,11 +36,8 @@ namespace Emby.Server.Implementations.Net } } - /// <summary> - /// Creates a new UDP acceptSocket that is a member of the SSDP multicast local admin group and binds it to the specified local port. - /// </summary> - /// <returns>An implementation of the <see cref="ISocket"/> interface used by RSSDP components to perform acceptSocket operations.</returns> - public ISocket CreateSsdpUdpSocket(IPAddress localIpAddress, int localPort) + /// <inheritdoc /> + public ISocket CreateSsdpUdpSocket(IPAddress localIp, int localPort) { if (localPort < 0) { @@ -53,8 +51,8 @@ namespace Emby.Server.Implementations.Net retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4); - retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), localIpAddress)); - return new UdpSocket(retVal, localPort, localIpAddress); + retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), localIp)); + return new UdpSocket(retVal, localPort, localIp); } catch { @@ -64,13 +62,7 @@ namespace Emby.Server.Implementations.Net } } - /// <summary> - /// Creates a new UDP acceptSocket that is a member of the specified multicast IP address, and binds it to the specified local port. - /// </summary> - /// <param name="ipAddress">The multicast IP address to make the acceptSocket a member of.</param> - /// <param name="multicastTimeToLive">The multicast time to live value for the acceptSocket.</param> - /// <param name="localPort">The number of the local port to bind to.</param> - /// <returns></returns> + /// <inheritdoc /> public ISocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort) { if (ipAddress == null) diff --git a/Emby.Server.Implementations/Net/UdpSocket.cs b/Emby.Server.Implementations/Net/UdpSocket.cs index a8b18d2925..9b799e854d 100644 --- a/Emby.Server.Implementations/Net/UdpSocket.cs +++ b/Emby.Server.Implementations/Net/UdpSocket.cs @@ -191,7 +191,7 @@ namespace Emby.Server.Implementations.Net return taskCompletion.Task; } - public Task SendToAsync(byte[] buffer, int offset, int size, IPEndPoint endPoint, CancellationToken cancellationToken) + public Task SendToAsync(byte[] buffer, int offset, int bytes, IPEndPoint endPoint, CancellationToken cancellationToken) { ThrowIfDisposed(); @@ -214,7 +214,7 @@ namespace Emby.Server.Implementations.Net } }; - var result = BeginSendTo(buffer, offset, size, endPoint, new AsyncCallback(callback), null); + var result = BeginSendTo(buffer, offset, bytes, endPoint, new AsyncCallback(callback), null); if (result.CompletedSynchronously) { diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs index 692d1667d2..a575b260cb 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs @@ -29,6 +29,10 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// <summary> /// Initializes a new instance of the <see cref="DeleteCacheFileTask" /> class. /// </summary> + /// <param name="appPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> public DeleteCacheFileTask( IApplicationPaths appPaths, ILogger<DeleteCacheFileTask> logger, diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index ea710013e9..4111590c83 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -235,12 +235,12 @@ namespace Emby.Server.Implementations.Session } /// <inheritdoc /> - public void UpdateDeviceName(string sessionId, string deviceName) + public void UpdateDeviceName(string sessionId, string reportedDeviceName) { var session = GetSession(sessionId); if (session != null) { - session.DeviceName = deviceName; + session.DeviceName = reportedDeviceName; } } @@ -316,14 +316,14 @@ namespace Emby.Server.Implementations.Session } /// <inheritdoc /> - public void OnSessionControllerConnected(SessionInfo info) + public void OnSessionControllerConnected(SessionInfo session) { EventHelper.QueueEventIfNotNull( SessionControllerConnected, this, new SessionEventArgs { - SessionInfo = info + SessionInfo = session }, _logger); } @@ -1581,16 +1581,16 @@ namespace Emby.Server.Implementations.Session } /// <inheritdoc /> - public async Task Logout(Device existing) + public async Task Logout(Device device) { CheckDisposed(); - _logger.LogInformation("Logging out access token {0}", existing.AccessToken); + _logger.LogInformation("Logging out access token {0}", device.AccessToken); - await _deviceManager.DeleteDevice(existing).ConfigureAwait(false); + await _deviceManager.DeleteDevice(device).ConfigureAwait(false); var sessions = Sessions - .Where(i => string.Equals(i.DeviceId, existing.DeviceId, StringComparison.OrdinalIgnoreCase)) + .Where(i => string.Equals(i.DeviceId, device.DeviceId, StringComparison.OrdinalIgnoreCase)) .ToList(); foreach (var session in sessions) @@ -1601,7 +1601,7 @@ namespace Emby.Server.Implementations.Session } catch (Exception ex) { - _logger.LogError("Error reporting session ended", ex); + _logger.LogError(ex, "Error reporting session ended"); } } } diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index af453d1480..4d990c8718 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -33,9 +33,9 @@ namespace Emby.Server.Implementations.TV _configurationManager = configurationManager; } - public QueryResult<BaseItem> GetNextUp(NextUpQuery request, DtoOptions dtoOptions) + public QueryResult<BaseItem> GetNextUp(NextUpQuery query, DtoOptions options) { - var user = _userManager.GetUserById(request.UserId); + var user = _userManager.GetUserById(query.UserId); if (user == null) { @@ -43,9 +43,9 @@ namespace Emby.Server.Implementations.TV } string presentationUniqueKey = null; - if (!string.IsNullOrEmpty(request.SeriesId)) + if (!string.IsNullOrEmpty(query.SeriesId)) { - if (_libraryManager.GetItemById(request.SeriesId) is Series series) + if (_libraryManager.GetItemById(query.SeriesId) is Series series) { presentationUniqueKey = GetUniqueSeriesKey(series); } @@ -53,14 +53,14 @@ namespace Emby.Server.Implementations.TV if (!string.IsNullOrEmpty(presentationUniqueKey)) { - return GetResult(GetNextUpEpisodes(request, user, new[] { presentationUniqueKey }, dtoOptions), request); + return GetResult(GetNextUpEpisodes(query, user, new[] { presentationUniqueKey }, options), query); } BaseItem[] parents; - if (request.ParentId.HasValue) + if (query.ParentId.HasValue) { - var parent = _libraryManager.GetItemById(request.ParentId.Value); + var parent = _libraryManager.GetItemById(query.ParentId.Value); if (parent != null) { @@ -79,10 +79,10 @@ namespace Emby.Server.Implementations.TV .ToArray(); } - return GetNextUp(request, parents, dtoOptions); + return GetNextUp(query, parents, options); } - public QueryResult<BaseItem> GetNextUp(NextUpQuery request, BaseItem[] parentsFolders, DtoOptions dtoOptions) + public QueryResult<BaseItem> GetNextUp(NextUpQuery request, BaseItem[] parentsFolders, DtoOptions options) { var user = _userManager.GetUserById(request.UserId); @@ -104,7 +104,7 @@ namespace Emby.Server.Implementations.TV if (!string.IsNullOrEmpty(presentationUniqueKey)) { - return GetResult(GetNextUpEpisodes(request, user, new[] { presentationUniqueKey }, dtoOptions), request); + return GetResult(GetNextUpEpisodes(request, user, new[] { presentationUniqueKey }, options), request); } if (limit.HasValue) @@ -128,7 +128,7 @@ namespace Emby.Server.Implementations.TV .Select(GetUniqueSeriesKey); // Avoid implicitly captured closure - var episodes = GetNextUpEpisodes(request, user, items, dtoOptions); + var episodes = GetNextUpEpisodes(request, user, items, options); return GetResult(episodes, request); } diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 0fb4771dd3..7dc7f774d0 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -97,8 +97,7 @@ namespace MediaBrowser.Controller.Entities { try { - var result = XmlSerializer.DeserializeFromFile(typeof(LibraryOptions), GetLibraryOptionsPath(path)) as LibraryOptions; - if (result == null) + if (XmlSerializer.DeserializeFromFile(typeof(LibraryOptions), GetLibraryOptionsPath(path)) is not LibraryOptions result) { return new LibraryOptions(); } diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 753c18bc7e..b0abca367c 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -104,13 +104,6 @@ namespace MediaBrowser.Controller /// <returns>The API URL.</returns> string GetLocalApiUrl(string hostname, string scheme = null, int? port = null); - /// <summary> - /// Open a URL in an external browser window. - /// </summary> - /// <param name="url">The URL to open.</param> - /// <exception cref="NotSupportedException"><see cref="CanLaunchWebBrowser"/> is false.</exception> - void LaunchUrl(string url); - IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo(); string ExpandVirtualPath(string path); diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 604960d8bc..d40e56c7d9 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -595,11 +595,11 @@ namespace MediaBrowser.Controller.Library Task RemoveVirtualFolder(string name, bool refreshLibrary); - void AddMediaPath(string virtualFolderName, MediaPathInfo path); + void AddMediaPath(string virtualFolderName, MediaPathInfo mediaPath); - void UpdateMediaPath(string virtualFolderName, MediaPathInfo path); + void UpdateMediaPath(string virtualFolderName, MediaPathInfo mediaPath); - void RemoveMediaPath(string virtualFolderName, string path); + void RemoveMediaPath(string virtualFolderName, string mediaPath); QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query); diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs index cf35b48dba..034c405910 100644 --- a/MediaBrowser.Controller/Library/IUserDataManager.cs +++ b/MediaBrowser.Controller/Library/IUserDataManager.cs @@ -47,7 +47,7 @@ namespace MediaBrowser.Controller.Library /// <returns>User data dto.</returns> UserItemDataDto GetUserDataDto(BaseItem item, User user); - UserItemDataDto GetUserDataDto(BaseItem item, BaseItemDto itemDto, User user, DtoOptions dto_options); + UserItemDataDto GetUserDataDto(BaseItem item, BaseItemDto itemDto, User user, DtoOptions options); /// <summary> /// Get all user data for the given user. @@ -69,8 +69,8 @@ namespace MediaBrowser.Controller.Library /// </summary> /// <param name="item">Item to update.</param> /// <param name="data">Data to update.</param> - /// <param name="positionTicks">New playstate.</param> + /// <param name="reportedPositionTicks">New playstate.</param> /// <returns>True if playstate was updated.</returns> - bool UpdatePlayState(BaseItem item, UserItemData data, long? positionTicks); + bool UpdatePlayState(BaseItem item, UserItemData data, long? reportedPositionTicks); } } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index bd097c90ac..dbd18165db 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -274,7 +274,7 @@ namespace MediaBrowser.Controller.LiveTv Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelNumber, string providerChannelNumber); - TunerChannelMapping GetTunerChannelMapping(ChannelInfo channel, NameValuePair[] mappings, List<ChannelInfo> providerChannels); + TunerChannelMapping GetTunerChannelMapping(ChannelInfo tunerChannel, NameValuePair[] mappings, List<ChannelInfo> providerChannels); /// <summary> /// Gets the lineups. diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs index 897f263f3d..ce34954e3f 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs @@ -70,10 +70,10 @@ namespace MediaBrowser.Controller.LiveTv /// <summary> /// Updates the timer asynchronous. /// </summary> - /// <param name="info">The information.</param> + /// <param name="updatedTimer">The updated timer information.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> - Task UpdateTimerAsync(TimerInfo info, CancellationToken cancellationToken); + Task UpdateTimerAsync(TimerInfo updatedTimer, CancellationToken cancellationToken); /// <summary> /// Updates the series timer asynchronous. diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index 0a9073e7f5..a084f91969 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -49,17 +49,17 @@ namespace MediaBrowser.Controller.Persistence /// <summary> /// Gets chapters for an item. /// </summary> - /// <param name="id">The item.</param> + /// <param name="item">The item.</param> /// <returns>The list of chapter info.</returns> - List<ChapterInfo> GetChapters(BaseItem id); + List<ChapterInfo> GetChapters(BaseItem item); /// <summary> /// Gets a single chapter for an item. /// </summary> - /// <param name="id">The item.</param> + /// <param name="item">The item.</param> /// <param name="index">The chapter index.</param> /// <returns>The chapter info at the specified index.</returns> - ChapterInfo GetChapter(BaseItem id, int index); + ChapterInfo GetChapter(BaseItem item, int index); /// <summary> /// Saves the chapters. diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 23b36cc10e..cc12cb102b 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -344,7 +344,7 @@ namespace MediaBrowser.Controller.Session /// <returns>A <see cref="Task"/> representing the log out process.</returns> Task Logout(string accessToken); - Task Logout(Device accessToken); + Task Logout(Device device); /// <summary> /// Revokes the user tokens. diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 432c1c1d60..4414415a27 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -133,7 +133,7 @@ namespace MediaBrowser.Model.Dlna var stream = TargetAudioStream; return AudioSampleRate.HasValue && !IsDirectStream ? AudioSampleRate - : stream == null ? null : stream.SampleRate; + : stream?.SampleRate; } } @@ -146,7 +146,7 @@ namespace MediaBrowser.Model.Dlna { if (IsDirectStream) { - return TargetAudioStream == null ? (int?)null : TargetAudioStream.BitDepth; + return TargetAudioStream?.BitDepth; } var targetAudioCodecs = TargetAudioCodec; @@ -156,7 +156,7 @@ namespace MediaBrowser.Model.Dlna return GetTargetAudioBitDepth(audioCodec); } - return TargetAudioStream == null ? (int?)null : TargetAudioStream.BitDepth; + return TargetAudioStream?.BitDepth; } } @@ -169,7 +169,7 @@ namespace MediaBrowser.Model.Dlna { if (IsDirectStream) { - return TargetVideoStream == null ? (int?)null : TargetVideoStream.BitDepth; + return TargetVideoStream?.BitDepth; } var targetVideoCodecs = TargetVideoCodec; @@ -179,7 +179,7 @@ namespace MediaBrowser.Model.Dlna return GetTargetVideoBitDepth(videoCodec); } - return TargetVideoStream == null ? (int?)null : TargetVideoStream.BitDepth; + return TargetVideoStream?.BitDepth; } } @@ -193,7 +193,7 @@ namespace MediaBrowser.Model.Dlna { if (IsDirectStream) { - return TargetVideoStream == null ? (int?)null : TargetVideoStream.RefFrames; + return TargetVideoStream?.RefFrames; } var targetVideoCodecs = TargetVideoCodec; @@ -203,7 +203,7 @@ namespace MediaBrowser.Model.Dlna return GetTargetRefFrames(videoCodec); } - return TargetVideoStream == null ? (int?)null : TargetVideoStream.RefFrames; + return TargetVideoStream?.RefFrames; } } @@ -230,7 +230,7 @@ namespace MediaBrowser.Model.Dlna { if (IsDirectStream) { - return TargetVideoStream == null ? (double?)null : TargetVideoStream.Level; + return TargetVideoStream?.Level; } var targetVideoCodecs = TargetVideoCodec; @@ -240,7 +240,7 @@ namespace MediaBrowser.Model.Dlna return GetTargetVideoLevel(videoCodec); } - return TargetVideoStream == null ? (double?)null : TargetVideoStream.Level; + return TargetVideoStream?.Level; } } @@ -254,7 +254,7 @@ namespace MediaBrowser.Model.Dlna var stream = TargetVideoStream; return !IsDirectStream ? null - : stream == null ? null : stream.PacketLength; + : stream?.PacketLength; } } @@ -267,7 +267,7 @@ namespace MediaBrowser.Model.Dlna { if (IsDirectStream) { - return TargetVideoStream == null ? null : TargetVideoStream.Profile; + return TargetVideoStream?.Profile; } var targetVideoCodecs = TargetVideoCodec; @@ -277,7 +277,7 @@ namespace MediaBrowser.Model.Dlna return GetOption(videoCodec, "profile"); } - return TargetVideoStream == null ? null : TargetVideoStream.Profile; + return TargetVideoStream?.Profile; } } @@ -292,7 +292,7 @@ namespace MediaBrowser.Model.Dlna var stream = TargetVideoStream; return !IsDirectStream ? null - : stream == null ? null : stream.CodecTag; + : stream?.CodecTag; } } @@ -306,7 +306,7 @@ namespace MediaBrowser.Model.Dlna var stream = TargetAudioStream; return AudioBitrate.HasValue && !IsDirectStream ? AudioBitrate - : stream == null ? null : stream.BitRate; + : stream?.BitRate; } } @@ -319,7 +319,7 @@ namespace MediaBrowser.Model.Dlna { if (IsDirectStream) { - return TargetAudioStream == null ? (int?)null : TargetAudioStream.Channels; + return TargetAudioStream?.Channels; } var targetAudioCodecs = TargetAudioCodec; @@ -329,7 +329,7 @@ namespace MediaBrowser.Model.Dlna return GetTargetRefFrames(codec); } - return TargetAudioStream == null ? (int?)null : TargetAudioStream.Channels; + return TargetAudioStream?.Channels; } } @@ -425,7 +425,7 @@ namespace MediaBrowser.Model.Dlna return VideoBitrate.HasValue && !IsDirectStream ? VideoBitrate - : stream == null ? null : stream.BitRate; + : stream?.BitRate; } } @@ -451,7 +451,7 @@ namespace MediaBrowser.Model.Dlna { if (IsDirectStream) { - return TargetVideoStream == null ? null : TargetVideoStream.IsAnamorphic; + return TargetVideoStream?.IsAnamorphic; } return false; @@ -464,7 +464,7 @@ namespace MediaBrowser.Model.Dlna { if (IsDirectStream) { - return TargetVideoStream == null ? (bool?)null : TargetVideoStream.IsInterlaced; + return TargetVideoStream?.IsInterlaced; } var targetVideoCodecs = TargetVideoCodec; @@ -477,7 +477,7 @@ namespace MediaBrowser.Model.Dlna } } - return TargetVideoStream == null ? (bool?)null : TargetVideoStream.IsInterlaced; + return TargetVideoStream?.IsInterlaced; } } @@ -487,7 +487,7 @@ namespace MediaBrowser.Model.Dlna { if (IsDirectStream) { - return TargetVideoStream == null ? null : TargetVideoStream.IsAVC; + return TargetVideoStream?.IsAVC; } return true; @@ -618,20 +618,20 @@ namespace MediaBrowser.Model.Dlna } // Try to keep the url clean by omitting defaults - if (string.Equals(pair.Name, "StartTimeTicks", StringComparison.OrdinalIgnoreCase) && - string.Equals(pair.Value, "0", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(pair.Name, "StartTimeTicks", StringComparison.OrdinalIgnoreCase) + && string.Equals(pair.Value, "0", StringComparison.OrdinalIgnoreCase)) { continue; } - if (string.Equals(pair.Name, "SubtitleStreamIndex", StringComparison.OrdinalIgnoreCase) && - string.Equals(pair.Value, "-1", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(pair.Name, "SubtitleStreamIndex", StringComparison.OrdinalIgnoreCase) + && string.Equals(pair.Value, "-1", StringComparison.OrdinalIgnoreCase)) { continue; } - if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase) && - string.Equals(pair.Value, "false", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase) + && string.Equals(pair.Value, "false", StringComparison.OrdinalIgnoreCase)) { continue; } @@ -641,7 +641,7 @@ namespace MediaBrowser.Model.Dlna list.Add(string.Format(CultureInfo.InvariantCulture, "{0}={1}", pair.Name, encodedValue)); } - string queryString = string.Join("&", list.ToArray()); + string queryString = string.Join('&', list); return GetUrl(baseUrl, queryString); } @@ -681,11 +681,11 @@ namespace MediaBrowser.Model.Dlna string audioCodecs = item.AudioCodecs.Length == 0 ? string.Empty : - string.Join(",", item.AudioCodecs); + string.Join(',', item.AudioCodecs); string videoCodecs = item.VideoCodecs.Length == 0 ? string.Empty : - string.Join(",", item.VideoCodecs); + string.Join(',', item.VideoCodecs); list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty)); list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty)); @@ -1024,30 +1024,5 @@ namespace MediaBrowser.Model.Dlna return count; } - - public List<MediaStream> GetSelectableAudioStreams() - { - return GetSelectableStreams(MediaStreamType.Audio); - } - - public List<MediaStream> GetSelectableSubtitleStreams() - { - return GetSelectableStreams(MediaStreamType.Subtitle); - } - - public List<MediaStream> GetSelectableStreams(MediaStreamType type) - { - var list = new List<MediaStream>(); - - foreach (var stream in MediaSource.MediaStreams) - { - if (type == stream.Type) - { - list.Add(stream); - } - } - - return list; - } } } diff --git a/jellyfin.ruleset b/jellyfin.ruleset index a2fc7bc8da..68fb9064e4 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -12,6 +12,8 @@ <Rule Id="SA1101" Action="None" /> <!-- disable warning SA1108: Block statements should not contain embedded comments --> <Rule Id="SA1108" Action="None" /> + <!-- disable warning SA1118: Parameter must not span multiple lines. --> + <Rule Id="SA1118" Action="None" /> <!-- disable warning SA1128:: Put constructor initializers on their own line --> <Rule Id="SA1128" Action="None" /> <!-- disable warning SA1130: Use lambda syntax --> @@ -39,6 +41,10 @@ </Rules> <Rules AnalyzerId="Microsoft.CodeAnalysis.NetAnalyzers" RuleNamespace="Microsoft.Design"> + <!-- error on CA1305: Specify IFormatProvider --> + <Rule Id="CA1305" Action="Error" /> + <!-- error on CA1725: Parameter names should match base declaration --> + <Rule Id="CA1725" Action="Error" /> <!-- error on CA2016: Forward the CancellationToken parameter to methods that take one or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token --> <Rule Id="CA2016" Action="Error" /> -- cgit v1.2.3 From 718098365409247812c1f5aa4605be20ff3f67da Mon Sep 17 00:00:00 2001 From: Patrick Barron <barronpm@gmail.com> Date: Sat, 2 Oct 2021 14:01:57 -0400 Subject: Fix warnings in SessionManager --- Emby.Server.Implementations/Session/SessionManager.cs | 2 ++ 1 file changed, 2 insertions(+) (limited to 'Emby.Server.Implementations/Session') diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 334ce5c9d2..341d2c8e61 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -741,6 +741,8 @@ namespace Emby.Server.Implementations.Session /// <summary> /// Used to report playback progress for an item. /// </summary> + /// <param name="info">The playback progress info.</param> + /// <param name="isAutomated">Whether this is an automated update.</param> /// <returns>Task.</returns> public async Task OnPlaybackProgress(PlaybackProgressInfo info, bool isAutomated) { -- cgit v1.2.3