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 diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index c58f16438..a215c9f4b 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 2ca44b7ea..943d52b0d 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 0bb1d832f..e3e071af9 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 b0126f7fa..4baf96ab5 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 1c3496e5d..000000000 --- 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 2292d86a4..b4f420e5d 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 4d160aa66..000000000 --- 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 e3047d392..000000000 --- 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 dfb81816c..000000000 --- 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 b1d513dd4..db00ceeb7 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 930f2d35d..13b42698d 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 0d483c55f..c17e67da9 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 67521d6c6..000000000 --- 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 ba5ba1904..000000000 --- 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 3ee5fb8b5..45f2b9d7c 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 f1a6622fb..cf7ddd631 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 25f0905eb..0790b6cd6 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 ee5c1a165..9d71426d8 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 46933c046..694e3954e 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 566897b31..e2a714d5b 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 a59c96ac7..04450085b 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 acda6a416..63ed43a83 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 c763216f1..308032f83 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(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 4baf96ab5..ebae4d0b1 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 b4f420e5d..88974f9ab 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 db00ceeb7..0d5df1dad 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 13b42698d..d4e4ba1f2 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 c17e67da9..536013c7a 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 e2a714d5b..d5555884d 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(-) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 88974f9ab..913a51217 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 536013c7a..c3c4b716f 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 d5555884d..09e43c683 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 8865b3ea3d0af201c37aa129016b843f0b9fe686 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 27 Dec 2019 15:20:27 +0100 Subject: Remove dead code and improve logging --- .../HttpServer/HttpListenerHost.cs | 8 +- .../HttpServer/WebSocketConnection.cs | 4 +- .../Middleware/WebSocketMiddleware.cs | 39 -------- .../WebSockets/WebSocketHandler.cs | 10 -- .../WebSockets/WebSocketManager.cs | 104 --------------------- .../System/ActivityLogWebSocketListener.cs | 17 ++-- .../Net/BasePeriodicWebSocketListener.cs | 11 +-- 7 files changed, 18 insertions(+), 175 deletions(-) delete mode 100644 Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs delete mode 100644 Emby.Server.Implementations/WebSockets/WebSocketHandler.cs delete mode 100644 Emby.Server.Implementations/WebSockets/WebSocketManager.cs diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index ebae4d0b1..3cdb0ecae 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -520,7 +520,7 @@ namespace Emby.Server.Implementations.HttpServer try { - _logger.LogInformation("WS Request from {IP}", context.Connection.RemoteIpAddress); + _logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress); WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false); @@ -536,11 +536,11 @@ namespace Emby.Server.Implementations.HttpServer WebSocketConnected?.Invoke(this, new GenericEventArgs(connection)); await connection.ProcessAsync().ConfigureAwait(false); - _logger.LogInformation("WS closed from {IP}", context.Connection.RemoteIpAddress); + _logger.LogInformation("WS {IP} closed", context.Connection.RemoteIpAddress); } catch (Exception ex) // Otherwise ASP.Net will ignore the exception { - _logger.LogError(ex, "WebSocketRequestHandler error"); + _logger.LogError(ex, "WS {IP} WebSocketRequestHandler error"); if (!context.Response.HasStarted) { context.Response.StatusCode = 500; @@ -705,8 +705,6 @@ namespace Emby.Server.Implementations.HttpServer return Task.CompletedTask; } - _logger.LogDebug("Websocket message received: {0}", result.MessageType); - IEnumerable GetTasks() { foreach (var x in _webSocketListeners) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 913a51217..0afd0ecce 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -195,7 +195,7 @@ namespace Emby.Server.Implementations.HttpServer // Tell the PipeReader how much of the buffer we have consumed reader.AdvanceTo(buffer.End); - _logger.LogDebug("WS received message: {@Message}", stub); + _logger.LogDebug("WS {IP} received message: {@Message}", RemoteEndPoint, stub); var info = new WebSocketMessageInfo { @@ -204,7 +204,7 @@ namespace Emby.Server.Implementations.HttpServer Connection = this }; - _logger.LogDebug("WS message info: {@MessageInfo}", info); + _logger.LogDebug("WS {IP} message info: {@MessageInfo}", RemoteEndPoint, info); await OnReceive(info).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs b/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs deleted file mode 100644 index fda32da5e..000000000 --- a/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using WebSocketManager = Emby.Server.Implementations.WebSockets.WebSocketManager; - -namespace Emby.Server.Implementations.Middleware -{ - public class WebSocketMiddleware - { - private readonly RequestDelegate _next; - private readonly ILogger _logger; - private readonly WebSocketManager _webSocketManager; - - public WebSocketMiddleware(RequestDelegate next, ILogger logger, WebSocketManager webSocketManager) - { - _next = next; - _logger = logger; - _webSocketManager = webSocketManager; - } - - public async Task Invoke(HttpContext httpContext) - { - _logger.LogInformation("Handling request: " + httpContext.Request.Path); - - if (httpContext.WebSockets.IsWebSocketRequest) - { - var webSocketContext = await httpContext.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); - if (webSocketContext != null) - { - await _webSocketManager.OnWebSocketConnected(webSocketContext).ConfigureAwait(false); - } - } - else - { - await _next.Invoke(httpContext).ConfigureAwait(false); - } - } - } -} diff --git a/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs b/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs deleted file mode 100644 index eb1877440..000000000 --- a/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Threading.Tasks; -using MediaBrowser.Model.Net; - -namespace Emby.Server.Implementations.WebSockets -{ - public interface IWebSocketHandler - { - Task ProcessMessage(WebSocketMessage message, TaskCompletionSource taskCompletionSource); - } -} diff --git a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs deleted file mode 100644 index efd97e4ff..000000000 --- a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Net; -using MediaBrowser.Model.Serialization; -using Microsoft.Extensions.Logging; -using UtfUnknown; - -namespace Emby.Server.Implementations.WebSockets -{ - public class WebSocketManager - { - private readonly IWebSocketHandler[] _webSocketHandlers; - private readonly IJsonSerializer _jsonSerializer; - private readonly ILogger _logger; - private const int BufferSize = 4096; - - public WebSocketManager(IWebSocketHandler[] webSocketHandlers, IJsonSerializer jsonSerializer, ILogger logger) - { - _webSocketHandlers = webSocketHandlers; - _jsonSerializer = jsonSerializer; - _logger = logger; - } - - public async Task OnWebSocketConnected(WebSocket webSocket) - { - var taskCompletionSource = new TaskCompletionSource(); - var cancellationToken = new CancellationTokenSource().Token; - WebSocketReceiveResult result; - var message = new List(); - - // Keep listening for incoming messages, otherwise the socket closes automatically - do - { - var buffer = WebSocket.CreateServerBuffer(BufferSize); - result = await webSocket.ReceiveAsync(buffer, cancellationToken).ConfigureAwait(false); - message.AddRange(buffer.Array.Take(result.Count)); - - if (result.EndOfMessage) - { - await ProcessMessage(message.ToArray(), taskCompletionSource).ConfigureAwait(false); - message.Clear(); - } - } while (!taskCompletionSource.Task.IsCompleted && - webSocket.State == WebSocketState.Open && - result.MessageType != WebSocketMessageType.Close); - - if (webSocket.State == WebSocketState.Open) - { - await webSocket.CloseAsync( - result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, - result.CloseStatusDescription, - cancellationToken).ConfigureAwait(false); - } - } - - private async Task ProcessMessage(byte[] messageBytes, TaskCompletionSource taskCompletionSource) - { - var charset = CharsetDetector.DetectFromBytes(messageBytes).Detected?.EncodingName; - var message = string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase) - ? Encoding.UTF8.GetString(messageBytes, 0, messageBytes.Length) - : Encoding.ASCII.GetString(messageBytes, 0, messageBytes.Length); - - // All messages are expected to be valid JSON objects - if (!message.StartsWith("{", StringComparison.OrdinalIgnoreCase)) - { - _logger.LogDebug("Received web socket message that is not a json structure: {Message}", message); - return; - } - - try - { - var info = _jsonSerializer.DeserializeFromString>(message); - - _logger.LogDebug("Websocket message received: {0}", info.MessageType); - - var tasks = _webSocketHandlers.Select(handler => Task.Run(() => - { - try - { - handler.ProcessMessage(info, taskCompletionSource).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError(ex, "{HandlerType} failed processing WebSocket message {MessageType}", - handler.GetType().Name, info.MessageType ?? string.Empty); - } - })); - - await Task.WhenAll(tasks); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error processing web socket message"); - } - } - } -} diff --git a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs b/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs index a036619b8..60b190a0e 100644 --- a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs +++ b/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Threading; +using System; using System.Threading.Tasks; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Activity; @@ -11,7 +10,7 @@ namespace MediaBrowser.Api.System /// /// Class SessionInfoWebSocketListener /// - public class ActivityLogWebSocketListener : BasePeriodicWebSocketListener, WebSocketListenerState> + public class ActivityLogWebSocketListener : BasePeriodicWebSocketListener { /// /// Gets the name. @@ -27,10 +26,10 @@ namespace MediaBrowser.Api.System public ActivityLogWebSocketListener(ILogger logger, IActivityManager activityManager) : base(logger) { _activityManager = activityManager; - _activityManager.EntryCreated += _activityManager_EntryCreated; + _activityManager.EntryCreated += OnEntryCreated; } - void _activityManager_EntryCreated(object sender, GenericEventArgs e) + private void OnEntryCreated(object sender, GenericEventArgs e) { SendData(true); } @@ -39,15 +38,15 @@ namespace MediaBrowser.Api.System /// Gets the data to send. /// /// Task{SystemInfo}. - protected override Task> GetDataToSend() + protected override Task GetDataToSend() { - return Task.FromResult(new List()); + return Task.FromResult(Array.Empty()); } - + /// protected override void Dispose(bool dispose) { - _activityManager.EntryCreated -= _activityManager_EntryCreated; + _activityManager.EntryCreated -= OnEntryCreated; base.Dispose(dispose); } diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index 9d71426d8..b193cbb55 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -77,8 +77,6 @@ namespace MediaBrowser.Controller.Net return Task.CompletedTask; } - protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); - /// /// Starts sending messages over a web socket /// @@ -87,12 +85,12 @@ namespace MediaBrowser.Controller.Net { var vals = message.Data.Split(','); - var dueTimeMs = long.Parse(vals[0], UsCulture); - var periodMs = long.Parse(vals[1], UsCulture); + var dueTimeMs = long.Parse(vals[0], CultureInfo.InvariantCulture); + var periodMs = long.Parse(vals[1], CultureInfo.InvariantCulture); var cancellationTokenSource = new CancellationTokenSource(); - Logger.LogDebug("{1} Begin transmitting over websocket to {0}", message.Connection.RemoteEndPoint, GetType().Name); + Logger.LogDebug("WS {1} begin transmitting to {0}", message.Connection.RemoteEndPoint, GetType().Name); var state = new TStateType { @@ -196,7 +194,7 @@ namespace MediaBrowser.Controller.Net /// The connection. private void DisposeConnection(Tuple connection) { - Logger.LogDebug("{1} stop transmitting over websocket to {0}", connection.Item1.RemoteEndPoint, GetType().Name); + Logger.LogDebug("WS {1} stop transmitting to {0}", connection.Item1.RemoteEndPoint, GetType().Name); // TODO disposing the connection seems to break websockets in subtle ways, so what is the purpose of this function really... // connection.Item1.Dispose(); @@ -241,6 +239,7 @@ namespace MediaBrowser.Controller.Net public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } } -- cgit v1.2.3 From bdd823d22ff4d20e8aa2e5d8bf34e0faaad285ba Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 27 Dec 2019 15:42:59 +0100 Subject: Handle unexpected disconnect --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 2 +- Emby.Server.Implementations/HttpServer/WebSocketConnection.cs | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 3cdb0ecae..05dbad624 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -540,7 +540,7 @@ namespace Emby.Server.Implementations.HttpServer } catch (Exception ex) // Otherwise ASP.Net will ignore the exception { - _logger.LogError(ex, "WS {IP} WebSocketRequestHandler error"); + _logger.LogError(ex, "WS {IP} WebSocketRequestHandler error", context.Connection.RemoteIpAddress); 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 0afd0ecce..7c0d82d89 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -116,7 +116,16 @@ namespace Emby.Server.Implementations.HttpServer { // Allocate at least 512 bytes from the PipeWriter Memory memory = writer.GetMemory(512); - receiveresult = await _socket.ReceiveAsync(memory, cancellationToken); + try + { + receiveresult = await _socket.ReceiveAsync(memory, cancellationToken); + } + catch (WebSocketException ex) + { + _logger.LogWarning("WS {IP} error receiving data: {Message}", RemoteEndPoint, ex.Message); + break; + } + int bytesRead = receiveresult.Count; if (bytesRead == 0) { -- cgit v1.2.3 From f89e18ea26aa2f4eec19f52ee6dfd28b53cee5df Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 27 Dec 2019 15:56:20 +0100 Subject: Improve error handling --- .../HttpServer/WebSocketConnection.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 7c0d82d89..0b376bf3c 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -149,14 +149,21 @@ namespace Emby.Server.Implementations.HttpServer { await ProcessInternal(pipe.Reader).ConfigureAwait(false); } - } while (_socket.State == WebSocketState.Open && receiveresult.MessageType != WebSocketMessageType.Close); + } while ( + (_socket.State == WebSocketState.Open || _socket.State == WebSocketState.Connecting) + && receiveresult.MessageType != WebSocketMessageType.Close); Closed?.Invoke(this, EventArgs.Empty); - await _socket.CloseAsync( - WebSocketCloseStatus.NormalClosure, - string.Empty, - cancellationToken).ConfigureAwait(false); + if (_socket.State == WebSocketState.Open + || _socket.State == WebSocketState.CloseReceived + || _socket.State == WebSocketState.CloseSent) + { + await _socket.CloseAsync( + WebSocketCloseStatus.NormalClosure, + string.Empty, + cancellationToken).ConfigureAwait(false); + } } private async Task ProcessInternal(PipeReader reader) -- cgit v1.2.3 From d01ba49be3cd643b7b306216cb96aef31dba9569 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Sun, 29 Dec 2019 14:53:04 +0100 Subject: Fix space --- Emby.Server.Implementations/HttpServer/WebSocketConnection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 0b376bf3c..a8d5e9086 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -211,7 +211,7 @@ namespace Emby.Server.Implementations.HttpServer // Tell the PipeReader how much of the buffer we have consumed reader.AdvanceTo(buffer.End); - _logger.LogDebug("WS {IP} received message: {@Message}", RemoteEndPoint, stub); + _logger.LogDebug("WS {IP} received message: {@Message}", RemoteEndPoint, stub); var info = new WebSocketMessageInfo { -- cgit v1.2.3 From ee964f8a58a0324b9e7b2ae37a9d4831f59c922f Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Sun, 29 Dec 2019 15:44:17 +0100 Subject: Don't log message info --- Emby.Server.Implementations/HttpServer/WebSocketConnection.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index a8d5e9086..1af748ebc 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -220,8 +220,6 @@ namespace Emby.Server.Implementations.HttpServer Connection = this }; - _logger.LogDebug("WS {IP} message info: {@MessageInfo}", RemoteEndPoint, info); - await OnReceive(info).ConfigureAwait(false); // Stop reading if there's no more data coming -- 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(-) diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index c3c4b716f..c7ef9b1ce 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 9bdb99fe92edaf06679ef855eae9f8bb69b970df Mon Sep 17 00:00:00 2001 From: Luke Foust Date: Sun, 22 Mar 2020 12:58:53 -0700 Subject: Add type to externalids to distinguish them in the UI --- MediaBrowser.Controller/Providers/IExternalId.cs | 19 +++++++++++++++ MediaBrowser.Model/Providers/ExternalIdInfo.cs | 6 +++++ MediaBrowser.Providers/Manager/ProviderManager.cs | 1 + MediaBrowser.Providers/Movies/MovieExternalIds.cs | 6 +++++ MediaBrowser.Providers/Music/MusicExternalIds.cs | 3 +++ .../Plugins/AudioDb/ExternalIds.cs | 16 +++++++++++-- .../Plugins/MusicBrainz/ExternalIds.cs | 28 ++++++++++++++++++---- MediaBrowser.Providers/TV/TvExternalIds.cs | 12 ++++++++++ .../Tmdb/BoxSets/TmdbBoxSetExternalId.cs | 3 +++ .../Tmdb/Movies/TmdbMovieExternalId.cs | 3 +++ .../Tmdb/People/TmdbPersonExternalId.cs | 3 +++ .../Tmdb/TV/TmdbSeriesExternalId.cs | 3 +++ 12 files changed, 96 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.Controller/Providers/IExternalId.cs b/MediaBrowser.Controller/Providers/IExternalId.cs index d7e337bda..157a2076e 100644 --- a/MediaBrowser.Controller/Providers/IExternalId.cs +++ b/MediaBrowser.Controller/Providers/IExternalId.cs @@ -8,8 +8,27 @@ namespace MediaBrowser.Controller.Providers string Key { get; } + ExternalIdType Type { get; } + string UrlFormatString { get; } bool Supports(IHasProviderIds item); } + + public enum ExternalIdType + { + None, + Album, + AlbumArtist, + Artist, + BoxSet, + Episode, + Movie, + OtherArtist, + Person, + ReleaseGroup, + Season, + Series, + Track + } } diff --git a/MediaBrowser.Model/Providers/ExternalIdInfo.cs b/MediaBrowser.Model/Providers/ExternalIdInfo.cs index 2b481ad7e..8d6d91143 100644 --- a/MediaBrowser.Model/Providers/ExternalIdInfo.cs +++ b/MediaBrowser.Model/Providers/ExternalIdInfo.cs @@ -16,6 +16,12 @@ namespace MediaBrowser.Model.Providers /// The key. public string Key { get; set; } + /// + /// Gets or sets the type. + /// + /// The type. + public string Type { get; set; } + /// /// Gets or sets the URL format string. /// diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index e7b349f67..608a0cd19 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -910,6 +910,7 @@ namespace MediaBrowser.Providers.Manager { Name = i.Name, Key = i.Key, + Type = i.Type == ExternalIdType.None ? null : i.Type.ToString(), UrlFormatString = i.UrlFormatString }); diff --git a/MediaBrowser.Providers/Movies/MovieExternalIds.cs b/MediaBrowser.Providers/Movies/MovieExternalIds.cs index 55810b1ed..1ede0e7a5 100644 --- a/MediaBrowser.Providers/Movies/MovieExternalIds.cs +++ b/MediaBrowser.Providers/Movies/MovieExternalIds.cs @@ -15,6 +15,9 @@ namespace MediaBrowser.Providers.Movies /// public string Key => MetadataProviders.Imdb.ToString(); + /// + public ExternalIdType Type => ExternalIdType.None; + /// public string UrlFormatString => "https://www.imdb.com/title/{0}"; @@ -39,6 +42,9 @@ namespace MediaBrowser.Providers.Movies /// public string Key => MetadataProviders.Imdb.ToString(); + /// + public ExternalIdType Type => ExternalIdType.Person; + /// public string UrlFormatString => "https://www.imdb.com/name/{0}"; diff --git a/MediaBrowser.Providers/Music/MusicExternalIds.cs b/MediaBrowser.Providers/Music/MusicExternalIds.cs index 628b9a9a1..54e034713 100644 --- a/MediaBrowser.Providers/Music/MusicExternalIds.cs +++ b/MediaBrowser.Providers/Music/MusicExternalIds.cs @@ -12,6 +12,9 @@ namespace MediaBrowser.Providers.Music /// public string Key => "IMVDb"; + /// + public ExternalIdType Type => ExternalIdType.None; + /// public string UrlFormatString => null; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs b/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs index 2d8cb431c..785185d61 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs @@ -12,6 +12,9 @@ namespace MediaBrowser.Providers.Plugins.AudioDb /// public string Key => MetadataProviders.AudioDbAlbum.ToString(); + /// + public ExternalIdType Type => ExternalIdType.None; + /// public string UrlFormatString => "https://www.theaudiodb.com/album/{0}"; @@ -22,11 +25,14 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public class AudioDbOtherAlbumExternalId : IExternalId { /// - public string Name => "TheAudioDb Album"; + public string Name => "TheAudioDb"; /// public string Key => MetadataProviders.AudioDbAlbum.ToString(); + /// + public ExternalIdType Type => ExternalIdType.Album; + /// public string UrlFormatString => "https://www.theaudiodb.com/album/{0}"; @@ -42,6 +48,9 @@ namespace MediaBrowser.Providers.Plugins.AudioDb /// public string Key => MetadataProviders.AudioDbArtist.ToString(); + /// + public ExternalIdType Type => ExternalIdType.Artist; + /// public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}"; @@ -52,11 +61,14 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public class AudioDbOtherArtistExternalId : IExternalId { /// - public string Name => "TheAudioDb Artist"; + public string Name => "TheAudioDb"; /// public string Key => MetadataProviders.AudioDbArtist.ToString(); + /// + public ExternalIdType Type => ExternalIdType.OtherArtist; + /// public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}"; diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs index 03565a34c..ed9fa6307 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs @@ -8,11 +8,14 @@ namespace MediaBrowser.Providers.Music public class MusicBrainzReleaseGroupExternalId : IExternalId { /// - public string Name => "MusicBrainz Release Group"; + public string Name => "MusicBrainz"; /// public string Key => MetadataProviders.MusicBrainzReleaseGroup.ToString(); + /// + public ExternalIdType Type => ExternalIdType.ReleaseGroup; + /// public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release-group/{0}"; @@ -23,11 +26,14 @@ namespace MediaBrowser.Providers.Music public class MusicBrainzAlbumArtistExternalId : IExternalId { /// - public string Name => "MusicBrainz Album Artist"; + public string Name => "MusicBrainz"; /// public string Key => MetadataProviders.MusicBrainzAlbumArtist.ToString(); + /// + public ExternalIdType Type => ExternalIdType.AlbumArtist; + /// public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; @@ -38,11 +44,14 @@ namespace MediaBrowser.Providers.Music public class MusicBrainzAlbumExternalId : IExternalId { /// - public string Name => "MusicBrainz Album"; + public string Name => "MusicBrainz"; /// public string Key => MetadataProviders.MusicBrainzAlbum.ToString(); + /// + public ExternalIdType Type => ExternalIdType.Album; + /// public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release/{0}"; @@ -58,6 +67,9 @@ namespace MediaBrowser.Providers.Music /// public string Key => MetadataProviders.MusicBrainzArtist.ToString(); + /// + public ExternalIdType Type => ExternalIdType.Artist; + /// public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; @@ -68,12 +80,15 @@ namespace MediaBrowser.Providers.Music public class MusicBrainzOtherArtistExternalId : IExternalId { /// - public string Name => "MusicBrainz Artist"; + public string Name => "MusicBrainz"; /// public string Key => MetadataProviders.MusicBrainzArtist.ToString(); + /// + public ExternalIdType Type => ExternalIdType.OtherArtist; + /// public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; @@ -84,11 +99,14 @@ namespace MediaBrowser.Providers.Music public class MusicBrainzTrackId : IExternalId { /// - public string Name => "MusicBrainz Track"; + public string Name => "MusicBrainz"; /// public string Key => MetadataProviders.MusicBrainzTrack.ToString(); + /// + public ExternalIdType Type => ExternalIdType.Track; + /// public string UrlFormatString => Plugin.Instance.Configuration.Server + "/track/{0}"; diff --git a/MediaBrowser.Providers/TV/TvExternalIds.cs b/MediaBrowser.Providers/TV/TvExternalIds.cs index baf854285..a3c24f7dd 100644 --- a/MediaBrowser.Providers/TV/TvExternalIds.cs +++ b/MediaBrowser.Providers/TV/TvExternalIds.cs @@ -13,6 +13,9 @@ namespace MediaBrowser.Providers.TV /// public string Key => MetadataProviders.Zap2It.ToString(); + /// + public ExternalIdType Type => ExternalIdType.None; + /// public string UrlFormatString => "http://tvlistings.zap2it.com/overview.html?programSeriesId={0}"; @@ -28,6 +31,9 @@ namespace MediaBrowser.Providers.TV /// public string Key => MetadataProviders.Tvdb.ToString(); + /// + public ExternalIdType Type => ExternalIdType.None; + /// public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=series&id={0}"; @@ -44,6 +50,9 @@ namespace MediaBrowser.Providers.TV /// public string Key => MetadataProviders.Tvdb.ToString(); + /// + public ExternalIdType Type => ExternalIdType.Season; + /// public string UrlFormatString => null; @@ -59,6 +68,9 @@ namespace MediaBrowser.Providers.TV /// public string Key => MetadataProviders.Tvdb.ToString(); + /// + public ExternalIdType Type => ExternalIdType.Episode; + /// public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=episode&id={0}"; diff --git a/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetExternalId.cs b/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetExternalId.cs index 187295e1e..a51355254 100644 --- a/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetExternalId.cs +++ b/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetExternalId.cs @@ -13,6 +13,9 @@ namespace MediaBrowser.Providers.Tmdb.BoxSets /// public string Key => MetadataProviders.TmdbCollection.ToString(); + /// + public ExternalIdType Type => ExternalIdType.BoxSet; + /// public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "collection/{0}"; diff --git a/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieExternalId.cs b/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieExternalId.cs index fc7a4583f..af565b079 100644 --- a/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieExternalId.cs +++ b/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieExternalId.cs @@ -14,6 +14,9 @@ namespace MediaBrowser.Providers.Tmdb.Movies /// public string Key => MetadataProviders.Tmdb.ToString(); + /// + public ExternalIdType Type => ExternalIdType.Movie; + /// public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "movie/{0}"; diff --git a/MediaBrowser.Providers/Tmdb/People/TmdbPersonExternalId.cs b/MediaBrowser.Providers/Tmdb/People/TmdbPersonExternalId.cs index 2c61bc70a..1ec43c269 100644 --- a/MediaBrowser.Providers/Tmdb/People/TmdbPersonExternalId.cs +++ b/MediaBrowser.Providers/Tmdb/People/TmdbPersonExternalId.cs @@ -12,6 +12,9 @@ namespace MediaBrowser.Providers.Tmdb.People /// public string Key => MetadataProviders.Tmdb.ToString(); + /// + public ExternalIdType Type => ExternalIdType.Person; + /// public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "person/{0}"; diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesExternalId.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesExternalId.cs index 524a3b05e..43ef06bf7 100644 --- a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesExternalId.cs +++ b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesExternalId.cs @@ -12,6 +12,9 @@ namespace MediaBrowser.Providers.Tmdb.TV /// public string Key => MetadataProviders.Tmdb.ToString(); + /// + public ExternalIdType Type => ExternalIdType.Series; + /// public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "tv/{0}"; -- cgit v1.2.3 From 0fb78cf54b51843c54e7ff59d191c490a5b196cd Mon Sep 17 00:00:00 2001 From: Luke Foust Date: Thu, 26 Mar 2020 14:26:12 -0700 Subject: Add documentation around Name, Id, and Type. Changed ExternalIdType to ExternalIdMediaType --- .../Providers/ExternalIdMediaType.cs | 45 ++++++++++++++++++++++ MediaBrowser.Controller/Providers/IExternalId.cs | 27 +++++-------- MediaBrowser.Model/Providers/ExternalIdInfo.cs | 17 ++++---- MediaBrowser.Providers/Manager/ProviderManager.cs | 2 +- MediaBrowser.Providers/Movies/MovieExternalIds.cs | 4 +- MediaBrowser.Providers/Music/MusicExternalIds.cs | 2 +- .../Plugins/AudioDb/ExternalIds.cs | 8 ++-- .../Plugins/MusicBrainz/ExternalIds.cs | 12 +++--- MediaBrowser.Providers/TV/TvExternalIds.cs | 8 ++-- .../Tmdb/BoxSets/TmdbBoxSetExternalId.cs | 2 +- .../Tmdb/Movies/TmdbMovieExternalId.cs | 2 +- .../Tmdb/People/TmdbPersonExternalId.cs | 2 +- .../Tmdb/TV/TmdbSeriesExternalId.cs | 2 +- 13 files changed, 84 insertions(+), 49 deletions(-) create mode 100644 MediaBrowser.Controller/Providers/ExternalIdMediaType.cs diff --git a/MediaBrowser.Controller/Providers/ExternalIdMediaType.cs b/MediaBrowser.Controller/Providers/ExternalIdMediaType.cs new file mode 100644 index 000000000..470f1e24c --- /dev/null +++ b/MediaBrowser.Controller/Providers/ExternalIdMediaType.cs @@ -0,0 +1,45 @@ +namespace MediaBrowser.Controller.Providers +{ + /// The specific media type of an . + public enum ExternalIdMediaType + { + /// There is no specific media type + None, + + /// A music album + Album, + + /// The artist of a music album + AlbumArtist, + + /// The artist of a media item + Artist, + + /// A boxed set of media + BoxSet, + + /// A series episode + Episode, + + /// A movie + Movie, + + /// An alternative artist apart from the main artist + OtherArtist, + + /// A person + Person, + + /// A release group + ReleaseGroup, + + /// A single season of a series + Season, + + /// A series + Series, + + /// A music track + Track + } +} diff --git a/MediaBrowser.Controller/Providers/IExternalId.cs b/MediaBrowser.Controller/Providers/IExternalId.cs index 157a2076e..c877ffe1f 100644 --- a/MediaBrowser.Controller/Providers/IExternalId.cs +++ b/MediaBrowser.Controller/Providers/IExternalId.cs @@ -2,33 +2,24 @@ using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Providers { + /// Represents and identifier for an external provider. public interface IExternalId { + /// Gets the name used to identify this provider string Name { get; } + /// Gets the unique key to distinguish this provider/type pair. This should be unique across providers. string Key { get; } - ExternalIdType Type { get; } + /// Gets the specific media type for this id. + ExternalIdMediaType Type { get; } + /// Gets the url format string for this id. string UrlFormatString { get; } + /// Determines whether this id supports a given item type. + /// The item. + /// True if this item is supported, otherwise false. bool Supports(IHasProviderIds item); } - - public enum ExternalIdType - { - None, - Album, - AlbumArtist, - Artist, - BoxSet, - Episode, - Movie, - OtherArtist, - Person, - ReleaseGroup, - Season, - Series, - Track - } } diff --git a/MediaBrowser.Model/Providers/ExternalIdInfo.cs b/MediaBrowser.Model/Providers/ExternalIdInfo.cs index 8d6d91143..befcc309b 100644 --- a/MediaBrowser.Model/Providers/ExternalIdInfo.cs +++ b/MediaBrowser.Model/Providers/ExternalIdInfo.cs @@ -1,31 +1,30 @@ -#pragma warning disable CS1591 - namespace MediaBrowser.Model.Providers { + /// + /// Represents the external id information for serialization to the client. + /// public class ExternalIdInfo { /// - /// Gets or sets the name. + /// Gets or sets the name of the external id provider (IE: IMDB, MusicBrainz, etc). /// - /// The name. public string Name { get; set; } /// - /// Gets or sets the key. + /// Gets or sets the unique key for this id. This key should be unique across all providers. /// - /// The key. public string Key { get; set; } /// - /// Gets or sets the type. + /// Gets or sets the media type (Album, Artist, etc). + /// This can be null if there is no specific type. + /// This string is also used to localize the media type on the client. /// - /// The type. public string Type { get; set; } /// /// Gets or sets the URL format string. /// - /// The URL format string. public string UrlFormatString { get; set; } } } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 608a0cd19..fee988d50 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -910,7 +910,7 @@ namespace MediaBrowser.Providers.Manager { Name = i.Name, Key = i.Key, - Type = i.Type == ExternalIdType.None ? null : i.Type.ToString(), + Type = i.Type == ExternalIdMediaType.None ? null : i.Type.ToString(), UrlFormatString = i.UrlFormatString }); diff --git a/MediaBrowser.Providers/Movies/MovieExternalIds.cs b/MediaBrowser.Providers/Movies/MovieExternalIds.cs index 1ede0e7a5..a7b359a2d 100644 --- a/MediaBrowser.Providers/Movies/MovieExternalIds.cs +++ b/MediaBrowser.Providers/Movies/MovieExternalIds.cs @@ -16,7 +16,7 @@ namespace MediaBrowser.Providers.Movies public string Key => MetadataProviders.Imdb.ToString(); /// - public ExternalIdType Type => ExternalIdType.None; + public ExternalIdMediaType Type => ExternalIdMediaType.None; /// public string UrlFormatString => "https://www.imdb.com/title/{0}"; @@ -43,7 +43,7 @@ namespace MediaBrowser.Providers.Movies public string Key => MetadataProviders.Imdb.ToString(); /// - public ExternalIdType Type => ExternalIdType.Person; + public ExternalIdMediaType Type => ExternalIdMediaType.Person; /// public string UrlFormatString => "https://www.imdb.com/name/{0}"; diff --git a/MediaBrowser.Providers/Music/MusicExternalIds.cs b/MediaBrowser.Providers/Music/MusicExternalIds.cs index 54e034713..19879411e 100644 --- a/MediaBrowser.Providers/Music/MusicExternalIds.cs +++ b/MediaBrowser.Providers/Music/MusicExternalIds.cs @@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Music public string Key => "IMVDb"; /// - public ExternalIdType Type => ExternalIdType.None; + public ExternalIdMediaType Type => ExternalIdMediaType.None; /// public string UrlFormatString => null; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs b/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs index 785185d61..cd65acb76 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs @@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public string Key => MetadataProviders.AudioDbAlbum.ToString(); /// - public ExternalIdType Type => ExternalIdType.None; + public ExternalIdMediaType Type => ExternalIdMediaType.None; /// public string UrlFormatString => "https://www.theaudiodb.com/album/{0}"; @@ -31,7 +31,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public string Key => MetadataProviders.AudioDbAlbum.ToString(); /// - public ExternalIdType Type => ExternalIdType.Album; + public ExternalIdMediaType Type => ExternalIdMediaType.Album; /// public string UrlFormatString => "https://www.theaudiodb.com/album/{0}"; @@ -49,7 +49,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public string Key => MetadataProviders.AudioDbArtist.ToString(); /// - public ExternalIdType Type => ExternalIdType.Artist; + public ExternalIdMediaType Type => ExternalIdMediaType.Artist; /// public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}"; @@ -67,7 +67,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public string Key => MetadataProviders.AudioDbArtist.ToString(); /// - public ExternalIdType Type => ExternalIdType.OtherArtist; + public ExternalIdMediaType Type => ExternalIdMediaType.OtherArtist; /// public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}"; diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs index ed9fa6307..7d74a8d35 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.Music public string Key => MetadataProviders.MusicBrainzReleaseGroup.ToString(); /// - public ExternalIdType Type => ExternalIdType.ReleaseGroup; + public ExternalIdMediaType Type => ExternalIdMediaType.ReleaseGroup; /// public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release-group/{0}"; @@ -32,7 +32,7 @@ namespace MediaBrowser.Providers.Music public string Key => MetadataProviders.MusicBrainzAlbumArtist.ToString(); /// - public ExternalIdType Type => ExternalIdType.AlbumArtist; + public ExternalIdMediaType Type => ExternalIdMediaType.AlbumArtist; /// public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; @@ -50,7 +50,7 @@ namespace MediaBrowser.Providers.Music public string Key => MetadataProviders.MusicBrainzAlbum.ToString(); /// - public ExternalIdType Type => ExternalIdType.Album; + public ExternalIdMediaType Type => ExternalIdMediaType.Album; /// public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release/{0}"; @@ -68,7 +68,7 @@ namespace MediaBrowser.Providers.Music public string Key => MetadataProviders.MusicBrainzArtist.ToString(); /// - public ExternalIdType Type => ExternalIdType.Artist; + public ExternalIdMediaType Type => ExternalIdMediaType.Artist; /// public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; @@ -87,7 +87,7 @@ namespace MediaBrowser.Providers.Music public string Key => MetadataProviders.MusicBrainzArtist.ToString(); /// - public ExternalIdType Type => ExternalIdType.OtherArtist; + public ExternalIdMediaType Type => ExternalIdMediaType.OtherArtist; /// public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; @@ -105,7 +105,7 @@ namespace MediaBrowser.Providers.Music public string Key => MetadataProviders.MusicBrainzTrack.ToString(); /// - public ExternalIdType Type => ExternalIdType.Track; + public ExternalIdMediaType Type => ExternalIdMediaType.Track; /// public string UrlFormatString => Plugin.Instance.Configuration.Server + "/track/{0}"; diff --git a/MediaBrowser.Providers/TV/TvExternalIds.cs b/MediaBrowser.Providers/TV/TvExternalIds.cs index a3c24f7dd..75d8b6bf5 100644 --- a/MediaBrowser.Providers/TV/TvExternalIds.cs +++ b/MediaBrowser.Providers/TV/TvExternalIds.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.TV public string Key => MetadataProviders.Zap2It.ToString(); /// - public ExternalIdType Type => ExternalIdType.None; + public ExternalIdMediaType Type => ExternalIdMediaType.None; /// public string UrlFormatString => "http://tvlistings.zap2it.com/overview.html?programSeriesId={0}"; @@ -32,7 +32,7 @@ namespace MediaBrowser.Providers.TV public string Key => MetadataProviders.Tvdb.ToString(); /// - public ExternalIdType Type => ExternalIdType.None; + public ExternalIdMediaType Type => ExternalIdMediaType.None; /// public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=series&id={0}"; @@ -51,7 +51,7 @@ namespace MediaBrowser.Providers.TV public string Key => MetadataProviders.Tvdb.ToString(); /// - public ExternalIdType Type => ExternalIdType.Season; + public ExternalIdMediaType Type => ExternalIdMediaType.Season; /// public string UrlFormatString => null; @@ -69,7 +69,7 @@ namespace MediaBrowser.Providers.TV public string Key => MetadataProviders.Tvdb.ToString(); /// - public ExternalIdType Type => ExternalIdType.Episode; + public ExternalIdMediaType Type => ExternalIdMediaType.Episode; /// public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=episode&id={0}"; diff --git a/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetExternalId.cs b/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetExternalId.cs index a51355254..a83cde93c 100644 --- a/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetExternalId.cs +++ b/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetExternalId.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.Tmdb.BoxSets public string Key => MetadataProviders.TmdbCollection.ToString(); /// - public ExternalIdType Type => ExternalIdType.BoxSet; + public ExternalIdMediaType Type => ExternalIdMediaType.BoxSet; /// public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "collection/{0}"; diff --git a/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieExternalId.cs b/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieExternalId.cs index af565b079..f9ea00067 100644 --- a/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieExternalId.cs +++ b/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieExternalId.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Providers.Tmdb.Movies public string Key => MetadataProviders.Tmdb.ToString(); /// - public ExternalIdType Type => ExternalIdType.Movie; + public ExternalIdMediaType Type => ExternalIdMediaType.Movie; /// public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "movie/{0}"; diff --git a/MediaBrowser.Providers/Tmdb/People/TmdbPersonExternalId.cs b/MediaBrowser.Providers/Tmdb/People/TmdbPersonExternalId.cs index 1ec43c269..854fd4156 100644 --- a/MediaBrowser.Providers/Tmdb/People/TmdbPersonExternalId.cs +++ b/MediaBrowser.Providers/Tmdb/People/TmdbPersonExternalId.cs @@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Tmdb.People public string Key => MetadataProviders.Tmdb.ToString(); /// - public ExternalIdType Type => ExternalIdType.Person; + public ExternalIdMediaType Type => ExternalIdMediaType.Person; /// public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "person/{0}"; diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesExternalId.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesExternalId.cs index 43ef06bf7..770448c7f 100644 --- a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesExternalId.cs +++ b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesExternalId.cs @@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Tmdb.TV public string Key => MetadataProviders.Tmdb.ToString(); /// - public ExternalIdType Type => ExternalIdType.Series; + public ExternalIdMediaType Type => ExternalIdMediaType.Series; /// public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "tv/{0}"; -- cgit v1.2.3 From ca71ac72abf5d5ff31d50553283a43de298e0c73 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Thu, 2 Apr 2020 17:45:04 -0400 Subject: Replace EnableHttps and SupportsHttps with ListenWithHttps and CanConnectWithHttps --- Emby.Server.Implementations/ApplicationHost.cs | 15 ++++++++++----- .../EntryPoints/ExternalPortForwarding.cs | 2 +- .../HttpServer/HttpListenerHost.cs | 2 +- Jellyfin.Server/Program.cs | 4 ++-- MediaBrowser.Controller/IServerApplicationHost.cs | 5 ++--- MediaBrowser.Model/System/SystemInfo.cs | 4 ++-- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index c959cc974..8158b4559 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1408,7 +1408,7 @@ namespace Emby.Server.Implementations InternalMetadataPath = ApplicationPaths.InternalMetadataPath, CachePath = ApplicationPaths.CachePath, HttpServerPortNumber = HttpPort, - SupportsHttps = SupportsHttps, + SupportsHttps = CanConnectWithHttps, HttpsPortNumber = HttpsPort, OperatingSystem = OperatingSystem.Id.ToString(), OperatingSystemDisplayName = OperatingSystem.Name, @@ -1446,9 +1446,14 @@ namespace Emby.Server.Implementations }; } - public bool EnableHttps => SupportsHttps && ServerConfigurationManager.Configuration.EnableHttps; + /// + public bool ListenWithHttps => Certificate != null && ServerConfigurationManager.Configuration.EnableHttps; - public bool SupportsHttps => Certificate != null || ServerConfigurationManager.Configuration.IsBehindProxy; + /// + /// Gets a value indicating whether a client can connect to the server over HTTPS, either directly or via a + /// reverse proxy. + /// + public bool CanConnectWithHttps => ListenWithHttps || ServerConfigurationManager.Configuration.IsBehindProxy; public async Task GetLocalApiUrl(CancellationToken cancellationToken) { @@ -1509,10 +1514,10 @@ namespace Emby.Server.Implementations public string GetLocalApiUrl(ReadOnlySpan host) { var url = new StringBuilder(64); - url.Append(EnableHttps ? "https://" : "http://") + url.Append(ListenWithHttps ? "https://" : "http://") .Append(host) .Append(':') - .Append(EnableHttps ? HttpsPort : HttpPort); + .Append(ListenWithHttps ? HttpsPort : HttpPort); string baseUrl = ServerConfigurationManager.Configuration.BaseUrl; if (baseUrl.Length != 0) diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index e290c62e1..2023c470a 100644 --- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.EntryPoints .Append(config.PublicPort).Append(Separator) .Append(_appHost.HttpPort).Append(Separator) .Append(_appHost.HttpsPort).Append(Separator) - .Append(_appHost.EnableHttps).Append(Separator) + .Append(_appHost.ListenWithHttps).Append(Separator) .Append(config.EnableRemoteAccess).Append(Separator) .ToString(); } diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 7a812f320..e3f8ec014 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -422,7 +422,7 @@ 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.ListenWithHttps) { if (urlString.IndexOf("https://", StringComparison.OrdinalIgnoreCase) == -1) { diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 4abdd59aa..ddd1054ad 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -274,7 +274,7 @@ namespace Jellyfin.Server _logger.LogInformation("Kestrel listening on {IpAddress}", address); options.Listen(address, appHost.HttpPort); - if (appHost.EnableHttps && appHost.Certificate != null) + if (appHost.ListenWithHttps) { options.Listen(address, appHost.HttpsPort, listenOptions => { @@ -289,7 +289,7 @@ namespace Jellyfin.Server _logger.LogInformation("Kestrel listening on all interfaces"); options.ListenAnyIP(appHost.HttpPort); - if (appHost.EnableHttps && appHost.Certificate != null) + if (appHost.ListenWithHttps) { options.ListenAnyIP(appHost.HttpsPort, listenOptions => { diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 608ffc61c..d999f76db 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -39,10 +39,9 @@ namespace MediaBrowser.Controller int HttpsPort { get; } /// - /// Gets a value indicating whether [supports HTTPS]. + /// Gets a value indicating whether the server should listen on an HTTPS port. /// - /// true if [supports HTTPS]; otherwise, false. - bool EnableHttps { get; } + bool ListenWithHttps { get; } /// /// Gets a value indicating whether this instance has update available. diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index cfa7684c9..83c60563e 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -124,9 +124,9 @@ namespace MediaBrowser.Model.System public int HttpServerPortNumber { get; set; } /// - /// Gets or sets a value indicating whether [enable HTTPS]. + /// Gets or sets a value indicating whether a client can connect to the server over HTTPS, either directly or + /// via a reverse proxy. /// - /// true if [enable HTTPS]; otherwise, false. public bool SupportsHttps { get; set; } /// -- cgit v1.2.3 From 387fa474aa8ee8e237648ab0ea3130b8b35cf92f Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Thu, 2 Apr 2020 17:45:33 -0400 Subject: Document HTTPS configuration options --- .../HttpServer/HttpListenerHost.cs | 4 +++ .../Configuration/ServerConfiguration.cs | 35 +++++++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index e3f8ec014..dc542af78 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -420,6 +420,10 @@ namespace Emby.Server.Implementations.HttpServer return true; } + /// + /// Validate a connection from a remote IP address to a URL to see if a redirection to HTTPS is required. + /// + /// True if the request is valid, or false if the request is not valid and an HTTPS redirect is required. private bool ValidateSsl(string remoteIp, string urlString) { if (_config.Configuration.RequireHttps && _appHost.ListenWithHttps) diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 3107ec242..b14b347cc 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -45,17 +45,24 @@ namespace MediaBrowser.Model.Configuration public int HttpsPortNumber { get; set; } /// - /// Gets or sets a value indicating whether [use HTTPS]. + /// Gets or sets a value indicating whether to use HTTPS. /// - /// true if [use HTTPS]; otherwise, false. + /// + /// In order for HTTPS to be used, in addition to setting this to true, valid values must also be + /// provided for and . + /// public bool EnableHttps { get; set; } + public bool EnableNormalizedItemByNameIds { get; set; } /// - /// Gets or sets the value pointing to the file system where the ssl certificate is located.. + /// Gets or sets the filesystem path of an X.509 certificate to use for SSL. /// - /// The value pointing to the file system where the ssl certificate is located.. public string CertificatePath { get; set; } + + /// + /// Gets or sets the password required to access the X.509 certificate data in the file specified by . + /// public string CertificatePassword { get; set; } /// @@ -65,8 +72,11 @@ namespace MediaBrowser.Model.Configuration public bool IsPortAuthorized { get; set; } public bool AutoRunWebApp { get; set; } + public bool EnableRemoteAccess { get; set; } + public bool CameraUploadUpgraded { get; set; } + public bool CollectionsUpgraded { get; set; } /// @@ -82,6 +92,7 @@ namespace MediaBrowser.Model.Configuration /// /// The metadata path. public string MetadataPath { get; set; } + public string MetadataNetworkPath { get; set; } /// @@ -204,15 +215,31 @@ namespace MediaBrowser.Model.Configuration public int RemoteClientBitrateLimit { get; set; } public bool EnableFolderView { get; set; } + public bool EnableGroupingIntoCollections { get; set; } + public bool DisplaySpecialsWithinSeasons { get; set; } + public string[] LocalNetworkSubnets { get; set; } + public string[] LocalNetworkAddresses { get; set; } + public string[] CodecsUsed { get; set; } + public bool IgnoreVirtualInterfaces { get; set; } + public bool EnableExternalContentInSuggestions { get; set; } + + /// + /// Gets or sets a value indicating whether the server should force connections over HTTPS. + /// public bool RequireHttps { get; set; } + + /// + /// Gets or sets a value indicating whether the server is behind a reverse proxy. + /// public bool IsBehindProxy { get; set; } + public bool EnableNewOmdbSupport { get; set; } public string[] RemoteIPFilter { get; set; } -- cgit v1.2.3 From 30ce346f343ca61f921ec7d6faf2f06311c04e71 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 5 Apr 2020 18:10:56 +0200 Subject: Enable nullabe reference types for MediaBrowser.Model --- Emby.Dlna/Ssdp/DeviceDiscovery.cs | 16 +++--- .../Configuration/ServerConfigurationManager.cs | 2 +- .../Cryptography/CryptographyProvider.cs | 4 +- .../Devices/DeviceManager.cs | 16 ++---- .../EntryPoints/UdpServerEntryPoint.cs | 3 +- .../Library/MediaSourceManager.cs | 6 +-- Emby.Server.Implementations/Library/UserManager.cs | 10 ++-- .../LiveTv/EmbyTV/TimerManager.cs | 4 +- .../LiveTv/LiveTvManager.cs | 57 ++++++---------------- .../Playlists/PlaylistManager.cs | 5 +- .../ScheduledTasks/ScheduledTaskWorker.cs | 11 ++--- .../ScheduledTasks/TaskManager.cs | 11 +---- .../Updates/InstallationManager.cs | 2 +- MediaBrowser.Api/EnvironmentService.cs | 15 +----- MediaBrowser.Api/Images/RemoteImageService.cs | 3 +- MediaBrowser.Controller/LiveTv/TimerEventInfo.cs | 13 ++++- MediaBrowser.Model/Activity/ActivityLogEntry.cs | 1 + .../ApiClient/ServerDiscoveryInfo.cs | 1 + MediaBrowser.Model/Branding/BrandingOptions.cs | 1 + MediaBrowser.Model/Channels/ChannelFeatures.cs | 1 + MediaBrowser.Model/Channels/ChannelInfo.cs | 1 + MediaBrowser.Model/Channels/ChannelQuery.cs | 6 +++ .../Configuration/BaseApplicationConfiguration.cs | 1 + .../Configuration/EncodingOptions.cs | 13 +++++ MediaBrowser.Model/Configuration/LibraryOptions.cs | 1 + .../Configuration/MetadataOptions.cs | 4 ++ MediaBrowser.Model/Configuration/MetadataPlugin.cs | 1 + .../Configuration/MetadataPluginSummary.cs | 1 + .../Configuration/ServerConfiguration.cs | 1 + .../Configuration/UserConfiguration.cs | 1 + .../Configuration/XbmcMetadataOptions.cs | 1 + MediaBrowser.Model/Cryptography/ICryptoProvider.cs | 2 +- MediaBrowser.Model/Devices/ContentUploadHistory.cs | 6 ++- MediaBrowser.Model/Devices/DeviceInfo.cs | 1 + MediaBrowser.Model/Devices/DevicesOptions.cs | 3 ++ MediaBrowser.Model/Devices/LocalFileInfo.cs | 4 ++ MediaBrowser.Model/Diagnostics/IProcess.cs | 8 +++ MediaBrowser.Model/Diagnostics/IProcessFactory.cs | 11 +++++ MediaBrowser.Model/Dlna/AudioOptions.cs | 6 +++ MediaBrowser.Model/Dlna/CodecProfile.cs | 1 + MediaBrowser.Model/Dlna/ConditionProcessor.cs | 36 ++------------ MediaBrowser.Model/Dlna/ContainerProfile.cs | 4 +- MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs | 6 ++- MediaBrowser.Model/Dlna/DeviceIdentification.cs | 1 + MediaBrowser.Model/Dlna/DeviceProfile.cs | 1 + MediaBrowser.Model/Dlna/DeviceProfileInfo.cs | 1 + MediaBrowser.Model/Dlna/DirectPlayProfile.cs | 1 + MediaBrowser.Model/Dlna/HttpHeaderInfo.cs | 1 + MediaBrowser.Model/Dlna/ITranscoderSupport.cs | 5 ++ .../Dlna/MediaFormatProfileResolver.cs | 24 +++++---- MediaBrowser.Model/Dlna/ProfileCondition.cs | 25 +++++----- MediaBrowser.Model/Dlna/ResolutionConfiguration.cs | 1 + MediaBrowser.Model/Dlna/ResolutionNormalizer.cs | 5 +- MediaBrowser.Model/Dlna/ResolutionOptions.cs | 1 + MediaBrowser.Model/Dlna/ResponseProfile.cs | 4 +- MediaBrowser.Model/Dlna/SearchCriteria.cs | 9 ++-- MediaBrowser.Model/Dlna/StreamBuilder.cs | 2 + MediaBrowser.Model/Dlna/StreamInfo.cs | 1 + MediaBrowser.Model/Dlna/SubtitleProfile.cs | 1 + MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs | 9 ++++ MediaBrowser.Model/Dlna/TranscodingProfile.cs | 1 + MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs | 4 ++ MediaBrowser.Model/Dlna/VideoOptions.cs | 1 + MediaBrowser.Model/Dlna/XmlAttribute.cs | 1 + MediaBrowser.Model/Drawing/DrawingUtils.cs | 5 +- MediaBrowser.Model/Dto/BaseItemDto.cs | 1 + MediaBrowser.Model/Dto/BaseItemPerson.cs | 1 + MediaBrowser.Model/Dto/IHasServerId.cs | 1 + MediaBrowser.Model/Dto/ImageByNameInfo.cs | 1 + MediaBrowser.Model/Dto/ImageInfo.cs | 5 +- MediaBrowser.Model/Dto/ImageOptions.cs | 17 ++++--- MediaBrowser.Model/Dto/ItemIndex.cs | 20 -------- MediaBrowser.Model/Dto/MediaSourceInfo.cs | 1 + MediaBrowser.Model/Dto/MetadataEditorInfo.cs | 1 + MediaBrowser.Model/Dto/NameIdPair.cs | 1 + MediaBrowser.Model/Dto/NameValuePair.cs | 2 +- MediaBrowser.Model/Dto/RecommendationDto.cs | 1 + MediaBrowser.Model/Dto/UserDto.cs | 1 + MediaBrowser.Model/Dto/UserItemDataDto.cs | 1 + MediaBrowser.Model/Entities/ChapterInfo.cs | 1 + MediaBrowser.Model/Entities/DisplayPreferences.cs | 1 + MediaBrowser.Model/Entities/IHasProviderIds.cs | 2 +- MediaBrowser.Model/Entities/LibraryUpdateInfo.cs | 29 +++++------ MediaBrowser.Model/Entities/MediaAttachment.cs | 1 + MediaBrowser.Model/Entities/MediaStream.cs | 1 + MediaBrowser.Model/Entities/MediaUrl.cs | 1 + MediaBrowser.Model/Entities/PackageReviewInfo.cs | 13 ++--- MediaBrowser.Model/Entities/ParentalRating.cs | 24 ++++----- .../Entities/ProviderIdsExtensions.cs | 12 ++--- MediaBrowser.Model/Entities/VirtualFolderInfo.cs | 1 + MediaBrowser.Model/Events/GenericEventArgs.cs | 7 --- MediaBrowser.Model/Extensions/ListHelper.cs | 2 + MediaBrowser.Model/Extensions/StringHelper.cs | 4 +- MediaBrowser.Model/Globalization/CountryInfo.cs | 1 + MediaBrowser.Model/Globalization/CultureDto.cs | 1 + .../Globalization/ILocalizationManager.cs | 1 + .../Globalization/LocalizationOption.cs | 2 + MediaBrowser.Model/IO/FileSystemEntryInfo.cs | 27 +++++++--- MediaBrowser.Model/IO/FileSystemMetadata.cs | 1 + MediaBrowser.Model/IO/IFileSystem.cs | 1 + MediaBrowser.Model/IO/IIsoManager.cs | 1 - MediaBrowser.Model/IO/IIsoMount.cs | 2 +- MediaBrowser.Model/IO/IIsoMounter.cs | 12 ++--- MediaBrowser.Model/IO/IStreamHelper.cs | 1 + MediaBrowser.Model/Library/UserViewQuery.cs | 12 ++--- MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs | 1 + MediaBrowser.Model/LiveTv/GuideInfo.cs | 1 + MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs | 1 + MediaBrowser.Model/LiveTv/LiveTvInfo.cs | 12 ++--- MediaBrowser.Model/LiveTv/LiveTvOptions.cs | 1 + MediaBrowser.Model/LiveTv/LiveTvServiceInfo.cs | 1 + MediaBrowser.Model/LiveTv/RecordingQuery.cs | 1 + MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs | 3 +- MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs | 2 +- MediaBrowser.Model/LiveTv/TimerInfoDto.cs | 1 + MediaBrowser.Model/LiveTv/TimerQuery.cs | 1 + MediaBrowser.Model/MediaBrowser.Model.csproj | 2 + MediaBrowser.Model/MediaInfo/AudioCodec.cs | 4 +- MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs | 1 + MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs | 18 ++++--- MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs | 7 ++- MediaBrowser.Model/MediaInfo/MediaInfo.cs | 1 + .../MediaInfo/PlaybackInfoRequest.cs | 1 + .../MediaInfo/PlaybackInfoResponse.cs | 2 +- MediaBrowser.Model/MediaInfo/SubtitleTrackEvent.cs | 4 ++ MediaBrowser.Model/Net/EndPointInfo.cs | 1 + MediaBrowser.Model/Net/ISocket.cs | 1 + MediaBrowser.Model/Net/MimeTypes.cs | 18 +++---- MediaBrowser.Model/Net/NetworkShare.cs | 1 + MediaBrowser.Model/Net/SocketReceiveResult.cs | 10 ++-- MediaBrowser.Model/Net/WebSocketMessage.cs | 1 + .../Notifications/NotificationOption.cs | 16 +++--- .../Notifications/NotificationOptions.cs | 37 ++++++-------- .../Notifications/NotificationRequest.cs | 1 + .../Notifications/NotificationTypeInfo.cs | 1 + .../Playlists/PlaylistCreationRequest.cs | 1 + .../Playlists/PlaylistCreationResult.cs | 7 ++- MediaBrowser.Model/Playlists/PlaylistItemQuery.cs | 39 --------------- MediaBrowser.Model/Plugins/PluginInfo.cs | 1 + MediaBrowser.Model/Plugins/PluginPageInfo.cs | 1 + MediaBrowser.Model/Providers/ExternalIdInfo.cs | 1 + MediaBrowser.Model/Providers/ExternalUrl.cs | 1 + MediaBrowser.Model/Providers/ImageProviderInfo.cs | 11 +++-- MediaBrowser.Model/Providers/RemoteImageInfo.cs | 1 + MediaBrowser.Model/Providers/RemoteImageQuery.cs | 7 ++- MediaBrowser.Model/Providers/RemoteImageResult.cs | 1 + MediaBrowser.Model/Providers/RemoteSearchResult.cs | 20 ++++++-- MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs | 1 + MediaBrowser.Model/Providers/SubtitleOptions.cs | 1 + .../Providers/SubtitleProviderInfo.cs | 1 + MediaBrowser.Model/Querying/AllThemeMediaResult.cs | 13 ++--- MediaBrowser.Model/Querying/EpisodeQuery.cs | 1 + MediaBrowser.Model/Querying/ItemCountsQuery.cs | 20 -------- MediaBrowser.Model/Querying/ItemSortBy.cs | 54 ++++++++++++++------ MediaBrowser.Model/Querying/LatestItemsQuery.cs | 20 +++++--- .../Querying/MovieRecommendationQuery.cs | 1 + MediaBrowser.Model/Querying/NextUpQuery.cs | 1 + MediaBrowser.Model/Querying/QueryFilters.cs | 1 + MediaBrowser.Model/Querying/QueryResult.cs | 1 + MediaBrowser.Model/Querying/ThemeMediaResult.cs | 2 +- .../Querying/UpcomingEpisodesQuery.cs | 1 + MediaBrowser.Model/Search/SearchHint.cs | 1 + MediaBrowser.Model/Search/SearchHintResult.cs | 1 + MediaBrowser.Model/Search/SearchQuery.cs | 1 + .../Serialization/IJsonSerializer.cs | 1 + MediaBrowser.Model/Serialization/IXmlSerializer.cs | 1 + MediaBrowser.Model/Services/ApiMemberAttribute.cs | 1 + MediaBrowser.Model/Services/IHasRequestFilter.cs | 10 ++-- MediaBrowser.Model/Services/IHttpRequest.cs | 4 +- MediaBrowser.Model/Services/IHttpResult.cs | 11 +++-- MediaBrowser.Model/Services/IRequest.cs | 1 + .../Services/QueryParamCollection.cs | 6 +-- MediaBrowser.Model/Services/RouteAttribute.cs | 1 + MediaBrowser.Model/Session/BrowseRequest.cs | 1 + MediaBrowser.Model/Session/ClientCapabilities.cs | 1 + MediaBrowser.Model/Session/GeneralCommand.cs | 1 + MediaBrowser.Model/Session/MessageCommand.cs | 1 + MediaBrowser.Model/Session/PlayRequest.cs | 1 + MediaBrowser.Model/Session/PlaybackProgressInfo.cs | 1 + MediaBrowser.Model/Session/PlaybackStopInfo.cs | 1 + MediaBrowser.Model/Session/PlayerStateInfo.cs | 1 + MediaBrowser.Model/Session/PlaystateRequest.cs | 2 +- MediaBrowser.Model/Session/SessionUserInfo.cs | 2 + MediaBrowser.Model/Session/TranscodingInfo.cs | 5 +- MediaBrowser.Model/Session/UserDataChangeInfo.cs | 1 + MediaBrowser.Model/Sync/SyncJob.cs | 1 + MediaBrowser.Model/Sync/SyncTarget.cs | 1 + MediaBrowser.Model/System/LogFile.cs | 1 + MediaBrowser.Model/System/PublicSystemInfo.cs | 1 + MediaBrowser.Model/System/SystemInfo.cs | 2 +- MediaBrowser.Model/System/WakeOnLanInfo.cs | 31 ++++++------ MediaBrowser.Model/Tasks/IScheduledTask.cs | 11 +++-- MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs | 1 + MediaBrowser.Model/Tasks/ScheduledTaskHelpers.cs | 4 +- .../Tasks/TaskCompletionEventArgs.cs | 10 +++- MediaBrowser.Model/Tasks/TaskInfo.cs | 1 + MediaBrowser.Model/Tasks/TaskResult.cs | 1 + MediaBrowser.Model/Tasks/TaskTriggerInfo.cs | 1 + MediaBrowser.Model/Updates/CheckForUpdateResult.cs | 1 + MediaBrowser.Model/Updates/InstallationInfo.cs | 1 + MediaBrowser.Model/Updates/PackageInfo.cs | 1 + MediaBrowser.Model/Updates/PackageVersionInfo.cs | 1 + MediaBrowser.Model/Users/ForgotPasswordResult.cs | 1 + MediaBrowser.Model/Users/PinRedeemResult.cs | 1 + MediaBrowser.Model/Users/UserAction.cs | 1 + MediaBrowser.Model/Users/UserPolicy.cs | 1 + .../Manager/ItemImageProvider.cs | 24 +++++---- MediaBrowser.Providers/Manager/ProviderManager.cs | 6 +-- 208 files changed, 636 insertions(+), 506 deletions(-) delete mode 100644 MediaBrowser.Model/Dto/ItemIndex.cs delete mode 100644 MediaBrowser.Model/Playlists/PlaylistItemQuery.cs delete mode 100644 MediaBrowser.Model/Querying/ItemCountsQuery.cs diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs index f95b8ce7d..ab5e56ab0 100644 --- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs +++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs @@ -100,15 +100,13 @@ namespace Emby.Dlna.Ssdp var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase); - var args = new GenericEventArgs - { - Argument = new UpnpDeviceInfo + var args = new GenericEventArgs( + new UpnpDeviceInfo { Location = e.DiscoveredDevice.DescriptionLocation, Headers = headers, LocalIpAddress = e.LocalIpAddress - } - }; + }); DeviceDiscoveredInternal?.Invoke(this, args); } @@ -121,14 +119,12 @@ namespace Emby.Dlna.Ssdp var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase); - var args = new GenericEventArgs - { - Argument = new UpnpDeviceInfo + var args = new GenericEventArgs( + new UpnpDeviceInfo { Location = e.DiscoveredDevice.DescriptionLocation, Headers = headers - } - }; + }); DeviceLeft?.Invoke(this, args); } diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs index f407317ec..5062683a1 100644 --- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -91,7 +91,7 @@ namespace Emby.Server.Implementations.Configuration ValidateMetadataPath(newConfig); ValidateSslCertificate(newConfig); - ConfigurationUpdating?.Invoke(this, new GenericEventArgs { Argument = newConfig }); + ConfigurationUpdating?.Invoke(this, new GenericEventArgs(newConfig)); base.ReplaceConfiguration(newConfiguration); } diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index de83b023d..1e42dbf67 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; using System.Collections.Generic; using System.Security.Cryptography; @@ -134,8 +136,6 @@ namespace Emby.Server.Implementations.Cryptography _randomNumberGenerator.Dispose(); } - _randomNumberGenerator = null; - _disposed = true; } } diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index adb8e793d..e8837892c 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -86,13 +86,7 @@ namespace Emby.Server.Implementations.Devices { _authRepo.UpdateDeviceOptions(deviceId, options); - if (DeviceOptionsUpdated != null) - { - DeviceOptionsUpdated(this, new GenericEventArgs>() - { - Argument = new Tuple(deviceId, options) - }); - } + DeviceOptionsUpdated?.Invoke(this, new GenericEventArgs>(new Tuple(deviceId, options))); } public DeviceOptions GetDeviceOptions(string deviceId) @@ -251,14 +245,12 @@ namespace Emby.Server.Implementations.Devices if (CameraImageUploaded != null) { - CameraImageUploaded?.Invoke(this, new GenericEventArgs - { - Argument = new CameraImageUploadInfo + CameraImageUploaded?.Invoke(this, new GenericEventArgs( + new CameraImageUploadInfo { Device = device, FileInfo = file - } - }); + })); } } diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index 50ba0f8fa..fa566d24b 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -44,10 +44,11 @@ namespace Emby.Server.Implementations.EntryPoints } /// - public async Task RunAsync() + public Task RunAsync() { _udpServer = new UdpServer(_logger, _appHost); _udpServer.Start(PortNumber, _cancellationTokenSource.Token); + return Task.CompletedTask; } /// diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 70d5bd9f4..4f12ad046 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -521,11 +521,7 @@ namespace Emby.Server.Implementations.Library SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user); } - return new Tuple(new LiveStreamResponse - { - MediaSource = clone - - }, liveStream as IDirectStreamProvider); + return new Tuple(new LiveStreamResponse(clone), liveStream as IDirectStreamProvider); } private static void AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio) diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 7b17cc913..614ab5669 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -131,7 +131,7 @@ namespace Emby.Server.Implementations.Library /// The user. private void OnUserUpdated(User user) { - UserUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); + UserUpdated?.Invoke(this, new GenericEventArgs(user)); } /// @@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.Library /// The user. private void OnUserDeleted(User user) { - UserDeleted?.Invoke(this, new GenericEventArgs { Argument = user }); + UserDeleted?.Invoke(this, new GenericEventArgs(user)); } public NameIdPair[] GetAuthenticationProviders() @@ -755,7 +755,7 @@ namespace Emby.Server.Implementations.Library _userRepository.CreateUser(user); - EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs { Argument = user }, _logger); + EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs(user), _logger); return user; } @@ -980,7 +980,7 @@ namespace Emby.Server.Implementations.Library if (fireEvent) { - UserPolicyUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); + UserPolicyUpdated?.Invoke(this, new GenericEventArgs(user)); } } @@ -1050,7 +1050,7 @@ namespace Emby.Server.Implementations.Library if (fireEvent) { - UserConfigurationUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); + UserConfigurationUpdated?.Invoke(this, new GenericEventArgs(user)); } } } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index 7ebb043d8..285a59a24 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -109,7 +109,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (startDate < now) { - TimerFired?.Invoke(this, new GenericEventArgs { Argument = item }); + TimerFired?.Invoke(this, new GenericEventArgs(item)); return; } @@ -151,7 +151,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var timer = GetAll().FirstOrDefault(i => string.Equals(i.Id, timerId, StringComparison.OrdinalIgnoreCase)); if (timer != null) { - TimerFired?.Invoke(this, new GenericEventArgs { Argument = timer }); + TimerFired?.Invoke(this, new GenericEventArgs(timer)); } } diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index b64fe8634..16c659532 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -149,27 +149,18 @@ namespace Emby.Server.Implementations.LiveTv { var timerId = e.Argument; - TimerCancelled?.Invoke(this, new GenericEventArgs - { - Argument = new TimerEventInfo - { - Id = timerId - } - }); + TimerCancelled?.Invoke(this, new GenericEventArgs(new TimerEventInfo(timerId))); } private void OnEmbyTvTimerCreated(object sender, GenericEventArgs e) { var timer = e.Argument; - TimerCreated?.Invoke(this, new GenericEventArgs - { - Argument = new TimerEventInfo + TimerCreated?.Invoke(this, new GenericEventArgs( + new TimerEventInfo(timer.Id) { - ProgramId = _tvDtoService.GetInternalProgramId(timer.ProgramId), - Id = timer.Id - } - }); + ProgramId = _tvDtoService.GetInternalProgramId(timer.ProgramId) + })); } public List GetTunerHostTypes() @@ -1725,13 +1716,7 @@ namespace Emby.Server.Implementations.LiveTv if (!(service is EmbyTV.EmbyTV)) { - TimerCancelled?.Invoke(this, new GenericEventArgs - { - Argument = new TimerEventInfo - { - Id = id - } - }); + TimerCancelled?.Invoke(this, new GenericEventArgs(new TimerEventInfo(id))); } } @@ -1748,13 +1733,7 @@ namespace Emby.Server.Implementations.LiveTv await service.CancelSeriesTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false); - SeriesTimerCancelled?.Invoke(this, new GenericEventArgs - { - Argument = new TimerEventInfo - { - Id = id - } - }); + SeriesTimerCancelled?.Invoke(this, new GenericEventArgs(new TimerEventInfo(id))); } public async Task GetTimer(string id, CancellationToken cancellationToken) @@ -2073,14 +2052,11 @@ namespace Emby.Server.Implementations.LiveTv if (!(service is EmbyTV.EmbyTV)) { - TimerCreated?.Invoke(this, new GenericEventArgs - { - Argument = new TimerEventInfo + TimerCreated?.Invoke(this, new GenericEventArgs( + new TimerEventInfo(newTimerId) { - ProgramId = _tvDtoService.GetInternalProgramId(info.ProgramId), - Id = newTimerId - } - }); + ProgramId = _tvDtoService.GetInternalProgramId(info.ProgramId) + })); } } @@ -2105,14 +2081,11 @@ namespace Emby.Server.Implementations.LiveTv await service.CreateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false); } - SeriesTimerCreated?.Invoke(this, new GenericEventArgs - { - Argument = new TimerEventInfo + SeriesTimerCreated?.Invoke(this, new GenericEventArgs( + new TimerEventInfo(newTimerId) { - ProgramId = _tvDtoService.GetInternalProgramId(info.ProgramId), - Id = newTimerId - } - }); + ProgramId = _tvDtoService.GetInternalProgramId(info.ProgramId) + })); } public async Task UpdateTimer(TimerInfoDto timer, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 9b1510ac9..021bc47cd 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -153,10 +153,7 @@ namespace Emby.Server.Implementations.Playlists }); } - return new PlaylistCreationResult - { - Id = playlist.Id.ToString("N", CultureInfo.InvariantCulture) - }; + return new PlaylistCreationResult(playlist.Id.ToString("N", CultureInfo.InvariantCulture)); } finally { diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 5b188d962..ca983764b 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -392,7 +392,7 @@ namespace Emby.Server.Implementations.ScheduledTasks ((TaskManager)TaskManager).OnTaskExecuting(this); - progress.ProgressChanged += progress_ProgressChanged; + progress.ProgressChanged += OnProgressChanged; TaskCompletionStatus status; CurrentExecutionStartTime = DateTime.UtcNow; @@ -426,7 +426,7 @@ namespace Emby.Server.Implementations.ScheduledTasks var startTime = CurrentExecutionStartTime; var endTime = DateTime.UtcNow; - progress.ProgressChanged -= progress_ProgressChanged; + progress.ProgressChanged -= OnProgressChanged; CurrentCancellationTokenSource.Dispose(); CurrentCancellationTokenSource = null; CurrentProgress = null; @@ -439,16 +439,13 @@ namespace Emby.Server.Implementations.ScheduledTasks /// /// The sender. /// The e. - void progress_ProgressChanged(object sender, double e) + private void OnProgressChanged(object sender, double e) { e = Math.Min(e, 100); CurrentProgress = e; - TaskProgress?.Invoke(this, new GenericEventArgs - { - Argument = e - }); + TaskProgress?.Invoke(this, new GenericEventArgs(e)); } /// diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index ecf58dbc0..f2e04d1fb 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -254,10 +254,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// The task. internal void OnTaskExecuting(IScheduledTaskWorker task) { - TaskExecuting?.Invoke(this, new GenericEventArgs - { - Argument = task - }); + TaskExecuting?.Invoke(this, new GenericEventArgs(task)); } /// @@ -267,11 +264,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// The result. internal void OnTaskCompleted(IScheduledTaskWorker task, TaskResult result) { - TaskCompleted?.Invoke(task, new TaskCompletionEventArgs - { - Result = result, - Task = task - }); + TaskCompleted?.Invoke(task, new TaskCompletionEventArgs(task, result)); ExecuteQueuedTasks(); } diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index c897036eb..51563fd5d 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -427,7 +427,7 @@ namespace Emby.Server.Implementations.Updates _config.SaveConfiguration(); } - PluginUninstalled?.Invoke(this, new GenericEventArgs { Argument = plugin }); + PluginUninstalled?.Invoke(this, new GenericEventArgs(plugin)); _applicationHost.NotifyPendingRestart(); } diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs index 36b03f09c..10726e2aa 100644 --- a/MediaBrowser.Api/EnvironmentService.cs +++ b/MediaBrowser.Api/EnvironmentService.cs @@ -226,12 +226,7 @@ namespace MediaBrowser.Api /// IEnumerable{FileSystemEntryInfo}. private IEnumerable GetDrives() { - return _fileSystem.GetDrives().Select(d => new FileSystemEntryInfo - { - Name = d.Name, - Path = d.FullName, - Type = FileSystemEntryType.Directory - }); + return _fileSystem.GetDrives().Select(d => new FileSystemEntryInfo(d.Name, d.FullName, FileSystemEntryType.Directory)); } /// @@ -266,13 +261,7 @@ namespace MediaBrowser.Api return true; }); - return entries.Select(f => new FileSystemEntryInfo - { - Name = f.Name, - Path = f.FullName, - Type = f.IsDirectory ? FileSystemEntryType.Directory : FileSystemEntryType.File - - }); + return entries.Select(f => new FileSystemEntryInfo(f.Name, f.FullName, f.IsDirectory ? FileSystemEntryType.Directory : FileSystemEntryType.File)); } public object Get(GetParentPath request) diff --git a/MediaBrowser.Api/Images/RemoteImageService.cs b/MediaBrowser.Api/Images/RemoteImageService.cs index f03f5efd8..3e4198aae 100644 --- a/MediaBrowser.Api/Images/RemoteImageService.cs +++ b/MediaBrowser.Api/Images/RemoteImageService.cs @@ -147,9 +147,8 @@ namespace MediaBrowser.Api.Images { var item = _libraryManager.GetItemById(request.Id); - var images = await _providerManager.GetAvailableRemoteImages(item, new RemoteImageQuery + var images = await _providerManager.GetAvailableRemoteImages(item, new RemoteImageQuery(request.ProviderName) { - ProviderName = request.ProviderName, IncludeAllLanguages = request.IncludeAllLanguages, IncludeDisabledProviders = true, ImageType = request.Type diff --git a/MediaBrowser.Controller/LiveTv/TimerEventInfo.cs b/MediaBrowser.Controller/LiveTv/TimerEventInfo.cs index cfec39b4e..1b8f41db6 100644 --- a/MediaBrowser.Controller/LiveTv/TimerEventInfo.cs +++ b/MediaBrowser.Controller/LiveTv/TimerEventInfo.cs @@ -1,10 +1,19 @@ +#nullable enable +#pragma warning disable CS1591 + using System; namespace MediaBrowser.Controller.LiveTv { public class TimerEventInfo { - public string Id { get; set; } - public Guid ProgramId { get; set; } + public TimerEventInfo(string id) + { + Id = id; + } + + public string Id { get; } + + public Guid? ProgramId { get; set; } } } diff --git a/MediaBrowser.Model/Activity/ActivityLogEntry.cs b/MediaBrowser.Model/Activity/ActivityLogEntry.cs index 80f01b66e..865d07b2c 100644 --- a/MediaBrowser.Model/Activity/ActivityLogEntry.cs +++ b/MediaBrowser.Model/Activity/ActivityLogEntry.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/ApiClient/ServerDiscoveryInfo.cs b/MediaBrowser.Model/ApiClient/ServerDiscoveryInfo.cs index bb203f895..fcc90a1f7 100644 --- a/MediaBrowser.Model/ApiClient/ServerDiscoveryInfo.cs +++ b/MediaBrowser.Model/ApiClient/ServerDiscoveryInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.ApiClient diff --git a/MediaBrowser.Model/Branding/BrandingOptions.cs b/MediaBrowser.Model/Branding/BrandingOptions.cs index 8ab268a64..5ddf1e7e6 100644 --- a/MediaBrowser.Model/Branding/BrandingOptions.cs +++ b/MediaBrowser.Model/Branding/BrandingOptions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Branding diff --git a/MediaBrowser.Model/Channels/ChannelFeatures.cs b/MediaBrowser.Model/Channels/ChannelFeatures.cs index c4e97ffe5..496102d83 100644 --- a/MediaBrowser.Model/Channels/ChannelFeatures.cs +++ b/MediaBrowser.Model/Channels/ChannelFeatures.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Channels/ChannelInfo.cs b/MediaBrowser.Model/Channels/ChannelInfo.cs index bfb34db55..f2432aaeb 100644 --- a/MediaBrowser.Model/Channels/ChannelInfo.cs +++ b/MediaBrowser.Model/Channels/ChannelInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Channels diff --git a/MediaBrowser.Model/Channels/ChannelQuery.cs b/MediaBrowser.Model/Channels/ChannelQuery.cs index 88fc94a6f..d11260039 100644 --- a/MediaBrowser.Model/Channels/ChannelQuery.cs +++ b/MediaBrowser.Model/Channels/ChannelQuery.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -13,8 +14,11 @@ namespace MediaBrowser.Model.Channels /// /// The fields. public ItemFields[] Fields { get; set; } + public bool? EnableImages { get; set; } + public int? ImageTypeLimit { get; set; } + public ImageType[] EnableImageTypes { get; set; } /// @@ -48,7 +52,9 @@ namespace MediaBrowser.Model.Channels /// /// null if [is favorite] contains no value, true if [is favorite]; otherwise, false. public bool? IsFavorite { get; set; } + public bool? IsRecordingsFolder { get; set; } + public bool RefreshLatestChannelItems { get; set; } } } diff --git a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs index cc2541f74..cdd322c94 100644 --- a/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs +++ b/MediaBrowser.Model/Configuration/BaseApplicationConfiguration.cs @@ -1,3 +1,4 @@ +#nullable disable using System; using System.Xml.Serialization; diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 648568fd7..0c0e01f11 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Configuration @@ -5,10 +6,15 @@ namespace MediaBrowser.Model.Configuration public class EncodingOptions { public int EncodingThreadCount { get; set; } + public string TranscodingTempPath { get; set; } + public double DownMixAudioBoost { get; set; } + public bool EnableThrottling { get; set; } + public int ThrottleDelaySeconds { get; set; } + public string HardwareAccelerationType { get; set; } /// @@ -20,12 +26,19 @@ namespace MediaBrowser.Model.Configuration /// The current FFmpeg path being used by the system and displayed on the transcode page. /// public string EncoderAppPathDisplay { get; set; } + public string VaapiDevice { get; set; } + public int H264Crf { get; set; } + public int H265Crf { get; set; } + public string EncoderPreset { get; set; } + public string DeinterlaceMethod { get; set; } + public bool EnableHardwareEncoding { get; set; } + public bool EnableSubtitleExtraction { get; set; } public string[] HardwareDecodingCodecs { get; set; } diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index 4342ccd8a..4229a4335 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Configuration/MetadataOptions.cs b/MediaBrowser.Model/Configuration/MetadataOptions.cs index 625054b9e..e7dc3da3c 100644 --- a/MediaBrowser.Model/Configuration/MetadataOptions.cs +++ b/MediaBrowser.Model/Configuration/MetadataOptions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -12,12 +13,15 @@ namespace MediaBrowser.Model.Configuration public string ItemType { get; set; } public string[] DisabledMetadataSavers { get; set; } + public string[] LocalMetadataReaderOrder { get; set; } public string[] DisabledMetadataFetchers { get; set; } + public string[] MetadataFetcherOrder { get; set; } public string[] DisabledImageFetchers { get; set; } + public string[] ImageFetcherOrder { get; set; } public MetadataOptions() diff --git a/MediaBrowser.Model/Configuration/MetadataPlugin.cs b/MediaBrowser.Model/Configuration/MetadataPlugin.cs index c2b47eb9b..db8cd1875 100644 --- a/MediaBrowser.Model/Configuration/MetadataPlugin.cs +++ b/MediaBrowser.Model/Configuration/MetadataPlugin.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Configuration diff --git a/MediaBrowser.Model/Configuration/MetadataPluginSummary.cs b/MediaBrowser.Model/Configuration/MetadataPluginSummary.cs index 53063810b..0c197ee02 100644 --- a/MediaBrowser.Model/Configuration/MetadataPluginSummary.cs +++ b/MediaBrowser.Model/Configuration/MetadataPluginSummary.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 3107ec242..333805e31 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs index a475c9910..289047d6b 100644 --- a/MediaBrowser.Model/Configuration/UserConfiguration.cs +++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs b/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs index d6c1295f4..c48a38192 100644 --- a/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs +++ b/MediaBrowser.Model/Configuration/XbmcMetadataOptions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Configuration diff --git a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs index 656c04f46..d8b7d848a 100644 --- a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs +++ b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs @@ -10,7 +10,7 @@ namespace MediaBrowser.Model.Cryptography IEnumerable GetSupportedHashMethods(); - byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt); + byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt); byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt); diff --git a/MediaBrowser.Model/Devices/ContentUploadHistory.cs b/MediaBrowser.Model/Devices/ContentUploadHistory.cs index c493760d5..868956df2 100644 --- a/MediaBrowser.Model/Devices/ContentUploadHistory.cs +++ b/MediaBrowser.Model/Devices/ContentUploadHistory.cs @@ -1,15 +1,19 @@ +#nullable disable #pragma warning disable CS1591 +using System; + namespace MediaBrowser.Model.Devices { public class ContentUploadHistory { public string DeviceId { get; set; } + public LocalFileInfo[] FilesUploaded { get; set; } public ContentUploadHistory() { - FilesUploaded = new LocalFileInfo[] { }; + FilesUploaded = Array.Empty(); } } } diff --git a/MediaBrowser.Model/Devices/DeviceInfo.cs b/MediaBrowser.Model/Devices/DeviceInfo.cs index d2563d1d0..0cccf931c 100644 --- a/MediaBrowser.Model/Devices/DeviceInfo.cs +++ b/MediaBrowser.Model/Devices/DeviceInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Devices/DevicesOptions.cs b/MediaBrowser.Model/Devices/DevicesOptions.cs index 02570650e..327b5836f 100644 --- a/MediaBrowser.Model/Devices/DevicesOptions.cs +++ b/MediaBrowser.Model/Devices/DevicesOptions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -7,7 +8,9 @@ namespace MediaBrowser.Model.Devices public class DevicesOptions { public string[] EnabledCameraUploadDevices { get; set; } + public string CameraUploadPath { get; set; } + public bool EnableCameraUploadSubfolders { get; set; } public DevicesOptions() diff --git a/MediaBrowser.Model/Devices/LocalFileInfo.cs b/MediaBrowser.Model/Devices/LocalFileInfo.cs index 63a8dc2aa..c3158b2f2 100644 --- a/MediaBrowser.Model/Devices/LocalFileInfo.cs +++ b/MediaBrowser.Model/Devices/LocalFileInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Devices @@ -5,8 +6,11 @@ namespace MediaBrowser.Model.Devices public class LocalFileInfo { public string Name { get; set; } + public string Id { get; set; } + public string Album { get; set; } + public string MimeType { get; set; } } } diff --git a/MediaBrowser.Model/Diagnostics/IProcess.cs b/MediaBrowser.Model/Diagnostics/IProcess.cs index 514d1e737..c067189a6 100644 --- a/MediaBrowser.Model/Diagnostics/IProcess.cs +++ b/MediaBrowser.Model/Diagnostics/IProcess.cs @@ -11,13 +11,21 @@ namespace MediaBrowser.Model.Diagnostics event EventHandler Exited; void Kill(); + bool WaitForExit(int timeMs); + Task WaitForExitAsync(int timeMs); + int ExitCode { get; } + void Start(); + StreamWriter StandardInput { get; } + StreamReader StandardError { get; } + StreamReader StandardOutput { get; } + ProcessOptions StartInfo { get; } } } diff --git a/MediaBrowser.Model/Diagnostics/IProcessFactory.cs b/MediaBrowser.Model/Diagnostics/IProcessFactory.cs index 57082acc5..2d15aed7e 100644 --- a/MediaBrowser.Model/Diagnostics/IProcessFactory.cs +++ b/MediaBrowser.Model/Diagnostics/IProcessFactory.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Diagnostics @@ -10,15 +11,25 @@ namespace MediaBrowser.Model.Diagnostics public class ProcessOptions { public string FileName { get; set; } + public string Arguments { get; set; } + public string WorkingDirectory { get; set; } + public bool CreateNoWindow { get; set; } + public bool UseShellExecute { get; set; } + public bool EnableRaisingEvents { get; set; } + public bool ErrorDialog { get; set; } + public bool RedirectStandardError { get; set; } + public bool RedirectStandardInput { get; set; } + public bool RedirectStandardOutput { get; set; } + public bool IsHidden { get; set; } } } diff --git a/MediaBrowser.Model/Dlna/AudioOptions.cs b/MediaBrowser.Model/Dlna/AudioOptions.cs index 40081b282..fc555c5f7 100644 --- a/MediaBrowser.Model/Dlna/AudioOptions.cs +++ b/MediaBrowser.Model/Dlna/AudioOptions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -19,12 +20,17 @@ namespace MediaBrowser.Model.Dlna } public bool EnableDirectPlay { get; set; } + public bool EnableDirectStream { get; set; } + public bool ForceDirectPlay { get; set; } + public bool ForceDirectStream { get; set; } public Guid ItemId { get; set; } + public MediaSourceInfo[] MediaSources { get; set; } + public DeviceProfile Profile { get; set; } /// diff --git a/MediaBrowser.Model/Dlna/CodecProfile.cs b/MediaBrowser.Model/Dlna/CodecProfile.cs index 756e500dd..cc5b840c7 100644 --- a/MediaBrowser.Model/Dlna/CodecProfile.cs +++ b/MediaBrowser.Model/Dlna/CodecProfile.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs index 7423efaf6..f3aaef930 100644 --- a/MediaBrowser.Model/Dlna/ConditionProcessor.cs +++ b/MediaBrowser.Model/Dlna/ConditionProcessor.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Model.Dlna int? height, int? videoBitDepth, int? videoBitrate, - string videoProfile, + string? videoProfile, double? videoLevel, float? videoFramerate, int? packetLength, @@ -25,7 +25,7 @@ namespace MediaBrowser.Model.Dlna int? refFrames, int? numVideoStreams, int? numAudioStreams, - string videoCodecTag, + string? videoCodecTag, bool? isAvc) { switch (condition.Property) @@ -103,7 +103,7 @@ namespace MediaBrowser.Model.Dlna int? audioBitrate, int? audioSampleRate, int? audioBitDepth, - string audioProfile, + string? audioProfile, bool? isSecondaryTrack) { switch (condition.Property) @@ -154,7 +154,7 @@ namespace MediaBrowser.Model.Dlna return false; } - private static bool IsConditionSatisfied(ProfileCondition condition, string currentValue) + private static bool IsConditionSatisfied(ProfileCondition condition, string? currentValue) { if (string.IsNullOrEmpty(currentValue)) { @@ -203,34 +203,6 @@ namespace MediaBrowser.Model.Dlna return false; } - private static bool IsConditionSatisfied(ProfileCondition condition, float currentValue) - { - if (currentValue <= 0) - { - // If the value is unknown, it satisfies if not marked as required - return !condition.IsRequired; - } - - if (float.TryParse(condition.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var expected)) - { - switch (condition.Condition) - { - case ProfileConditionType.Equals: - return currentValue.Equals(expected); - case ProfileConditionType.GreaterThanEqual: - return currentValue >= expected; - case ProfileConditionType.LessThanEqual: - return currentValue <= expected; - case ProfileConditionType.NotEquals: - return !currentValue.Equals(expected); - default: - throw new InvalidOperationException("Unexpected ProfileConditionType: " + condition.Condition); - } - } - - return false; - } - private static bool IsConditionSatisfied(ProfileCondition condition, double? currentValue) { if (!currentValue.HasValue) diff --git a/MediaBrowser.Model/Dlna/ContainerProfile.cs b/MediaBrowser.Model/Dlna/ContainerProfile.cs index e6691c513..1d18da6a0 100644 --- a/MediaBrowser.Model/Dlna/ContainerProfile.cs +++ b/MediaBrowser.Model/Dlna/ContainerProfile.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -10,6 +11,7 @@ namespace MediaBrowser.Model.Dlna { [XmlAttribute("type")] public DlnaProfileType Type { get; set; } + public ProfileCondition[] Conditions { get; set; } [XmlAttribute("container")] @@ -45,7 +47,7 @@ namespace MediaBrowser.Model.Dlna public static bool ContainsContainer(string profileContainers, string inputContainer) { var isNegativeList = false; - if (profileContainers != null && profileContainers.StartsWith("-")) + if (profileContainers != null && profileContainers.StartsWith("-", StringComparison.Ordinal)) { isNegativeList = true; profileContainers = profileContainers.Substring(1); diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs index a20f11503..b055ad41a 100644 --- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs +++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -32,7 +33,10 @@ namespace MediaBrowser.Model.Dlna DlnaFlags.InteractiveTransferMode | DlnaFlags.DlnaV15; - string dlnaflags = string.Format(";DLNA.ORG_FLAGS={0}", DlnaMaps.FlagsToString(flagValue)); + string dlnaflags = string.Format( + CultureInfo.InvariantCulture, + ";DLNA.ORG_FLAGS={0}", + DlnaMaps.FlagsToString(flagValue)); ResponseProfile mediaProfile = _profile.GetImageMediaProfile(container, width, diff --git a/MediaBrowser.Model/Dlna/DeviceIdentification.cs b/MediaBrowser.Model/Dlna/DeviceIdentification.cs index f1699d930..85cc9e3c1 100644 --- a/MediaBrowser.Model/Dlna/DeviceIdentification.cs +++ b/MediaBrowser.Model/Dlna/DeviceIdentification.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs index 0cefbbe01..704d4ec37 100644 --- a/MediaBrowser.Model/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Dlna/DeviceProfileInfo.cs b/MediaBrowser.Model/Dlna/DeviceProfileInfo.cs index 347583965..74c32c523 100644 --- a/MediaBrowser.Model/Dlna/DeviceProfileInfo.cs +++ b/MediaBrowser.Model/Dlna/DeviceProfileInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Dlna diff --git a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs index a5947bbf4..6f4b4ab1b 100644 --- a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs +++ b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System.Xml.Serialization; diff --git a/MediaBrowser.Model/Dlna/HttpHeaderInfo.cs b/MediaBrowser.Model/Dlna/HttpHeaderInfo.cs index f23a24084..17c4dffcc 100644 --- a/MediaBrowser.Model/Dlna/HttpHeaderInfo.cs +++ b/MediaBrowser.Model/Dlna/HttpHeaderInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System.Xml.Serialization; diff --git a/MediaBrowser.Model/Dlna/ITranscoderSupport.cs b/MediaBrowser.Model/Dlna/ITranscoderSupport.cs index 7e35cc85b..d9bd094d9 100644 --- a/MediaBrowser.Model/Dlna/ITranscoderSupport.cs +++ b/MediaBrowser.Model/Dlna/ITranscoderSupport.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Dlna @@ -5,7 +6,9 @@ namespace MediaBrowser.Model.Dlna public interface ITranscoderSupport { bool CanEncodeToAudioCodec(string codec); + bool CanEncodeToSubtitleCodec(string codec); + bool CanExtractSubtitles(string codec); } @@ -15,10 +18,12 @@ namespace MediaBrowser.Model.Dlna { return true; } + public bool CanEncodeToSubtitleCodec(string codec) { return true; } + public bool CanExtractSubtitles(string codec) { return true; diff --git a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs index 4cd318abb..10e9179c0 100644 --- a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs +++ b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -21,13 +22,13 @@ namespace MediaBrowser.Model.Dlna if (string.Equals(container, "asf", StringComparison.OrdinalIgnoreCase)) { MediaFormatProfile? val = ResolveVideoASFFormat(videoCodec, audioCodec, width, height); - return val.HasValue ? new MediaFormatProfile[] { val.Value } : new MediaFormatProfile[] { }; + return val.HasValue ? new MediaFormatProfile[] { val.Value } : Array.Empty(); } if (string.Equals(container, "mp4", StringComparison.OrdinalIgnoreCase)) { MediaFormatProfile? val = ResolveVideoMP4Format(videoCodec, audioCodec, width, height); - return val.HasValue ? new MediaFormatProfile[] { val.Value } : new MediaFormatProfile[] { }; + return val.HasValue ? new MediaFormatProfile[] { val.Value } : Array.Empty(); } if (string.Equals(container, "avi", StringComparison.OrdinalIgnoreCase)) @@ -61,18 +62,18 @@ namespace MediaBrowser.Model.Dlna if (string.Equals(container, "3gp", StringComparison.OrdinalIgnoreCase)) { MediaFormatProfile? val = ResolveVideo3GPFormat(videoCodec, audioCodec); - return val.HasValue ? new MediaFormatProfile[] { val.Value } : new MediaFormatProfile[] { }; + return val.HasValue ? new MediaFormatProfile[] { val.Value } : Array.Empty(); } if (string.Equals(container, "ogv", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ogg", StringComparison.OrdinalIgnoreCase)) return new MediaFormatProfile[] { MediaFormatProfile.OGV }; - return new MediaFormatProfile[] { }; + return Array.Empty(); } private MediaFormatProfile[] ResolveVideoMPEG2TSFormat(string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestampType) { - string suffix = ""; + string suffix = string.Empty; switch (timestampType) { @@ -92,16 +93,18 @@ namespace MediaBrowser.Model.Dlna if (string.Equals(videoCodec, "mpeg2video", StringComparison.OrdinalIgnoreCase)) { - var list = new List(); - - list.Add(ValueOf("MPEG_TS_SD_NA" + suffix)); - list.Add(ValueOf("MPEG_TS_SD_EU" + suffix)); - list.Add(ValueOf("MPEG_TS_SD_KO" + suffix)); + var list = new List + { + ValueOf("MPEG_TS_SD_NA" + suffix), + ValueOf("MPEG_TS_SD_EU" + suffix), + ValueOf("MPEG_TS_SD_KO" + suffix) + }; if ((timestampType == TransportStreamTimestamp.Valid) && string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)) { list.Add(MediaFormatProfile.MPEG_TS_JP_T); } + return list.ToArray(); } if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)) @@ -115,6 +118,7 @@ namespace MediaBrowser.Model.Dlna { return new MediaFormatProfile[] { MediaFormatProfile.AVC_TS_HD_DTS_ISO }; } + return new MediaFormatProfile[] { MediaFormatProfile.AVC_TS_HD_DTS_T }; } diff --git a/MediaBrowser.Model/Dlna/ProfileCondition.cs b/MediaBrowser.Model/Dlna/ProfileCondition.cs index 2021038d8..f8b5dee81 100644 --- a/MediaBrowser.Model/Dlna/ProfileCondition.cs +++ b/MediaBrowser.Model/Dlna/ProfileCondition.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System.Xml.Serialization; @@ -6,18 +7,6 @@ namespace MediaBrowser.Model.Dlna { public class ProfileCondition { - [XmlAttribute("condition")] - public ProfileConditionType Condition { get; set; } - - [XmlAttribute("property")] - public ProfileConditionValue Property { get; set; } - - [XmlAttribute("value")] - public string Value { get; set; } - - [XmlAttribute("isRequired")] - public bool IsRequired { get; set; } - public ProfileCondition() { IsRequired = true; @@ -36,5 +25,17 @@ namespace MediaBrowser.Model.Dlna Value = value; IsRequired = isRequired; } + + [XmlAttribute("condition")] + public ProfileConditionType Condition { get; set; } + + [XmlAttribute("property")] + public ProfileConditionValue Property { get; set; } + + [XmlAttribute("value")] + public string Value { get; set; } + + [XmlAttribute("isRequired")] + public bool IsRequired { get; set; } } } diff --git a/MediaBrowser.Model/Dlna/ResolutionConfiguration.cs b/MediaBrowser.Model/Dlna/ResolutionConfiguration.cs index c26eeec77..30c44fbe0 100644 --- a/MediaBrowser.Model/Dlna/ResolutionConfiguration.cs +++ b/MediaBrowser.Model/Dlna/ResolutionConfiguration.cs @@ -5,6 +5,7 @@ namespace MediaBrowser.Model.Dlna public class ResolutionConfiguration { public int MaxWidth { get; set; } + public int MaxBitrate { get; set; } public ResolutionConfiguration(int maxWidth, int maxBitrate) diff --git a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs index 8235b72d1..102db3b44 100644 --- a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs +++ b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -17,7 +18,8 @@ namespace MediaBrowser.Model.Dlna new ResolutionConfiguration(3840, 35000000) }; - public static ResolutionOptions Normalize(int? inputBitrate, + public static ResolutionOptions Normalize( + int? inputBitrate, int? unused1, int? unused2, int outputBitrate, @@ -83,6 +85,7 @@ namespace MediaBrowser.Model.Dlna { return .5; } + return 1; } diff --git a/MediaBrowser.Model/Dlna/ResolutionOptions.cs b/MediaBrowser.Model/Dlna/ResolutionOptions.cs index 5ea0252cb..774592abc 100644 --- a/MediaBrowser.Model/Dlna/ResolutionOptions.cs +++ b/MediaBrowser.Model/Dlna/ResolutionOptions.cs @@ -5,6 +5,7 @@ namespace MediaBrowser.Model.Dlna public class ResolutionOptions { public int? MaxWidth { get; set; } + public int? MaxHeight { get; set; } } } diff --git a/MediaBrowser.Model/Dlna/ResponseProfile.cs b/MediaBrowser.Model/Dlna/ResponseProfile.cs index c264cb936..48f53f06c 100644 --- a/MediaBrowser.Model/Dlna/ResponseProfile.cs +++ b/MediaBrowser.Model/Dlna/ResponseProfile.cs @@ -1,5 +1,7 @@ +#nullable disable #pragma warning disable CS1591 +using System; using System.Xml.Serialization; namespace MediaBrowser.Model.Dlna @@ -28,7 +30,7 @@ namespace MediaBrowser.Model.Dlna public ResponseProfile() { - Conditions = new ProfileCondition[] { }; + Conditions = Array.Empty(); } public string[] GetContainers() diff --git a/MediaBrowser.Model/Dlna/SearchCriteria.cs b/MediaBrowser.Model/Dlna/SearchCriteria.cs index 394fb9af9..94f5bd3db 100644 --- a/MediaBrowser.Model/Dlna/SearchCriteria.cs +++ b/MediaBrowser.Model/Dlna/SearchCriteria.cs @@ -34,9 +34,9 @@ namespace MediaBrowser.Model.Dlna public SearchCriteria(string search) { - if (string.IsNullOrEmpty(search)) + if (search.Length == 0) { - throw new ArgumentNullException(nameof(search)); + throw new ArgumentException("String can't be empty.", nameof(search)); } SearchType = SearchType.Unknown; @@ -48,11 +48,10 @@ namespace MediaBrowser.Model.Dlna if (subFactors.Length == 3) { - if (string.Equals("upnp:class", subFactors[0], StringComparison.OrdinalIgnoreCase) && - (string.Equals("=", subFactors[1]) || string.Equals("derivedfrom", subFactors[1], StringComparison.OrdinalIgnoreCase))) + (string.Equals("=", subFactors[1], StringComparison.Ordinal) || string.Equals("derivedfrom", subFactors[1], StringComparison.OrdinalIgnoreCase))) { - if (string.Equals("\"object.item.imageItem\"", subFactors[2]) || string.Equals("\"object.item.imageItem.photo\"", subFactors[2], StringComparison.OrdinalIgnoreCase)) + if (string.Equals("\"object.item.imageItem\"", subFactors[2], StringComparison.Ordinal) || string.Equals("\"object.item.imageItem.photo\"", subFactors[2], StringComparison.OrdinalIgnoreCase)) { SearchType = SearchType.Image; } diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 58755b171..a18ad36c5 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -339,6 +340,7 @@ namespace MediaBrowser.Model.Dlna { transcodeReasons.Add(transcodeReason.Value); } + all = false; break; } diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index c9fe679e1..244463803 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Dlna/SubtitleProfile.cs b/MediaBrowser.Model/Dlna/SubtitleProfile.cs index 6a8f655ac..f565fb025 100644 --- a/MediaBrowser.Model/Dlna/SubtitleProfile.cs +++ b/MediaBrowser.Model/Dlna/SubtitleProfile.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System.Xml.Serialization; diff --git a/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs b/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs index 02b3a198c..2f01836bd 100644 --- a/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs +++ b/MediaBrowser.Model/Dlna/SubtitleStreamInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Dlna @@ -5,13 +6,21 @@ namespace MediaBrowser.Model.Dlna public class SubtitleStreamInfo { public string Url { get; set; } + public string Language { get; set; } + public string Name { get; set; } + public bool IsForced { get; set; } + public string Format { get; set; } + public string DisplayTitle { get; set; } + public int Index { get; set; } + public SubtitleDeliveryMethod DeliveryMethod { get; set; } + public bool IsExternalUrl { get; set; } } } diff --git a/MediaBrowser.Model/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs index 570ee7baa..f05e31047 100644 --- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs +++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System.Xml.Serialization; diff --git a/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs b/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs index 3dc1fca36..d71013f01 100644 --- a/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs +++ b/MediaBrowser.Model/Dlna/UpnpDeviceInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -9,8 +10,11 @@ namespace MediaBrowser.Model.Dlna public class UpnpDeviceInfo { public Uri Location { get; set; } + public Dictionary Headers { get; set; } + public IPAddress LocalIpAddress { get; set; } + public int LocalPort { get; set; } } } diff --git a/MediaBrowser.Model/Dlna/VideoOptions.cs b/MediaBrowser.Model/Dlna/VideoOptions.cs index 5b12fff1c..4194f17c6 100644 --- a/MediaBrowser.Model/Dlna/VideoOptions.cs +++ b/MediaBrowser.Model/Dlna/VideoOptions.cs @@ -8,6 +8,7 @@ namespace MediaBrowser.Model.Dlna public class VideoOptions : AudioOptions { public int? AudioStreamIndex { get; set; } + public int? SubtitleStreamIndex { get; set; } } } diff --git a/MediaBrowser.Model/Dlna/XmlAttribute.cs b/MediaBrowser.Model/Dlna/XmlAttribute.cs index 31603a754..3a8939a79 100644 --- a/MediaBrowser.Model/Dlna/XmlAttribute.cs +++ b/MediaBrowser.Model/Dlna/XmlAttribute.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System.Xml.Serialization; diff --git a/MediaBrowser.Model/Drawing/DrawingUtils.cs b/MediaBrowser.Model/Drawing/DrawingUtils.cs index 0be30b0ba..1512c5233 100644 --- a/MediaBrowser.Model/Drawing/DrawingUtils.cs +++ b/MediaBrowser.Model/Drawing/DrawingUtils.cs @@ -16,7 +16,8 @@ namespace MediaBrowser.Model.Drawing /// A max fixed width, if desired. /// A max fixed height, if desired. /// A new size object. - public static ImageDimensions Resize(ImageDimensions size, + public static ImageDimensions Resize( + ImageDimensions size, int width, int height, int maxWidth, @@ -62,7 +63,7 @@ namespace MediaBrowser.Model.Drawing /// Height of the current. /// Width of the current. /// The new height. - /// the new width + /// The new width. private static int GetNewWidth(int currentHeight, int currentWidth, int newHeight) => Convert.ToInt32((double)newHeight / currentHeight * currentWidth); diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 607355d8d..55393d32c 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Dto/BaseItemPerson.cs b/MediaBrowser.Model/Dto/BaseItemPerson.cs index 5b7eefd70..b080f3e4a 100644 --- a/MediaBrowser.Model/Dto/BaseItemPerson.cs +++ b/MediaBrowser.Model/Dto/BaseItemPerson.cs @@ -1,3 +1,4 @@ +#nullable disable using System.Text.Json.Serialization; namespace MediaBrowser.Model.Dto diff --git a/MediaBrowser.Model/Dto/IHasServerId.cs b/MediaBrowser.Model/Dto/IHasServerId.cs index 8c9798c5c..c754d276c 100644 --- a/MediaBrowser.Model/Dto/IHasServerId.cs +++ b/MediaBrowser.Model/Dto/IHasServerId.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Dto diff --git a/MediaBrowser.Model/Dto/ImageByNameInfo.cs b/MediaBrowser.Model/Dto/ImageByNameInfo.cs index d2e43634d..06cc3e73c 100644 --- a/MediaBrowser.Model/Dto/ImageByNameInfo.cs +++ b/MediaBrowser.Model/Dto/ImageByNameInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Dto diff --git a/MediaBrowser.Model/Dto/ImageInfo.cs b/MediaBrowser.Model/Dto/ImageInfo.cs index 57942ac23..1e9b47267 100644 --- a/MediaBrowser.Model/Dto/ImageInfo.cs +++ b/MediaBrowser.Model/Dto/ImageInfo.cs @@ -1,3 +1,4 @@ +#nullable disable using MediaBrowser.Model.Entities; namespace MediaBrowser.Model.Dto @@ -20,9 +21,9 @@ namespace MediaBrowser.Model.Dto public int? ImageIndex { get; set; } /// - /// The image tag + /// Gets or sets the image tag. /// - public string ImageTag; + public string ImageTag { get; set; } /// /// Gets or sets the path. diff --git a/MediaBrowser.Model/Dto/ImageOptions.cs b/MediaBrowser.Model/Dto/ImageOptions.cs index 4e672a007..158e622a8 100644 --- a/MediaBrowser.Model/Dto/ImageOptions.cs +++ b/MediaBrowser.Model/Dto/ImageOptions.cs @@ -1,3 +1,4 @@ +#nullable disable using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Entities; @@ -8,6 +9,14 @@ namespace MediaBrowser.Model.Dto /// public class ImageOptions { + /// + /// Initializes a new instance of the class. + /// + public ImageOptions() + { + EnableImageEnhancers = true; + } + /// /// Gets or sets the type of the image. /// @@ -98,13 +107,5 @@ namespace MediaBrowser.Model.Dto /// /// The color of the background. public string BackgroundColor { get; set; } - - /// - /// Initializes a new instance of the class. - /// - public ImageOptions() - { - EnableImageEnhancers = true; - } } } diff --git a/MediaBrowser.Model/Dto/ItemIndex.cs b/MediaBrowser.Model/Dto/ItemIndex.cs deleted file mode 100644 index 525576d61..000000000 --- a/MediaBrowser.Model/Dto/ItemIndex.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace MediaBrowser.Model.Dto -{ - /// - /// Class ItemIndex. - /// - public class ItemIndex - { - /// - /// Gets or sets the name. - /// - /// The name. - public string Name { get; set; } - - /// - /// Gets or sets the item count. - /// - /// The item count. - public int ItemCount { get; set; } - } -} diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs index 29613adbf..74c2cb4f4 100644 --- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs +++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Dto/MetadataEditorInfo.cs b/MediaBrowser.Model/Dto/MetadataEditorInfo.cs index 21d8a31f2..1d840a300 100644 --- a/MediaBrowser.Model/Dto/MetadataEditorInfo.cs +++ b/MediaBrowser.Model/Dto/MetadataEditorInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Dto/NameIdPair.cs b/MediaBrowser.Model/Dto/NameIdPair.cs index 1b4800863..efb2c157c 100644 --- a/MediaBrowser.Model/Dto/NameIdPair.cs +++ b/MediaBrowser.Model/Dto/NameIdPair.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Dto/NameValuePair.cs b/MediaBrowser.Model/Dto/NameValuePair.cs index 74040c2cb..e71ff3c21 100644 --- a/MediaBrowser.Model/Dto/NameValuePair.cs +++ b/MediaBrowser.Model/Dto/NameValuePair.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Dto @@ -6,7 +7,6 @@ namespace MediaBrowser.Model.Dto { public NameValuePair() { - } public NameValuePair(string name, string value) diff --git a/MediaBrowser.Model/Dto/RecommendationDto.cs b/MediaBrowser.Model/Dto/RecommendationDto.cs index bc97dd6f1..107f41540 100644 --- a/MediaBrowser.Model/Dto/RecommendationDto.cs +++ b/MediaBrowser.Model/Dto/RecommendationDto.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Dto/UserDto.cs b/MediaBrowser.Model/Dto/UserDto.cs index d36706c38..40222c9dc 100644 --- a/MediaBrowser.Model/Dto/UserDto.cs +++ b/MediaBrowser.Model/Dto/UserDto.cs @@ -1,3 +1,4 @@ +#nullable disable using System; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Users; diff --git a/MediaBrowser.Model/Dto/UserItemDataDto.cs b/MediaBrowser.Model/Dto/UserItemDataDto.cs index 92f06c973..adb2cd2ab 100644 --- a/MediaBrowser.Model/Dto/UserItemDataDto.cs +++ b/MediaBrowser.Model/Dto/UserItemDataDto.cs @@ -1,3 +1,4 @@ +#nullable disable using System; namespace MediaBrowser.Model.Dto diff --git a/MediaBrowser.Model/Entities/ChapterInfo.cs b/MediaBrowser.Model/Entities/ChapterInfo.cs index bea7ec1db..45554c3dc 100644 --- a/MediaBrowser.Model/Entities/ChapterInfo.cs +++ b/MediaBrowser.Model/Entities/ChapterInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Entities/DisplayPreferences.cs b/MediaBrowser.Model/Entities/DisplayPreferences.cs index 2cd8bd306..0e5db01dd 100644 --- a/MediaBrowser.Model/Entities/DisplayPreferences.cs +++ b/MediaBrowser.Model/Entities/DisplayPreferences.cs @@ -1,3 +1,4 @@ +#nullable disable using System.Collections.Generic; namespace MediaBrowser.Model.Entities diff --git a/MediaBrowser.Model/Entities/IHasProviderIds.cs b/MediaBrowser.Model/Entities/IHasProviderIds.cs index c117efde9..1310f68ae 100644 --- a/MediaBrowser.Model/Entities/IHasProviderIds.cs +++ b/MediaBrowser.Model/Entities/IHasProviderIds.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace MediaBrowser.Model.Entities { /// - /// Since BaseItem and DTOBaseItem both have ProviderIds, this interface helps avoid code repition by using extension methods. + /// Since BaseItem and DTOBaseItem both have ProviderIds, this interface helps avoid code repetition by using extension methods. /// public interface IHasProviderIds { diff --git a/MediaBrowser.Model/Entities/LibraryUpdateInfo.cs b/MediaBrowser.Model/Entities/LibraryUpdateInfo.cs index b98c00240..6dd6653dc 100644 --- a/MediaBrowser.Model/Entities/LibraryUpdateInfo.cs +++ b/MediaBrowser.Model/Entities/LibraryUpdateInfo.cs @@ -5,15 +5,29 @@ using System; namespace MediaBrowser.Model.Entities { /// - /// Class LibraryUpdateInfo + /// Class LibraryUpdateInfo. /// public class LibraryUpdateInfo { + /// + /// Initializes a new instance of the class. + /// + public LibraryUpdateInfo() + { + FoldersAddedTo = Array.Empty(); + FoldersRemovedFrom = Array.Empty(); + ItemsAdded = Array.Empty(); + ItemsRemoved = Array.Empty(); + ItemsUpdated = Array.Empty(); + CollectionFolders = Array.Empty(); + } + /// /// Gets or sets the folders added to. /// /// The folders added to. public string[] FoldersAddedTo { get; set; } + /// /// Gets or sets the folders removed from. /// @@ -41,18 +55,5 @@ namespace MediaBrowser.Model.Entities public string[] CollectionFolders { get; set; } public bool IsEmpty => FoldersAddedTo.Length == 0 && FoldersRemovedFrom.Length == 0 && ItemsAdded.Length == 0 && ItemsRemoved.Length == 0 && ItemsUpdated.Length == 0 && CollectionFolders.Length == 0; - - /// - /// Initializes a new instance of the class. - /// - public LibraryUpdateInfo() - { - FoldersAddedTo = Array.Empty(); - FoldersRemovedFrom = Array.Empty(); - ItemsAdded = Array.Empty(); - ItemsRemoved = Array.Empty(); - ItemsUpdated = Array.Empty(); - CollectionFolders = Array.Empty(); - } } } diff --git a/MediaBrowser.Model/Entities/MediaAttachment.cs b/MediaBrowser.Model/Entities/MediaAttachment.cs index 167be18c9..34e3eabc9 100644 --- a/MediaBrowser.Model/Entities/MediaAttachment.cs +++ b/MediaBrowser.Model/Entities/MediaAttachment.cs @@ -1,3 +1,4 @@ +#nullable disable namespace MediaBrowser.Model.Entities { /// diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index e7e8d7cec..d68f37c9c 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Entities/MediaUrl.cs b/MediaBrowser.Model/Entities/MediaUrl.cs index e44143755..74f982437 100644 --- a/MediaBrowser.Model/Entities/MediaUrl.cs +++ b/MediaBrowser.Model/Entities/MediaUrl.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Entities diff --git a/MediaBrowser.Model/Entities/PackageReviewInfo.cs b/MediaBrowser.Model/Entities/PackageReviewInfo.cs index a034de8ba..1ebbc3323 100644 --- a/MediaBrowser.Model/Entities/PackageReviewInfo.cs +++ b/MediaBrowser.Model/Entities/PackageReviewInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -7,32 +8,32 @@ namespace MediaBrowser.Model.Entities public class PackageReviewInfo { /// - /// The package id (database key) for this review + /// Gets or sets the package id (database key) for this review. /// public int id { get; set; } /// - /// The rating value + /// Gets or sets the rating value. /// public int rating { get; set; } /// - /// Whether or not this review recommends this item + /// Gets or sets whether or not this review recommends this item. /// public bool recommend { get; set; } /// - /// A short description of the review + /// Gets or sets a short description of the review. /// public string title { get; set; } /// - /// A full review + /// Gets or sets the full review. /// public string review { get; set; } /// - /// Time of review + /// Gets or sets the time of review. /// public DateTime timestamp { get; set; } diff --git a/MediaBrowser.Model/Entities/ParentalRating.cs b/MediaBrowser.Model/Entities/ParentalRating.cs index 4b37bd64a..17b2868a3 100644 --- a/MediaBrowser.Model/Entities/ParentalRating.cs +++ b/MediaBrowser.Model/Entities/ParentalRating.cs @@ -1,12 +1,23 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Entities { /// - /// Class ParentalRating + /// Class ParentalRating. /// public class ParentalRating { + public ParentalRating() + { + } + + public ParentalRating(string name, int value) + { + Name = name; + Value = value; + } + /// /// Gets or sets the name. /// @@ -18,16 +29,5 @@ namespace MediaBrowser.Model.Entities /// /// The value. public int Value { get; set; } - - public ParentalRating() - { - - } - - public ParentalRating(string name, int value) - { - Name = name; - Value = value; - } } } diff --git a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs index cd387bd54..e089dd1e5 100644 --- a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs +++ b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs @@ -20,23 +20,23 @@ namespace MediaBrowser.Model.Entities } /// - /// Gets a provider id + /// Gets a provider id. /// /// The instance. /// The provider. /// System.String. - public static string GetProviderId(this IHasProviderIds instance, MetadataProviders provider) + public static string? GetProviderId(this IHasProviderIds instance, MetadataProviders provider) { return instance.GetProviderId(provider.ToString()); } /// - /// Gets a provider id + /// Gets a provider id. /// /// The instance. /// The name. /// System.String. - public static string GetProviderId(this IHasProviderIds instance, string name) + public static string? GetProviderId(this IHasProviderIds instance, string name) { if (instance == null) { @@ -53,7 +53,7 @@ namespace MediaBrowser.Model.Entities } /// - /// Sets a provider id + /// Sets a provider id. /// /// The instance. /// The name. @@ -89,7 +89,7 @@ namespace MediaBrowser.Model.Entities } /// - /// Sets a provider id + /// Sets a provider id. /// /// The instance. /// The provider. diff --git a/MediaBrowser.Model/Entities/VirtualFolderInfo.cs b/MediaBrowser.Model/Entities/VirtualFolderInfo.cs index dd30c9c84..2de02e403 100644 --- a/MediaBrowser.Model/Entities/VirtualFolderInfo.cs +++ b/MediaBrowser.Model/Entities/VirtualFolderInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Events/GenericEventArgs.cs b/MediaBrowser.Model/Events/GenericEventArgs.cs index 1ef0b25c9..44f60f811 100644 --- a/MediaBrowser.Model/Events/GenericEventArgs.cs +++ b/MediaBrowser.Model/Events/GenericEventArgs.cs @@ -22,12 +22,5 @@ namespace MediaBrowser.Model.Events { Argument = arg; } - - /// - /// Initializes a new instance of the class. - /// - public GenericEventArgs() - { - } } } diff --git a/MediaBrowser.Model/Extensions/ListHelper.cs b/MediaBrowser.Model/Extensions/ListHelper.cs index 90ce6f2e5..b893a3509 100644 --- a/MediaBrowser.Model/Extensions/ListHelper.cs +++ b/MediaBrowser.Model/Extensions/ListHelper.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -21,6 +22,7 @@ namespace MediaBrowser.Model.Extensions return true; } } + return false; } } diff --git a/MediaBrowser.Model/Extensions/StringHelper.cs b/MediaBrowser.Model/Extensions/StringHelper.cs index f819a295c..8ffa3c4ba 100644 --- a/MediaBrowser.Model/Extensions/StringHelper.cs +++ b/MediaBrowser.Model/Extensions/StringHelper.cs @@ -12,9 +12,9 @@ namespace MediaBrowser.Model.Extensions /// The string with the first character as uppercase. public static string FirstToUpper(string str) { - if (string.IsNullOrEmpty(str)) + if (str.Length == 0) { - return string.Empty; + return str; } if (char.IsUpper(str[0])) diff --git a/MediaBrowser.Model/Globalization/CountryInfo.cs b/MediaBrowser.Model/Globalization/CountryInfo.cs index 72362f4f3..6f6979316 100644 --- a/MediaBrowser.Model/Globalization/CountryInfo.cs +++ b/MediaBrowser.Model/Globalization/CountryInfo.cs @@ -1,3 +1,4 @@ +#nullable disable namespace MediaBrowser.Model.Globalization { /// diff --git a/MediaBrowser.Model/Globalization/CultureDto.cs b/MediaBrowser.Model/Globalization/CultureDto.cs index f415840b0..6af4a872c 100644 --- a/MediaBrowser.Model/Globalization/CultureDto.cs +++ b/MediaBrowser.Model/Globalization/CultureDto.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Globalization/ILocalizationManager.cs b/MediaBrowser.Model/Globalization/ILocalizationManager.cs index 613bfca69..baefeb39c 100644 --- a/MediaBrowser.Model/Globalization/ILocalizationManager.cs +++ b/MediaBrowser.Model/Globalization/ILocalizationManager.cs @@ -1,3 +1,4 @@ +#nullable disable using System.Collections.Generic; using System.Globalization; using MediaBrowser.Model.Entities; diff --git a/MediaBrowser.Model/Globalization/LocalizationOption.cs b/MediaBrowser.Model/Globalization/LocalizationOption.cs index 00caf5e11..81f47d978 100644 --- a/MediaBrowser.Model/Globalization/LocalizationOption.cs +++ b/MediaBrowser.Model/Globalization/LocalizationOption.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Globalization @@ -11,6 +12,7 @@ namespace MediaBrowser.Model.Globalization } public string Name { get; set; } + public string Value { get; set; } } } diff --git a/MediaBrowser.Model/IO/FileSystemEntryInfo.cs b/MediaBrowser.Model/IO/FileSystemEntryInfo.cs index a197f0fbe..36ff5d041 100644 --- a/MediaBrowser.Model/IO/FileSystemEntryInfo.cs +++ b/MediaBrowser.Model/IO/FileSystemEntryInfo.cs @@ -1,26 +1,39 @@ namespace MediaBrowser.Model.IO { /// - /// Class FileSystemEntryInfo + /// Class FileSystemEntryInfo. /// public class FileSystemEntryInfo { /// - /// Gets or sets the name. + /// Initializes a new instance of the class. + /// + /// The filename. + /// The file path. + /// The file type. + public FileSystemEntryInfo(string name, string path, FileSystemEntryType type) + { + Name = name; + Path = path; + Type = type; + } + + /// + /// Gets the name. /// /// The name. - public string Name { get; set; } + public string Name { get; } /// - /// Gets or sets the path. + /// Gets the path. /// /// The path. - public string Path { get; set; } + public string Path { get; } /// - /// Gets or sets the type. + /// Gets the type. /// /// The type. - public FileSystemEntryType Type { get; set; } + public FileSystemEntryType Type { get; } } } diff --git a/MediaBrowser.Model/IO/FileSystemMetadata.cs b/MediaBrowser.Model/IO/FileSystemMetadata.cs index 4b9102392..b23119d08 100644 --- a/MediaBrowser.Model/IO/FileSystemMetadata.cs +++ b/MediaBrowser.Model/IO/FileSystemMetadata.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs index 53f23a8e0..bba69d4b4 100644 --- a/MediaBrowser.Model/IO/IFileSystem.cs +++ b/MediaBrowser.Model/IO/IFileSystem.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/IO/IIsoManager.cs b/MediaBrowser.Model/IO/IIsoManager.cs index 8b6af019d..299bb0a21 100644 --- a/MediaBrowser.Model/IO/IIsoManager.cs +++ b/MediaBrowser.Model/IO/IIsoManager.cs @@ -16,7 +16,6 @@ namespace MediaBrowser.Model.IO /// The iso path. /// The cancellation token. /// IsoMount. - /// isoPath /// Unable to create mount. Task Mount(string isoPath, CancellationToken cancellationToken); diff --git a/MediaBrowser.Model/IO/IIsoMount.cs b/MediaBrowser.Model/IO/IIsoMount.cs index 72ec673ee..ea65d976a 100644 --- a/MediaBrowser.Model/IO/IIsoMount.cs +++ b/MediaBrowser.Model/IO/IIsoMount.cs @@ -8,7 +8,7 @@ namespace MediaBrowser.Model.IO public interface IIsoMount : IDisposable { /// - /// Gets or sets the iso path. + /// Gets the iso path. /// /// The iso path. string IsoPath { get; } diff --git a/MediaBrowser.Model/IO/IIsoMounter.cs b/MediaBrowser.Model/IO/IIsoMounter.cs index 83fdb5fd6..0d257395a 100644 --- a/MediaBrowser.Model/IO/IIsoMounter.cs +++ b/MediaBrowser.Model/IO/IIsoMounter.cs @@ -9,6 +9,12 @@ namespace MediaBrowser.Model.IO { public interface IIsoMounter { + /// + /// Gets the name. + /// + /// The name. + string Name { get; } + /// /// Mounts the specified iso path. /// @@ -25,11 +31,5 @@ namespace MediaBrowser.Model.IO /// The path. /// true if this instance can mount the specified path; otherwise, false. bool CanMount(string path); - - /// - /// Gets the name. - /// - /// The name. - string Name { get; } } } diff --git a/MediaBrowser.Model/IO/IStreamHelper.cs b/MediaBrowser.Model/IO/IStreamHelper.cs index e348cd725..af5ba5b17 100644 --- a/MediaBrowser.Model/IO/IStreamHelper.cs +++ b/MediaBrowser.Model/IO/IStreamHelper.cs @@ -14,6 +14,7 @@ namespace MediaBrowser.Model.IO Task CopyToAsync(Stream source, Stream destination, int bufferSize, int emptyReadLimit, CancellationToken cancellationToken); Task CopyToAsync(Stream source, Stream destination, CancellationToken cancellationToken); + Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken); Task CopyUntilCancelled(Stream source, Stream target, int bufferSize, CancellationToken cancellationToken); diff --git a/MediaBrowser.Model/Library/UserViewQuery.cs b/MediaBrowser.Model/Library/UserViewQuery.cs index a538efd25..8a49b6863 100644 --- a/MediaBrowser.Model/Library/UserViewQuery.cs +++ b/MediaBrowser.Model/Library/UserViewQuery.cs @@ -6,6 +6,12 @@ namespace MediaBrowser.Model.Library { public class UserViewQuery { + public UserViewQuery() + { + IncludeExternalContent = true; + PresetViews = Array.Empty(); + } + /// /// Gets or sets the user identifier. /// @@ -25,11 +31,5 @@ namespace MediaBrowser.Model.Library public bool IncludeHidden { get; set; } public string[] PresetViews { get; set; } - - public UserViewQuery() - { - IncludeExternalContent = true; - PresetViews = Array.Empty(); - } } } diff --git a/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs index 064ce6520..45970cf6b 100644 --- a/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/BaseTimerInfoDto.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/LiveTv/GuideInfo.cs b/MediaBrowser.Model/LiveTv/GuideInfo.cs index a224d73b7..b1cc8cfdf 100644 --- a/MediaBrowser.Model/LiveTv/GuideInfo.cs +++ b/MediaBrowser.Model/LiveTv/GuideInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs index 8154fbd0e..d1a94d8b3 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvChannelQuery.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/LiveTv/LiveTvInfo.cs b/MediaBrowser.Model/LiveTv/LiveTvInfo.cs index 85b77af24..9767509d0 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvInfo.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvInfo.cs @@ -6,6 +6,12 @@ namespace MediaBrowser.Model.LiveTv { public class LiveTvInfo { + public LiveTvInfo() + { + Services = Array.Empty(); + EnabledUsers = Array.Empty(); + } + /// /// Gets or sets the services. /// @@ -23,11 +29,5 @@ namespace MediaBrowser.Model.LiveTv /// /// The enabled users. public string[] EnabledUsers { get; set; } - - public LiveTvInfo() - { - Services = Array.Empty(); - EnabledUsers = Array.Empty(); - } } } diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs index dc8e0f91b..69c43efd4 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/LiveTv/LiveTvServiceInfo.cs b/MediaBrowser.Model/LiveTv/LiveTvServiceInfo.cs index 09e900643..856f638c5 100644 --- a/MediaBrowser.Model/LiveTv/LiveTvServiceInfo.cs +++ b/MediaBrowser.Model/LiveTv/LiveTvServiceInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/LiveTv/RecordingQuery.cs b/MediaBrowser.Model/LiveTv/RecordingQuery.cs index c75092b79..264982930 100644 --- a/MediaBrowser.Model/LiveTv/RecordingQuery.cs +++ b/MediaBrowser.Model/LiveTv/RecordingQuery.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs index e30dd84dc..90422d19c 100644 --- a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -14,7 +15,7 @@ namespace MediaBrowser.Model.LiveTv public SeriesTimerInfoDto() { ImageTags = new Dictionary(); - Days = new DayOfWeek[] { }; + Days = Array.Empty(); Type = "SeriesTimer"; } diff --git a/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs b/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs index bb553a576..bda46dd2b 100644 --- a/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs +++ b/MediaBrowser.Model/LiveTv/SeriesTimerQuery.cs @@ -10,7 +10,7 @@ namespace MediaBrowser.Model.LiveTv /// Gets or sets the sort by - SortName, Priority /// /// The sort by. - public string SortBy { get; set; } + public string? SortBy { get; set; } /// /// Gets or sets the sort order. diff --git a/MediaBrowser.Model/LiveTv/TimerInfoDto.cs b/MediaBrowser.Model/LiveTv/TimerInfoDto.cs index a1fbc5177..19039d448 100644 --- a/MediaBrowser.Model/LiveTv/TimerInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/TimerInfoDto.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using MediaBrowser.Model.Dto; diff --git a/MediaBrowser.Model/LiveTv/TimerQuery.cs b/MediaBrowser.Model/LiveTv/TimerQuery.cs index 1ef6dd67e..367c45b9d 100644 --- a/MediaBrowser.Model/LiveTv/TimerQuery.cs +++ b/MediaBrowser.Model/LiveTv/TimerQuery.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.LiveTv diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 0fdfe5761..b24409fcc 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -12,6 +12,8 @@ false true true + enable + latest diff --git a/MediaBrowser.Model/MediaInfo/AudioCodec.cs b/MediaBrowser.Model/MediaInfo/AudioCodec.cs index dcb6fa270..8b17757b8 100644 --- a/MediaBrowser.Model/MediaInfo/AudioCodec.cs +++ b/MediaBrowser.Model/MediaInfo/AudioCodec.cs @@ -10,9 +10,9 @@ namespace MediaBrowser.Model.MediaInfo public static string GetFriendlyName(string codec) { - if (string.IsNullOrEmpty(codec)) + if (codec.Length == 0) { - return string.Empty; + return codec; } switch (codec.ToLowerInvariant()) diff --git a/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs b/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs index 29ba10dbb..83f982a5c 100644 --- a/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs +++ b/MediaBrowser.Model/MediaInfo/BlurayDiscInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using MediaBrowser.Model.Entities; diff --git a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs index 52348f802..ea5d4e7d7 100644 --- a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs +++ b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -7,6 +8,13 @@ namespace MediaBrowser.Model.MediaInfo { public class LiveStreamRequest { + public LiveStreamRequest() + { + EnableDirectPlay = true; + EnableDirectStream = true; + DirectPlayProtocols = new MediaProtocol[] { MediaProtocol.Http }; + } + public string OpenToken { get; set; } public Guid UserId { get; set; } public string PlaySessionId { get; set; } @@ -22,12 +30,7 @@ namespace MediaBrowser.Model.MediaInfo public bool EnableDirectStream { get; set; } public MediaProtocol[] DirectPlayProtocols { get; set; } - public LiveStreamRequest() - { - EnableDirectPlay = true; - EnableDirectStream = true; - DirectPlayProtocols = new MediaProtocol[] { MediaProtocol.Http }; - } + public LiveStreamRequest(AudioOptions options) { @@ -38,8 +41,7 @@ namespace MediaBrowser.Model.MediaInfo DirectPlayProtocols = new MediaProtocol[] { MediaProtocol.Http }; - var videoOptions = options as VideoOptions; - if (videoOptions != null) + if (options is VideoOptions videoOptions) { AudioStreamIndex = videoOptions.AudioStreamIndex; SubtitleStreamIndex = videoOptions.SubtitleStreamIndex; diff --git a/MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs b/MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs index 45b8fcce9..f017c1a11 100644 --- a/MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs +++ b/MediaBrowser.Model/MediaInfo/LiveStreamResponse.cs @@ -6,6 +6,11 @@ namespace MediaBrowser.Model.MediaInfo { public class LiveStreamResponse { - public MediaSourceInfo MediaSource { get; set; } + public LiveStreamResponse(MediaSourceInfo mediaSource) + { + MediaSource = mediaSource; + } + + public MediaSourceInfo MediaSource { get; } } } diff --git a/MediaBrowser.Model/MediaInfo/MediaInfo.cs b/MediaBrowser.Model/MediaInfo/MediaInfo.cs index ad174f15d..97b979935 100644 --- a/MediaBrowser.Model/MediaInfo/MediaInfo.cs +++ b/MediaBrowser.Model/MediaInfo/MediaInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs index a2f163422..82e13e0eb 100644 --- a/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs +++ b/MediaBrowser.Model/MediaInfo/PlaybackInfoRequest.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs b/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs index 440818c3e..273350182 100644 --- a/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs +++ b/MediaBrowser.Model/MediaInfo/PlaybackInfoResponse.cs @@ -20,7 +20,7 @@ namespace MediaBrowser.Model.MediaInfo /// Gets or sets the play session identifier. /// /// The play session identifier. - public string PlaySessionId { get; set; } + public string? PlaySessionId { get; set; } /// /// Gets or sets the error code. diff --git a/MediaBrowser.Model/MediaInfo/SubtitleTrackEvent.cs b/MediaBrowser.Model/MediaInfo/SubtitleTrackEvent.cs index 5b0ccb28a..72bb3d9c6 100644 --- a/MediaBrowser.Model/MediaInfo/SubtitleTrackEvent.cs +++ b/MediaBrowser.Model/MediaInfo/SubtitleTrackEvent.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.MediaInfo @@ -5,8 +6,11 @@ namespace MediaBrowser.Model.MediaInfo public class SubtitleTrackEvent { public string Id { get; set; } + public string Text { get; set; } + public long StartPositionTicks { get; set; } + public long EndPositionTicks { get; set; } } } diff --git a/MediaBrowser.Model/Net/EndPointInfo.cs b/MediaBrowser.Model/Net/EndPointInfo.cs index f5ac3d169..034734a9e 100644 --- a/MediaBrowser.Model/Net/EndPointInfo.cs +++ b/MediaBrowser.Model/Net/EndPointInfo.cs @@ -5,6 +5,7 @@ namespace MediaBrowser.Model.Net public class EndPointInfo { public bool IsLocal { get; set; } + public bool IsInNetwork { get; set; } } } diff --git a/MediaBrowser.Model/Net/ISocket.cs b/MediaBrowser.Model/Net/ISocket.cs index 2bfbfcb20..5b6ed92df 100644 --- a/MediaBrowser.Model/Net/ISocket.cs +++ b/MediaBrowser.Model/Net/ISocket.cs @@ -17,6 +17,7 @@ namespace MediaBrowser.Model.Net Task ReceiveAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken); IAsyncResult BeginReceive(byte[] buffer, int offset, int count, AsyncCallback callback); + SocketReceiveResult EndReceive(IAsyncResult result); /// diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs index 68bcc590c..cfac4d1e2 100644 --- a/MediaBrowser.Model/Net/MimeTypes.cs +++ b/MediaBrowser.Model/Net/MimeTypes.cs @@ -8,12 +8,12 @@ using System.Linq; namespace MediaBrowser.Model.Net { /// - /// Class MimeTypes + /// Class MimeTypes. /// public static class MimeTypes { /// - /// Any extension in this list is considered a video file + /// Any extension in this list is considered a video file. /// private static readonly HashSet _videoFileExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) { @@ -141,16 +141,16 @@ namespace MediaBrowser.Model.Net return dict; } - public static string GetMimeType(string path) => GetMimeType(path, true); + public static string? GetMimeType(string path) => GetMimeType(path, true); /// /// Gets the type of the MIME. /// - public static string GetMimeType(string path, bool enableStreamDefault) + public static string? GetMimeType(string path, bool enableStreamDefault) { - if (string.IsNullOrEmpty(path)) + if (path.Length == 0) { - throw new ArgumentNullException(nameof(path)); + throw new ArgumentException("String can't be empty.", nameof(path)); } var ext = Path.GetExtension(path); @@ -188,11 +188,11 @@ namespace MediaBrowser.Model.Net return enableStreamDefault ? "application/octet-stream" : null; } - public static string ToExtension(string mimeType) + public static string? ToExtension(string mimeType) { - if (string.IsNullOrEmpty(mimeType)) + if (mimeType.Length == 0) { - throw new ArgumentNullException(nameof(mimeType)); + throw new ArgumentException("String can't be empty.", nameof(mimeType)); } // handle text/html; charset=UTF-8 diff --git a/MediaBrowser.Model/Net/NetworkShare.cs b/MediaBrowser.Model/Net/NetworkShare.cs index 744c6ec14..a40cf73e4 100644 --- a/MediaBrowser.Model/Net/NetworkShare.cs +++ b/MediaBrowser.Model/Net/NetworkShare.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Net diff --git a/MediaBrowser.Model/Net/SocketReceiveResult.cs b/MediaBrowser.Model/Net/SocketReceiveResult.cs index 141ae1608..54139fe9c 100644 --- a/MediaBrowser.Model/Net/SocketReceiveResult.cs +++ b/MediaBrowser.Model/Net/SocketReceiveResult.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#nullable disable using System.Net; @@ -10,12 +10,12 @@ namespace MediaBrowser.Model.Net public sealed class SocketReceiveResult { /// - /// The buffer to place received data into. + /// Gets or sets the buffer to place received data into. /// public byte[] Buffer { get; set; } /// - /// The number of bytes received. + /// Gets or sets the number of bytes received. /// public int ReceivedBytes { get; set; } @@ -23,6 +23,10 @@ namespace MediaBrowser.Model.Net /// The the data was received from. /// public IPEndPoint RemoteEndPoint { get; set; } + + /// + /// The local . + /// public IPAddress LocalIPAddress { get; set; } } } diff --git a/MediaBrowser.Model/Net/WebSocketMessage.cs b/MediaBrowser.Model/Net/WebSocketMessage.cs index 7575224d4..962b81b95 100644 --- a/MediaBrowser.Model/Net/WebSocketMessage.cs +++ b/MediaBrowser.Model/Net/WebSocketMessage.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Net diff --git a/MediaBrowser.Model/Notifications/NotificationOption.cs b/MediaBrowser.Model/Notifications/NotificationOption.cs index 4fb724515..144949a3b 100644 --- a/MediaBrowser.Model/Notifications/NotificationOption.cs +++ b/MediaBrowser.Model/Notifications/NotificationOption.cs @@ -6,6 +6,15 @@ namespace MediaBrowser.Model.Notifications { public class NotificationOption { + public NotificationOption(string type) + { + Type = type; + + DisabledServices = Array.Empty(); + DisabledMonitorUsers = Array.Empty(); + SendToUsers = Array.Empty(); + } + public string Type { get; set; } /// @@ -35,12 +44,5 @@ namespace MediaBrowser.Model.Notifications /// /// The send to user mode. public SendToUserType SendToUserMode { get; set; } - - public NotificationOption() - { - DisabledServices = Array.Empty(); - DisabledMonitorUsers = Array.Empty(); - SendToUsers = Array.Empty(); - } } } diff --git a/MediaBrowser.Model/Notifications/NotificationOptions.cs b/MediaBrowser.Model/Notifications/NotificationOptions.cs index 79a128e9b..7b299b1aa 100644 --- a/MediaBrowser.Model/Notifications/NotificationOptions.cs +++ b/MediaBrowser.Model/Notifications/NotificationOptions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -14,63 +15,53 @@ namespace MediaBrowser.Model.Notifications { Options = new[] { - new NotificationOption + new NotificationOption(NotificationType.TaskFailed.ToString()) { - Type = NotificationType.TaskFailed.ToString(), Enabled = true, SendToUserMode = SendToUserType.Admins }, - new NotificationOption + new NotificationOption(NotificationType.ServerRestartRequired.ToString()) { - Type = NotificationType.ServerRestartRequired.ToString(), Enabled = true, SendToUserMode = SendToUserType.Admins }, - new NotificationOption + new NotificationOption(NotificationType.ApplicationUpdateAvailable.ToString()) { - Type = NotificationType.ApplicationUpdateAvailable.ToString(), Enabled = true, SendToUserMode = SendToUserType.Admins }, - new NotificationOption + new NotificationOption(NotificationType.ApplicationUpdateInstalled.ToString()) { - Type = NotificationType.ApplicationUpdateInstalled.ToString(), Enabled = true, SendToUserMode = SendToUserType.Admins }, - new NotificationOption + new NotificationOption(NotificationType.PluginUpdateInstalled.ToString()) { - Type = NotificationType.PluginUpdateInstalled.ToString(), Enabled = true, SendToUserMode = SendToUserType.Admins }, - new NotificationOption + new NotificationOption(NotificationType.PluginUninstalled.ToString()) { - Type = NotificationType.PluginUninstalled.ToString(), Enabled = true, SendToUserMode = SendToUserType.Admins }, - new NotificationOption + new NotificationOption(NotificationType.InstallationFailed.ToString()) { - Type = NotificationType.InstallationFailed.ToString(), Enabled = true, SendToUserMode = SendToUserType.Admins }, - new NotificationOption + new NotificationOption(NotificationType.PluginInstalled.ToString()) { - Type = NotificationType.PluginInstalled.ToString(), Enabled = true, SendToUserMode = SendToUserType.Admins }, - new NotificationOption + new NotificationOption(NotificationType.PluginError.ToString()) { - Type = NotificationType.PluginError.ToString(), Enabled = true, SendToUserMode = SendToUserType.Admins }, - new NotificationOption + new NotificationOption(NotificationType.UserLockedOut.ToString()) { - Type = NotificationType.UserLockedOut.ToString(), Enabled = true, SendToUserMode = SendToUserType.Admins } @@ -81,8 +72,12 @@ namespace MediaBrowser.Model.Notifications { foreach (NotificationOption i in Options) { - if (string.Equals(type, i.Type, StringComparison.OrdinalIgnoreCase)) return i; + if (string.Equals(type, i.Type, StringComparison.OrdinalIgnoreCase)) + { + return i; + } } + return null; } diff --git a/MediaBrowser.Model/Notifications/NotificationRequest.cs b/MediaBrowser.Model/Notifications/NotificationRequest.cs index ffcfab24f..febc2bc09 100644 --- a/MediaBrowser.Model/Notifications/NotificationRequest.cs +++ b/MediaBrowser.Model/Notifications/NotificationRequest.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Notifications/NotificationTypeInfo.cs b/MediaBrowser.Model/Notifications/NotificationTypeInfo.cs index bfa163b40..402fbe81a 100644 --- a/MediaBrowser.Model/Notifications/NotificationTypeInfo.cs +++ b/MediaBrowser.Model/Notifications/NotificationTypeInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Notifications diff --git a/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs index b7003c4c8..ef435b21e 100644 --- a/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs +++ b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Playlists/PlaylistCreationResult.cs b/MediaBrowser.Model/Playlists/PlaylistCreationResult.cs index 4f2067b98..f3a1518ed 100644 --- a/MediaBrowser.Model/Playlists/PlaylistCreationResult.cs +++ b/MediaBrowser.Model/Playlists/PlaylistCreationResult.cs @@ -4,6 +4,11 @@ namespace MediaBrowser.Model.Playlists { public class PlaylistCreationResult { - public string Id { get; set; } + public PlaylistCreationResult(string id) + { + Id = id; + } + + public string Id { get; } } } diff --git a/MediaBrowser.Model/Playlists/PlaylistItemQuery.cs b/MediaBrowser.Model/Playlists/PlaylistItemQuery.cs deleted file mode 100644 index 324a38e70..000000000 --- a/MediaBrowser.Model/Playlists/PlaylistItemQuery.cs +++ /dev/null @@ -1,39 +0,0 @@ -#pragma warning disable CS1591 - -using MediaBrowser.Model.Querying; - -namespace MediaBrowser.Model.Playlists -{ - public class PlaylistItemQuery - { - /// - /// Gets or sets the identifier. - /// - /// The identifier. - public string Id { get; set; } - - /// - /// Gets or sets the user identifier. - /// - /// The user identifier. - public string UserId { get; set; } - - /// - /// Gets or sets the start index. - /// - /// The start index. - public int? StartIndex { get; set; } - - /// - /// Gets or sets the limit. - /// - /// The limit. - public int? Limit { get; set; } - - /// - /// Gets or sets the fields. - /// - /// The fields. - public ItemFields[] Fields { get; set; } - } -} diff --git a/MediaBrowser.Model/Plugins/PluginInfo.cs b/MediaBrowser.Model/Plugins/PluginInfo.cs index 9ff9ea457..c13f1a89f 100644 --- a/MediaBrowser.Model/Plugins/PluginInfo.cs +++ b/MediaBrowser.Model/Plugins/PluginInfo.cs @@ -1,3 +1,4 @@ +#nullable disable namespace MediaBrowser.Model.Plugins { /// diff --git a/MediaBrowser.Model/Plugins/PluginPageInfo.cs b/MediaBrowser.Model/Plugins/PluginPageInfo.cs index eb6a1527d..ca72e19ee 100644 --- a/MediaBrowser.Model/Plugins/PluginPageInfo.cs +++ b/MediaBrowser.Model/Plugins/PluginPageInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Plugins diff --git a/MediaBrowser.Model/Providers/ExternalIdInfo.cs b/MediaBrowser.Model/Providers/ExternalIdInfo.cs index 2b481ad7e..f2e6d8ef3 100644 --- a/MediaBrowser.Model/Providers/ExternalIdInfo.cs +++ b/MediaBrowser.Model/Providers/ExternalIdInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Providers diff --git a/MediaBrowser.Model/Providers/ExternalUrl.cs b/MediaBrowser.Model/Providers/ExternalUrl.cs index d4f4fa840..9467a2b00 100644 --- a/MediaBrowser.Model/Providers/ExternalUrl.cs +++ b/MediaBrowser.Model/Providers/ExternalUrl.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Providers diff --git a/MediaBrowser.Model/Providers/ImageProviderInfo.cs b/MediaBrowser.Model/Providers/ImageProviderInfo.cs index a22ec3c07..c63a2ceda 100644 --- a/MediaBrowser.Model/Providers/ImageProviderInfo.cs +++ b/MediaBrowser.Model/Providers/ImageProviderInfo.cs @@ -10,6 +10,12 @@ namespace MediaBrowser.Model.Providers /// public class ImageProviderInfo { + public ImageProviderInfo(string name, ImageType[] supportedImages) + { + Name = name; + SupportedImages = supportedImages; + } + /// /// Gets or sets the name. /// @@ -17,10 +23,5 @@ namespace MediaBrowser.Model.Providers public string Name { get; set; } public ImageType[] SupportedImages { get; set; } - - public ImageProviderInfo() - { - SupportedImages = Array.Empty(); - } } } diff --git a/MediaBrowser.Model/Providers/RemoteImageInfo.cs b/MediaBrowser.Model/Providers/RemoteImageInfo.cs index ee2b9d8fd..78ab6c706 100644 --- a/MediaBrowser.Model/Providers/RemoteImageInfo.cs +++ b/MediaBrowser.Model/Providers/RemoteImageInfo.cs @@ -1,3 +1,4 @@ +#nullable disable using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; diff --git a/MediaBrowser.Model/Providers/RemoteImageQuery.cs b/MediaBrowser.Model/Providers/RemoteImageQuery.cs index 2873c1003..b7fad87ab 100644 --- a/MediaBrowser.Model/Providers/RemoteImageQuery.cs +++ b/MediaBrowser.Model/Providers/RemoteImageQuery.cs @@ -6,7 +6,12 @@ namespace MediaBrowser.Model.Providers { public class RemoteImageQuery { - public string ProviderName { get; set; } + public RemoteImageQuery(string providerName) + { + ProviderName = providerName; + } + + public string ProviderName { get; } public ImageType? ImageType { get; set; } diff --git a/MediaBrowser.Model/Providers/RemoteImageResult.cs b/MediaBrowser.Model/Providers/RemoteImageResult.cs index 5ca00f770..e6067ee6e 100644 --- a/MediaBrowser.Model/Providers/RemoteImageResult.cs +++ b/MediaBrowser.Model/Providers/RemoteImageResult.cs @@ -1,3 +1,4 @@ +#nullable disable namespace MediaBrowser.Model.Providers { /// diff --git a/MediaBrowser.Model/Providers/RemoteSearchResult.cs b/MediaBrowser.Model/Providers/RemoteSearchResult.cs index 161e04821..c96eb0b59 100644 --- a/MediaBrowser.Model/Providers/RemoteSearchResult.cs +++ b/MediaBrowser.Model/Providers/RemoteSearchResult.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -8,23 +9,34 @@ namespace MediaBrowser.Model.Providers { public class RemoteSearchResult : IHasProviderIds { + public RemoteSearchResult() + { + ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + Artists = Array.Empty(); + } + /// /// Gets or sets the name. /// /// The name. public string Name { get; set; } + /// /// Gets or sets the provider ids. /// /// The provider ids. public Dictionary ProviderIds { get; set; } + /// /// Gets or sets the year. /// /// The year. public int? ProductionYear { get; set; } + public int? IndexNumber { get; set; } + public int? IndexNumberEnd { get; set; } + public int? ParentIndexNumber { get; set; } public DateTime? PremiereDate { get; set; } @@ -32,15 +44,13 @@ namespace MediaBrowser.Model.Providers public string ImageUrl { get; set; } public string SearchProviderName { get; set; } + public string Overview { get; set; } public RemoteSearchResult AlbumArtist { get; set; } + public RemoteSearchResult[] Artists { get; set; } - public RemoteSearchResult() - { - ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - Artists = new RemoteSearchResult[] { }; - } + } } diff --git a/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs b/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs index 06f29df3f..d9f7a852c 100644 --- a/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs +++ b/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Providers/SubtitleOptions.cs b/MediaBrowser.Model/Providers/SubtitleOptions.cs index 9e6049246..c07379570 100644 --- a/MediaBrowser.Model/Providers/SubtitleOptions.cs +++ b/MediaBrowser.Model/Providers/SubtitleOptions.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Providers/SubtitleProviderInfo.cs b/MediaBrowser.Model/Providers/SubtitleProviderInfo.cs index fca93d176..ee25be4b6 100644 --- a/MediaBrowser.Model/Providers/SubtitleProviderInfo.cs +++ b/MediaBrowser.Model/Providers/SubtitleProviderInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Providers diff --git a/MediaBrowser.Model/Querying/AllThemeMediaResult.cs b/MediaBrowser.Model/Querying/AllThemeMediaResult.cs index a264c6178..6b503ba6b 100644 --- a/MediaBrowser.Model/Querying/AllThemeMediaResult.cs +++ b/MediaBrowser.Model/Querying/AllThemeMediaResult.cs @@ -1,15 +1,10 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Querying { public class AllThemeMediaResult { - public ThemeMediaResult ThemeVideosResult { get; set; } - - public ThemeMediaResult ThemeSongsResult { get; set; } - - public ThemeMediaResult SoundtrackSongsResult { get; set; } - public AllThemeMediaResult() { ThemeVideosResult = new ThemeMediaResult(); @@ -18,5 +13,11 @@ namespace MediaBrowser.Model.Querying SoundtrackSongsResult = new ThemeMediaResult(); } + + public ThemeMediaResult ThemeVideosResult { get; set; } + + public ThemeMediaResult ThemeSongsResult { get; set; } + + public ThemeMediaResult SoundtrackSongsResult { get; set; } } } diff --git a/MediaBrowser.Model/Querying/EpisodeQuery.cs b/MediaBrowser.Model/Querying/EpisodeQuery.cs index 6fb4df676..13b1a0dcb 100644 --- a/MediaBrowser.Model/Querying/EpisodeQuery.cs +++ b/MediaBrowser.Model/Querying/EpisodeQuery.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Querying/ItemCountsQuery.cs b/MediaBrowser.Model/Querying/ItemCountsQuery.cs deleted file mode 100644 index f113cf380..000000000 --- a/MediaBrowser.Model/Querying/ItemCountsQuery.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace MediaBrowser.Model.Querying -{ - /// - /// Class ItemCountsQuery. - /// - public class ItemCountsQuery - { - /// - /// Gets or sets the user id. - /// - /// The user id. - public string UserId { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is favorite. - /// - /// null if [is favorite] contains no value, true if [is favorite]; otherwise, false. - public bool? IsFavorite { get; set; } - } -} diff --git a/MediaBrowser.Model/Querying/ItemSortBy.cs b/MediaBrowser.Model/Querying/ItemSortBy.cs index 15b60ad84..edf71c1a7 100644 --- a/MediaBrowser.Model/Querying/ItemSortBy.cs +++ b/MediaBrowser.Model/Querying/ItemSortBy.cs @@ -8,73 +8,99 @@ namespace MediaBrowser.Model.Querying public static class ItemSortBy { public const string AiredEpisodeOrder = "AiredEpisodeOrder"; + /// - /// The album + /// The album. /// public const string Album = "Album"; + /// - /// The album artist + /// The album artist. /// public const string AlbumArtist = "AlbumArtist"; + /// - /// The artist + /// The artist. /// public const string Artist = "Artist"; + /// - /// The date created + /// The date created. /// public const string DateCreated = "DateCreated"; + /// - /// The official rating + /// The official rating. /// public const string OfficialRating = "OfficialRating"; + /// - /// The date played + /// The date played. /// public const string DatePlayed = "DatePlayed"; + /// - /// The premiere date + /// The premiere date. /// public const string PremiereDate = "PremiereDate"; + public const string StartDate = "StartDate"; + /// - /// The sort name + /// The sort name. /// public const string SortName = "SortName"; + public const string Name = "Name"; + /// - /// The random + /// The random. /// public const string Random = "Random"; + /// - /// The runtime + /// The runtime. /// public const string Runtime = "Runtime"; + /// - /// The community rating + /// The community rating. /// public const string CommunityRating = "CommunityRating"; + /// - /// The production year + /// The production year. /// public const string ProductionYear = "ProductionYear"; + /// - /// The play count + /// The play count. /// public const string PlayCount = "PlayCount"; + /// - /// The critic rating + /// The critic rating. /// public const string CriticRating = "CriticRating"; + public const string IsFolder = "IsFolder"; + public const string IsUnplayed = "IsUnplayed"; + public const string IsPlayed = "IsPlayed"; + public const string SeriesSortName = "SeriesSortName"; + public const string VideoBitRate = "VideoBitRate"; + public const string AirTime = "AirTime"; + public const string Studio = "Studio"; + public const string IsFavoriteOrLiked = "IsFavoriteOrLiked"; + public const string DateLastContentAdded = "DateLastContentAdded"; + public const string SeriesDatePlayed = "SeriesDatePlayed"; } } diff --git a/MediaBrowser.Model/Querying/LatestItemsQuery.cs b/MediaBrowser.Model/Querying/LatestItemsQuery.cs index 84e29e76a..7954ef4b4 100644 --- a/MediaBrowser.Model/Querying/LatestItemsQuery.cs +++ b/MediaBrowser.Model/Querying/LatestItemsQuery.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -7,8 +8,13 @@ namespace MediaBrowser.Model.Querying { public class LatestItemsQuery { + public LatestItemsQuery() + { + EnableImageTypes = Array.Empty(); + } + /// - /// The user to localize search results for + /// The user to localize search results for. /// /// The user id. public Guid UserId { get; set; } @@ -26,13 +32,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; } @@ -54,25 +60,23 @@ namespace MediaBrowser.Model.Querying /// /// true if [group items]; otherwise, false. public bool GroupItems { get; set; } + /// /// Gets or sets a value indicating whether [enable images]. /// /// null if [enable images] contains no value, true if [enable images]; otherwise, false. public bool? EnableImages { get; set; } + /// /// Gets or sets the image type limit. /// /// The image type limit. public int? ImageTypeLimit { get; set; } + /// /// Gets or sets the enable image types. /// /// The enable image types. public ImageType[] EnableImageTypes { get; set; } - - public LatestItemsQuery() - { - EnableImageTypes = new ImageType[] { }; - } } } diff --git a/MediaBrowser.Model/Querying/MovieRecommendationQuery.cs b/MediaBrowser.Model/Querying/MovieRecommendationQuery.cs index 93de0a8cd..1c8875890 100644 --- a/MediaBrowser.Model/Querying/MovieRecommendationQuery.cs +++ b/MediaBrowser.Model/Querying/MovieRecommendationQuery.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Querying/NextUpQuery.cs b/MediaBrowser.Model/Querying/NextUpQuery.cs index 1543aea16..0df86cb22 100644 --- a/MediaBrowser.Model/Querying/NextUpQuery.cs +++ b/MediaBrowser.Model/Querying/NextUpQuery.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Querying/QueryFilters.cs b/MediaBrowser.Model/Querying/QueryFilters.cs index 8d879c174..e04208f76 100644 --- a/MediaBrowser.Model/Querying/QueryFilters.cs +++ b/MediaBrowser.Model/Querying/QueryFilters.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Querying/QueryResult.cs b/MediaBrowser.Model/Querying/QueryResult.cs index 266f1c7e6..42586243d 100644 --- a/MediaBrowser.Model/Querying/QueryResult.cs +++ b/MediaBrowser.Model/Querying/QueryResult.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Querying/ThemeMediaResult.cs b/MediaBrowser.Model/Querying/ThemeMediaResult.cs index bae954d78..5afedeeaf 100644 --- a/MediaBrowser.Model/Querying/ThemeMediaResult.cs +++ b/MediaBrowser.Model/Querying/ThemeMediaResult.cs @@ -4,7 +4,7 @@ using MediaBrowser.Model.Dto; namespace MediaBrowser.Model.Querying { /// - /// Class ThemeMediaResult + /// Class ThemeMediaResult. /// public class ThemeMediaResult : QueryResult { diff --git a/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs b/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs index 123d0fad2..ed1aa7ac6 100644 --- a/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs +++ b/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using MediaBrowser.Model.Entities; diff --git a/MediaBrowser.Model/Search/SearchHint.cs b/MediaBrowser.Model/Search/SearchHint.cs index 6e52314fa..c7a721df6 100644 --- a/MediaBrowser.Model/Search/SearchHint.cs +++ b/MediaBrowser.Model/Search/SearchHint.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Search/SearchHintResult.cs b/MediaBrowser.Model/Search/SearchHintResult.cs index 3c4fbec9e..92ba4139e 100644 --- a/MediaBrowser.Model/Search/SearchHintResult.cs +++ b/MediaBrowser.Model/Search/SearchHintResult.cs @@ -1,3 +1,4 @@ +#nullable disable namespace MediaBrowser.Model.Search { /// diff --git a/MediaBrowser.Model/Search/SearchQuery.cs b/MediaBrowser.Model/Search/SearchQuery.cs index 8a018312e..4470f1ad9 100644 --- a/MediaBrowser.Model/Search/SearchQuery.cs +++ b/MediaBrowser.Model/Search/SearchQuery.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Serialization/IJsonSerializer.cs b/MediaBrowser.Model/Serialization/IJsonSerializer.cs index 6223bb559..09b6ff9b5 100644 --- a/MediaBrowser.Model/Serialization/IJsonSerializer.cs +++ b/MediaBrowser.Model/Serialization/IJsonSerializer.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Serialization/IXmlSerializer.cs b/MediaBrowser.Model/Serialization/IXmlSerializer.cs index 1edd98fad..16d126ac7 100644 --- a/MediaBrowser.Model/Serialization/IXmlSerializer.cs +++ b/MediaBrowser.Model/Serialization/IXmlSerializer.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Services/ApiMemberAttribute.cs b/MediaBrowser.Model/Services/ApiMemberAttribute.cs index 8e50836f4..7c23eee44 100644 --- a/MediaBrowser.Model/Services/ApiMemberAttribute.cs +++ b/MediaBrowser.Model/Services/ApiMemberAttribute.cs @@ -1,3 +1,4 @@ +#nullable disable using System; namespace MediaBrowser.Model.Services diff --git a/MediaBrowser.Model/Services/IHasRequestFilter.cs b/MediaBrowser.Model/Services/IHasRequestFilter.cs index 3d2e9c0dc..332ba113c 100644 --- a/MediaBrowser.Model/Services/IHasRequestFilter.cs +++ b/MediaBrowser.Model/Services/IHasRequestFilter.cs @@ -7,18 +7,18 @@ namespace MediaBrowser.Model.Services public interface IHasRequestFilter { /// - /// Order in which Request Filters are executed. + /// Gets the order in which Request Filters are executed. /// <0 Executed before global request filters - /// >0 Executed after global request filters + /// >0 Executed after global request filters. /// int Priority { get; } /// /// The request filter is executed before the service. /// - /// The http request wrapper - /// The http response wrapper - /// The request DTO + /// The http request wrapper. + /// The http response wrapper. + /// The request DTO. void RequestFilter(IRequest req, HttpResponse res, object requestDto); } } diff --git a/MediaBrowser.Model/Services/IHttpRequest.cs b/MediaBrowser.Model/Services/IHttpRequest.cs index 4dccd2d68..3ea65195c 100644 --- a/MediaBrowser.Model/Services/IHttpRequest.cs +++ b/MediaBrowser.Model/Services/IHttpRequest.cs @@ -5,12 +5,12 @@ namespace MediaBrowser.Model.Services public interface IHttpRequest : IRequest { /// - /// The HTTP Verb + /// Gets the HTTP Verb. /// string HttpMethod { get; } /// - /// The value of the Accept HTTP Request Header + /// Gets the value of the Accept HTTP Request Header. /// string Accept { get; } } diff --git a/MediaBrowser.Model/Services/IHttpResult.cs b/MediaBrowser.Model/Services/IHttpResult.cs index b153f15ec..abc581d8e 100644 --- a/MediaBrowser.Model/Services/IHttpResult.cs +++ b/MediaBrowser.Model/Services/IHttpResult.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System.Net; @@ -7,27 +8,27 @@ namespace MediaBrowser.Model.Services public interface IHttpResult : IHasHeaders { /// - /// The HTTP Response Status + /// The HTTP Response Status. /// int Status { get; set; } /// - /// The HTTP Response Status Code + /// The HTTP Response Status Code. /// HttpStatusCode StatusCode { get; set; } /// - /// The HTTP Response ContentType + /// The HTTP Response ContentType. /// string ContentType { get; set; } /// - /// Response DTO + /// Response DTO. /// object Response { get; set; } /// - /// Holds the request call context + /// Holds the request call context. /// IRequest RequestContext { get; set; } } diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index 3f4edced6..f413f1e17 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Services/QueryParamCollection.cs b/MediaBrowser.Model/Services/QueryParamCollection.cs index 19e9e53e7..d07ff1548 100644 --- a/MediaBrowser.Model/Services/QueryParamCollection.cs +++ b/MediaBrowser.Model/Services/QueryParamCollection.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -19,11 +20,6 @@ namespace MediaBrowser.Model.Services return StringComparison.OrdinalIgnoreCase; } - private static StringComparer GetStringComparer() - { - return StringComparer.OrdinalIgnoreCase; - } - /// /// Adds a new query parameter. /// diff --git a/MediaBrowser.Model/Services/RouteAttribute.cs b/MediaBrowser.Model/Services/RouteAttribute.cs index 197ba05e5..162576aa7 100644 --- a/MediaBrowser.Model/Services/RouteAttribute.cs +++ b/MediaBrowser.Model/Services/RouteAttribute.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Session/BrowseRequest.cs b/MediaBrowser.Model/Session/BrowseRequest.cs index f485d680e..1c997d584 100644 --- a/MediaBrowser.Model/Session/BrowseRequest.cs +++ b/MediaBrowser.Model/Session/BrowseRequest.cs @@ -1,3 +1,4 @@ +#nullable disable namespace MediaBrowser.Model.Session { /// diff --git a/MediaBrowser.Model/Session/ClientCapabilities.cs b/MediaBrowser.Model/Session/ClientCapabilities.cs index 5da4998e8..51db66d21 100644 --- a/MediaBrowser.Model/Session/ClientCapabilities.cs +++ b/MediaBrowser.Model/Session/ClientCapabilities.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Session/GeneralCommand.cs b/MediaBrowser.Model/Session/GeneralCommand.cs index 980e1f88b..9794bd292 100644 --- a/MediaBrowser.Model/Session/GeneralCommand.cs +++ b/MediaBrowser.Model/Session/GeneralCommand.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Session/MessageCommand.cs b/MediaBrowser.Model/Session/MessageCommand.cs index 473a7bccc..09abfbb3f 100644 --- a/MediaBrowser.Model/Session/MessageCommand.cs +++ b/MediaBrowser.Model/Session/MessageCommand.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Session diff --git a/MediaBrowser.Model/Session/PlayRequest.cs b/MediaBrowser.Model/Session/PlayRequest.cs index bdb2b2439..62b68b49e 100644 --- a/MediaBrowser.Model/Session/PlayRequest.cs +++ b/MediaBrowser.Model/Session/PlayRequest.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Session/PlaybackProgressInfo.cs b/MediaBrowser.Model/Session/PlaybackProgressInfo.cs index 5687ba84b..6b4cfe4f0 100644 --- a/MediaBrowser.Model/Session/PlaybackProgressInfo.cs +++ b/MediaBrowser.Model/Session/PlaybackProgressInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Session/PlaybackStopInfo.cs b/MediaBrowser.Model/Session/PlaybackStopInfo.cs index f8cfacc20..b0827ac99 100644 --- a/MediaBrowser.Model/Session/PlaybackStopInfo.cs +++ b/MediaBrowser.Model/Session/PlaybackStopInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Session/PlayerStateInfo.cs b/MediaBrowser.Model/Session/PlayerStateInfo.cs index 0f9956873..0f10605ea 100644 --- a/MediaBrowser.Model/Session/PlayerStateInfo.cs +++ b/MediaBrowser.Model/Session/PlayerStateInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Session diff --git a/MediaBrowser.Model/Session/PlaystateRequest.cs b/MediaBrowser.Model/Session/PlaystateRequest.cs index 493a8063a..ba2c024b7 100644 --- a/MediaBrowser.Model/Session/PlaystateRequest.cs +++ b/MediaBrowser.Model/Session/PlaystateRequest.cs @@ -12,6 +12,6 @@ namespace MediaBrowser.Model.Session /// Gets or sets the controlling user identifier. /// /// The controlling user identifier. - public string ControllingUserId { get; set; } + public string? ControllingUserId { get; set; } } } diff --git a/MediaBrowser.Model/Session/SessionUserInfo.cs b/MediaBrowser.Model/Session/SessionUserInfo.cs index 42a56b92b..4d6f35efc 100644 --- a/MediaBrowser.Model/Session/SessionUserInfo.cs +++ b/MediaBrowser.Model/Session/SessionUserInfo.cs @@ -1,3 +1,4 @@ +#nullable disable using System; namespace MediaBrowser.Model.Session @@ -12,6 +13,7 @@ namespace MediaBrowser.Model.Session /// /// The user identifier. public Guid UserId { get; set; } + /// /// Gets or sets the name of the user. /// diff --git a/MediaBrowser.Model/Session/TranscodingInfo.cs b/MediaBrowser.Model/Session/TranscodingInfo.cs index 8f4e688f0..d6dc83413 100644 --- a/MediaBrowser.Model/Session/TranscodingInfo.cs +++ b/MediaBrowser.Model/Session/TranscodingInfo.cs @@ -1,5 +1,8 @@ +#nullable disable #pragma warning disable CS1591 +using System; + namespace MediaBrowser.Model.Session { public class TranscodingInfo @@ -22,7 +25,7 @@ namespace MediaBrowser.Model.Session public TranscodingInfo() { - TranscodeReasons = new TranscodeReason[] { }; + TranscodeReasons = Array.Empty(); } } diff --git a/MediaBrowser.Model/Session/UserDataChangeInfo.cs b/MediaBrowser.Model/Session/UserDataChangeInfo.cs index 0872eb4b1..0fd24edcc 100644 --- a/MediaBrowser.Model/Session/UserDataChangeInfo.cs +++ b/MediaBrowser.Model/Session/UserDataChangeInfo.cs @@ -1,3 +1,4 @@ +#nullable disable using MediaBrowser.Model.Dto; namespace MediaBrowser.Model.Session diff --git a/MediaBrowser.Model/Sync/SyncJob.cs b/MediaBrowser.Model/Sync/SyncJob.cs index 30bf27f38..3cc9ff726 100644 --- a/MediaBrowser.Model/Sync/SyncJob.cs +++ b/MediaBrowser.Model/Sync/SyncJob.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Sync/SyncTarget.cs b/MediaBrowser.Model/Sync/SyncTarget.cs index 20a0c8cc7..9e6bbbc00 100644 --- a/MediaBrowser.Model/Sync/SyncTarget.cs +++ b/MediaBrowser.Model/Sync/SyncTarget.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Sync diff --git a/MediaBrowser.Model/System/LogFile.cs b/MediaBrowser.Model/System/LogFile.cs index a2b701664..aec910c92 100644 --- a/MediaBrowser.Model/System/LogFile.cs +++ b/MediaBrowser.Model/System/LogFile.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/System/PublicSystemInfo.cs b/MediaBrowser.Model/System/PublicSystemInfo.cs index 1775470b5..b6196a43f 100644 --- a/MediaBrowser.Model/System/PublicSystemInfo.cs +++ b/MediaBrowser.Model/System/PublicSystemInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.System diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index cfa7684c9..7582cb748 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; @@ -34,7 +35,6 @@ namespace MediaBrowser.Model.System /// The display name of the operating system. public string OperatingSystemDisplayName { get; set; } - /// /// Get or sets the package name. /// diff --git a/MediaBrowser.Model/System/WakeOnLanInfo.cs b/MediaBrowser.Model/System/WakeOnLanInfo.cs index 534ad19ec..b2cbe737d 100644 --- a/MediaBrowser.Model/System/WakeOnLanInfo.cs +++ b/MediaBrowser.Model/System/WakeOnLanInfo.cs @@ -7,36 +7,21 @@ namespace MediaBrowser.Model.System /// public class WakeOnLanInfo { - /// - /// Returns the MAC address of the device. - /// - /// The MAC address. - public string MacAddress { get; set; } - - /// - /// Returns the wake-on-LAN port. - /// - /// The wake-on-LAN port. - public int Port { get; set; } - /// /// Initializes a new instance of the class. /// /// The MAC address. - public WakeOnLanInfo(PhysicalAddress macAddress) + public WakeOnLanInfo(PhysicalAddress macAddress) : this(macAddress.ToString()) { - MacAddress = macAddress.ToString(); - Port = 9; } /// /// Initializes a new instance of the class. /// /// The MAC address. - public WakeOnLanInfo(string macAddress) + public WakeOnLanInfo(string macAddress) : this() { MacAddress = macAddress; - Port = 9; } /// @@ -46,5 +31,17 @@ namespace MediaBrowser.Model.System { Port = 9; } + + /// + /// Gets the MAC address of the device. + /// + /// The MAC address. + public string? MacAddress { get; set; } + + /// + /// Gets or sets the wake-on-LAN port. + /// + /// The wake-on-LAN port. + public int Port { get; set; } } } diff --git a/MediaBrowser.Model/Tasks/IScheduledTask.cs b/MediaBrowser.Model/Tasks/IScheduledTask.cs index ed160e176..bf87088e4 100644 --- a/MediaBrowser.Model/Tasks/IScheduledTask.cs +++ b/MediaBrowser.Model/Tasks/IScheduledTask.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Threading; @@ -8,16 +6,19 @@ using System.Threading.Tasks; namespace MediaBrowser.Model.Tasks { /// - /// Interface IScheduledTaskWorker + /// Interface IScheduledTaskWorker. /// public interface IScheduledTask { /// - /// Gets the name of the task + /// Gets the name of the task. /// /// The name. string Name { get; } + /// + /// Gets the key of the task. + /// string Key { get; } /// @@ -33,7 +34,7 @@ namespace MediaBrowser.Model.Tasks string Category { get; } /// - /// Executes the task + /// Executes the task. /// /// The cancellation token. /// The progress. diff --git a/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs b/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs index 4dd1bb5d0..c79d7fe75 100644 --- a/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs +++ b/MediaBrowser.Model/Tasks/IScheduledTaskWorker.cs @@ -1,3 +1,4 @@ +#nullable disable using System; using MediaBrowser.Model.Events; diff --git a/MediaBrowser.Model/Tasks/ScheduledTaskHelpers.cs b/MediaBrowser.Model/Tasks/ScheduledTaskHelpers.cs index ca0743cca..9063903ae 100644 --- a/MediaBrowser.Model/Tasks/ScheduledTaskHelpers.cs +++ b/MediaBrowser.Model/Tasks/ScheduledTaskHelpers.cs @@ -14,9 +14,7 @@ namespace MediaBrowser.Model.Tasks { var isHidden = false; - var configurableTask = task.ScheduledTask as IConfigurableScheduledTask; - - if (configurableTask != null) + if (task.ScheduledTask is IConfigurableScheduledTask configurableTask) { isHidden = configurableTask.IsHidden; } diff --git a/MediaBrowser.Model/Tasks/TaskCompletionEventArgs.cs b/MediaBrowser.Model/Tasks/TaskCompletionEventArgs.cs index cc6c2b62b..48950667e 100644 --- a/MediaBrowser.Model/Tasks/TaskCompletionEventArgs.cs +++ b/MediaBrowser.Model/Tasks/TaskCompletionEventArgs.cs @@ -6,8 +6,14 @@ namespace MediaBrowser.Model.Tasks { public class TaskCompletionEventArgs : EventArgs { - public IScheduledTaskWorker Task { get; set; } + public TaskCompletionEventArgs(IScheduledTaskWorker task, TaskResult result) + { + Task = task; + Result = result; + } - public TaskResult Result { get; set; } + public IScheduledTaskWorker Task { get; } + + public TaskResult Result { get; } } } diff --git a/MediaBrowser.Model/Tasks/TaskInfo.cs b/MediaBrowser.Model/Tasks/TaskInfo.cs index 5144c035a..77100dfe7 100644 --- a/MediaBrowser.Model/Tasks/TaskInfo.cs +++ b/MediaBrowser.Model/Tasks/TaskInfo.cs @@ -1,3 +1,4 @@ +#nullable disable using System; namespace MediaBrowser.Model.Tasks diff --git a/MediaBrowser.Model/Tasks/TaskResult.cs b/MediaBrowser.Model/Tasks/TaskResult.cs index c6f92e7ed..31001aeb2 100644 --- a/MediaBrowser.Model/Tasks/TaskResult.cs +++ b/MediaBrowser.Model/Tasks/TaskResult.cs @@ -1,3 +1,4 @@ +#nullable disable using System; namespace MediaBrowser.Model.Tasks diff --git a/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs b/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs index 699e0ea3a..5aeaffc2b 100644 --- a/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs +++ b/MediaBrowser.Model/Tasks/TaskTriggerInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Updates/CheckForUpdateResult.cs b/MediaBrowser.Model/Updates/CheckForUpdateResult.cs index be1b08223..9c59a7c88 100644 --- a/MediaBrowser.Model/Updates/CheckForUpdateResult.cs +++ b/MediaBrowser.Model/Updates/CheckForUpdateResult.cs @@ -1,3 +1,4 @@ +#nullable disable namespace MediaBrowser.Model.Updates { /// diff --git a/MediaBrowser.Model/Updates/InstallationInfo.cs b/MediaBrowser.Model/Updates/InstallationInfo.cs index 42c2105f5..4651a4169 100644 --- a/MediaBrowser.Model/Updates/InstallationInfo.cs +++ b/MediaBrowser.Model/Updates/InstallationInfo.cs @@ -1,3 +1,4 @@ +#nullable disable using System; namespace MediaBrowser.Model.Updates diff --git a/MediaBrowser.Model/Updates/PackageInfo.cs b/MediaBrowser.Model/Updates/PackageInfo.cs index abbe91eff..b5a5068e7 100644 --- a/MediaBrowser.Model/Updates/PackageInfo.cs +++ b/MediaBrowser.Model/Updates/PackageInfo.cs @@ -1,3 +1,4 @@ +#nullable disable using System; using System.Collections.Generic; diff --git a/MediaBrowser.Model/Updates/PackageVersionInfo.cs b/MediaBrowser.Model/Updates/PackageVersionInfo.cs index 3eef965dd..9ef67966b 100644 --- a/MediaBrowser.Model/Updates/PackageVersionInfo.cs +++ b/MediaBrowser.Model/Updates/PackageVersionInfo.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Users/ForgotPasswordResult.cs b/MediaBrowser.Model/Users/ForgotPasswordResult.cs index 368c642e8..6bb13d4c9 100644 --- a/MediaBrowser.Model/Users/ForgotPasswordResult.cs +++ b/MediaBrowser.Model/Users/ForgotPasswordResult.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Users/PinRedeemResult.cs b/MediaBrowser.Model/Users/PinRedeemResult.cs index ab868cad4..7e4553bac 100644 --- a/MediaBrowser.Model/Users/PinRedeemResult.cs +++ b/MediaBrowser.Model/Users/PinRedeemResult.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 namespace MediaBrowser.Model.Users diff --git a/MediaBrowser.Model/Users/UserAction.cs b/MediaBrowser.Model/Users/UserAction.cs index f6bb6451b..36b8e6ee5 100644 --- a/MediaBrowser.Model/Users/UserAction.cs +++ b/MediaBrowser.Model/Users/UserAction.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index ae2b3fd4e..9f85022ef 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -1,3 +1,4 @@ +#nullable disable #pragma warning disable CS1591 using System; diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 6ef0e44a2..48e1c94ad 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -230,7 +230,9 @@ namespace MediaBrowser.Providers.Manager /// The result. /// The cancellation token. /// Task. - private async Task RefreshFromProvider(BaseItem item, LibraryOptions libraryOptions, + private async Task RefreshFromProvider( + BaseItem item, + LibraryOptions libraryOptions, IRemoteImageProvider provider, ImageRefreshOptions refreshOptions, TypeOptions savedOptions, @@ -256,20 +258,24 @@ namespace MediaBrowser.Providers.Manager _logger.LogDebug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name); - var images = await _providerManager.GetAvailableRemoteImages(item, new RemoteImageQuery - { - ProviderName = provider.Name, - IncludeAllLanguages = false, - IncludeDisabledProviders = false, - - }, cancellationToken).ConfigureAwait(false); + var images = await _providerManager.GetAvailableRemoteImages( + item, + new RemoteImageQuery(provider.Name) + { + IncludeAllLanguages = false, + IncludeDisabledProviders = false, + }, + cancellationToken).ConfigureAwait(false); var list = images.ToList(); int minWidth; foreach (var imageType in _singularImages) { - if (!IsEnabled(savedOptions, imageType, item)) continue; + if (!IsEnabled(savedOptions, imageType, item)) + { + continue; + } if (!HasImage(item, imageType) || (refreshOptions.IsReplacingImage(imageType) && !downloadedImages.Contains(imageType))) { diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 7125f34c5..bf3677850 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -264,11 +264,7 @@ namespace MediaBrowser.Providers.Manager /// IEnumerable{IImageProvider}. public IEnumerable GetRemoteImageProviderInfo(BaseItem item) { - return GetRemoteImageProviders(item, true).Select(i => new ImageProviderInfo - { - Name = i.Name, - SupportedImages = i.GetSupportedImages(item).ToArray() - }); + return GetRemoteImageProviders(item, true).Select(i => new ImageProviderInfo(i.Name, i.GetSupportedImages(item).ToArray())); } public IEnumerable GetImageProviders(BaseItem item, ImageRefreshOptions refreshOptions) -- cgit v1.2.3 From 410a322fe22302eb3c8a37ea38bbe1d7e9d12aff Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 5 Apr 2020 23:30:57 -0400 Subject: Add CanConnectWithHttps to interface --- Emby.Server.Implementations/ApplicationHost.cs | 5 +---- MediaBrowser.Controller/IServerApplicationHost.cs | 6 ++++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 8158b4559..9cb747171 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1449,10 +1449,7 @@ namespace Emby.Server.Implementations /// public bool ListenWithHttps => Certificate != null && ServerConfigurationManager.Configuration.EnableHttps; - /// - /// Gets a value indicating whether a client can connect to the server over HTTPS, either directly or via a - /// reverse proxy. - /// + /// public bool CanConnectWithHttps => ListenWithHttps || ServerConfigurationManager.Configuration.IsBehindProxy; public async Task GetLocalApiUrl(CancellationToken cancellationToken) diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index d999f76db..7742279f7 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -43,6 +43,12 @@ namespace MediaBrowser.Controller /// bool ListenWithHttps { get; } + /// + /// Gets a value indicating whether a client can connect to the server over HTTPS, either directly or via a + /// reverse proxy. + /// + bool CanConnectWithHttps { get; } + /// /// Gets a value indicating whether this instance has update available. /// -- cgit v1.2.3 From 1cf31229d81f59fadd96cb9c3cf17bb00268ce79 Mon Sep 17 00:00:00 2001 From: Pika Date: Mon, 6 Apr 2020 13:49:35 -0400 Subject: Use embedded title for other track types --- MediaBrowser.Model/Entities/MediaStream.cs | 193 ++++++++++++++++------------- 1 file changed, 104 insertions(+), 89 deletions(-) diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index e7e8d7cec..68e0242a9 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -77,106 +77,121 @@ namespace MediaBrowser.Model.Entities { get { - if (Type == MediaStreamType.Audio) + switch (Type) { - //if (!string.IsNullOrEmpty(Title)) - //{ - // return AddLanguageIfNeeded(Title); - //} - - var attributes = new List(); - - if (!string.IsNullOrEmpty(Language)) - { - attributes.Add(StringHelper.FirstToUpper(Language)); - } - if (!string.IsNullOrEmpty(Codec) && !string.Equals(Codec, "dca", StringComparison.OrdinalIgnoreCase)) - { - attributes.Add(AudioCodec.GetFriendlyName(Codec)); - } - else if (!string.IsNullOrEmpty(Profile) && !string.Equals(Profile, "lc", StringComparison.OrdinalIgnoreCase)) - { - attributes.Add(Profile); - } - - if (!string.IsNullOrEmpty(ChannelLayout)) - { - attributes.Add(ChannelLayout); - } - else if (Channels.HasValue) - { - attributes.Add(Channels.Value.ToString(CultureInfo.InvariantCulture) + " ch"); - } - if (IsDefault) - { - attributes.Add("Default"); - } - - return string.Join(" ", attributes); - } - - if (Type == MediaStreamType.Video) - { - var attributes = new List(); - - var resolutionText = GetResolutionText(); - - if (!string.IsNullOrEmpty(resolutionText)) + case MediaStreamType.Audio: { - attributes.Add(resolutionText); + var attributes = new List(); + + if (!string.IsNullOrEmpty(Language)) + { + attributes.Add(StringHelper.FirstToUpper(Language)); + } + + if (!string.IsNullOrEmpty(Codec) && !string.Equals(Codec, "dca", StringComparison.OrdinalIgnoreCase)) + { + attributes.Add(AudioCodec.GetFriendlyName(Codec)); + } + else if (!string.IsNullOrEmpty(Profile) && !string.Equals(Profile, "lc", StringComparison.OrdinalIgnoreCase)) + { + attributes.Add(Profile); + } + + if (!string.IsNullOrEmpty(ChannelLayout)) + { + attributes.Add(ChannelLayout); + } + else if (Channels.HasValue) + { + attributes.Add(Channels.Value.ToString(CultureInfo.InvariantCulture) + " ch"); + } + + if (IsDefault) + { + attributes.Add(string.IsNullOrEmpty(localizedDefault) ? "Default" : localizedDefault); + } + + if (!string.IsNullOrEmpty(Title)) + { + return attributes.AsEnumerable() + // keep Tags that are not already in Title + .Where(tag => Title.IndexOf(tag, StringComparison.OrdinalIgnoreCase) == -1) + // attributes concatenation, starting with Title + .Aggregate(new StringBuilder(Title), (builder, attr) => builder.Append(" - ").Append(attr)) + .ToString(); + } + + return string.Join(" ", attributes); } - if (!string.IsNullOrEmpty(Codec)) + case MediaStreamType.Video: { - attributes.Add(Codec.ToUpperInvariant()); + var attributes = new List(); + + var resolutionText = GetResolutionText(); + + if (!string.IsNullOrEmpty(resolutionText)) + { + attributes.Add(resolutionText); + } + + if (!string.IsNullOrEmpty(Codec)) + { + attributes.Add(Codec.ToUpperInvariant()); + } + + if (!string.IsNullOrEmpty(Title)) + { + return attributes.AsEnumerable() + // keep Tags that are not already in Title + .Where(tag => Title.IndexOf(tag, StringComparison.OrdinalIgnoreCase) == -1) + // attributes concatenation, starting with Title + .Aggregate(new StringBuilder(Title), (builder, attr) => builder.Append(" - ").Append(attr)) + .ToString(); + } + + return string.Join(" ", attributes); } - return string.Join(" ", attributes); - } - - if (Type == MediaStreamType.Subtitle) - { - - var attributes = new List(); - - if (!string.IsNullOrEmpty(Language)) + case MediaStreamType.Subtitle: { - attributes.Add(StringHelper.FirstToUpper(Language)); - } - else - { - attributes.Add(string.IsNullOrEmpty(localizedUndefined) ? "Und" : localizedUndefined); + var attributes = new List(); + + if (!string.IsNullOrEmpty(Language)) + { + attributes.Add(StringHelper.FirstToUpper(Language)); + } + else + { + attributes.Add(string.IsNullOrEmpty(localizedUndefined) ? "Und" : localizedUndefined); + } + + if (IsDefault) + { + attributes.Add(string.IsNullOrEmpty(localizedDefault) ? "Default" : localizedDefault); + } + + if (IsForced) + { + attributes.Add(string.IsNullOrEmpty(localizedForced) ? "Forced" : localizedForced); + } + + if (!string.IsNullOrEmpty(Title)) + { + return attributes.AsEnumerable() + // keep Tags that are not already in Title + .Where(tag => Title.IndexOf(tag, StringComparison.OrdinalIgnoreCase) == -1) + // attributes concatenation, starting with Title + .Aggregate(new StringBuilder(Title), (builder, attr) => builder.Append(" - ").Append(attr)) + .ToString(); + } + + return string.Join(" - ", attributes.ToArray()); } - if (IsDefault) - { - attributes.Add(string.IsNullOrEmpty(localizedDefault) ? "Default" : localizedDefault); - } - - if (IsForced) - { - attributes.Add(string.IsNullOrEmpty(localizedForced) ? "Forced" : localizedForced); - } - - if (!string.IsNullOrEmpty(Title)) - { - return attributes.AsEnumerable() - // keep Tags that are not already in Title - .Where(tag => Title.IndexOf(tag, StringComparison.OrdinalIgnoreCase) == -1) - // attributes concatenation, starting with Title - .Aggregate(new StringBuilder(Title), (builder, attr) => builder.Append(" - ").Append(attr)) - .ToString(); - } - - return string.Join(" - ", attributes.ToArray()); + default: + return null; } - - if (Type == MediaStreamType.Video) - { - - } - - return null; } } -- cgit v1.2.3 From d85ca5276b0ab7848575a14b027d7a3e84f07b54 Mon Sep 17 00:00:00 2001 From: Pika Date: Wed, 8 Apr 2020 18:40:38 -0400 Subject: Port changes from #2773 --- MediaBrowser.Model/Entities/MediaStream.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index 68e0242a9..f96c4f7e0 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -85,7 +85,12 @@ namespace MediaBrowser.Model.Entities if (!string.IsNullOrEmpty(Language)) { - attributes.Add(StringHelper.FirstToUpper(Language)); + // Get full language string i.e. eng -> English. Will not work for some languages which use ISO 639-2/B instead of /T codes. + string fullLanguage = CultureInfo + .GetCultures(CultureTypes.NeutralCultures) + .FirstOrDefault(r => r.ThreeLetterISOLanguageName.Equals(Language, StringComparison.OrdinalIgnoreCase)) + ?.DisplayName; + attributes.Add(StringHelper.FirstToUpper(fullLanguage ?? Language)); } if (!string.IsNullOrEmpty(Codec) && !string.Equals(Codec, "dca", StringComparison.OrdinalIgnoreCase)) @@ -99,7 +104,7 @@ namespace MediaBrowser.Model.Entities if (!string.IsNullOrEmpty(ChannelLayout)) { - attributes.Add(ChannelLayout); + attributes.Add(StringHelper.FirstToUpper(ChannelLayout)); } else if (Channels.HasValue) { @@ -121,7 +126,7 @@ namespace MediaBrowser.Model.Entities .ToString(); } - return string.Join(" ", attributes); + return string.Join(" - ", attributes); } case MediaStreamType.Video: -- cgit v1.2.3 From 7aba10eff67151a9f6593e9d3d702f17029b994f Mon Sep 17 00:00:00 2001 From: Pika Date: Wed, 8 Apr 2020 18:50:25 -0400 Subject: Forgot one --- MediaBrowser.Model/Entities/MediaStream.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index f96c4f7e0..be9c39677 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -164,7 +164,12 @@ namespace MediaBrowser.Model.Entities if (!string.IsNullOrEmpty(Language)) { - attributes.Add(StringHelper.FirstToUpper(Language)); + // Get full language string i.e. eng -> English. Will not work for some languages which use ISO 639-2/B instead of /T codes. + string fullLanguage = CultureInfo + .GetCultures(CultureTypes.NeutralCultures) + .FirstOrDefault(r => r.ThreeLetterISOLanguageName.Equals(Language, StringComparison.OrdinalIgnoreCase)) + ?.DisplayName; + attributes.Add(StringHelper.FirstToUpper(fullLanguage ?? Language)); } else { -- cgit v1.2.3 From 9cca964b0880dc41792fe27ee03e69609ff2ac7e Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 9 Apr 2020 19:22:29 +0200 Subject: Address comments --- MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs | 32 +++++++++++------------ MediaBrowser.Model/Providers/ImageProviderInfo.cs | 12 ++++----- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs index ea5d4e7d7..cce508809 100644 --- a/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs +++ b/MediaBrowser.Model/MediaInfo/LiveStreamRequest.cs @@ -15,23 +15,6 @@ namespace MediaBrowser.Model.MediaInfo DirectPlayProtocols = new MediaProtocol[] { MediaProtocol.Http }; } - 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; } - - - public LiveStreamRequest(AudioOptions options) { MaxStreamingBitrate = options.MaxBitrate; @@ -47,5 +30,20 @@ namespace MediaBrowser.Model.MediaInfo SubtitleStreamIndex = videoOptions.SubtitleStreamIndex; } } + + 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/Providers/ImageProviderInfo.cs b/MediaBrowser.Model/Providers/ImageProviderInfo.cs index c63a2ceda..d1a7c2506 100644 --- a/MediaBrowser.Model/Providers/ImageProviderInfo.cs +++ b/MediaBrowser.Model/Providers/ImageProviderInfo.cs @@ -1,6 +1,3 @@ -#pragma warning disable CS1591 - -using System; using MediaBrowser.Model.Entities; namespace MediaBrowser.Model.Providers @@ -17,11 +14,14 @@ namespace MediaBrowser.Model.Providers } /// - /// Gets or sets the name. + /// Gets the name. /// /// The name. - public string Name { get; set; } + public string Name { get; } - public ImageType[] SupportedImages { get; set; } + /// + /// Gets the supported image types. + /// + public ImageType[] SupportedImages { get; } } } -- cgit v1.2.3 From 1180b9746fe7c4a6562baff77910819a6706510b Mon Sep 17 00:00:00 2001 From: ZadenRB Date: Wed, 15 Apr 2020 00:01:31 -0600 Subject: Migrates the notifications service to use ASP.NET MVC framework --- Emby.Notifications/Api/NotificationsService.cs | 189 --------------------- .../Controllers/NotificationsController.cs | 138 +++++++++++++++ .../Models/NotificationDtos/NotificationDto.cs | 51 ++++++ .../NotificationDtos/NotificationsSummaryDto.cs | 20 +++ .../Extensions/ApiServiceCollectionExtensions.cs | 1 + 5 files changed, 210 insertions(+), 189 deletions(-) delete mode 100644 Emby.Notifications/Api/NotificationsService.cs create mode 100644 Jellyfin.Api/Controllers/NotificationsController.cs create mode 100644 Jellyfin.Api/Models/NotificationDtos/NotificationDto.cs create mode 100644 Jellyfin.Api/Models/NotificationDtos/NotificationsSummaryDto.cs diff --git a/Emby.Notifications/Api/NotificationsService.cs b/Emby.Notifications/Api/NotificationsService.cs deleted file mode 100644 index 788750796..000000000 --- a/Emby.Notifications/Api/NotificationsService.cs +++ /dev/null @@ -1,189 +0,0 @@ -#pragma warning disable CS1591 -#pragma warning disable SA1402 -#pragma warning disable SA1649 - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Notifications; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Notifications; -using MediaBrowser.Model.Services; - -namespace Emby.Notifications.Api -{ - [Route("/Notifications/{UserId}", "GET", Summary = "Gets notifications")] - public class GetNotifications : IReturn - { - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UserId { get; set; } = string.Empty; - - [ApiMember(Name = "IsRead", Description = "An optional filter by IsRead", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsRead { get; set; } - - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - [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; } - } - - public class Notification - { - public string Id { get; set; } = string.Empty; - - public string UserId { get; set; } = string.Empty; - - public DateTime Date { get; set; } - - public bool IsRead { get; set; } - - public string Name { get; set; } = string.Empty; - - public string Description { get; set; } = string.Empty; - - public string Url { get; set; } = string.Empty; - - public NotificationLevel Level { get; set; } - } - - public class NotificationResult - { - public IReadOnlyList Notifications { get; set; } = Array.Empty(); - - public int TotalRecordCount { get; set; } - } - - public class NotificationsSummary - { - public int UnreadCount { get; set; } - - public NotificationLevel MaxUnreadNotificationLevel { get; set; } - } - - [Route("/Notifications/{UserId}/Summary", "GET", Summary = "Gets a notification summary for a user")] - public class GetNotificationsSummary : IReturn - { - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UserId { get; set; } = string.Empty; - } - - [Route("/Notifications/Types", "GET", Summary = "Gets notification types")] - public class GetNotificationTypes : IReturn> - { - } - - [Route("/Notifications/Services", "GET", Summary = "Gets notification types")] - public class GetNotificationServices : IReturn> - { - } - - [Route("/Notifications/Admin", "POST", Summary = "Sends a notification to all admin users")] - public class AddAdminNotification : IReturnVoid - { - [ApiMember(Name = "Name", Description = "The notification's name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Name { get; set; } = string.Empty; - - [ApiMember(Name = "Description", Description = "The notification's description", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Description { get; set; } = string.Empty; - - [ApiMember(Name = "ImageUrl", Description = "The notification's image url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string? ImageUrl { get; set; } - - [ApiMember(Name = "Url", Description = "The notification's info url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string? Url { get; set; } - - [ApiMember(Name = "Level", Description = "The notification level", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public NotificationLevel Level { get; set; } - } - - [Route("/Notifications/{UserId}/Read", "POST", Summary = "Marks notifications as read")] - public class MarkRead : IReturnVoid - { - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string UserId { get; set; } = string.Empty; - - [ApiMember(Name = "Ids", Description = "A list of notification ids, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)] - public string Ids { get; set; } = string.Empty; - } - - [Route("/Notifications/{UserId}/Unread", "POST", Summary = "Marks notifications as unread")] - public class MarkUnread : IReturnVoid - { - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string UserId { get; set; } = string.Empty; - - [ApiMember(Name = "Ids", Description = "A list of notification ids, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST", AllowMultiple = true)] - public string Ids { get; set; } = string.Empty; - } - - [Authenticated] - public class NotificationsService : IService - { - private readonly INotificationManager _notificationManager; - private readonly IUserManager _userManager; - - public NotificationsService(INotificationManager notificationManager, IUserManager userManager) - { - _notificationManager = notificationManager; - _userManager = userManager; - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Get(GetNotificationTypes request) - { - return _notificationManager.GetNotificationTypes(); - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Get(GetNotificationServices request) - { - return _notificationManager.GetNotificationServices().ToList(); - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Get(GetNotificationsSummary request) - { - return new NotificationsSummary - { - }; - } - - public Task Post(AddAdminNotification request) - { - // This endpoint really just exists as post of a real with sickbeard - var notification = new NotificationRequest - { - Date = DateTime.UtcNow, - Description = request.Description, - Level = request.Level, - Name = request.Name, - Url = request.Url, - UserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id).ToArray() - }; - - return _notificationManager.SendNotification(notification, CancellationToken.None); - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public void Post(MarkRead request) - { - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public void Post(MarkUnread request) - { - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Get(GetNotifications request) - { - return new NotificationResult(); - } - } -} diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs new file mode 100644 index 000000000..6602fca9c --- /dev/null +++ b/Jellyfin.Api/Controllers/NotificationsController.cs @@ -0,0 +1,138 @@ +#nullable enable +#pragma warning disable CA1801 +#pragma warning disable SA1313 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Jellyfin.Api.Models.NotificationDtos; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Notifications; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Notifications; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers +{ + /// + /// The notification controller. + /// + public class NotificationsController : BaseJellyfinApiController + { + private readonly INotificationManager _notificationManager; + private readonly IUserManager _userManager; + + /// + /// Initializes a new instance of the class. + /// + /// The notification manager. + /// The user manager. + public NotificationsController(INotificationManager notificationManager, IUserManager userManager) + { + _notificationManager = notificationManager; + _userManager = userManager; + } + + /// + /// Endpoint for getting a user's notifications. + /// + /// The UserID. + /// An optional filter by IsRead. + /// The optional index to start at. All notifications with a lower index will be dropped from the results. + /// An optional limit on the number of notifications returned. + /// A read-only list of all of the user's notifications. + [HttpGet("{UserID}")] + public IReadOnlyList GetNotifications( + [FromRoute] string UserID, + [FromQuery] bool? IsRead, + [FromQuery] int? StartIndex, + [FromQuery] int? Limit) + { + return new List(); + } + + /// + /// Endpoint for getting a user's notification summary. + /// + /// The UserID. + /// Notifications summary for the user. + [HttpGet("{UserId}/Summary")] + public NotificationsSummaryDto GetNotificationsSummary( + [FromRoute] string UserID) + { + return new NotificationsSummaryDto(); + } + + /// + /// Endpoint for getting notification types. + /// + /// All notification types. + [HttpGet("Types")] + public IEnumerable GetNotificationTypes() + { + return _notificationManager.GetNotificationTypes(); + } + + /// + /// Endpoint for getting notification services. + /// + /// All notification services. + [HttpGet("Services")] + public IEnumerable GetNotificationServices() + { + return _notificationManager.GetNotificationServices(); + } + + /// + /// Endpoint to send a notification to all admins. + /// + /// The name of the notification. + /// The description of the notification. + /// The URL of the notification. + /// The level of the notification. + [HttpPost("Admin")] + public void CreateAdminNotification( + [FromForm] string Name, + [FromForm] string Description, + [FromForm] string? URL, + [FromForm] NotificationLevel Level) + { + var notification = new NotificationRequest + { + Name = Name, + Description = Description, + Url = URL, + Level = Level, + UserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id).ToArray(), + Date = DateTime.UtcNow, + }; + + _notificationManager.SendNotification(notification, CancellationToken.None); + } + + /// + /// Endpoint to set notifications as read. + /// + /// The UserID. + /// The IDs of notifications which should be set as read. + [HttpPost("{UserID}/Read")] + public void SetRead( + [FromRoute] string UserID, + [FromForm] List IDs) + { + } + + /// + /// Endpoint to set notifications as unread. + /// + /// The UserID. + /// The IDs of notifications which should be set as unread. + [HttpPost("{UserID}/Unread")] + public void SetUnread( + [FromRoute] string UserID, + [FromForm] List IDs) + { + } + } +} diff --git a/Jellyfin.Api/Models/NotificationDtos/NotificationDto.cs b/Jellyfin.Api/Models/NotificationDtos/NotificationDto.cs new file mode 100644 index 000000000..7ecd2a49d --- /dev/null +++ b/Jellyfin.Api/Models/NotificationDtos/NotificationDto.cs @@ -0,0 +1,51 @@ +using System; +using MediaBrowser.Model.Notifications; + +namespace Jellyfin.Api.Models.NotificationDtos +{ + /// + /// The notification DTO. + /// + public class NotificationDto + { + /// + /// Gets or sets the notification ID. Defaults to an empty string. + /// + public string Id { get; set; } = string.Empty; + + /// + /// Gets or sets the notification's user ID. Defaults to an empty string. + /// + public string UserId { get; set; } = string.Empty; + + /// + /// Gets or sets the notification date. + /// + public DateTime Date { get; set; } + + /// + /// Gets or sets a value indicating whether the notification has been read. + /// + public bool IsRead { get; set; } + + /// + /// Gets or sets the notification's name. + /// + public string Name { get; set; } = string.Empty; + + /// + /// Gets or sets the notification's description. + /// + public string Description { get; set; } = string.Empty; + + /// + /// Gets or sets the notification's URL. + /// + public string Url { get; set; } = string.Empty; + + /// + /// Gets or sets the notification level. + /// + public NotificationLevel Level { get; set; } + } +} diff --git a/Jellyfin.Api/Models/NotificationDtos/NotificationsSummaryDto.cs b/Jellyfin.Api/Models/NotificationDtos/NotificationsSummaryDto.cs new file mode 100644 index 000000000..c18ab545d --- /dev/null +++ b/Jellyfin.Api/Models/NotificationDtos/NotificationsSummaryDto.cs @@ -0,0 +1,20 @@ +using MediaBrowser.Model.Notifications; + +namespace Jellyfin.Api.Models.NotificationDtos +{ + /// + /// The notification summary DTO. + /// + public class NotificationsSummaryDto + { + /// + /// Gets or sets the number of unread notifications. + /// + public int UnreadCount { get; set; } + + /// + /// Gets or sets the maximum unread notification level. + /// + public NotificationLevel MaxUnreadNotificationLevel { get; set; } + } +} diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index dd4f9cd23..b3164e068 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -71,6 +71,7 @@ namespace Jellyfin.Server.Extensions // Clear app parts to avoid other assemblies being picked up .ConfigureApplicationPartManager(a => a.ApplicationParts.Clear()) .AddApplicationPart(typeof(StartupController).Assembly) + .AddApplicationPart(typeof(NotificationsController).Assembly) .AddControllersAsServices(); } -- cgit v1.2.3 From ad1c880751dda93f1226e3846bb6a344ac58d0b6 Mon Sep 17 00:00:00 2001 From: ZadenRB Date: Wed, 15 Apr 2020 00:34:50 -0600 Subject: Lowercase parameters --- .../Controllers/NotificationsController.cs | 63 +++++++++++----------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs index 6602fca9c..31747584e 100644 --- a/Jellyfin.Api/Controllers/NotificationsController.cs +++ b/Jellyfin.Api/Controllers/NotificationsController.cs @@ -1,6 +1,5 @@ #nullable enable #pragma warning disable CA1801 -#pragma warning disable SA1313 using System; using System.Collections.Generic; @@ -37,17 +36,17 @@ namespace Jellyfin.Api.Controllers /// /// Endpoint for getting a user's notifications. /// - /// The UserID. - /// An optional filter by IsRead. - /// The optional index to start at. All notifications with a lower index will be dropped from the results. - /// An optional limit on the number of notifications returned. + /// The UserID. + /// An optional filter by IsRead. + /// The optional index to start at. All notifications with a lower index will be dropped from the results. + /// An optional limit on the number of notifications returned. /// A read-only list of all of the user's notifications. [HttpGet("{UserID}")] public IReadOnlyList GetNotifications( - [FromRoute] string UserID, - [FromQuery] bool? IsRead, - [FromQuery] int? StartIndex, - [FromQuery] int? Limit) + [FromRoute] string userID, + [FromQuery] bool? isRead, + [FromQuery] int? startIndex, + [FromQuery] int? limit) { return new List(); } @@ -55,11 +54,11 @@ namespace Jellyfin.Api.Controllers /// /// Endpoint for getting a user's notification summary. /// - /// The UserID. + /// The userID. /// Notifications summary for the user. - [HttpGet("{UserId}/Summary")] + [HttpGet("{UserID}/Summary")] public NotificationsSummaryDto GetNotificationsSummary( - [FromRoute] string UserID) + [FromRoute] string userID) { return new NotificationsSummaryDto(); } @@ -87,23 +86,23 @@ namespace Jellyfin.Api.Controllers /// /// Endpoint to send a notification to all admins. /// - /// The name of the notification. - /// The description of the notification. - /// The URL of the notification. - /// The level of the notification. + /// The name of the notification. + /// The description of the notification. + /// The URL of the notification. + /// The level of the notification. [HttpPost("Admin")] public void CreateAdminNotification( - [FromForm] string Name, - [FromForm] string Description, - [FromForm] string? URL, - [FromForm] NotificationLevel Level) + [FromForm] string name, + [FromForm] string description, + [FromForm] string? url, + [FromForm] NotificationLevel level) { var notification = new NotificationRequest { - Name = Name, - Description = Description, - Url = URL, - Level = Level, + Name = name, + Description = description, + Url = url, + Level = level, UserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id).ToArray(), Date = DateTime.UtcNow, }; @@ -114,24 +113,24 @@ namespace Jellyfin.Api.Controllers /// /// Endpoint to set notifications as read. /// - /// The UserID. - /// The IDs of notifications which should be set as read. + /// The userID. + /// The IDs of notifications which should be set as read. [HttpPost("{UserID}/Read")] public void SetRead( - [FromRoute] string UserID, - [FromForm] List IDs) + [FromRoute] string userID, + [FromForm] List ids) { } /// /// Endpoint to set notifications as unread. /// - /// The UserID. - /// The IDs of notifications which should be set as unread. + /// The userID. + /// The IDs of notifications which should be set as unread. [HttpPost("{UserID}/Unread")] public void SetUnread( - [FromRoute] string UserID, - [FromForm] List IDs) + [FromRoute] string userID, + [FromForm] List ids) { } } -- cgit v1.2.3 From 27ce10d0c13bc30fa1b08682e13bab67784b289d Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 15 Apr 2020 11:18:13 +0200 Subject: Fix release build --- MediaBrowser.Model/Providers/ImageProviderInfo.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MediaBrowser.Model/Providers/ImageProviderInfo.cs b/MediaBrowser.Model/Providers/ImageProviderInfo.cs index d1a7c2506..19af81c85 100644 --- a/MediaBrowser.Model/Providers/ImageProviderInfo.cs +++ b/MediaBrowser.Model/Providers/ImageProviderInfo.cs @@ -7,6 +7,11 @@ namespace MediaBrowser.Model.Providers /// public class ImageProviderInfo { + /// + /// Initializes a new instance of the class. + /// + /// The name of the image provider. + /// The image types supported by the image provider. public ImageProviderInfo(string name, ImageType[] supportedImages) { Name = name; -- cgit v1.2.3 From 558b50a094adc82728a52b13862e19bc04783679 Mon Sep 17 00:00:00 2001 From: ZadenRB Date: Wed, 15 Apr 2020 09:29:29 -0600 Subject: Remove unnecessary assembly, update casing, enable nullable reference types on notification DTOs. --- Jellyfin.Api/Controllers/NotificationsController.cs | 20 ++++++++++---------- .../Models/NotificationDtos/NotificationDto.cs | 16 +++++++++------- .../NotificationDtos/NotificationsSummaryDto.cs | 4 +++- .../Extensions/ApiServiceCollectionExtensions.cs | 1 - 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs index 31747584e..c8a5be89b 100644 --- a/Jellyfin.Api/Controllers/NotificationsController.cs +++ b/Jellyfin.Api/Controllers/NotificationsController.cs @@ -36,14 +36,14 @@ namespace Jellyfin.Api.Controllers /// /// Endpoint for getting a user's notifications. /// - /// The UserID. + /// The user's ID. /// An optional filter by IsRead. /// The optional index to start at. All notifications with a lower index will be dropped from the results. /// An optional limit on the number of notifications returned. /// A read-only list of all of the user's notifications. [HttpGet("{UserID}")] public IReadOnlyList GetNotifications( - [FromRoute] string userID, + [FromRoute] string userId, [FromQuery] bool? isRead, [FromQuery] int? startIndex, [FromQuery] int? limit) @@ -54,11 +54,11 @@ namespace Jellyfin.Api.Controllers /// /// Endpoint for getting a user's notification summary. /// - /// The userID. + /// The user's ID. /// Notifications summary for the user. [HttpGet("{UserID}/Summary")] public NotificationsSummaryDto GetNotificationsSummary( - [FromRoute] string userID) + [FromRoute] string userId) { return new NotificationsSummaryDto(); } @@ -95,14 +95,14 @@ namespace Jellyfin.Api.Controllers [FromForm] string name, [FromForm] string description, [FromForm] string? url, - [FromForm] NotificationLevel level) + [FromForm] NotificationLevel? level) { var notification = new NotificationRequest { Name = name, Description = description, Url = url, - Level = level, + Level = level ?? NotificationLevel.Normal, UserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id).ToArray(), Date = DateTime.UtcNow, }; @@ -113,11 +113,11 @@ namespace Jellyfin.Api.Controllers /// /// Endpoint to set notifications as read. /// - /// The userID. + /// The userID. /// The IDs of notifications which should be set as read. [HttpPost("{UserID}/Read")] public void SetRead( - [FromRoute] string userID, + [FromRoute] string userId, [FromForm] List ids) { } @@ -125,11 +125,11 @@ namespace Jellyfin.Api.Controllers /// /// Endpoint to set notifications as unread. /// - /// The userID. + /// The userID. /// The IDs of notifications which should be set as unread. [HttpPost("{UserID}/Unread")] public void SetUnread( - [FromRoute] string userID, + [FromRoute] string userId, [FromForm] List ids) { } diff --git a/Jellyfin.Api/Models/NotificationDtos/NotificationDto.cs b/Jellyfin.Api/Models/NotificationDtos/NotificationDto.cs index 7ecd2a49d..c849ecd75 100644 --- a/Jellyfin.Api/Models/NotificationDtos/NotificationDto.cs +++ b/Jellyfin.Api/Models/NotificationDtos/NotificationDto.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; using MediaBrowser.Model.Notifications; @@ -24,28 +26,28 @@ namespace Jellyfin.Api.Models.NotificationDtos public DateTime Date { get; set; } /// - /// Gets or sets a value indicating whether the notification has been read. + /// Gets or sets a value indicating whether the notification has been read. Defaults to false. /// - public bool IsRead { get; set; } + public bool IsRead { get; set; } = false; /// - /// Gets or sets the notification's name. + /// Gets or sets the notification's name. Defaults to an empty string. /// public string Name { get; set; } = string.Empty; /// - /// Gets or sets the notification's description. + /// Gets or sets the notification's description. Defaults to an empty string. /// public string Description { get; set; } = string.Empty; /// - /// Gets or sets the notification's URL. + /// Gets or sets the notification's URL. Defaults to null. /// - public string Url { get; set; } = string.Empty; + public string? Url { get; set; } /// /// Gets or sets the notification level. /// - public NotificationLevel Level { get; set; } + public NotificationLevel? Level { get; set; } } } diff --git a/Jellyfin.Api/Models/NotificationDtos/NotificationsSummaryDto.cs b/Jellyfin.Api/Models/NotificationDtos/NotificationsSummaryDto.cs index c18ab545d..b3746ee2d 100644 --- a/Jellyfin.Api/Models/NotificationDtos/NotificationsSummaryDto.cs +++ b/Jellyfin.Api/Models/NotificationDtos/NotificationsSummaryDto.cs @@ -1,3 +1,5 @@ +#nullable enable + using MediaBrowser.Model.Notifications; namespace Jellyfin.Api.Models.NotificationDtos @@ -15,6 +17,6 @@ namespace Jellyfin.Api.Models.NotificationDtos /// /// Gets or sets the maximum unread notification level. /// - public NotificationLevel MaxUnreadNotificationLevel { get; set; } + public NotificationLevel? MaxUnreadNotificationLevel { get; set; } } } diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index b3164e068..dd4f9cd23 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -71,7 +71,6 @@ namespace Jellyfin.Server.Extensions // Clear app parts to avoid other assemblies being picked up .ConfigureApplicationPartManager(a => a.ApplicationParts.Clear()) .AddApplicationPart(typeof(StartupController).Assembly) - .AddApplicationPart(typeof(NotificationsController).Assembly) .AddControllersAsServices(); } -- 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 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index ce956176e..edd33a2fb 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 81a80ddb2..de044a4aa 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 000000000..30418097c --- /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 de768333d..2c8b2f29d 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 000000000..889a78839 --- /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 401514349..ebcacd2a3 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 000000000..e4a790ffe --- /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 771027103..74ffd5a18 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 000000000..bc3fd0046 --- /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 000000000..671b7cc94 --- /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 000000000..9f250519b --- /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 fee76097f44d0f11356e4a559df9178063b1bf29 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Thu, 16 Apr 2020 21:45:00 -0400 Subject: Remove CanConnectWithHttps Property It is only used in one place and only adds confusion by existing --- Emby.Server.Implementations/ApplicationHost.cs | 4 +--- MediaBrowser.Controller/IServerApplicationHost.cs | 6 ------ 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 6a324e090..a39baa84a 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1413,7 +1413,7 @@ namespace Emby.Server.Implementations InternalMetadataPath = ApplicationPaths.InternalMetadataPath, CachePath = ApplicationPaths.CachePath, HttpServerPortNumber = HttpPort, - SupportsHttps = CanConnectWithHttps, + SupportsHttps = ListenWithHttps || ServerConfigurationManager.Configuration.IsBehindProxy, HttpsPortNumber = HttpsPort, OperatingSystem = OperatingSystem.Id.ToString(), OperatingSystemDisplayName = OperatingSystem.Name, @@ -1455,8 +1455,6 @@ namespace Emby.Server.Implementations public bool ListenWithHttps => Certificate != null && ServerConfigurationManager.Configuration.EnableHttps; /// - public bool CanConnectWithHttps => ListenWithHttps || ServerConfigurationManager.Configuration.IsBehindProxy; - public async Task GetLocalApiUrl(CancellationToken cancellationToken) { try diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 7742279f7..d999f76db 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -43,12 +43,6 @@ namespace MediaBrowser.Controller /// bool ListenWithHttps { get; } - /// - /// Gets a value indicating whether a client can connect to the server over HTTPS, either directly or via a - /// reverse proxy. - /// - bool CanConnectWithHttps { get; } - /// /// Gets a value indicating whether this instance has update available. /// -- cgit v1.2.3 From 00a0e013c695a3741218985afadd31265a6ddb40 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Thu, 16 Apr 2020 21:46:49 -0400 Subject: Update documentation for URL methods in ApplicationHost --- Emby.Server.Implementations/ApplicationHost.cs | 6 +++--- MediaBrowser.Controller/IServerApplicationHost.cs | 24 +++++++++++++---------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index a39baa84a..7699faf14 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1510,7 +1510,7 @@ namespace Emby.Server.Implementations return GetLocalApiUrl(ipAddress.ToString()); } - /// + /// public string GetLocalApiUrl(ReadOnlySpan host) { var url = new StringBuilder(64); @@ -1559,7 +1559,7 @@ namespace Emby.Server.Implementations } } - var valid = await IsIpAddressValidAsync(address, cancellationToken).ConfigureAwait(false); + var valid = await IsLocalIpAddressValidAsync(address, cancellationToken).ConfigureAwait(false); if (valid) { resultList.Add(address); @@ -1593,7 +1593,7 @@ namespace Emby.Server.Implementations private readonly ConcurrentDictionary _validAddressResults = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - private async Task IsIpAddressValidAsync(IPAddress address, CancellationToken cancellationToken) + private async Task IsLocalIpAddressValidAsync(IPAddress address, CancellationToken cancellationToken) { if (address.Equals(IPAddress.Loopback) || address.Equals(IPAddress.IPv6Loopback)) diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index d999f76db..8537e4180 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -56,29 +56,33 @@ namespace MediaBrowser.Controller string FriendlyName { get; } /// - /// Gets the local ip address. + /// Gets all the local IP addresses of this API instance. Each address is validated by sending a 'ping' request + /// to the API that should exist at the address. /// - /// The local ip address. + /// A cancellation token that can be used to cancel the task. + /// A list containing all the local IP addresses of the server. Task> GetLocalIpAddresses(CancellationToken cancellationToken); /// - /// Gets the local API URL. + /// Gets a local (LAN) URL that can be used to access the API. The hostname used is the first valid configured + /// IP address that can be found via . /// - /// The local API URL. + /// A cancellation token that can be used to cancel the task. + /// The server URL. Task GetLocalApiUrl(CancellationToken cancellationToken); /// - /// Gets the local API URL. + /// Gets a local (LAN) URL that can be used to access the API. /// - /// The hostname. - /// The local API URL. + /// The hostname to use in the URL. + /// The API URL. string GetLocalApiUrl(ReadOnlySpan hostname); /// - /// Gets the local API URL. + /// Gets a local (LAN) URL that can be used to access the API. /// - /// The IP address. - /// The local API URL. + /// The IP address to use as the hostname in the URL. + /// The API URL. string GetLocalApiUrl(IPAddress address); /// -- cgit v1.2.3 From 1666f3ca148c38609c85cb81e1e8b69e3473e319 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Thu, 16 Apr 2020 23:40:32 -0400 Subject: Use dependency injection to construct migration routines --- Jellyfin.Server/Migrations/IMigrationRoutine.cs | 4 +--- Jellyfin.Server/Migrations/MigrationRunner.cs | 19 ++++++++++++------- .../Routines/CreateUserLoggingConfigFile.cs | 11 +++++++++-- .../Routines/DisableTranscodingThrottling.cs | 17 +++++++++++++---- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/Jellyfin.Server/Migrations/IMigrationRoutine.cs b/Jellyfin.Server/Migrations/IMigrationRoutine.cs index eab995d67..b79fdeac0 100644 --- a/Jellyfin.Server/Migrations/IMigrationRoutine.cs +++ b/Jellyfin.Server/Migrations/IMigrationRoutine.cs @@ -21,8 +21,6 @@ namespace Jellyfin.Server.Migrations /// /// Execute the migration routine. /// - /// Host that hosts current version. - /// Host logger. - public void Perform(CoreAppHost host, ILogger logger); + public void Perform(); } } diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index b5ea04dca..ca1748282 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using MediaBrowser.Common.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Migrations @@ -13,10 +14,10 @@ namespace Jellyfin.Server.Migrations /// /// The list of known migrations, in order of applicability. /// - internal static readonly IMigrationRoutine[] Migrations = + private static readonly Type[] _migrationTypes = { - new Routines.DisableTranscodingThrottling(), - new Routines.CreateUserLoggingConfigFile() + typeof(Routines.DisableTranscodingThrottling), + typeof(Routines.CreateUserLoggingConfigFile) }; /// @@ -27,6 +28,10 @@ namespace Jellyfin.Server.Migrations public static void Run(CoreAppHost host, ILoggerFactory loggerFactory) { var logger = loggerFactory.CreateLogger(); + var migrations = _migrationTypes + .Select(m => ActivatorUtilities.CreateInstance(host.ServiceProvider, m)) + .OfType() + .ToArray(); var migrationOptions = ((IConfigurationManager)host.ServerConfigurationManager).GetConfiguration(MigrationsListStore.StoreKey); if (!host.ServerConfigurationManager.Configuration.IsStartupWizardCompleted && migrationOptions.Applied.Count == 0) @@ -34,16 +39,16 @@ namespace Jellyfin.Server.Migrations // If startup wizard is not finished, this is a fresh install. // Don't run any migrations, just mark all of them as applied. logger.LogInformation("Marking all known migrations as applied because this is a fresh install"); - migrationOptions.Applied.AddRange(Migrations.Select(m => (m.Id, m.Name))); + migrationOptions.Applied.AddRange(migrations.Select(m => (m.Id, m.Name))); host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions); return; } var appliedMigrationIds = migrationOptions.Applied.Select(m => m.Id).ToHashSet(); - for (var i = 0; i < Migrations.Length; i++) + for (var i = 0; i < migrations.Length; i++) { - var migrationRoutine = Migrations[i]; + var migrationRoutine = migrations[i]; if (appliedMigrationIds.Contains(migrationRoutine.Id)) { logger.LogDebug("Skipping migration '{Name}' since it is already applied", migrationRoutine.Name); @@ -54,7 +59,7 @@ namespace Jellyfin.Server.Migrations try { - migrationRoutine.Perform(host, logger); + migrationRoutine.Perform(); } catch (Exception ex) { diff --git a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs index 3bc32c047..89514c89b 100644 --- a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs +++ b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs @@ -36,6 +36,13 @@ namespace Jellyfin.Server.Migrations.Routines @"{""Serilog"":{""MinimumLevel"":""Information"",""WriteTo"":[{""Name"":""Console"",""Args"":{""outputTemplate"":""[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}""}},{""Name"":""Async"",""Args"":{""configure"":[{""Name"":""File"",""Args"":{""path"":""%JELLYFIN_LOG_DIR%//log_.log"",""rollingInterval"":""Day"",""retainedFileCountLimit"":3,""rollOnFileSizeLimit"":true,""fileSizeLimitBytes"":100000000,""outputTemplate"":""[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] [{ThreadId}] {SourceContext}:{Message}{NewLine}{Exception}""}}]}}],""Enrich"":[""FromLogContext"",""WithThreadId""]}}", }; + private readonly IApplicationPaths _appPaths; + + public CreateUserLoggingConfigFile(IApplicationPaths appPaths) + { + _appPaths = appPaths; + } + /// public Guid Id => Guid.Parse("{EF103419-8451-40D8-9F34-D1A8E93A1679}"); @@ -43,9 +50,9 @@ namespace Jellyfin.Server.Migrations.Routines public string Name => "CreateLoggingConfigHeirarchy"; /// - public void Perform(CoreAppHost host, ILogger logger) + public void Perform() { - var logDirectory = host.Resolve().ConfigurationDirectoryPath; + var logDirectory = _appPaths.ConfigurationDirectoryPath; var existingConfigPath = Path.Combine(logDirectory, "logging.json"); // If the existing logging.json config file is unmodified, then 'reset' it by moving it to 'logging.old.json' diff --git a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs index 6f8e4a8ff..b2e957d5b 100644 --- a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs +++ b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs @@ -10,6 +10,15 @@ namespace Jellyfin.Server.Migrations.Routines /// internal class DisableTranscodingThrottling : IMigrationRoutine { + private readonly ILogger _logger; + private readonly IConfigurationManager _configManager; + + public DisableTranscodingThrottling(ILogger logger, IConfigurationManager configManager) + { + _logger = logger; + _configManager = configManager; + } + /// public Guid Id => Guid.Parse("{4124C2CD-E939-4FFB-9BE9-9B311C413638}"); @@ -17,16 +26,16 @@ namespace Jellyfin.Server.Migrations.Routines public string Name => "DisableTranscodingThrottling"; /// - public void Perform(CoreAppHost host, ILogger logger) + public void Perform() { // Set EnableThrottling to false since it wasn't used before and may introduce issues - var encoding = ((IConfigurationManager)host.ServerConfigurationManager).GetConfiguration("encoding"); + var encoding = _configManager.GetConfiguration("encoding"); if (encoding.EnableThrottling) { - logger.LogInformation("Disabling transcoding throttling during migration"); + _logger.LogInformation("Disabling transcoding throttling during migration"); encoding.EnableThrottling = false; - host.ServerConfigurationManager.SaveConfiguration("encoding", encoding); + _configManager.SaveConfiguration("encoding", encoding); } } } -- cgit v1.2.3 From f055995a1f59e3395e99e8fc9b470d1dfed2914b Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 17 Apr 2020 14:21:15 +0200 Subject: Use System.Buffers in RangeRequestWriter --- .../HttpServer/RangeRequestWriter.cs | 181 ++++++++++----------- 1 file changed, 84 insertions(+), 97 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index 8b9028f6b..980c2cd3a 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System; +using System.Buffers; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -8,46 +9,17 @@ using System.Net; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.HttpServer { public class RangeRequestWriter : IAsyncStreamWriter, IHttpResult { - /// - /// Gets or sets the source stream. - /// - /// 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; - /// - /// The _options - /// private readonly Dictionary _options = new Dictionary(); - /// - /// The us culture - /// - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - /// - /// Additional HTTP Headers - /// - /// The headers. - public IDictionary Headers => _options; + private List> _requestedRanges; /// /// Initializes a new instance of the class. @@ -57,8 +29,7 @@ namespace Emby.Server.Implementations.HttpServer /// 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) + public RangeRequestWriter(string rangeHeader, long contentLength, Stream source, string contentType, bool isHeadRequest) { if (string.IsNullOrEmpty(contentType)) { @@ -68,7 +39,6 @@ namespace Emby.Server.Implementations.HttpServer RangeHeader = rangeHeader; SourceStream = source; IsHeadRequest = isHeadRequest; - this._logger = logger; ContentType = contentType; Headers[HeaderNames.ContentType] = contentType; @@ -79,40 +49,26 @@ namespace Emby.Server.Implementations.HttpServer } /// - /// Sets the range values. + /// Gets or sets the source stream. /// - private void SetRangeValues(long contentLength) - { - var requestedRange = RequestedRanges[0]; - - TotalContentLength = contentLength; - - // If the requested range is "0-", we can optimize by just doing a stream copy - if (!requestedRange.Value.HasValue) - { - RangeEnd = TotalContentLength - 1; - } - else - { - RangeEnd = requestedRange.Value.Value; - } - - RangeStart = requestedRange.Key; - RangeLength = 1 + RangeEnd - RangeStart; + /// The source stream. + private Stream SourceStream { get; set; } + private string RangeHeader { get; set; } + private bool IsHeadRequest { get; set; } - Headers[HeaderNames.ContentLength] = RangeLength.ToString(CultureInfo.InvariantCulture); - Headers[HeaderNames.ContentRange] = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; + private long RangeStart { get; set; } + private long RangeEnd { get; set; } + private long RangeLength { get; set; } + private long TotalContentLength { get; set; } - if (RangeStart > 0 && SourceStream.CanSeek) - { - SourceStream.Position = RangeStart; - } - } + public Action OnComplete { get; set; } /// - /// The _requested ranges + /// Additional HTTP Headers /// - private List> _requestedRanges; + /// The headers. + public IDictionary Headers => _options; + /// /// Gets the requested ranges. /// @@ -137,11 +93,12 @@ namespace Emby.Server.Implementations.HttpServer if (!string.IsNullOrEmpty(vals[0])) { - start = long.Parse(vals[0], UsCulture); + start = long.Parse(vals[0], CultureInfo.InvariantCulture); } + if (!string.IsNullOrEmpty(vals[1])) { - end = long.Parse(vals[1], UsCulture); + end = long.Parse(vals[1], CultureInfo.InvariantCulture); } _requestedRanges.Add(new KeyValuePair(start, end)); @@ -152,6 +109,51 @@ namespace Emby.Server.Implementations.HttpServer } } + public string ContentType { get; set; } + + public IRequest RequestContext { get; set; } + + public object Response { get; set; } + + public int Status { get; set; } + + public HttpStatusCode StatusCode + { + get => (HttpStatusCode)Status; + set => Status = (int)value; + } + + /// + /// Sets the range values. + /// + private void SetRangeValues(long contentLength) + { + var requestedRange = RequestedRanges[0]; + + TotalContentLength = contentLength; + + // If the requested range is "0-", we can optimize by just doing a stream copy + if (!requestedRange.Value.HasValue) + { + RangeEnd = TotalContentLength - 1; + } + else + { + RangeEnd = requestedRange.Value.Value; + } + + RangeStart = requestedRange.Key; + RangeLength = 1 + RangeEnd - RangeStart; + + Headers[HeaderNames.ContentLength] = RangeLength.ToString(CultureInfo.InvariantCulture); + Headers[HeaderNames.ContentRange] = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; + + if (RangeStart > 0 && SourceStream.CanSeek) + { + SourceStream.Position = RangeStart; + } + } + public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) { try @@ -167,59 +169,44 @@ namespace Emby.Server.Implementations.HttpServer // If the requested range is "0-", we can optimize by just doing a stream copy if (RangeEnd >= TotalContentLength - 1) { - await source.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false); + await source.CopyToAsync(responseStream, BufferSize, cancellationToken).ConfigureAwait(false); } else { - await CopyToInternalAsync(source, responseStream, RangeLength).ConfigureAwait(false); + await CopyToInternalAsync(source, responseStream, RangeLength, cancellationToken).ConfigureAwait(false); } } } finally { - if (OnComplete != null) - { - OnComplete(); - } + OnComplete?.Invoke(); } } - private static async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength) + private static async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken) { - var array = new byte[BufferSize]; - int bytesRead; - while ((bytesRead = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0) + var array = ArrayPool.Shared.Rent(BufferSize); + try { - if (bytesRead == 0) + int bytesRead; + while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0) { - break; - } + var bytesToCopy = Math.Min(bytesRead, copyLength); - var bytesToCopy = Math.Min(bytesRead, copyLength); + await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy), cancellationToken).ConfigureAwait(false); - await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy)).ConfigureAwait(false); + copyLength -= bytesToCopy; - copyLength -= bytesToCopy; - - if (copyLength <= 0) - { - break; + if (copyLength <= 0) + { + break; + } } } - } - - public string ContentType { get; set; } - - public IRequest RequestContext { get; set; } - - public object Response { get; set; } - - public int Status { get; set; } - - public HttpStatusCode StatusCode - { - get => (HttpStatusCode)Status; - set => Status = (int)value; + finally + { + ArrayPool.Shared.Return(array); + } } } } -- cgit v1.2.3 From 6b959f40ac208094da0a1d41d8c8a42df9a87876 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 17 Apr 2020 20:01:25 +0200 Subject: Fix build --- Emby.Server.Implementations/HttpServer/HttpResultFactory.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index b42662420..0d0396bc7 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -565,13 +565,12 @@ namespace Emby.Server.Implementations.HttpServer } catch (NotSupportedException) { - } } if (!string.IsNullOrWhiteSpace(rangeHeader) && totalContentLength.HasValue) { - var hasHeaders = new RangeRequestWriter(rangeHeader, totalContentLength.Value, stream, contentType, isHeadRequest, _logger) + var hasHeaders = new RangeRequestWriter(rangeHeader, totalContentLength.Value, stream, contentType, isHeadRequest) { OnComplete = options.OnComplete }; @@ -608,8 +607,11 @@ namespace Emby.Server.Implementations.HttpServer /// /// Adds the caching responseHeaders. /// - private void AddCachingHeaders(IDictionary responseHeaders, TimeSpan? cacheDuration, - bool noCache, DateTime? lastModifiedDate) + private void AddCachingHeaders( + IDictionary responseHeaders, + TimeSpan? cacheDuration, + bool noCache, + DateTime? lastModifiedDate) { if (noCache) { -- cgit v1.2.3 From 387a07c6dd4792ea1e77d333e178f9b4e9c56678 Mon Sep 17 00:00:00 2001 From: ConfusedPolarBear <33811686+ConfusedPolarBear@users.noreply.github.com> Date: Sun, 19 Apr 2020 01:33:09 -0500 Subject: Add persistent setting configuration and temporary activation --- .../QuickConnect/ConfigurationExtension.cs | 30 +++++++++ .../QuickConnect/QuickConnectConfiguration.cs | 13 ++++ .../QuickConnect/QuickConnectManager.cs | 72 +++++++++++++++++++--- .../QuickConnect/QuickConnectService.cs | 40 ++++++++++-- .../QuickConnect/IQuickConnect.cs | 8 ++- 5 files changed, 149 insertions(+), 14 deletions(-) create mode 100644 Emby.Server.Implementations/QuickConnect/ConfigurationExtension.cs create mode 100644 Emby.Server.Implementations/QuickConnect/QuickConnectConfiguration.cs diff --git a/Emby.Server.Implementations/QuickConnect/ConfigurationExtension.cs b/Emby.Server.Implementations/QuickConnect/ConfigurationExtension.cs new file mode 100644 index 000000000..0e35ba80a --- /dev/null +++ b/Emby.Server.Implementations/QuickConnect/ConfigurationExtension.cs @@ -0,0 +1,30 @@ +#pragma warning disable CS1591 + +using System.Collections.Generic; +using MediaBrowser.Common.Configuration; + +namespace Emby.Server.Implementations.QuickConnect +{ + public static class ConfigurationExtension + { + public static QuickConnectConfiguration GetQuickConnectConfiguration(this IConfigurationManager manager) + { + return manager.GetConfiguration("quickconnect"); + } + } + + public class QuickConnectConfigurationFactory : IConfigurationFactory + { + public IEnumerable GetConfigurations() + { + return new ConfigurationStore[] + { + new ConfigurationStore + { + Key = "quickconnect", + ConfigurationType = typeof(QuickConnectConfiguration) + } + }; + } + } +} diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectConfiguration.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectConfiguration.cs new file mode 100644 index 000000000..befc46379 --- /dev/null +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectConfiguration.cs @@ -0,0 +1,13 @@ +using MediaBrowser.Model.QuickConnect; + +namespace Emby.Server.Implementations.QuickConnect +{ + public class QuickConnectConfiguration + { + public QuickConnectConfiguration() + { + } + + public QuickConnectState State { get; set; } + } +} diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs index 30418097c..671ddc2b9 100644 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs @@ -3,7 +3,9 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Security.Cryptography; +using MediaBrowser.Common.Configuration; using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.QuickConnect; @@ -12,6 +14,7 @@ using MediaBrowser.Model.Globalization; using MediaBrowser.Model.QuickConnect; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; +using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.QuickConnect @@ -24,6 +27,7 @@ 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; @@ -31,11 +35,13 @@ namespace Emby.Server.Implementations.QuickConnect private IAuthenticationRepository _authenticationRepository; private IAuthorizationContext _authContext; private IServerApplicationHost _appHost; + private ITaskManager _taskManager; /// /// Initializes a new instance of the class. /// Should only be called at server startup when a singleton is created. /// + /// Configuration. /// Logger. /// User manager. /// Localization. @@ -43,15 +49,19 @@ namespace Emby.Server.Implementations.QuickConnect /// Application host. /// Authentication context. /// Authentication repository. + /// Task scheduler. public QuickConnectManager( + IServerConfigurationManager config, ILoggerFactory loggerFactory, IUserManager userManager, ILocalizationManager localization, IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IAuthorizationContext authContext, - IAuthenticationRepository authenticationRepository) + IAuthenticationRepository authenticationRepository, + ITaskManager taskManager) { + _config = config; _logger = loggerFactory.CreateLogger(nameof(QuickConnectManager)); _userManager = userManager; _localizationManager = localization; @@ -59,6 +69,16 @@ namespace Emby.Server.Implementations.QuickConnect _appHost = appHost; _authContext = authContext; _authenticationRepository = authenticationRepository; + _taskManager = taskManager; + + ReloadConfiguration(); + } + + private void ReloadConfiguration() + { + var config = _config.GetQuickConnectConfiguration(); + + State = config.State; } /// @@ -73,6 +93,10 @@ namespace Emby.Server.Implementations.QuickConnect /// public int RequestExpiry { get; set; } = 30; + private bool TemporaryActivation { get; set; } = false; + + private DateTime DateActivated { get; set; } + /// public void AssertActive() { @@ -82,17 +106,37 @@ namespace Emby.Server.Implementations.QuickConnect } } + /// + public QuickConnectResult Activate() + { + // This should not call SetEnabled since that would persist the "temporary" activation to the configuration file + State = QuickConnectState.Active; + DateActivated = DateTime.Now; + TemporaryActivation = true; + + return new QuickConnectResult(); + } + /// public void SetEnabled(QuickConnectState newState) { _logger.LogDebug("Changed quick connect state from {0} to {1}", State, newState); State = newState; + + _config.SaveConfiguration("quickconnect", new QuickConnectConfiguration() + { + State = State + }); + + _logger.LogDebug("Configuration saved"); } /// public QuickConnectResult TryConnect(string friendlyName) { + ExpireRequests(true); + if (State != QuickConnectState.Active) { _logger.LogDebug("Refusing quick connect initiation request, current state is {0}", State); @@ -122,13 +166,11 @@ namespace Emby.Server.Implementations.QuickConnect /// public QuickConnectResult CheckRequestStatus(string secret) { - AssertActive(); ExpireRequests(); + AssertActive(); 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"); @@ -146,8 +188,8 @@ namespace Emby.Server.Implementations.QuickConnect /// public List GetCurrentRequestsInternal() { - AssertActive(); ExpireRequests(); + AssertActive(); return _currentRequests.Values.ToList(); } @@ -174,12 +216,11 @@ namespace Emby.Server.Implementations.QuickConnect /// public bool AuthorizeRequest(IRequest request, string lookup) { + ExpireRequests(); AssertActive(); var auth = _authContext.GetAuthorizationInfo(request); - ExpireRequests(); - if (!_currentRequests.ContainsKey(lookup)) { throw new KeyNotFoundException("Unable to find request"); @@ -208,6 +249,8 @@ namespace Emby.Server.Implementations.QuickConnect UserId = auth.UserId }); + _logger.LogInformation("Allowing device {0} to login as user {1} with quick connect code {2}", result.FriendlyName, auth.User.Name, result.Code); + return true; } @@ -239,8 +282,21 @@ namespace Emby.Server.Implementations.QuickConnect return string.Join(string.Empty, bytes.Select(x => x.ToString("x2", CultureInfo.InvariantCulture))); } - private void ExpireRequests() + private void ExpireRequests(bool onlyCheckTime = false) { + // check if quick connect should be deactivated + if (TemporaryActivation && DateTime.Now > DateActivated.AddMinutes(10) && State == QuickConnectState.Active) + { + _logger.LogDebug("Quick connect time expired, deactivating"); + SetEnabled(QuickConnectState.Available); + } + + if (onlyCheckTime) + { + return; + } + + // expire stale connection requests var delete = new List(); var values = _currentRequests.Values.ToList(); diff --git a/MediaBrowser.Api/QuickConnect/QuickConnectService.cs b/MediaBrowser.Api/QuickConnect/QuickConnectService.cs index 889a78839..60d6ac414 100644 --- a/MediaBrowser.Api/QuickConnect/QuickConnectService.cs +++ b/MediaBrowser.Api/QuickConnect/QuickConnectService.cs @@ -98,6 +98,11 @@ namespace MediaBrowser.Api.QuickConnect public object Get(QuickConnectList request) { + if(_quickConnect.State != QuickConnectState.Active) + { + return Array.Empty(); + } + return _quickConnect.GetCurrentRequests(); } @@ -124,15 +129,40 @@ namespace MediaBrowser.Api.QuickConnect public object Post(Activate request) { - if (_quickConnect.State == QuickConnectState.Available) + string name = _authContext.GetAuthorizationInfo(Request).User.Name; + + if(_quickConnect.State == QuickConnectState.Unavailable) + { + return new QuickConnectResult() + { + Error = "Quick connect is not enabled on this server" + }; + } + + else if(_quickConnect.State == QuickConnectState.Available) { - _quickConnect.SetEnabled(QuickConnectState.Active); + var result = _quickConnect.Activate(); - string name = _authContext.GetAuthorizationInfo(Request).User.Name; - Logger.LogInformation("{name} enabled quick connect", name); + if (string.IsNullOrEmpty(result.Error)) + { + Logger.LogInformation("{name} temporarily activated quick connect", name); + } + + return result; } - return _quickConnect.State; + else if(_quickConnect.State == QuickConnectState.Active) + { + return new QuickConnectResult() + { + Error = "" + }; + } + + return new QuickConnectResult() + { + Error = "Unknown current state" + }; } public object Post(Available request) diff --git a/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs b/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs index e4a790ffe..d44765e11 100644 --- a/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs +++ b/MediaBrowser.Controller/QuickConnect/IQuickConnect.cs @@ -35,10 +35,16 @@ namespace MediaBrowser.Controller.QuickConnect /// void AssertActive(); + /// + /// Temporarily activates quick connect for a short amount of time. + /// + /// A quick connect result object indicating success. + QuickConnectResult Activate(); + /// /// Changes the status of quick connect. /// - /// New state to change to + /// New state to change to. void SetEnabled(QuickConnectState newState); /// -- cgit v1.2.3 From 8a7e4cd639be24eb58385dc7b36b466c3d6aed92 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 10:51:51 -0600 Subject: add redoc --- Jellyfin.Api/Jellyfin.Api.csproj | 3 ++- .../Extensions/ApiApplicationBuilderExtensions.cs | 16 ++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 8f23ef9d0..cbb1d3007 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -10,7 +10,8 @@ - + + diff --git a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs index db06eb455..2ab9b0ba5 100644 --- a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs @@ -14,14 +14,18 @@ namespace Jellyfin.Server.Extensions /// 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"); - }); + const string specEndpoint = "/swagger/v1/swagger.json"; + return applicationBuilder.UseSwagger() + .UseSwaggerUI(c => + { + c.SwaggerEndpoint(specEndpoint, "Jellyfin API V1"); + }) + .UseReDoc(c => + { + c.SpecUrl(specEndpoint); + }); } } } -- cgit v1.2.3 From e72a543570b59df61f48cb9a4049ab3dc9675250 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 11:24:03 -0600 Subject: Add Redoc, move docs to api-docs/ --- Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs index 2ab9b0ba5..766243f20 100644 --- a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs @@ -21,10 +21,12 @@ namespace Jellyfin.Server.Extensions .UseSwaggerUI(c => { c.SwaggerEndpoint(specEndpoint, "Jellyfin API V1"); + c.RoutePrefix = "api-docs/swagger"; }) .UseReDoc(c => { c.SpecUrl(specEndpoint); + c.RoutePrefix = "api-docs/redoc"; }); } } -- cgit v1.2.3 From 5da88fac4d0681126bdee635d59237d8d7fcebeb Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 11:24:32 -0600 Subject: Enable string enum converter --- .../Extensions/ApiServiceCollectionExtensions.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 71ef9a69a..a4f078b5b 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -1,3 +1,8 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json.Serialization; using Jellyfin.Api; using Jellyfin.Api.Auth; using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; @@ -75,6 +80,9 @@ namespace Jellyfin.Server.Extensions { // Setting the naming policy to null leaves the property names as-is when serializing objects to JSON. options.JsonSerializerOptions.PropertyNamingPolicy = null; + + // Accept string enums + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); }) .AddControllersAsServices(); } @@ -89,6 +97,17 @@ namespace Jellyfin.Server.Extensions return serviceCollection.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "Jellyfin API", Version = "v1" }); + + // Add all xml doc files to swagger generator. + var xmlFiles = Directory.GetFiles( + AppContext.BaseDirectory, + "*.xml", + SearchOption.TopDirectoryOnly); + + foreach (var xmlFile in xmlFiles) + { + c.IncludeXmlComments(xmlFile); + } }); } } -- cgit v1.2.3 From 72745f47225a5b1071660acc4dcde618d938eaa0 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 11:28:56 -0600 Subject: fix formatting --- Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs index 766243f20..43c49307d 100644 --- a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs @@ -17,7 +17,8 @@ namespace Jellyfin.Server.Extensions // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), // specifying the Swagger JSON endpoint. const string specEndpoint = "/swagger/v1/swagger.json"; - return applicationBuilder.UseSwagger() + return applicationBuilder + .UseSwagger() .UseSwaggerUI(c => { c.SwaggerEndpoint(specEndpoint, "Jellyfin API V1"); -- cgit v1.2.3 From 86d68e23e7af367152edc36977a9a39431bd2641 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 12:06:18 -0600 Subject: Add DisplayPreferencesController --- Jellyfin.Api/Controllers/DisplayPreferencesController.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Jellyfin.Api/Controllers/DisplayPreferencesController.cs diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs new file mode 100644 index 000000000..537a94046 --- /dev/null +++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers +{ + /// + /// Display Preferences Controller. + /// + public class DisplayPreferencesController : BaseJellyfinApiController + { + } +} -- cgit v1.2.3 From a282fbe9668263481b850b29b3fb8064d4d7ee9f Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 12:26:38 -0600 Subject: Move DisplayPreferences to Jellyfin.Api --- .../Controllers/DisplayPreferencesController.cs | 92 +++++++++++++++++++ MediaBrowser.Api/DisplayPreferencesService.cs | 101 --------------------- 2 files changed, 92 insertions(+), 101 deletions(-) delete mode 100644 MediaBrowser.Api/DisplayPreferencesService.cs diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs index 537a94046..6182c3507 100644 --- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs +++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs @@ -1,4 +1,11 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Threading; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Jellyfin.Api.Controllers { @@ -7,5 +14,90 @@ namespace Jellyfin.Api.Controllers /// public class DisplayPreferencesController : BaseJellyfinApiController { + private readonly IDisplayPreferencesRepository _displayPreferencesRepository; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of interface. + public DisplayPreferencesController(IDisplayPreferencesRepository displayPreferencesRepository) + { + _displayPreferencesRepository = displayPreferencesRepository; + } + + /// + /// Get Display Preferences + /// + /// Display preferences id. + /// User id. + /// Client. + /// Display Preferences. + [HttpGet("{DisplayPreferencesId")] + [ProducesResponseType(typeof(DisplayPreferences), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + public IActionResult GetDisplayPreferences( + [FromRoute] string displayPreferencesId, + [FromQuery] [Required] string userId, + [FromQuery] [Required] string client + ) + { + try + { + var result = _displayPreferencesRepository.GetDisplayPreferences(displayPreferencesId, userId, client); + if (result == null) + { + return NotFound(); + } + + // TODO ToOptimizedResult + return Ok(result); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Update Display Preferences + /// + /// Display preferences id. + /// User Id. + /// Client. + /// New Display Preferences object. + /// Status. + [HttpPost("{DisplayPreferencesId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ModelStateDictionary), StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult UpdateDisplayPreferences( + [FromRoute] string displayPreferencesId, + [FromQuery, BindRequired] string userId, + [FromQuery, BindRequired] string client, + [FromBody, BindRequired] DisplayPreferences displayPreferences) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + displayPreferences.Id = displayPreferencesId; + _displayPreferencesRepository.SaveDisplayPreferences( + displayPreferences, + userId, + client, + CancellationToken.None); + + return Ok(); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } } } diff --git a/MediaBrowser.Api/DisplayPreferencesService.cs b/MediaBrowser.Api/DisplayPreferencesService.cs deleted file mode 100644 index 62c4ff43f..000000000 --- a/MediaBrowser.Api/DisplayPreferencesService.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System.Threading; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - /// - /// Class UpdateDisplayPreferences - /// - [Route("/DisplayPreferences/{DisplayPreferencesId}", "POST", Summary = "Updates a user's display preferences for an item")] - public class UpdateDisplayPreferences : DisplayPreferences, IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "DisplayPreferencesId", Description = "DisplayPreferences Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string DisplayPreferencesId { get; set; } - - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string UserId { get; set; } - } - - [Route("/DisplayPreferences/{Id}", "GET", Summary = "Gets a user's display preferences for an item")] - public class GetDisplayPreferences : IReturn - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } - - [ApiMember(Name = "Client", Description = "Client", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Client { get; set; } - } - - /// - /// Class DisplayPreferencesService - /// - [Authenticated] - public class DisplayPreferencesService : BaseApiService - { - /// - /// The _display preferences manager - /// - private readonly IDisplayPreferencesRepository _displayPreferencesManager; - /// - /// The _json serializer - /// - private readonly IJsonSerializer _jsonSerializer; - - /// - /// Initializes a new instance of the class. - /// - /// The json serializer. - /// The display preferences manager. - public DisplayPreferencesService( - ILogger logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IJsonSerializer jsonSerializer, - IDisplayPreferencesRepository displayPreferencesManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _jsonSerializer = jsonSerializer; - _displayPreferencesManager = displayPreferencesManager; - } - - /// - /// Gets the specified request. - /// - /// The request. - public object Get(GetDisplayPreferences request) - { - var result = _displayPreferencesManager.GetDisplayPreferences(request.Id, request.UserId, request.Client); - - return ToOptimizedResult(result); - } - - /// - /// Posts the specified request. - /// - /// The request. - public void Post(UpdateDisplayPreferences request) - { - // Serialize to json and then back so that the core doesn't see the request dto type - var displayPreferences = _jsonSerializer.DeserializeFromString(_jsonSerializer.SerializeToString(request)); - - _displayPreferencesManager.SaveDisplayPreferences(displayPreferences, request.UserId, request.Client, CancellationToken.None); - } - } -} -- cgit v1.2.3 From c31b9f5169ae62787fa356ccecc2f1fc6896d04b Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 12:30:10 -0600 Subject: Fix build & runtime errors --- Jellyfin.Api/Controllers/DisplayPreferencesController.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs index 6182c3507..a3bcafaea 100644 --- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs +++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs @@ -26,21 +26,20 @@ namespace Jellyfin.Api.Controllers } /// - /// Get Display Preferences + /// Get Display Preferences. /// /// Display preferences id. /// User id. /// Client. /// Display Preferences. - [HttpGet("{DisplayPreferencesId")] + [HttpGet("{DisplayPreferencesId}")] [ProducesResponseType(typeof(DisplayPreferences), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public IActionResult GetDisplayPreferences( [FromRoute] string displayPreferencesId, [FromQuery] [Required] string userId, - [FromQuery] [Required] string client - ) + [FromQuery] [Required] string client) { try { @@ -60,7 +59,7 @@ namespace Jellyfin.Api.Controllers } /// - /// Update Display Preferences + /// Update Display Preferences. /// /// Display preferences id. /// User Id. -- cgit v1.2.3 From 60607ab60c3051815179859adfd2a7182f9ceb9a Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 12:34:34 -0600 Subject: Fix saving DisplayPreferences --- Jellyfin.Api/Controllers/DisplayPreferencesController.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs index a3bcafaea..2c4072b39 100644 --- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs +++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs @@ -84,7 +84,11 @@ namespace Jellyfin.Api.Controllers return BadRequest(ModelState); } - displayPreferences.Id = displayPreferencesId; + if (displayPreferencesId == null) + { + // do nothing. + } + _displayPreferencesRepository.SaveDisplayPreferences( displayPreferences, userId, -- cgit v1.2.3 From e6b873f2aeadd01ed4638148be857ddf45a33576 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 12:56:16 -0600 Subject: Fix missing attributes --- Jellyfin.Api/Controllers/DisplayPreferencesController.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs index 2c4072b39..0fbdcb6b8 100644 --- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs +++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs @@ -1,6 +1,9 @@ +#nullable enable + using System; using System.ComponentModel.DataAnnotations; using System.Threading; +using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; using Microsoft.AspNetCore.Http; @@ -12,6 +15,7 @@ namespace Jellyfin.Api.Controllers /// /// Display Preferences Controller. /// + [Authenticated] public class DisplayPreferencesController : BaseJellyfinApiController { private readonly IDisplayPreferencesRepository _displayPreferencesRepository; -- cgit v1.2.3 From 7c8188194b5bf9b74413f25d471a212f1677f7ed Mon Sep 17 00:00:00 2001 From: ZadenRB Date: Sun, 19 Apr 2020 13:19:15 -0600 Subject: Address PR comments, and revert changes that changed the API schema --- Jellyfin.Api/Controllers/NotificationsController.cs | 20 ++++++++++---------- .../Models/NotificationDtos/NotificationDto.cs | 6 +++--- .../NotificationDtos/NotificationResultDto.cs | 21 +++++++++++++++++++++ 3 files changed, 34 insertions(+), 13 deletions(-) create mode 100644 Jellyfin.Api/Models/NotificationDtos/NotificationResultDto.cs diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs index c8a5be89b..d9a5c5e31 100644 --- a/Jellyfin.Api/Controllers/NotificationsController.cs +++ b/Jellyfin.Api/Controllers/NotificationsController.cs @@ -42,13 +42,13 @@ namespace Jellyfin.Api.Controllers /// An optional limit on the number of notifications returned. /// A read-only list of all of the user's notifications. [HttpGet("{UserID}")] - public IReadOnlyList GetNotifications( + public NotificationResultDto GetNotifications( [FromRoute] string userId, [FromQuery] bool? isRead, [FromQuery] int? startIndex, [FromQuery] int? limit) { - return new List(); + return new NotificationResultDto(); } /// @@ -92,10 +92,10 @@ namespace Jellyfin.Api.Controllers /// The level of the notification. [HttpPost("Admin")] public void CreateAdminNotification( - [FromForm] string name, - [FromForm] string description, - [FromForm] string? url, - [FromForm] NotificationLevel? level) + [FromQuery] string name, + [FromQuery] string description, + [FromQuery] string? url, + [FromQuery] NotificationLevel? level) { var notification = new NotificationRequest { @@ -114,11 +114,11 @@ namespace Jellyfin.Api.Controllers /// Endpoint to set notifications as read. /// /// The userID. - /// The IDs of notifications which should be set as read. + /// A comma-separated list of the IDs of notifications which should be set as read. [HttpPost("{UserID}/Read")] public void SetRead( [FromRoute] string userId, - [FromForm] List ids) + [FromQuery] string ids) { } @@ -126,11 +126,11 @@ namespace Jellyfin.Api.Controllers /// Endpoint to set notifications as unread. /// /// The userID. - /// The IDs of notifications which should be set as unread. + /// A comma-separated list of the IDs of notifications which should be set as unread. [HttpPost("{UserID}/Unread")] public void SetUnread( [FromRoute] string userId, - [FromForm] List ids) + [FromQuery] string ids) { } } diff --git a/Jellyfin.Api/Models/NotificationDtos/NotificationDto.cs b/Jellyfin.Api/Models/NotificationDtos/NotificationDto.cs index c849ecd75..502b22623 100644 --- a/Jellyfin.Api/Models/NotificationDtos/NotificationDto.cs +++ b/Jellyfin.Api/Models/NotificationDtos/NotificationDto.cs @@ -41,13 +41,13 @@ namespace Jellyfin.Api.Models.NotificationDtos public string Description { get; set; } = string.Empty; /// - /// Gets or sets the notification's URL. Defaults to null. + /// Gets or sets the notification's URL. Defaults to an empty string. /// - public string? Url { get; set; } + public string Url { get; set; } = string.Empty; /// /// Gets or sets the notification level. /// - public NotificationLevel? Level { get; set; } + public NotificationLevel Level { get; set; } } } diff --git a/Jellyfin.Api/Models/NotificationDtos/NotificationResultDto.cs b/Jellyfin.Api/Models/NotificationDtos/NotificationResultDto.cs new file mode 100644 index 000000000..64e92bd83 --- /dev/null +++ b/Jellyfin.Api/Models/NotificationDtos/NotificationResultDto.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace Jellyfin.Api.Models.NotificationDtos +{ + /// + /// A list of notifications with the total record count for pagination. + /// + public class NotificationResultDto + { + /// + /// Gets or sets the current page of notifications. + /// + public IReadOnlyList Notifications { get; set; } = Array.Empty(); + + /// + /// Gets or sets the total number of notifications. + /// + public int TotalRecordCount { get; set; } + } +} -- cgit v1.2.3 From 5d9c40ec72d31957cec48e141ca5ce4f9141b413 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 16:26:20 -0600 Subject: move scheduled tasks to Jellyfin.Api --- .../Controllers/ScheduledTasksController.cs | 207 +++++++++++++++++++++ .../Converters/LongToStringConverter.cs | 56 ++++++ .../Extensions/ApiServiceCollectionExtensions.cs | 2 + 3 files changed, 265 insertions(+) create mode 100644 Jellyfin.Api/Controllers/ScheduledTasksController.cs create mode 100644 Jellyfin.Server/Converters/LongToStringConverter.cs diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs new file mode 100644 index 000000000..bb07af397 --- /dev/null +++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs @@ -0,0 +1,207 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Model.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Jellyfin.Api.Controllers +{ + /// + /// Scheduled Tasks Controller. + /// + public class ScheduledTasksController : BaseJellyfinApiController + { + private readonly ITaskManager _taskManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + public ScheduledTasksController(ITaskManager taskManager) + { + _taskManager = taskManager; + } + + /// + /// Get tasks. + /// + /// Optional filter tasks that are hidden, or not. + /// Optional filter tasks that are enabled, or not. + /// Task list. + [HttpGet] + [ProducesResponseType(typeof(TaskInfo[]), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult GetTasks( + [FromQuery] bool? isHidden = false, + [FromQuery] bool? isEnabled = false) + { + try + { + IEnumerable tasks = _taskManager.ScheduledTasks.OrderBy(o => o.Name); + + if (isHidden.HasValue) + { + var hiddenValue = isHidden.Value; + tasks = tasks.Where(o => + { + var itemIsHidden = false; + if (o.ScheduledTask is IConfigurableScheduledTask configurableScheduledTask) + { + itemIsHidden = configurableScheduledTask.IsHidden; + } + + return itemIsHidden == hiddenValue; + }); + } + + if (isEnabled.HasValue) + { + var enabledValue = isEnabled.Value; + tasks = tasks.Where(o => + { + var itemIsEnabled = false; + if (o.ScheduledTask is IConfigurableScheduledTask configurableScheduledTask) + { + itemIsEnabled = configurableScheduledTask.IsEnabled; + } + + return itemIsEnabled == enabledValue; + }); + } + + var taskInfos = tasks.Select(ScheduledTaskHelpers.GetTaskInfo); + + // TODO ToOptimizedResult + return Ok(taskInfos); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Get task by id. + /// + /// Task Id. + /// Task Info. + [HttpGet("{TaskID}")] + [ProducesResponseType(typeof(TaskInfo), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult GetTask([FromRoute] string taskId) + { + try + { + var task = _taskManager.ScheduledTasks.FirstOrDefault(i => + string.Equals(i.Id, taskId, StringComparison.OrdinalIgnoreCase)); + + if (task == null) + { + return NotFound(); + } + + var result = ScheduledTaskHelpers.GetTaskInfo(task); + return Ok(result); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Start specified task. + /// + /// Task Id. + /// Status. + [HttpPost("Running/{TaskID}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult StartTask([FromRoute] string taskId) + { + try + { + var task = _taskManager.ScheduledTasks.FirstOrDefault(o => + o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); + + if (task == null) + { + return NotFound(); + } + + _taskManager.Execute(task, new TaskOptions()); + return Ok(); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Stop specified task. + /// + /// Task Id. + /// Status. + [HttpDelete("Running/{TaskID}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult StopTask([FromRoute] string taskId) + { + try + { + var task = _taskManager.ScheduledTasks.FirstOrDefault(o => + o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); + + if (task == null) + { + return NotFound(); + } + + _taskManager.Cancel(task); + return Ok(); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Update specified task triggers. + /// + /// Task Id. + /// Triggers. + /// Status. + [HttpPost("{TaskID}/Triggers")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult UpdateTask([FromRoute] string taskId, [FromBody] TaskTriggerInfo[] triggerInfos) + { + try + { + var task = _taskManager.ScheduledTasks.FirstOrDefault(o => + o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); + if (task == null) + { + return NotFound(); + } + + task.Triggers = triggerInfos; + return Ok(); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + } +} diff --git a/Jellyfin.Server/Converters/LongToStringConverter.cs b/Jellyfin.Server/Converters/LongToStringConverter.cs new file mode 100644 index 000000000..ad66b7b0c --- /dev/null +++ b/Jellyfin.Server/Converters/LongToStringConverter.cs @@ -0,0 +1,56 @@ +using System; +using System.Buffers; +using System.Buffers.Text; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Jellyfin.Server.Converters +{ + /// + /// Long to String JSON converter. + /// Javascript does not support 64-bit integers. + /// + public class LongToStringConverter : JsonConverter + { + /// + /// Read JSON string as Long. + /// + /// . + /// Type. + /// Options. + /// Parsed value. + public override long Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + // try to parse number directly from bytes + ReadOnlySpan span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; + if (Utf8Parser.TryParse(span, out long number, out int bytesConsumed) && span.Length == bytesConsumed) + { + return number; + } + + // try to parse from a string if the above failed, this covers cases with other escaped/UTF characters + if (long.TryParse(reader.GetString(), out number)) + { + return number; + } + } + + // fallback to default handling + return reader.GetInt64(); + } + + /// + /// Write long to JSON string. + /// + /// . + /// Value to write. + /// Options. + public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString(NumberFormatInfo.InvariantInfo)); + } + } +} diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 71ef9a69a..afd42ac5a 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -4,6 +4,7 @@ using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; using Jellyfin.Api.Auth.RequiresElevationPolicy; using Jellyfin.Api.Constants; using Jellyfin.Api.Controllers; +using Jellyfin.Server.Converters; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; @@ -75,6 +76,7 @@ namespace Jellyfin.Server.Extensions { // Setting the naming policy to null leaves the property names as-is when serializing objects to JSON. options.JsonSerializerOptions.PropertyNamingPolicy = null; + options.JsonSerializerOptions.Converters.Add(new LongToStringConverter()); }) .AddControllersAsServices(); } -- cgit v1.2.3 From d8fc4f91dbcc38df0e13e51a3631e87f783361de Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 16:29:29 -0600 Subject: burn ToOptimizedResult --- Jellyfin.Api/Controllers/ScheduledTasksController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs index bb07af397..f90b44967 100644 --- a/Jellyfin.Api/Controllers/ScheduledTasksController.cs +++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs @@ -75,7 +75,6 @@ namespace Jellyfin.Api.Controllers var taskInfos = tasks.Select(ScheduledTaskHelpers.GetTaskInfo); - // TODO ToOptimizedResult return Ok(taskInfos); } catch (Exception e) -- cgit v1.2.3 From 4a960892c20676ce6400f4cae1c85e8ce4d4a841 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 16:31:09 -0600 Subject: Add Authorize and BindRequired --- Jellyfin.Api/Controllers/ScheduledTasksController.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs index f90b44967..157e98519 100644 --- a/Jellyfin.Api/Controllers/ScheduledTasksController.cs +++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -13,6 +14,7 @@ namespace Jellyfin.Api.Controllers /// /// Scheduled Tasks Controller. /// + [Authenticated] public class ScheduledTasksController : BaseJellyfinApiController { private readonly ITaskManager _taskManager; @@ -183,7 +185,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult UpdateTask([FromRoute] string taskId, [FromBody] TaskTriggerInfo[] triggerInfos) + public IActionResult UpdateTask([FromRoute] string taskId, [FromBody, BindRequired] TaskTriggerInfo[] triggerInfos) { try { -- cgit v1.2.3 From a96db5f48e57a192369b220422517171c06411b6 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 16:32:03 -0600 Subject: Remove old scheduled tasks service --- .../ScheduledTasks/ScheduledTaskService.cs | 234 --------------------- 1 file changed, 234 deletions(-) delete mode 100644 MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs deleted file mode 100644 index e08a8482e..000000000 --- a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs +++ /dev/null @@ -1,234 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.Tasks; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.ScheduledTasks -{ - /// - /// Class GetScheduledTask - /// - [Route("/ScheduledTasks/{Id}", "GET", Summary = "Gets a scheduled task, by Id")] - public class GetScheduledTask : IReturn - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - /// - /// Class GetScheduledTasks - /// - [Route("/ScheduledTasks", "GET", Summary = "Gets scheduled tasks")] - public class GetScheduledTasks : IReturn - { - [ApiMember(Name = "IsHidden", Description = "Optional filter tasks that are hidden, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsHidden { get; set; } - - [ApiMember(Name = "IsEnabled", Description = "Optional filter tasks that are enabled, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsEnabled { get; set; } - } - - /// - /// Class StartScheduledTask - /// - [Route("/ScheduledTasks/Running/{Id}", "POST", Summary = "Starts a scheduled task")] - public class StartScheduledTask : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - } - - /// - /// Class StopScheduledTask - /// - [Route("/ScheduledTasks/Running/{Id}", "DELETE", Summary = "Stops a scheduled task")] - public class StopScheduledTask : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - } - - /// - /// Class UpdateScheduledTaskTriggers - /// - [Route("/ScheduledTasks/{Id}/Triggers", "POST", Summary = "Updates the triggers for a scheduled task")] - public class UpdateScheduledTaskTriggers : List, IReturnVoid - { - /// - /// Gets or sets the task id. - /// - /// The task id. - [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - } - - /// - /// Class ScheduledTasksService - /// - [Authenticated(Roles = "Admin")] - public class ScheduledTaskService : BaseApiService - { - /// - /// The task manager. - /// - private readonly ITaskManager _taskManager; - - /// - /// Initializes a new instance of the class. - /// - /// The task manager. - /// taskManager - public ScheduledTaskService( - ILogger logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - ITaskManager taskManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _taskManager = taskManager; - } - - /// - /// Gets the specified request. - /// - /// The request. - /// IEnumerable{TaskInfo}. - public object Get(GetScheduledTasks request) - { - IEnumerable result = _taskManager.ScheduledTasks - .OrderBy(i => i.Name); - - if (request.IsHidden.HasValue) - { - var val = request.IsHidden.Value; - - result = result.Where(i => - { - var isHidden = false; - - if (i.ScheduledTask is IConfigurableScheduledTask configurableTask) - { - isHidden = configurableTask.IsHidden; - } - - return isHidden == val; - }); - } - - if (request.IsEnabled.HasValue) - { - var val = request.IsEnabled.Value; - - result = result.Where(i => - { - var isEnabled = true; - - if (i.ScheduledTask is IConfigurableScheduledTask configurableTask) - { - isEnabled = configurableTask.IsEnabled; - } - - return isEnabled == val; - }); - } - - var infos = result - .Select(ScheduledTaskHelpers.GetTaskInfo) - .ToArray(); - - return ToOptimizedResult(infos); - } - - /// - /// Gets the specified request. - /// - /// The request. - /// IEnumerable{TaskInfo}. - /// Task not found - public object Get(GetScheduledTask request) - { - var task = _taskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, request.Id)); - - if (task == null) - { - throw new ResourceNotFoundException("Task not found"); - } - - var result = ScheduledTaskHelpers.GetTaskInfo(task); - - return ToOptimizedResult(result); - } - - /// - /// Posts the specified request. - /// - /// The request. - /// Task not found - public void Post(StartScheduledTask request) - { - var task = _taskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, request.Id)); - - if (task == null) - { - throw new ResourceNotFoundException("Task not found"); - } - - _taskManager.Execute(task, new TaskOptions()); - } - - /// - /// Posts the specified request. - /// - /// The request. - /// Task not found - public void Delete(StopScheduledTask request) - { - var task = _taskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, request.Id)); - - if (task == null) - { - throw new ResourceNotFoundException("Task not found"); - } - - _taskManager.Cancel(task); - } - - /// - /// Posts the specified request. - /// - /// The request. - /// Task not found - public void Post(UpdateScheduledTaskTriggers request) - { - // We need to parse this manually because we told service stack not to with IRequiresRequestStream - // https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs - var id = GetPathValue(1).ToString(); - - var task = _taskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.Ordinal)); - - if (task == null) - { - throw new ResourceNotFoundException("Task not found"); - } - - task.Triggers = request.ToArray(); - } - } -} -- cgit v1.2.3 From c5d709f77ed2158bf68b8cc81238067d4525518f Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 16:35:31 -0600 Subject: remove todo --- Jellyfin.Api/Controllers/DisplayPreferencesController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs index 0fbdcb6b8..0554091b4 100644 --- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs +++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs @@ -53,7 +53,6 @@ namespace Jellyfin.Api.Controllers return NotFound(); } - // TODO ToOptimizedResult return Ok(result); } catch (Exception e) -- cgit v1.2.3 From a41d5fcea4ee082bb49ddac34a1606204e12e8e8 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 17:36:05 -0600 Subject: Move AttachmentsService to AttachmentsController --- Jellyfin.Api/Controllers/AttachmentsController.cs | 86 +++++++++++++++++++++++ MediaBrowser.Api/Attachments/AttachmentService.cs | 63 ----------------- MediaBrowser.Api/MediaBrowser.Api.csproj | 4 ++ 3 files changed, 90 insertions(+), 63 deletions(-) create mode 100644 Jellyfin.Api/Controllers/AttachmentsController.cs delete mode 100644 MediaBrowser.Api/Attachments/AttachmentService.cs diff --git a/Jellyfin.Api/Controllers/AttachmentsController.cs b/Jellyfin.Api/Controllers/AttachmentsController.cs new file mode 100644 index 000000000..5d48a79b9 --- /dev/null +++ b/Jellyfin.Api/Controllers/AttachmentsController.cs @@ -0,0 +1,86 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Net; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers +{ + /// + /// Attachments controller. + /// + [Route("Videos")] + [Authenticated] + public class AttachmentsController : Controller + { + private readonly ILibraryManager _libraryManager; + private readonly IAttachmentExtractor _attachmentExtractor; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + public AttachmentsController( + ILibraryManager libraryManager, + IAttachmentExtractor attachmentExtractor) + { + _libraryManager = libraryManager; + _attachmentExtractor = attachmentExtractor; + } + + /// + /// Get video attachment. + /// + /// Video ID. + /// Media Source ID. + /// Attachment Index. + /// Attachment. + [HttpGet("{VideoID}/{MediaSourceID}/Attachments/{Index}")] + [Produces("application/octet-stream")] + [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public async Task GetAttachment( + [FromRoute] Guid videoId, + [FromRoute] string mediaSourceId, + [FromRoute] int index) + { + try + { + var item = _libraryManager.GetItemById(videoId); + if (item == null) + { + return NotFound(); + } + + var (attachment, stream) = await _attachmentExtractor.GetAttachment( + item, + mediaSourceId, + index, + CancellationToken.None) + .ConfigureAwait(false); + + var contentType = "application/octet-stream"; + if (string.IsNullOrWhiteSpace(attachment.MimeType)) + { + contentType = attachment.MimeType; + } + + return new FileStreamResult(stream, contentType); + } + catch (ResourceNotFoundException e) + { + return StatusCode(StatusCodes.Status404NotFound, e.Message); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + } +} diff --git a/MediaBrowser.Api/Attachments/AttachmentService.cs b/MediaBrowser.Api/Attachments/AttachmentService.cs deleted file mode 100644 index 1632ca1b0..000000000 --- a/MediaBrowser.Api/Attachments/AttachmentService.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Attachments -{ - [Route("/Videos/{Id}/{MediaSourceId}/Attachments/{Index}", "GET", Summary = "Gets specified attachment.")] - public class GetAttachment - { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid Id { get; set; } - - [ApiMember(Name = "MediaSourceId", Description = "MediaSourceId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string MediaSourceId { get; set; } - - [ApiMember(Name = "Index", Description = "The attachment stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")] - public int Index { get; set; } - } - - public class AttachmentService : BaseApiService - { - private readonly ILibraryManager _libraryManager; - private readonly IAttachmentExtractor _attachmentExtractor; - - public AttachmentService( - ILogger logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - ILibraryManager libraryManager, - IAttachmentExtractor attachmentExtractor) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _libraryManager = libraryManager; - _attachmentExtractor = attachmentExtractor; - } - - public async Task Get(GetAttachment request) - { - var (attachment, attachmentStream) = await GetAttachment(request).ConfigureAwait(false); - var mime = string.IsNullOrWhiteSpace(attachment.MimeType) ? "application/octet-stream" : attachment.MimeType; - - return ResultFactory.GetResult(Request, attachmentStream, mime); - } - - private Task<(MediaAttachment, Stream)> GetAttachment(GetAttachment request) - { - var item = _libraryManager.GetItemById(request.Id); - - return _attachmentExtractor.GetAttachment(item, - request.MediaSourceId, - request.Index, - CancellationToken.None); - } - } -} diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 0d62cf8c5..5ca74d423 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -9,6 +9,10 @@ + + + + netstandard2.1 false -- cgit v1.2.3 From 1fc682541050e074227736f0c8556d53f98228a1 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 17:37:15 -0600 Subject: nullable --- Jellyfin.Api/Controllers/AttachmentsController.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Jellyfin.Api/Controllers/AttachmentsController.cs b/Jellyfin.Api/Controllers/AttachmentsController.cs index 5d48a79b9..f4c1a761f 100644 --- a/Jellyfin.Api/Controllers/AttachmentsController.cs +++ b/Jellyfin.Api/Controllers/AttachmentsController.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; using System.Threading; using System.Threading.Tasks; -- cgit v1.2.3 From ad67081840ec61085673634795d0b6363f649dbf Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 18:04:36 -0600 Subject: add camelCase formatter --- .../Formatters/CamelCaseJsonProfileFormatter.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs diff --git a/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs b/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs new file mode 100644 index 000000000..433a3197d --- /dev/null +++ b/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs @@ -0,0 +1,21 @@ +using System.Text.Json; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.Net.Http.Headers; + +namespace Jellyfin.Server.Formatters +{ + /// + /// Camel Case Json Profile Formatter. + /// + public class CamelCaseJsonProfileFormatter : SystemTextJsonOutputFormatter + { + /// + /// Initializes a new instance of the class. + /// + public CamelCaseJsonProfileFormatter() : base(new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }) + { + SupportedMediaTypes.Clear(); + SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json;profile=\"CamelCase\"")); + } + } +} -- cgit v1.2.3 From c89dc8921ffb0ce11031e9cfb096b525d94e21b3 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 18:10:59 -0600 Subject: Fix PascalCase --- .../Extensions/ApiServiceCollectionExtensions.cs | 3 +++ .../Formatters/PascalCaseJsonProfileFormatter.cs | 23 ++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 71ef9a69a..00688074f 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -4,6 +4,7 @@ using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; using Jellyfin.Api.Auth.RequiresElevationPolicy; using Jellyfin.Api.Constants; using Jellyfin.Api.Controllers; +using Jellyfin.Server.Formatters; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; @@ -66,6 +67,8 @@ namespace Jellyfin.Server.Extensions return serviceCollection.AddMvc(opts => { opts.UseGeneralRoutePrefix(baseUrl); + opts.OutputFormatters.Insert(0, new CamelCaseJsonProfileFormatter()); + opts.OutputFormatters.Insert(0, new PascalCaseJsonProfileFormatter()); }) // Clear app parts to avoid other assemblies being picked up diff --git a/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs b/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs new file mode 100644 index 000000000..2ed006a33 --- /dev/null +++ b/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs @@ -0,0 +1,23 @@ +using System.Text.Json; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.Net.Http.Headers; + +namespace Jellyfin.Server.Formatters +{ + /// + /// Pascal Case Json Profile Formatter. + /// + public class PascalCaseJsonProfileFormatter : SystemTextJsonOutputFormatter + { + /// + /// Initializes a new instance of the class. + /// + public PascalCaseJsonProfileFormatter() : base(new JsonSerializerOptions { PropertyNamingPolicy = null }) + { + SupportedMediaTypes.Clear(); + // Add application/json for default formatter + SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json")); + SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json;profile=\"PascalCase\"")); + } + } +} -- cgit v1.2.3 From 21b54b4ad8477d654e4f79e9805701c9737346a6 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 19:33:55 -0600 Subject: Move DeviceService to DevicesController --- Jellyfin.Api/Controllers/DevicesController.cs | 260 ++++++++++++++++++++++++++ MediaBrowser.Api/Devices/DeviceService.cs | 168 ----------------- 2 files changed, 260 insertions(+), 168 deletions(-) create mode 100644 Jellyfin.Api/Controllers/DevicesController.cs delete mode 100644 MediaBrowser.Api/Devices/DeviceService.cs diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs new file mode 100644 index 000000000..7407c4487 --- /dev/null +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -0,0 +1,260 @@ +#nullable enable + +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Security; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Devices; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Jellyfin.Api.Controllers +{ + /// + /// Devices Controller. + /// + [Authenticated] + public class DevicesController : BaseJellyfinApiController + { + private readonly IDeviceManager _deviceManager; + private readonly IAuthenticationRepository _authenticationRepository; + private readonly ISessionManager _sessionManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of interface. + /// Instance of interface. + /// Instance of interface. + public DevicesController( + IDeviceManager deviceManager, + IAuthenticationRepository authenticationRepository, + ISessionManager sessionManager) + { + _deviceManager = deviceManager; + _authenticationRepository = authenticationRepository; + _sessionManager = sessionManager; + } + + /// + /// Get Devices. + /// + /// /// Gets or sets a value indicating whether [supports synchronize]. + /// /// Gets or sets the user identifier. + /// Device Infos. + [HttpGet] + [ProducesResponseType(typeof(DeviceInfo[]), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId) + { + try + { + var deviceQuery = new DeviceQuery { SupportsSync = supportsSync, UserId = userId ?? Guid.Empty }; + var devices = _deviceManager.GetDevices(deviceQuery); + return Ok(devices); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Get info for a device. + /// + /// Device Id. + /// Device Info. + [HttpGet("Info")] + [ProducesResponseType(typeof(DeviceInfo), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult GetDeviceInfo([FromQuery, BindRequired] string id) + { + try + { + var deviceInfo = _deviceManager.GetDevice(id); + if (deviceInfo == null) + { + return NotFound(); + } + + return Ok(deviceInfo); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Get options for a device. + /// + /// Device Id. + /// Device Info. + [HttpGet("Options")] + [ProducesResponseType(typeof(DeviceOptions), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult GetDeviceOptions([FromQuery, BindRequired] string id) + { + try + { + var deviceInfo = _deviceManager.GetDeviceOptions(id); + if (deviceInfo == null) + { + return NotFound(); + } + + return Ok(deviceInfo); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Update device options. + /// + /// Device Id. + /// Device Options. + /// Status. + [HttpPost("Options")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult UpdateDeviceOptions( + [FromQuery, BindRequired] string id, + [FromBody, BindRequired] DeviceOptions deviceOptions) + { + try + { + var existingDeviceOptions = _deviceManager.GetDeviceOptions(id); + if (existingDeviceOptions == null) + { + return NotFound(); + } + + _deviceManager.UpdateDeviceOptions(id, deviceOptions); + return Ok(); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Deletes a device. + /// + /// Device Id. + /// Status. + [HttpDelete] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult DeleteDevice([FromQuery, BindRequired] string id) + { + try + { + var sessions = _authenticationRepository.Get(new AuthenticationInfoQuery { DeviceId = id }).Items; + + foreach (var session in sessions) + { + _sessionManager.Logout(session); + } + + return Ok(); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Gets camera upload history for a device. + /// + /// Device Id. + /// Content Upload History. + [HttpGet("CameraUploads")] + [ProducesResponseType(typeof(ContentUploadHistory), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult GetCameraUploads([FromQuery, BindRequired] string id) + { + try + { + var uploadHistory = _deviceManager.GetCameraUploadHistory(id); + return Ok(uploadHistory); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Uploads content. + /// + /// Device Id. + /// Album. + /// Name. + /// Id. + /// Status. + [HttpPost("CameraUploads")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public async Task PostCameraUploadAsync( + [FromQuery, BindRequired] string deviceId, + [FromQuery, BindRequired] string album, + [FromQuery, BindRequired] string name, + [FromQuery, BindRequired] string id) + { + try + { + Stream fileStream; + string contentType; + + if (Request.HasFormContentType) + { + if (Request.Form.Files.Any()) + { + fileStream = Request.Form.Files[0].OpenReadStream(); + contentType = Request.Form.Files[0].ContentType; + } + else + { + return BadRequest(); + } + } + else + { + fileStream = Request.Body; + contentType = Request.ContentType; + } + + await _deviceManager.AcceptCameraUpload( + deviceId, + fileStream, + new LocalFileInfo + { + MimeType = contentType, + Album = album, + Name = name, + Id = id + }).ConfigureAwait(false); + + return Ok(); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + } +} diff --git a/MediaBrowser.Api/Devices/DeviceService.cs b/MediaBrowser.Api/Devices/DeviceService.cs deleted file mode 100644 index 7004a2559..000000000 --- a/MediaBrowser.Api/Devices/DeviceService.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System.IO; -using System.Threading.Tasks; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Security; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Devices; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Devices -{ - [Route("/Devices", "GET", Summary = "Gets all devices")] - [Authenticated(Roles = "Admin")] - public class GetDevices : DeviceQuery, IReturn> - { - } - - [Route("/Devices/Info", "GET", Summary = "Gets info for a device")] - [Authenticated(Roles = "Admin")] - public class GetDeviceInfo : IReturn - { - [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Devices/Options", "GET", Summary = "Gets options for a device")] - [Authenticated(Roles = "Admin")] - public class GetDeviceOptions : IReturn - { - [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Devices", "DELETE", Summary = "Deletes a device")] - public class DeleteDevice - { - [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string Id { get; set; } - } - - [Route("/Devices/CameraUploads", "GET", Summary = "Gets camera upload history for a device")] - [Authenticated] - public class GetCameraUploads : IReturn - { - [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string DeviceId { get; set; } - } - - [Route("/Devices/CameraUploads", "POST", Summary = "Uploads content")] - [Authenticated] - public class PostCameraUpload : IRequiresRequestStream, IReturnVoid - { - [ApiMember(Name = "DeviceId", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string DeviceId { get; set; } - - [ApiMember(Name = "Album", Description = "Album", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Album { get; set; } - - [ApiMember(Name = "Name", Description = "Name", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Name { get; set; } - - [ApiMember(Name = "Id", Description = "Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Id { get; set; } - - public Stream RequestStream { get; set; } - } - - [Route("/Devices/Options", "POST", Summary = "Updates device options")] - [Authenticated(Roles = "Admin")] - public class PostDeviceOptions : DeviceOptions, IReturnVoid - { - [ApiMember(Name = "Id", Description = "Device Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")] - public string Id { get; set; } - } - - public class DeviceService : BaseApiService - { - private readonly IDeviceManager _deviceManager; - private readonly IAuthenticationRepository _authRepo; - private readonly ISessionManager _sessionManager; - - public DeviceService( - ILogger logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IDeviceManager deviceManager, - IAuthenticationRepository authRepo, - ISessionManager sessionManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _deviceManager = deviceManager; - _authRepo = authRepo; - _sessionManager = sessionManager; - } - - public void Post(PostDeviceOptions request) - { - _deviceManager.UpdateDeviceOptions(request.Id, request); - } - - public object Get(GetDevices request) - { - return ToOptimizedResult(_deviceManager.GetDevices(request)); - } - - public object Get(GetDeviceInfo request) - { - return _deviceManager.GetDevice(request.Id); - } - - public object Get(GetDeviceOptions request) - { - return _deviceManager.GetDeviceOptions(request.Id); - } - - public object Get(GetCameraUploads request) - { - return ToOptimizedResult(_deviceManager.GetCameraUploadHistory(request.DeviceId)); - } - - public void Delete(DeleteDevice request) - { - var sessions = _authRepo.Get(new AuthenticationInfoQuery - { - DeviceId = request.Id - - }).Items; - - foreach (var session in sessions) - { - _sessionManager.Logout(session); - } - } - - public Task Post(PostCameraUpload request) - { - var deviceId = Request.QueryString["DeviceId"]; - var album = Request.QueryString["Album"]; - var id = Request.QueryString["Id"]; - var name = Request.QueryString["Name"]; - var req = Request.Response.HttpContext.Request; - - if (req.HasFormContentType) - { - var file = req.Form.Files.Count == 0 ? null : req.Form.Files[0]; - - return _deviceManager.AcceptCameraUpload(deviceId, file.OpenReadStream(), new LocalFileInfo - { - MimeType = file.ContentType, - Album = album, - Name = name, - Id = id - }); - } - - return _deviceManager.AcceptCameraUpload(deviceId, request.RequestStream, new LocalFileInfo - { - MimeType = Request.ContentType, - Album = album, - Name = name, - Id = id - }); - } - } -} -- cgit v1.2.3 From 440f060da6cfa8336d51bd05b723d67cfcf168eb Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 19:36:18 -0600 Subject: Fix Authenticated Roles --- Jellyfin.Api/Controllers/DevicesController.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs index 7407c4487..a9dcfb955 100644 --- a/Jellyfin.Api/Controllers/DevicesController.cs +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -48,6 +48,7 @@ namespace Jellyfin.Api.Controllers /// /// Gets or sets the user identifier. /// Device Infos. [HttpGet] + [Authenticated(Roles = "Admin")] [ProducesResponseType(typeof(DeviceInfo[]), StatusCodes.Status200OK)] [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public IActionResult GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId) @@ -70,6 +71,7 @@ namespace Jellyfin.Api.Controllers /// Device Id. /// Device Info. [HttpGet("Info")] + [Authenticated(Roles = "Admin")] [ProducesResponseType(typeof(DeviceInfo), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] @@ -97,6 +99,7 @@ namespace Jellyfin.Api.Controllers /// Device Id. /// Device Info. [HttpGet("Options")] + [Authenticated(Roles = "Admin")] [ProducesResponseType(typeof(DeviceOptions), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] @@ -125,6 +128,7 @@ namespace Jellyfin.Api.Controllers /// Device Options. /// Status. [HttpPost("Options")] + [Authenticated(Roles = "Admin")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] -- cgit v1.2.3 From 16cae23bbee14a7398d39014973b1a476e1ca57c Mon Sep 17 00:00:00 2001 From: ZadenRB Date: Sun, 19 Apr 2020 21:06:28 -0600 Subject: Add response type annotations, return IActionResult to handle errors --- .../Controllers/NotificationsController.cs | 79 ++++++++++++++++------ 1 file changed, 59 insertions(+), 20 deletions(-) diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs index d9a5c5e31..76b025fa1 100644 --- a/Jellyfin.Api/Controllers/NotificationsController.cs +++ b/Jellyfin.Api/Controllers/NotificationsController.cs @@ -10,6 +10,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Notifications; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Notifications; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace Jellyfin.Api.Controllers @@ -42,13 +43,14 @@ namespace Jellyfin.Api.Controllers /// An optional limit on the number of notifications returned. /// A read-only list of all of the user's notifications. [HttpGet("{UserID}")] - public NotificationResultDto GetNotifications( + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public IActionResult GetNotifications( [FromRoute] string userId, [FromQuery] bool? isRead, [FromQuery] int? startIndex, [FromQuery] int? limit) { - return new NotificationResultDto(); + return Ok(new NotificationResultDto()); } /// @@ -57,10 +59,11 @@ namespace Jellyfin.Api.Controllers /// The user's ID. /// Notifications summary for the user. [HttpGet("{UserID}/Summary")] - public NotificationsSummaryDto GetNotificationsSummary( + [ProducesResponseType(typeof(NotificationsSummaryDto), StatusCodes.Status200OK)] + public IActionResult GetNotificationsSummary( [FromRoute] string userId) { - return new NotificationsSummaryDto(); + return Ok(new NotificationsSummaryDto()); } /// @@ -68,9 +71,18 @@ namespace Jellyfin.Api.Controllers /// /// All notification types. [HttpGet("Types")] - public IEnumerable GetNotificationTypes() + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult GetNotificationTypes() { - return _notificationManager.GetNotificationTypes(); + try + { + return Ok(_notificationManager.GetNotificationTypes()); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } } /// @@ -78,9 +90,18 @@ namespace Jellyfin.Api.Controllers /// /// All notification services. [HttpGet("Services")] - public IEnumerable GetNotificationServices() + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult GetNotificationServices() { - return _notificationManager.GetNotificationServices(); + try + { + return Ok(_notificationManager.GetNotificationServices()); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } } /// @@ -90,24 +111,36 @@ namespace Jellyfin.Api.Controllers /// The description of the notification. /// The URL of the notification. /// The level of the notification. + /// Status. [HttpPost("Admin")] - public void CreateAdminNotification( + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult CreateAdminNotification( [FromQuery] string name, [FromQuery] string description, [FromQuery] string? url, [FromQuery] NotificationLevel? level) { - var notification = new NotificationRequest + try { - Name = name, - Description = description, - Url = url, - Level = level ?? NotificationLevel.Normal, - UserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id).ToArray(), - Date = DateTime.UtcNow, - }; + var notification = new NotificationRequest + { + Name = name, + Description = description, + Url = url, + Level = level ?? NotificationLevel.Normal, + UserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id).ToArray(), + Date = DateTime.UtcNow, + }; + + _notificationManager.SendNotification(notification, CancellationToken.None); - _notificationManager.SendNotification(notification, CancellationToken.None); + return Ok(); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } } /// @@ -115,11 +148,14 @@ namespace Jellyfin.Api.Controllers /// /// The userID. /// A comma-separated list of the IDs of notifications which should be set as read. + /// Status. [HttpPost("{UserID}/Read")] - public void SetRead( + [ProducesResponseType(StatusCodes.Status200OK)] + public IActionResult SetRead( [FromRoute] string userId, [FromQuery] string ids) { + return Ok(); } /// @@ -127,11 +163,14 @@ namespace Jellyfin.Api.Controllers /// /// The userID. /// A comma-separated list of the IDs of notifications which should be set as unread. + /// Status. [HttpPost("{UserID}/Unread")] - public void SetUnread( + [ProducesResponseType(StatusCodes.Status200OK)] + public IActionResult SetUnread( [FromRoute] string userId, [FromQuery] string ids) { + return Ok(); } } } -- cgit v1.2.3 From 688240151bae0f333cd329572b3774954d13ebae Mon Sep 17 00:00:00 2001 From: ZadenRB Date: Mon, 20 Apr 2020 00:00:00 -0600 Subject: Enable nullable reference types on new class, remove unnecessary documenation and return types --- Jellyfin.Api/Controllers/NotificationsController.cs | 11 ++--------- Jellyfin.Api/Models/NotificationDtos/NotificationResultDto.cs | 2 ++ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs index 76b025fa1..c0c2be626 100644 --- a/Jellyfin.Api/Controllers/NotificationsController.cs +++ b/Jellyfin.Api/Controllers/NotificationsController.cs @@ -72,7 +72,6 @@ namespace Jellyfin.Api.Controllers /// All notification types. [HttpGet("Types")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public IActionResult GetNotificationTypes() { try @@ -91,7 +90,6 @@ namespace Jellyfin.Api.Controllers /// All notification services. [HttpGet("Services")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public IActionResult GetNotificationServices() { try @@ -114,7 +112,6 @@ namespace Jellyfin.Api.Controllers /// Status. [HttpPost("Admin")] [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public IActionResult CreateAdminNotification( [FromQuery] string name, [FromQuery] string description, @@ -148,14 +145,12 @@ namespace Jellyfin.Api.Controllers /// /// The userID. /// A comma-separated list of the IDs of notifications which should be set as read. - /// Status. [HttpPost("{UserID}/Read")] [ProducesResponseType(StatusCodes.Status200OK)] - public IActionResult SetRead( + public void SetRead( [FromRoute] string userId, [FromQuery] string ids) { - return Ok(); } /// @@ -163,14 +158,12 @@ namespace Jellyfin.Api.Controllers /// /// The userID. /// A comma-separated list of the IDs of notifications which should be set as unread. - /// Status. [HttpPost("{UserID}/Unread")] [ProducesResponseType(StatusCodes.Status200OK)] - public IActionResult SetUnread( + public void SetUnread( [FromRoute] string userId, [FromQuery] string ids) { - return Ok(); } } } diff --git a/Jellyfin.Api/Models/NotificationDtos/NotificationResultDto.cs b/Jellyfin.Api/Models/NotificationDtos/NotificationResultDto.cs index 64e92bd83..e34e176cb 100644 --- a/Jellyfin.Api/Models/NotificationDtos/NotificationResultDto.cs +++ b/Jellyfin.Api/Models/NotificationDtos/NotificationResultDto.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; using System.Collections.Generic; -- cgit v1.2.3 From fff2a40ffc4e5010b26143185c68d221225c1a22 Mon Sep 17 00:00:00 2001 From: crobibero Date: Mon, 20 Apr 2020 07:24:13 -0600 Subject: Remove StringEnumConverter --- Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index a4f078b5b..92bacb440 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -80,9 +80,6 @@ namespace Jellyfin.Server.Extensions { // Setting the naming policy to null leaves the property names as-is when serializing objects to JSON. options.JsonSerializerOptions.PropertyNamingPolicy = null; - - // Accept string enums - options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); }) .AddControllersAsServices(); } -- cgit v1.2.3 From e151d539f2041fb249af82118bde1168d1859c6b Mon Sep 17 00:00:00 2001 From: crobibero Date: Mon, 20 Apr 2020 13:06:29 -0600 Subject: Move ImageByNameService to Jellyfin.Api --- .../Controllers/Images/ImageByNameController.cs | 261 +++++++++++++++++++ MediaBrowser.Api/Images/ImageByNameService.cs | 277 --------------------- 2 files changed, 261 insertions(+), 277 deletions(-) create mode 100644 Jellyfin.Api/Controllers/Images/ImageByNameController.cs delete mode 100644 MediaBrowser.Api/Images/ImageByNameService.cs diff --git a/Jellyfin.Api/Controllers/Images/ImageByNameController.cs b/Jellyfin.Api/Controllers/Images/ImageByNameController.cs new file mode 100644 index 000000000..a14e2403c --- /dev/null +++ b/Jellyfin.Api/Controllers/Images/ImageByNameController.cs @@ -0,0 +1,261 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Net; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers.Images +{ + /// + /// Images By Name Controller. + /// + [Route("Images")] + [Authenticated] + public class ImageByNameController : BaseJellyfinApiController + { + private readonly IServerApplicationPaths _applicationPaths; + private readonly IFileSystem _fileSystem; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + public ImageByNameController( + IServerConfigurationManager serverConfigurationManager, + IFileSystem fileSystem) + { + _applicationPaths = serverConfigurationManager.ApplicationPaths; + _fileSystem = fileSystem; + } + + /// + /// Get all general images. + /// + /// General images. + [HttpGet("General")] + [ProducesResponseType(typeof(ImageByNameInfo[]), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult GetGeneralImages() + { + try + { + return Ok(GetImageList(_applicationPaths.GeneralPath, false)); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Get General Image. + /// + /// The name of the image. + /// Image Type (primary, backdrop, logo, etc). + /// Image Stream. + [HttpGet("General/{Name}/{Type}")] + [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult GetGeneralImage([FromRoute] string name, [FromRoute] string type) + { + try + { + var filename = string.Equals(type, "primary", StringComparison.OrdinalIgnoreCase) + ? "folder" + : type; + + var paths = BaseItem.SupportedImageExtensions + .Select(i => Path.Combine(_applicationPaths.GeneralPath, name, filename + i)).ToList(); + + var path = paths.FirstOrDefault(System.IO.File.Exists) ?? paths.FirstOrDefault(); + if (path == null || !System.IO.File.Exists(path)) + { + return NotFound(); + } + + var contentType = MimeTypes.GetMimeType(path); + return new FileStreamResult(System.IO.File.OpenRead(path), contentType); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Get all general images. + /// + /// General images. + [HttpGet("Ratings")] + [ProducesResponseType(typeof(ImageByNameInfo[]), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult GetRatingImages() + { + try + { + return Ok(GetImageList(_applicationPaths.RatingsPath, false)); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Get rating image. + /// + /// The theme to get the image from. + /// The name of the image. + /// Image Stream. + [HttpGet("Ratings/{Theme}/{Name}")] + [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult GetRatingImage( + [FromRoute] string theme, + [FromRoute] string name) + { + try + { + return GetImageFile(_applicationPaths.RatingsPath, theme, name); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Get all media info images. + /// + /// Media Info images. + [HttpGet("MediaInfo")] + [ProducesResponseType(typeof(ImageByNameInfo[]), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult GetMediaInfoImages() + { + try + { + return Ok(GetImageList(_applicationPaths.MediaInfoImagesPath, false)); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Get media info image. + /// + /// The theme to get the image from. + /// The name of the image. + /// Image Stream. + [HttpGet("MediaInfo/{Theme}/{Name}")] + [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult GetMediaInfoImage( + [FromRoute] string theme, + [FromRoute] string name) + { + try + { + return GetImageFile(_applicationPaths.MediaInfoImagesPath, theme, name); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Internal FileHelper. + /// + /// Path to begin search. + /// Theme to search. + /// File name to search for. + /// Image Stream. + private IActionResult GetImageFile(string basePath, string theme, string name) + { + var themeFolder = Path.Combine(basePath, theme); + if (Directory.Exists(themeFolder)) + { + var path = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(themeFolder, name + i)) + .FirstOrDefault(System.IO.File.Exists); + + if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path)) + { + var contentType = MimeTypes.GetMimeType(path); + return new FileStreamResult(System.IO.File.OpenRead(path), contentType); + } + } + + var allFolder = Path.Combine(basePath, "all"); + if (Directory.Exists(allFolder)) + { + var path = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(allFolder, name + i)) + .FirstOrDefault(System.IO.File.Exists); + + if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path)) + { + var contentType = MimeTypes.GetMimeType(path); + return new FileStreamResult(System.IO.File.OpenRead(path), contentType); + } + } + + return NotFound(); + } + + private List GetImageList(string path, bool supportsThemes) + { + try + { + return _fileSystem.GetFiles(path, BaseItem.SupportedImageExtensions, false, true) + .Select(i => new ImageByNameInfo + { + Name = _fileSystem.GetFileNameWithoutExtension(i), + FileLength = i.Length, + + // For themeable images, use the Theme property + // For general images, the same object structure is fine, + // but it's not owned by a theme, so call it Context + Theme = supportsThemes ? GetThemeName(i.FullName, path) : null, + Context = supportsThemes ? null : GetThemeName(i.FullName, path), + Format = i.Extension.ToLowerInvariant().TrimStart('.') + }) + .OrderBy(i => i.Name) + .ToList(); + } + catch (IOException) + { + return new List(); + } + } + + private string GetThemeName(string path, string rootImagePath) + { + var parentName = Path.GetDirectoryName(path); + + if (string.Equals(parentName, rootImagePath, StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + parentName = Path.GetFileName(parentName); + + return string.Equals(parentName, "all", StringComparison.OrdinalIgnoreCase) ? null : parentName; + } + } +} diff --git a/MediaBrowser.Api/Images/ImageByNameService.cs b/MediaBrowser.Api/Images/ImageByNameService.cs deleted file mode 100644 index 45b7d0c10..000000000 --- a/MediaBrowser.Api/Images/ImageByNameService.cs +++ /dev/null @@ -1,277 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Images -{ - /// - /// Class GetGeneralImage - /// - [Route("/Images/General/{Name}/{Type}", "GET", Summary = "Gets a general image by name")] - public class GetGeneralImage - { - /// - /// Gets or sets the name. - /// - /// The name. - [ApiMember(Name = "Name", Description = "The name of the image", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - - [ApiMember(Name = "Type", Description = "Image Type (primary, backdrop, logo, etc).", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Type { get; set; } - } - - /// - /// Class GetRatingImage - /// - [Route("/Images/Ratings/{Theme}/{Name}", "GET", Summary = "Gets a rating image by name")] - public class GetRatingImage - { - /// - /// Gets or sets the name. - /// - /// The name. - [ApiMember(Name = "Name", Description = "The name of the image", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - - /// - /// Gets or sets the theme. - /// - /// The theme. - [ApiMember(Name = "Theme", Description = "The theme to get the image from", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Theme { get; set; } - } - - /// - /// Class GetMediaInfoImage - /// - [Route("/Images/MediaInfo/{Theme}/{Name}", "GET", Summary = "Gets a media info image by name")] - public class GetMediaInfoImage - { - /// - /// Gets or sets the name. - /// - /// The name. - [ApiMember(Name = "Name", Description = "The name of the image", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - - /// - /// Gets or sets the theme. - /// - /// The theme. - [ApiMember(Name = "Theme", Description = "The theme to get the image from", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Theme { get; set; } - } - - [Route("/Images/MediaInfo", "GET", Summary = "Gets all media info image by name")] - [Authenticated] - public class GetMediaInfoImages : IReturn> - { - } - - [Route("/Images/Ratings", "GET", Summary = "Gets all rating images by name")] - [Authenticated] - public class GetRatingImages : IReturn> - { - } - - [Route("/Images/General", "GET", Summary = "Gets all general images by name")] - [Authenticated] - public class GetGeneralImages : IReturn> - { - } - - /// - /// Class ImageByNameService - /// - public class ImageByNameService : BaseApiService - { - /// - /// The _app paths - /// - private readonly IServerApplicationPaths _appPaths; - - private readonly IFileSystem _fileSystem; - - /// - /// Initializes a new instance of the class. - /// - public ImageByNameService( - ILogger logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory resultFactory, - IFileSystem fileSystem) - : base(logger, serverConfigurationManager, resultFactory) - { - _appPaths = serverConfigurationManager.ApplicationPaths; - _fileSystem = fileSystem; - } - - public object Get(GetMediaInfoImages request) - { - return ToOptimizedResult(GetImageList(_appPaths.MediaInfoImagesPath, true)); - } - - public object Get(GetRatingImages request) - { - return ToOptimizedResult(GetImageList(_appPaths.RatingsPath, true)); - } - - public object Get(GetGeneralImages request) - { - return ToOptimizedResult(GetImageList(_appPaths.GeneralPath, false)); - } - - private List GetImageList(string path, bool supportsThemes) - { - try - { - return _fileSystem.GetFiles(path, BaseItem.SupportedImageExtensions, false, true) - .Select(i => new ImageByNameInfo - { - Name = _fileSystem.GetFileNameWithoutExtension(i), - FileLength = i.Length, - - // For themeable images, use the Theme property - // For general images, the same object structure is fine, - // but it's not owned by a theme, so call it Context - Theme = supportsThemes ? GetThemeName(i.FullName, path) : null, - Context = supportsThemes ? null : GetThemeName(i.FullName, path), - - Format = i.Extension.ToLowerInvariant().TrimStart('.') - }) - .OrderBy(i => i.Name) - .ToList(); - } - catch (IOException) - { - return new List(); - } - } - - private string GetThemeName(string path, string rootImagePath) - { - var parentName = Path.GetDirectoryName(path); - - if (string.Equals(parentName, rootImagePath, StringComparison.OrdinalIgnoreCase)) - { - return null; - } - - parentName = Path.GetFileName(parentName); - - return string.Equals(parentName, "all", StringComparison.OrdinalIgnoreCase) ? - null : - parentName; - } - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public Task Get(GetGeneralImage request) - { - var filename = string.Equals(request.Type, "primary", StringComparison.OrdinalIgnoreCase) - ? "folder" - : request.Type; - - var paths = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(_appPaths.GeneralPath, request.Name, filename + i)).ToList(); - - var path = paths.FirstOrDefault(File.Exists) ?? paths.FirstOrDefault(); - - return ResultFactory.GetStaticFileResult(Request, path); - } - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetRatingImage request) - { - var themeFolder = Path.Combine(_appPaths.RatingsPath, request.Theme); - - if (Directory.Exists(themeFolder)) - { - var path = BaseItem.SupportedImageExtensions - .Select(i => Path.Combine(themeFolder, request.Name + i)) - .FirstOrDefault(File.Exists); - - if (!string.IsNullOrEmpty(path)) - { - return ResultFactory.GetStaticFileResult(Request, path); - } - } - - var allFolder = Path.Combine(_appPaths.RatingsPath, "all"); - - if (Directory.Exists(allFolder)) - { - // Avoid implicitly captured closure - var currentRequest = request; - - var path = BaseItem.SupportedImageExtensions - .Select(i => Path.Combine(allFolder, currentRequest.Name + i)) - .FirstOrDefault(File.Exists); - - if (!string.IsNullOrEmpty(path)) - { - return ResultFactory.GetStaticFileResult(Request, path); - } - } - - throw new ResourceNotFoundException("MediaInfo image not found: " + request.Name); - } - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public Task Get(GetMediaInfoImage request) - { - var themeFolder = Path.Combine(_appPaths.MediaInfoImagesPath, request.Theme); - - if (Directory.Exists(themeFolder)) - { - var path = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(themeFolder, request.Name + i)) - .FirstOrDefault(File.Exists); - - if (!string.IsNullOrEmpty(path)) - { - return ResultFactory.GetStaticFileResult(Request, path); - } - } - - var allFolder = Path.Combine(_appPaths.MediaInfoImagesPath, "all"); - - if (Directory.Exists(allFolder)) - { - // Avoid implicitly captured closure - var currentRequest = request; - - var path = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(allFolder, currentRequest.Name + i)) - .FirstOrDefault(File.Exists); - - if (!string.IsNullOrEmpty(path)) - { - return ResultFactory.GetStaticFileResult(Request, path); - } - } - - throw new ResourceNotFoundException("MediaInfo image not found: " + request.Name); - } - } -} -- cgit v1.2.3 From 376619369d8b1e889475da1191092f43e7f26ae6 Mon Sep 17 00:00:00 2001 From: crobibero Date: Mon, 20 Apr 2020 13:12:35 -0600 Subject: fix build --- Jellyfin.Api/Controllers/Images/ImageByNameController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/Images/ImageByNameController.cs b/Jellyfin.Api/Controllers/Images/ImageByNameController.cs index a14e2403c..309729605 100644 --- a/Jellyfin.Api/Controllers/Images/ImageByNameController.cs +++ b/Jellyfin.Api/Controllers/Images/ImageByNameController.cs @@ -244,7 +244,7 @@ namespace Jellyfin.Api.Controllers.Images } } - private string GetThemeName(string path, string rootImagePath) + private string? GetThemeName(string path, string rootImagePath) { var parentName = Path.GetDirectoryName(path); -- cgit v1.2.3 From 766d2ee413a15c682c0d687619064caf98f9031c Mon Sep 17 00:00:00 2001 From: crobibero Date: Mon, 20 Apr 2020 14:21:06 -0600 Subject: Move RemoteImageService to Jellyfin.API --- .../Controllers/Images/RemoteImageController.cs | 290 ++++++++++++++++++++ MediaBrowser.Api/Images/RemoteImageService.cs | 295 --------------------- 2 files changed, 290 insertions(+), 295 deletions(-) create mode 100644 Jellyfin.Api/Controllers/Images/RemoteImageController.cs delete mode 100644 MediaBrowser.Api/Images/RemoteImageService.cs diff --git a/Jellyfin.Api/Controllers/Images/RemoteImageController.cs b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs new file mode 100644 index 000000000..66479582d --- /dev/null +++ b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs @@ -0,0 +1,290 @@ +#nullable enable + +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.Providers; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Jellyfin.Api.Controllers.Images +{ + /// + /// Remote Images Controller. + /// + [Route("Images")] + [Authenticated] + public class RemoteImageController : BaseJellyfinApiController + { + private readonly IProviderManager _providerManager; + private readonly IServerApplicationPaths _applicationPaths; + private readonly IHttpClient _httpClient; + private readonly ILibraryManager _libraryManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public RemoteImageController( + IProviderManager providerManager, + IServerApplicationPaths applicationPaths, + IHttpClient httpClient, + ILibraryManager libraryManager) + { + _providerManager = providerManager; + _applicationPaths = applicationPaths; + _httpClient = httpClient; + _libraryManager = libraryManager; + } + + /// + /// Gets available remote images for an item. + /// + /// Item Id. + /// The image type. + /// Optional. The record index to start at. All items with a lower index will be dropped from the results. + /// Optional. The maximum number of records to return. + /// Optional. The image provider to use. + /// Optinal. Include all languages. + /// Remote Image Result. + [HttpGet("{Id}/RemoteImages")] + [ProducesResponseType(typeof(RemoteImageResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status404NotFound)] + public async Task GetRemoteImages( + [FromRoute] string id, + [FromQuery] ImageType? type, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] string providerName, + [FromQuery] bool includeAllLanguages) + { + try + { + var item = _libraryManager.GetItemById(id); + if (item == null) + { + return NotFound(); + } + + var images = await _providerManager.GetAvailableRemoteImages( + item, + new RemoteImageQuery + { + ProviderName = providerName, + IncludeAllLanguages = includeAllLanguages, + IncludeDisabledProviders = true, + ImageType = type + }, CancellationToken.None) + .ConfigureAwait(false); + + var imageArray = images.ToArray(); + var allProviders = _providerManager.GetRemoteImageProviderInfo(item); + if (type.HasValue) + { + allProviders = allProviders.Where(o => o.SupportedImages.Contains(type.Value)); + } + + var result = new RemoteImageResult + { + TotalRecordCount = imageArray.Length, + Providers = allProviders.Select(o => o.Name) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray() + }; + + if (startIndex.HasValue) + { + imageArray = imageArray.Skip(startIndex.Value).ToArray(); + } + + if (limit.HasValue) + { + imageArray = imageArray.Take(limit.Value).ToArray(); + } + + result.Images = imageArray; + return Ok(result); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Gets available remote image providers for an item. + /// + /// Item Id. + /// List of providers. + [HttpGet("{Id}/RemoteImages/Providers")] + [ProducesResponseType(typeof(ImageProviderInfo[]), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult GetRemoteImageProviders([FromRoute] string id) + { + try + { + var item = _libraryManager.GetItemById(id); + if (item == null) + { + return NotFound(); + } + + var providers = _providerManager.GetRemoteImageProviderInfo(item); + return Ok(providers); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Gets a remote image. + /// + /// The image url. + /// Image Stream. + [HttpGet("Remote")] + [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public async Task GetRemoteImage([FromQuery, BindRequired] string imageUrl) + { + try + { + var urlHash = imageUrl.GetMD5(); + var pointerCachePath = GetFullCachePath(urlHash.ToString()); + + string? contentPath = null; + bool hasFile = false; + + try + { + contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false); + if (System.IO.File.Exists(contentPath)) + { + hasFile = true; + } + } + catch (FileNotFoundException) + { + // Means the file isn't cached yet + } + catch (IOException) + { + // Means the file isn't cached yet + } + + if (!hasFile) + { + await DownloadImage(imageUrl, urlHash, pointerCachePath).ConfigureAwait(false); + contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false); + } + + if (string.IsNullOrEmpty(contentPath)) + { + return NotFound(); + } + + var contentType = MimeTypes.GetMimeType(contentPath); + return new FileStreamResult(System.IO.File.OpenRead(contentPath), contentType); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Downloads a remote image for an item. + /// + /// Item Id. + /// The image type. + /// The image url. + /// Status. + [HttpPost("{Id}/RemoteImages/Download")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public async Task DownloadRemoteImage( + [FromRoute] string id, + [FromQuery, BindRequired] ImageType type, + [FromQuery] string imageUrl) + { + try + { + var item = _libraryManager.GetItemById(id); + if (item == null) + { + return NotFound(); + } + + await _providerManager.SaveImage(item, imageUrl, type, null, CancellationToken.None) + .ConfigureAwait(false); + + item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None); + return Ok(); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Gets the full cache path. + /// + /// The filename. + /// System.String. + private string GetFullCachePath(string filename) + { + return Path.Combine(_applicationPaths.CachePath, "remote-images", filename.Substring(0, 1), filename); + } + + /// + /// Downloads the image. + /// + /// The URL. + /// The URL hash. + /// The pointer cache path. + /// Task. + private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath) + { + using var result = await _httpClient.GetResponse(new HttpRequestOptions + { + Url = url, + BufferContent = false + }).ConfigureAwait(false); + var ext = result.ContentType.Split('/').Last(); + + var fullCachePath = GetFullCachePath(urlHash + "." + ext); + + Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath)); + using (var stream = result.Content) + { + using var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); + await stream.CopyToAsync(filestream).ConfigureAwait(false); + } + + Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath)); + await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath, CancellationToken.None) + .ConfigureAwait(false); + } + } +} diff --git a/MediaBrowser.Api/Images/RemoteImageService.cs b/MediaBrowser.Api/Images/RemoteImageService.cs deleted file mode 100644 index 222bb34d3..000000000 --- a/MediaBrowser.Api/Images/RemoteImageService.cs +++ /dev/null @@ -1,295 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Images -{ - public class BaseRemoteImageRequest : IReturn - { - [ApiMember(Name = "Type", Description = "The image type", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public ImageType? Type { get; set; } - - /// - /// Skips over a given number of items within the results. Use for paging. - /// - /// The start index. - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// - /// 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; } - - [ApiMember(Name = "ProviderName", Description = "Optional. The image provider to use", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ProviderName { get; set; } - - [ApiMember(Name = "IncludeAllLanguages", Description = "Optional.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool IncludeAllLanguages { get; set; } - } - - [Route("/Items/{Id}/RemoteImages", "GET", Summary = "Gets available remote images for an item")] - [Authenticated] - public class GetRemoteImages : BaseRemoteImageRequest - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Items/{Id}/RemoteImages/Providers", "GET", Summary = "Gets available remote image providers for an item")] - [Authenticated] - public class GetRemoteImageProviders : IReturn> - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - public class BaseDownloadRemoteImage : IReturnVoid - { - [ApiMember(Name = "Type", Description = "The image type", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public ImageType Type { get; set; } - - [ApiMember(Name = "ProviderName", Description = "The image provider", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public string ProviderName { get; set; } - - [ApiMember(Name = "ImageUrl", Description = "The image url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public string ImageUrl { get; set; } - } - - [Route("/Items/{Id}/RemoteImages/Download", "POST", Summary = "Downloads a remote image for an item")] - [Authenticated(Roles = "Admin")] - public class DownloadRemoteImage : BaseDownloadRemoteImage - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - } - - [Route("/Images/Remote", "GET", Summary = "Gets a remote image")] - public class GetRemoteImage - { - [ApiMember(Name = "ImageUrl", Description = "The image url", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ImageUrl { get; set; } - } - - public class RemoteImageService : BaseApiService - { - private readonly IProviderManager _providerManager; - - private readonly IServerApplicationPaths _appPaths; - private readonly IHttpClient _httpClient; - private readonly IFileSystem _fileSystem; - - private readonly ILibraryManager _libraryManager; - - public RemoteImageService( - ILogger logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IProviderManager providerManager, - IServerApplicationPaths appPaths, - IHttpClient httpClient, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _providerManager = providerManager; - _appPaths = appPaths; - _httpClient = httpClient; - _fileSystem = fileSystem; - _libraryManager = libraryManager; - } - - public object Get(GetRemoteImageProviders request) - { - var item = _libraryManager.GetItemById(request.Id); - - var result = GetImageProviders(item); - - return ToOptimizedResult(result); - } - - private List GetImageProviders(BaseItem item) - { - return _providerManager.GetRemoteImageProviderInfo(item).ToList(); - } - - public async Task Get(GetRemoteImages request) - { - var item = _libraryManager.GetItemById(request.Id); - - var images = await _providerManager.GetAvailableRemoteImages(item, new RemoteImageQuery - { - ProviderName = request.ProviderName, - IncludeAllLanguages = request.IncludeAllLanguages, - IncludeDisabledProviders = true, - ImageType = request.Type - - }, CancellationToken.None).ConfigureAwait(false); - - var imagesList = images.ToArray(); - - var allProviders = _providerManager.GetRemoteImageProviderInfo(item); - - if (request.Type.HasValue) - { - allProviders = allProviders.Where(i => i.SupportedImages.Contains(request.Type.Value)); - } - - var result = new RemoteImageResult - { - TotalRecordCount = imagesList.Length, - Providers = allProviders.Select(i => i.Name) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToArray() - }; - - if (request.StartIndex.HasValue) - { - imagesList = imagesList.Skip(request.StartIndex.Value) - .ToArray(); - } - - if (request.Limit.HasValue) - { - imagesList = imagesList.Take(request.Limit.Value) - .ToArray(); - } - - result.Images = imagesList; - - return result; - } - - /// - /// Posts the specified request. - /// - /// The request. - public Task Post(DownloadRemoteImage request) - { - var item = _libraryManager.GetItemById(request.Id); - - return DownloadRemoteImage(item, request); - } - - /// - /// Downloads the remote image. - /// - /// The item. - /// The request. - /// Task. - private async Task DownloadRemoteImage(BaseItem item, BaseDownloadRemoteImage request) - { - await _providerManager.SaveImage(item, request.ImageUrl, request.Type, null, CancellationToken.None).ConfigureAwait(false); - - item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None); - } - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public async Task Get(GetRemoteImage request) - { - var urlHash = request.ImageUrl.GetMD5(); - var pointerCachePath = GetFullCachePath(urlHash.ToString()); - - string contentPath; - - try - { - contentPath = File.ReadAllText(pointerCachePath); - - if (File.Exists(contentPath)) - { - return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false); - } - } - catch (FileNotFoundException) - { - // Means the file isn't cached yet - } - catch (IOException) - { - // Means the file isn't cached yet - } - - await DownloadImage(request.ImageUrl, urlHash, pointerCachePath).ConfigureAwait(false); - - // Read the pointer file again - contentPath = File.ReadAllText(pointerCachePath); - - return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false); - } - - /// - /// Downloads the image. - /// - /// The URL. - /// The URL hash. - /// The pointer cache path. - /// Task. - private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath) - { - using var result = await _httpClient.GetResponse(new HttpRequestOptions - { - Url = url, - BufferContent = false - - }).ConfigureAwait(false); - var ext = result.ContentType.Split('/').Last(); - - var fullCachePath = GetFullCachePath(urlHash + "." + ext); - - Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath)); - using (var stream = result.Content) - { - using var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); - await stream.CopyToAsync(filestream).ConfigureAwait(false); - } - - Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath)); - File.WriteAllText(pointerCachePath, fullCachePath); - } - - /// - /// Gets the full cache path. - /// - /// The filename. - /// System.String. - private string GetFullCachePath(string filename) - { - return Path.Combine(_appPaths.CachePath, "remote-images", filename.Substring(0, 1), filename); - } - } -} -- cgit v1.2.3 From 67efcbee05fe7917aaff11fd27235fb952938434 Mon Sep 17 00:00:00 2001 From: ZadenRB Date: Mon, 20 Apr 2020 20:16:58 -0600 Subject: Remove error handlers, to be implemented at a global level in a separate PR --- .../Controllers/NotificationsController.cs | 47 ++++++---------------- 1 file changed, 12 insertions(+), 35 deletions(-) diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs index c0c2be626..2a41f6020 100644 --- a/Jellyfin.Api/Controllers/NotificationsController.cs +++ b/Jellyfin.Api/Controllers/NotificationsController.cs @@ -74,14 +74,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public IActionResult GetNotificationTypes() { - try - { - return Ok(_notificationManager.GetNotificationTypes()); - } - catch (Exception e) - { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); - } + return Ok(_notificationManager.GetNotificationTypes()); } /// @@ -92,14 +85,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public IActionResult GetNotificationServices() { - try - { - return Ok(_notificationManager.GetNotificationServices()); - } - catch (Exception e) - { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); - } + return Ok(_notificationManager.GetNotificationServices()); } /// @@ -112,32 +98,23 @@ namespace Jellyfin.Api.Controllers /// Status. [HttpPost("Admin")] [ProducesResponseType(StatusCodes.Status200OK)] - public IActionResult CreateAdminNotification( + public void CreateAdminNotification( [FromQuery] string name, [FromQuery] string description, [FromQuery] string? url, [FromQuery] NotificationLevel? level) { - try + var notification = new NotificationRequest { - var notification = new NotificationRequest - { - Name = name, - Description = description, - Url = url, - Level = level ?? NotificationLevel.Normal, - UserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id).ToArray(), - Date = DateTime.UtcNow, - }; - - _notificationManager.SendNotification(notification, CancellationToken.None); + Name = name, + Description = description, + Url = url, + Level = level ?? NotificationLevel.Normal, + UserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id).ToArray(), + Date = DateTime.UtcNow, + }; - return Ok(); - } - catch (Exception e) - { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); - } + _notificationManager.SendNotification(notification, CancellationToken.None); } /// -- cgit v1.2.3 From 6c8e1d37bd49339d298c46c24cddf8e858b334c8 Mon Sep 17 00:00:00 2001 From: ZadenRB Date: Mon, 20 Apr 2020 23:53:09 -0600 Subject: Remove more unnecessary IActionResult --- Jellyfin.Api/Controllers/NotificationsController.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs index 2a41f6020..932b91d55 100644 --- a/Jellyfin.Api/Controllers/NotificationsController.cs +++ b/Jellyfin.Api/Controllers/NotificationsController.cs @@ -43,14 +43,14 @@ namespace Jellyfin.Api.Controllers /// An optional limit on the number of notifications returned. /// A read-only list of all of the user's notifications. [HttpGet("{UserID}")] - [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] - public IActionResult GetNotifications( + [ProducesResponseType(typeof(NotificationResultDto), StatusCodes.Status200OK)] + public NotificationResultDto GetNotifications( [FromRoute] string userId, [FromQuery] bool? isRead, [FromQuery] int? startIndex, [FromQuery] int? limit) { - return Ok(new NotificationResultDto()); + return new NotificationResultDto(); } /// @@ -60,10 +60,10 @@ namespace Jellyfin.Api.Controllers /// Notifications summary for the user. [HttpGet("{UserID}/Summary")] [ProducesResponseType(typeof(NotificationsSummaryDto), StatusCodes.Status200OK)] - public IActionResult GetNotificationsSummary( + public NotificationsSummaryDto GetNotificationsSummary( [FromRoute] string userId) { - return Ok(new NotificationsSummaryDto()); + return new NotificationsSummaryDto(); } /// @@ -71,10 +71,10 @@ namespace Jellyfin.Api.Controllers /// /// All notification types. [HttpGet("Types")] - [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] - public IActionResult GetNotificationTypes() + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public IEnumerable GetNotificationTypes() { - return Ok(_notificationManager.GetNotificationTypes()); + return _notificationManager.GetNotificationTypes(); } /// @@ -83,9 +83,9 @@ namespace Jellyfin.Api.Controllers /// All notification services. [HttpGet("Services")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] - public IActionResult GetNotificationServices() + public IEnumerable GetNotificationServices() { - return Ok(_notificationManager.GetNotificationServices()); + return _notificationManager.GetNotificationServices(); } /// -- cgit v1.2.3 From dae69657108f90de54166a670c47a6dff2dae139 Mon Sep 17 00:00:00 2001 From: ZadenRB Date: Tue, 21 Apr 2020 00:24:35 -0600 Subject: Remove documentation of void return type --- Jellyfin.Api/Controllers/NotificationsController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs index 932b91d55..c1d9e3251 100644 --- a/Jellyfin.Api/Controllers/NotificationsController.cs +++ b/Jellyfin.Api/Controllers/NotificationsController.cs @@ -95,7 +95,6 @@ namespace Jellyfin.Api.Controllers /// The description of the notification. /// The URL of the notification. /// The level of the notification. - /// Status. [HttpPost("Admin")] [ProducesResponseType(StatusCodes.Status200OK)] public void CreateAdminNotification( -- cgit v1.2.3 From 1175ce3f97fdebc6fdb489ce65deaac59c7b7f87 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 07:36:22 -0600 Subject: Add Exception Middleware --- Jellyfin.Api/Models/ExceptionDtos/ExceptionDto.cs | 14 +++++ .../Extensions/ApiApplicationBuilderExtensions.cs | 11 ++++ Jellyfin.Server/Middleware/ExceptionMiddleware.cs | 60 ++++++++++++++++++++++ Jellyfin.Server/Startup.cs | 2 + 4 files changed, 87 insertions(+) create mode 100644 Jellyfin.Api/Models/ExceptionDtos/ExceptionDto.cs create mode 100644 Jellyfin.Server/Middleware/ExceptionMiddleware.cs diff --git a/Jellyfin.Api/Models/ExceptionDtos/ExceptionDto.cs b/Jellyfin.Api/Models/ExceptionDtos/ExceptionDto.cs new file mode 100644 index 000000000..d2b48d4ae --- /dev/null +++ b/Jellyfin.Api/Models/ExceptionDtos/ExceptionDto.cs @@ -0,0 +1,14 @@ +namespace Jellyfin.Api.Models.ExceptionDtos +{ + /// + /// Exception Dto. + /// Used for graceful handling of API exceptions. + /// + public class ExceptionDto + { + /// + /// Gets or sets exception message. + /// + public string Message { get; set; } + } +} diff --git a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs index db06eb455..6c105ab65 100644 --- a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs @@ -1,3 +1,4 @@ +using Jellyfin.Server.Middleware; using Microsoft.AspNetCore.Builder; namespace Jellyfin.Server.Extensions @@ -23,5 +24,15 @@ namespace Jellyfin.Server.Extensions c.SwaggerEndpoint("/swagger/v1/swagger.json", "Jellyfin API V1"); }); } + + /// + /// Adds exception middleware to the application pipeline. + /// + /// The application builder. + /// The updated application builder. + public static IApplicationBuilder UseExceptionMiddleware(this IApplicationBuilder applicationBuilder) + { + return applicationBuilder.UseMiddleware(); + } } } diff --git a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs new file mode 100644 index 000000000..39aace95d --- /dev/null +++ b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs @@ -0,0 +1,60 @@ +using System; +using System.Text.Json; +using System.Threading.Tasks; +using Jellyfin.Api.Models.ExceptionDtos; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Middleware +{ + /// + /// Exception Middleware. + /// + public class ExceptionMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// Next request delegate. + /// Instance of the interface. + public ExceptionMiddleware(RequestDelegate next, ILoggerFactory loggerFactory) + { + _next = next ?? throw new ArgumentNullException(nameof(next)); + _logger = loggerFactory.CreateLogger() ?? + throw new ArgumentNullException(nameof(loggerFactory)); + } + + /// + /// Invoke request. + /// + /// Request context. + /// Task. + public async Task Invoke(HttpContext context) + { + try + { + await _next(context).ConfigureAwait(false); + } + catch (Exception ex) + { + if (context.Response.HasStarted) + { + _logger.LogWarning("The response has already started, the exception middleware will not be executed."); + throw; + } + + var exceptionBody = new ExceptionDto { Message = ex.Message }; + var exceptionJson = JsonSerializer.Serialize(exceptionBody); + + context.Response.Clear(); + context.Response.StatusCode = StatusCodes.Status500InternalServerError; + // TODO switch between PascalCase and camelCase + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(exceptionJson).ConfigureAwait(false); + } + } + } +} diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs index 4d7d56e9d..7a632f6c4 100644 --- a/Jellyfin.Server/Startup.cs +++ b/Jellyfin.Server/Startup.cs @@ -58,6 +58,8 @@ namespace Jellyfin.Server app.UseDeveloperExceptionPage(); } + app.UseExceptionMiddleware(); + app.UseWebSockets(); app.UseResponseCompression(); -- cgit v1.2.3 From 08eba82bb7bebe277f6b106fa48994bb98c3dd41 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 07:52:33 -0600 Subject: Remove exception handler --- Jellyfin.Api/Controllers/AttachmentsController.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Jellyfin.Api/Controllers/AttachmentsController.cs b/Jellyfin.Api/Controllers/AttachmentsController.cs index f4c1a761f..aeeaf5cbd 100644 --- a/Jellyfin.Api/Controllers/AttachmentsController.cs +++ b/Jellyfin.Api/Controllers/AttachmentsController.cs @@ -79,10 +79,6 @@ namespace Jellyfin.Api.Controllers { return StatusCode(StatusCodes.Status404NotFound, e.Message); } - catch (Exception e) - { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); - } } } } -- cgit v1.2.3 From 5ef71d592b84b73290e3e7a34cd7fa8b9f337f50 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 07:55:01 -0600 Subject: Remove exception handler --- Jellyfin.Api/Controllers/DevicesController.cs | 143 ++++++++------------------ 1 file changed, 44 insertions(+), 99 deletions(-) diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs index a9dcfb955..5dc3f27ee 100644 --- a/Jellyfin.Api/Controllers/DevicesController.cs +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -53,16 +53,9 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public IActionResult GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId) { - try - { - var deviceQuery = new DeviceQuery { SupportsSync = supportsSync, UserId = userId ?? Guid.Empty }; - var devices = _deviceManager.GetDevices(deviceQuery); - return Ok(devices); - } - catch (Exception e) - { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); - } + var deviceQuery = new DeviceQuery { SupportsSync = supportsSync, UserId = userId ?? Guid.Empty }; + var devices = _deviceManager.GetDevices(deviceQuery); + return Ok(devices); } /// @@ -77,20 +70,13 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public IActionResult GetDeviceInfo([FromQuery, BindRequired] string id) { - try - { - var deviceInfo = _deviceManager.GetDevice(id); - if (deviceInfo == null) - { - return NotFound(); - } - - return Ok(deviceInfo); - } - catch (Exception e) + var deviceInfo = _deviceManager.GetDevice(id); + if (deviceInfo == null) { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + return NotFound(); } + + return Ok(deviceInfo); } /// @@ -105,20 +91,13 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public IActionResult GetDeviceOptions([FromQuery, BindRequired] string id) { - try + var deviceInfo = _deviceManager.GetDeviceOptions(id); + if (deviceInfo == null) { - var deviceInfo = _deviceManager.GetDeviceOptions(id); - if (deviceInfo == null) - { - return NotFound(); - } - - return Ok(deviceInfo); - } - catch (Exception e) - { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + return NotFound(); } + + return Ok(deviceInfo); } /// @@ -136,21 +115,14 @@ namespace Jellyfin.Api.Controllers [FromQuery, BindRequired] string id, [FromBody, BindRequired] DeviceOptions deviceOptions) { - try - { - var existingDeviceOptions = _deviceManager.GetDeviceOptions(id); - if (existingDeviceOptions == null) - { - return NotFound(); - } - - _deviceManager.UpdateDeviceOptions(id, deviceOptions); - return Ok(); - } - catch (Exception e) + var existingDeviceOptions = _deviceManager.GetDeviceOptions(id); + if (existingDeviceOptions == null) { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + return NotFound(); } + + _deviceManager.UpdateDeviceOptions(id, deviceOptions); + return Ok(); } /// @@ -163,21 +135,14 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public IActionResult DeleteDevice([FromQuery, BindRequired] string id) { - try - { - var sessions = _authenticationRepository.Get(new AuthenticationInfoQuery { DeviceId = id }).Items; - - foreach (var session in sessions) - { - _sessionManager.Logout(session); - } + var sessions = _authenticationRepository.Get(new AuthenticationInfoQuery { DeviceId = id }).Items; - return Ok(); - } - catch (Exception e) + foreach (var session in sessions) { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + _sessionManager.Logout(session); } + + return Ok(); } /// @@ -190,15 +155,8 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public IActionResult GetCameraUploads([FromQuery, BindRequired] string id) { - try - { - var uploadHistory = _deviceManager.GetCameraUploadHistory(id); - return Ok(uploadHistory); - } - catch (Exception e) - { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); - } + var uploadHistory = _deviceManager.GetCameraUploadHistory(id); + return Ok(uploadHistory); } /// @@ -219,46 +177,33 @@ namespace Jellyfin.Api.Controllers [FromQuery, BindRequired] string name, [FromQuery, BindRequired] string id) { - try - { - Stream fileStream; - string contentType; + Stream fileStream; + string contentType; - if (Request.HasFormContentType) + if (Request.HasFormContentType) + { + if (Request.Form.Files.Any()) { - if (Request.Form.Files.Any()) - { - fileStream = Request.Form.Files[0].OpenReadStream(); - contentType = Request.Form.Files[0].ContentType; - } - else - { - return BadRequest(); - } + fileStream = Request.Form.Files[0].OpenReadStream(); + contentType = Request.Form.Files[0].ContentType; } else { - fileStream = Request.Body; - contentType = Request.ContentType; + return BadRequest(); } - - await _deviceManager.AcceptCameraUpload( - deviceId, - fileStream, - new LocalFileInfo - { - MimeType = contentType, - Album = album, - Name = name, - Id = id - }).ConfigureAwait(false); - - return Ok(); } - catch (Exception e) + else { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + fileStream = Request.Body; + contentType = Request.ContentType; } + + await _deviceManager.AcceptCameraUpload( + deviceId, + fileStream, + new LocalFileInfo { MimeType = contentType, Album = album, Name = name, Id = id }).ConfigureAwait(false); + + return Ok(); } } } -- cgit v1.2.3 From 04119c0d409342050cb7624f025a21985e10a412 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 07:55:57 -0600 Subject: Remove exception handler --- .../Controllers/DisplayPreferencesController.cs | 51 ++++++++-------------- 1 file changed, 18 insertions(+), 33 deletions(-) diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs index 0554091b4..e15e9c4be 100644 --- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs +++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs @@ -1,6 +1,5 @@ #nullable enable -using System; using System.ComponentModel.DataAnnotations; using System.Threading; using MediaBrowser.Controller.Net; @@ -45,20 +44,13 @@ namespace Jellyfin.Api.Controllers [FromQuery] [Required] string userId, [FromQuery] [Required] string client) { - try + var result = _displayPreferencesRepository.GetDisplayPreferences(displayPreferencesId, userId, client); + if (result == null) { - var result = _displayPreferencesRepository.GetDisplayPreferences(displayPreferencesId, userId, client); - if (result == null) - { - return NotFound(); - } - - return Ok(result); - } - catch (Exception e) - { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + return NotFound(); } + + return Ok(result); } /// @@ -80,30 +72,23 @@ namespace Jellyfin.Api.Controllers [FromQuery, BindRequired] string client, [FromBody, BindRequired] DisplayPreferences displayPreferences) { - try + if (!ModelState.IsValid) { - if (!ModelState.IsValid) - { - return BadRequest(ModelState); - } - - if (displayPreferencesId == null) - { - // do nothing. - } - - _displayPreferencesRepository.SaveDisplayPreferences( - displayPreferences, - userId, - client, - CancellationToken.None); - - return Ok(); + return BadRequest(ModelState); } - catch (Exception e) + + if (displayPreferencesId == null) { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + // do nothing. } + + _displayPreferencesRepository.SaveDisplayPreferences( + displayPreferences, + userId, + client, + CancellationToken.None); + + return Ok(); } } } -- cgit v1.2.3 From 30609236ab58532d021e45edcdacd32d78aeca94 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 07:57:45 -0600 Subject: Remove exception handler --- .../Controllers/Images/ImageByNameController.cs | 74 +++++----------------- 1 file changed, 16 insertions(+), 58 deletions(-) diff --git a/Jellyfin.Api/Controllers/Images/ImageByNameController.cs b/Jellyfin.Api/Controllers/Images/ImageByNameController.cs index 309729605..4034c9e85 100644 --- a/Jellyfin.Api/Controllers/Images/ImageByNameController.cs +++ b/Jellyfin.Api/Controllers/Images/ImageByNameController.cs @@ -48,14 +48,7 @@ namespace Jellyfin.Api.Controllers.Images [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public IActionResult GetGeneralImages() { - try - { - return Ok(GetImageList(_applicationPaths.GeneralPath, false)); - } - catch (Exception e) - { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); - } + return Ok(GetImageList(_applicationPaths.GeneralPath, false)); } /// @@ -70,28 +63,21 @@ namespace Jellyfin.Api.Controllers.Images [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public IActionResult GetGeneralImage([FromRoute] string name, [FromRoute] string type) { - try - { - var filename = string.Equals(type, "primary", StringComparison.OrdinalIgnoreCase) - ? "folder" - : type; - - var paths = BaseItem.SupportedImageExtensions - .Select(i => Path.Combine(_applicationPaths.GeneralPath, name, filename + i)).ToList(); + var filename = string.Equals(type, "primary", StringComparison.OrdinalIgnoreCase) + ? "folder" + : type; - var path = paths.FirstOrDefault(System.IO.File.Exists) ?? paths.FirstOrDefault(); - if (path == null || !System.IO.File.Exists(path)) - { - return NotFound(); - } + var paths = BaseItem.SupportedImageExtensions + .Select(i => Path.Combine(_applicationPaths.GeneralPath, name, filename + i)).ToList(); - var contentType = MimeTypes.GetMimeType(path); - return new FileStreamResult(System.IO.File.OpenRead(path), contentType); - } - catch (Exception e) + var path = paths.FirstOrDefault(System.IO.File.Exists) ?? paths.FirstOrDefault(); + if (path == null || !System.IO.File.Exists(path)) { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + return NotFound(); } + + var contentType = MimeTypes.GetMimeType(path); + return new FileStreamResult(System.IO.File.OpenRead(path), contentType); } /// @@ -103,14 +89,7 @@ namespace Jellyfin.Api.Controllers.Images [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public IActionResult GetRatingImages() { - try - { - return Ok(GetImageList(_applicationPaths.RatingsPath, false)); - } - catch (Exception e) - { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); - } + return Ok(GetImageList(_applicationPaths.RatingsPath, false)); } /// @@ -127,14 +106,7 @@ namespace Jellyfin.Api.Controllers.Images [FromRoute] string theme, [FromRoute] string name) { - try - { - return GetImageFile(_applicationPaths.RatingsPath, theme, name); - } - catch (Exception e) - { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); - } + return GetImageFile(_applicationPaths.RatingsPath, theme, name); } /// @@ -146,14 +118,7 @@ namespace Jellyfin.Api.Controllers.Images [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public IActionResult GetMediaInfoImages() { - try - { - return Ok(GetImageList(_applicationPaths.MediaInfoImagesPath, false)); - } - catch (Exception e) - { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); - } + return Ok(GetImageList(_applicationPaths.MediaInfoImagesPath, false)); } /// @@ -170,14 +135,7 @@ namespace Jellyfin.Api.Controllers.Images [FromRoute] string theme, [FromRoute] string name) { - try - { - return GetImageFile(_applicationPaths.MediaInfoImagesPath, theme, name); - } - catch (Exception e) - { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); - } + return GetImageFile(_applicationPaths.MediaInfoImagesPath, theme, name); } /// -- cgit v1.2.3 From a6cd8526758386045a6895b0037f2199bdcb9003 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 07:58:54 -0600 Subject: Remove exception handler --- .../Controllers/Images/RemoteImageController.cs | 184 +++++++++------------ 1 file changed, 78 insertions(+), 106 deletions(-) diff --git a/Jellyfin.Api/Controllers/Images/RemoteImageController.cs b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs index 66479582d..8c7d21cd5 100644 --- a/Jellyfin.Api/Controllers/Images/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs @@ -74,57 +74,50 @@ namespace Jellyfin.Api.Controllers.Images [FromQuery] string providerName, [FromQuery] bool includeAllLanguages) { - try + var item = _libraryManager.GetItemById(id); + if (item == null) { - var item = _libraryManager.GetItemById(id); - if (item == null) - { - return NotFound(); - } - - var images = await _providerManager.GetAvailableRemoteImages( - item, - new RemoteImageQuery - { - ProviderName = providerName, - IncludeAllLanguages = includeAllLanguages, - IncludeDisabledProviders = true, - ImageType = type - }, CancellationToken.None) - .ConfigureAwait(false); - - var imageArray = images.ToArray(); - var allProviders = _providerManager.GetRemoteImageProviderInfo(item); - if (type.HasValue) - { - allProviders = allProviders.Where(o => o.SupportedImages.Contains(type.Value)); - } + return NotFound(); + } - var result = new RemoteImageResult - { - TotalRecordCount = imageArray.Length, - Providers = allProviders.Select(o => o.Name) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToArray() - }; + var images = await _providerManager.GetAvailableRemoteImages( + item, + new RemoteImageQuery + { + ProviderName = providerName, + IncludeAllLanguages = includeAllLanguages, + IncludeDisabledProviders = true, + ImageType = type + }, CancellationToken.None) + .ConfigureAwait(false); - if (startIndex.HasValue) - { - imageArray = imageArray.Skip(startIndex.Value).ToArray(); - } + var imageArray = images.ToArray(); + var allProviders = _providerManager.GetRemoteImageProviderInfo(item); + if (type.HasValue) + { + allProviders = allProviders.Where(o => o.SupportedImages.Contains(type.Value)); + } - if (limit.HasValue) - { - imageArray = imageArray.Take(limit.Value).ToArray(); - } + var result = new RemoteImageResult + { + TotalRecordCount = imageArray.Length, + Providers = allProviders.Select(o => o.Name) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray() + }; - result.Images = imageArray; - return Ok(result); + if (startIndex.HasValue) + { + imageArray = imageArray.Skip(startIndex.Value).ToArray(); } - catch (Exception e) + + if (limit.HasValue) { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + imageArray = imageArray.Take(limit.Value).ToArray(); } + + result.Images = imageArray; + return Ok(result); } /// @@ -138,21 +131,14 @@ namespace Jellyfin.Api.Controllers.Images [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public IActionResult GetRemoteImageProviders([FromRoute] string id) { - try + var item = _libraryManager.GetItemById(id); + if (item == null) { - var item = _libraryManager.GetItemById(id); - if (item == null) - { - return NotFound(); - } - - var providers = _providerManager.GetRemoteImageProviderInfo(item); - return Ok(providers); - } - catch (Exception e) - { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + return NotFound(); } + + var providers = _providerManager.GetRemoteImageProviderInfo(item); + return Ok(providers); } /// @@ -166,49 +152,42 @@ namespace Jellyfin.Api.Controllers.Images [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public async Task GetRemoteImage([FromQuery, BindRequired] string imageUrl) { - try - { - var urlHash = imageUrl.GetMD5(); - var pointerCachePath = GetFullCachePath(urlHash.ToString()); + var urlHash = imageUrl.GetMD5(); + var pointerCachePath = GetFullCachePath(urlHash.ToString()); - string? contentPath = null; - bool hasFile = false; + string? contentPath = null; + bool hasFile = false; - try - { - contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false); - if (System.IO.File.Exists(contentPath)) - { - hasFile = true; - } - } - catch (FileNotFoundException) - { - // Means the file isn't cached yet - } - catch (IOException) - { - // Means the file isn't cached yet - } - - if (!hasFile) - { - await DownloadImage(imageUrl, urlHash, pointerCachePath).ConfigureAwait(false); - contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false); - } - - if (string.IsNullOrEmpty(contentPath)) + try + { + contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false); + if (System.IO.File.Exists(contentPath)) { - return NotFound(); + hasFile = true; } + } + catch (FileNotFoundException) + { + // Means the file isn't cached yet + } + catch (IOException) + { + // Means the file isn't cached yet + } - var contentType = MimeTypes.GetMimeType(contentPath); - return new FileStreamResult(System.IO.File.OpenRead(contentPath), contentType); + if (!hasFile) + { + await DownloadImage(imageUrl, urlHash, pointerCachePath).ConfigureAwait(false); + contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false); } - catch (Exception e) + + if (string.IsNullOrEmpty(contentPath)) { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + return NotFound(); } + + var contentType = MimeTypes.GetMimeType(contentPath); + return new FileStreamResult(System.IO.File.OpenRead(contentPath), contentType); } /// @@ -227,24 +206,17 @@ namespace Jellyfin.Api.Controllers.Images [FromQuery, BindRequired] ImageType type, [FromQuery] string imageUrl) { - try + var item = _libraryManager.GetItemById(id); + if (item == null) { - var item = _libraryManager.GetItemById(id); - if (item == null) - { - return NotFound(); - } + return NotFound(); + } - await _providerManager.SaveImage(item, imageUrl, type, null, CancellationToken.None) - .ConfigureAwait(false); + await _providerManager.SaveImage(item, imageUrl, type, null, CancellationToken.None) + .ConfigureAwait(false); - item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None); - return Ok(); - } - catch (Exception e) - { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); - } + item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None); + return Ok(); } /// -- cgit v1.2.3 From 8ab9949db5a1c0072ec35937cb96e93ce5b9d672 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 08:02:07 -0600 Subject: Remove exception handler --- .../Controllers/ScheduledTasksController.cs | 145 ++++++++------------- 1 file changed, 56 insertions(+), 89 deletions(-) diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs index 157e98519..acbc630c2 100644 --- a/Jellyfin.Api/Controllers/ScheduledTasksController.cs +++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs @@ -41,48 +41,41 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? isHidden = false, [FromQuery] bool? isEnabled = false) { - try - { - IEnumerable tasks = _taskManager.ScheduledTasks.OrderBy(o => o.Name); + IEnumerable tasks = _taskManager.ScheduledTasks.OrderBy(o => o.Name); - if (isHidden.HasValue) + if (isHidden.HasValue) + { + var hiddenValue = isHidden.Value; + tasks = tasks.Where(o => { - var hiddenValue = isHidden.Value; - tasks = tasks.Where(o => + var itemIsHidden = false; + if (o.ScheduledTask is IConfigurableScheduledTask configurableScheduledTask) { - var itemIsHidden = false; - if (o.ScheduledTask is IConfigurableScheduledTask configurableScheduledTask) - { - itemIsHidden = configurableScheduledTask.IsHidden; - } + itemIsHidden = configurableScheduledTask.IsHidden; + } - return itemIsHidden == hiddenValue; - }); - } + return itemIsHidden == hiddenValue; + }); + } - if (isEnabled.HasValue) + if (isEnabled.HasValue) + { + var enabledValue = isEnabled.Value; + tasks = tasks.Where(o => { - var enabledValue = isEnabled.Value; - tasks = tasks.Where(o => + var itemIsEnabled = false; + if (o.ScheduledTask is IConfigurableScheduledTask configurableScheduledTask) { - var itemIsEnabled = false; - if (o.ScheduledTask is IConfigurableScheduledTask configurableScheduledTask) - { - itemIsEnabled = configurableScheduledTask.IsEnabled; - } + itemIsEnabled = configurableScheduledTask.IsEnabled; + } - return itemIsEnabled == enabledValue; - }); - } + return itemIsEnabled == enabledValue; + }); + } - var taskInfos = tasks.Select(ScheduledTaskHelpers.GetTaskInfo); + var taskInfos = tasks.Select(ScheduledTaskHelpers.GetTaskInfo); - return Ok(taskInfos); - } - catch (Exception e) - { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); - } + return Ok(taskInfos); } /// @@ -96,23 +89,16 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public IActionResult GetTask([FromRoute] string taskId) { - try - { - var task = _taskManager.ScheduledTasks.FirstOrDefault(i => - string.Equals(i.Id, taskId, StringComparison.OrdinalIgnoreCase)); - - if (task == null) - { - return NotFound(); - } + var task = _taskManager.ScheduledTasks.FirstOrDefault(i => + string.Equals(i.Id, taskId, StringComparison.OrdinalIgnoreCase)); - var result = ScheduledTaskHelpers.GetTaskInfo(task); - return Ok(result); - } - catch (Exception e) + if (task == null) { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + return NotFound(); } + + var result = ScheduledTaskHelpers.GetTaskInfo(task); + return Ok(result); } /// @@ -126,23 +112,16 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public IActionResult StartTask([FromRoute] string taskId) { - try - { - var task = _taskManager.ScheduledTasks.FirstOrDefault(o => - o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); + var task = _taskManager.ScheduledTasks.FirstOrDefault(o => + o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); - if (task == null) - { - return NotFound(); - } - - _taskManager.Execute(task, new TaskOptions()); - return Ok(); - } - catch (Exception e) + if (task == null) { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + return NotFound(); } + + _taskManager.Execute(task, new TaskOptions()); + return Ok(); } /// @@ -156,23 +135,16 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public IActionResult StopTask([FromRoute] string taskId) { - try - { - var task = _taskManager.ScheduledTasks.FirstOrDefault(o => - o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); - - if (task == null) - { - return NotFound(); - } + var task = _taskManager.ScheduledTasks.FirstOrDefault(o => + o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); - _taskManager.Cancel(task); - return Ok(); - } - catch (Exception e) + if (task == null) { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + return NotFound(); } + + _taskManager.Cancel(task); + return Ok(); } /// @@ -185,24 +157,19 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult UpdateTask([FromRoute] string taskId, [FromBody, BindRequired] TaskTriggerInfo[] triggerInfos) + public IActionResult UpdateTask( + [FromRoute] string taskId, + [FromBody, BindRequired] TaskTriggerInfo[] triggerInfos) { - try + var task = _taskManager.ScheduledTasks.FirstOrDefault(o => + o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); + if (task == null) { - var task = _taskManager.ScheduledTasks.FirstOrDefault(o => - o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); - if (task == null) - { - return NotFound(); - } - - task.Triggers = triggerInfos; - return Ok(); - } - catch (Exception e) - { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + return NotFound(); } + + task.Triggers = triggerInfos; + return Ok(); } } } -- cgit v1.2.3 From fe632146dcba69edeec56b850736227ff5f4c5b3 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 08:17:13 -0600 Subject: Move Json Options to static class for easier access. --- .../Formatters/CamelCaseJsonProfileFormatter.cs | 4 +-- .../Formatters/PascalCaseJsonProfileFormatter.cs | 4 +-- Jellyfin.Server/Models/JsonOptions.cs | 41 ++++++++++++++++++++++ 3 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 Jellyfin.Server/Models/JsonOptions.cs diff --git a/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs b/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs index 433a3197d..e6ad6dfb1 100644 --- a/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs +++ b/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs @@ -1,4 +1,4 @@ -using System.Text.Json; +using Jellyfin.Server.Models; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Net.Http.Headers; @@ -12,7 +12,7 @@ namespace Jellyfin.Server.Formatters /// /// Initializes a new instance of the class. /// - public CamelCaseJsonProfileFormatter() : base(new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }) + public CamelCaseJsonProfileFormatter() : base(JsonOptions.CamelCase) { SupportedMediaTypes.Clear(); SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json;profile=\"CamelCase\"")); diff --git a/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs b/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs index 2ed006a33..675f4c79e 100644 --- a/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs +++ b/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs @@ -1,4 +1,4 @@ -using System.Text.Json; +using Jellyfin.Server.Models; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.Net.Http.Headers; @@ -12,7 +12,7 @@ namespace Jellyfin.Server.Formatters /// /// Initializes a new instance of the class. /// - public PascalCaseJsonProfileFormatter() : base(new JsonSerializerOptions { PropertyNamingPolicy = null }) + public PascalCaseJsonProfileFormatter() : base(JsonOptions.PascalCase) { SupportedMediaTypes.Clear(); // Add application/json for default formatter diff --git a/Jellyfin.Server/Models/JsonOptions.cs b/Jellyfin.Server/Models/JsonOptions.cs new file mode 100644 index 000000000..fa503bc9a --- /dev/null +++ b/Jellyfin.Server/Models/JsonOptions.cs @@ -0,0 +1,41 @@ +using System.Text.Json; + +namespace Jellyfin.Server.Models +{ + /// + /// Json Options. + /// + public static class JsonOptions + { + /// + /// Base Json Serializer Options. + /// + private static readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions(); + + /// + /// Gets CamelCase json options. + /// + public static JsonSerializerOptions CamelCase + { + get + { + var options = _jsonOptions; + options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + return options; + } + } + + /// + /// Gets PascalCase json options. + /// + public static JsonSerializerOptions PascalCase + { + get + { + var options = _jsonOptions; + options.PropertyNamingPolicy = null; + return options; + } + } + } +} -- cgit v1.2.3 From 14361c68cf71bc810d282901a764d2f8d5858eea Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 08:38:31 -0600 Subject: Add ProducesResponseType to base controller --- Jellyfin.Api/BaseJellyfinApiController.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Jellyfin.Api/BaseJellyfinApiController.cs b/Jellyfin.Api/BaseJellyfinApiController.cs index 1f4508e6c..f69175986 100644 --- a/Jellyfin.Api/BaseJellyfinApiController.cs +++ b/Jellyfin.Api/BaseJellyfinApiController.cs @@ -1,3 +1,5 @@ +using Jellyfin.Api.Models.ExceptionDtos; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace Jellyfin.Api @@ -7,6 +9,7 @@ namespace Jellyfin.Api /// [ApiController] [Route("[controller]")] + [ProducesResponseType(typeof(ExceptionDto), StatusCodes.Status500InternalServerError)] public class BaseJellyfinApiController : ControllerBase { } -- cgit v1.2.3 From b8fd9c785e107b6d2ae8125d6e6b6374f36fe9a3 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 08:42:48 -0600 Subject: Convert StartupController to IActionResult --- Jellyfin.Api/Controllers/StartupController.cs | 35 ++++++++++++++++++--------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index afc9b8f3d..b0b26c176 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -5,6 +5,7 @@ using Jellyfin.Api.Models.StartupDtos; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace Jellyfin.Api.Controllers @@ -32,12 +33,15 @@ namespace Jellyfin.Api.Controllers /// /// Api endpoint for completing the startup wizard. /// + /// Status. [HttpPost("Complete")] - public void CompleteWizard() + [ProducesResponseType(StatusCodes.Status200OK)] + public IActionResult CompleteWizard() { _config.Configuration.IsStartupWizardCompleted = true; _config.SetOptimalValues(); _config.SaveConfiguration(); + return Ok(); } /// @@ -45,7 +49,8 @@ namespace Jellyfin.Api.Controllers /// /// The initial startup wizard configuration. [HttpGet("Configuration")] - public StartupConfigurationDto GetStartupConfiguration() + [ProducesResponseType(typeof(StartupConfigurationDto), StatusCodes.Status200OK)] + public IActionResult GetStartupConfiguration() { var result = new StartupConfigurationDto { @@ -54,7 +59,7 @@ namespace Jellyfin.Api.Controllers PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage }; - return result; + return Ok(result); } /// @@ -63,8 +68,10 @@ namespace Jellyfin.Api.Controllers /// The UI language culture. /// The metadata country code. /// The preferred language for metadata. + /// Status. [HttpPost("Configuration")] - public void UpdateInitialConfiguration( + [ProducesResponseType(StatusCodes.Status200OK)] + public IActionResult UpdateInitialConfiguration( [FromForm] string uiCulture, [FromForm] string metadataCountryCode, [FromForm] string preferredMetadataLanguage) @@ -73,6 +80,7 @@ namespace Jellyfin.Api.Controllers _config.Configuration.MetadataCountryCode = metadataCountryCode; _config.Configuration.PreferredMetadataLanguage = preferredMetadataLanguage; _config.SaveConfiguration(); + return Ok(); } /// @@ -80,12 +88,15 @@ namespace Jellyfin.Api.Controllers /// /// Enable remote access. /// Enable UPnP. + /// Status. [HttpPost("RemoteAccess")] - public void SetRemoteAccess([FromForm] bool enableRemoteAccess, [FromForm] bool enableAutomaticPortMapping) + [ProducesResponseType(StatusCodes.Status200OK)] + public IActionResult SetRemoteAccess([FromForm] bool enableRemoteAccess, [FromForm] bool enableAutomaticPortMapping) { _config.Configuration.EnableRemoteAccess = enableRemoteAccess; _config.Configuration.EnableUPnP = enableAutomaticPortMapping; _config.SaveConfiguration(); + return Ok(); } /// @@ -93,14 +104,11 @@ namespace Jellyfin.Api.Controllers /// /// The first user. [HttpGet("User")] - public StartupUserDto GetFirstUser() + [ProducesResponseType(typeof(StartupUserDto), StatusCodes.Status200OK)] + public IActionResult GetFirstUser() { var user = _userManager.Users.First(); - return new StartupUserDto - { - Name = user.Name, - Password = user.Password - }; + return Ok(new StartupUserDto { Name = user.Name, Password = user.Password }); } /// @@ -109,7 +117,8 @@ namespace Jellyfin.Api.Controllers /// The DTO containing username and password. /// The async task. [HttpPost("User")] - public async Task UpdateUser([FromForm] StartupUserDto startupUserDto) + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task UpdateUser([FromForm] StartupUserDto startupUserDto) { var user = _userManager.Users.First(); @@ -121,6 +130,8 @@ namespace Jellyfin.Api.Controllers { await _userManager.ChangePassword(user, startupUserDto.Password).ConfigureAwait(false); } + + return Ok(); } } } -- cgit v1.2.3 From 3ef8448a518e673feae0c70c2682d60e4632c0cd Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 09:09:05 -0600 Subject: Return to previous exception handle implementation --- Jellyfin.Api/BaseJellyfinApiController.cs | 3 - Jellyfin.Api/Models/ExceptionDtos/ExceptionDto.cs | 14 ---- Jellyfin.Server/Middleware/ExceptionMiddleware.cs | 86 +++++++++++++++++++---- 3 files changed, 73 insertions(+), 30 deletions(-) delete mode 100644 Jellyfin.Api/Models/ExceptionDtos/ExceptionDto.cs diff --git a/Jellyfin.Api/BaseJellyfinApiController.cs b/Jellyfin.Api/BaseJellyfinApiController.cs index f69175986..1f4508e6c 100644 --- a/Jellyfin.Api/BaseJellyfinApiController.cs +++ b/Jellyfin.Api/BaseJellyfinApiController.cs @@ -1,5 +1,3 @@ -using Jellyfin.Api.Models.ExceptionDtos; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace Jellyfin.Api @@ -9,7 +7,6 @@ namespace Jellyfin.Api /// [ApiController] [Route("[controller]")] - [ProducesResponseType(typeof(ExceptionDto), StatusCodes.Status500InternalServerError)] public class BaseJellyfinApiController : ControllerBase { } diff --git a/Jellyfin.Api/Models/ExceptionDtos/ExceptionDto.cs b/Jellyfin.Api/Models/ExceptionDtos/ExceptionDto.cs deleted file mode 100644 index d2b48d4ae..000000000 --- a/Jellyfin.Api/Models/ExceptionDtos/ExceptionDto.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Jellyfin.Api.Models.ExceptionDtos -{ - /// - /// Exception Dto. - /// Used for graceful handling of API exceptions. - /// - public class ExceptionDto - { - /// - /// Gets or sets exception message. - /// - public string Message { get; set; } - } -} diff --git a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs index 39aace95d..0d9dac89f 100644 --- a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs +++ b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs @@ -1,7 +1,9 @@ using System; -using System.Text.Json; +using System.IO; using System.Threading.Tasks; -using Jellyfin.Api.Models.ExceptionDtos; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Net; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -14,17 +16,22 @@ namespace Jellyfin.Server.Middleware { private readonly RequestDelegate _next; private readonly ILogger _logger; + private readonly IServerConfigurationManager _configuration; /// /// Initializes a new instance of the class. /// /// Next request delegate. /// Instance of the interface. - public ExceptionMiddleware(RequestDelegate next, ILoggerFactory loggerFactory) + /// Instance of the interface. + public ExceptionMiddleware( + RequestDelegate next, + ILoggerFactory loggerFactory, + IServerConfigurationManager serverConfigurationManager) { - _next = next ?? throw new ArgumentNullException(nameof(next)); - _logger = loggerFactory.CreateLogger() ?? - throw new ArgumentNullException(nameof(loggerFactory)); + _next = next; + _logger = loggerFactory.CreateLogger(); + _configuration = serverConfigurationManager; } /// @@ -46,15 +53,68 @@ namespace Jellyfin.Server.Middleware throw; } - var exceptionBody = new ExceptionDto { Message = ex.Message }; - var exceptionJson = JsonSerializer.Serialize(exceptionBody); + ex = GetActualException(ex); + _logger.LogError(ex, "Error processing request: {0}", ex.Message); + context.Response.StatusCode = GetStatusCode(ex); + context.Response.ContentType = "text/plain"; - context.Response.Clear(); - context.Response.StatusCode = StatusCodes.Status500InternalServerError; - // TODO switch between PascalCase and camelCase - context.Response.ContentType = "application/json"; - await context.Response.WriteAsync(exceptionJson).ConfigureAwait(false); + var errorContent = NormalizeExceptionMessage(ex.Message); + await context.Response.WriteAsync(errorContent).ConfigureAwait(false); } } + + private static Exception GetActualException(Exception ex) + { + if (ex is AggregateException agg) + { + var inner = agg.InnerException; + if (inner != null) + { + return GetActualException(inner); + } + + var inners = agg.InnerExceptions; + if (inners.Count > 0) + { + return GetActualException(inners[0]); + } + } + + return ex; + } + + private static int GetStatusCode(Exception ex) + { + switch (ex) + { + case ArgumentException _: return StatusCodes.Status400BadRequest; + case SecurityException _: return StatusCodes.Status401Unauthorized; + case DirectoryNotFoundException _: + case FileNotFoundException _: + case ResourceNotFoundException _: return StatusCodes.Status404NotFound; + case MethodNotAllowedException _: return StatusCodes.Status405MethodNotAllowed; + default: return StatusCodes.Status500InternalServerError; + } + } + + private string NormalizeExceptionMessage(string msg) + { + if (msg == null) + { + return string.Empty; + } + + // Strip any information we don't want to reveal + msg = msg.Replace( + _configuration.ApplicationPaths.ProgramSystemPath, + string.Empty, + StringComparison.OrdinalIgnoreCase); + msg = msg.Replace( + _configuration.ApplicationPaths.ProgramDataPath, + string.Empty, + StringComparison.OrdinalIgnoreCase); + + return msg; + } } } -- cgit v1.2.3 From 69d9bfb233bd2716e3803b38c55275de58bb8d46 Mon Sep 17 00:00:00 2001 From: ZadenRB Date: Tue, 21 Apr 2020 12:10:34 -0600 Subject: Make documentation of parameters clearer Co-Authored-By: Vasily --- Jellyfin.Api/Controllers/NotificationsController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs index c1d9e3251..6145246ed 100644 --- a/Jellyfin.Api/Controllers/NotificationsController.cs +++ b/Jellyfin.Api/Controllers/NotificationsController.cs @@ -38,7 +38,7 @@ namespace Jellyfin.Api.Controllers /// Endpoint for getting a user's notifications. /// /// The user's ID. - /// An optional filter by IsRead. + /// An optional filter by notification read state. /// The optional index to start at. All notifications with a lower index will be dropped from the results. /// An optional limit on the number of notifications returned. /// A read-only list of all of the user's notifications. -- cgit v1.2.3 From 466e20ea8cb8c262605d06dc01eff4463559d9b0 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 13:57:11 -0600 Subject: move to ActionResult --- Jellyfin.Api/Controllers/AttachmentsController.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Api/Controllers/AttachmentsController.cs b/Jellyfin.Api/Controllers/AttachmentsController.cs index aeeaf5cbd..351401de1 100644 --- a/Jellyfin.Api/Controllers/AttachmentsController.cs +++ b/Jellyfin.Api/Controllers/AttachmentsController.cs @@ -44,10 +44,9 @@ namespace Jellyfin.Api.Controllers /// Attachment. [HttpGet("{VideoID}/{MediaSourceID}/Attachments/{Index}")] [Produces("application/octet-stream")] - [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public async Task GetAttachment( + public async Task> GetAttachment( [FromRoute] Guid videoId, [FromRoute] string mediaSourceId, [FromRoute] int index) -- cgit v1.2.3 From 927696c4036960018864864a4acbf0aeb797f7ac Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 13:59:43 -0600 Subject: move to ActionResult --- Jellyfin.Api/Controllers/DevicesController.cs | 29 ++++++++++----------------- 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs index 5dc3f27ee..559a26007 100644 --- a/Jellyfin.Api/Controllers/DevicesController.cs +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -49,9 +49,8 @@ namespace Jellyfin.Api.Controllers /// Device Infos. [HttpGet] [Authenticated(Roles = "Admin")] - [ProducesResponseType(typeof(DeviceInfo[]), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId) + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId) { var deviceQuery = new DeviceQuery { SupportsSync = supportsSync, UserId = userId ?? Guid.Empty }; var devices = _deviceManager.GetDevices(deviceQuery); @@ -65,10 +64,9 @@ namespace Jellyfin.Api.Controllers /// Device Info. [HttpGet("Info")] [Authenticated(Roles = "Admin")] - [ProducesResponseType(typeof(DeviceInfo), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult GetDeviceInfo([FromQuery, BindRequired] string id) + public ActionResult GetDeviceInfo([FromQuery, BindRequired] string id) { var deviceInfo = _deviceManager.GetDevice(id); if (deviceInfo == null) @@ -86,10 +84,9 @@ namespace Jellyfin.Api.Controllers /// Device Info. [HttpGet("Options")] [Authenticated(Roles = "Admin")] - [ProducesResponseType(typeof(DeviceOptions), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult GetDeviceOptions([FromQuery, BindRequired] string id) + public ActionResult GetDeviceOptions([FromQuery, BindRequired] string id) { var deviceInfo = _deviceManager.GetDeviceOptions(id); if (deviceInfo == null) @@ -110,8 +107,7 @@ namespace Jellyfin.Api.Controllers [Authenticated(Roles = "Admin")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult UpdateDeviceOptions( + public ActionResult UpdateDeviceOptions( [FromQuery, BindRequired] string id, [FromBody, BindRequired] DeviceOptions deviceOptions) { @@ -132,8 +128,7 @@ namespace Jellyfin.Api.Controllers /// Status. [HttpDelete] [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult DeleteDevice([FromQuery, BindRequired] string id) + public ActionResult DeleteDevice([FromQuery, BindRequired] string id) { var sessions = _authenticationRepository.Get(new AuthenticationInfoQuery { DeviceId = id }).Items; @@ -151,9 +146,8 @@ namespace Jellyfin.Api.Controllers /// Device Id. /// Content Upload History. [HttpGet("CameraUploads")] - [ProducesResponseType(typeof(ContentUploadHistory), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult GetCameraUploads([FromQuery, BindRequired] string id) + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetCameraUploads([FromQuery, BindRequired] string id) { var uploadHistory = _deviceManager.GetCameraUploadHistory(id); return Ok(uploadHistory); @@ -170,8 +164,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("CameraUploads")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public async Task PostCameraUploadAsync( + public async Task PostCameraUploadAsync( [FromQuery, BindRequired] string deviceId, [FromQuery, BindRequired] string album, [FromQuery, BindRequired] string name, -- cgit v1.2.3 From 98224dee9e3bfc2bb30c14792aec4bda47670863 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 14:01:47 -0600 Subject: move to ActionResult --- Jellyfin.Api/Controllers/DisplayPreferencesController.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs index e15e9c4be..25391bcf8 100644 --- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs +++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs @@ -36,10 +36,9 @@ namespace Jellyfin.Api.Controllers /// Client. /// Display Preferences. [HttpGet("{DisplayPreferencesId}")] - [ProducesResponseType(typeof(DisplayPreferences), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] - public IActionResult GetDisplayPreferences( + public ActionResult GetDisplayPreferences( [FromRoute] string displayPreferencesId, [FromQuery] [Required] string userId, [FromQuery] [Required] string client) @@ -65,8 +64,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ModelStateDictionary), StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult UpdateDisplayPreferences( + public ActionResult UpdateDisplayPreferences( [FromRoute] string displayPreferencesId, [FromQuery, BindRequired] string userId, [FromQuery, BindRequired] string client, -- cgit v1.2.3 From 02a78aaae98bdecacd04325e124bde9224c66955 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 14:07:11 -0600 Subject: move to ActionResult --- .../Controllers/Images/ImageByNameController.cs | 35 ++++++++++------------ 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/Jellyfin.Api/Controllers/Images/ImageByNameController.cs b/Jellyfin.Api/Controllers/Images/ImageByNameController.cs index 4034c9e85..ce509b4e6 100644 --- a/Jellyfin.Api/Controllers/Images/ImageByNameController.cs +++ b/Jellyfin.Api/Controllers/Images/ImageByNameController.cs @@ -44,9 +44,8 @@ namespace Jellyfin.Api.Controllers.Images /// /// General images. [HttpGet("General")] - [ProducesResponseType(typeof(ImageByNameInfo[]), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult GetGeneralImages() + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetGeneralImages() { return Ok(GetImageList(_applicationPaths.GeneralPath, false)); } @@ -58,10 +57,10 @@ namespace Jellyfin.Api.Controllers.Images /// Image Type (primary, backdrop, logo, etc). /// Image Stream. [HttpGet("General/{Name}/{Type}")] - [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [Produces("application/octet-stream")] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult GetGeneralImage([FromRoute] string name, [FromRoute] string type) + public ActionResult GetGeneralImage([FromRoute] string name, [FromRoute] string type) { var filename = string.Equals(type, "primary", StringComparison.OrdinalIgnoreCase) ? "folder" @@ -85,9 +84,8 @@ namespace Jellyfin.Api.Controllers.Images /// /// General images. [HttpGet("Ratings")] - [ProducesResponseType(typeof(ImageByNameInfo[]), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult GetRatingImages() + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetRatingImages() { return Ok(GetImageList(_applicationPaths.RatingsPath, false)); } @@ -99,10 +97,10 @@ namespace Jellyfin.Api.Controllers.Images /// The name of the image. /// Image Stream. [HttpGet("Ratings/{Theme}/{Name}")] - [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [Produces("application/octet-stream")] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult GetRatingImage( + public ActionResult GetRatingImage( [FromRoute] string theme, [FromRoute] string name) { @@ -114,9 +112,8 @@ namespace Jellyfin.Api.Controllers.Images /// /// Media Info images. [HttpGet("MediaInfo")] - [ProducesResponseType(typeof(ImageByNameInfo[]), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult GetMediaInfoImages() + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetMediaInfoImages() { return Ok(GetImageList(_applicationPaths.MediaInfoImagesPath, false)); } @@ -128,10 +125,10 @@ namespace Jellyfin.Api.Controllers.Images /// The name of the image. /// Image Stream. [HttpGet("MediaInfo/{Theme}/{Name}")] - [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [Produces("application/octet-stream")] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult GetMediaInfoImage( + public ActionResult GetMediaInfoImage( [FromRoute] string theme, [FromRoute] string name) { @@ -145,7 +142,7 @@ namespace Jellyfin.Api.Controllers.Images /// Theme to search. /// File name to search for. /// Image Stream. - private IActionResult GetImageFile(string basePath, string theme, string name) + private ActionResult GetImageFile(string basePath, string theme, string name) { var themeFolder = Path.Combine(basePath, theme); if (Directory.Exists(themeFolder)) -- cgit v1.2.3 From 9ae895ba213a508f676d21e5425b25bb518ed89a Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 14:09:06 -0600 Subject: move to ActionResult --- .../Controllers/Images/RemoteImageController.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/Jellyfin.Api/Controllers/Images/RemoteImageController.cs b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs index 8c7d21cd5..a0754ed4e 100644 --- a/Jellyfin.Api/Controllers/Images/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs @@ -63,10 +63,9 @@ namespace Jellyfin.Api.Controllers.Images /// Optinal. Include all languages. /// Remote Image Result. [HttpGet("{Id}/RemoteImages")] - [ProducesResponseType(typeof(RemoteImageResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status404NotFound)] - public async Task GetRemoteImages( + public async Task> GetRemoteImages( [FromRoute] string id, [FromQuery] ImageType? type, [FromQuery] int? startIndex, @@ -126,10 +125,9 @@ namespace Jellyfin.Api.Controllers.Images /// Item Id. /// List of providers. [HttpGet("{Id}/RemoteImages/Providers")] - [ProducesResponseType(typeof(ImageProviderInfo[]), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult GetRemoteImageProviders([FromRoute] string id) + public ActionResult GetRemoteImageProviders([FromRoute] string id) { var item = _libraryManager.GetItemById(id); if (item == null) @@ -147,10 +145,10 @@ namespace Jellyfin.Api.Controllers.Images /// The image url. /// Image Stream. [HttpGet("Remote")] - [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [Produces("application/octet-stream")] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public async Task GetRemoteImage([FromQuery, BindRequired] string imageUrl) + public async Task> GetRemoteImage([FromQuery, BindRequired] string imageUrl) { var urlHash = imageUrl.GetMD5(); var pointerCachePath = GetFullCachePath(urlHash.ToString()); @@ -200,8 +198,7 @@ namespace Jellyfin.Api.Controllers.Images [HttpPost("{Id}/RemoteImages/Download")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public async Task DownloadRemoteImage( + public async Task DownloadRemoteImage( [FromRoute] string id, [FromQuery, BindRequired] ImageType type, [FromQuery] string imageUrl) -- cgit v1.2.3 From 88b856796a9e4852ae4f9938baddd4741e8285d5 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 14:23:08 -0600 Subject: move to ActionResult --- .../Controllers/ScheduledTasksController.cs | 53 +++++++--------------- 1 file changed, 16 insertions(+), 37 deletions(-) diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs index acbc630c2..da7cfbc3a 100644 --- a/Jellyfin.Api/Controllers/ScheduledTasksController.cs +++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs @@ -14,7 +14,7 @@ namespace Jellyfin.Api.Controllers /// /// Scheduled Tasks Controller. /// - [Authenticated] + // [Authenticated] public class ScheduledTasksController : BaseJellyfinApiController { private readonly ITaskManager _taskManager; @@ -35,47 +35,30 @@ namespace Jellyfin.Api.Controllers /// Optional filter tasks that are enabled, or not. /// Task list. [HttpGet] - [ProducesResponseType(typeof(TaskInfo[]), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult GetTasks( + [ProducesResponseType(StatusCodes.Status200OK)] + public IEnumerable GetTasks( [FromQuery] bool? isHidden = false, [FromQuery] bool? isEnabled = false) { IEnumerable tasks = _taskManager.ScheduledTasks.OrderBy(o => o.Name); - if (isHidden.HasValue) + foreach (var task in tasks) { - var hiddenValue = isHidden.Value; - tasks = tasks.Where(o => + if (task.ScheduledTask is IConfigurableScheduledTask scheduledTask) { - var itemIsHidden = false; - if (o.ScheduledTask is IConfigurableScheduledTask configurableScheduledTask) + if (isHidden.HasValue && isHidden.Value != scheduledTask.IsHidden) { - itemIsHidden = configurableScheduledTask.IsHidden; + continue; } - return itemIsHidden == hiddenValue; - }); - } - - if (isEnabled.HasValue) - { - var enabledValue = isEnabled.Value; - tasks = tasks.Where(o => - { - var itemIsEnabled = false; - if (o.ScheduledTask is IConfigurableScheduledTask configurableScheduledTask) + if (isEnabled.HasValue && isEnabled.Value != scheduledTask.IsEnabled) { - itemIsEnabled = configurableScheduledTask.IsEnabled; + continue; } + } - return itemIsEnabled == enabledValue; - }); + yield return task; } - - var taskInfos = tasks.Select(ScheduledTaskHelpers.GetTaskInfo); - - return Ok(taskInfos); } /// @@ -84,10 +67,9 @@ namespace Jellyfin.Api.Controllers /// Task Id. /// Task Info. [HttpGet("{TaskID}")] - [ProducesResponseType(typeof(TaskInfo), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult GetTask([FromRoute] string taskId) + public ActionResult GetTask([FromRoute] string taskId) { var task = _taskManager.ScheduledTasks.FirstOrDefault(i => string.Equals(i.Id, taskId, StringComparison.OrdinalIgnoreCase)); @@ -109,8 +91,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("Running/{TaskID}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult StartTask([FromRoute] string taskId) + public ActionResult StartTask([FromRoute] string taskId) { var task = _taskManager.ScheduledTasks.FirstOrDefault(o => o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); @@ -132,8 +113,7 @@ namespace Jellyfin.Api.Controllers [HttpDelete("Running/{TaskID}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult StopTask([FromRoute] string taskId) + public ActionResult StopTask([FromRoute] string taskId) { var task = _taskManager.ScheduledTasks.FirstOrDefault(o => o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase)); @@ -156,8 +136,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("{TaskID}/Triggers")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult UpdateTask( + public ActionResult UpdateTask( [FromRoute] string taskId, [FromBody, BindRequired] TaskTriggerInfo[] triggerInfos) { -- cgit v1.2.3 From 7db3b035a6d1f7e6f4886c4497b98b7a6af6c679 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 14:25:03 -0600 Subject: move to ActionResult --- Jellyfin.Api/Controllers/StartupController.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index b0b26c176..2db7e32aa 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -36,7 +36,7 @@ namespace Jellyfin.Api.Controllers /// Status. [HttpPost("Complete")] [ProducesResponseType(StatusCodes.Status200OK)] - public IActionResult CompleteWizard() + public ActionResult CompleteWizard() { _config.Configuration.IsStartupWizardCompleted = true; _config.SetOptimalValues(); @@ -49,8 +49,8 @@ namespace Jellyfin.Api.Controllers /// /// The initial startup wizard configuration. [HttpGet("Configuration")] - [ProducesResponseType(typeof(StartupConfigurationDto), StatusCodes.Status200OK)] - public IActionResult GetStartupConfiguration() + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetStartupConfiguration() { var result = new StartupConfigurationDto { @@ -71,7 +71,7 @@ namespace Jellyfin.Api.Controllers /// Status. [HttpPost("Configuration")] [ProducesResponseType(StatusCodes.Status200OK)] - public IActionResult UpdateInitialConfiguration( + public ActionResult UpdateInitialConfiguration( [FromForm] string uiCulture, [FromForm] string metadataCountryCode, [FromForm] string preferredMetadataLanguage) @@ -91,7 +91,7 @@ namespace Jellyfin.Api.Controllers /// Status. [HttpPost("RemoteAccess")] [ProducesResponseType(StatusCodes.Status200OK)] - public IActionResult SetRemoteAccess([FromForm] bool enableRemoteAccess, [FromForm] bool enableAutomaticPortMapping) + public ActionResult SetRemoteAccess([FromForm] bool enableRemoteAccess, [FromForm] bool enableAutomaticPortMapping) { _config.Configuration.EnableRemoteAccess = enableRemoteAccess; _config.Configuration.EnableUPnP = enableAutomaticPortMapping; @@ -104,8 +104,8 @@ namespace Jellyfin.Api.Controllers /// /// The first user. [HttpGet("User")] - [ProducesResponseType(typeof(StartupUserDto), StatusCodes.Status200OK)] - public IActionResult GetFirstUser() + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetFirstUser() { var user = _userManager.Users.First(); return Ok(new StartupUserDto { Name = user.Name, Password = user.Password }); @@ -118,7 +118,7 @@ namespace Jellyfin.Api.Controllers /// The async task. [HttpPost("User")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task UpdateUser([FromForm] StartupUserDto startupUserDto) + public async Task UpdateUser([FromForm] StartupUserDto startupUserDto) { var user = _userManager.Users.First(); -- cgit v1.2.3 From 3ab61dbdc252670abf28797d3183614b1cd05ece Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 15:49:04 -0600 Subject: bump swashbuckle --- Jellyfin.Api/Jellyfin.Api.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index cbb1d3007..77bb52c6a 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -10,8 +10,8 @@ - - + + -- cgit v1.2.3 From 2542a27bd5f79ccfbc2547ddd877ddb0423ae296 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 16:15:31 -0600 Subject: Fix documentation endpoint for use with baseUrl --- .../Extensions/ApiApplicationBuilderExtensions.cs | 28 ++++++++++++++++------ Jellyfin.Server/Startup.cs | 2 +- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs index 43c49307d..df3bab931 100644 --- a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs @@ -1,3 +1,4 @@ +using MediaBrowser.Controller.Configuration; using Microsoft.AspNetCore.Builder; namespace Jellyfin.Server.Extensions @@ -11,23 +12,36 @@ namespace Jellyfin.Server.Extensions /// Adds swagger and swagger UI to the application pipeline. /// /// The application builder. + /// The server configuration. /// The updated application builder. - public static IApplicationBuilder UseJellyfinApiSwagger(this IApplicationBuilder applicationBuilder) + public static IApplicationBuilder UseJellyfinApiSwagger( + this IApplicationBuilder applicationBuilder, + IServerConfigurationManager serverConfigurationManager) { // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), // specifying the Swagger JSON endpoint. - const string specEndpoint = "/swagger/v1/swagger.json"; + + var baseUrl = serverConfigurationManager.Configuration.BaseUrl.Trim('/'); + if (!string.IsNullOrEmpty(baseUrl)) + { + baseUrl += '/'; + } + return applicationBuilder - .UseSwagger() + .UseSwagger(c => + { + c.RouteTemplate = $"/{baseUrl}api-docs/{{documentName}}/openapi.json"; + }) .UseSwaggerUI(c => { - c.SwaggerEndpoint(specEndpoint, "Jellyfin API V1"); - c.RoutePrefix = "api-docs/swagger"; + c.SwaggerEndpoint($"/{baseUrl}api-docs/v1/openapi.json", "Jellyfin API v1"); + c.RoutePrefix = $"{baseUrl}api-docs/v1/swagger"; }) .UseReDoc(c => { - c.SpecUrl(specEndpoint); - c.RoutePrefix = "api-docs/redoc"; + c.DocumentTitle = "Jellyfin API v1"; + c.SpecUrl($"/{baseUrl}api-docs/v1/openapi.json"); + c.RoutePrefix = $"{baseUrl}api-docs/v1/redoc"; }); } } diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs index 4d7d56e9d..ee08d2580 100644 --- a/Jellyfin.Server/Startup.cs +++ b/Jellyfin.Server/Startup.cs @@ -66,7 +66,7 @@ namespace Jellyfin.Server app.Use(serverApplicationHost.ExecuteWebsocketHandlerAsync); // TODO use when old API is removed: app.UseAuthentication(); - app.UseJellyfinApiSwagger(); + app.UseJellyfinApiSwagger(_serverConfigurationManager); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => -- cgit v1.2.3 From 041d674eb6e4a675b68406ed5c2d7018d61e870a Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 16:19:26 -0600 Subject: Fix swagger ui Document Title --- Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs index df3bab931..d09424225 100644 --- a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs @@ -34,6 +34,7 @@ namespace Jellyfin.Server.Extensions }) .UseSwaggerUI(c => { + c.DocumentTitle = "Jellyfin API v1"; c.SwaggerEndpoint($"/{baseUrl}api-docs/v1/openapi.json", "Jellyfin API v1"); c.RoutePrefix = $"{baseUrl}api-docs/v1/swagger"; }) -- cgit v1.2.3 From f5385e4735849cbb1552e69faa0116e5498b3688 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 18:12:46 -0600 Subject: Move Emby.Dlna DlnaService.cs to Jellyfin.Api --- Emby.Dlna/Api/DlnaService.cs | 88 -------------------- Jellyfin.Api/Controllers/DlnaController.cs | 124 +++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 88 deletions(-) delete mode 100644 Emby.Dlna/Api/DlnaService.cs create mode 100644 Jellyfin.Api/Controllers/DlnaController.cs diff --git a/Emby.Dlna/Api/DlnaService.cs b/Emby.Dlna/Api/DlnaService.cs deleted file mode 100644 index 5f984bb33..000000000 --- a/Emby.Dlna/Api/DlnaService.cs +++ /dev/null @@ -1,88 +0,0 @@ -#pragma warning disable CS1591 - -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Services; - -namespace Emby.Dlna.Api -{ - [Route("/Dlna/ProfileInfos", "GET", Summary = "Gets a list of profiles")] - public class GetProfileInfos : IReturn - { - } - - [Route("/Dlna/Profiles/{Id}", "DELETE", Summary = "Deletes a profile")] - public class DeleteProfile : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Profile Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - } - - [Route("/Dlna/Profiles/Default", "GET", Summary = "Gets the default profile")] - public class GetDefaultProfile : IReturn - { - } - - [Route("/Dlna/Profiles/{Id}", "GET", Summary = "Gets a single profile")] - public class GetProfile : IReturn - { - [ApiMember(Name = "Id", Description = "Profile Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Dlna/Profiles/{Id}", "POST", Summary = "Updates a profile")] - public class UpdateProfile : DeviceProfile, IReturnVoid - { - } - - [Route("/Dlna/Profiles", "POST", Summary = "Creates a profile")] - public class CreateProfile : DeviceProfile, IReturnVoid - { - } - - [Authenticated(Roles = "Admin")] - public class DlnaService : IService - { - private readonly IDlnaManager _dlnaManager; - - public DlnaService(IDlnaManager dlnaManager) - { - _dlnaManager = dlnaManager; - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Get(GetProfileInfos request) - { - return _dlnaManager.GetProfileInfos().ToArray(); - } - - public object Get(GetProfile request) - { - return _dlnaManager.GetProfile(request.Id); - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Get(GetDefaultProfile request) - { - return _dlnaManager.GetDefaultProfile(); - } - - public void Delete(DeleteProfile request) - { - _dlnaManager.DeleteProfile(request.Id); - } - - public void Post(UpdateProfile request) - { - _dlnaManager.UpdateProfile(request); - } - - public void Post(CreateProfile request) - { - _dlnaManager.CreateProfile(request); - } - } -} diff --git a/Jellyfin.Api/Controllers/DlnaController.cs b/Jellyfin.Api/Controllers/DlnaController.cs new file mode 100644 index 000000000..68cd144f4 --- /dev/null +++ b/Jellyfin.Api/Controllers/DlnaController.cs @@ -0,0 +1,124 @@ +#nullable enable + +using System.Collections.Generic; +using MediaBrowser.Controller.Dlna; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Dlna; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers +{ + /// + /// Dlna Controller. + /// + [Authenticated(Roles = "Admin")] + public class DlnaController : BaseJellyfinApiController + { + private readonly IDlnaManager _dlnaManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + public DlnaController(IDlnaManager dlnaManager) + { + _dlnaManager = dlnaManager; + } + + /// + /// Get profile infos. + /// + /// Profile infos. + [HttpGet("ProfileInfos")] + [ProducesResponseType(StatusCodes.Status200OK)] + public IEnumerable GetProfileInfos() + { + return _dlnaManager.GetProfileInfos(); + } + + /// + /// Gets the default profile. + /// + /// Default profile. + [HttpGet("Profiles/Default")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetDefaultProfile() + { + return Ok(_dlnaManager.GetDefaultProfile()); + } + + /// + /// Gets a single profile. + /// + /// Profile Id. + /// Profile. + [HttpGet("Profiles/{Id}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult GetProfile([FromRoute] string id) + { + var profile = _dlnaManager.GetProfile(id); + if (profile == null) + { + return NotFound(); + } + + return Ok(profile); + } + + /// + /// Deletes a profile. + /// + /// Profile id. + /// Status. + [HttpDelete("Profiles/{Id}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult DeleteProfile([FromRoute] string id) + { + var existingDeviceProfile = _dlnaManager.GetProfile(id); + if (existingDeviceProfile == null) + { + return NotFound(); + } + + _dlnaManager.DeleteProfile(id); + return Ok(); + } + + /// + /// Creates a profile. + /// + /// Device profile. + /// Status. + [HttpPost("Profiles")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult CreateProfile([FromBody] DeviceProfile deviceProfile) + { + _dlnaManager.CreateProfile(deviceProfile); + return Ok(); + } + + /// + /// Updates a profile. + /// + /// Profile id. + /// Device profile. + /// Status. + [HttpPost("Profiles/{Id}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult UpdateProfile([FromRoute] string id, [FromBody] DeviceProfile deviceProfile) + { + var existingDeviceProfile = _dlnaManager.GetProfile(id); + if (existingDeviceProfile == null) + { + return NotFound(); + } + + _dlnaManager.UpdateProfile(deviceProfile); + return Ok(); + } + } +} -- cgit v1.2.3 From 461b298be7247afd7f7962604efab3b58b9dae4b Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 19:15:27 -0600 Subject: Migrate DlnaServerController to Jellyfin.Api --- Emby.Dlna/Api/DlnaServerService.cs | 383 --------------------- Emby.Dlna/Main/DlnaEntryPoint.cs | 6 +- Jellyfin.Api/Attributes/HttpSubscribeAttribute.cs | 35 ++ .../Attributes/HttpUnsubscribeAttribute.cs | 35 ++ Jellyfin.Api/Controllers/DlnaServerController.cs | 259 ++++++++++++++ Jellyfin.Api/Jellyfin.Api.csproj | 1 + 6 files changed, 333 insertions(+), 386 deletions(-) delete mode 100644 Emby.Dlna/Api/DlnaServerService.cs create mode 100644 Jellyfin.Api/Attributes/HttpSubscribeAttribute.cs create mode 100644 Jellyfin.Api/Attributes/HttpUnsubscribeAttribute.cs create mode 100644 Jellyfin.Api/Controllers/DlnaServerController.cs diff --git a/Emby.Dlna/Api/DlnaServerService.cs b/Emby.Dlna/Api/DlnaServerService.cs deleted file mode 100644 index 7fba2184a..000000000 --- a/Emby.Dlna/Api/DlnaServerService.cs +++ /dev/null @@ -1,383 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using Emby.Dlna.Main; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Services; - -namespace Emby.Dlna.Api -{ - [Route("/Dlna/{UuId}/description.xml", "GET", Summary = "Gets dlna server info")] - [Route("/Dlna/{UuId}/description", "GET", Summary = "Gets dlna server info")] - public class GetDescriptionXml - { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UuId { get; set; } - } - - [Route("/Dlna/{UuId}/contentdirectory/contentdirectory.xml", "GET", Summary = "Gets dlna content directory xml")] - [Route("/Dlna/{UuId}/contentdirectory/contentdirectory", "GET", Summary = "Gets dlna content directory xml")] - public class GetContentDirectory - { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UuId { get; set; } - } - - [Route("/Dlna/{UuId}/connectionmanager/connectionmanager.xml", "GET", Summary = "Gets dlna connection manager xml")] - [Route("/Dlna/{UuId}/connectionmanager/connectionmanager", "GET", Summary = "Gets dlna connection manager xml")] - public class GetConnnectionManager - { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UuId { get; set; } - } - - [Route("/Dlna/{UuId}/mediareceiverregistrar/mediareceiverregistrar.xml", "GET", Summary = "Gets dlna mediareceiverregistrar xml")] - [Route("/Dlna/{UuId}/mediareceiverregistrar/mediareceiverregistrar", "GET", Summary = "Gets dlna mediareceiverregistrar xml")] - public class GetMediaReceiverRegistrar - { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UuId { get; set; } - } - - [Route("/Dlna/{UuId}/contentdirectory/control", "POST", Summary = "Processes a control request")] - public class ProcessContentDirectoryControlRequest : IRequiresRequestStream - { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UuId { get; set; } - - public Stream RequestStream { get; set; } - } - - [Route("/Dlna/{UuId}/connectionmanager/control", "POST", Summary = "Processes a control request")] - public class ProcessConnectionManagerControlRequest : IRequiresRequestStream - { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UuId { get; set; } - - public Stream RequestStream { get; set; } - } - - [Route("/Dlna/{UuId}/mediareceiverregistrar/control", "POST", Summary = "Processes a control request")] - public class ProcessMediaReceiverRegistrarControlRequest : IRequiresRequestStream - { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UuId { get; set; } - - public Stream RequestStream { get; set; } - } - - [Route("/Dlna/{UuId}/mediareceiverregistrar/events", "SUBSCRIBE", Summary = "Processes an event subscription request")] - [Route("/Dlna/{UuId}/mediareceiverregistrar/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")] - public class ProcessMediaReceiverRegistrarEventRequest - { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")] - public string UuId { get; set; } - } - - [Route("/Dlna/{UuId}/contentdirectory/events", "SUBSCRIBE", Summary = "Processes an event subscription request")] - [Route("/Dlna/{UuId}/contentdirectory/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")] - public class ProcessContentDirectoryEventRequest - { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")] - public string UuId { get; set; } - } - - [Route("/Dlna/{UuId}/connectionmanager/events", "SUBSCRIBE", Summary = "Processes an event subscription request")] - [Route("/Dlna/{UuId}/connectionmanager/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")] - public class ProcessConnectionManagerEventRequest - { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")] - public string UuId { get; set; } - } - - [Route("/Dlna/{UuId}/icons/{Filename}", "GET", Summary = "Gets a server icon")] - [Route("/Dlna/icons/{Filename}", "GET", Summary = "Gets a server icon")] - public class GetIcon - { - [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UuId { get; set; } - - [ApiMember(Name = "Filename", Description = "The icon filename", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Filename { get; set; } - } - - public class DlnaServerService : IService, IRequiresRequest - { - private const string XMLContentType = "text/xml; charset=UTF-8"; - - private readonly IDlnaManager _dlnaManager; - private readonly IHttpResultFactory _resultFactory; - private readonly IServerConfigurationManager _configurationManager; - - public IRequest Request { get; set; } - - private IContentDirectory ContentDirectory => DlnaEntryPoint.Current.ContentDirectory; - - private IConnectionManager ConnectionManager => DlnaEntryPoint.Current.ConnectionManager; - - private IMediaReceiverRegistrar MediaReceiverRegistrar => DlnaEntryPoint.Current.MediaReceiverRegistrar; - - public DlnaServerService( - IDlnaManager dlnaManager, - IHttpResultFactory httpResultFactory, - IServerConfigurationManager configurationManager) - { - _dlnaManager = dlnaManager; - _resultFactory = httpResultFactory; - _configurationManager = configurationManager; - } - - private string GetHeader(string name) - { - return Request.Headers[name]; - } - - public object Get(GetDescriptionXml request) - { - var url = Request.AbsoluteUri; - var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase)); - var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, request.UuId, serverAddress); - - var cacheLength = TimeSpan.FromDays(1); - var cacheKey = Request.RawUrl.GetMD5(); - var bytes = Encoding.UTF8.GetBytes(xml); - - return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, XMLContentType, () => Task.FromResult(new MemoryStream(bytes))); - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Get(GetContentDirectory request) - { - var xml = ContentDirectory.GetServiceXml(); - - return _resultFactory.GetResult(Request, xml, XMLContentType); - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Get(GetMediaReceiverRegistrar request) - { - var xml = MediaReceiverRegistrar.GetServiceXml(); - - return _resultFactory.GetResult(Request, xml, XMLContentType); - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Get(GetConnnectionManager request) - { - var xml = ConnectionManager.GetServiceXml(); - - return _resultFactory.GetResult(Request, xml, XMLContentType); - } - - public async Task Post(ProcessMediaReceiverRegistrarControlRequest request) - { - var response = await PostAsync(request.RequestStream, MediaReceiverRegistrar).ConfigureAwait(false); - - return _resultFactory.GetResult(Request, response.Xml, XMLContentType); - } - - public async Task Post(ProcessContentDirectoryControlRequest request) - { - var response = await PostAsync(request.RequestStream, ContentDirectory).ConfigureAwait(false); - - return _resultFactory.GetResult(Request, response.Xml, XMLContentType); - } - - public async Task Post(ProcessConnectionManagerControlRequest request) - { - var response = await PostAsync(request.RequestStream, ConnectionManager).ConfigureAwait(false); - - return _resultFactory.GetResult(Request, response.Xml, XMLContentType); - } - - private Task PostAsync(Stream requestStream, IUpnpService service) - { - var id = GetPathValue(2).ToString(); - - return service.ProcessControlRequestAsync(new ControlRequest - { - Headers = Request.Headers, - InputXml = requestStream, - TargetServerUuId = id, - RequestedUrl = Request.AbsoluteUri - }); - } - - // Copied from MediaBrowser.Api/BaseApiService.cs - // TODO: Remove code duplication - /// - /// Gets the path segment at the specified index. - /// - /// The index of the path segment. - /// The path segment at the specified index. - /// Path doesn't contain enough segments. - /// Path doesn't start with the base url. - protected internal ReadOnlySpan GetPathValue(int index) - { - static void ThrowIndexOutOfRangeException() - => throw new IndexOutOfRangeException("Path doesn't contain enough segments."); - - static void ThrowInvalidDataException() - => throw new InvalidDataException("Path doesn't start with the base url."); - - ReadOnlySpan path = Request.PathInfo; - - // Remove the protocol part from the url - int pos = path.LastIndexOf("://"); - if (pos != -1) - { - path = path.Slice(pos + 3); - } - - // Remove the query string - pos = path.LastIndexOf('?'); - if (pos != -1) - { - path = path.Slice(0, pos); - } - - // Remove the domain - pos = path.IndexOf('/'); - if (pos != -1) - { - path = path.Slice(pos); - } - - // Remove base url - string baseUrl = _configurationManager.Configuration.BaseUrl; - int baseUrlLen = baseUrl.Length; - if (baseUrlLen != 0) - { - if (path.StartsWith(baseUrl, StringComparison.OrdinalIgnoreCase)) - { - path = path.Slice(baseUrlLen); - } - else - { - // The path doesn't start with the base url, - // how did we get here? - ThrowInvalidDataException(); - } - } - - // Remove leading / - path = path.Slice(1); - - // Backwards compatibility - const string Emby = "emby/"; - if (path.StartsWith(Emby, StringComparison.OrdinalIgnoreCase)) - { - path = path.Slice(Emby.Length); - } - - const string MediaBrowser = "mediabrowser/"; - if (path.StartsWith(MediaBrowser, StringComparison.OrdinalIgnoreCase)) - { - path = path.Slice(MediaBrowser.Length); - } - - // Skip segments until we are at the right index - for (int i = 0; i < index; i++) - { - pos = path.IndexOf('/'); - if (pos == -1) - { - ThrowIndexOutOfRangeException(); - } - - path = path.Slice(pos + 1); - } - - // Remove the rest - pos = path.IndexOf('/'); - if (pos != -1) - { - path = path.Slice(0, pos); - } - - return path; - } - - public object Get(GetIcon request) - { - var contentType = "image/" + Path.GetExtension(request.Filename) - .TrimStart('.') - .ToLowerInvariant(); - - var cacheLength = TimeSpan.FromDays(365); - var cacheKey = Request.RawUrl.GetMD5(); - - return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, contentType, () => Task.FromResult(_dlnaManager.GetIcon(request.Filename).Stream)); - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Subscribe(ProcessContentDirectoryEventRequest request) - { - return ProcessEventRequest(ContentDirectory); - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Subscribe(ProcessConnectionManagerEventRequest request) - { - return ProcessEventRequest(ConnectionManager); - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Subscribe(ProcessMediaReceiverRegistrarEventRequest request) - { - return ProcessEventRequest(MediaReceiverRegistrar); - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Unsubscribe(ProcessContentDirectoryEventRequest request) - { - return ProcessEventRequest(ContentDirectory); - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Unsubscribe(ProcessConnectionManagerEventRequest request) - { - return ProcessEventRequest(ConnectionManager); - } - - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] - public object Unsubscribe(ProcessMediaReceiverRegistrarEventRequest request) - { - return ProcessEventRequest(MediaReceiverRegistrar); - } - - private object ProcessEventRequest(IEventManager eventManager) - { - var subscriptionId = GetHeader("SID"); - - if (string.Equals(Request.Verb, "SUBSCRIBE", StringComparison.OrdinalIgnoreCase)) - { - var notificationType = GetHeader("NT"); - - var callback = GetHeader("CALLBACK"); - var timeoutString = GetHeader("TIMEOUT"); - - if (string.IsNullOrEmpty(notificationType)) - { - return GetSubscriptionResponse(eventManager.RenewEventSubscription(subscriptionId, notificationType, timeoutString, callback)); - } - - return GetSubscriptionResponse(eventManager.CreateEventSubscription(notificationType, timeoutString, callback)); - } - - return GetSubscriptionResponse(eventManager.CancelEventSubscription(subscriptionId)); - } - - private object GetSubscriptionResponse(EventSubscriptionResponse response) - { - return _resultFactory.GetResult(Request, response.Content, response.ContentType, response.Headers); - } - } -} diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index c5d60b2a0..c0d01f448 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -57,11 +57,11 @@ namespace Emby.Dlna.Main private ISsdpCommunicationsServer _communicationsServer; - internal IContentDirectory ContentDirectory { get; private set; } + public IContentDirectory ContentDirectory { get; private set; } - internal IConnectionManager ConnectionManager { get; private set; } + public IConnectionManager ConnectionManager { get; private set; } - internal IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; } + public IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; } public static DlnaEntryPoint Current; diff --git a/Jellyfin.Api/Attributes/HttpSubscribeAttribute.cs b/Jellyfin.Api/Attributes/HttpSubscribeAttribute.cs new file mode 100644 index 000000000..2fdd1e489 --- /dev/null +++ b/Jellyfin.Api/Attributes/HttpSubscribeAttribute.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Routing; + +namespace Jellyfin.Api.Attributes +{ + /// + /// Identifies an action that supports the HTTP GET method. + /// + public class HttpSubscribeAttribute : HttpMethodAttribute + { + private static readonly IEnumerable _supportedMethods = new[] { "SUBSCRIBE" }; + + /// + /// Initializes a new instance of the class. + /// + public HttpSubscribeAttribute() + : base(_supportedMethods) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The route template. May not be null. + public HttpSubscribeAttribute(string template) + : base(_supportedMethods, template) + { + if (template == null) + { + throw new ArgumentNullException(nameof(template)); + } + } + } +} diff --git a/Jellyfin.Api/Attributes/HttpUnsubscribeAttribute.cs b/Jellyfin.Api/Attributes/HttpUnsubscribeAttribute.cs new file mode 100644 index 000000000..d6d7e4563 --- /dev/null +++ b/Jellyfin.Api/Attributes/HttpUnsubscribeAttribute.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Routing; + +namespace Jellyfin.Api.Attributes +{ + /// + /// Identifies an action that supports the HTTP GET method. + /// + public class HttpUnsubscribeAttribute : HttpMethodAttribute + { + private static readonly IEnumerable _supportedMethods = new[] { "UNSUBSCRIBE" }; + + /// + /// Initializes a new instance of the class. + /// + public HttpUnsubscribeAttribute() + : base(_supportedMethods) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The route template. May not be null. + public HttpUnsubscribeAttribute(string template) + : base(_supportedMethods, template) + { + if (template == null) + { + throw new ArgumentNullException(nameof(template)); + } + } + } +} diff --git a/Jellyfin.Api/Controllers/DlnaServerController.cs b/Jellyfin.Api/Controllers/DlnaServerController.cs new file mode 100644 index 000000000..731d6707c --- /dev/null +++ b/Jellyfin.Api/Controllers/DlnaServerController.cs @@ -0,0 +1,259 @@ +#nullable enable + +using System; +using System.IO; +using System.Threading.Tasks; +using Emby.Dlna; +using Emby.Dlna.Main; +using Jellyfin.Api.Attributes; +using MediaBrowser.Controller.Dlna; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CA1801 + +namespace Jellyfin.Api.Controllers +{ + /// + /// Dlna Server Controller. + /// + [Route("Dlna")] + public class DlnaServerController : BaseJellyfinApiController + { + private const string XMLContentType = "text/xml; charset=UTF-8"; + + private readonly IDlnaManager _dlnaManager; + private readonly IContentDirectory _contentDirectory; + private readonly IConnectionManager _connectionManager; + private readonly IMediaReceiverRegistrar _mediaReceiverRegistrar; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + public DlnaServerController(IDlnaManager dlnaManager) + { + _dlnaManager = dlnaManager; + _contentDirectory = DlnaEntryPoint.Current.ContentDirectory; + _connectionManager = DlnaEntryPoint.Current.ConnectionManager; + _mediaReceiverRegistrar = DlnaEntryPoint.Current.MediaReceiverRegistrar; + } + + /// + /// Get Description Xml. + /// + /// Server UUID. + /// Description Xml. + [HttpGet("{Uuid}/description.xml")] + [HttpGet("{Uuid}/description")] + [Produces(XMLContentType)] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetDescriptionXml([FromRoute] string uuid) + { + var url = GetAbsoluteUri(); + var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase)); + var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, uuid, serverAddress); + + // TODO GetStaticResult doesn't do anything special? + /* + var cacheLength = TimeSpan.FromDays(1); + var cacheKey = Request.Path.Value.GetMD5(); + var bytes = Encoding.UTF8.GetBytes(xml); + */ + return Ok(xml); + } + + /// + /// Gets Dlna content directory xml. + /// + /// Server UUID. + /// Dlna content directory xml. + [HttpGet("{Uuid}/ContentDirectory/ContentDirectory.xml")] + [HttpGet("{Uuid}/ContentDirectory/ContentDirectory")] + [Produces(XMLContentType)] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetContentDirectory([FromRoute] string uuid) + { + return Ok(_contentDirectory.GetServiceXml()); + } + + /// + /// Gets Dlna media receiver registrar xml. + /// + /// Server UUID. + /// Dlna media receiver registrar xml. + [HttpGet("{Uuid}/MediaReceiverRegistrar/MediaReceiverRegistrar.xml")] + [HttpGet("{Uuid}/MediaReceiverRegistrar/MediaReceiverRegistrar")] + [Produces(XMLContentType)] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetMediaReceiverRegistrar([FromRoute] string uuid) + { + return Ok(_mediaReceiverRegistrar.GetServiceXml()); + } + + /// + /// Gets Dlna media receiver registrar xml. + /// + /// Server UUID. + /// Dlna media receiver registrar xml. + [HttpGet("{Uuid}/ConnectionManager/ConnectionManager.xml")] + [HttpGet("{Uuid}/ConnectionManager/ConnectionManager")] + [Produces(XMLContentType)] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetConnectionManager([FromRoute] string uuid) + { + return Ok(_connectionManager.GetServiceXml()); + } + + /// + /// Process a content directory control request. + /// + /// Server UUID. + /// Control response. + [HttpPost("{Uuid}/ContentDirectory/Control")] + public async Task> ProcessContentDirectoryControlRequest([FromRoute] string uuid) + { + var response = await PostAsync(uuid, Request.Body, _contentDirectory).ConfigureAwait(false); + return Ok(response); + } + + /// + /// Process a connection manager control request. + /// + /// Server UUID. + /// Control response. + [HttpPost("{Uuid}/ConnectionManager/Control")] + public async Task> ProcessConnectionManagerControlRequest([FromRoute] string uuid) + { + var response = await PostAsync(uuid, Request.Body, _connectionManager).ConfigureAwait(false); + return Ok(response); + } + + /// + /// Process a media receiver registrar control request. + /// + /// Server UUID. + /// Control response. + [HttpPost("{Uuid}/MediaReceiverRegistrar/Control")] + public async Task> ProcessMediaReceiverRegistrarControlRequest([FromRoute] string uuid) + { + var response = await PostAsync(uuid, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false); + return Ok(response); + } + + /// + /// Processes an event subscription request. + /// + /// Server UUID. + /// Event subscription response. + [HttpSubscribe("{Uuid}/MediaReceiverRegistrar/Events")] + [HttpUnsubscribe("{Uuid}/MediaReceiverRegistrar/Events")] + public ActionResult ProcessMediaReceiverRegistrarEventRequest(string uuid) + { + return Ok(ProcessEventRequest(_mediaReceiverRegistrar)); + } + + /// + /// Processes an event subscription request. + /// + /// Server UUID. + /// Event subscription response. + [HttpSubscribe("{Uuid}/ContentDirectory/Events")] + [HttpUnsubscribe("{Uuid}/ContentDirectory/Events")] + public ActionResult ProcessContentDirectoryEventRequest(string uuid) + { + return Ok(ProcessEventRequest(_contentDirectory)); + } + + /// + /// Processes an event subscription request. + /// + /// Server UUID. + /// Event subscription response. + [HttpSubscribe("{Uuid}/ConnectionManager/Events")] + [HttpUnsubscribe("{Uuid}/ConnectionManager/Events")] + public ActionResult ProcessConnectionManagerEventRequest(string uuid) + { + return Ok(ProcessEventRequest(_connectionManager)); + } + + /// + /// Gets a server icon. + /// + /// Server UUID. + /// The icon filename. + /// Icon stream. + [HttpGet("{Uuid}/icons/{Filename}")] + public ActionResult GetIconId([FromRoute] string uuid, [FromRoute] string fileName) + { + return GetIcon(fileName); + } + + /// + /// Gets a server icon. + /// + /// Server UUID. + /// The icon filename. + /// Icon stream. + [HttpGet("icons/{Filename}")] + public ActionResult GetIcon([FromQuery] string uuid, [FromRoute] string fileName) + { + return GetIcon(fileName); + } + + private ActionResult GetIcon(string fileName) + { + var icon = _dlnaManager.GetIcon(fileName); + if (icon == null) + { + return NotFound(); + } + + var contentType = "image/" + Path.GetExtension(fileName) + .TrimStart('.') + .ToLowerInvariant(); + + return new FileStreamResult(icon.Stream, contentType); + } + + private string GetAbsoluteUri() + { + return $"{Request.Scheme}://{Request.Host}{Request.Path}"; + } + + private Task PostAsync(string id, Stream requestStream, IUpnpService service) + { + return service.ProcessControlRequestAsync(new ControlRequest + { + Headers = Request.Headers, + InputXml = requestStream, + TargetServerUuId = id, + RequestedUrl = GetAbsoluteUri() + }); + } + + private EventSubscriptionResponse ProcessEventRequest(IEventManager eventManager) + { + var subscriptionId = Request.Headers["SID"]; + if (string.Equals(Request.Method, "subscribe", StringComparison.OrdinalIgnoreCase)) + { + var notificationType = Request.Headers["NT"]; + var callback = Request.Headers["CALLBACK"]; + var timeoutString = Request.Headers["TIMEOUT"]; + + if (string.IsNullOrEmpty(notificationType)) + { + return eventManager.RenewEventSubscription( + subscriptionId, + notificationType, + timeoutString, + callback); + } + + return eventManager.CreateEventSubscription(notificationType, timeoutString, callback); + } + + return eventManager.CancelEventSubscription(subscriptionId); + } + } +} diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 8f23ef9d0..a2e116fd7 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -14,6 +14,7 @@ + -- cgit v1.2.3 From 2a49b19a7c02f16cd4bb1d847c1ff76c5df316fb Mon Sep 17 00:00:00 2001 From: ZadenRB Date: Wed, 22 Apr 2020 00:21:37 -0600 Subject: Update documentation of startIndex Co-Authored-By: Vasily --- Jellyfin.Api/Controllers/NotificationsController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs index 6145246ed..bb9f5a7b3 100644 --- a/Jellyfin.Api/Controllers/NotificationsController.cs +++ b/Jellyfin.Api/Controllers/NotificationsController.cs @@ -39,7 +39,7 @@ namespace Jellyfin.Api.Controllers /// /// The user's ID. /// An optional filter by notification read state. - /// The optional index to start at. All notifications with a lower index will be dropped from the results. + /// The optional index to start at. All notifications with a lower index will be omitted from the results. /// An optional limit on the number of notifications returned. /// A read-only list of all of the user's notifications. [HttpGet("{UserID}")] -- cgit v1.2.3 From 7693cc0db006ef4eb3a90d161b14ac4551bb96a7 Mon Sep 17 00:00:00 2001 From: ZadenRB Date: Wed, 22 Apr 2020 10:00:10 -0600 Subject: Use ActionResult return type for all endpoints --- .../Controllers/NotificationsController.cs | 28 ++++++++++++---------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs index bb9f5a7b3..0bf3aa1b4 100644 --- a/Jellyfin.Api/Controllers/NotificationsController.cs +++ b/Jellyfin.Api/Controllers/NotificationsController.cs @@ -43,8 +43,8 @@ namespace Jellyfin.Api.Controllers /// An optional limit on the number of notifications returned. /// A read-only list of all of the user's notifications. [HttpGet("{UserID}")] - [ProducesResponseType(typeof(NotificationResultDto), StatusCodes.Status200OK)] - public NotificationResultDto GetNotifications( + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetNotifications( [FromRoute] string userId, [FromQuery] bool? isRead, [FromQuery] int? startIndex, @@ -59,8 +59,8 @@ namespace Jellyfin.Api.Controllers /// The user's ID. /// Notifications summary for the user. [HttpGet("{UserID}/Summary")] - [ProducesResponseType(typeof(NotificationsSummaryDto), StatusCodes.Status200OK)] - public NotificationsSummaryDto GetNotificationsSummary( + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetNotificationsSummary( [FromRoute] string userId) { return new NotificationsSummaryDto(); @@ -71,8 +71,8 @@ namespace Jellyfin.Api.Controllers /// /// All notification types. [HttpGet("Types")] - [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] - public IEnumerable GetNotificationTypes() + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult> GetNotificationTypes() { return _notificationManager.GetNotificationTypes(); } @@ -82,10 +82,10 @@ namespace Jellyfin.Api.Controllers /// /// All notification services. [HttpGet("Services")] - [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] - public IEnumerable GetNotificationServices() + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult> GetNotificationServices() { - return _notificationManager.GetNotificationServices(); + return _notificationManager.GetNotificationServices().ToList(); } /// @@ -97,7 +97,7 @@ namespace Jellyfin.Api.Controllers /// The level of the notification. [HttpPost("Admin")] [ProducesResponseType(StatusCodes.Status200OK)] - public void CreateAdminNotification( + public ActionResult CreateAdminNotification( [FromQuery] string name, [FromQuery] string description, [FromQuery] string? url, @@ -114,6 +114,8 @@ namespace Jellyfin.Api.Controllers }; _notificationManager.SendNotification(notification, CancellationToken.None); + + return Ok(); } /// @@ -123,10 +125,11 @@ namespace Jellyfin.Api.Controllers /// A comma-separated list of the IDs of notifications which should be set as read. [HttpPost("{UserID}/Read")] [ProducesResponseType(StatusCodes.Status200OK)] - public void SetRead( + public ActionResult SetRead( [FromRoute] string userId, [FromQuery] string ids) { + return Ok(); } /// @@ -136,10 +139,11 @@ namespace Jellyfin.Api.Controllers /// A comma-separated list of the IDs of notifications which should be set as unread. [HttpPost("{UserID}/Unread")] [ProducesResponseType(StatusCodes.Status200OK)] - public void SetUnread( + public ActionResult SetUnread( [FromRoute] string userId, [FromQuery] string ids) { + return Ok(); } } } -- cgit v1.2.3 From a06d271725f6e746d9a970f29283ab8f3ebae607 Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 22 Apr 2020 13:07:21 -0600 Subject: Move ConfigurationService to Jellyfin.Api --- .../Controllers/ConfigurationController.cs | 128 ++++++++++++++++++ .../ConfigurationDtos/MediaEncoderPathDto.cs | 18 +++ MediaBrowser.Api/ConfigurationService.cs | 146 --------------------- 3 files changed, 146 insertions(+), 146 deletions(-) create mode 100644 Jellyfin.Api/Controllers/ConfigurationController.cs create mode 100644 Jellyfin.Api/Models/ConfigurationDtos/MediaEncoderPathDto.cs delete mode 100644 MediaBrowser.Api/ConfigurationService.cs diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs new file mode 100644 index 000000000..14e45833f --- /dev/null +++ b/Jellyfin.Api/Controllers/ConfigurationController.cs @@ -0,0 +1,128 @@ +#nullable enable + +using System.Threading.Tasks; +using Jellyfin.Api.Models.ConfigurationDtos; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Serialization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Jellyfin.Api.Controllers +{ + /// + /// Configuration Controller. + /// + [Route("System")] + [Authenticated] + public class ConfigurationController : BaseJellyfinApiController + { + private readonly IServerConfigurationManager _configurationManager; + private readonly IMediaEncoder _mediaEncoder; + private readonly IJsonSerializer _jsonSerializer; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public ConfigurationController( + IServerConfigurationManager configurationManager, + IMediaEncoder mediaEncoder, + IJsonSerializer jsonSerializer) + { + _configurationManager = configurationManager; + _mediaEncoder = mediaEncoder; + _jsonSerializer = jsonSerializer; + } + + /// + /// Gets application configuration. + /// + /// Application configuration. + [HttpGet("Configuration")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetConfiguration() + { + return Ok(_configurationManager.Configuration); + } + + /// + /// Updates application configuration. + /// + /// Configuration. + /// Status. + [HttpPost("Configuration")] + [Authenticated(Roles = "Admin")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult UpdateConfiguration([FromBody, BindRequired] ServerConfiguration configuration) + { + _configurationManager.ReplaceConfiguration(configuration); + return Ok(); + } + + /// + /// Gets a named configuration. + /// + /// Configuration key. + /// Configuration. + [HttpGet("Configuration/{Key}")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetNamedConfiguration([FromRoute] string key) + { + return Ok(_configurationManager.GetConfiguration(key)); + } + + /// + /// Updates named configuration. + /// + /// Configuration key. + /// Status. + [HttpPost("Configuration/{Key}")] + [Authenticated(Roles = "Admin")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task UpdateNamedConfiguration([FromRoute] string key) + { + var configurationType = _configurationManager.GetConfigurationType(key); + /* + // TODO switch to System.Text.Json when https://github.com/dotnet/runtime/issues/30255 is fixed. + var configuration = await JsonSerializer.DeserializeAsync(Request.Body, configurationType); + */ + + var configuration = await _jsonSerializer.DeserializeFromStreamAsync(Request.Body, configurationType) + .ConfigureAwait(false); + _configurationManager.SaveConfiguration(key, configuration); + return Ok(); + } + + /// + /// Gets a default MetadataOptions object. + /// + /// MetadataOptions. + [HttpGet("Configuration/MetadataOptions/Default")] + [Authenticated(Roles = "Admin")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetDefaultMetadataOptions() + { + return Ok(new MetadataOptions()); + } + + /// + /// Updates the path to the media encoder. + /// + /// Media encoder path form body. + /// Status. + [HttpPost("MediaEncoder/Path")] + [Authenticated(Roles = "Admin", AllowBeforeStartupWizard = true)] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult UpdateMediaEncoderPath([FromForm, BindRequired] MediaEncoderPathDto mediaEncoderPath) + { + _mediaEncoder.UpdateEncoderPath(mediaEncoderPath.Path, mediaEncoderPath.PathType); + return Ok(); + } + } +} diff --git a/Jellyfin.Api/Models/ConfigurationDtos/MediaEncoderPathDto.cs b/Jellyfin.Api/Models/ConfigurationDtos/MediaEncoderPathDto.cs new file mode 100644 index 000000000..b05e0cdf5 --- /dev/null +++ b/Jellyfin.Api/Models/ConfigurationDtos/MediaEncoderPathDto.cs @@ -0,0 +1,18 @@ +namespace Jellyfin.Api.Models.ConfigurationDtos +{ + /// + /// Media Encoder Path Dto. + /// + public class MediaEncoderPathDto + { + /// + /// Gets or sets media encoder path. + /// + public string Path { get; set; } + + /// + /// Gets or sets media encoder path type. + /// + public string PathType { get; set; } + } +} diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs deleted file mode 100644 index 316be04a0..000000000 --- a/MediaBrowser.Api/ConfigurationService.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System.IO; -using System.Threading.Tasks; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - /// - /// Class GetConfiguration - /// - [Route("/System/Configuration", "GET", Summary = "Gets application configuration")] - [Authenticated] - public class GetConfiguration : IReturn - { - - } - - [Route("/System/Configuration/{Key}", "GET", Summary = "Gets a named configuration")] - [Authenticated(AllowBeforeStartupWizard = true)] - public class GetNamedConfiguration - { - [ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Key { get; set; } - } - - /// - /// Class UpdateConfiguration - /// - [Route("/System/Configuration", "POST", Summary = "Updates application configuration")] - [Authenticated(Roles = "Admin")] - public class UpdateConfiguration : ServerConfiguration, IReturnVoid - { - } - - [Route("/System/Configuration/{Key}", "POST", Summary = "Updates named configuration")] - [Authenticated(Roles = "Admin")] - public class UpdateNamedConfiguration : IReturnVoid, IRequiresRequestStream - { - [ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Key { get; set; } - - public Stream RequestStream { get; set; } - } - - [Route("/System/Configuration/MetadataOptions/Default", "GET", Summary = "Gets a default MetadataOptions object")] - [Authenticated(Roles = "Admin")] - public class GetDefaultMetadataOptions : IReturn - { - - } - - [Route("/System/MediaEncoder/Path", "POST", Summary = "Updates the path to the media encoder")] - [Authenticated(Roles = "Admin", AllowBeforeStartupWizard = true)] - public class UpdateMediaEncoderPath : IReturnVoid - { - [ApiMember(Name = "Path", Description = "Path", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Path { get; set; } - [ApiMember(Name = "PathType", Description = "PathType", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string PathType { get; set; } - } - - public class ConfigurationService : BaseApiService - { - /// - /// The _json serializer - /// - private readonly IJsonSerializer _jsonSerializer; - - /// - /// The _configuration manager - /// - private readonly IServerConfigurationManager _configurationManager; - - private readonly IMediaEncoder _mediaEncoder; - - public ConfigurationService( - ILogger logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IJsonSerializer jsonSerializer, - IServerConfigurationManager configurationManager, - IMediaEncoder mediaEncoder) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _jsonSerializer = jsonSerializer; - _configurationManager = configurationManager; - _mediaEncoder = mediaEncoder; - } - - public void Post(UpdateMediaEncoderPath request) - { - _mediaEncoder.UpdateEncoderPath(request.Path, request.PathType); - } - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetConfiguration request) - { - return ToOptimizedResult(_configurationManager.Configuration); - } - - public object Get(GetNamedConfiguration request) - { - var result = _configurationManager.GetConfiguration(request.Key); - - return ToOptimizedResult(result); - } - - /// - /// Posts the specified configuraiton. - /// - /// The request. - public void Post(UpdateConfiguration request) - { - // Silly, but we need to serialize and deserialize or the XmlSerializer will write the xml with an element name of UpdateConfiguration - var json = _jsonSerializer.SerializeToString(request); - - var config = _jsonSerializer.DeserializeFromString(json); - - _configurationManager.ReplaceConfiguration(config); - } - - public async Task Post(UpdateNamedConfiguration request) - { - var key = GetPathValue(2).ToString(); - - var configurationType = _configurationManager.GetConfigurationType(key); - var configuration = await _jsonSerializer.DeserializeFromStreamAsync(request.RequestStream, configurationType).ConfigureAwait(false); - - _configurationManager.SaveConfiguration(key, configuration); - } - - public object Get(GetDefaultMetadataOptions request) - { - return ToOptimizedResult(new MetadataOptions()); - } - } -} -- cgit v1.2.3 From c6eebca335d09d6a6c627205e126448ab5441f37 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 23 Apr 2020 07:29:28 -0600 Subject: Apply suggestions and add URL to log message --- Jellyfin.Server/Middleware/ExceptionMiddleware.cs | 34 +++++++++++++---------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs index 0d9dac89f..ecc76594e 100644 --- a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs +++ b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Net.Mime; using System.Threading.Tasks; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; @@ -22,15 +23,15 @@ namespace Jellyfin.Server.Middleware /// Initializes a new instance of the class. /// /// Next request delegate. - /// Instance of the interface. + /// Instance of the interface. /// Instance of the interface. public ExceptionMiddleware( RequestDelegate next, - ILoggerFactory loggerFactory, + ILogger logger, IServerConfigurationManager serverConfigurationManager) { _next = next; - _logger = loggerFactory.CreateLogger(); + _logger = logger; _configuration = serverConfigurationManager; } @@ -54,9 +55,14 @@ namespace Jellyfin.Server.Middleware } ex = GetActualException(ex); - _logger.LogError(ex, "Error processing request: {0}", ex.Message); + _logger.LogError( + ex, + "Error processing request: {ExceptionMessage}. URL {Method} {Url}. ", + ex.Message, + context.Request.Method, + context.Request.Path); context.Response.StatusCode = GetStatusCode(ex); - context.Response.ContentType = "text/plain"; + context.Response.ContentType = MediaTypeNames.Text.Plain; var errorContent = NormalizeExceptionMessage(ex.Message); await context.Response.WriteAsync(errorContent).ConfigureAwait(false); @@ -105,16 +111,14 @@ namespace Jellyfin.Server.Middleware } // Strip any information we don't want to reveal - msg = msg.Replace( - _configuration.ApplicationPaths.ProgramSystemPath, - string.Empty, - StringComparison.OrdinalIgnoreCase); - msg = msg.Replace( - _configuration.ApplicationPaths.ProgramDataPath, - string.Empty, - StringComparison.OrdinalIgnoreCase); - - return msg; + return msg.Replace( + _configuration.ApplicationPaths.ProgramSystemPath, + string.Empty, + StringComparison.OrdinalIgnoreCase) + .Replace( + _configuration.ApplicationPaths.ProgramDataPath, + string.Empty, + StringComparison.OrdinalIgnoreCase); } } } -- cgit v1.2.3 From c7c2f9da90a7cf7a452de0ab1adf7e36f422bbe1 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 23 Apr 2020 07:51:04 -0600 Subject: Apply suggestions --- Jellyfin.Api/Controllers/StartupController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index 2db7e32aa..14c59593f 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -59,7 +59,7 @@ namespace Jellyfin.Api.Controllers PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage }; - return Ok(result); + return result; } /// @@ -108,7 +108,7 @@ namespace Jellyfin.Api.Controllers public ActionResult GetFirstUser() { var user = _userManager.Users.First(); - return Ok(new StartupUserDto { Name = user.Name, Password = user.Password }); + return new StartupUserDto { Name = user.Name, Password = user.Password }; } /// -- cgit v1.2.3 From 4d894c4344fd23026bbfdc0a1cdd24231441a444 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 23 Apr 2020 07:55:47 -0600 Subject: Remove unneeded Ok calls. --- Jellyfin.Api/Controllers/DevicesController.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs index 559a26007..cebb51ccf 100644 --- a/Jellyfin.Api/Controllers/DevicesController.cs +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -74,7 +74,7 @@ namespace Jellyfin.Api.Controllers return NotFound(); } - return Ok(deviceInfo); + return deviceInfo; } /// @@ -86,7 +86,7 @@ namespace Jellyfin.Api.Controllers [Authenticated(Roles = "Admin")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult GetDeviceOptions([FromQuery, BindRequired] string id) + public ActionResult GetDeviceOptions([FromQuery, BindRequired] string id) { var deviceInfo = _deviceManager.GetDeviceOptions(id); if (deviceInfo == null) @@ -94,7 +94,7 @@ namespace Jellyfin.Api.Controllers return NotFound(); } - return Ok(deviceInfo); + return deviceInfo; } /// @@ -150,7 +150,7 @@ namespace Jellyfin.Api.Controllers public ActionResult GetCameraUploads([FromQuery, BindRequired] string id) { var uploadHistory = _deviceManager.GetCameraUploadHistory(id); - return Ok(uploadHistory); + return uploadHistory; } /// -- cgit v1.2.3 From 534b372b8124bbd5343759cf13bed621b0ebed99 Mon Sep 17 00:00:00 2001 From: dkanada Date: Thu, 23 Apr 2020 22:56:32 +0900 Subject: remove several deprecated utilities in the web package --- MediaBrowser.WebDashboard/Api/DashboardService.cs | 155 +-------------------- MediaBrowser.WebDashboard/Api/PackageCreator.cs | 161 ---------------------- 2 files changed, 7 insertions(+), 309 deletions(-) delete mode 100644 MediaBrowser.WebDashboard/Api/PackageCreator.cs diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 133a35527..55fc463d0 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -52,12 +52,6 @@ namespace MediaBrowser.WebDashboard.Api public string Name { get; set; } } - [Route("/web/Package", "GET", IsHidden = true)] - public class GetDashboardPackage - { - public string Mode { get; set; } - } - [Route("/robots.txt", "GET", IsHidden = true)] public class GetRobotsTxt { @@ -225,7 +219,7 @@ namespace MediaBrowser.WebDashboard.Api return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => Task.FromResult(stream)); } - return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => PackageCreator.ModifyHtml(false, stream, null, _appHost.ApplicationVersionString, null)); + return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => Task.FromResult(stream)); } throw new ResourceNotFoundException(); @@ -328,154 +322,19 @@ namespace MediaBrowser.WebDashboard.Api throw new ResourceNotFoundException(); } - var path = request.ResourceName; - - var contentType = MimeTypes.GetMimeType(path); + var path = request?.ResourceName; var basePath = DashboardUIPath; // Bounce them to the startup wizard if it hasn't been completed yet - if (!_serverConfigurationManager.Configuration.IsStartupWizardCompleted && - Request.RawUrl.IndexOf("wizard", StringComparison.OrdinalIgnoreCase) == -1 && - PackageCreator.IsCoreHtml(path)) - { - // But don't redirect if an html import is being requested. - if (path.IndexOf("bower_components", StringComparison.OrdinalIgnoreCase) == -1) - { - Request.Response.Redirect("index.html?start=wizard#!/wizardstart.html"); - return null; - } - } - - var localizationCulture = GetLocalizationCulture(); - - // Don't cache if not configured to do so - // But always cache images to simulate production - if (!_serverConfigurationManager.Configuration.EnableDashboardResponseCaching && - !contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) && - !contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase)) - { - var stream = await GetResourceStream(basePath, path, localizationCulture).ConfigureAwait(false); - return _resultFactory.GetResult(Request, stream, contentType); - } - - TimeSpan? cacheDuration = null; - - // Cache images unconditionally - updates to image files will require new filename - // If there's a version number in the query string we can cache this unconditionally - if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) || contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase) || !string.IsNullOrEmpty(request.V)) + if (!_serverConfigurationManager.Configuration.IsStartupWizardCompleted + && !Request.RawUrl.Contains("wizard", StringComparison.OrdinalIgnoreCase) + && Request.RawUrl.Contains("index", StringComparison.OrdinalIgnoreCase)) { - cacheDuration = TimeSpan.FromDays(365); - } - - var cacheKey = (_appHost.ApplicationVersionString + (localizationCulture ?? string.Empty) + path).GetMD5(); - - // html gets modified on the fly - if (contentType.StartsWith("text/html", StringComparison.OrdinalIgnoreCase)) - { - return await _resultFactory.GetStaticResult(Request, cacheKey, null, cacheDuration, contentType, () => GetResourceStream(basePath, path, localizationCulture)).ConfigureAwait(false); + Request.Response.Redirect("index.html?start=wizard#!/wizardstart.html"); + return null; } return await _resultFactory.GetStaticFileResult(Request, _resourceFileManager.GetResourcePath(basePath, path)).ConfigureAwait(false); } - - private string GetLocalizationCulture() - { - return _serverConfigurationManager.Configuration.UICulture; - } - - /// - /// Gets the resource stream. - /// - private Task GetResourceStream(string basePath, string virtualPath, string localizationCulture) - { - return GetPackageCreator(basePath) - .GetResource(virtualPath, null, localizationCulture, _appHost.ApplicationVersionString); - } - - private PackageCreator GetPackageCreator(string basePath) - { - return new PackageCreator(basePath, _resourceFileManager); - } - - public async Task Get(GetDashboardPackage request) - { - if (!_appConfig.HostWebClient() || DashboardUIPath == null) - { - throw new ResourceNotFoundException(); - } - - var mode = request.Mode; - - var inputPath = string.IsNullOrWhiteSpace(mode) ? - DashboardUIPath - : "C:\\dev\\emby-web-mobile-master\\dist"; - - var targetPath = !string.IsNullOrWhiteSpace(mode) ? - inputPath - : "C:\\dev\\emby-web-mobile\\src"; - - var packageCreator = GetPackageCreator(inputPath); - - if (!string.Equals(inputPath, targetPath, StringComparison.OrdinalIgnoreCase)) - { - try - { - Directory.Delete(targetPath, true); - } - catch (IOException ex) - { - _logger.LogError(ex, "Error deleting {Path}", targetPath); - } - - CopyDirectory(inputPath, targetPath); - } - - var appVersion = _appHost.ApplicationVersionString; - - await DumpHtml(packageCreator, inputPath, targetPath, mode, appVersion).ConfigureAwait(false); - - return string.Empty; - } - - private async Task DumpHtml(PackageCreator packageCreator, string source, string destination, string mode, string appVersion) - { - foreach (var file in _fileSystem.GetFiles(source)) - { - var filename = file.Name; - - if (!string.Equals(file.Extension, ".html", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - await DumpFile(packageCreator, filename, Path.Combine(destination, filename), mode, appVersion).ConfigureAwait(false); - } - } - - private async Task DumpFile(PackageCreator packageCreator, string resourceVirtualPath, string destinationFilePath, string mode, string appVersion) - { - using (var stream = await packageCreator.GetResource(resourceVirtualPath, mode, null, appVersion).ConfigureAwait(false)) - using (var fs = new FileStream(destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.Read)) - { - await stream.CopyToAsync(fs).ConfigureAwait(false); - } - } - - private void CopyDirectory(string source, string destination) - { - Directory.CreateDirectory(destination); - - // Now Create all of the directories - foreach (var dirPath in _fileSystem.GetDirectories(source, true)) - { - Directory.CreateDirectory(dirPath.FullName.Replace(source, destination, StringComparison.Ordinal)); - } - - // Copy all the files & Replaces any files with the same name - foreach (var newPath in _fileSystem.GetFiles(source, true)) - { - File.Copy(newPath.FullName, newPath.FullName.Replace(source, destination, StringComparison.Ordinal), true); - } - } } } diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs deleted file mode 100644 index b7c15a840..000000000 --- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs +++ /dev/null @@ -1,161 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Globalization; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller; - -namespace MediaBrowser.WebDashboard.Api -{ - public class PackageCreator - { - private readonly string _basePath; - private readonly IResourceFileManager _resourceFileManager; - - public PackageCreator(string basePath, IResourceFileManager resourceFileManager) - { - _basePath = basePath; - _resourceFileManager = resourceFileManager; - } - - public async Task GetResource( - string virtualPath, - string mode, - string localizationCulture, - string appVersion) - { - var resourcePath = _resourceFileManager.GetResourcePath(_basePath, virtualPath); - Stream resourceStream = File.OpenRead(resourcePath); - - if (resourceStream != null && IsCoreHtml(virtualPath)) - { - bool isMainIndexPage = string.Equals(virtualPath, "index.html", StringComparison.OrdinalIgnoreCase); - resourceStream = await ModifyHtml(isMainIndexPage, resourceStream, mode, appVersion, localizationCulture).ConfigureAwait(false); - } - - return resourceStream; - } - - public static bool IsCoreHtml(string path) - { - if (path.IndexOf(".template.html", StringComparison.OrdinalIgnoreCase) != -1) - { - return false; - } - - return string.Equals(Path.GetExtension(path), ".html", StringComparison.OrdinalIgnoreCase); - } - - /// - /// Modifies the source HTML stream by adding common meta tags, css and js. - /// - /// True if the stream contains content for the main index page. - /// The stream whose content should be modified. - /// The client mode ('cordova', 'android', etc). - /// The application version. - /// The localization culture. - /// - /// A task that represents the async operation to read and modify the input stream. - /// The task result contains a stream containing the modified HTML content. - /// - public static async Task ModifyHtml( - bool isMainIndexPage, - Stream sourceStream, - string mode, - string appVersion, - string localizationCulture) - { - string html; - using (var reader = new StreamReader(sourceStream, Encoding.UTF8)) - { - html = await reader.ReadToEndAsync().ConfigureAwait(false); - } - - if (isMainIndexPage && !string.IsNullOrWhiteSpace(localizationCulture)) - { - var lang = localizationCulture.Split('-')[0]; - - html = html.Replace("", "" + GetMetaTags(mode), StringComparison.Ordinal); - } - - // Disable embedded scripts from plugins. We'll run them later once resources have loaded - if (html.IndexOf("", "-->", StringComparison.Ordinal); - } - - if (isMainIndexPage) - { - html = html.Replace("", GetCommonJavascript(mode, appVersion) + "", StringComparison.Ordinal); - } - - var bytes = Encoding.UTF8.GetBytes(html); - - return new MemoryStream(bytes); - } - - /// - /// Gets the meta tags. - /// - /// System.String. - private static string GetMetaTags(string mode) - { - var sb = new StringBuilder(); - - if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase) - || string.Equals(mode, "android", StringComparison.OrdinalIgnoreCase)) - { - sb.Append(""); - } - - return sb.ToString(); - } - - /// - /// Gets the common javascript. - /// - /// The mode. - /// The version. - /// System.String. - private static string GetCommonJavascript(string mode, string version) - { - var builder = new StringBuilder(); - - builder.Append(""); - - if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase)) - { - builder.Append(""); - } - - builder.Append(""); - - return builder.ToString(); - } - } -} -- cgit v1.2.3 From 1223eb5a2285c48f50b07fb5aa2c463928b69afe Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 23 Apr 2020 08:03:41 -0600 Subject: Remove unneeded Ok calls. --- Jellyfin.Api/Controllers/DisplayPreferencesController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs index 25391bcf8..42e87edd6 100644 --- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs +++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs @@ -49,7 +49,7 @@ namespace Jellyfin.Api.Controllers return NotFound(); } - return Ok(result); + return result; } /// -- cgit v1.2.3 From 5ca7e1fd79d85e7e531c747f4eca203ff862be8d Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 23 Apr 2020 08:54:28 -0600 Subject: Move ChannelService to Jellyfin.Api --- Jellyfin.Api/Controllers/ChannelsController.cs | 238 +++++++++++++++++ Jellyfin.Api/Extensions/RequestExtensions.cs | 90 +++++++ MediaBrowser.Api/ChannelService.cs | 341 ------------------------- 3 files changed, 328 insertions(+), 341 deletions(-) create mode 100644 Jellyfin.Api/Controllers/ChannelsController.cs create mode 100644 Jellyfin.Api/Extensions/RequestExtensions.cs delete mode 100644 MediaBrowser.Api/ChannelService.cs diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs new file mode 100644 index 000000000..4e2621b7b --- /dev/null +++ b/Jellyfin.Api/Controllers/ChannelsController.cs @@ -0,0 +1,238 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Api.Extensions; +using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Channels; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers +{ + /// + /// Channels Controller. + /// + public class ChannelsController : BaseJellyfinApiController + { + private readonly IChannelManager _channelManager; + private readonly IUserManager _userManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + public ChannelsController(IChannelManager channelManager, IUserManager userManager) + { + _channelManager = channelManager; + _userManager = userManager; + } + + /// + /// Gets available channels. + /// + /// User Id. + /// Optional. The record index to start at. All items with a lower index will be dropped from the results. + /// Optional. The maximum number of records to return. + /// Optional. Filter by channels that support getting latest items. + /// Optional. Filter by channels that support media deletion. + /// Optional. Filter by channels that are favorite. + /// Channels. + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult> GetChannels( + [FromQuery] Guid userId, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] bool? supportsLatestItems, + [FromQuery] bool? supportsMediaDeletion, + [FromQuery] bool? isFavorite) + { + return _channelManager.GetChannels(new ChannelQuery + { + Limit = limit, + StartIndex = startIndex, + UserId = userId, + SupportsLatestItems = supportsLatestItems, + SupportsMediaDeletion = supportsMediaDeletion, + IsFavorite = isFavorite + }); + } + + /// + /// Get all channel features. + /// + /// Channel features. + [HttpGet("Features")] + [ProducesResponseType(StatusCodes.Status200OK)] + public IEnumerable GetAllChannelFeatures() + { + return _channelManager.GetAllChannelFeatures(); + } + + /// + /// Get channel features. + /// + /// Channel id. + /// Channel features. + [HttpGet("{Id}/Features")] + public ActionResult GetChannelFeatures([FromRoute] string id) + { + return _channelManager.GetChannelFeatures(id); + } + + /// + /// Get channel items. + /// + /// Channel Id. + /// Folder Id. + /// User Id. + /// Optional. The record index to start at. All items with a lower index will be dropped from the results. + /// Optional. The maximum number of records to return. + /// Sort Order - Ascending,Descending. + /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. + /// Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime. + /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. + /// Channel items. + [HttpGet("{Id}/Items")] + public async Task>> GetChannelItems( + [FromRoute] Guid id, + [FromQuery] Guid? folderId, + [FromQuery] Guid? userId, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] string sortOrder, + [FromQuery] string filters, + [FromQuery] string sortBy, + [FromQuery] string fields) + { + var user = userId == null + ? null + : _userManager.GetUserById(userId.Value); + + var query = new InternalItemsQuery(user) + { + Limit = limit, + StartIndex = startIndex, + ChannelIds = new[] { id }, + ParentId = folderId ?? Guid.Empty, + OrderBy = RequestExtensions.GetOrderBy(sortBy, sortOrder), + DtoOptions = new DtoOptions { Fields = RequestExtensions.GetItemFields(fields) } + }; + + foreach (var filter in RequestExtensions.GetFilters(filters)) + { + switch (filter) + { + case ItemFilter.IsFolder: + query.IsFolder = true; + break; + case ItemFilter.IsNotFolder: + query.IsFolder = false; + break; + case ItemFilter.IsUnplayed: + query.IsPlayed = false; + break; + case ItemFilter.IsPlayed: + query.IsPlayed = true; + break; + case ItemFilter.IsFavorite: + query.IsFavorite = true; + break; + case ItemFilter.IsResumable: + query.IsResumable = true; + break; + case ItemFilter.Likes: + query.IsLiked = true; + break; + case ItemFilter.Dislikes: + query.IsLiked = false; + break; + case ItemFilter.IsFavoriteOrLikes: + query.IsFavoriteOrLiked = true; + break; + } + } + + return await _channelManager.GetChannelItems(query, CancellationToken.None).ConfigureAwait(false); + } + + /// + /// Gets latest channel items. + /// + /// User Id. + /// Optional. The record index to start at. All items with a lower index will be dropped from the results. + /// Optional. The maximum number of records to return. + /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. + /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. + /// Optional. Specify one or more channel id's, comma delimited. + /// Latest channel items. + public async Task>> GetLatestChannelItems( + [FromQuery] Guid? userId, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] string filters, + [FromQuery] string fields, + [FromQuery] string channelIds) + { + var user = userId == null + ? null + : _userManager.GetUserById(userId.Value); + + var query = new InternalItemsQuery(user) + { + Limit = limit, + StartIndex = startIndex, + ChannelIds = + (channelIds ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)) + .Select(i => new Guid(i)).ToArray(), + DtoOptions = new DtoOptions { Fields = RequestExtensions.GetItemFields(fields) } + }; + + foreach (var filter in RequestExtensions.GetFilters(filters)) + { + switch (filter) + { + case ItemFilter.IsFolder: + query.IsFolder = true; + break; + case ItemFilter.IsNotFolder: + query.IsFolder = false; + break; + case ItemFilter.IsUnplayed: + query.IsPlayed = false; + break; + case ItemFilter.IsPlayed: + query.IsPlayed = true; + break; + case ItemFilter.IsFavorite: + query.IsFavorite = true; + break; + case ItemFilter.IsResumable: + query.IsResumable = true; + break; + case ItemFilter.Likes: + query.IsLiked = true; + break; + case ItemFilter.Dislikes: + query.IsLiked = false; + break; + case ItemFilter.IsFavoriteOrLikes: + query.IsFavoriteOrLiked = true; + break; + } + } + + return await _channelManager.GetLatestChannelItems(query, CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Api/Extensions/RequestExtensions.cs b/Jellyfin.Api/Extensions/RequestExtensions.cs new file mode 100644 index 000000000..b9d11dfef --- /dev/null +++ b/Jellyfin.Api/Extensions/RequestExtensions.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Api.Extensions +{ + /// + /// Request Extensions. + /// + public static class RequestExtensions + { + /// + /// Get Order By. + /// + /// Sort By. Comma delimited string. + /// Sort Order. Comma delimited string. + /// Order By. + public static ValueTuple[] GetOrderBy(string sortBy, string requestedSortOrder) + { + var val = sortBy; + + if (string.IsNullOrEmpty(val)) + { + return Array.Empty>(); + } + + var vals = val.Split(','); + if (string.IsNullOrWhiteSpace(requestedSortOrder)) + { + requestedSortOrder = "Ascending"; + } + + var sortOrders = requestedSortOrder.Split(','); + + var result = new ValueTuple[vals.Length]; + + for (var i = 0; i < vals.Length; i++) + { + var sortOrderIndex = sortOrders.Length > i ? i : 0; + + var sortOrderValue = sortOrders.Length > sortOrderIndex ? sortOrders[sortOrderIndex] : null; + var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase) + ? SortOrder.Descending + : SortOrder.Ascending; + + result[i] = new ValueTuple(vals[i], sortOrder); + } + + return result; + } + + /// + /// Gets the item fields. + /// + /// The fields. + /// IEnumerable{ItemFields}. + public static ItemFields[] GetItemFields(string fields) + { + if (string.IsNullOrEmpty(fields)) + { + return Array.Empty(); + } + + return fields.Split(',').Select(v => + { + if (Enum.TryParse(v, true, out ItemFields value)) + { + return (ItemFields?)value; + } + + return null; + }).Where(i => i.HasValue).Select(i => i.Value).ToArray(); + } + + /// + /// Get parsed filters. + /// + /// The filters. + /// Item filters. + public static IEnumerable GetFilters(string filters) + { + return string.IsNullOrEmpty(filters) + ? Array.Empty() + : filters.Split(',').Select(v => Enum.Parse(v, true)); + } + } +} diff --git a/MediaBrowser.Api/ChannelService.cs b/MediaBrowser.Api/ChannelService.cs deleted file mode 100644 index fd9b8c396..000000000 --- a/MediaBrowser.Api/ChannelService.cs +++ /dev/null @@ -1,341 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Api.UserLibrary; -using MediaBrowser.Controller.Channels; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Channels; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - [Route("/Channels", "GET", Summary = "Gets available channels")] - public class GetChannels : IReturn> - { - /// - /// Gets or sets the user id. - /// - /// The user id. - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - /// - /// Skips over a given number of items within the results. Use for paging. - /// - /// The start index. - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// - /// 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; } - - [ApiMember(Name = "SupportsLatestItems", Description = "Optional. Filter by channels that support getting latest items.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] - public bool? SupportsLatestItems { get; set; } - - public bool? SupportsMediaDeletion { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is favorite. - /// - /// null if [is favorite] contains no value, true if [is favorite]; otherwise, false. - public bool? IsFavorite { get; set; } - } - - [Route("/Channels/{Id}/Features", "GET", Summary = "Gets features for a channel")] - public class GetChannelFeatures : IReturn - { - [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Channels/Features", "GET", Summary = "Gets features for a channel")] - public class GetAllChannelFeatures : IReturn - { - } - - [Route("/Channels/{Id}/Items", "GET", Summary = "Gets channel items")] - public class GetChannelItems : IReturn>, IHasItemFields - { - [ApiMember(Name = "Id", Description = "Channel Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "FolderId", Description = "Folder Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string FolderId { get; set; } - - /// - /// Gets or sets the user id. - /// - /// The user id. - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - /// - /// Skips over a given number of items within the results. Use for paging. - /// - /// The start index. - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// - /// 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; } - - [ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string SortOrder { get; set; } - - [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; } - - [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)] - public string SortBy { get; set; } - - [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)] - public string Fields { get; set; } - - /// - /// Gets the filters. - /// - /// IEnumerable{ItemFilter}. - public IEnumerable GetFilters() - { - var val = Filters; - - return string.IsNullOrEmpty(val) - ? Array.Empty() - : val.Split(',').Select(v => Enum.Parse(v, true)); - } - - /// - /// Gets the order by. - /// - /// IEnumerable{ItemSortBy}. - public ValueTuple[] GetOrderBy() - { - return BaseItemsRequest.GetOrderBy(SortBy, SortOrder); - } - } - - [Route("/Channels/Items/Latest", "GET", Summary = "Gets channel items")] - public class GetLatestChannelItems : IReturn>, IHasItemFields - { - /// - /// Gets or sets the user id. - /// - /// The user id. - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - - /// - /// Skips over a given number of items within the results. Use for paging. - /// - /// The start index. - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// - /// 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; } - - [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; } - - [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)] - public string Fields { get; set; } - - [ApiMember(Name = "ChannelIds", Description = "Optional. Specify one or more channel id's, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string ChannelIds { get; set; } - - /// - /// Gets the filters. - /// - /// IEnumerable{ItemFilter}. - public IEnumerable GetFilters() - { - return string.IsNullOrEmpty(Filters) - ? Array.Empty() - : Filters.Split(',').Select(v => Enum.Parse(v, true)); - } - } - - [Authenticated] - public class ChannelService : BaseApiService - { - private readonly IChannelManager _channelManager; - private IUserManager _userManager; - - public ChannelService( - ILogger logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IChannelManager channelManager, - IUserManager userManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _channelManager = channelManager; - _userManager = userManager; - } - - public object Get(GetAllChannelFeatures request) - { - var result = _channelManager.GetAllChannelFeatures(); - - return ToOptimizedResult(result); - } - - public object Get(GetChannelFeatures request) - { - var result = _channelManager.GetChannelFeatures(request.Id); - - return ToOptimizedResult(result); - } - - public object Get(GetChannels request) - { - var result = _channelManager.GetChannels(new ChannelQuery - { - Limit = request.Limit, - StartIndex = request.StartIndex, - UserId = request.UserId, - SupportsLatestItems = request.SupportsLatestItems, - SupportsMediaDeletion = request.SupportsMediaDeletion, - IsFavorite = request.IsFavorite - }); - - return ToOptimizedResult(result); - } - - public async Task Get(GetChannelItems request) - { - var user = request.UserId.Equals(Guid.Empty) - ? null - : _userManager.GetUserById(request.UserId); - - var query = new InternalItemsQuery(user) - { - Limit = request.Limit, - StartIndex = request.StartIndex, - ChannelIds = new[] { new Guid(request.Id) }, - ParentId = string.IsNullOrWhiteSpace(request.FolderId) ? Guid.Empty : new Guid(request.FolderId), - OrderBy = request.GetOrderBy(), - DtoOptions = new Controller.Dto.DtoOptions - { - Fields = request.GetItemFields() - } - - }; - - foreach (var filter in request.GetFilters()) - { - switch (filter) - { - case ItemFilter.Dislikes: - query.IsLiked = false; - break; - case ItemFilter.IsFavorite: - query.IsFavorite = true; - break; - case ItemFilter.IsFavoriteOrLikes: - query.IsFavoriteOrLiked = true; - break; - case ItemFilter.IsFolder: - query.IsFolder = true; - break; - case ItemFilter.IsNotFolder: - query.IsFolder = false; - break; - case ItemFilter.IsPlayed: - query.IsPlayed = true; - break; - case ItemFilter.IsResumable: - query.IsResumable = true; - break; - case ItemFilter.IsUnplayed: - query.IsPlayed = false; - break; - case ItemFilter.Likes: - query.IsLiked = true; - break; - } - } - - var result = await _channelManager.GetChannelItems(query, CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - - public async Task Get(GetLatestChannelItems request) - { - var user = request.UserId.Equals(Guid.Empty) - ? null - : _userManager.GetUserById(request.UserId); - - var query = new InternalItemsQuery(user) - { - Limit = request.Limit, - StartIndex = request.StartIndex, - ChannelIds = (request.ChannelIds ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).Select(i => new Guid(i)).ToArray(), - DtoOptions = new Controller.Dto.DtoOptions - { - Fields = request.GetItemFields() - } - }; - - foreach (var filter in request.GetFilters()) - { - switch (filter) - { - case ItemFilter.Dislikes: - query.IsLiked = false; - break; - case ItemFilter.IsFavorite: - query.IsFavorite = true; - break; - case ItemFilter.IsFavoriteOrLikes: - query.IsFavoriteOrLiked = true; - break; - case ItemFilter.IsFolder: - query.IsFolder = true; - break; - case ItemFilter.IsNotFolder: - query.IsFolder = false; - break; - case ItemFilter.IsPlayed: - query.IsPlayed = true; - break; - case ItemFilter.IsResumable: - query.IsResumable = true; - break; - case ItemFilter.IsUnplayed: - query.IsPlayed = false; - break; - case ItemFilter.Likes: - query.IsLiked = true; - break; - } - } - - var result = await _channelManager.GetLatestChannelItems(query, CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - } -} -- cgit v1.2.3 From bb8e738a0817be2e13a8b21929d0f0aeb0c6a461 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 23 Apr 2020 10:03:54 -0600 Subject: Fix Authorize attributes --- Jellyfin.Api/Controllers/ConfigurationController.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs index 14e45833f..b508ac054 100644 --- a/Jellyfin.Api/Controllers/ConfigurationController.cs +++ b/Jellyfin.Api/Controllers/ConfigurationController.cs @@ -1,12 +1,13 @@ #nullable enable using System.Threading.Tasks; +using Jellyfin.Api.Constants; using Jellyfin.Api.Models.ConfigurationDtos; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Serialization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; @@ -17,7 +18,7 @@ namespace Jellyfin.Api.Controllers /// Configuration Controller. /// [Route("System")] - [Authenticated] + [Authorize] public class ConfigurationController : BaseJellyfinApiController { private readonly IServerConfigurationManager _configurationManager; @@ -48,7 +49,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetConfiguration() { - return Ok(_configurationManager.Configuration); + return _configurationManager.Configuration; } /// @@ -57,7 +58,7 @@ namespace Jellyfin.Api.Controllers /// Configuration. /// Status. [HttpPost("Configuration")] - [Authenticated(Roles = "Admin")] + [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult UpdateConfiguration([FromBody, BindRequired] ServerConfiguration configuration) { @@ -74,7 +75,7 @@ namespace Jellyfin.Api.Controllers [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetNamedConfiguration([FromRoute] string key) { - return Ok(_configurationManager.GetConfiguration(key)); + return _configurationManager.GetConfiguration(key); } /// @@ -83,7 +84,7 @@ namespace Jellyfin.Api.Controllers /// Configuration key. /// Status. [HttpPost("Configuration/{Key}")] - [Authenticated(Roles = "Admin")] + [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status200OK)] public async Task UpdateNamedConfiguration([FromRoute] string key) { @@ -104,11 +105,11 @@ namespace Jellyfin.Api.Controllers /// /// MetadataOptions. [HttpGet("Configuration/MetadataOptions/Default")] - [Authenticated(Roles = "Admin")] + [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetDefaultMetadataOptions() { - return Ok(new MetadataOptions()); + return new MetadataOptions(); } /// @@ -117,7 +118,7 @@ namespace Jellyfin.Api.Controllers /// Media encoder path form body. /// Status. [HttpPost("MediaEncoder/Path")] - [Authenticated(Roles = "Admin", AllowBeforeStartupWizard = true)] + [Authorize(Policy = Policies.FirstTimeSetupOrElevated)] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult UpdateMediaEncoderPath([FromForm, BindRequired] MediaEncoderPathDto mediaEncoderPath) { -- cgit v1.2.3 From f3da5dc8b7fef7e5fdeddff941c6d99063a1fd97 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 23 Apr 2020 10:04:37 -0600 Subject: Fix Authorize attributes --- Jellyfin.Api/Controllers/AttachmentsController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/AttachmentsController.cs b/Jellyfin.Api/Controllers/AttachmentsController.cs index 351401de1..b0cdfb86e 100644 --- a/Jellyfin.Api/Controllers/AttachmentsController.cs +++ b/Jellyfin.Api/Controllers/AttachmentsController.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -16,7 +16,7 @@ namespace Jellyfin.Api.Controllers /// Attachments controller. /// [Route("Videos")] - [Authenticated] + [Authorize] public class AttachmentsController : Controller { private readonly ILibraryManager _libraryManager; -- cgit v1.2.3 From 311f2e2bc317cea7ac4d4cc783b961793bb997d5 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 23 Apr 2020 10:07:21 -0600 Subject: Fix Authorize attributes --- Jellyfin.Api/Controllers/DisplayPreferencesController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs index 42e87edd6..0d375e668 100644 --- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs +++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs @@ -2,9 +2,9 @@ using System.ComponentModel.DataAnnotations; using System.Threading; -using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; @@ -14,7 +14,7 @@ namespace Jellyfin.Api.Controllers /// /// Display Preferences Controller. /// - [Authenticated] + [Authorize] public class DisplayPreferencesController : BaseJellyfinApiController { private readonly IDisplayPreferencesRepository _displayPreferencesRepository; -- cgit v1.2.3 From 3c34d956088430da08bdd812c05d6a87c3bf9d25 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 23 Apr 2020 21:23:29 -0600 Subject: Address comments --- Jellyfin.Server/Middleware/ExceptionMiddleware.cs | 36 ++++++++++++++++++----- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs index ecc76594e..6ebe01503 100644 --- a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs +++ b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs @@ -1,8 +1,10 @@ using System; using System.IO; using System.Net.Mime; +using System.Net.Sockets; using System.Threading.Tasks; using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using Microsoft.AspNetCore.Http; @@ -55,15 +57,35 @@ namespace Jellyfin.Server.Middleware } ex = GetActualException(ex); - _logger.LogError( - ex, - "Error processing request: {ExceptionMessage}. URL {Method} {Url}. ", - ex.Message, - context.Request.Method, - context.Request.Path); + + bool ignoreStackTrace = + ex is SocketException + || ex is IOException + || ex is OperationCanceledException + || ex is SecurityException + || ex is AuthenticationException + || ex is FileNotFoundException; + + if (ignoreStackTrace) + { + _logger.LogError( + "Error processing request: {ExceptionMessage}. URL {Method} {Url}.", + ex.Message.TrimEnd('.'), + context.Request.Method, + context.Request.Path); + } + else + { + _logger.LogError( + ex, + "Error processing request. URL {Method} {Url}.", + ex.Message.TrimEnd('.'), + context.Request.Method, + context.Request.Path); + } + context.Response.StatusCode = GetStatusCode(ex); context.Response.ContentType = MediaTypeNames.Text.Plain; - var errorContent = NormalizeExceptionMessage(ex.Message); await context.Response.WriteAsync(errorContent).ConfigureAwait(false); } -- cgit v1.2.3 From be50fae38a27878cb520e20ed7956a72d7b05a84 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 23 Apr 2020 21:24:40 -0600 Subject: Address comments --- Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs | 10 ---------- Jellyfin.Server/Startup.cs | 3 ++- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs index 6c105ab65..0bd654c7d 100644 --- a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs @@ -24,15 +24,5 @@ namespace Jellyfin.Server.Extensions c.SwaggerEndpoint("/swagger/v1/swagger.json", "Jellyfin API V1"); }); } - - /// - /// Adds exception middleware to the application pipeline. - /// - /// The application builder. - /// The updated application builder. - public static IApplicationBuilder UseExceptionMiddleware(this IApplicationBuilder applicationBuilder) - { - return applicationBuilder.UseMiddleware(); - } } } diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs index 7a632f6c4..b17357fc3 100644 --- a/Jellyfin.Server/Startup.cs +++ b/Jellyfin.Server/Startup.cs @@ -1,4 +1,5 @@ using Jellyfin.Server.Extensions; +using Jellyfin.Server.Middleware; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using Microsoft.AspNetCore.Builder; @@ -58,7 +59,7 @@ namespace Jellyfin.Server app.UseDeveloperExceptionPage(); } - app.UseExceptionMiddleware(); + app.UseMiddleware(); app.UseWebSockets(); -- cgit v1.2.3 From b8508a57d8320085c01a7e2d4656b233169584f2 Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 23 Apr 2020 21:38:40 -0600 Subject: oop --- Jellyfin.Server/Middleware/ExceptionMiddleware.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs index 6ebe01503..0d79bbfaf 100644 --- a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs +++ b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs @@ -79,7 +79,6 @@ namespace Jellyfin.Server.Middleware _logger.LogError( ex, "Error processing request. URL {Method} {Url}.", - ex.Message.TrimEnd('.'), context.Request.Method, context.Request.Path); } -- cgit v1.2.3 From 0765ef8bda4d23e33fde7a1bfe49b5a365c6d28e Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 23 Apr 2020 21:41:10 -0600 Subject: Apply suggestions, fix warning --- Jellyfin.Server/Models/JsonOptions.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Jellyfin.Server/Models/JsonOptions.cs b/Jellyfin.Server/Models/JsonOptions.cs index fa503bc9a..2f0df3d2c 100644 --- a/Jellyfin.Server/Models/JsonOptions.cs +++ b/Jellyfin.Server/Models/JsonOptions.cs @@ -7,11 +7,6 @@ namespace Jellyfin.Server.Models /// public static class JsonOptions { - /// - /// Base Json Serializer Options. - /// - private static readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions(); - /// /// Gets CamelCase json options. /// @@ -19,7 +14,7 @@ namespace Jellyfin.Server.Models { get { - var options = _jsonOptions; + var options = DefaultJsonOptions; options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; return options; } @@ -32,10 +27,15 @@ namespace Jellyfin.Server.Models { get { - var options = _jsonOptions; + var options = DefaultJsonOptions; options.PropertyNamingPolicy = null; return options; } } + + /// + /// Gets base Json Serializer Options. + /// + private static JsonSerializerOptions DefaultJsonOptions => new JsonSerializerOptions(); } } -- cgit v1.2.3 From 33390153fdfec33e4149c12dd3a876248f4e08cc Mon Sep 17 00:00:00 2001 From: ConfusedPolarBear <33811686+ConfusedPolarBear@users.noreply.github.com> Date: Thu, 23 Apr 2020 23:44:15 -0500 Subject: Minor fix --- .../QuickConnect/QuickConnectManager.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs index 671ddc2b9..e24dc3a67 100644 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs @@ -135,7 +135,7 @@ namespace Emby.Server.Implementations.QuickConnect /// public QuickConnectResult TryConnect(string friendlyName) { - ExpireRequests(true); + ExpireRequests(); if (State != QuickConnectState.Active) { @@ -282,18 +282,17 @@ namespace Emby.Server.Implementations.QuickConnect return string.Join(string.Empty, bytes.Select(x => x.ToString("x2", CultureInfo.InvariantCulture))); } - private void ExpireRequests(bool onlyCheckTime = false) + private void ExpireRequests() { + bool expireAll = false; + // check if quick connect should be deactivated if (TemporaryActivation && DateTime.Now > DateActivated.AddMinutes(10) && State == QuickConnectState.Active) { _logger.LogDebug("Quick connect time expired, deactivating"); SetEnabled(QuickConnectState.Available); - } - - if (onlyCheckTime) - { - return; + expireAll = true; + TemporaryActivation = false; } // expire stale connection requests @@ -302,7 +301,7 @@ namespace Emby.Server.Implementations.QuickConnect for (int i = 0; i < _currentRequests.Count; i++) { - if (DateTime.Now > values[i].DateAdded.AddMinutes(RequestExpiry)) + if (DateTime.Now > values[i].DateAdded.AddMinutes(RequestExpiry) || expireAll) { delete.Add(values[i].Lookup); } -- cgit v1.2.3 From 85853f9ce3d77469b84e3334d7080cd025474ee8 Mon Sep 17 00:00:00 2001 From: ZadenRB Date: Fri, 24 Apr 2020 17:11:11 -0600 Subject: Add back in return type documentation --- Jellyfin.Api/Controllers/NotificationsController.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs index 0bf3aa1b4..8da2a6c53 100644 --- a/Jellyfin.Api/Controllers/NotificationsController.cs +++ b/Jellyfin.Api/Controllers/NotificationsController.cs @@ -95,6 +95,7 @@ namespace Jellyfin.Api.Controllers /// The description of the notification. /// The URL of the notification. /// The level of the notification. + /// Status. [HttpPost("Admin")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult CreateAdminNotification( @@ -123,6 +124,7 @@ namespace Jellyfin.Api.Controllers /// /// The userID. /// A comma-separated list of the IDs of notifications which should be set as read. + /// Status. [HttpPost("{UserID}/Read")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult SetRead( @@ -137,6 +139,7 @@ namespace Jellyfin.Api.Controllers /// /// The userID. /// A comma-separated list of the IDs of notifications which should be set as unread. + /// Status. [HttpPost("{UserID}/Unread")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult SetUnread( -- cgit v1.2.3 From 70e50dfa90575cc5e906be1509d3ed363eb1ada4 Mon Sep 17 00:00:00 2001 From: ConfusedPolarBear <33811686+ConfusedPolarBear@users.noreply.github.com> Date: Fri, 24 Apr 2020 18:51:19 -0500 Subject: Apply suggestions from code review --- .../QuickConnect/ConfigurationExtension.cs | 2 -- Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs | 8 +++----- MediaBrowser.Model/QuickConnect/QuickConnectState.cs | 6 +++--- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Emby.Server.Implementations/QuickConnect/ConfigurationExtension.cs b/Emby.Server.Implementations/QuickConnect/ConfigurationExtension.cs index 0e35ba80a..458bb7614 100644 --- a/Emby.Server.Implementations/QuickConnect/ConfigurationExtension.cs +++ b/Emby.Server.Implementations/QuickConnect/ConfigurationExtension.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; using MediaBrowser.Common.Configuration; diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs index e24dc3a67..b8b51adb6 100644 --- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs +++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs @@ -42,7 +42,7 @@ namespace Emby.Server.Implementations.QuickConnect /// Should only be called at server startup when a singleton is created. /// /// Configuration. - /// Logger. + /// Logger. /// User manager. /// Localization. /// JSON serializer. @@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.QuickConnect /// Task scheduler. public QuickConnectManager( IServerConfigurationManager config, - ILoggerFactory loggerFactory, + ILogger logger, IUserManager userManager, ILocalizationManager localization, IJsonSerializer jsonSerializer, @@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.QuickConnect ITaskManager taskManager) { _config = config; - _logger = loggerFactory.CreateLogger(nameof(QuickConnectManager)); + _logger = logger; _userManager = userManager; _localizationManager = localization; _jsonSerializer = jsonSerializer; @@ -196,8 +196,6 @@ namespace Emby.Server.Implementations.QuickConnect /// public string GenerateCode() { - // TODO: output may be biased - int min = (int)Math.Pow(10, CodeLength - 1); int max = (int)Math.Pow(10, CodeLength); diff --git a/MediaBrowser.Model/QuickConnect/QuickConnectState.cs b/MediaBrowser.Model/QuickConnect/QuickConnectState.cs index 9f250519b..f1074f25f 100644 --- a/MediaBrowser.Model/QuickConnect/QuickConnectState.cs +++ b/MediaBrowser.Model/QuickConnect/QuickConnectState.cs @@ -8,16 +8,16 @@ namespace MediaBrowser.Model.QuickConnect /// /// This feature has not been opted into and is unavailable until the server administrator chooses to opt-in. /// - Unavailable, + Unavailable = 0, /// /// The feature is enabled for use on the server but is not currently accepting connection requests. /// - Available, + Available = 1, /// /// The feature is actively accepting connection requests. /// - Active + Active = 2 } } -- cgit v1.2.3 From 714aaefbcc3abf0b952efc831001cf42e1c873b0 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 24 Apr 2020 18:20:36 -0600 Subject: Transfer EnvironmentService to Jellyfin.Api --- Jellyfin.Api/Controllers/EnvironmentController.cs | 200 ++++++++++++++ .../EnvironmentDtos/DefaultDirectoryBrowserInfo.cs | 13 + .../Models/EnvironmentDtos/ValidatePathDto.cs | 23 ++ MediaBrowser.Api/EnvironmentService.cs | 296 --------------------- 4 files changed, 236 insertions(+), 296 deletions(-) create mode 100644 Jellyfin.Api/Controllers/EnvironmentController.cs create mode 100644 Jellyfin.Api/Models/EnvironmentDtos/DefaultDirectoryBrowserInfo.cs create mode 100644 Jellyfin.Api/Models/EnvironmentDtos/ValidatePathDto.cs delete mode 100644 MediaBrowser.Api/EnvironmentService.cs diff --git a/Jellyfin.Api/Controllers/EnvironmentController.cs b/Jellyfin.Api/Controllers/EnvironmentController.cs new file mode 100644 index 000000000..139c1af08 --- /dev/null +++ b/Jellyfin.Api/Controllers/EnvironmentController.cs @@ -0,0 +1,200 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Jellyfin.Api.Constants; +using Jellyfin.Api.Models.EnvironmentDtos; +using MediaBrowser.Model.IO; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Jellyfin.Api.Controllers +{ + /// + /// Environment Controller. + /// + [Authorize(Policy = Policies.RequiresElevation)] + public class EnvironmentController : BaseJellyfinApiController + { + private const char UncSeparator = '\\'; + private const string UncSeparatorString = "\\"; + + private readonly IFileSystem _fileSystem; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + public EnvironmentController(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + } + + /// + /// Gets the contents of a given directory in the file system. + /// + /// The path. + /// An optional filter to include or exclude files from the results. true/false. + /// An optional filter to include or exclude folders from the results. true/false. + /// File system entries. + [HttpGet("DirectoryContents")] + [ProducesResponseType(StatusCodes.Status200OK)] + public IEnumerable GetDirectoryContents( + [FromQuery, BindRequired] string path, + [FromQuery] bool includeFiles, + [FromQuery] bool includeDirectories) + { + const string networkPrefix = UncSeparatorString + UncSeparatorString; + if (path.StartsWith(networkPrefix, StringComparison.OrdinalIgnoreCase) + && path.LastIndexOf(UncSeparator) == 1) + { + return Array.Empty(); + } + + var entries = _fileSystem.GetFileSystemEntries(path).OrderBy(i => i.FullName).Where(i => + { + var isDirectory = i.IsDirectory; + + if (!includeFiles && !isDirectory) + { + return false; + } + + return includeDirectories || !isDirectory; + }); + + return entries.Select(f => new FileSystemEntryInfo + { + Name = f.Name, + Path = f.FullName, + Type = f.IsDirectory ? FileSystemEntryType.Directory : FileSystemEntryType.File + }); + } + + /// + /// Validates path. + /// + /// Validate request object. + /// Status. + [HttpPost("ValidatePath")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult ValidatePath([FromBody, BindRequired] ValidatePathDto validatePathDto) + { + if (validatePathDto.IsFile.HasValue) + { + if (validatePathDto.IsFile.Value) + { + if (!System.IO.File.Exists(validatePathDto.Path)) + { + return NotFound(); + } + } + else + { + if (!Directory.Exists(validatePathDto.Path)) + { + return NotFound(); + } + } + } + else + { + if (!System.IO.File.Exists(validatePathDto.Path) && !Directory.Exists(validatePathDto.Path)) + { + return NotFound(); + } + + if (validatePathDto.ValidateWritable) + { + var file = Path.Combine(validatePathDto.Path, Guid.NewGuid().ToString()); + try + { + System.IO.File.WriteAllText(file, string.Empty); + } + finally + { + if (System.IO.File.Exists(file)) + { + System.IO.File.Delete(file); + } + } + } + } + + return Ok(); + } + + /// + /// Gets network paths. + /// + /// List of entries. + [Obsolete("This endpoint is obsolete.")] + [HttpGet("NetworkShares")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult> GetNetworkShares() + { + return Array.Empty(); + } + + /// + /// Gets available drives from the server's file system. + /// + /// List of entries. + [HttpGet("Drives")] + [ProducesResponseType(StatusCodes.Status200OK)] + public IEnumerable GetDrives() + { + return _fileSystem.GetDrives().Select(d => new FileSystemEntryInfo + { + Name = d.Name, + Path = d.FullName, + Type = FileSystemEntryType.Directory + }); + } + + /// + /// Gets the parent path of a given path. + /// + /// The path. + /// Parent path. + [HttpGet("ParentPath")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetParentPath([FromQuery, BindRequired] string path) + { + string? parent = Path.GetDirectoryName(path); + if (string.IsNullOrEmpty(parent)) + { + // Check if unc share + var index = path.LastIndexOf(UncSeparator); + + if (index != -1 && path.IndexOf(UncSeparator, StringComparison.OrdinalIgnoreCase) == 0) + { + parent = path.Substring(0, index); + + if (string.IsNullOrWhiteSpace(parent.TrimStart(UncSeparator))) + { + parent = null; + } + } + } + + return parent; + } + + /// + /// Get Default directory browser. + /// + /// Default directory browser. + [HttpGet("DefaultDirectoryBrowser")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetDefaultDirectoryBrowser() + { + return new DefaultDirectoryBrowserInfo(); + } + } +} diff --git a/Jellyfin.Api/Models/EnvironmentDtos/DefaultDirectoryBrowserInfo.cs b/Jellyfin.Api/Models/EnvironmentDtos/DefaultDirectoryBrowserInfo.cs new file mode 100644 index 000000000..6b1c750bf --- /dev/null +++ b/Jellyfin.Api/Models/EnvironmentDtos/DefaultDirectoryBrowserInfo.cs @@ -0,0 +1,13 @@ +namespace Jellyfin.Api.Models.EnvironmentDtos +{ + /// + /// Default directory browser info. + /// + public class DefaultDirectoryBrowserInfo + { + /// + /// Gets or sets the path. + /// + public string Path { get; set; } + } +} diff --git a/Jellyfin.Api/Models/EnvironmentDtos/ValidatePathDto.cs b/Jellyfin.Api/Models/EnvironmentDtos/ValidatePathDto.cs new file mode 100644 index 000000000..60c82e166 --- /dev/null +++ b/Jellyfin.Api/Models/EnvironmentDtos/ValidatePathDto.cs @@ -0,0 +1,23 @@ +namespace Jellyfin.Api.Models.EnvironmentDtos +{ + /// + /// Validate path object. + /// + public class ValidatePathDto + { + /// + /// Gets or sets a value indicating whether validate if path is writable. + /// + public bool ValidateWritable { get; set; } + + /// + /// Gets or sets the path. + /// + public string Path { get; set; } + + /// + /// Gets or sets is path file. + /// + public bool? IsFile { get; set; } + } +} diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs deleted file mode 100644 index d199ce154..000000000 --- a/MediaBrowser.Api/EnvironmentService.cs +++ /dev/null @@ -1,296 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - /// - /// Class GetDirectoryContents - /// - [Route("/Environment/DirectoryContents", "GET", Summary = "Gets the contents of a given directory in the file system")] - public class GetDirectoryContents : IReturn> - { - /// - /// Gets or sets the path. - /// - /// The path. - [ApiMember(Name = "Path", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Path { get; set; } - - /// - /// Gets or sets a value indicating whether [include files]. - /// - /// true if [include files]; otherwise, false. - [ApiMember(Name = "IncludeFiles", Description = "An optional filter to include or exclude files from the results. true/false", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] - public bool IncludeFiles { get; set; } - - /// - /// Gets or sets a value indicating whether [include directories]. - /// - /// true if [include directories]; otherwise, false. - [ApiMember(Name = "IncludeDirectories", Description = "An optional filter to include or exclude folders from the results. true/false", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] - public bool IncludeDirectories { get; set; } - } - - [Route("/Environment/ValidatePath", "POST", Summary = "Gets the contents of a given directory in the file system")] - public class ValidatePath - { - /// - /// Gets or sets the path. - /// - /// The path. - [ApiMember(Name = "Path", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Path { get; set; } - - public bool ValidateWriteable { get; set; } - public bool? IsFile { get; set; } - } - - [Obsolete] - [Route("/Environment/NetworkShares", "GET", Summary = "Gets shares from a network device")] - public class GetNetworkShares : IReturn> - { - /// - /// Gets or sets the path. - /// - /// The path. - [ApiMember(Name = "Path", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Path { get; set; } - } - - /// - /// Class GetDrives - /// - [Route("/Environment/Drives", "GET", Summary = "Gets available drives from the server's file system")] - public class GetDrives : IReturn> - { - } - - /// - /// Class GetNetworkComputers - /// - [Route("/Environment/NetworkDevices", "GET", Summary = "Gets a list of devices on the network")] - public class GetNetworkDevices : IReturn> - { - } - - [Route("/Environment/ParentPath", "GET", Summary = "Gets the parent path of a given path")] - public class GetParentPath : IReturn - { - /// - /// Gets or sets the path. - /// - /// The path. - [ApiMember(Name = "Path", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Path { get; set; } - } - - public class DefaultDirectoryBrowserInfo - { - public string Path { get; set; } - } - - [Route("/Environment/DefaultDirectoryBrowser", "GET", Summary = "Gets the parent path of a given path")] - public class GetDefaultDirectoryBrowser : IReturn - { - - } - - /// - /// Class EnvironmentService - /// - [Authenticated(Roles = "Admin", AllowBeforeStartupWizard = true)] - public class EnvironmentService : BaseApiService - { - private const char UncSeparator = '\\'; - private const string UncSeparatorString = "\\"; - - /// - /// The _network manager - /// - private readonly INetworkManager _networkManager; - private readonly IFileSystem _fileSystem; - - /// - /// Initializes a new instance of the class. - /// - /// The network manager. - public EnvironmentService( - ILogger logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - INetworkManager networkManager, - IFileSystem fileSystem) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _networkManager = networkManager; - _fileSystem = fileSystem; - } - - public void Post(ValidatePath request) - { - if (request.IsFile.HasValue) - { - if (request.IsFile.Value) - { - if (!File.Exists(request.Path)) - { - throw new FileNotFoundException("File not found", request.Path); - } - } - else - { - if (!Directory.Exists(request.Path)) - { - throw new FileNotFoundException("File not found", request.Path); - } - } - } - - else - { - if (!File.Exists(request.Path) && !Directory.Exists(request.Path)) - { - throw new FileNotFoundException("Path not found", request.Path); - } - - if (request.ValidateWriteable) - { - EnsureWriteAccess(request.Path); - } - } - } - - protected void EnsureWriteAccess(string path) - { - var file = Path.Combine(path, Guid.NewGuid().ToString()); - - File.WriteAllText(file, string.Empty); - _fileSystem.DeleteFile(file); - } - - public object Get(GetDefaultDirectoryBrowser request) => - ToOptimizedResult(new DefaultDirectoryBrowserInfo { Path = null }); - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetDirectoryContents request) - { - var path = request.Path; - - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException(nameof(Path)); - } - - var networkPrefix = UncSeparatorString + UncSeparatorString; - - if (path.StartsWith(networkPrefix, StringComparison.OrdinalIgnoreCase) - && path.LastIndexOf(UncSeparator) == 1) - { - return ToOptimizedResult(Array.Empty()); - } - - return ToOptimizedResult(GetFileSystemEntries(request).ToList()); - } - - [Obsolete] - public object Get(GetNetworkShares request) - => ToOptimizedResult(Array.Empty()); - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetDrives request) - { - var result = GetDrives().ToList(); - - return ToOptimizedResult(result); - } - - /// - /// Gets the list that is returned when an empty path is supplied - /// - /// IEnumerable{FileSystemEntryInfo}. - private IEnumerable GetDrives() - { - return _fileSystem.GetDrives().Select(d => new FileSystemEntryInfo - { - Name = d.Name, - Path = d.FullName, - Type = FileSystemEntryType.Directory - }); - } - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetNetworkDevices request) - => ToOptimizedResult(Array.Empty()); - - /// - /// Gets the file system entries. - /// - /// The request. - /// IEnumerable{FileSystemEntryInfo}. - private IEnumerable GetFileSystemEntries(GetDirectoryContents request) - { - var entries = _fileSystem.GetFileSystemEntries(request.Path).OrderBy(i => i.FullName).Where(i => - { - var isDirectory = i.IsDirectory; - - if (!request.IncludeFiles && !isDirectory) - { - return false; - } - - return request.IncludeDirectories || !isDirectory; - }); - - return entries.Select(f => new FileSystemEntryInfo - { - Name = f.Name, - Path = f.FullName, - Type = f.IsDirectory ? FileSystemEntryType.Directory : FileSystemEntryType.File - - }); - } - - public object Get(GetParentPath request) - { - var parent = Path.GetDirectoryName(request.Path); - - if (string.IsNullOrEmpty(parent)) - { - // Check if unc share - var index = request.Path.LastIndexOf(UncSeparator); - - if (index != -1 && request.Path.IndexOf(UncSeparator) == 0) - { - parent = request.Path.Substring(0, index); - - if (string.IsNullOrWhiteSpace(parent.TrimStart(UncSeparator))) - { - parent = null; - } - } - } - - return parent; - } - } -} -- cgit v1.2.3 From c7fe8b04cc854df110528a0eda4383263e99e554 Mon Sep 17 00:00:00 2001 From: Bruce Date: Sat, 25 Apr 2020 19:59:31 +0100 Subject: PackageService to Jellyfin.API --- Jellyfin.Api/Controllers/PackageController.cs | 115 +++++++++++++++++ MediaBrowser.Api/PackageService.cs | 171 -------------------------- 2 files changed, 115 insertions(+), 171 deletions(-) create mode 100644 Jellyfin.Api/Controllers/PackageController.cs delete mode 100644 MediaBrowser.Api/PackageService.cs diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs new file mode 100644 index 000000000..1fb9ab697 --- /dev/null +++ b/Jellyfin.Api/Controllers/PackageController.cs @@ -0,0 +1,115 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using Jellyfin.Api.Constants; +using MediaBrowser.Common.Updates; +using MediaBrowser.Model.Updates; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers +{ + /// + /// Package Controller. + /// + [Route("Packages")] + [Authorize] + public class PackageController : BaseJellyfinApiController + { + private readonly IInstallationManager _installationManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of Installation Manager. + public PackageController(IInstallationManager installationManager) + { + _installationManager = installationManager; + } + + /// + /// Gets a package, by name or assembly guid. + /// + /// The name of the package. + /// The guid of the associated assembly. + /// Package info. + [HttpGet("/{Name}")] + [ProducesResponseType(typeof(PackageInfo), StatusCodes.Status200OK)] + public ActionResult GetPackageInfo( + [FromRoute] [Required] string name, + [FromQuery] string? assemblyGuid) + { + var packages = _installationManager.GetAvailablePackages().GetAwaiter().GetResult(); + var result = _installationManager.FilterPackages( + packages, + name, + string.IsNullOrEmpty(assemblyGuid) ? default : Guid.Parse(assemblyGuid)).FirstOrDefault(); + + return Ok(result); + } + + /// + /// Gets available packages. + /// + /// Packages information. + [HttpGet] + [ProducesResponseType(typeof(PackageInfo[]), StatusCodes.Status200OK)] + public async Task> GetPackages() + { + IEnumerable packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false); + + return Ok(packages.ToArray()); + } + + /// + /// Installs a package. + /// + /// Package name. + /// Guid of the associated assembly. + /// Optional version. Defaults to latest version. + /// Status. + [HttpPost("/Installed/{Name}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task InstallPackage( + [FromRoute] [Required] string name, + [FromQuery] string assemblyGuid, + [FromQuery] string version) + { + var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false); + var package = _installationManager.GetCompatibleVersions( + packages, + name, + string.IsNullOrEmpty(assemblyGuid) ? Guid.Empty : Guid.Parse(assemblyGuid), + string.IsNullOrEmpty(version) ? null : Version.Parse(version)).FirstOrDefault(); + + if (package == null) + { + return NotFound(); + } + + await _installationManager.InstallPackage(package).ConfigureAwait(false); + + return Ok(); + } + + /// + /// Cancels a package installation. + /// + /// Installation Id. + /// Status. + [HttpDelete("/Installing/{id}")] + [Authorize(Policy = Policies.RequiresElevation)] + public IActionResult CancelPackageInstallation( + [FromRoute] [Required] string id) + { + _installationManager.CancelInstallation(new Guid(id)); + + return Ok(); + } + } +} diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs deleted file mode 100644 index 444354a99..000000000 --- a/MediaBrowser.Api/PackageService.cs +++ /dev/null @@ -1,171 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Updates; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.Updates; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api -{ - /// - /// Class GetPackage - /// - [Route("/Packages/{Name}", "GET", Summary = "Gets a package, by name or assembly guid")] - [Authenticated] - public class GetPackage : IReturn - { - /// - /// Gets or sets the name. - /// - /// The name. - [ApiMember(Name = "Name", Description = "The name of the package", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - - /// - /// Gets or sets the name. - /// - /// The name. - [ApiMember(Name = "AssemblyGuid", Description = "The guid of the associated assembly", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string AssemblyGuid { get; set; } - } - - /// - /// Class GetPackages - /// - [Route("/Packages", "GET", Summary = "Gets available packages")] - [Authenticated] - public class GetPackages : IReturn - { - } - - /// - /// Class InstallPackage - /// - [Route("/Packages/Installed/{Name}", "POST", Summary = "Installs a package")] - [Authenticated(Roles = "Admin")] - public class InstallPackage : IReturnVoid - { - /// - /// Gets or sets the name. - /// - /// The name. - [ApiMember(Name = "Name", Description = "Package name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Name { get; set; } - - /// - /// Gets or sets the name. - /// - /// The name. - [ApiMember(Name = "AssemblyGuid", Description = "Guid of the associated assembly", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string AssemblyGuid { get; set; } - - /// - /// Gets or sets the version. - /// - /// The version. - [ApiMember(Name = "Version", Description = "Optional version. Defaults to latest version.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string Version { get; set; } - } - - /// - /// Class CancelPackageInstallation - /// - [Route("/Packages/Installing/{Id}", "DELETE", Summary = "Cancels a package installation")] - [Authenticated(Roles = "Admin")] - public class CancelPackageInstallation : IReturnVoid - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Installation Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - } - - /// - /// Class PackageService - /// - public class PackageService : BaseApiService - { - private readonly IInstallationManager _installationManager; - - public PackageService( - ILogger logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IInstallationManager installationManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _installationManager = installationManager; - } - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetPackage request) - { - var packages = _installationManager.GetAvailablePackages().GetAwaiter().GetResult(); - var result = _installationManager.FilterPackages( - packages, - request.Name, - string.IsNullOrEmpty(request.AssemblyGuid) ? default : Guid.Parse(request.AssemblyGuid)).FirstOrDefault(); - - return ToOptimizedResult(result); - } - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public async Task Get(GetPackages request) - { - IEnumerable packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false); - - return ToOptimizedResult(packages.ToArray()); - } - - /// - /// Posts the specified request. - /// - /// The request. - /// - public async Task Post(InstallPackage request) - { - var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false); - var package = _installationManager.GetCompatibleVersions( - packages, - request.Name, - string.IsNullOrEmpty(request.AssemblyGuid) ? Guid.Empty : Guid.Parse(request.AssemblyGuid), - string.IsNullOrEmpty(request.Version) ? null : Version.Parse(request.Version)).FirstOrDefault(); - - if (package == null) - { - throw new ResourceNotFoundException( - string.Format( - CultureInfo.InvariantCulture, - "Package not found: {0}", - request.Name)); - } - - await _installationManager.InstallPackage(package); - } - - /// - /// Deletes the specified request. - /// - /// The request. - public void Delete(CancelPackageInstallation request) - { - _installationManager.CancelInstallation(new Guid(request.Id)); - } - } -} -- cgit v1.2.3 From f66714561e0fef18ba25c36abdf97ee62ccda007 Mon Sep 17 00:00:00 2001 From: Bruce Coelho Date: Sat, 25 Apr 2020 21:32:49 +0100 Subject: Update Jellyfin.Api/Controllers/PackageController.cs Applying requested changes to PackageController Co-Authored-By: Cody Robibero --- Jellyfin.Api/Controllers/PackageController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs index 1fb9ab697..ab4d20458 100644 --- a/Jellyfin.Api/Controllers/PackageController.cs +++ b/Jellyfin.Api/Controllers/PackageController.cs @@ -49,7 +49,7 @@ namespace Jellyfin.Api.Controllers name, string.IsNullOrEmpty(assemblyGuid) ? default : Guid.Parse(assemblyGuid)).FirstOrDefault(); - return Ok(result); + return result; } /// -- cgit v1.2.3 From 5aced0ea0f4bca17aee392698351d54b0ad50e26 Mon Sep 17 00:00:00 2001 From: Bruce Coelho Date: Sat, 25 Apr 2020 21:41:56 +0100 Subject: Apply suggestions from code review Co-Authored-By: Cody Robibero --- Jellyfin.Api/Controllers/PackageController.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs index ab4d20458..1da5ac0e9 100644 --- a/Jellyfin.Api/Controllers/PackageController.cs +++ b/Jellyfin.Api/Controllers/PackageController.cs @@ -39,11 +39,11 @@ namespace Jellyfin.Api.Controllers /// Package info. [HttpGet("/{Name}")] [ProducesResponseType(typeof(PackageInfo), StatusCodes.Status200OK)] - public ActionResult GetPackageInfo( + public async Task> GetPackageInfo( [FromRoute] [Required] string name, [FromQuery] string? assemblyGuid) { - var packages = _installationManager.GetAvailablePackages().GetAwaiter().GetResult(); + var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false); var result = _installationManager.FilterPackages( packages, name, @@ -58,11 +58,11 @@ namespace Jellyfin.Api.Controllers /// Packages information. [HttpGet] [ProducesResponseType(typeof(PackageInfo[]), StatusCodes.Status200OK)] - public async Task> GetPackages() + public async Task> GetPackages() { IEnumerable packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false); - return Ok(packages.ToArray()); + return packages; } /// @@ -75,6 +75,7 @@ namespace Jellyfin.Api.Controllers [HttpPost("/Installed/{Name}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] + [Authorize(Policy = Policies.RequiresElevation)] public async Task InstallPackage( [FromRoute] [Required] string name, [FromQuery] string assemblyGuid, -- cgit v1.2.3 From 890e659cd390fc45c68b42c1a20f24a33e8c1570 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 25 Apr 2020 15:12:18 -0600 Subject: Fix autolaunch & redirect of swagger. --- Emby.Server.Implementations/Browser/BrowserLauncher.cs | 4 +++- Jellyfin.Server/Program.cs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Browser/BrowserLauncher.cs b/Emby.Server.Implementations/Browser/BrowserLauncher.cs index 96096e142..384cb049f 100644 --- a/Emby.Server.Implementations/Browser/BrowserLauncher.cs +++ b/Emby.Server.Implementations/Browser/BrowserLauncher.cs @@ -1,5 +1,7 @@ using System; using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Browser @@ -24,7 +26,7 @@ namespace Emby.Server.Implementations.Browser /// The app host. public static void OpenSwaggerPage(IServerApplicationHost appHost) { - TryOpenUrl(appHost, "/swagger/index.html"); + TryOpenUrl(appHost, "/api-docs/v1/swagger"); } /// diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index e55b0d4ed..23ddcf159 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -529,7 +529,7 @@ namespace Jellyfin.Server var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration; if (startupConfig != null && !startupConfig.HostWebClient()) { - inMemoryDefaultConfig[HttpListenerHost.DefaultRedirectKey] = "swagger/index.html"; + inMemoryDefaultConfig[HttpListenerHost.DefaultRedirectKey] = "api-docs/v1/swagger"; } return config -- cgit v1.2.3 From 000088f8f94e24ea715f15b722a2e64958bec07b Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 25 Apr 2020 18:18:33 -0600 Subject: init --- Jellyfin.Api/Controllers/LibraryController.cs | 56 +++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 Jellyfin.Api/Controllers/LibraryController.cs diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs new file mode 100644 index 000000000..f45101c0c --- /dev/null +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -0,0 +1,56 @@ +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers +{ + /// + /// Library Controller. + /// + public class LibraryController : BaseJellyfinApiController + { + private readonly IProviderManager _providerManager; + private readonly ILibraryManager _libraryManager; + private readonly IUserManager _userManager; + private readonly IDtoService _dtoService; + private readonly IAuthorizationContext _authContext; + private readonly IActivityManager _activityManager; + private readonly ILocalizationManager _localization; + private readonly ILibraryMonitor _libraryMonitor; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public LibraryController( + IProviderManager providerManager, + ILibraryManager libraryManager, + IUserManager userManager, + IDtoService dtoService, + IAuthorizationContext authContext, + IActivityManager activityManager, + ILocalizationManager localization, + ILibraryMonitor libraryMonitor) + { + _providerManager = providerManager; + _libraryManager = libraryManager; + _userManager = userManager; + _dtoService = dtoService; + _authContext = authContext; + _activityManager = activityManager; + _localization = localization; + _libraryMonitor = libraryMonitor; + } + } +} -- cgit v1.2.3 From 57b5ec1d514ad8c5a0e8aced4b84b46b4c8923a7 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 26 Apr 2020 12:07:54 -0400 Subject: Remove unnecessary properties from SystemInfo response object These properties do not provide any useful information to the client. The client would already have to have all this information in order to connect to the endpoint to retrieve it --- Emby.Server.Implementations/ApplicationHost.cs | 4 ---- Jellyfin.Server/Program.cs | 3 --- .../Configuration/ServerConfiguration.cs | 5 ----- MediaBrowser.Model/System/SystemInfo.cs | 18 ------------------ 4 files changed, 30 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 9655d9f5e..edfed67a1 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -94,7 +94,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; @@ -1143,9 +1142,6 @@ namespace Emby.Server.Implementations ItemsByNamePath = ApplicationPaths.InternalMetadataPath, InternalMetadataPath = ApplicationPaths.InternalMetadataPath, CachePath = ApplicationPaths.CachePath, - HttpServerPortNumber = HttpPort, - SupportsHttps = ListenWithHttps || ServerConfigurationManager.Configuration.IsBehindProxy, - HttpsPortNumber = HttpsPort, OperatingSystem = OperatingSystem.Id.ToString(), OperatingSystemDisplayName = OperatingSystem.Name, CanSelfRestart = CanSelfRestart, diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 7aa238efa..1c586ffdd 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -10,14 +10,11 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using CommandLine; -using Emby.Drawing; using Emby.Server.Implementations; using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.IO; using Emby.Server.Implementations.Networking; -using Jellyfin.Drawing.Skia; using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Extensions; using MediaBrowser.WebDashboard.Api; using Microsoft.AspNetCore.Hosting; diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 6ee6a1f93..e4cd6c34a 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -234,11 +234,6 @@ namespace MediaBrowser.Model.Configuration /// public bool RequireHttps { get; set; } - /// - /// Gets or sets a value indicating whether the server is behind a reverse proxy. - /// - public bool IsBehindProxy { get; set; } - public bool EnableNewOmdbSupport { get; set; } public string[] RemoteIPFilter { get; set; } diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index 9753f4e06..f2c5aa1e3 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -115,24 +115,6 @@ namespace MediaBrowser.Model.System /// The transcode path. public string TranscodingTempPath { get; set; } - /// - /// Gets or sets the HTTP server port number. - /// - /// The HTTP server port number. - public int HttpServerPortNumber { get; set; } - - /// - /// Gets or sets a value indicating whether a client can connect to the server over HTTPS, either directly or - /// via a reverse proxy. - /// - public bool SupportsHttps { get; set; } - - /// - /// Gets or sets the HTTPS server port number. - /// - /// The HTTPS server port number. - public int HttpsPortNumber { get; set; } - /// /// Gets or sets a value indicating whether this instance has update available. /// -- cgit v1.2.3 From cabdec87e8e946bfa5de0dd0f97129f1a23e7d98 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 26 Apr 2020 16:55:00 -0400 Subject: Fix merge with master --- Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index adec9dbe2..878cee23c 100644 --- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -158,7 +158,7 @@ namespace Emby.Server.Implementations.EntryPoints { yield return CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort); - if (_appHost.EnableHttps) + if (_appHost.ListenWithHttps) { yield return CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort); } -- cgit v1.2.3 From 15fd4812f09282e9b81f53845ce462f42ff1b5e9 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 26 Apr 2020 18:04:34 -0400 Subject: Remove unnecessary foreach loop --- Emby.Server.Implementations/ApplicationHost.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index edfed67a1..8f20a4921 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1187,13 +1187,12 @@ namespace Emby.Server.Implementations { // Return the first matched address, if found, or the first known local address var addresses = await GetLocalIpAddressesInternal(false, 1, cancellationToken).ConfigureAwait(false); - - foreach (var address in addresses) + if (addresses.Count == 0) { - return GetLocalApiUrl(address); + return null; } - return null; + return GetLocalApiUrl(addresses.First()); } catch (Exception ex) { -- cgit v1.2.3 From 068368df6352cfad4e69df599c364b3f05b367ba Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 26 Apr 2020 23:28:32 -0600 Subject: Order actions by route, then http method --- Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 92bacb440..00a73ade6 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -105,6 +105,10 @@ namespace Jellyfin.Server.Extensions { c.IncludeXmlComments(xmlFile); } + + // Order actions by route path, then by http method. + c.OrderActionsBy(description => + $"{description.ActionDescriptor.RouteValues["controller"]}_{description.HttpMethod}"); }); } } -- 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 diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 4d906a1bf..8419014c2 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 c93c02c48..b1519b572 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 000000000..4a20ceba0 --- /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 000000000..6bfd6aa9b --- /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 000000000..f17cca9ee --- /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 771027103..4bfc0c73f 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 000000000..d37e8563b --- /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 000000000..7630428d7 --- /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 000000000..c9465b27a --- /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 000000000..ec91ea69d --- /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 000000000..599c0dbfc --- /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 000000000..769316e80 --- /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 000000000..87b9ad66d --- /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 000000000..c5c2f3540 --- /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 000000000..c7c5f534d --- /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 000000000..7dba74ae9 --- /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 000000000..44d7a0af2 --- /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 b3354ec6374e2491679c699aaff8ee407dd0ac7c Mon Sep 17 00:00:00 2001 From: gion Date: Fri, 3 Apr 2020 10:11:55 +0200 Subject: Ignore unrelated events --- Emby.Server.Implementations/Syncplay/SyncplayManager.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index 6bfd6aa9b..7583793bb 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -91,12 +91,16 @@ namespace Emby.Server.Implementations.Syncplay void _sessionManager_SessionEnded(object sender, SessionEventArgs e) { - LeaveGroup(e.SessionInfo); + var user = e.SessionInfo; + if (!IsUserInGroup(user)) return; + LeaveGroup(user); } void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) { - LeaveGroup(e.Session); + var user = e.Session; + if (!IsUserInGroup(user)) return; + LeaveGroup(user); } private bool IsUserInGroup(SessionInfo user) -- cgit v1.2.3 From f273995f5bd89f12322d80f3009ad6d8d20b8e81 Mon Sep 17 00:00:00 2001 From: gion Date: Sat, 4 Apr 2020 17:56:21 +0200 Subject: Refactor: rename user to session --- .../Syncplay/SyncplayController.cs | 124 ++++++++++----------- .../Syncplay/SyncplayManager.cs | 88 +++++++-------- MediaBrowser.Controller/Syncplay/GroupInfo.cs | 62 +++++------ .../Syncplay/ISyncplayController.cs | 24 ++-- .../Syncplay/ISyncplayManager.cs | 40 +++---- 5 files changed, 169 insertions(+), 169 deletions(-) diff --git a/Emby.Server.Implementations/Syncplay/SyncplayController.cs b/Emby.Server.Implementations/Syncplay/SyncplayController.cs index 4a20ceba0..b156e5a87 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayController.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayController.cs @@ -19,8 +19,8 @@ namespace Emby.Server.Implementations.Syncplay private enum BroadcastType { AllGroup = 0, - SingleUser = 1, - AllExceptUser = 2, + SingleSession = 1, + AllExceptSession = 2, AllReady = 3 } @@ -95,32 +95,32 @@ namespace Emby.Server.Implementations.Syncplay } } - private SessionInfo[] FilterUsers(SessionInfo from, BroadcastType type) + private SessionInfo[] FilterSessions(SessionInfo from, BroadcastType type) { - if (type == BroadcastType.SingleUser) + if (type == BroadcastType.SingleSession) { return new SessionInfo[] { from }; } else if (type == BroadcastType.AllGroup) { return _group.Partecipants.Values.Select( - user => user.Session + session => session.Session ).ToArray(); } - else if (type == BroadcastType.AllExceptUser) + else if (type == BroadcastType.AllExceptSession) { return _group.Partecipants.Values.Select( - user => user.Session + session => session.Session ).Where( - user => !user.Id.Equals(from.Id) + session => !session.Id.Equals(from.Id) ).ToArray(); } else if (type == BroadcastType.AllReady) { return _group.Partecipants.Values.Where( - user => !user.IsBuffering + session => !session.IsBuffering ).Select( - user => user.Session + session => session.Session ).ToArray(); } else @@ -133,10 +133,10 @@ namespace Emby.Server.Implementations.Syncplay { IEnumerable GetTasks() { - SessionInfo[] users = FilterUsers(from, type); - foreach (var user in users) + SessionInfo[] sessions = FilterSessions(from, type); + foreach (var session in sessions) { - yield return _sessionManager.SendSyncplayGroupUpdate(user.Id.ToString(), message, CancellationToken.None); + yield return _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), message, CancellationToken.None); } } @@ -147,10 +147,10 @@ namespace Emby.Server.Implementations.Syncplay { IEnumerable GetTasks() { - SessionInfo[] users = FilterUsers(from, type); - foreach (var user in users) + SessionInfo[] sessions = FilterSessions(from, type); + foreach (var session in sessions) { - yield return _sessionManager.SendSyncplayCommand(user.Id.ToString(), message, CancellationToken.None); + yield return _sessionManager.SendSyncplayCommand(session.Id.ToString(), message, CancellationToken.None); } } @@ -176,46 +176,46 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void InitGroup(SessionInfo user) + public void InitGroup(SessionInfo session) { - _group.AddUser(user); - _syncplayManager.MapUserToGroup(user, this); + _group.AddSession(session); + _syncplayManager.MapSessionToGroup(session, this); - _group.PlayingItem = user.FullNowPlayingItem; + _group.PlayingItem = session.FullNowPlayingItem; _group.IsPaused = true; - _group.PositionTicks = user.PlayState.PositionTicks ??= 0; + _group.PositionTicks = session.PlayState.PositionTicks ??= 0; _group.LastActivity = DateTime.UtcNow; - var updateUser = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); - SendGroupUpdate(user, BroadcastType.SingleUser, updateUser); + var updateSession = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupJoined, DateTime.UtcNow.ToUniversalTime().ToString("o")); + SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); var pauseCommand = NewSyncplayCommand(SyncplayCommandType.Pause); - SendCommand(user, BroadcastType.SingleUser, pauseCommand); + SendCommand(session, BroadcastType.SingleSession, pauseCommand); } /// - public void UserJoin(SessionInfo user) + public void SessionJoin(SessionInfo session) { - if (user.NowPlayingItem != null && user.NowPlayingItem.Id.Equals(_group.PlayingItem.Id)) + if (session.NowPlayingItem != null && session.NowPlayingItem.Id.Equals(_group.PlayingItem.Id)) { - _group.AddUser(user); - _syncplayManager.MapUserToGroup(user, this); + _group.AddSession(session); + _syncplayManager.MapSessionToGroup(session, this); - var updateUser = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupJoined, _group.PositionTicks); - SendGroupUpdate(user, BroadcastType.SingleUser, updateUser); + var updateSession = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupJoined, _group.PositionTicks); + SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); - var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.UserJoined, user.UserName); - SendGroupUpdate(user, BroadcastType.AllExceptUser, updateOthers); + var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.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); - SendCommand(user, BroadcastType.SingleUser, playCommand); + SendCommand(session, BroadcastType.SingleSession, playCommand); } else { var pauseCommand = NewSyncplayCommand(SyncplayCommandType.Pause); - SendCommand(user, BroadcastType.SingleUser, pauseCommand); + SendCommand(session, BroadcastType.SingleSession, pauseCommand); } } else @@ -224,25 +224,25 @@ namespace Emby.Server.Implementations.Syncplay playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id }; playRequest.StartPositionTicks = _group.PositionTicks; var update = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.PrepareSession, playRequest); - SendGroupUpdate(user, BroadcastType.SingleUser, update); + SendGroupUpdate(session, BroadcastType.SingleSession, update); } } /// - public void UserLeave(SessionInfo user) + public void SessionLeave(SessionInfo session) { - _group.RemoveUser(user); - _syncplayManager.UnmapUserFromGroup(user, this); + _group.RemoveSession(session); + _syncplayManager.UnmapSessionFromGroup(session, this); - var updateUser = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupLeft, _group.PositionTicks); - SendGroupUpdate(user, BroadcastType.SingleUser, updateUser); + var updateSession = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupLeft, _group.PositionTicks); + SendGroupUpdate(session, BroadcastType.SingleSession, updateSession); - var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.UserLeft, user.UserName); - SendGroupUpdate(user, BroadcastType.AllExceptUser, updateOthers); + var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.UserLeft, session.UserName); + SendGroupUpdate(session, BroadcastType.AllExceptSession, updateOthers); } /// - public void HandleRequest(SessionInfo user, SyncplayRequestInfo request) + public void HandleRequest(SessionInfo session, SyncplayRequestInfo request) { if (request.Type.Equals(SyncplayRequestType.Play)) { @@ -257,13 +257,13 @@ namespace Emby.Server.Implementations.Syncplay ); var command = NewSyncplayCommand(SyncplayCommandType.Play); - SendCommand(user, BroadcastType.AllGroup, command); + SendCommand(session, BroadcastType.AllGroup, command); } else { // Client got lost var command = NewSyncplayCommand(SyncplayCommandType.Play); - SendCommand(user, BroadcastType.SingleUser, command); + SendCommand(session, BroadcastType.SingleSession, command); } } else if (request.Type.Equals(SyncplayRequestType.Pause)) @@ -277,12 +277,12 @@ namespace Emby.Server.Implementations.Syncplay _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; var command = NewSyncplayCommand(SyncplayCommandType.Pause); - SendCommand(user, BroadcastType.AllGroup, command); + SendCommand(session, BroadcastType.AllGroup, command); } else { var command = NewSyncplayCommand(SyncplayCommandType.Pause); - SendCommand(user, BroadcastType.SingleUser, command); + SendCommand(session, BroadcastType.SingleSession, command); } } else if (request.Type.Equals(SyncplayRequestType.Seek)) @@ -301,7 +301,7 @@ namespace Emby.Server.Implementations.Syncplay _group.LastActivity = DateTime.UtcNow; var command = NewSyncplayCommand(SyncplayCommandType.Seek); - SendCommand(user, BroadcastType.AllGroup, command); + SendCommand(session, BroadcastType.AllGroup, command); } // TODO: client does not implement this yet else if (request.Type.Equals(SyncplayRequestType.Buffering)) @@ -314,19 +314,19 @@ namespace Emby.Server.Implementations.Syncplay _group.LastActivity = currentTime; _group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0; - _group.SetBuffering(user, true); + _group.SetBuffering(session, true); - // Send pause command to all non-buffering users + // Send pause command to all non-buffering sessions var command = NewSyncplayCommand(SyncplayCommandType.Pause); - SendCommand(user, BroadcastType.AllReady, command); + SendCommand(session, BroadcastType.AllReady, command); - var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupWait, user.UserName); - SendGroupUpdate(user, BroadcastType.AllExceptUser, updateOthers); + var updateOthers = NewSyncplayGroupUpdate(SyncplayGroupUpdateType.GroupWait, session.UserName); + SendGroupUpdate(session, BroadcastType.AllExceptSession, updateOthers); } else { var command = NewSyncplayCommand(SyncplayCommandType.Pause); - SendCommand(user, BroadcastType.SingleUser, command); + SendCommand(session, BroadcastType.SingleSession, command); } } // TODO: client does not implement this yet @@ -334,7 +334,7 @@ namespace Emby.Server.Implementations.Syncplay { if (_group.IsPaused) { - _group.SetBuffering(user, false); + _group.SetBuffering(session, false); if (_group.IsBuffering()) { // Others are buffering, tell this client to pause when ready @@ -348,7 +348,7 @@ namespace Emby.Server.Implementations.Syncplay command.When = currentTime.AddMilliseconds( delay ).ToUniversalTime().ToString("o"); - SendCommand(user, BroadcastType.SingleUser, command); + SendCommand(session, BroadcastType.SingleSession, command); } else { @@ -368,7 +368,7 @@ namespace Emby.Server.Implementations.Syncplay delay ); var command = NewSyncplayCommand(SyncplayCommandType.Play); - SendCommand(user, BroadcastType.AllExceptUser, command); + SendCommand(session, BroadcastType.AllExceptSession, command); } else { @@ -381,7 +381,7 @@ namespace Emby.Server.Implementations.Syncplay ); var command = NewSyncplayCommand(SyncplayCommandType.Play); - SendCommand(user, BroadcastType.AllGroup, command); + SendCommand(session, BroadcastType.AllGroup, command); } } } @@ -389,17 +389,17 @@ namespace Emby.Server.Implementations.Syncplay { // Make sure client has latest group state var command = NewSyncplayCommand(SyncplayCommandType.Play); - SendCommand(user, BroadcastType.SingleUser, command); + SendCommand(session, BroadcastType.SingleSession, command); } } else if (request.Type.Equals(SyncplayRequestType.KeepAlive)) { - _group.UpdatePing(user, request.Ping ??= _group.DefaulPing); + _group.UpdatePing(session, request.Ping ??= _group.DefaulPing); var keepAlive = new SyncplayGroupUpdate(); keepAlive.GroupId = _group.GroupId.ToString(); keepAlive.Type = SyncplayGroupUpdateType.KeepAlive; - SendGroupUpdate(user, BroadcastType.SingleUser, keepAlive); + SendGroupUpdate(session, BroadcastType.SingleSession, keepAlive); } } @@ -411,7 +411,7 @@ namespace Emby.Server.Implementations.Syncplay 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(); + info.Partecipants = _group.Partecipants.Values.Select(session => session.Session.UserName).ToArray(); return info; } } diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index 7583793bb..f76d243d5 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -27,9 +27,9 @@ namespace Emby.Server.Implementations.Syncplay private readonly ISessionManager _sessionManager; /// - /// The map between users and groups. + /// The map between sessions and groups. /// - private readonly ConcurrentDictionary _userToGroupMap = + private readonly ConcurrentDictionary _sessionToGroupMap = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); /// @@ -91,27 +91,27 @@ namespace Emby.Server.Implementations.Syncplay void _sessionManager_SessionEnded(object sender, SessionEventArgs e) { - var user = e.SessionInfo; - if (!IsUserInGroup(user)) return; - LeaveGroup(user); + var session = e.SessionInfo; + if (!IsSessionInGroup(session)) return; + LeaveGroup(session); } void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) { - var user = e.Session; - if (!IsUserInGroup(user)) return; - LeaveGroup(user); + var session = e.Session; + if (!IsSessionInGroup(session)) return; + LeaveGroup(session); } - private bool IsUserInGroup(SessionInfo user) + private bool IsSessionInGroup(SessionInfo session) { - return _userToGroupMap.ContainsKey(user.Id); + return _sessionToGroupMap.ContainsKey(session.Id); } - private Guid? GetUserGroup(SessionInfo user) + private Guid? GetSessionGroup(SessionInfo session) { ISyncplayController group; - _userToGroupMap.TryGetValue(user.Id, out group); + _sessionToGroupMap.TryGetValue(session.Id, out group); if (group != null) { return group.GetGroupId(); @@ -123,26 +123,26 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void NewGroup(SessionInfo user) + public void NewGroup(SessionInfo session) { - if (IsUserInGroup(user)) { - LeaveGroup(user); + if (IsSessionInGroup(session)) + LeaveGroup(session); } var group = new SyncplayController(_logger, _sessionManager, this); _groups[group.GetGroupId().ToString()] = group; - group.InitGroup(user); + group.InitGroup(session); } /// - public void JoinGroup(SessionInfo user, string groupId) + public void JoinGroup(SessionInfo session, string groupId) { - if (IsUserInGroup(user)) + if (IsSessionInGroup(session)) { - if (GetUserGroup(user).Equals(groupId)) return; - LeaveGroup(user); + if (GetSessionGroup(session).Equals(groupId)) return; + LeaveGroup(session); } ISyncplayController group; @@ -154,28 +154,28 @@ namespace Emby.Server.Implementations.Syncplay var update = new SyncplayGroupUpdate(); update.Type = SyncplayGroupUpdateType.NotInGroup; - _sessionManager.SendSyncplayGroupUpdate(user.Id.ToString(), update, CancellationToken.None); + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), update, CancellationToken.None); return; } - group.UserJoin(user); + group.SessionJoin(session); } /// - public void LeaveGroup(SessionInfo user) + public void LeaveGroup(SessionInfo session) { ISyncplayController group; - _userToGroupMap.TryGetValue(user.Id, out group); + _sessionToGroupMap.TryGetValue(session.Id, out group); if (group == null) { - _logger.LogWarning("Syncplaymanager HandleRequest: " + user.Id + " not in group."); + _logger.LogWarning("Syncplaymanager HandleRequest: " + session.Id + " not in group."); var update = new SyncplayGroupUpdate(); update.Type = SyncplayGroupUpdateType.NotInGroup; - _sessionManager.SendSyncplayGroupUpdate(user.Id.ToString(), update, CancellationToken.None); + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), update, CancellationToken.None); return; } - group.UserLeave(user); + group.SessionLeave(session); if (group.IsGroupEmpty()) { @@ -184,13 +184,13 @@ namespace Emby.Server.Implementations.Syncplay } /// - public List ListGroups(SessionInfo user) + public List ListGroups(SessionInfo session) { // Filter by playing item if the user is viewing something already - if (user.NowPlayingItem != null) + if (session.NowPlayingItem != null) { return _groups.Values.Where( - group => group.GetPlayingItemId().Equals(user.FullNowPlayingItem.Id) + group => group.GetPlayingItemId().Equals(session.FullNowPlayingItem.Id) ).Select( group => group.GetInfo() ).ToList(); @@ -205,47 +205,47 @@ namespace Emby.Server.Implementations.Syncplay } /// - public void HandleRequest(SessionInfo user, SyncplayRequestInfo request) + public void HandleRequest(SessionInfo session, SyncplayRequestInfo request) { ISyncplayController group; - _userToGroupMap.TryGetValue(user.Id, out group); + _sessionToGroupMap.TryGetValue(session.Id, out group); if (group == null) { - _logger.LogWarning("Syncplaymanager HandleRequest: " + user.Id + " not in group."); + _logger.LogWarning("Syncplaymanager HandleRequest: " + session.Id + " not in group."); var update = new SyncplayGroupUpdate(); update.Type = SyncplayGroupUpdateType.NotInGroup; - _sessionManager.SendSyncplayGroupUpdate(user.Id.ToString(), update, CancellationToken.None); + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), update, CancellationToken.None); return; } - group.HandleRequest(user, request); + group.HandleRequest(session, request); } /// - public void MapUserToGroup(SessionInfo user, ISyncplayController group) + public void MapSessionToGroup(SessionInfo session, ISyncplayController group) { - if (IsUserInGroup(user)) + if (IsSessionInGroup(session)) { - throw new InvalidOperationException("User in other group already!"); + throw new InvalidOperationException("Session in other group already!"); } - _userToGroupMap[user.Id] = group; + _sessionToGroupMap[session.Id] = group; } /// - public void UnmapUserFromGroup(SessionInfo user, ISyncplayController group) + public void UnmapSessionFromGroup(SessionInfo session, ISyncplayController group) { - if (!IsUserInGroup(user)) + if (!IsSessionInGroup(session)) { - throw new InvalidOperationException("User not in any group!"); + throw new InvalidOperationException("Session not in any group!"); } ISyncplayController tempGroup; - _userToGroupMap.Remove(user.Id, out tempGroup); + _sessionToGroupMap.Remove(session.Id, out tempGroup); if (!tempGroup.GetGroupId().Equals(group.GetGroupId())) { - throw new InvalidOperationException("User was in wrong group!"); + throw new InvalidOperationException("Session was in wrong group!"); } } } diff --git a/MediaBrowser.Controller/Syncplay/GroupInfo.cs b/MediaBrowser.Controller/Syncplay/GroupInfo.cs index d37e8563b..42e85ef86 100644 --- a/MediaBrowser.Controller/Syncplay/GroupInfo.cs +++ b/MediaBrowser.Controller/Syncplay/GroupInfo.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.Syncplay public class GroupInfo { /// - /// Default ping value used for users. + /// Default ping value used for sessions. /// public readonly long DefaulPing = 500; /// @@ -53,85 +53,85 @@ namespace MediaBrowser.Controller.Syncplay new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); /// - /// Checks if a user is in this group. + /// Checks if a session is in this group. /// - /// true if the user is in this group; false otherwise. - public bool ContainsUser(string sessionId) + /// true if the session is in this group; false otherwise. + public bool ContainsSession(string sessionId) { return Partecipants.ContainsKey(sessionId); } /// - /// Adds the user to the group. + /// Adds the session to the group. /// - /// The session. - public void AddUser(SessionInfo user) + /// The session. + public void AddSession(SessionInfo session) { - if (ContainsUser(user.Id.ToString())) return; + if (ContainsSession(session.Id.ToString())) return; var member = new GroupMember(); - member.Session = user; + member.Session = session; member.Ping = DefaulPing; member.IsBuffering = false; - Partecipants[user.Id.ToString()] = member; + Partecipants[session.Id.ToString()] = member; } /// - /// Removes the user from the group. + /// Removes the session from the group. /// - /// The session. + /// The session. - public void RemoveUser(SessionInfo user) + public void RemoveSession(SessionInfo session) { - if (!ContainsUser(user.Id.ToString())) return; + if (!ContainsSession(session.Id.ToString())) return; GroupMember member; - Partecipants.Remove(user.Id.ToString(), out member); + Partecipants.Remove(session.Id.ToString(), out member); } /// - /// Updates the ping of a user. + /// Updates the ping of a session. /// - /// The session. + /// The session. /// The ping. - public void UpdatePing(SessionInfo user, long ping) + public void UpdatePing(SessionInfo session, long ping) { - if (!ContainsUser(user.Id.ToString())) return; - Partecipants[user.Id.ToString()].Ping = ping; + if (!ContainsSession(session.Id.ToString())) return; + Partecipants[session.Id.ToString()].Ping = ping; } /// /// Gets the highest ping in the group. /// - /// 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) + foreach (var session in Partecipants.Values) { - max = Math.Max(max, user.Ping); + max = Math.Max(max, session.Ping); } return max; } /// - /// Sets the user's buffering state. + /// Sets the session's buffering state. /// - /// The session. + /// The session. /// The state. - public void SetBuffering(SessionInfo user, bool isBuffering) + public void SetBuffering(SessionInfo session, bool isBuffering) { - if (!ContainsUser(user.Id.ToString())) return; - Partecipants[user.Id.ToString()].IsBuffering = isBuffering; + if (!ContainsSession(session.Id.ToString())) return; + Partecipants[session.Id.ToString()].IsBuffering = isBuffering; } /// /// Gets the group buffering state. /// - /// true if there is a user buffering in the group; false otherwise. + /// true if there is a session buffering in the group; false otherwise. public bool IsBuffering() { - foreach (var user in Partecipants.Values) + foreach (var session in Partecipants.Values) { - if (user.IsBuffering) return true; + if (session.IsBuffering) return true; } return false; } diff --git a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs index c9465b27a..d35ae3101 100644 --- a/MediaBrowser.Controller/Syncplay/ISyncplayController.cs +++ b/MediaBrowser.Controller/Syncplay/ISyncplayController.cs @@ -28,29 +28,29 @@ namespace MediaBrowser.Controller.Syncplay bool IsGroupEmpty(); /// - /// Initializes the group with the user's info. + /// Initializes the group with the session's info. /// - /// The session. - void InitGroup(SessionInfo user); + /// The session. + void InitGroup(SessionInfo session); /// - /// Adds the user to the group. + /// Adds the session to the group. /// - /// The session. - void UserJoin(SessionInfo user); + /// The session. + void SessionJoin(SessionInfo session); /// - /// Removes the user from the group. + /// Removes the session from the group. /// - /// The session. - void UserLeave(SessionInfo user); + /// The session. + void SessionLeave(SessionInfo session); /// - /// Handles the requested action by the user. + /// Handles the requested action by the session. /// - /// The session. + /// The session. /// The requested action. - void HandleRequest(SessionInfo user, SyncplayRequestInfo request); + void HandleRequest(SessionInfo session, SyncplayRequestInfo 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 ec91ea69d..09920a19f 100644 --- a/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs +++ b/MediaBrowser.Controller/Syncplay/ISyncplayManager.cs @@ -13,50 +13,50 @@ namespace MediaBrowser.Controller.Syncplay /// /// Creates a new group. /// - /// The user that's creating the group. - void NewGroup(SessionInfo user); + /// The session that's creating the group. + void NewGroup(SessionInfo session); /// - /// Adds the user to a group. + /// Adds the session to a group. /// - /// The session. + /// The session. /// The group id. - void JoinGroup(SessionInfo user, string groupId); + void JoinGroup(SessionInfo session, string groupId); /// - /// Removes the user from a group. + /// Removes the session from a group. /// - /// The session. - void LeaveGroup(SessionInfo user); + /// The session. + void LeaveGroup(SessionInfo session); /// - /// Gets list of available groups for a user. + /// Gets list of available groups for a session. /// - /// The user. + /// The session. /// The list of available groups. - List ListGroups(SessionInfo user); + List ListGroups(SessionInfo session); /// - /// Handle a request by a user in a group. + /// Handle a request by a session in a group. /// - /// The session. + /// The session. /// The request. - void HandleRequest(SessionInfo user, SyncplayRequestInfo request); + void HandleRequest(SessionInfo session, SyncplayRequestInfo request); /// - /// Maps a user to a group. + /// Maps a session to a group. /// - /// The user. + /// The session. /// The group. /// - void MapUserToGroup(SessionInfo user, ISyncplayController group); + void MapSessionToGroup(SessionInfo session, ISyncplayController group); /// - /// Unmaps a user from a group. + /// Unmaps a session from a group. /// - /// The user. + /// The session. /// The group. /// - void UnmapUserFromGroup(SessionInfo user, ISyncplayController group); + void UnmapSessionFromGroup(SessionInfo session, ISyncplayController group); } } -- cgit v1.2.3 From 459297211ecb435886e8cdde8a6521671ca869f6 Mon Sep 17 00:00:00 2001 From: gion Date: Sat, 4 Apr 2020 17:59:16 +0200 Subject: Implement syncplay permissions for a user --- .../Syncplay/SyncplayManager.cs | 41 ++++++++++++++++++++++ MediaBrowser.Model/Configuration/SyncplayAccess.cs | 23 ++++++++++++ MediaBrowser.Model/Users/UserPolicy.cs | 7 ++++ 3 files changed, 71 insertions(+) create mode 100644 MediaBrowser.Model/Configuration/SyncplayAccess.cs diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index f76d243d5..f6311d098 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.Logging; 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 @@ -21,6 +22,11 @@ namespace Emby.Server.Implementations.Syncplay /// private readonly ILogger _logger; + /// + /// The user manager. + /// + private readonly IUserManager _userManager; + /// /// The session manager. /// @@ -42,9 +48,11 @@ namespace Emby.Server.Implementations.Syncplay public SyncplayManager( ILogger logger, + IUserManager userManager, ISessionManager sessionManager) { _logger = logger; + _userManager = userManager; _sessionManager = sessionManager; _sessionManager.SessionEnded += _sessionManager_SessionEnded; @@ -125,8 +133,16 @@ namespace Emby.Server.Implementations.Syncplay /// public void NewGroup(SessionInfo session) { + var user = _userManager.GetUserById(session.UserId); + + if (user.Policy.SyncplayAccess != SyncplayAccess.CreateAndJoinGroups) { + // TODO: shall an error message be sent back to the client? + return; + } + if (IsSessionInGroup(session)) + { LeaveGroup(session); } @@ -139,6 +155,14 @@ namespace Emby.Server.Implementations.Syncplay /// public void JoinGroup(SessionInfo session, string groupId) { + var user = _userManager.GetUserById(session.UserId); + + if (user.Policy.SyncplayAccess == SyncplayAccess.None) + { + // TODO: shall an error message be sent back to the client? + return; + } + if (IsSessionInGroup(session)) { if (GetSessionGroup(session).Equals(groupId)) return; @@ -163,6 +187,8 @@ namespace Emby.Server.Implementations.Syncplay /// public void LeaveGroup(SessionInfo session) { + // TODO: what happens to users that are in a group and get their permissions revoked? + ISyncplayController group; _sessionToGroupMap.TryGetValue(session.Id, out group); @@ -186,6 +212,13 @@ namespace Emby.Server.Implementations.Syncplay /// public List ListGroups(SessionInfo session) { + var user = _userManager.GetUserById(session.UserId); + + if (user.Policy.SyncplayAccess == SyncplayAccess.None) + { + return new List(); + } + // Filter by playing item if the user is viewing something already if (session.NowPlayingItem != null) { @@ -207,6 +240,14 @@ namespace Emby.Server.Implementations.Syncplay /// public void HandleRequest(SessionInfo session, SyncplayRequestInfo request) { + var user = _userManager.GetUserById(session.UserId); + + if (user.Policy.SyncplayAccess == SyncplayAccess.None) + { + // TODO: same as LeaveGroup + return; + } + ISyncplayController group; _sessionToGroupMap.TryGetValue(session.Id, out group); diff --git a/MediaBrowser.Model/Configuration/SyncplayAccess.cs b/MediaBrowser.Model/Configuration/SyncplayAccess.cs new file mode 100644 index 000000000..cddf68c42 --- /dev/null +++ b/MediaBrowser.Model/Configuration/SyncplayAccess.cs @@ -0,0 +1,23 @@ +namespace MediaBrowser.Model.Configuration +{ + /// + /// Enum SyncplayAccess. + /// + public enum SyncplayAccess + { + /// + /// User can create groups and join them. + /// + CreateAndJoinGroups, + + /// + /// User can only join already existing groups. + /// + JoinGroups, + + /// + /// Syncplay is disabled for the user. + /// + None + } +} diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index ae2b3fd4e..cf576c358 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -80,6 +80,12 @@ namespace MediaBrowser.Model.Users public string AuthenticationProviderId { get; set; } public string PasswordResetProviderId { get; set; } + /// + /// Gets or sets a value indicating what Syncplay features the user can access. + /// + /// Access level to Syncplay features. + public SyncplayAccess SyncplayAccess { get; set; } + public UserPolicy() { IsHidden = true; @@ -125,6 +131,7 @@ namespace MediaBrowser.Model.Users EnableContentDownloading = true; EnablePublicSharing = true; EnableRemoteAccess = true; + SyncplayAccess = SyncplayAccess.CreateAndJoinGroups; } } } -- cgit v1.2.3 From e74832d13946b63d41341ac91bd4ef9964be2162 Mon Sep 17 00:00:00 2001 From: gion Date: Sun, 5 Apr 2020 00:50:57 +0200 Subject: Filter groups by library access --- .../Syncplay/SyncplayManager.cs | 40 ++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index f6311d098..743933810 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Concurrent; 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; @@ -32,6 +34,11 @@ namespace Emby.Server.Implementations.Syncplay /// private readonly ISessionManager _sessionManager; + /// + /// The library manager. + /// + private readonly ILibraryManager _libraryManager; + /// /// The map between sessions and groups. /// @@ -49,11 +56,13 @@ namespace Emby.Server.Implementations.Syncplay public SyncplayManager( ILogger logger, IUserManager userManager, - ISessionManager sessionManager) + ISessionManager sessionManager, + ILibraryManager libraryManager) { _logger = logger; _userManager = userManager; _sessionManager = sessionManager; + _libraryManager = libraryManager; _sessionManager.SessionEnded += _sessionManager_SessionEnded; _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped; @@ -116,6 +125,23 @@ namespace Emby.Server.Implementations.Syncplay return _sessionToGroupMap.ContainsKey(session.Id); } + private bool HasAccessToItem(User user, Guid itemId) + { + if (!user.Policy.EnableAllFolders) + { + var item = _libraryManager.GetItemById(itemId); + var collections = _libraryManager.GetCollectionFolders(item).Select( + folder => folder.Id.ToString("N", CultureInfo.InvariantCulture) + ); + var intersect = collections.Intersect(user.Policy.EnabledFolders); + return intersect.Count() > 0; + } + else + { + return true; + } + } + private Guid? GetSessionGroup(SessionInfo session) { ISyncplayController group; @@ -181,6 +207,12 @@ namespace Emby.Server.Implementations.Syncplay _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), update, CancellationToken.None); return; } + + if (!HasAccessToItem(user, group.GetPlayingItemId())) + { + return; + } + group.SessionJoin(session); } @@ -223,6 +255,8 @@ 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) ).Select( group => group.GetInfo() @@ -231,7 +265,9 @@ namespace Emby.Server.Implementations.Syncplay // Otherwise show all available groups else { - return _groups.Values.Select( + return _groups.Values.Where( + group => HasAccessToItem(user, group.GetPlayingItemId()) + ).Select( group => group.GetInfo() ).ToList(); } -- cgit v1.2.3 From 73c19bd2811abf7daa2db3801388db488cab3a59 Mon Sep 17 00:00:00 2001 From: gion Date: Sun, 5 Apr 2020 09:28:55 +0200 Subject: Filter groups by parental rating --- Emby.Server.Implementations/Syncplay/SyncplayManager.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index 743933810..5c44326f5 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -127,18 +127,20 @@ 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; + if (!user.Policy.EnableAllFolders) { - var item = _libraryManager.GetItemById(itemId); var collections = _libraryManager.GetCollectionFolders(item).Select( folder => folder.Id.ToString("N", CultureInfo.InvariantCulture) ); var intersect = collections.Intersect(user.Policy.EnabledFolders); - return intersect.Count() > 0; + return intersect.Count() > 0 && hasParentalRatingAccess; } else { - return true; + return hasParentalRatingAccess; } } -- 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 diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index b1519b572..6a64209c1 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 b156e5a87..fb37b2fb6 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 5c44326f5..60d70e5fd 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 f17cca9ee..0f9d1b733 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 4bfc0c73f..39c065b89 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 d35ae3101..5b08eac0a 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 09920a19f..d0cf8fa9c 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 000000000..cc49e92a9 --- /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 000000000..ceb778b36 --- /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 000000000..8d8a2646a --- /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 000000000..cae769db0 --- /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 000000000..da770736c --- /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 000000000..d9f391403 --- /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 000000000..02e4774d0 --- /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 769316e80..000000000 --- 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 87b9ad66d..000000000 --- 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 c5c2f3540..000000000 --- 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 c7c5f534d..000000000 --- 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 7dba74ae9..000000000 --- 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 44d7a0af2..000000000 --- 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 6519eebabb1df44535e0681e3bf798e7823d4c05 Mon Sep 17 00:00:00 2001 From: gion Date: Thu, 16 Apr 2020 16:02:52 +0200 Subject: Implement NTP like time sync --- MediaBrowser.Api/Syncplay/SyncplayService.cs | 21 ++------ MediaBrowser.Api/Syncplay/TimeSyncService.cs | 70 ++++++++++++++++++++++++++ MediaBrowser.Model/Syncplay/UtcTimeResponse.cs | 20 ++++++++ 3 files changed, 93 insertions(+), 18 deletions(-) create mode 100644 MediaBrowser.Api/Syncplay/TimeSyncService.cs create mode 100644 MediaBrowser.Model/Syncplay/UtcTimeResponse.cs diff --git a/MediaBrowser.Api/Syncplay/SyncplayService.cs b/MediaBrowser.Api/Syncplay/SyncplayService.cs index 0f9d1b733..c273e6c38 100644 --- a/MediaBrowser.Api/Syncplay/SyncplayService.cs +++ b/MediaBrowser.Api/Syncplay/SyncplayService.cs @@ -111,14 +111,6 @@ namespace MediaBrowser.Api.Syncplay 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. /// @@ -129,6 +121,9 @@ namespace MediaBrowser.Api.Syncplay /// private readonly ISessionManager _sessionManager; + /// + /// The session context. + /// private readonly ISessionContext _sessionContext; /// @@ -268,15 +263,5 @@ namespace MediaBrowser.Api.Syncplay 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.Api/Syncplay/TimeSyncService.cs b/MediaBrowser.Api/Syncplay/TimeSyncService.cs new file mode 100644 index 000000000..049684d94 --- /dev/null +++ b/MediaBrowser.Api/Syncplay/TimeSyncService.cs @@ -0,0 +1,70 @@ +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("/GetUtcTime", "GET", Summary = "Get UtcTime")] + [Authenticated] + public class GetUtcTime : IReturnVoid + { + // Nothing + } + + /// + /// Class TimeSyncService. + /// + public class TimeSyncService : BaseApiService + { + /// + /// The session manager. + /// + private readonly ISessionManager _sessionManager; + + /// + /// The session context. + /// + private readonly ISessionContext _sessionContext; + + public TimeSyncService( + ILogger logger, + IServerConfigurationManager serverConfigurationManager, + IHttpResultFactory httpResultFactory, + ISessionManager sessionManager, + ISessionContext sessionContext) + : base(logger, serverConfigurationManager, httpResultFactory) + { + _sessionManager = sessionManager; + _sessionContext = sessionContext; + } + + /// + /// 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; + var currentSession = GetSession(_sessionContext); + + // 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.Model/Syncplay/UtcTimeResponse.cs b/MediaBrowser.Model/Syncplay/UtcTimeResponse.cs new file mode 100644 index 000000000..f7887dc33 --- /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; } + } +} -- cgit v1.2.3 From 40889702d05c7a6f3dc30090e9443e94cb29fbd9 Mon Sep 17 00:00:00 2001 From: gion Date: Fri, 17 Apr 2020 12:57:36 +0200 Subject: Update session ping --- Emby.Server.Implementations/Syncplay/SyncplayController.cs | 7 +------ MediaBrowser.Api/Syncplay/SyncplayService.cs | 8 ++++---- MediaBrowser.Api/Syncplay/TimeSyncService.cs | 2 -- MediaBrowser.Model/Syncplay/GroupUpdateType.cs | 4 ---- MediaBrowser.Model/Syncplay/PlaybackRequestType.cs | 2 +- 5 files changed, 6 insertions(+), 17 deletions(-) diff --git a/Emby.Server.Implementations/Syncplay/SyncplayController.cs b/Emby.Server.Implementations/Syncplay/SyncplayController.cs index fb37b2fb6..83b477944 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayController.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayController.cs @@ -396,14 +396,9 @@ namespace Emby.Server.Implementations.Syncplay SendCommand(session, BroadcastType.SingleSession, command); } } - else if (request.Type.Equals(PlaybackRequestType.KeepAlive)) + else if (request.Type.Equals(PlaybackRequestType.UpdatePing)) { _group.UpdatePing(session, request.Ping ??= _group.DefaulPing); - - var keepAlive = new GroupUpdate(); - keepAlive.GroupId = _group.GroupId.ToString(); - keepAlive.Type = GroupUpdateType.KeepAlive; - SendGroupUpdate(session, BroadcastType.SingleSession, keepAlive); } } diff --git a/MediaBrowser.Api/Syncplay/SyncplayService.cs b/MediaBrowser.Api/Syncplay/SyncplayService.cs index c273e6c38..af220ed81 100644 --- a/MediaBrowser.Api/Syncplay/SyncplayService.cs +++ b/MediaBrowser.Api/Syncplay/SyncplayService.cs @@ -100,9 +100,9 @@ namespace MediaBrowser.Api.Syncplay public bool Resume { get; set; } } - [Route("/Syncplay/{SessionId}/KeepAlive", "POST", Summary = "Keep session alive")] + [Route("/Syncplay/{SessionId}/UpdatePing", "POST", Summary = "Update session ping")] [Authenticated] - public class SyncplayKeepAlive : IReturnVoid + public class SyncplayUpdatePing : IReturnVoid { [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string SessionId { get; set; } @@ -255,11 +255,11 @@ namespace MediaBrowser.Api.Syncplay /// Handles the specified request. /// /// The request. - public void Post(SyncplayKeepAlive request) + public void Post(SyncplayUpdatePing request) { var currentSession = GetSession(_sessionContext); var syncplayRequest = new PlaybackRequest(); - syncplayRequest.Type = PlaybackRequestType.KeepAlive; + syncplayRequest.Type = PlaybackRequestType.UpdatePing; syncplayRequest.Ping = Convert.ToInt64(request.Ping); _syncplayManager.HandleRequest(currentSession, syncplayRequest); } diff --git a/MediaBrowser.Api/Syncplay/TimeSyncService.cs b/MediaBrowser.Api/Syncplay/TimeSyncService.cs index 049684d94..a69e0e293 100644 --- a/MediaBrowser.Api/Syncplay/TimeSyncService.cs +++ b/MediaBrowser.Api/Syncplay/TimeSyncService.cs @@ -1,9 +1,7 @@ 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; diff --git a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs index ceb778b36..0ef8b2785 100644 --- a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs +++ b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs @@ -30,10 +30,6 @@ namespace MediaBrowser.Model.Syncplay /// 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/PlaybackRequestType.cs b/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs index da770736c..3d99b2718 100644 --- a/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs +++ b/MediaBrowser.Model/Syncplay/PlaybackRequestType.cs @@ -28,6 +28,6 @@ namespace MediaBrowser.Model.Syncplay /// /// A user is reporting its ping. /// - KeepAlive = 5 + UpdatePing = 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(-) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 2292d86a4..171047e65 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 930f2d35d..d8e02ef39 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 31eb7ccb7..fb766ab57 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 diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 171047e65..c819c163a 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 d8e02ef39..b0c6d0aa0 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 83b477944..02cf08cd7 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 60d70e5fd..e7df8925e 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 af220ed81..2eaf9ce83 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 a69e0e293..897413015 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 42e85ef86..8e886a2cb 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 d0cf8fa9c..433d6d8bc 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 599c0dbfc..000000000 --- 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 000000000..50ad70630 --- /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 3d99b2718..b3d49d09e 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 73fcbe90c04d9b3de0fc0591565d9a3548a0fa70 Mon Sep 17 00:00:00 2001 From: gion Date: Wed, 22 Apr 2020 22:05:53 +0200 Subject: Send error messages to clients --- .../Syncplay/SyncplayManager.cs | 68 ++++++++++++++++------ MediaBrowser.Model/Syncplay/GroupUpdateType.cs | 34 ++++++++--- 2 files changed, 75 insertions(+), 27 deletions(-) diff --git a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs index e7df8925e..5aefd1fd9 100644 --- a/Emby.Server.Implementations/Syncplay/SyncplayManager.cs +++ b/Emby.Server.Implementations/Syncplay/SyncplayManager.cs @@ -165,8 +165,14 @@ namespace Emby.Server.Implementations.Syncplay if (user.Policy.SyncplayAccess != SyncplayAccess.CreateAndJoinGroups) { - // TODO: report the error to the client - throw new ArgumentException("User does not have permission to create groups"); + _logger.LogWarning("Syncplaymanager 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; } if (IsSessionInGroup(session)) @@ -187,8 +193,14 @@ namespace Emby.Server.Implementations.Syncplay if (user.Policy.SyncplayAccess == SyncplayAccess.None) { - // TODO: report the error to the client - throw new ArgumentException("User does not have access to syncplay"); + _logger.LogWarning("Syncplaymanager 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; } ISyncplayController group; @@ -196,17 +208,27 @@ namespace Emby.Server.Implementations.Syncplay if (group == null) { - _logger.LogWarning("Syncplaymanager JoinGroup: {0} does not exist.", groupId); + _logger.LogWarning("Syncplaymanager JoinGroup: {0} tried to join group {0} that does not exist.", session.Id, groupId); - var update = new GroupUpdate(); - update.Type = GroupUpdateType.NotInGroup; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), update, CancellationToken.None); + var error = new GroupUpdate() + { + Type = GroupUpdateType.GroupNotJoined + }; + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); return; } if (!HasAccessToItem(user, group.GetPlayingItemId())) { - throw new ArgumentException("User does not have access to playing item"); + _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)) @@ -230,9 +252,11 @@ namespace Emby.Server.Implementations.Syncplay { _logger.LogWarning("Syncplaymanager LeaveGroup: {0} does not belong to any group.", session.Id); - var update = new GroupUpdate(); - update.Type = GroupUpdateType.NotInGroup; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), update, CancellationToken.None); + var error = new GroupUpdate() + { + Type = GroupUpdateType.NotInGroup + }; + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); return; } group.SessionLeave(session); @@ -280,8 +304,14 @@ namespace Emby.Server.Implementations.Syncplay if (user.Policy.SyncplayAccess == SyncplayAccess.None) { - // TODO: same as LeaveGroup - throw new ArgumentException("User does not have access to syncplay"); + _logger.LogWarning("Syncplaymanager 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; } ISyncplayController group; @@ -289,11 +319,13 @@ namespace Emby.Server.Implementations.Syncplay if (group == null) { - _logger.LogWarning("Syncplaymanager HandleRequest: {0} not in a group.", session.Id); + _logger.LogWarning("Syncplaymanager HandleRequest: {0} does not belong to any group.", session.Id); - var update = new GroupUpdate(); - update.Type = GroupUpdateType.NotInGroup; - _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), update, CancellationToken.None); + var error = new GroupUpdate() + { + Type = GroupUpdateType.NotInGroup + }; + _sessionManager.SendSyncplayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); return; } group.HandleRequest(session, request); diff --git a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs index 0ef8b2785..20e76932d 100644 --- a/MediaBrowser.Model/Syncplay/GroupUpdateType.cs +++ b/MediaBrowser.Model/Syncplay/GroupUpdateType.cs @@ -1,37 +1,53 @@ namespace MediaBrowser.Model.Syncplay { /// - /// Enum GroupUpdateType + /// Enum GroupUpdateType. /// public enum GroupUpdateType { /// /// The user-joined update. Tells members of a group about a new user. /// - UserJoined = 0, + UserJoined, /// /// The user-left update. Tells members of a group that a user left. /// - UserLeft = 1, + UserLeft, /// /// The group-joined update. Tells a user that the group has been joined. /// - GroupJoined = 2, + GroupJoined, /// /// The group-left update. Tells a user that the group has been left. /// - GroupLeft = 3, + GroupLeft, /// /// The group-wait update. Tells members of the group that a user is buffering. /// - GroupWait = 4, + GroupWait, /// /// The prepare-session update. Tells a user to load some content. /// - PrepareSession = 5, + PrepareSession, /// - /// The not-in-group update. Tells a user that no group has been joined. + /// The not-in-group error. Tells a user that it doesn't belong to a group. /// - NotInGroup = 7 + NotInGroup, + /// + /// The group-not-joined error. Sent when a request to join a group fails. + /// + GroupNotJoined, + /// + /// 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 } } -- 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(-) diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index b0c6d0aa0..7a316b070 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 5aefd1fd9..eb61da7f3 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 897413015..930968d9f 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 c61a200c9de2714b3d6353f3a4ae52b8962d369a Mon Sep 17 00:00:00 2001 From: ZadenRB Date: Tue, 28 Apr 2020 09:30:59 -0600 Subject: Revise documentation based on discussion in #2872 --- .../Controllers/NotificationsController.cs | 35 +++++++++++++--------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs index 8da2a6c53..8feea9ab6 100644 --- a/Jellyfin.Api/Controllers/NotificationsController.cs +++ b/Jellyfin.Api/Controllers/NotificationsController.cs @@ -35,13 +35,14 @@ namespace Jellyfin.Api.Controllers } /// - /// Endpoint for getting a user's notifications. + /// Gets a user's notifications. /// /// The user's ID. /// An optional filter by notification read state. /// The optional index to start at. All notifications with a lower index will be omitted from the results. /// An optional limit on the number of notifications returned. - /// A read-only list of all of the user's notifications. + /// Notifications returned. + /// An containing a list of notifications. [HttpGet("{UserID}")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetNotifications( @@ -54,10 +55,11 @@ namespace Jellyfin.Api.Controllers } /// - /// Endpoint for getting a user's notification summary. + /// Gets a user's notification summary. /// /// The user's ID. - /// Notifications summary for the user. + /// Summary of user's notifications returned. + /// An containing a summary of the users notifications. [HttpGet("{UserID}/Summary")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetNotificationsSummary( @@ -67,9 +69,10 @@ namespace Jellyfin.Api.Controllers } /// - /// Endpoint for getting notification types. + /// Gets notification types. /// - /// All notification types. + /// All notification types returned. + /// An containing a list of all notification types. [HttpGet("Types")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetNotificationTypes() @@ -78,9 +81,10 @@ namespace Jellyfin.Api.Controllers } /// - /// Endpoint for getting notification services. + /// Gets notification services. /// - /// All notification services. + /// All notification services returned. + /// An containing a list of all notification services. [HttpGet("Services")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetNotificationServices() @@ -89,13 +93,14 @@ namespace Jellyfin.Api.Controllers } /// - /// Endpoint to send a notification to all admins. + /// Sends a notification to all admins. /// /// The name of the notification. /// The description of the notification. /// The URL of the notification. /// The level of the notification. - /// Status. + /// Notification sent. + /// An . [HttpPost("Admin")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult CreateAdminNotification( @@ -120,11 +125,12 @@ namespace Jellyfin.Api.Controllers } /// - /// Endpoint to set notifications as read. + /// Sets notifications as read. /// /// The userID. /// A comma-separated list of the IDs of notifications which should be set as read. - /// Status. + /// Notifications set as read. + /// An . [HttpPost("{UserID}/Read")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult SetRead( @@ -135,11 +141,12 @@ namespace Jellyfin.Api.Controllers } /// - /// Endpoint to set notifications as unread. + /// Sets notifications as unread. /// /// The userID. /// A comma-separated list of the IDs of notifications which should be set as unread. - /// Status. + /// Notifications set as unread. + /// An . [HttpPost("{UserID}/Unread")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult SetUnread( -- cgit v1.2.3 From 2aaecb8e148aef6cda67797fa4227a8ebcf7e5bb Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 28 Apr 2020 21:45:46 +0100 Subject: Whilst fixing issues with SSDP on devices with multiple interfaces, i came across a design issue in the current code - namely interfaces without a gateway were ignored. Fixing this required the removal of the code that attempted to detect virtual interfaces. Not wanting to remove functionality, but not able to keep the code in place, I implemented a work around solution (see 4 below). Whilst in the area, I also fixed a few minor bugs i encountered (1, 5, 6 below) and stopped SSDP messages from going out on non-LAN interfaces (3) All these changes are related. Changes 1 IsInPrivateAddressSpace - improved subnet code checking 2 interfaces with no gateway were being excluded from SSDP blasts 3 filtered SSDP blasts from not LAN addresses as defined on the network page. 4 removed #986 mod - as this was part of the issue of #2986. Interfaces can be excluded from the LAN by putting the LAN address in brackets. eg. [10.1.1.1] will exclude an interface with ip address 10.1.1.1 from SSDP 5 fixed a problem where an invalid LAN address causing the SSDP to crash 6 corrected local link filter (FilterIPAddress) to filter on 169.254. addresses --- Emby.Dlna/Main/DlnaEntryPoint.cs | 6 + Emby.Server.Implementations/ApplicationHost.cs | 2 +- .../Networking/NetworkManager.cs | 149 ++++++++++++--------- MediaBrowser.Common/Net/INetworkManager.cs | 4 +- RSSDP/SsdpCommunicationsServer.cs | 4 +- RSSDP/SsdpDeviceLocator.cs | 2 +- 6 files changed, 97 insertions(+), 70 deletions(-) diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index c5d60b2a0..721350483 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -266,6 +266,12 @@ namespace Emby.Dlna.Main continue; } + // Limit to LAN addresses only + if (!_networkManager.IsAddressInSubnets(address, true, true)) + { + continue; + } + var fullService = "urn:schemas-upnp-org:device:MediaServer:1"; _logger.LogInformation("Registering publisher for {0} on {1}", fullService, address); diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 33aec1a06..97fc2c004 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1274,7 +1274,7 @@ namespace Emby.Server.Implementations if (addresses.Count == 0) { - addresses.AddRange(_networkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces)); + addresses.AddRange(_networkManager.GetLocalIpAddresses()); } var resultList = new List(); diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index b3e88b667..465530888 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -56,13 +56,13 @@ namespace Emby.Server.Implementations.Networking NetworkChanged?.Invoke(this, EventArgs.Empty); } - public IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface = true) + public IPAddress[] GetLocalIpAddresses() { lock (_localIpAddressSyncLock) { if (_localIpAddresses == null) { - var addresses = GetLocalIpAddressesInternal(ignoreVirtualInterface).ToArray(); + var addresses = GetLocalIpAddressesInternal().ToArray(); _localIpAddresses = addresses; } @@ -71,35 +71,37 @@ namespace Emby.Server.Implementations.Networking } } - private List GetLocalIpAddressesInternal(bool ignoreVirtualInterface) + private List GetLocalIpAddressesInternal() { - var list = GetIPsDefault(ignoreVirtualInterface).ToList(); + var list = GetIPsDefault().ToList(); if (list.Count == 0) { list = GetLocalIpAddressesFallback().GetAwaiter().GetResult().ToList(); } - var listClone = list.ToList(); + var listClone = new List(); - return list - .OrderBy(i => i.AddressFamily == AddressFamily.InterNetwork ? 0 : 1) - .ThenBy(i => listClone.IndexOf(i)) - .Where(FilterIpAddress) - .GroupBy(i => i.ToString()) - .Select(x => x.First()) - .ToList(); - } + var subnets = LocalSubnetsFn(); - private static bool FilterIpAddress(IPAddress address) - { - if (address.IsIPv6LinkLocal - || address.ToString().StartsWith("169.", StringComparison.OrdinalIgnoreCase)) + foreach (var i in list) { - return false; + if (i.IsIPv6LinkLocal || i.ToString().StartsWith("169.254.", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + if (Array.IndexOf(subnets, "[" + i.ToString() + "]") == -1) + { + listClone.Add(i); + } } - return true; + return listClone + .OrderBy(i => i.AddressFamily == AddressFamily.InterNetwork ? 0 : 1) + // .ThenBy(i => listClone.IndexOf(i)) + .GroupBy(i => i.ToString()) + .Select(x => x.First()) + .ToList(); } public bool IsInPrivateAddressSpace(string endpoint) @@ -107,6 +109,7 @@ namespace Emby.Server.Implementations.Networking return IsInPrivateAddressSpace(endpoint, true); } + // checks if the address in endpoint is an RFC1918, RFC1122, or RFC3927 address private bool IsInPrivateAddressSpace(string endpoint, bool checkSubnets) { if (string.Equals(endpoint, "::1", StringComparison.OrdinalIgnoreCase)) @@ -128,23 +131,28 @@ namespace Emby.Server.Implementations.Networking } // Private address space: - // http://en.wikipedia.org/wiki/Private_network - if (endpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase)) + if (endpoint.ToLower() == "localhost") { - return Is172AddressPrivate(endpoint); + return true; } - if (endpoint.StartsWith("localhost", StringComparison.OrdinalIgnoreCase) || - endpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) || - endpoint.StartsWith("169.", StringComparison.OrdinalIgnoreCase)) + try { - return true; - } + byte[] octet = IPAddress.Parse(endpoint).GetAddressBytes(); - if (checkSubnets && endpoint.StartsWith("192.168", StringComparison.OrdinalIgnoreCase)) + if ((octet[0] == 10) || + (octet[0] == 172 && (octet[1] >= 16 && octet[1] <= 31)) || // RFC1918 + (octet[0] == 192 && octet[1] == 168) || // RFC1918 + (octet[0] == 127) || // RFC1122 + (octet[0] == 169 && octet[1] == 254)) // RFC3927 + { + return false; + } + } + catch { - return true; + // return false; } if (checkSubnets && IsInPrivateAddressSpaceAndLocalSubnet(endpoint)) @@ -177,6 +185,7 @@ namespace Emby.Server.Implementations.Networking return false; } + // Gives a list of possible subnets from the system whose interface ip starts with endpointFirstPart private List GetSubnets(string endpointFirstPart) { lock (_subnetLookupLock) @@ -222,19 +231,6 @@ namespace Emby.Server.Implementations.Networking } } - private static bool Is172AddressPrivate(string endpoint) - { - for (var i = 16; i <= 31; i++) - { - if (endpoint.StartsWith("172." + i.ToString(CultureInfo.InvariantCulture) + ".", StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; - } - public bool IsInLocalNetwork(string endpoint) { return IsInLocalNetworkInternal(endpoint, true); @@ -245,23 +241,57 @@ namespace Emby.Server.Implementations.Networking return IsAddressInSubnets(IPAddress.Parse(addressString), addressString, subnets); } + // returns true if address is in the LAN list in the config file + // always returns false if address has been excluded from the LAN if excludeInterfaces is true + // and excludes RFC addresses if excludeRFC is true + public bool IsAddressInSubnets(IPAddress address, bool excludeInterfaces, bool excludeRFC) + { + byte[] octet = address.GetAddressBytes(); + + if ((octet[0] == 127) || // RFC1122 + (octet[0] == 169 && octet[1] == 254)) // RFC3927 + { + // don't use on loopback or 169 interfaces + return false; + } + + string addressString = address.ToString(); + string excludeAddress = "[" + addressString + "]"; + var subnets = LocalSubnetsFn(); + + // Exclude any addresses if they appear in the LAN list in [ ] + if (Array.IndexOf(subnets, excludeAddress) != -1) + { + return false; + } + return IsAddressInSubnets(address, addressString, subnets); + } + + // Checks to see if address/addressString (same but different type) falls within subnets[] private static bool IsAddressInSubnets(IPAddress address, string addressString, string[] subnets) { foreach (var subnet in subnets) { var normalizedSubnet = subnet.Trim(); - + // is the subnet a host address and does it match the address being passes? if (string.Equals(normalizedSubnet, addressString, StringComparison.OrdinalIgnoreCase)) { return true; } - + // parse CIDR subnets and see if address falls within it. if (normalizedSubnet.Contains('/', StringComparison.Ordinal)) { - var ipNetwork = IPNetwork.Parse(normalizedSubnet); - if (ipNetwork.Contains(address)) + try { - return true; + var ipNetwork = IPNetwork.Parse(normalizedSubnet); + if (ipNetwork.Contains(address)) + { + return true; + } + } + catch + { + // Ignoring - invalid subnet passed encountered. } } } @@ -359,8 +389,8 @@ namespace Emby.Server.Implementations.Networking { return Dns.GetHostAddressesAsync(hostName); } - - private IEnumerable GetIPsDefault(bool ignoreVirtualInterface) + + private IEnumerable GetIPsDefault() { IEnumerable interfaces; @@ -380,15 +410,7 @@ namespace Emby.Server.Implementations.Networking { var ipProperties = network.GetIPProperties(); - // Try to exclude virtual adapters - // http://stackoverflow.com/questions/8089685/c-sharp-finding-my-machines-local-ip-address-and-not-the-vms - var addr = ipProperties.GatewayAddresses.FirstOrDefault(); - if (addr == null - || (ignoreVirtualInterface - && (addr.Address.Equals(IPAddress.Any) || addr.Address.Equals(IPAddress.IPv6Any)))) - { - return Enumerable.Empty(); - } + // Exclude any addresses if they appear in the LAN list in [ ] return ipProperties.UnicastAddresses .Select(i => i.Address) @@ -494,15 +516,12 @@ namespace Emby.Server.Implementations.Networking foreach (NetworkInterface ni in interfaces) { - if (ni.GetIPProperties().GatewayAddresses.FirstOrDefault() != null) + foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses) { - foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses) + if (ip.Address.Equals(address) && ip.IPv4Mask != null) { - if (ip.Address.Equals(address) && ip.IPv4Mask != null) - { - return ip.IPv4Mask; - } - } + return ip.IPv4Mask; + } } } diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 3ba75abd8..b7ec1d122 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -41,10 +41,12 @@ namespace MediaBrowser.Common.Net /// true if [is in local network] [the specified endpoint]; otherwise, false. bool IsInLocalNetwork(string endpoint); - IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface); + IPAddress[] GetLocalIpAddresses(); bool IsAddressInSubnets(string addressString, string[] subnets); + bool IsAddressInSubnets(IPAddress address, bool excludeInterfaces, bool excludeRFC); + bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask); IPAddress GetLocalIpSubnetMask(IPAddress address); diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index 18097ef24..47da52005 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -370,13 +370,13 @@ namespace Rssdp.Infrastructure if (_enableMultiSocketBinding) { - foreach (var address in _networkManager.GetLocalIpAddresses(_config.Configuration.IgnoreVirtualInterfaces)) + foreach (var address in _networkManager.GetLocalIpAddresses()) { if (address.AddressFamily == AddressFamily.InterNetworkV6) { // Not support IPv6 right now continue; - } + } try { diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs index 59a2710d5..b62c50e28 100644 --- a/RSSDP/SsdpDeviceLocator.cs +++ b/RSSDP/SsdpDeviceLocator.cs @@ -357,7 +357,7 @@ namespace Rssdp.Infrastructure private void ProcessSearchResponseMessage(HttpResponseMessage message, IPAddress localIpAddress) { if (!message.IsSuccessStatusCode) return; - + var location = GetFirstHeaderUriValue("Location", message); if (location != null) { -- cgit v1.2.3 From a3140f83c6461164658303d1bb7c1d992cfd9802 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 28 Apr 2020 21:51:49 +0100 Subject: Revert "Whilst fixing issues with SSDP on devices with multiple interfaces, i came across a design issue in the current code - namely interfaces without a gateway were ignored." This reverts commit 2aaecb8e148aef6cda67797fa4227a8ebcf7e5bb. --- Emby.Dlna/Main/DlnaEntryPoint.cs | 6 - Emby.Server.Implementations/ApplicationHost.cs | 2 +- .../Networking/NetworkManager.cs | 149 +++++++++------------ MediaBrowser.Common/Net/INetworkManager.cs | 4 +- RSSDP/SsdpCommunicationsServer.cs | 4 +- RSSDP/SsdpDeviceLocator.cs | 2 +- 6 files changed, 70 insertions(+), 97 deletions(-) diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 721350483..c5d60b2a0 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -266,12 +266,6 @@ namespace Emby.Dlna.Main continue; } - // Limit to LAN addresses only - if (!_networkManager.IsAddressInSubnets(address, true, true)) - { - continue; - } - var fullService = "urn:schemas-upnp-org:device:MediaServer:1"; _logger.LogInformation("Registering publisher for {0} on {1}", fullService, address); diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 97fc2c004..33aec1a06 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1274,7 +1274,7 @@ namespace Emby.Server.Implementations if (addresses.Count == 0) { - addresses.AddRange(_networkManager.GetLocalIpAddresses()); + addresses.AddRange(_networkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces)); } var resultList = new List(); diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index 465530888..b3e88b667 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -56,13 +56,13 @@ namespace Emby.Server.Implementations.Networking NetworkChanged?.Invoke(this, EventArgs.Empty); } - public IPAddress[] GetLocalIpAddresses() + public IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface = true) { lock (_localIpAddressSyncLock) { if (_localIpAddresses == null) { - var addresses = GetLocalIpAddressesInternal().ToArray(); + var addresses = GetLocalIpAddressesInternal(ignoreVirtualInterface).ToArray(); _localIpAddresses = addresses; } @@ -71,45 +71,42 @@ namespace Emby.Server.Implementations.Networking } } - private List GetLocalIpAddressesInternal() + private List GetLocalIpAddressesInternal(bool ignoreVirtualInterface) { - var list = GetIPsDefault().ToList(); + var list = GetIPsDefault(ignoreVirtualInterface).ToList(); if (list.Count == 0) { list = GetLocalIpAddressesFallback().GetAwaiter().GetResult().ToList(); } - var listClone = new List(); + var listClone = list.ToList(); - var subnets = LocalSubnetsFn(); - - foreach (var i in list) - { - if (i.IsIPv6LinkLocal || i.ToString().StartsWith("169.254.", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - if (Array.IndexOf(subnets, "[" + i.ToString() + "]") == -1) - { - listClone.Add(i); - } - } - - return listClone + return list .OrderBy(i => i.AddressFamily == AddressFamily.InterNetwork ? 0 : 1) - // .ThenBy(i => listClone.IndexOf(i)) + .ThenBy(i => listClone.IndexOf(i)) + .Where(FilterIpAddress) .GroupBy(i => i.ToString()) .Select(x => x.First()) .ToList(); } + private static bool FilterIpAddress(IPAddress address) + { + if (address.IsIPv6LinkLocal + || address.ToString().StartsWith("169.", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + return true; + } + public bool IsInPrivateAddressSpace(string endpoint) { return IsInPrivateAddressSpace(endpoint, true); } - // checks if the address in endpoint is an RFC1918, RFC1122, or RFC3927 address private bool IsInPrivateAddressSpace(string endpoint, bool checkSubnets) { if (string.Equals(endpoint, "::1", StringComparison.OrdinalIgnoreCase)) @@ -131,28 +128,23 @@ namespace Emby.Server.Implementations.Networking } // Private address space: + // http://en.wikipedia.org/wiki/Private_network - if (endpoint.ToLower() == "localhost") + if (endpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase)) { - return true; + return Is172AddressPrivate(endpoint); } - try + if (endpoint.StartsWith("localhost", StringComparison.OrdinalIgnoreCase) || + endpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) || + endpoint.StartsWith("169.", StringComparison.OrdinalIgnoreCase)) { - byte[] octet = IPAddress.Parse(endpoint).GetAddressBytes(); - - if ((octet[0] == 10) || - (octet[0] == 172 && (octet[1] >= 16 && octet[1] <= 31)) || // RFC1918 - (octet[0] == 192 && octet[1] == 168) || // RFC1918 - (octet[0] == 127) || // RFC1122 - (octet[0] == 169 && octet[1] == 254)) // RFC3927 - { - return false; - } + return true; } - catch + + if (checkSubnets && endpoint.StartsWith("192.168", StringComparison.OrdinalIgnoreCase)) { - // return false; + return true; } if (checkSubnets && IsInPrivateAddressSpaceAndLocalSubnet(endpoint)) @@ -185,7 +177,6 @@ namespace Emby.Server.Implementations.Networking return false; } - // Gives a list of possible subnets from the system whose interface ip starts with endpointFirstPart private List GetSubnets(string endpointFirstPart) { lock (_subnetLookupLock) @@ -231,6 +222,19 @@ namespace Emby.Server.Implementations.Networking } } + private static bool Is172AddressPrivate(string endpoint) + { + for (var i = 16; i <= 31; i++) + { + if (endpoint.StartsWith("172." + i.ToString(CultureInfo.InvariantCulture) + ".", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + public bool IsInLocalNetwork(string endpoint) { return IsInLocalNetworkInternal(endpoint, true); @@ -241,57 +245,23 @@ namespace Emby.Server.Implementations.Networking return IsAddressInSubnets(IPAddress.Parse(addressString), addressString, subnets); } - // returns true if address is in the LAN list in the config file - // always returns false if address has been excluded from the LAN if excludeInterfaces is true - // and excludes RFC addresses if excludeRFC is true - public bool IsAddressInSubnets(IPAddress address, bool excludeInterfaces, bool excludeRFC) - { - byte[] octet = address.GetAddressBytes(); - - if ((octet[0] == 127) || // RFC1122 - (octet[0] == 169 && octet[1] == 254)) // RFC3927 - { - // don't use on loopback or 169 interfaces - return false; - } - - string addressString = address.ToString(); - string excludeAddress = "[" + addressString + "]"; - var subnets = LocalSubnetsFn(); - - // Exclude any addresses if they appear in the LAN list in [ ] - if (Array.IndexOf(subnets, excludeAddress) != -1) - { - return false; - } - return IsAddressInSubnets(address, addressString, subnets); - } - - // Checks to see if address/addressString (same but different type) falls within subnets[] private static bool IsAddressInSubnets(IPAddress address, string addressString, string[] subnets) { foreach (var subnet in subnets) { var normalizedSubnet = subnet.Trim(); - // is the subnet a host address and does it match the address being passes? + if (string.Equals(normalizedSubnet, addressString, StringComparison.OrdinalIgnoreCase)) { return true; } - // parse CIDR subnets and see if address falls within it. + if (normalizedSubnet.Contains('/', StringComparison.Ordinal)) { - try - { - var ipNetwork = IPNetwork.Parse(normalizedSubnet); - if (ipNetwork.Contains(address)) - { - return true; - } - } - catch + var ipNetwork = IPNetwork.Parse(normalizedSubnet); + if (ipNetwork.Contains(address)) { - // Ignoring - invalid subnet passed encountered. + return true; } } } @@ -389,8 +359,8 @@ namespace Emby.Server.Implementations.Networking { return Dns.GetHostAddressesAsync(hostName); } - - private IEnumerable GetIPsDefault() + + private IEnumerable GetIPsDefault(bool ignoreVirtualInterface) { IEnumerable interfaces; @@ -410,7 +380,15 @@ namespace Emby.Server.Implementations.Networking { var ipProperties = network.GetIPProperties(); - // Exclude any addresses if they appear in the LAN list in [ ] + // Try to exclude virtual adapters + // http://stackoverflow.com/questions/8089685/c-sharp-finding-my-machines-local-ip-address-and-not-the-vms + var addr = ipProperties.GatewayAddresses.FirstOrDefault(); + if (addr == null + || (ignoreVirtualInterface + && (addr.Address.Equals(IPAddress.Any) || addr.Address.Equals(IPAddress.IPv6Any)))) + { + return Enumerable.Empty(); + } return ipProperties.UnicastAddresses .Select(i => i.Address) @@ -516,12 +494,15 @@ namespace Emby.Server.Implementations.Networking foreach (NetworkInterface ni in interfaces) { - foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses) + if (ni.GetIPProperties().GatewayAddresses.FirstOrDefault() != null) { - if (ip.Address.Equals(address) && ip.IPv4Mask != null) + foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses) { - return ip.IPv4Mask; - } + if (ip.Address.Equals(address) && ip.IPv4Mask != null) + { + return ip.IPv4Mask; + } + } } } diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index b7ec1d122..3ba75abd8 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -41,12 +41,10 @@ namespace MediaBrowser.Common.Net /// true if [is in local network] [the specified endpoint]; otherwise, false. bool IsInLocalNetwork(string endpoint); - IPAddress[] GetLocalIpAddresses(); + IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface); bool IsAddressInSubnets(string addressString, string[] subnets); - bool IsAddressInSubnets(IPAddress address, bool excludeInterfaces, bool excludeRFC); - bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask); IPAddress GetLocalIpSubnetMask(IPAddress address); diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index 47da52005..18097ef24 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -370,13 +370,13 @@ namespace Rssdp.Infrastructure if (_enableMultiSocketBinding) { - foreach (var address in _networkManager.GetLocalIpAddresses()) + foreach (var address in _networkManager.GetLocalIpAddresses(_config.Configuration.IgnoreVirtualInterfaces)) { if (address.AddressFamily == AddressFamily.InterNetworkV6) { // Not support IPv6 right now continue; - } + } try { diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs index b62c50e28..59a2710d5 100644 --- a/RSSDP/SsdpDeviceLocator.cs +++ b/RSSDP/SsdpDeviceLocator.cs @@ -357,7 +357,7 @@ namespace Rssdp.Infrastructure private void ProcessSearchResponseMessage(HttpResponseMessage message, IPAddress localIpAddress) { if (!message.IsSuccessStatusCode) return; - + var location = GetFirstHeaderUriValue("Location", message); if (location != null) { -- cgit v1.2.3 From ebd589aa86abb7bdbc9ab981cb8f4c908f790ac1 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Tue, 28 Apr 2020 21:57:39 +0100 Subject: Whilst fixing issues with SSDP on devices with multiple interfaces, i came across a design issue in the current code - namely interfaces without a gateway were ignored. Fixing this required the removal of the code that attempted to detect virtual interfaces. Not wanting to remove functionality, but not able to keep the code in place, I implemented a work around solution (see 4 below). Whilst in the area, I also fixed a few minor bugs i encountered (1, 5, 6 below) and stopped SSDP messages from going out on non-LAN interfaces (3) All these changes are related. Changes 1 IsInPrivateAddressSpace - improved subnet code checking 2 interfaces with no gateway were being excluded from SSDP blasts 3 filtered SSDP blasts from not LAN addresses as defined on the network page. 4 removed #986 mod - as this was part of the issue of #2986. Interfaces can be excluded from the LAN by putting the LAN address in brackets. eg. [10.1.1.1] will exclude an interface with ip address 10.1.1.1 from SSDP 5 fixed a problem where an invalid LAN address causing the SSDP to crash 6 corrected local link filter (FilterIPAddress) to filter on 169.254. addresses --- Emby.Dlna/Main/DlnaEntryPoint.cs | 8 +- Emby.Server.Implementations/ApplicationHost.cs | 2 +- .../Networking/NetworkManager.cs | 150 ++++++++++++--------- MediaBrowser.Common/Net/INetworkManager.cs | 4 +- RSSDP/SsdpCommunicationsServer.cs | 11 +- RSSDP/SsdpDeviceLocator.cs | 2 +- 6 files changed, 101 insertions(+), 76 deletions(-) diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index c5d60b2a0..e6806c87b 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -180,7 +180,7 @@ namespace Emby.Dlna.Main var enableMultiSocketBinding = OperatingSystem.Id == OperatingSystemId.Windows || OperatingSystem.Id == OperatingSystemId.Linux; - _communicationsServer = new SsdpCommunicationsServer(_config, _socketFactory, _networkManager, _logger, enableMultiSocketBinding) + _communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding) { IsShared = true }; @@ -266,6 +266,12 @@ namespace Emby.Dlna.Main continue; } + // Limit to LAN addresses only + if (!_networkManager.IsAddressInSubnets(address, true, true)) + { + continue; + } + var fullService = "urn:schemas-upnp-org:device:MediaServer:1"; _logger.LogInformation("Registering publisher for {0} on {1}", fullService, address); diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 33aec1a06..97fc2c004 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1274,7 +1274,7 @@ namespace Emby.Server.Implementations if (addresses.Count == 0) { - addresses.AddRange(_networkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces)); + addresses.AddRange(_networkManager.GetLocalIpAddresses()); } var resultList = new List(); diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index b3e88b667..5979d1eae 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Net; using System.Net.NetworkInformation; @@ -56,13 +55,13 @@ namespace Emby.Server.Implementations.Networking NetworkChanged?.Invoke(this, EventArgs.Empty); } - public IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface = true) + public IPAddress[] GetLocalIpAddresses() { lock (_localIpAddressSyncLock) { if (_localIpAddresses == null) { - var addresses = GetLocalIpAddressesInternal(ignoreVirtualInterface).ToArray(); + var addresses = GetLocalIpAddressesInternal().ToArray(); _localIpAddresses = addresses; } @@ -71,35 +70,37 @@ namespace Emby.Server.Implementations.Networking } } - private List GetLocalIpAddressesInternal(bool ignoreVirtualInterface) + private List GetLocalIpAddressesInternal() { - var list = GetIPsDefault(ignoreVirtualInterface).ToList(); + var list = GetIPsDefault().ToList(); if (list.Count == 0) { list = GetLocalIpAddressesFallback().GetAwaiter().GetResult().ToList(); } - var listClone = list.ToList(); + var listClone = new List(); - return list - .OrderBy(i => i.AddressFamily == AddressFamily.InterNetwork ? 0 : 1) - .ThenBy(i => listClone.IndexOf(i)) - .Where(FilterIpAddress) - .GroupBy(i => i.ToString()) - .Select(x => x.First()) - .ToList(); - } + var subnets = LocalSubnetsFn(); - private static bool FilterIpAddress(IPAddress address) - { - if (address.IsIPv6LinkLocal - || address.ToString().StartsWith("169.", StringComparison.OrdinalIgnoreCase)) + foreach (var i in list) { - return false; + if (i.IsIPv6LinkLocal || i.ToString().StartsWith("169.254.", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + if (Array.IndexOf(subnets, "[" + i.ToString() + "]") == -1) + { + listClone.Add(i); + } } - return true; + return listClone + .OrderBy(i => i.AddressFamily == AddressFamily.InterNetwork ? 0 : 1) + // .ThenBy(i => listClone.IndexOf(i)) + .GroupBy(i => i.ToString()) + .Select(x => x.First()) + .ToList(); } public bool IsInPrivateAddressSpace(string endpoint) @@ -107,6 +108,7 @@ namespace Emby.Server.Implementations.Networking return IsInPrivateAddressSpace(endpoint, true); } + // checks if the address in endpoint is an RFC1918, RFC1122, or RFC3927 address private bool IsInPrivateAddressSpace(string endpoint, bool checkSubnets) { if (string.Equals(endpoint, "::1", StringComparison.OrdinalIgnoreCase)) @@ -128,23 +130,28 @@ namespace Emby.Server.Implementations.Networking } // Private address space: - // http://en.wikipedia.org/wiki/Private_network - if (endpoint.StartsWith("172.", StringComparison.OrdinalIgnoreCase)) + if (endpoint.ToLower() == "localhost") { - return Is172AddressPrivate(endpoint); + return true; } - if (endpoint.StartsWith("localhost", StringComparison.OrdinalIgnoreCase) || - endpoint.StartsWith("127.", StringComparison.OrdinalIgnoreCase) || - endpoint.StartsWith("169.", StringComparison.OrdinalIgnoreCase)) + try { - return true; - } + byte[] octet = IPAddress.Parse(endpoint).GetAddressBytes(); - if (checkSubnets && endpoint.StartsWith("192.168", StringComparison.OrdinalIgnoreCase)) + if ((octet[0] == 10) || + (octet[0] == 172 && (octet[1] >= 16 && octet[1] <= 31)) || // RFC1918 + (octet[0] == 192 && octet[1] == 168) || // RFC1918 + (octet[0] == 127) || // RFC1122 + (octet[0] == 169 && octet[1] == 254)) // RFC3927 + { + return false; + } + } + catch { - return true; + } if (checkSubnets && IsInPrivateAddressSpaceAndLocalSubnet(endpoint)) @@ -177,6 +184,7 @@ namespace Emby.Server.Implementations.Networking return false; } + // Gives a list of possible subnets from the system whose interface ip starts with endpointFirstPart private List GetSubnets(string endpointFirstPart) { lock (_subnetLookupLock) @@ -222,19 +230,6 @@ namespace Emby.Server.Implementations.Networking } } - private static bool Is172AddressPrivate(string endpoint) - { - for (var i = 16; i <= 31; i++) - { - if (endpoint.StartsWith("172." + i.ToString(CultureInfo.InvariantCulture) + ".", StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; - } - public bool IsInLocalNetwork(string endpoint) { return IsInLocalNetworkInternal(endpoint, true); @@ -245,23 +240,57 @@ namespace Emby.Server.Implementations.Networking return IsAddressInSubnets(IPAddress.Parse(addressString), addressString, subnets); } + // returns true if address is in the LAN list in the config file + // always returns false if address has been excluded from the LAN if excludeInterfaces is true + // and excludes RFC addresses if excludeRFC is true + public bool IsAddressInSubnets(IPAddress address, bool excludeInterfaces, bool excludeRFC) + { + byte[] octet = address.GetAddressBytes(); + + if ((octet[0] == 127) || // RFC1122 + (octet[0] == 169 && octet[1] == 254)) // RFC3927 + { + // don't use on loopback or 169 interfaces + return false; + } + + string addressString = address.ToString(); + string excludeAddress = "[" + addressString + "]"; + var subnets = LocalSubnetsFn(); + + // Exclude any addresses if they appear in the LAN list in [ ] + if (Array.IndexOf(subnets, excludeAddress) != -1) + { + return false; + } + return IsAddressInSubnets(address, addressString, subnets); + } + + // Checks to see if address/addressString (same but different type) falls within subnets[] private static bool IsAddressInSubnets(IPAddress address, string addressString, string[] subnets) { foreach (var subnet in subnets) { var normalizedSubnet = subnet.Trim(); - + // is the subnet a host address and does it match the address being passes? if (string.Equals(normalizedSubnet, addressString, StringComparison.OrdinalIgnoreCase)) { return true; } - + // parse CIDR subnets and see if address falls within it. if (normalizedSubnet.Contains('/', StringComparison.Ordinal)) { - var ipNetwork = IPNetwork.Parse(normalizedSubnet); - if (ipNetwork.Contains(address)) + try { - return true; + var ipNetwork = IPNetwork.Parse(normalizedSubnet); + if (ipNetwork.Contains(address)) + { + return true; + } + } + catch + { + // Ignoring - invalid subnet passed encountered. } } } @@ -359,8 +388,8 @@ namespace Emby.Server.Implementations.Networking { return Dns.GetHostAddressesAsync(hostName); } - - private IEnumerable GetIPsDefault(bool ignoreVirtualInterface) + + private IEnumerable GetIPsDefault() { IEnumerable interfaces; @@ -380,15 +409,7 @@ namespace Emby.Server.Implementations.Networking { var ipProperties = network.GetIPProperties(); - // Try to exclude virtual adapters - // http://stackoverflow.com/questions/8089685/c-sharp-finding-my-machines-local-ip-address-and-not-the-vms - var addr = ipProperties.GatewayAddresses.FirstOrDefault(); - if (addr == null - || (ignoreVirtualInterface - && (addr.Address.Equals(IPAddress.Any) || addr.Address.Equals(IPAddress.IPv6Any)))) - { - return Enumerable.Empty(); - } + // Exclude any addresses if they appear in the LAN list in [ ] return ipProperties.UnicastAddresses .Select(i => i.Address) @@ -494,15 +515,12 @@ namespace Emby.Server.Implementations.Networking foreach (NetworkInterface ni in interfaces) { - if (ni.GetIPProperties().GatewayAddresses.FirstOrDefault() != null) + foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses) { - foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses) + if (ip.Address.Equals(address) && ip.IPv4Mask != null) { - if (ip.Address.Equals(address) && ip.IPv4Mask != null) - { - return ip.IPv4Mask; - } - } + return ip.IPv4Mask; + } } } diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 3ba75abd8..b7ec1d122 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -41,10 +41,12 @@ namespace MediaBrowser.Common.Net /// true if [is in local network] [the specified endpoint]; otherwise, false. bool IsInLocalNetwork(string endpoint); - IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface); + IPAddress[] GetLocalIpAddresses(); bool IsAddressInSubnets(string addressString, string[] subnets); + bool IsAddressInSubnets(IPAddress address, bool excludeInterfaces, bool excludeRFC); + bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask); IPAddress GetLocalIpSubnetMask(IPAddress address); diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index 18097ef24..a16e4c73f 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -46,8 +46,7 @@ namespace Rssdp.Infrastructure private HttpResponseParser _ResponseParser; private readonly ILogger _logger; private ISocketFactory _SocketFactory; - private readonly INetworkManager _networkManager; - private readonly IServerConfigurationManager _config; + private readonly INetworkManager _networkManager; private int _LocalPort; private int _MulticastTtl; @@ -77,11 +76,11 @@ namespace Rssdp.Infrastructure /// Minimum constructor. /// /// The argument is null. - public SsdpCommunicationsServer(IServerConfigurationManager config, ISocketFactory socketFactory, + public SsdpCommunicationsServer(ISocketFactory socketFactory, INetworkManager networkManager, ILogger logger, bool enableMultiSocketBinding) : this(socketFactory, 0, SsdpConstants.SsdpDefaultMulticastTimeToLive, networkManager, logger, enableMultiSocketBinding) { - _config = config; + } /// @@ -370,13 +369,13 @@ namespace Rssdp.Infrastructure if (_enableMultiSocketBinding) { - foreach (var address in _networkManager.GetLocalIpAddresses(_config.Configuration.IgnoreVirtualInterfaces)) + foreach (var address in _networkManager.GetLocalIpAddresses()) { if (address.AddressFamily == AddressFamily.InterNetworkV6) { // Not support IPv6 right now continue; - } + } try { diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs index 59a2710d5..b62c50e28 100644 --- a/RSSDP/SsdpDeviceLocator.cs +++ b/RSSDP/SsdpDeviceLocator.cs @@ -357,7 +357,7 @@ namespace Rssdp.Infrastructure private void ProcessSearchResponseMessage(HttpResponseMessage message, IPAddress localIpAddress) { if (!message.IsSuccessStatusCode) return; - + var location = GetFirstHeaderUriValue("Location", message); if (location != null) { -- cgit v1.2.3 From 806ae1bc07e715c6109a3e8ec96c6d3dd6a802ef Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 29 Apr 2020 08:04:05 -0600 Subject: Remove versioned API --- Emby.Server.Implementations/Browser/BrowserLauncher.cs | 2 +- .../Extensions/ApiApplicationBuilderExtensions.cs | 16 ++++++++-------- .../Extensions/ApiServiceCollectionExtensions.cs | 2 +- Jellyfin.Server/Program.cs | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Emby.Server.Implementations/Browser/BrowserLauncher.cs b/Emby.Server.Implementations/Browser/BrowserLauncher.cs index 384cb049f..e706401fd 100644 --- a/Emby.Server.Implementations/Browser/BrowserLauncher.cs +++ b/Emby.Server.Implementations/Browser/BrowserLauncher.cs @@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.Browser /// The app host. public static void OpenSwaggerPage(IServerApplicationHost appHost) { - TryOpenUrl(appHost, "/api-docs/v1/swagger"); + TryOpenUrl(appHost, "/api-docs/swagger"); } /// diff --git a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs index 33fd77d9c..745567703 100644 --- a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs @@ -1,5 +1,4 @@ using MediaBrowser.Controller.Configuration; -using Jellyfin.Server.Middleware; using Microsoft.AspNetCore.Builder; namespace Jellyfin.Server.Extensions @@ -31,19 +30,20 @@ namespace Jellyfin.Server.Extensions return applicationBuilder .UseSwagger(c => { - c.RouteTemplate = $"/{baseUrl}api-docs/{{documentName}}/openapi.json"; + // Custom path requires {documentName}, SwaggerDoc documentName is 'api-docs' + c.RouteTemplate = $"/{baseUrl}{{documentName}}/openapi.json"; }) .UseSwaggerUI(c => { - c.DocumentTitle = "Jellyfin API v1"; - c.SwaggerEndpoint($"/{baseUrl}api-docs/v1/openapi.json", "Jellyfin API v1"); - c.RoutePrefix = $"{baseUrl}api-docs/v1/swagger"; + c.DocumentTitle = "Jellyfin API"; + c.SwaggerEndpoint($"/{baseUrl}api-docs/openapi.json", "Jellyfin API"); + c.RoutePrefix = $"{baseUrl}api-docs/swagger"; }) .UseReDoc(c => { - c.DocumentTitle = "Jellyfin API v1"; - c.SpecUrl($"/{baseUrl}api-docs/v1/openapi.json"); - c.RoutePrefix = $"{baseUrl}api-docs/v1/redoc"; + c.DocumentTitle = "Jellyfin API"; + c.SpecUrl($"/{baseUrl}api-docs/openapi.json"); + c.RoutePrefix = $"{baseUrl}api-docs/redoc"; }); } } diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index a24785d57..a354f45aa 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -96,7 +96,7 @@ namespace Jellyfin.Server.Extensions { return serviceCollection.AddSwaggerGen(c => { - c.SwaggerDoc("v1", new OpenApiInfo { Title = "Jellyfin API", Version = "v1" }); + c.SwaggerDoc("api-docs", new OpenApiInfo { Title = "Jellyfin API" }); // Add all xml doc files to swagger generator. var xmlFiles = Directory.GetFiles( diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 23ddcf159..713580080 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -529,7 +529,7 @@ namespace Jellyfin.Server var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration; if (startupConfig != null && !startupConfig.HostWebClient()) { - inMemoryDefaultConfig[HttpListenerHost.DefaultRedirectKey] = "api-docs/v1/swagger"; + inMemoryDefaultConfig[HttpListenerHost.DefaultRedirectKey] = "api-docs/swagger"; } return config -- cgit v1.2.3 From 97ecffceb7fe655010c1f415fd688b3ee0f9d48d Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 29 Apr 2020 08:59:34 -0600 Subject: Add response code descriptions --- Jellyfin.Api/Controllers/StartupController.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index 14c59593f..d60e46a01 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -33,6 +33,7 @@ namespace Jellyfin.Api.Controllers /// /// Api endpoint for completing the startup wizard. /// + /// Startup wizard completed. /// Status. [HttpPost("Complete")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -47,6 +48,7 @@ namespace Jellyfin.Api.Controllers /// /// Endpoint for getting the initial startup wizard configuration. /// + /// Initial startup wizard configuration retrieved. /// The initial startup wizard configuration. [HttpGet("Configuration")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -68,6 +70,7 @@ namespace Jellyfin.Api.Controllers /// The UI language culture. /// The metadata country code. /// The preferred language for metadata. + /// Configuration saved. /// Status. [HttpPost("Configuration")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -88,6 +91,7 @@ namespace Jellyfin.Api.Controllers /// /// Enable remote access. /// Enable UPnP. + /// Configuration saved. /// Status. [HttpPost("RemoteAccess")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -102,6 +106,7 @@ namespace Jellyfin.Api.Controllers /// /// Endpoint for returning the first user. /// + /// Initial user retrieved. /// The first user. [HttpGet("User")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -115,6 +120,7 @@ namespace Jellyfin.Api.Controllers /// Endpoint for updating the user name and password. /// /// The DTO containing username and password. + /// Updated user name and password. /// The async task. [HttpPost("User")] [ProducesResponseType(StatusCodes.Status200OK)] -- cgit v1.2.3 From 7a3925b863a12bea492a93f41cda4eb92dc9c183 Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 29 Apr 2020 09:41:12 -0600 Subject: Fix docs --- Jellyfin.Api/Controllers/StartupController.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index d60e46a01..66e4774aa 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -31,7 +31,7 @@ namespace Jellyfin.Api.Controllers } /// - /// Api endpoint for completing the startup wizard. + /// Completes the startup wizard. /// /// Startup wizard completed. /// Status. @@ -46,7 +46,7 @@ namespace Jellyfin.Api.Controllers } /// - /// Endpoint for getting the initial startup wizard configuration. + /// Gets the initial startup wizard configuration. /// /// Initial startup wizard configuration retrieved. /// The initial startup wizard configuration. @@ -65,7 +65,7 @@ namespace Jellyfin.Api.Controllers } /// - /// Endpoint for updating the initial startup wizard configuration. + /// Sets the initial startup wizard configuration. /// /// The UI language culture. /// The metadata country code. @@ -87,7 +87,7 @@ namespace Jellyfin.Api.Controllers } /// - /// Endpoint for (dis)allowing remote access and UPnP. + /// Sets remote access and UPnP. /// /// Enable remote access. /// Enable UPnP. @@ -104,7 +104,7 @@ namespace Jellyfin.Api.Controllers } /// - /// Endpoint for returning the first user. + /// Gets the first user. /// /// Initial user retrieved. /// The first user. @@ -117,7 +117,7 @@ namespace Jellyfin.Api.Controllers } /// - /// Endpoint for updating the user name and password. + /// Sets the user name and password. /// /// The DTO containing username and password. /// Updated user name and password. -- cgit v1.2.3 From 82231b4393bb367f7fca50fed21f00e469b9f960 Mon Sep 17 00:00:00 2001 From: ZadenRB Date: Wed, 29 Apr 2020 15:53:29 -0600 Subject: Update to return IEnumerable directly where possible --- Jellyfin.Api/Controllers/NotificationsController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs index 8feea9ab6..3cbb3a3a3 100644 --- a/Jellyfin.Api/Controllers/NotificationsController.cs +++ b/Jellyfin.Api/Controllers/NotificationsController.cs @@ -75,7 +75,7 @@ namespace Jellyfin.Api.Controllers /// An containing a list of all notification types. [HttpGet("Types")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetNotificationTypes() + public IEnumerable GetNotificationTypes() { return _notificationManager.GetNotificationTypes(); } @@ -87,9 +87,9 @@ namespace Jellyfin.Api.Controllers /// An containing a list of all notification services. [HttpGet("Services")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetNotificationServices() + public IEnumerable GetNotificationServices() { - return _notificationManager.GetNotificationServices().ToList(); + return _notificationManager.GetNotificationServices(); } /// -- cgit v1.2.3 From 0d8253d8e22d4cf34c58577e7fefb3f5733adedd Mon Sep 17 00:00:00 2001 From: Bruce Date: Fri, 1 May 2020 15:17:40 +0100 Subject: Updated documentation according to discussion in jellyfin#2872 --- Jellyfin.Api/Controllers/PackageController.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs index 1da5ac0e9..b5ee47ee4 100644 --- a/Jellyfin.Api/Controllers/PackageController.cs +++ b/Jellyfin.Api/Controllers/PackageController.cs @@ -32,11 +32,11 @@ namespace Jellyfin.Api.Controllers } /// - /// Gets a package, by name or assembly guid. + /// Gets a package by name or assembly guid. /// /// The name of the package. /// The guid of the associated assembly. - /// Package info. + /// A containing package information. [HttpGet("/{Name}")] [ProducesResponseType(typeof(PackageInfo), StatusCodes.Status200OK)] public async Task> GetPackageInfo( @@ -55,7 +55,7 @@ namespace Jellyfin.Api.Controllers /// /// Gets available packages. /// - /// Packages information. + /// An containing available packages information. [HttpGet] [ProducesResponseType(typeof(PackageInfo[]), StatusCodes.Status200OK)] public async Task> GetPackages() @@ -71,7 +71,9 @@ namespace Jellyfin.Api.Controllers /// Package name. /// Guid of the associated assembly. /// Optional version. Defaults to latest version. - /// Status. + /// Package found. + /// Package not found. + /// An on success, or a if the package could not be found. [HttpPost("/Installed/{Name}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -102,7 +104,8 @@ namespace Jellyfin.Api.Controllers /// Cancels a package installation. /// /// Installation Id. - /// Status. + /// Installation cancelled. + /// An on successfully cancelling a package installation. [HttpDelete("/Installing/{id}")] [Authorize(Policy = Policies.RequiresElevation)] public IActionResult CancelPackageInstallation( -- cgit v1.2.3 From 04f826e50c23a8a996fd317124260f67b7ff3f9a Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 2 May 2020 01:09:35 +0200 Subject: Fix merge errors --- Emby.Server.Implementations/ApplicationHost.cs | 3 +- .../HttpServer/HttpListenerHost.cs | 9 +- .../HttpServer/IHttpListener.cs | 39 ------ .../Net/WebSocketConnectEventArgs.cs | 29 ----- .../SocketSharp/WebSocketSharpListener.cs | 135 --------------------- .../WebSockets/WebSocketManager.cs | 102 ---------------- 6 files changed, 4 insertions(+), 313 deletions(-) delete mode 100644 Emby.Server.Implementations/HttpServer/IHttpListener.cs delete mode 100644 Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs delete mode 100644 Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs delete mode 100644 Emby.Server.Implementations/WebSockets/WebSocketManager.cs diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 8b387e195..6279ce5d0 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -504,7 +504,7 @@ namespace Emby.Server.Implementations } public Task ExecuteHttpHandlerAsync(HttpContext context, Func next) - => HttpServer.RequestHandler(context); + => _httpServer.RequestHandler(context); /// /// Registers services/resources with the service collection that will be available via DI. @@ -597,7 +597,6 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 2648b57d5..e75140d6c 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -53,7 +53,6 @@ namespace Emby.Server.Implementations.HttpServer private readonly string _baseUrlPrefix; private readonly Dictionary _serviceOperationsMap = new Dictionary(); - private readonly List _webSocketConnections = new List(); private readonly IHostEnvironment _hostEnvironment; private IWebSocketListener[] _webSocketListeners = Array.Empty(); @@ -67,10 +66,10 @@ namespace Emby.Server.Implementations.HttpServer INetworkManager networkManager, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, - IHttpListener socketListener, ILocalizationManager localizationManager, ServiceController serviceController, - IHostEnvironment hostEnvironment) + IHostEnvironment hostEnvironment, + ILoggerFactory loggerFactory) { _appHost = applicationHost; _logger = logger; @@ -80,11 +79,9 @@ namespace Emby.Server.Implementations.HttpServer _networkManager = networkManager; _jsonSerializer = jsonSerializer; _xmlSerializer = xmlSerializer; - _socketListener = socketListener; ServiceController = serviceController; - - _socketListener.WebSocketConnected = OnWebSocketConnected; _hostEnvironment = hostEnvironment; + _loggerFactory = loggerFactory; _funcParseFn = t => s => JsvReader.GetParseFn(t)(s); diff --git a/Emby.Server.Implementations/HttpServer/IHttpListener.cs b/Emby.Server.Implementations/HttpServer/IHttpListener.cs deleted file mode 100644 index 501593725..000000000 --- a/Emby.Server.Implementations/HttpServer/IHttpListener.cs +++ /dev/null @@ -1,39 +0,0 @@ -#pragma warning disable CS1591 - -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/Net/WebSocketConnectEventArgs.cs b/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs deleted file mode 100644 index 6880766f9..000000000 --- a/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -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/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs deleted file mode 100644 index b85750c9b..000000000 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; -using Emby.Server.Implementations.HttpServer; -using Emby.Server.Implementations.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/Emby.Server.Implementations/WebSockets/WebSocketManager.cs b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs deleted file mode 100644 index 31a7468fb..000000000 --- a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.Net; -using MediaBrowser.Model.Serialization; -using Microsoft.Extensions.Logging; -using UtfUnknown; - -namespace Emby.Server.Implementations.WebSockets -{ - public class WebSocketManager - { - private readonly IWebSocketHandler[] _webSocketHandlers; - private readonly IJsonSerializer _jsonSerializer; - private readonly ILogger _logger; - private const int BufferSize = 4096; - - public WebSocketManager(IWebSocketHandler[] webSocketHandlers, IJsonSerializer jsonSerializer, ILogger logger) - { - _webSocketHandlers = webSocketHandlers; - _jsonSerializer = jsonSerializer; - _logger = logger; - } - - public async Task OnWebSocketConnected(WebSocket webSocket) - { - var taskCompletionSource = new TaskCompletionSource(); - var cancellationToken = new CancellationTokenSource().Token; - WebSocketReceiveResult result; - var message = new List(); - - // Keep listening for incoming messages, otherwise the socket closes automatically - do - { - var buffer = WebSocket.CreateServerBuffer(BufferSize); - result = await webSocket.ReceiveAsync(buffer, cancellationToken).ConfigureAwait(false); - message.AddRange(buffer.Array.Take(result.Count)); - - if (result.EndOfMessage) - { - await ProcessMessage(message.ToArray(), taskCompletionSource).ConfigureAwait(false); - message.Clear(); - } - } while (!taskCompletionSource.Task.IsCompleted && - webSocket.State == WebSocketState.Open && - result.MessageType != WebSocketMessageType.Close); - - if (webSocket.State == WebSocketState.Open) - { - await webSocket.CloseAsync( - result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, - result.CloseStatusDescription, - cancellationToken).ConfigureAwait(false); - } - } - - private async Task ProcessMessage(byte[] messageBytes, TaskCompletionSource taskCompletionSource) - { - var charset = CharsetDetector.DetectFromBytes(messageBytes).Detected?.EncodingName; - var message = string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase) - ? Encoding.UTF8.GetString(messageBytes, 0, messageBytes.Length) - : Encoding.ASCII.GetString(messageBytes, 0, messageBytes.Length); - - // All messages are expected to be valid JSON objects - if (!message.StartsWith("{", StringComparison.OrdinalIgnoreCase)) - { - _logger.LogDebug("Received web socket message that is not a json structure: {Message}", message); - return; - } - - try - { - var info = _jsonSerializer.DeserializeFromString>(message); - - _logger.LogDebug("Websocket message received: {0}", info.MessageType); - - var tasks = _webSocketHandlers.Select(handler => Task.Run(() => - { - try - { - handler.ProcessMessage(info, taskCompletionSource).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError(ex, "{HandlerType} failed processing WebSocket message {MessageType}", - handler.GetType().Name, info.MessageType ?? string.Empty); - } - })); - - await Task.WhenAll(tasks); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error processing web socket message"); - } - } - } -} -- 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(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 6279ce5d0..11fed24f7 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 e75140d6c..7358c1a81 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 1af748ebc..095725c50 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 c7ef9b1ce..a0274acd2 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 666ac1cfe..f1c441761 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 b737301c709ba4c2575b2a38ddbba6de96477413 Mon Sep 17 00:00:00 2001 From: Neil Burrows Date: Sat, 2 May 2020 17:56:09 +0100 Subject: Auto discover published URL override --- .../EntryPoints/UdpServerEntryPoint.cs | 9 +++++--- Emby.Server.Implementations/IStartupOptions.cs | 5 +++++ Emby.Server.Implementations/Udp/UdpServer.cs | 24 +++++++++++++++++++--- Jellyfin.Server/StartupOptions.cs | 11 ++++++++++ 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index 50ba0f8fa..6929c81f9 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Emby.Server.Implementations.Udp; using MediaBrowser.Controller; using MediaBrowser.Controller.Plugins; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.EntryPoints @@ -22,6 +23,7 @@ namespace Emby.Server.Implementations.EntryPoints /// private readonly ILogger _logger; private readonly IServerApplicationHost _appHost; + private readonly IConfiguration _config; /// /// The UDP server. @@ -35,18 +37,19 @@ namespace Emby.Server.Implementations.EntryPoints /// public UdpServerEntryPoint( ILogger logger, - IServerApplicationHost appHost) + IServerApplicationHost appHost, + IConfiguration configuration) { _logger = logger; _appHost = appHost; - + _config = configuration; } /// public async Task RunAsync() { - _udpServer = new UdpServer(_logger, _appHost); + _udpServer = new UdpServer(_logger, _appHost, _config); _udpServer.Start(PortNumber, _cancellationTokenSource.Token); } diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index 16b68170b..a3a047057 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -36,5 +36,10 @@ namespace Emby.Server.Implementations /// Gets the value of the --plugin-manifest-url command line option. /// string PluginManifestUrl { get; } + + /// + /// Gets the value of the --auto-discover-publish-url command line option. + /// + string AutoDiscoverPublishUrl { get; } } } diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index c91d137a7..57228d208 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller; using MediaBrowser.Model.ApiClient; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Udp @@ -21,6 +22,12 @@ namespace Emby.Server.Implementations.Udp /// private readonly ILogger _logger; private readonly IServerApplicationHost _appHost; + private readonly IConfiguration _config; + + /// + /// Address Override Configuration Key + /// + public const string AddressOverrideConfigKey = "AutoDiscoverAddressOverride"; private Socket _udpSocket; private IPEndPoint _endpoint; @@ -31,15 +38,26 @@ namespace Emby.Server.Implementations.Udp /// /// Initializes a new instance of the class. /// - public UdpServer(ILogger logger, IServerApplicationHost appHost) + public UdpServer(ILogger logger, IServerApplicationHost appHost, IConfiguration configuration) { _logger = logger; _appHost = appHost; + _config = configuration; } private async Task RespondToV2Message(string messageText, EndPoint endpoint, CancellationToken cancellationToken) { - var localUrl = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false); + string localUrl; + + if (!string.IsNullOrEmpty(_config[AddressOverrideConfigKey])) + { + localUrl = _config[AddressOverrideConfigKey]; + } + else + { + localUrl = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false); + } + if (!string.IsNullOrEmpty(localUrl)) { @@ -105,7 +123,7 @@ namespace Emby.Server.Implementations.Udp } catch (SocketException ex) { - _logger.LogError(ex, "Failed to receive data drom socket"); + _logger.LogError(ex, "Failed to receive data from socket"); } catch (OperationCanceledException) { diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index 6e15d058f..135ba9d7f 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using CommandLine; using Emby.Server.Implementations; +using Emby.Server.Implementations.EntryPoints; +using Emby.Server.Implementations.Udp; using Emby.Server.Implementations.Updates; using MediaBrowser.Controller.Extensions; @@ -80,6 +82,10 @@ namespace Jellyfin.Server [Option("plugin-manifest-url", Required = false, HelpText = "A custom URL for the plugin repository JSON manifest")] public string? PluginManifestUrl { get; set; } + /// + [Option("auto-discover-publish-url", Required = false, HelpText = "Jellyfin Server URL to publish via auto discover process")] + public string? AutoDiscoverPublishUrl { get; set; } + /// /// Gets the command line options as a dictionary that can be used in the .NET configuration system. /// @@ -98,6 +104,11 @@ namespace Jellyfin.Server config.Add(ConfigurationExtensions.HostWebClientKey, bool.FalseString); } + if (AutoDiscoverPublishUrl != null) + { + config.Add(UdpServer.AddressOverrideConfigKey, AutoDiscoverPublishUrl); + } + return config; } } -- cgit v1.2.3 From df65e3ab0db8fd55a6a02b8c067565abc926136f Mon Sep 17 00:00:00 2001 From: ConfusedPolarBear <33811686+ConfusedPolarBear@users.noreply.github.com> Date: Sat, 2 May 2020 15:29:05 -0500 Subject: Add Access-Control-Allow-Origin header to exceptions Fixes #1794 --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 211a0c1d9..77878eacc 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -542,6 +542,11 @@ namespace Emby.Server.Implementations.HttpServer var requestInnerEx = GetActualException(requestEx); var statusCode = GetStatusCode(requestInnerEx); + if (!httpRes.Headers.ContainsKey("Access-Control-Allow-Origin")) + { + httpRes.Headers.Add("Access-Control-Allow-Origin", "*"); + } + // Do not handle 500 server exceptions manually when in development mode // The framework-defined development exception page will be returned instead if (statusCode == 500 && _hostEnvironment.IsDevelopment()) -- cgit v1.2.3 From 2b41f8ab632b706b0f4e2d17032dbb07ce73136e Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 2 May 2020 17:56:05 -0400 Subject: Clean up generated code --- Jellyfin.Data/Entities/Artwork.cs | 366 +++++++------- Jellyfin.Data/Entities/Book.cs | 109 ++--- Jellyfin.Data/Entities/BookMetadata.cs | 199 ++++---- Jellyfin.Data/Entities/Chapter.cs | 453 +++++++++-------- Jellyfin.Data/Entities/Collection.cs | 201 ++++---- Jellyfin.Data/Entities/CollectionItem.cs | 270 ++++++----- Jellyfin.Data/Entities/Company.cs | 259 +++++----- Jellyfin.Data/Entities/CompanyMetadata.cs | 411 ++++++++-------- Jellyfin.Data/Entities/CustomItem.cs | 109 ++--- Jellyfin.Data/Entities/CustomItemMetadata.cs | 113 ++--- Jellyfin.Data/Entities/Episode.cs | 209 ++++---- Jellyfin.Data/Entities/EpisodeMetadata.cs | 341 +++++++------ Jellyfin.Data/Entities/Genre.cs | 281 ++++++----- Jellyfin.Data/Entities/Group.cs | 203 ++++---- Jellyfin.Data/Entities/Library.cs | 271 +++++------ Jellyfin.Data/Entities/LibraryItem.cs | 318 ++++++------ Jellyfin.Data/Entities/LibraryRoot.cs | 362 +++++++------- Jellyfin.Data/Entities/MediaFile.cs | 368 +++++++------- Jellyfin.Data/Entities/MediaFileStream.cs | 275 ++++++----- Jellyfin.Data/Entities/Metadata.cs | 696 +++++++++++++-------------- Jellyfin.Data/Entities/MetadataProvider.cs | 271 +++++------ Jellyfin.Data/Entities/MetadataProviderId.cs | 340 +++++++------ Jellyfin.Data/Entities/Movie.cs | 109 ++--- Jellyfin.Data/Entities/MovieMetadata.cs | 421 ++++++++-------- Jellyfin.Data/Entities/MusicAlbum.cs | 110 ++--- Jellyfin.Data/Entities/MusicAlbumMetadata.cs | 350 +++++++------- Jellyfin.Data/Entities/Permission.cs | 270 +++++------ Jellyfin.Data/Entities/PermissionKind.cs | 40 -- Jellyfin.Data/Entities/Person.cs | 519 ++++++++++---------- Jellyfin.Data/Entities/PersonRole.cs | 379 ++++++++------- Jellyfin.Data/Entities/Photo.cs | 110 ++--- Jellyfin.Data/Entities/PhotoMetadata.cs | 113 ++--- Jellyfin.Data/Entities/Preference.cs | 204 ++++---- Jellyfin.Data/Entities/PreferenceKind.cs | 27 -- Jellyfin.Data/Entities/ProviderMapping.cs | 236 +++++---- Jellyfin.Data/Entities/Rating.cs | 352 +++++++------- Jellyfin.Data/Entities/RatingSource.cs | 437 +++++++++-------- Jellyfin.Data/Entities/Release.cs | 356 +++++++------- Jellyfin.Data/Entities/Season.cs | 208 ++++---- Jellyfin.Data/Entities/SeasonMetadata.cs | 201 ++++---- Jellyfin.Data/Entities/Series.cs | 312 ++++++------ Jellyfin.Data/Entities/SeriesMetadata.cs | 421 ++++++++-------- Jellyfin.Data/Entities/Track.cs | 207 ++++---- Jellyfin.Data/Entities/TrackMetadata.cs | 113 ++--- Jellyfin.Data/Entities/User.cs | 456 +++++++++--------- Jellyfin.Data/Enums/ArtKind.cs | 28 +- Jellyfin.Data/Enums/MediaFileKind.cs | 28 +- Jellyfin.Data/Enums/PermissionKind.cs | 28 ++ Jellyfin.Data/Enums/PersonRoleType.cs | 42 +- Jellyfin.Data/Enums/PreferenceKind.cs | 15 + Jellyfin.Data/Enums/Weekday.cs | 32 +- Jellyfin.Data/Structs/.gitkeep | 0 52 files changed, 6093 insertions(+), 6456 deletions(-) delete mode 100644 Jellyfin.Data/Entities/PermissionKind.cs delete mode 100644 Jellyfin.Data/Entities/PreferenceKind.cs create mode 100644 Jellyfin.Data/Enums/PermissionKind.cs create mode 100644 Jellyfin.Data/Enums/PreferenceKind.cs delete mode 100644 Jellyfin.Data/Structs/.gitkeep diff --git a/Jellyfin.Data/Entities/Artwork.cs b/Jellyfin.Data/Entities/Artwork.cs index be13686dc..da31d686e 100644 --- a/Jellyfin.Data/Entities/Artwork.cs +++ b/Jellyfin.Data/Entities/Artwork.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,188 +9,194 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class Artwork - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Artwork() - { - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static Artwork CreateArtworkUnsafe() - { - return new Artwork(); - } - - /// - /// Public constructor with required data - /// - /// - /// - /// - /// - public Artwork(string path, global::Jellyfin.Data.Enums.ArtKind kind, global::Jellyfin.Data.Entities.Metadata _metadata0, global::Jellyfin.Data.Entities.PersonRole _personrole1) - { - if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); - this.Path = path; - - this.Kind = kind; - - if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0)); - _metadata0.Artwork.Add(this); - - if (_personrole1 == null) throw new ArgumentNullException(nameof(_personrole1)); - _personrole1.Artwork = this; - - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// - /// - /// - /// - public static Artwork Create(string path, global::Jellyfin.Data.Enums.ArtKind kind, global::Jellyfin.Data.Entities.Metadata _metadata0, global::Jellyfin.Data.Entities.PersonRole _personrole1) - { - return new Artwork(path, kind, _metadata0, _personrole1); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for Id - /// - internal int _Id; - /// - /// When provided in a partial class, allows value of Id to be changed before setting. - /// - partial void SetId(int oldValue, ref int newValue); - /// - /// When provided in a partial class, allows value of Id to be changed before returning. - /// - partial void GetId(ref int result); - - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public int Id - { - get - { - int value = _Id; - GetId(ref value); - return (_Id = value); - } - protected set - { - int oldValue = _Id; - SetId(oldValue, ref value); - if (oldValue != value) + [Table("Artwork")] + public partial class Artwork + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Artwork() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Artwork CreateArtworkUnsafe() + { + return new Artwork(); + } + + /// + /// Public constructor with required data + /// + /// + /// + /// + /// + public Artwork(string path, Enums.ArtKind kind, Metadata _metadata0, PersonRole _personrole1) + { + if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); + this.Path = path; + + this.Kind = kind; + + if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0)); + _metadata0.Artwork.Add(this); + + if (_personrole1 == null) throw new ArgumentNullException(nameof(_personrole1)); + _personrole1.Artwork = this; + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + /// + /// + public static Artwork Create(string path, Enums.ArtKind kind, Metadata _metadata0, PersonRole _personrole1) + { + return new Artwork(path, kind, _metadata0, _personrole1); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for Path + /// + protected string _Path; + /// + /// When provided in a partial class, allows value of Path to be changed before setting. + /// + partial void SetPath(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Path to be changed before returning. + /// + partial void GetPath(ref string result); + + /// + /// Required, Max length = 65535 + /// + [Required] + [MaxLength(65535)] + [StringLength(65535)] + public string Path + { + get + { + string value = _Path; + GetPath(ref value); + return (_Path = value); + } + set { - _Id = value; + string oldValue = _Path; + SetPath(oldValue, ref value); + if (oldValue != value) + { + _Path = value; + } } - } - } - - /// - /// Backing field for Path - /// - protected string _Path; - /// - /// When provided in a partial class, allows value of Path to be changed before setting. - /// - partial void SetPath(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Path to be changed before returning. - /// - partial void GetPath(ref string result); - - /// - /// Required, Max length = 65535 - /// - [Required] - [MaxLength(65535)] - [StringLength(65535)] - public string Path - { - get - { - string value = _Path; - GetPath(ref value); - return (_Path = value); - } - set - { - string oldValue = _Path; - SetPath(oldValue, ref value); - if (oldValue != value) + } + + /// + /// Backing field for Kind + /// + internal Enums.ArtKind _Kind; + /// + /// When provided in a partial class, allows value of Kind to be changed before setting. + /// + partial void SetKind(Enums.ArtKind oldValue, ref Enums.ArtKind newValue); + /// + /// When provided in a partial class, allows value of Kind to be changed before returning. + /// + partial void GetKind(ref Enums.ArtKind result); + + /// + /// Indexed, Required + /// + [Required] + public Enums.ArtKind Kind + { + get { - _Path = value; + Enums.ArtKind value = _Kind; + GetKind(ref value); + return (_Kind = value); } - } - } - - /// - /// Backing field for Kind - /// - internal global::Jellyfin.Data.Enums.ArtKind _Kind; - /// - /// When provided in a partial class, allows value of Kind to be changed before setting. - /// - partial void SetKind(global::Jellyfin.Data.Enums.ArtKind oldValue, ref global::Jellyfin.Data.Enums.ArtKind newValue); - /// - /// When provided in a partial class, allows value of Kind to be changed before returning. - /// - partial void GetKind(ref global::Jellyfin.Data.Enums.ArtKind result); - - /// - /// Indexed, Required - /// - [Required] - public global::Jellyfin.Data.Enums.ArtKind Kind - { - get - { - global::Jellyfin.Data.Enums.ArtKind value = _Kind; - GetKind(ref value); - return (_Kind = value); - } - set - { - global::Jellyfin.Data.Enums.ArtKind oldValue = _Kind; - SetKind(oldValue, ref value); - if (oldValue != value) + set { - _Kind = value; + Enums.ArtKind oldValue = _Kind; + SetKind(oldValue, ref value); + if (oldValue != value) + { + _Kind = value; + } } - } - } + } + + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } - /// - /// Required - /// - [ConcurrencyCheck] - [Required] - public byte[] Timestamp { get; set; } + public void OnSavingChanges() + { + RowVersion++; + } - /************************************************************************* - * Navigation properties - *************************************************************************/ + /************************************************************************* + * Navigation properties + *************************************************************************/ - } + } } diff --git a/Jellyfin.Data/Entities/Book.cs b/Jellyfin.Data/Entities/Book.cs index 30c89ae5c..7dda26f41 100644 --- a/Jellyfin.Data/Entities/Book.cs +++ b/Jellyfin.Data/Entities/Book.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,64 +9,67 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class Book: global::Jellyfin.Data.Entities.LibraryItem - { - partial void Init(); + [Table("Book")] + public partial class Book : LibraryItem + { + partial void Init(); - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Book(): base() - { - BookMetadata = new System.Collections.Generic.HashSet(); - Releases = new System.Collections.Generic.HashSet(); + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Book() : base() + { + BookMetadata = new HashSet(); + Releases = new HashSet(); - Init(); - } + Init(); + } - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static Book CreateBookUnsafe() - { - return new Book(); - } + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Book CreateBookUnsafe() + { + return new Book(); + } - /// - /// 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) - { - this.UrlId = urlid; + /// + /// 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) + { + this.UrlId = urlid; - this.BookMetadata = new System.Collections.Generic.HashSet(); - this.Releases = new System.Collections.Generic.HashSet(); + this.BookMetadata = new HashSet(); + this.Releases = new HashSet(); - Init(); - } + Init(); + } - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// This is whats gets displayed in the Urls and API requests. This could also be a string. - public static Book Create(Guid urlid, DateTime dateadded) - { - return new Book(urlid, dateadded); - } + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + public static Book Create(Guid urlid, DateTime dateadded) + { + return new Book(urlid, dateadded); + } - /************************************************************************* - * Properties - *************************************************************************/ + /************************************************************************* + * Properties + *************************************************************************/ - /************************************************************************* - * Navigation properties - *************************************************************************/ + /************************************************************************* + * Navigation properties + *************************************************************************/ - public virtual ICollection BookMetadata { get; protected set; } + [ForeignKey("BookMetadata_BookMetadata_Id")] + public virtual ICollection BookMetadata { get; protected set; } - public virtual ICollection Releases { get; protected set; } + [ForeignKey("Release_Releases_Id")] + public virtual ICollection Releases { get; protected set; } - } + } } diff --git a/Jellyfin.Data/Entities/BookMetadata.cs b/Jellyfin.Data/Entities/BookMetadata.cs index 3a28244d6..8afd37163 100644 --- a/Jellyfin.Data/Entities/BookMetadata.cs +++ b/Jellyfin.Data/Entities/BookMetadata.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,103 +9,104 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class BookMetadata: global::Jellyfin.Data.Entities.Metadata - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected BookMetadata(): base() - { - Publishers = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static BookMetadata CreateBookMetadataUnsafe() - { - return new BookMetadata(); - } - - /// - /// Public constructor with required data - /// - /// The title or name of the object - /// ISO-639-3 3-character language codes - /// - public BookMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Book _book0) - { - if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); - this.Title = title; - - if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); - this.Language = language; - - if (_book0 == null) throw new ArgumentNullException(nameof(_book0)); - _book0.BookMetadata.Add(this); - - this.Publishers = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// The title or name of the object - /// ISO-639-3 3-character language codes - /// - public static BookMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Book _book0) - { - return new BookMetadata(title, language, dateadded, datemodified, _book0); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for ISBN - /// - protected long? _ISBN; - /// - /// When provided in a partial class, allows value of ISBN to be changed before setting. - /// - partial void SetISBN(long? oldValue, ref long? newValue); - /// - /// When provided in a partial class, allows value of ISBN to be changed before returning. - /// - partial void GetISBN(ref long? result); - - public long? ISBN - { - get - { - long? value = _ISBN; - GetISBN(ref value); - return (_ISBN = value); - } - set - { - long? oldValue = _ISBN; - SetISBN(oldValue, ref value); - if (oldValue != value) + public partial class BookMetadata : Metadata + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected BookMetadata() : base() + { + Publishers = new HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static BookMetadata CreateBookMetadataUnsafe() + { + return new BookMetadata(); + } + + /// + /// Public constructor with required data + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public BookMetadata(string title, string language, DateTime dateadded, DateTime datemodified, Book _book0) + { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + this.Title = title; + + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; + + if (_book0 == null) throw new ArgumentNullException(nameof(_book0)); + _book0.BookMetadata.Add(this); + + this.Publishers = new HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public static BookMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, Book _book0) + { + return new BookMetadata(title, language, dateadded, datemodified, _book0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for ISBN + /// + protected long? _ISBN; + /// + /// When provided in a partial class, allows value of ISBN to be changed before setting. + /// + partial void SetISBN(long? oldValue, ref long? newValue); + /// + /// When provided in a partial class, allows value of ISBN to be changed before returning. + /// + partial void GetISBN(ref long? result); + + public long? ISBN + { + get + { + long? value = _ISBN; + GetISBN(ref value); + return (_ISBN = value); + } + set { - _ISBN = value; + long? oldValue = _ISBN; + SetISBN(oldValue, ref value); + if (oldValue != value) + { + _ISBN = value; + } } - } - } + } - /************************************************************************* - * Navigation properties - *************************************************************************/ + /************************************************************************* + * Navigation properties + *************************************************************************/ - public virtual ICollection Publishers { get; protected set; } + [ForeignKey("Company_Publishers_Id")] + public virtual ICollection Publishers { get; protected set; } - } + } } diff --git a/Jellyfin.Data/Entities/Chapter.cs b/Jellyfin.Data/Entities/Chapter.cs index 21a5dd73e..1ee6a9c07 100644 --- a/Jellyfin.Data/Entities/Chapter.cs +++ b/Jellyfin.Data/Entities/Chapter.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,254 +9,261 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class Chapter - { - partial void Init(); + [Table("Chapter")] + public partial class Chapter + { + partial void Init(); - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Chapter() - { - Init(); - } + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Chapter() + { + Init(); + } - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static Chapter CreateChapterUnsafe() - { - return new Chapter(); - } + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Chapter CreateChapterUnsafe() + { + return new Chapter(); + } - /// - /// Public constructor with required data - /// - /// ISO-639-3 3-character language codes - /// - /// - public Chapter(string language, long timestart, global::Jellyfin.Data.Entities.Release _release0) - { - if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); - this.Language = language; + /// + /// Public constructor with required data + /// + /// ISO-639-3 3-character language codes + /// + /// + public Chapter(string language, long timestart, Release _release0) + { + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; - this.TimeStart = timestart; + this.TimeStart = timestart; - if (_release0 == null) throw new ArgumentNullException(nameof(_release0)); - _release0.Chapters.Add(this); + if (_release0 == null) throw new ArgumentNullException(nameof(_release0)); + _release0.Chapters.Add(this); - Init(); - } + Init(); + } - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// ISO-639-3 3-character language codes - /// - /// - public static Chapter Create(string language, long timestart, global::Jellyfin.Data.Entities.Release _release0) - { - return new Chapter(language, timestart, _release0); - } + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// ISO-639-3 3-character language codes + /// + /// + public static Chapter Create(string language, long timestart, Release _release0) + { + return new Chapter(language, timestart, _release0); + } - /************************************************************************* - * Properties - *************************************************************************/ + /************************************************************************* + * Properties + *************************************************************************/ - /// - /// Backing field for Id - /// - internal int _Id; - /// - /// When provided in a partial class, allows value of Id to be changed before setting. - /// - partial void SetId(int oldValue, ref int newValue); - /// - /// When provided in a partial class, allows value of Id to be changed before returning. - /// - partial void GetId(ref int result); + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public int Id - { - get - { - int value = _Id; - GetId(ref value); - return (_Id = value); - } - protected set - { - int oldValue = _Id; - SetId(oldValue, ref value); - if (oldValue != value) + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set { - _Id = value; + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } } - } - } + } - /// - /// Backing field for Name - /// - protected string _Name; - /// - /// When provided in a partial class, allows value of Name to be changed before setting. - /// - partial void SetName(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Name to be changed before returning. - /// - partial void GetName(ref string result); + /// + /// Backing field for Name + /// + protected string _Name; + /// + /// When provided in a partial class, allows value of Name to be changed before setting. + /// + partial void SetName(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Name to be changed before returning. + /// + partial void GetName(ref string result); - /// - /// Max length = 1024 - /// - [MaxLength(1024)] - [StringLength(1024)] - public string Name - { - get - { - string value = _Name; - GetName(ref value); - return (_Name = value); - } - set - { - string oldValue = _Name; - SetName(oldValue, ref value); - if (oldValue != value) + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Name + { + get { - _Name = value; + string value = _Name; + GetName(ref value); + return (_Name = value); } - } - } + set + { + string oldValue = _Name; + SetName(oldValue, ref value); + if (oldValue != value) + { + _Name = value; + } + } + } - /// - /// Backing field for Language - /// - protected string _Language; - /// - /// When provided in a partial class, allows value of Language to be changed before setting. - /// - partial void SetLanguage(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Language to be changed before returning. - /// - partial void GetLanguage(ref string result); + /// + /// Backing field for Language + /// + protected string _Language; + /// + /// When provided in a partial class, allows value of Language to be changed before setting. + /// + partial void SetLanguage(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Language to be changed before returning. + /// + partial void GetLanguage(ref string result); - /// - /// Required, Min length = 3, Max length = 3 - /// ISO-639-3 3-character language codes - /// - [Required] - [MinLength(3)] - [MaxLength(3)] - [StringLength(3)] - public string Language - { - get - { - string value = _Language; - GetLanguage(ref value); - return (_Language = value); - } - set - { - string oldValue = _Language; - SetLanguage(oldValue, ref value); - if (oldValue != value) + /// + /// Required, Min length = 3, Max length = 3 + /// ISO-639-3 3-character language codes + /// + [Required] + [MinLength(3)] + [MaxLength(3)] + [StringLength(3)] + public string Language + { + get + { + string value = _Language; + GetLanguage(ref value); + return (_Language = value); + } + set { - _Language = value; + string oldValue = _Language; + SetLanguage(oldValue, ref value); + if (oldValue != value) + { + _Language = value; + } } - } - } + } - /// - /// Backing field for TimeStart - /// - protected long _TimeStart; - /// - /// When provided in a partial class, allows value of TimeStart to be changed before setting. - /// - partial void SetTimeStart(long oldValue, ref long newValue); - /// - /// When provided in a partial class, allows value of TimeStart to be changed before returning. - /// - partial void GetTimeStart(ref long result); + /// + /// Backing field for TimeStart + /// + protected long _TimeStart; + /// + /// When provided in a partial class, allows value of TimeStart to be changed before setting. + /// + partial void SetTimeStart(long oldValue, ref long newValue); + /// + /// When provided in a partial class, allows value of TimeStart to be changed before returning. + /// + partial void GetTimeStart(ref long result); - /// - /// Required - /// - [Required] - public long TimeStart - { - get - { - long value = _TimeStart; - GetTimeStart(ref value); - return (_TimeStart = value); - } - set - { - long oldValue = _TimeStart; - SetTimeStart(oldValue, ref value); - if (oldValue != value) + /// + /// Required + /// + [Required] + public long TimeStart + { + get + { + long value = _TimeStart; + GetTimeStart(ref value); + return (_TimeStart = value); + } + set { - _TimeStart = value; + long oldValue = _TimeStart; + SetTimeStart(oldValue, ref value); + if (oldValue != value) + { + _TimeStart = value; + } } - } - } + } - /// - /// Backing field for TimeEnd - /// - protected long? _TimeEnd; - /// - /// When provided in a partial class, allows value of TimeEnd to be changed before setting. - /// - partial void SetTimeEnd(long? oldValue, ref long? newValue); - /// - /// When provided in a partial class, allows value of TimeEnd to be changed before returning. - /// - partial void GetTimeEnd(ref long? result); + /// + /// Backing field for TimeEnd + /// + protected long? _TimeEnd; + /// + /// When provided in a partial class, allows value of TimeEnd to be changed before setting. + /// + partial void SetTimeEnd(long? oldValue, ref long? newValue); + /// + /// When provided in a partial class, allows value of TimeEnd to be changed before returning. + /// + partial void GetTimeEnd(ref long? result); - public long? TimeEnd - { - get - { - long? value = _TimeEnd; - GetTimeEnd(ref value); - return (_TimeEnd = value); - } - set - { - long? oldValue = _TimeEnd; - SetTimeEnd(oldValue, ref value); - if (oldValue != value) + public long? TimeEnd + { + get { - _TimeEnd = value; + long? value = _TimeEnd; + GetTimeEnd(ref value); + return (_TimeEnd = value); } - } - } + set + { + long? oldValue = _TimeEnd; + SetTimeEnd(oldValue, ref value); + if (oldValue != value) + { + _TimeEnd = value; + } + } + } + + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } - /// - /// Required - /// - [ConcurrencyCheck] - [Required] - public byte[] Timestamp { get; set; } + public void OnSavingChanges() + { + RowVersion++; + } - /************************************************************************* - * Navigation properties - *************************************************************************/ + /************************************************************************* + * Navigation properties + *************************************************************************/ - } + } } diff --git a/Jellyfin.Data/Entities/Collection.cs b/Jellyfin.Data/Entities/Collection.cs index 68979eb2f..d3ccb13f5 100644 --- a/Jellyfin.Data/Entities/Collection.cs +++ b/Jellyfin.Data/Entities/Collection.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,111 +9,118 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class Collection - { - partial void Init(); + [Table("Collection")] + public partial class Collection + { + partial void Init(); - /// - /// Default constructor - /// - public Collection() - { - CollectionItem = new System.Collections.Generic.LinkedList(); + /// + /// Default constructor + /// + public Collection() + { + CollectionItem = new LinkedList(); - Init(); - } + Init(); + } - /************************************************************************* - * Properties - *************************************************************************/ + /************************************************************************* + * Properties + *************************************************************************/ - /// - /// Backing field for Id - /// - internal int _Id; - /// - /// When provided in a partial class, allows value of Id to be changed before setting. - /// - partial void SetId(int oldValue, ref int newValue); - /// - /// When provided in a partial class, allows value of Id to be changed before returning. - /// - partial void GetId(ref int result); + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public int Id - { - get - { - int value = _Id; - GetId(ref value); - return (_Id = value); - } - protected set - { - int oldValue = _Id; - SetId(oldValue, ref value); - if (oldValue != value) + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set { - _Id = value; + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } } - } - } + } - /// - /// Backing field for Name - /// - protected string _Name; - /// - /// When provided in a partial class, allows value of Name to be changed before setting. - /// - partial void SetName(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Name to be changed before returning. - /// - partial void GetName(ref string result); + /// + /// Backing field for Name + /// + protected string _Name; + /// + /// When provided in a partial class, allows value of Name to be changed before setting. + /// + partial void SetName(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Name to be changed before returning. + /// + partial void GetName(ref string result); - /// - /// Max length = 1024 - /// - [MaxLength(1024)] - [StringLength(1024)] - public string Name - { - get - { - string value = _Name; - GetName(ref value); - return (_Name = value); - } - set - { - string oldValue = _Name; - SetName(oldValue, ref value); - if (oldValue != value) + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Name + { + get + { + string value = _Name; + GetName(ref value); + return (_Name = value); + } + set { - _Name = value; + string oldValue = _Name; + SetName(oldValue, ref value); + if (oldValue != value) + { + _Name = value; + } } - } - } + } - /// - /// Required - /// - [ConcurrencyCheck] - [Required] - public byte[] Timestamp { get; set; } + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } - /************************************************************************* - * Navigation properties - *************************************************************************/ + public void OnSavingChanges() + { + RowVersion++; + } - public virtual ICollection CollectionItem { get; protected set; } + /************************************************************************* + * Navigation properties + *************************************************************************/ + [ForeignKey("CollectionItem_CollectionItem_Id")] + public virtual ICollection CollectionItem { get; protected set; } - } + } } diff --git a/Jellyfin.Data/Entities/CollectionItem.cs b/Jellyfin.Data/Entities/CollectionItem.cs index 8e575e0a2..f40158b20 100644 --- a/Jellyfin.Data/Entities/CollectionItem.cs +++ b/Jellyfin.Data/Entities/CollectionItem.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,131 +9,141 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class CollectionItem - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected CollectionItem() - { - // NOTE: This class has one-to-one associations with CollectionItem. - // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. - - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static CollectionItem CreateCollectionItemUnsafe() - { - return new CollectionItem(); - } - - /// - /// Public constructor with required data - /// - /// - /// - /// - public CollectionItem(global::Jellyfin.Data.Entities.Collection _collection0, global::Jellyfin.Data.Entities.CollectionItem _collectionitem1, global::Jellyfin.Data.Entities.CollectionItem _collectionitem2) - { - // NOTE: This class has one-to-one associations with CollectionItem. - // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. - - if (_collection0 == null) throw new ArgumentNullException(nameof(_collection0)); - _collection0.CollectionItem.Add(this); - - if (_collectionitem1 == null) throw new ArgumentNullException(nameof(_collectionitem1)); - _collectionitem1.Next = this; - - if (_collectionitem2 == null) throw new ArgumentNullException(nameof(_collectionitem2)); - _collectionitem2.Previous = this; - - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// - /// - /// - public static CollectionItem Create(global::Jellyfin.Data.Entities.Collection _collection0, global::Jellyfin.Data.Entities.CollectionItem _collectionitem1, global::Jellyfin.Data.Entities.CollectionItem _collectionitem2) - { - return new CollectionItem(_collection0, _collectionitem1, _collectionitem2); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for Id - /// - internal int _Id; - /// - /// When provided in a partial class, allows value of Id to be changed before setting. - /// - partial void SetId(int oldValue, ref int newValue); - /// - /// When provided in a partial class, allows value of Id to be changed before returning. - /// - partial void GetId(ref int result); - - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public int Id - { - get - { - int value = _Id; - GetId(ref value); - return (_Id = value); - } - protected set - { - int oldValue = _Id; - SetId(oldValue, ref value); - if (oldValue != value) + [Table("CollectionItem")] + public partial class CollectionItem + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected CollectionItem() + { + // NOTE: This class has one-to-one associations with CollectionItem. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static CollectionItem CreateCollectionItemUnsafe() + { + return new CollectionItem(); + } + + /// + /// Public constructor with required data + /// + /// + /// + /// + public CollectionItem(Collection _collection0, CollectionItem _collectionitem1, CollectionItem _collectionitem2) + { + // NOTE: This class has one-to-one associations with CollectionItem. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + if (_collection0 == null) throw new ArgumentNullException(nameof(_collection0)); + _collection0.CollectionItem.Add(this); + + if (_collectionitem1 == null) throw new ArgumentNullException(nameof(_collectionitem1)); + _collectionitem1.Next = this; + + if (_collectionitem2 == null) throw new ArgumentNullException(nameof(_collectionitem2)); + _collectionitem2.Previous = this; + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + /// + public static CollectionItem Create(Collection _collection0, CollectionItem _collectionitem1, CollectionItem _collectionitem2) + { + return new CollectionItem(_collection0, _collectionitem1, _collectionitem2); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set { - _Id = value; + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } } - } - } - - /// - /// Required - /// - [ConcurrencyCheck] - [Required] - public byte[] Timestamp { get; set; } - - /************************************************************************* - * Navigation properties - *************************************************************************/ - - /// - /// Required - /// - public virtual global::Jellyfin.Data.Entities.LibraryItem LibraryItem { get; set; } - - /// - /// TODO check if this properly updated dependant and has the proper principal relationship - /// - public virtual global::Jellyfin.Data.Entities.CollectionItem Next { get; set; } - - /// - /// TODO check if this properly updated dependant and has the proper principal relationship - /// - public virtual global::Jellyfin.Data.Entities.CollectionItem Previous { get; set; } - - } + } + + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } + + public void OnSavingChanges() + { + RowVersion++; + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + /// + /// Required + /// + [ForeignKey("LibraryItem_Id")] + public virtual LibraryItem LibraryItem { get; set; } + + /// + /// TODO check if this properly updated dependant and has the proper principal relationship + /// + [ForeignKey("CollectionItem_Next_Id")] + public virtual CollectionItem Next { get; set; } + + /// + /// TODO check if this properly updated dependant and has the proper principal relationship + /// + [ForeignKey("CollectionItem_Previous_Id")] + public virtual CollectionItem Previous { get; set; } + + } } diff --git a/Jellyfin.Data/Entities/Company.cs b/Jellyfin.Data/Entities/Company.cs index 444ae9c56..5b8a21423 100644 --- a/Jellyfin.Data/Entities/Company.cs +++ b/Jellyfin.Data/Entities/Company.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,127 +9,134 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class Company - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Company() - { - CompanyMetadata = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static Company CreateCompanyUnsafe() - { - return new Company(); - } - - /// - /// Public constructor with required data - /// - /// - /// - /// - /// - /// - public Company(global::Jellyfin.Data.Entities.MovieMetadata _moviemetadata0, global::Jellyfin.Data.Entities.SeriesMetadata _seriesmetadata1, global::Jellyfin.Data.Entities.MusicAlbumMetadata _musicalbummetadata2, global::Jellyfin.Data.Entities.BookMetadata _bookmetadata3, global::Jellyfin.Data.Entities.Company _company4) - { - if (_moviemetadata0 == null) throw new ArgumentNullException(nameof(_moviemetadata0)); - _moviemetadata0.Studios.Add(this); - - if (_seriesmetadata1 == null) throw new ArgumentNullException(nameof(_seriesmetadata1)); - _seriesmetadata1.Networks.Add(this); - - if (_musicalbummetadata2 == null) throw new ArgumentNullException(nameof(_musicalbummetadata2)); - _musicalbummetadata2.Labels.Add(this); - - if (_bookmetadata3 == null) throw new ArgumentNullException(nameof(_bookmetadata3)); - _bookmetadata3.Publishers.Add(this); - - if (_company4 == null) throw new ArgumentNullException(nameof(_company4)); - _company4.Parent = this; - - this.CompanyMetadata = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// - /// - /// - /// - /// - public static Company Create(global::Jellyfin.Data.Entities.MovieMetadata _moviemetadata0, global::Jellyfin.Data.Entities.SeriesMetadata _seriesmetadata1, global::Jellyfin.Data.Entities.MusicAlbumMetadata _musicalbummetadata2, global::Jellyfin.Data.Entities.BookMetadata _bookmetadata3, global::Jellyfin.Data.Entities.Company _company4) - { - return new Company(_moviemetadata0, _seriesmetadata1, _musicalbummetadata2, _bookmetadata3, _company4); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for Id - /// - internal int _Id; - /// - /// When provided in a partial class, allows value of Id to be changed before setting. - /// - partial void SetId(int oldValue, ref int newValue); - /// - /// When provided in a partial class, allows value of Id to be changed before returning. - /// - partial void GetId(ref int result); - - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public int Id - { - get - { - int value = _Id; - GetId(ref value); - return (_Id = value); - } - protected set - { - int oldValue = _Id; - SetId(oldValue, ref value); - if (oldValue != value) + [Table("Company")] + public partial class Company + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Company() + { + CompanyMetadata = new HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Company CreateCompanyUnsafe() + { + return new Company(); + } + + /// + /// Public constructor with required data + /// + /// + /// + /// + /// + /// + public Company(MovieMetadata _moviemetadata0, SeriesMetadata _seriesmetadata1, MusicAlbumMetadata _musicalbummetadata2, BookMetadata _bookmetadata3, Company _company4) + { + if (_moviemetadata0 == null) throw new ArgumentNullException(nameof(_moviemetadata0)); + _moviemetadata0.Studios.Add(this); + + if (_seriesmetadata1 == null) throw new ArgumentNullException(nameof(_seriesmetadata1)); + _seriesmetadata1.Networks.Add(this); + + if (_musicalbummetadata2 == null) throw new ArgumentNullException(nameof(_musicalbummetadata2)); + _musicalbummetadata2.Labels.Add(this); + + if (_bookmetadata3 == null) throw new ArgumentNullException(nameof(_bookmetadata3)); + _bookmetadata3.Publishers.Add(this); + + if (_company4 == null) throw new ArgumentNullException(nameof(_company4)); + _company4.Parent = this; + + this.CompanyMetadata = new HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + /// + /// + /// + public static Company Create(MovieMetadata _moviemetadata0, SeriesMetadata _seriesmetadata1, MusicAlbumMetadata _musicalbummetadata2, BookMetadata _bookmetadata3, Company _company4) + { + return new Company(_moviemetadata0, _seriesmetadata1, _musicalbummetadata2, _bookmetadata3, _company4); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id + { + get { - _Id = value; + int value = _Id; + GetId(ref value); + return (_Id = value); } - } - } - - /// - /// Required - /// - [ConcurrencyCheck] - [Required] - public byte[] Timestamp { get; set; } - - /************************************************************************* - * Navigation properties - *************************************************************************/ - - public virtual ICollection CompanyMetadata { get; protected set; } - - public virtual global::Jellyfin.Data.Entities.Company Parent { get; set; } - - } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } + + public void OnSavingChanges() + { + RowVersion++; + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + [ForeignKey("CompanyMetadata_CompanyMetadata_Id")] + public virtual ICollection CompanyMetadata { get; protected set; } + [ForeignKey("Company_Parent_Id")] + public virtual Company Parent { get; set; } + + } } diff --git a/Jellyfin.Data/Entities/CompanyMetadata.cs b/Jellyfin.Data/Entities/CompanyMetadata.cs index 6d636e884..18357b326 100644 --- a/Jellyfin.Data/Entities/CompanyMetadata.cs +++ b/Jellyfin.Data/Entities/CompanyMetadata.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,214 +9,215 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class CompanyMetadata: global::Jellyfin.Data.Entities.Metadata - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected CompanyMetadata(): base() - { - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static CompanyMetadata CreateCompanyMetadataUnsafe() - { - return new CompanyMetadata(); - } - - /// - /// Public constructor with required data - /// - /// The title or name of the object - /// ISO-639-3 3-character language codes - /// - public CompanyMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Company _company0) - { - if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); - this.Title = title; - - if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); - this.Language = language; - - if (_company0 == null) throw new ArgumentNullException(nameof(_company0)); - _company0.CompanyMetadata.Add(this); - - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// The title or name of the object - /// ISO-639-3 3-character language codes - /// - public static CompanyMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Company _company0) - { - return new CompanyMetadata(title, language, dateadded, datemodified, _company0); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for Description - /// - protected string _Description; - /// - /// When provided in a partial class, allows value of Description to be changed before setting. - /// - partial void SetDescription(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Description to be changed before returning. - /// - partial void GetDescription(ref string result); - - /// - /// Max length = 65535 - /// - [MaxLength(65535)] - [StringLength(65535)] - public string Description - { - get - { - string value = _Description; - GetDescription(ref value); - return (_Description = value); - } - set - { - string oldValue = _Description; - SetDescription(oldValue, ref value); - if (oldValue != value) + [Table("CompanyMetadata")] + public partial class CompanyMetadata : Metadata + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected CompanyMetadata() : base() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static CompanyMetadata CreateCompanyMetadataUnsafe() + { + return new CompanyMetadata(); + } + + /// + /// Public constructor with required data + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public CompanyMetadata(string title, string language, DateTime dateadded, DateTime datemodified, Company _company0) + { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + this.Title = title; + + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; + + if (_company0 == null) throw new ArgumentNullException(nameof(_company0)); + _company0.CompanyMetadata.Add(this); + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public static CompanyMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, Company _company0) + { + return new CompanyMetadata(title, language, dateadded, datemodified, _company0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Description + /// + protected string _Description; + /// + /// When provided in a partial class, allows value of Description to be changed before setting. + /// + partial void SetDescription(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Description to be changed before returning. + /// + partial void GetDescription(ref string result); + + /// + /// Max length = 65535 + /// + [MaxLength(65535)] + [StringLength(65535)] + public string Description + { + get + { + string value = _Description; + GetDescription(ref value); + return (_Description = value); + } + set + { + string oldValue = _Description; + SetDescription(oldValue, ref value); + if (oldValue != value) + { + _Description = value; + } + } + } + + /// + /// Backing field for Headquarters + /// + protected string _Headquarters; + /// + /// When provided in a partial class, allows value of Headquarters to be changed before setting. + /// + partial void SetHeadquarters(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Headquarters to be changed before returning. + /// + partial void GetHeadquarters(ref string result); + + /// + /// Max length = 255 + /// + [MaxLength(255)] + [StringLength(255)] + public string Headquarters + { + get + { + string value = _Headquarters; + GetHeadquarters(ref value); + return (_Headquarters = value); + } + set + { + string oldValue = _Headquarters; + SetHeadquarters(oldValue, ref value); + if (oldValue != value) + { + _Headquarters = value; + } + } + } + + /// + /// Backing field for Country + /// + protected string _Country; + /// + /// When provided in a partial class, allows value of Country to be changed before setting. + /// + partial void SetCountry(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Country to be changed before returning. + /// + partial void GetCountry(ref string result); + + /// + /// Max length = 2 + /// + [MaxLength(2)] + [StringLength(2)] + public string Country + { + get { - _Description = value; + string value = _Country; + GetCountry(ref value); + return (_Country = value); } - } - } - - /// - /// Backing field for Headquarters - /// - protected string _Headquarters; - /// - /// When provided in a partial class, allows value of Headquarters to be changed before setting. - /// - partial void SetHeadquarters(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Headquarters to be changed before returning. - /// - partial void GetHeadquarters(ref string result); - - /// - /// Max length = 255 - /// - [MaxLength(255)] - [StringLength(255)] - public string Headquarters - { - get - { - string value = _Headquarters; - GetHeadquarters(ref value); - return (_Headquarters = value); - } - set - { - string oldValue = _Headquarters; - SetHeadquarters(oldValue, ref value); - if (oldValue != value) + set { - _Headquarters = value; + string oldValue = _Country; + SetCountry(oldValue, ref value); + if (oldValue != value) + { + _Country = value; + } } - } - } - - /// - /// Backing field for Country - /// - protected string _Country; - /// - /// When provided in a partial class, allows value of Country to be changed before setting. - /// - partial void SetCountry(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Country to be changed before returning. - /// - partial void GetCountry(ref string result); - - /// - /// Max length = 2 - /// - [MaxLength(2)] - [StringLength(2)] - public string Country - { - get - { - string value = _Country; - GetCountry(ref value); - return (_Country = value); - } - set - { - string oldValue = _Country; - SetCountry(oldValue, ref value); - if (oldValue != value) + } + + /// + /// Backing field for Homepage + /// + protected string _Homepage; + /// + /// When provided in a partial class, allows value of Homepage to be changed before setting. + /// + partial void SetHomepage(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Homepage to be changed before returning. + /// + partial void GetHomepage(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Homepage + { + get { - _Country = value; + string value = _Homepage; + GetHomepage(ref value); + return (_Homepage = value); } - } - } - - /// - /// Backing field for Homepage - /// - protected string _Homepage; - /// - /// When provided in a partial class, allows value of Homepage to be changed before setting. - /// - partial void SetHomepage(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Homepage to be changed before returning. - /// - partial void GetHomepage(ref string result); - - /// - /// Max length = 1024 - /// - [MaxLength(1024)] - [StringLength(1024)] - public string Homepage - { - get - { - string value = _Homepage; - GetHomepage(ref value); - return (_Homepage = value); - } - set - { - string oldValue = _Homepage; - SetHomepage(oldValue, ref value); - if (oldValue != value) + set { - _Homepage = value; + string oldValue = _Homepage; + SetHomepage(oldValue, ref value); + if (oldValue != value) + { + _Homepage = value; + } } - } - } + } - /************************************************************************* - * Navigation properties - *************************************************************************/ + /************************************************************************* + * Navigation properties + *************************************************************************/ - } + } } diff --git a/Jellyfin.Data/Entities/CustomItem.cs b/Jellyfin.Data/Entities/CustomItem.cs index eb6d2752d..29f4b62a6 100644 --- a/Jellyfin.Data/Entities/CustomItem.cs +++ b/Jellyfin.Data/Entities/CustomItem.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,64 +9,65 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class CustomItem: global::Jellyfin.Data.Entities.LibraryItem - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected CustomItem(): base() - { - CustomItemMetadata = new System.Collections.Generic.HashSet(); - Releases = new System.Collections.Generic.HashSet(); + public partial class CustomItem : LibraryItem + { + partial void Init(); - Init(); - } + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected CustomItem() : base() + { + CustomItemMetadata = new HashSet(); + Releases = new HashSet(); - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static CustomItem CreateCustomItemUnsafe() - { - return new CustomItem(); - } + Init(); + } - /// - /// 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) - { - this.UrlId = urlid; + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static CustomItem CreateCustomItemUnsafe() + { + return new CustomItem(); + } - this.CustomItemMetadata = new System.Collections.Generic.HashSet(); - this.Releases = new System.Collections.Generic.HashSet(); + /// + /// 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) + { + this.UrlId = urlid; - Init(); - } + this.CustomItemMetadata = new HashSet(); + this.Releases = new HashSet(); - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// This is whats gets displayed in the Urls and API requests. This could also be a string. - public static CustomItem Create(Guid urlid, DateTime dateadded) - { - return new CustomItem(urlid, dateadded); - } + Init(); + } - /************************************************************************* - * Properties - *************************************************************************/ + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + public static CustomItem Create(Guid urlid, DateTime dateadded) + { + return new CustomItem(urlid, dateadded); + } - /************************************************************************* - * Navigation properties - *************************************************************************/ + /************************************************************************* + * Properties + *************************************************************************/ - public virtual ICollection CustomItemMetadata { get; protected set; } + /************************************************************************* + * Navigation properties + *************************************************************************/ + [ForeignKey("CustomItemMetadata_CustomItemMetadata_Id")] + public virtual ICollection CustomItemMetadata { get; protected set; } - public virtual ICollection Releases { get; protected set; } + [ForeignKey("Release_Releases_Id")] + public virtual ICollection Releases { get; protected set; } - } + } } diff --git a/Jellyfin.Data/Entities/CustomItemMetadata.cs b/Jellyfin.Data/Entities/CustomItemMetadata.cs index f2c15d3fe..8fbccfdd8 100644 --- a/Jellyfin.Data/Entities/CustomItemMetadata.cs +++ b/Jellyfin.Data/Entities/CustomItemMetadata.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,66 +9,67 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class CustomItemMetadata: global::Jellyfin.Data.Entities.Metadata - { - partial void Init(); + [Table("CustomItemMetadata")] + public partial class CustomItemMetadata : Metadata + { + partial void Init(); - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected CustomItemMetadata(): base() - { - Init(); - } + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected CustomItemMetadata() : base() + { + Init(); + } - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static CustomItemMetadata CreateCustomItemMetadataUnsafe() - { - return new CustomItemMetadata(); - } + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static CustomItemMetadata CreateCustomItemMetadataUnsafe() + { + return new CustomItemMetadata(); + } - /// - /// Public constructor with required data - /// - /// The title or name of the object - /// ISO-639-3 3-character language codes - /// - public CustomItemMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.CustomItem _customitem0) - { - if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); - this.Title = title; + /// + /// Public constructor with required data + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public CustomItemMetadata(string title, string language, DateTime dateadded, DateTime datemodified, CustomItem _customitem0) + { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + this.Title = title; - if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); - this.Language = language; + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; - if (_customitem0 == null) throw new ArgumentNullException(nameof(_customitem0)); - _customitem0.CustomItemMetadata.Add(this); + if (_customitem0 == null) throw new ArgumentNullException(nameof(_customitem0)); + _customitem0.CustomItemMetadata.Add(this); - Init(); - } + Init(); + } - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// The title or name of the object - /// ISO-639-3 3-character language codes - /// - public static CustomItemMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.CustomItem _customitem0) - { - return new CustomItemMetadata(title, language, dateadded, datemodified, _customitem0); - } + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public static CustomItemMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, CustomItem _customitem0) + { + return new CustomItemMetadata(title, language, dateadded, datemodified, _customitem0); + } - /************************************************************************* - * Properties - *************************************************************************/ + /************************************************************************* + * Properties + *************************************************************************/ - /************************************************************************* - * Navigation properties - *************************************************************************/ + /************************************************************************* + * Navigation properties + *************************************************************************/ - } + } } diff --git a/Jellyfin.Data/Entities/Episode.cs b/Jellyfin.Data/Entities/Episode.cs index 3a23f0976..be358a0fd 100644 --- a/Jellyfin.Data/Entities/Episode.cs +++ b/Jellyfin.Data/Entities/Episode.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,107 +9,108 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class Episode: global::Jellyfin.Data.Entities.LibraryItem - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Episode(): base() - { - // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem. - // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. - - Releases = new System.Collections.Generic.HashSet(); - EpisodeMetadata = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static Episode CreateEpisodeUnsafe() - { - return new Episode(); - } - - /// - /// Public constructor with required data - /// - /// This is whats gets displayed in the Urls and API requests. This could also be a string. - /// - public Episode(Guid urlid, DateTime dateadded, global::Jellyfin.Data.Entities.Season _season0) - { - // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem. - // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. - - this.UrlId = urlid; - - if (_season0 == null) throw new ArgumentNullException(nameof(_season0)); - _season0.Episodes.Add(this); - - this.Releases = new System.Collections.Generic.HashSet(); - this.EpisodeMetadata = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// This is whats gets displayed in the Urls and API requests. This could also be a string. - /// - public static Episode Create(Guid urlid, DateTime dateadded, global::Jellyfin.Data.Entities.Season _season0) - { - return new Episode(urlid, dateadded, _season0); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for EpisodeNumber - /// - protected int? _EpisodeNumber; - /// - /// When provided in a partial class, allows value of EpisodeNumber to be changed before setting. - /// - partial void SetEpisodeNumber(int? oldValue, ref int? newValue); - /// - /// When provided in a partial class, allows value of EpisodeNumber to be changed before returning. - /// - partial void GetEpisodeNumber(ref int? result); - - public int? EpisodeNumber - { - get - { - int? value = _EpisodeNumber; - GetEpisodeNumber(ref value); - return (_EpisodeNumber = value); - } - set - { - int? oldValue = _EpisodeNumber; - SetEpisodeNumber(oldValue, ref value); - if (oldValue != value) + [Table("Episode")] + public partial class Episode : LibraryItem + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Episode() : base() + { + // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + Releases = new HashSet(); + EpisodeMetadata = new HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Episode CreateEpisodeUnsafe() + { + return new Episode(); + } + + /// + /// Public constructor with required data + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + /// + public Episode(Guid urlid, DateTime dateadded, Season _season0) + { + // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + this.UrlId = urlid; + + if (_season0 == null) throw new ArgumentNullException(nameof(_season0)); + _season0.Episodes.Add(this); + + this.Releases = new HashSet(); + this.EpisodeMetadata = new HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + /// + public static Episode Create(Guid urlid, DateTime dateadded, Season _season0) + { + return new Episode(urlid, dateadded, _season0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for EpisodeNumber + /// + protected int? _EpisodeNumber; + /// + /// When provided in a partial class, allows value of EpisodeNumber to be changed before setting. + /// + partial void SetEpisodeNumber(int? oldValue, ref int? newValue); + /// + /// When provided in a partial class, allows value of EpisodeNumber to be changed before returning. + /// + partial void GetEpisodeNumber(ref int? result); + + public int? EpisodeNumber + { + get { - _EpisodeNumber = value; + int? value = _EpisodeNumber; + GetEpisodeNumber(ref value); + return (_EpisodeNumber = value); } - } - } - - /************************************************************************* - * Navigation properties - *************************************************************************/ - - public virtual ICollection Releases { get; protected set; } + set + { + int? oldValue = _EpisodeNumber; + SetEpisodeNumber(oldValue, ref value); + if (oldValue != value) + { + _EpisodeNumber = value; + } + } + } - public virtual ICollection EpisodeMetadata { get; protected set; } + /************************************************************************* + * Navigation properties + *************************************************************************/ + [ForeignKey("Release_Releases_Id")] + public virtual ICollection Releases { get; protected set; } + [ForeignKey("EpisodeMetadata_EpisodeMetadata_Id")] + public virtual ICollection EpisodeMetadata { get; protected set; } - } + } } diff --git a/Jellyfin.Data/Entities/EpisodeMetadata.cs b/Jellyfin.Data/Entities/EpisodeMetadata.cs index 963219140..a1f4adf7b 100644 --- a/Jellyfin.Data/Entities/EpisodeMetadata.cs +++ b/Jellyfin.Data/Entities/EpisodeMetadata.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,177 +9,178 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class EpisodeMetadata: global::Jellyfin.Data.Entities.Metadata - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected EpisodeMetadata(): base() - { - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static EpisodeMetadata CreateEpisodeMetadataUnsafe() - { - return new EpisodeMetadata(); - } - - /// - /// Public constructor with required data - /// - /// The title or name of the object - /// ISO-639-3 3-character language codes - /// - public EpisodeMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Episode _episode0) - { - if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); - this.Title = title; - - if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); - this.Language = language; - - if (_episode0 == null) throw new ArgumentNullException(nameof(_episode0)); - _episode0.EpisodeMetadata.Add(this); - - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// The title or name of the object - /// ISO-639-3 3-character language codes - /// - public static EpisodeMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Episode _episode0) - { - return new EpisodeMetadata(title, language, dateadded, datemodified, _episode0); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for Outline - /// - protected string _Outline; - /// - /// When provided in a partial class, allows value of Outline to be changed before setting. - /// - partial void SetOutline(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Outline to be changed before returning. - /// - partial void GetOutline(ref string result); - - /// - /// Max length = 1024 - /// - [MaxLength(1024)] - [StringLength(1024)] - public string Outline - { - get - { - string value = _Outline; - GetOutline(ref value); - return (_Outline = value); - } - set - { - string oldValue = _Outline; - SetOutline(oldValue, ref value); - if (oldValue != value) + [Table("EpisodeMetadata")] + public partial class EpisodeMetadata : Metadata + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected EpisodeMetadata() : base() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static EpisodeMetadata CreateEpisodeMetadataUnsafe() + { + return new EpisodeMetadata(); + } + + /// + /// Public constructor with required data + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public EpisodeMetadata(string title, string language, DateTime dateadded, DateTime datemodified, Episode _episode0) + { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + this.Title = title; + + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; + + if (_episode0 == null) throw new ArgumentNullException(nameof(_episode0)); + _episode0.EpisodeMetadata.Add(this); + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public static EpisodeMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, Episode _episode0) + { + return new EpisodeMetadata(title, language, dateadded, datemodified, _episode0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Outline + /// + protected string _Outline; + /// + /// When provided in a partial class, allows value of Outline to be changed before setting. + /// + partial void SetOutline(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Outline to be changed before returning. + /// + partial void GetOutline(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Outline + { + get + { + string value = _Outline; + GetOutline(ref value); + return (_Outline = value); + } + set + { + string oldValue = _Outline; + SetOutline(oldValue, ref value); + if (oldValue != value) + { + _Outline = value; + } + } + } + + /// + /// Backing field for Plot + /// + protected string _Plot; + /// + /// When provided in a partial class, allows value of Plot to be changed before setting. + /// + partial void SetPlot(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Plot to be changed before returning. + /// + partial void GetPlot(ref string result); + + /// + /// Max length = 65535 + /// + [MaxLength(65535)] + [StringLength(65535)] + public string Plot + { + get + { + string value = _Plot; + GetPlot(ref value); + return (_Plot = value); + } + set { - _Outline = value; + string oldValue = _Plot; + SetPlot(oldValue, ref value); + if (oldValue != value) + { + _Plot = value; + } } - } - } - - /// - /// Backing field for Plot - /// - protected string _Plot; - /// - /// When provided in a partial class, allows value of Plot to be changed before setting. - /// - partial void SetPlot(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Plot to be changed before returning. - /// - partial void GetPlot(ref string result); - - /// - /// Max length = 65535 - /// - [MaxLength(65535)] - [StringLength(65535)] - public string Plot - { - get - { - string value = _Plot; - GetPlot(ref value); - return (_Plot = value); - } - set - { - string oldValue = _Plot; - SetPlot(oldValue, ref value); - if (oldValue != value) + } + + /// + /// Backing field for Tagline + /// + protected string _Tagline; + /// + /// When provided in a partial class, allows value of Tagline to be changed before setting. + /// + partial void SetTagline(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Tagline to be changed before returning. + /// + partial void GetTagline(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Tagline + { + get { - _Plot = value; + string value = _Tagline; + GetTagline(ref value); + return (_Tagline = value); } - } - } - - /// - /// Backing field for Tagline - /// - protected string _Tagline; - /// - /// When provided in a partial class, allows value of Tagline to be changed before setting. - /// - partial void SetTagline(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Tagline to be changed before returning. - /// - partial void GetTagline(ref string result); - - /// - /// Max length = 1024 - /// - [MaxLength(1024)] - [StringLength(1024)] - public string Tagline - { - get - { - string value = _Tagline; - GetTagline(ref value); - return (_Tagline = value); - } - set - { - string oldValue = _Tagline; - SetTagline(oldValue, ref value); - if (oldValue != value) + set { - _Tagline = value; + string oldValue = _Tagline; + SetTagline(oldValue, ref value); + if (oldValue != value) + { + _Tagline = value; + } } - } - } + } - /************************************************************************* - * Navigation properties - *************************************************************************/ + /************************************************************************* + * Navigation properties + *************************************************************************/ - } + } } diff --git a/Jellyfin.Data/Entities/Genre.cs b/Jellyfin.Data/Entities/Genre.cs index 982600553..d38265d80 100644 --- a/Jellyfin.Data/Entities/Genre.cs +++ b/Jellyfin.Data/Entities/Genre.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,143 +9,150 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class Genre - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Genre() - { - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static Genre CreateGenreUnsafe() - { - return new Genre(); - } - - /// - /// Public constructor with required data - /// - /// - /// - public Genre(string name, global::Jellyfin.Data.Entities.Metadata _metadata0) - { - if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); - this.Name = name; - - if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0)); - _metadata0.Genres.Add(this); - - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// - /// - public static Genre Create(string name, global::Jellyfin.Data.Entities.Metadata _metadata0) - { - return new Genre(name, _metadata0); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for Id - /// - internal int _Id; - /// - /// When provided in a partial class, allows value of Id to be changed before setting. - /// - partial void SetId(int oldValue, ref int newValue); - /// - /// When provided in a partial class, allows value of Id to be changed before returning. - /// - partial void GetId(ref int result); - - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public int Id - { - get - { - int value = _Id; - GetId(ref value); - return (_Id = value); - } - protected set - { - int oldValue = _Id; - SetId(oldValue, ref value); - if (oldValue != value) + [Table("Genre")] + public partial class Genre + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Genre() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Genre CreateGenreUnsafe() + { + return new Genre(); + } + + /// + /// Public constructor with required data + /// + /// + /// + public Genre(string name, Metadata _metadata0) + { + if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); + this.Name = name; + + if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0)); + _metadata0.Genres.Add(this); + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + public static Genre Create(string name, Metadata _metadata0) + { + return new Genre(name, _metadata0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set { - _Id = value; + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } } - } - } - - /// - /// Backing field for Name - /// - internal string _Name; - /// - /// When provided in a partial class, allows value of Name to be changed before setting. - /// - partial void SetName(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Name to be changed before returning. - /// - partial void GetName(ref string result); - - /// - /// Indexed, Required, Max length = 255 - /// - [Required] - [MaxLength(255)] - [StringLength(255)] - public string Name - { - get - { - string value = _Name; - GetName(ref value); - return (_Name = value); - } - set - { - string oldValue = _Name; - SetName(oldValue, ref value); - if (oldValue != value) + } + + /// + /// Backing field for Name + /// + internal string _Name; + /// + /// When provided in a partial class, allows value of Name to be changed before setting. + /// + partial void SetName(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Name to be changed before returning. + /// + partial void GetName(ref string result); + + /// + /// Indexed, Required, Max length = 255 + /// + [Required] + [MaxLength(255)] + [StringLength(255)] + public string Name + { + get { - _Name = value; + string value = _Name; + GetName(ref value); + return (_Name = value); } - } - } + set + { + string oldValue = _Name; + SetName(oldValue, ref value); + if (oldValue != value) + { + _Name = value; + } + } + } + + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } - /// - /// Required - /// - [ConcurrencyCheck] - [Required] - public byte[] Timestamp { get; set; } + public void OnSavingChanges() + { + RowVersion++; + } - /************************************************************************* - * Navigation properties - *************************************************************************/ + /************************************************************************* + * Navigation properties + *************************************************************************/ - } + } } diff --git a/Jellyfin.Data/Entities/Group.cs b/Jellyfin.Data/Entities/Group.cs index ff19e9b01..4b58120fc 100644 --- a/Jellyfin.Data/Entities/Group.cs +++ b/Jellyfin.Data/Entities/Group.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,95 +9,106 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class Group - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Group() - { - GroupPermissions = new System.Collections.Generic.HashSet(); - ProviderMappings = new System.Collections.Generic.HashSet(); - Preferences = new System.Collections.Generic.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, global::Jellyfin.Data.Entities.User _user0) - { - if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); - this.Name = name; - - if (_user0 == null) throw new ArgumentNullException(nameof(_user0)); - _user0.Groups.Add(this); - - this.GroupPermissions = new System.Collections.Generic.HashSet(); - this.ProviderMappings = new System.Collections.Generic.HashSet(); - this.Preferences = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// - /// - public static Group Create(string name, global::Jellyfin.Data.Entities.User _user0) - { - return new Group(name, _user0); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public int Id { get; protected set; } - - /// - /// Required, Max length = 255 - /// - [Required] - [MaxLength(255)] - [StringLength(255)] - public string Name { get; set; } - - /// - /// Concurrency token - /// - [Timestamp] - public Byte[] Timestamp { get; set; } - - /************************************************************************* - * Navigation properties - *************************************************************************/ - - public virtual ICollection GroupPermissions { get; protected set; } - - public virtual ICollection ProviderMappings { get; protected set; } - - public virtual ICollection Preferences { get; protected set; } - - } + [Table("Group")] + public partial class Group + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Group() + { + GroupPermissions = 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) + { + if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); + this.Name = name; + + if (_user0 == null) throw new ArgumentNullException(nameof(_user0)); + _user0.Groups.Add(this); + + this.GroupPermissions = new HashSet(); + this.ProviderMappings = new HashSet(); + this.Preferences = new HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + public static Group Create(string name, User _user0) + { + return new Group(name, _user0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; protected set; } + + /// + /// Required, Max length = 255 + /// + [Required] + [MaxLength(255)] + [StringLength(255)] + public string Name { get; set; } + + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } + + public void OnSavingChanges() + { + RowVersion++; + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + [ForeignKey("Permission_GroupPermissions_Id")] + public virtual ICollection GroupPermissions { get; protected set; } + + [ForeignKey("ProviderMapping_ProviderMappings_Id")] + public virtual ICollection ProviderMappings { get; protected set; } + + [ForeignKey("Preference_Preferences_Id")] + public virtual ICollection Preferences { get; protected set; } + + } } diff --git a/Jellyfin.Data/Entities/Library.cs b/Jellyfin.Data/Entities/Library.cs index 19ca14294..f3faa8699 100644 --- a/Jellyfin.Data/Entities/Library.cs +++ b/Jellyfin.Data/Entities/Library.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,138 +9,145 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class Library - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Library() - { - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static Library CreateLibraryUnsafe() - { - return new Library(); - } - - /// - /// Public constructor with required data - /// - /// - public Library(string name) - { - if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); - this.Name = name; - - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// - public static Library Create(string name) - { - return new Library(name); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for Id - /// - internal int _Id; - /// - /// When provided in a partial class, allows value of Id to be changed before setting. - /// - partial void SetId(int oldValue, ref int newValue); - /// - /// When provided in a partial class, allows value of Id to be changed before returning. - /// - partial void GetId(ref int result); - - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public int Id - { - get - { - int value = _Id; - GetId(ref value); - return (_Id = value); - } - protected set - { - int oldValue = _Id; - SetId(oldValue, ref value); - if (oldValue != value) + [Table("Library")] + public partial class Library + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Library() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Library CreateLibraryUnsafe() + { + return new Library(); + } + + /// + /// Public constructor with required data + /// + /// + public Library(string name) + { + if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); + this.Name = name; + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + public static Library Create(string name) + { + return new Library(name); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set { - _Id = value; + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } } - } - } - - /// - /// Backing field for Name - /// - protected string _Name; - /// - /// When provided in a partial class, allows value of Name to be changed before setting. - /// - partial void SetName(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Name to be changed before returning. - /// - partial void GetName(ref string result); - - /// - /// Required, Max length = 1024 - /// - [Required] - [MaxLength(1024)] - [StringLength(1024)] - public string Name - { - get - { - string value = _Name; - GetName(ref value); - return (_Name = value); - } - set - { - string oldValue = _Name; - SetName(oldValue, ref value); - if (oldValue != value) + } + + /// + /// Backing field for Name + /// + protected string _Name; + /// + /// When provided in a partial class, allows value of Name to be changed before setting. + /// + partial void SetName(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Name to be changed before returning. + /// + partial void GetName(ref string result); + + /// + /// Required, Max length = 1024 + /// + [Required] + [MaxLength(1024)] + [StringLength(1024)] + public string Name + { + get { - _Name = value; + string value = _Name; + GetName(ref value); + return (_Name = value); } - } - } + set + { + string oldValue = _Name; + SetName(oldValue, ref value); + if (oldValue != value) + { + _Name = value; + } + } + } + + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } - /// - /// Required - /// - [ConcurrencyCheck] - [Required] - public byte[] Timestamp { get; set; } + public void OnSavingChanges() + { + RowVersion++; + } - /************************************************************************* - * Navigation properties - *************************************************************************/ + /************************************************************************* + * Navigation properties + *************************************************************************/ - } + } } diff --git a/Jellyfin.Data/Entities/LibraryItem.cs b/Jellyfin.Data/Entities/LibraryItem.cs index 1987196d6..29547562b 100644 --- a/Jellyfin.Data/Entities/LibraryItem.cs +++ b/Jellyfin.Data/Entities/LibraryItem.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,160 +9,168 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public abstract partial class LibraryItem - { - partial void Init(); - - /// - /// Default constructor. Protected due to being abstract. - /// - protected LibraryItem() - { - Init(); - } - - /// - /// 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) - { - this.UrlId = urlid; - - - Init(); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for Id - /// - internal int _Id; - /// - /// When provided in a partial class, allows value of Id to be changed before setting. - /// - partial void SetId(int oldValue, ref int newValue); - /// - /// When provided in a partial class, allows value of Id to be changed before returning. - /// - partial void GetId(ref int result); - - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public int Id - { - get - { - int value = _Id; - GetId(ref value); - return (_Id = value); - } - protected set - { - int oldValue = _Id; - SetId(oldValue, ref value); - if (oldValue != value) + [Table("LibraryItem")] + public abstract partial class LibraryItem + { + partial void Init(); + + /// + /// Default constructor. Protected due to being abstract. + /// + protected LibraryItem() + { + Init(); + } + + /// + /// 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) + { + this.UrlId = urlid; + + + Init(); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for UrlId + /// + internal Guid _UrlId; + /// + /// When provided in a partial class, allows value of UrlId to be changed before setting. + /// + partial void SetUrlId(Guid oldValue, ref Guid newValue); + /// + /// When provided in a partial class, allows value of UrlId to be changed before returning. + /// + partial void GetUrlId(ref Guid result); + + /// + /// Indexed, Required + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + /// + [Required] + public Guid UrlId + { + get + { + Guid value = _UrlId; + GetUrlId(ref value); + return (_UrlId = value); + } + set { - _Id = value; + Guid oldValue = _UrlId; + SetUrlId(oldValue, ref value); + if (oldValue != value) + { + _UrlId = value; + } } - } - } - - /// - /// Backing field for UrlId - /// - internal Guid _UrlId; - /// - /// When provided in a partial class, allows value of UrlId to be changed before setting. - /// - partial void SetUrlId(Guid oldValue, ref Guid newValue); - /// - /// When provided in a partial class, allows value of UrlId to be changed before returning. - /// - partial void GetUrlId(ref Guid result); - - /// - /// Indexed, Required - /// This is whats gets displayed in the Urls and API requests. This could also be a string. - /// - [Required] - public Guid UrlId - { - get - { - Guid value = _UrlId; - GetUrlId(ref value); - return (_UrlId = value); - } - set - { - Guid oldValue = _UrlId; - SetUrlId(oldValue, ref value); - if (oldValue != value) + } + + /// + /// Backing field for DateAdded + /// + protected DateTime _DateAdded; + /// + /// When provided in a partial class, allows value of DateAdded to be changed before setting. + /// + partial void SetDateAdded(DateTime oldValue, ref DateTime newValue); + /// + /// When provided in a partial class, allows value of DateAdded to be changed before returning. + /// + partial void GetDateAdded(ref DateTime result); + + /// + /// Required + /// + [Required] + public DateTime DateAdded + { + get { - _UrlId = value; + DateTime value = _DateAdded; + GetDateAdded(ref value); + return (_DateAdded = value); } - } - } - - /// - /// Backing field for DateAdded - /// - protected DateTime _DateAdded; - /// - /// When provided in a partial class, allows value of DateAdded to be changed before setting. - /// - partial void SetDateAdded(DateTime oldValue, ref DateTime newValue); - /// - /// When provided in a partial class, allows value of DateAdded to be changed before returning. - /// - partial void GetDateAdded(ref DateTime result); - - /// - /// Required - /// - [Required] - public DateTime DateAdded - { - get - { - DateTime value = _DateAdded; - GetDateAdded(ref value); - return (_DateAdded = value); - } - internal set - { - DateTime oldValue = _DateAdded; - SetDateAdded(oldValue, ref value); - if (oldValue != value) + internal set { - _DateAdded = value; + DateTime oldValue = _DateAdded; + SetDateAdded(oldValue, ref value); + if (oldValue != value) + { + _DateAdded = value; + } } - } - } - - /// - /// Required - /// - [ConcurrencyCheck] - [Required] - public byte[] Timestamp { get; set; } - - /************************************************************************* - * Navigation properties - *************************************************************************/ - - /// - /// Required - /// - public virtual global::Jellyfin.Data.Entities.LibraryRoot LibraryRoot { get; set; } - - } + } + + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } + + public void OnSavingChanges() + { + RowVersion++; + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + /// + /// 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 015fc4ea9..932e3edb8 100644 --- a/Jellyfin.Data/Entities/LibraryRoot.cs +++ b/Jellyfin.Data/Entities/LibraryRoot.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,182 +9,190 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class LibraryRoot - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected LibraryRoot() - { - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static LibraryRoot CreateLibraryRootUnsafe() - { - return new LibraryRoot(); - } - - /// - /// Public constructor with required data - /// - /// Absolute Path - public LibraryRoot(string path) - { - if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); - this.Path = path; - - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// Absolute Path - public static LibraryRoot Create(string path) - { - return new LibraryRoot(path); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for Id - /// - internal int _Id; - /// - /// When provided in a partial class, allows value of Id to be changed before setting. - /// - partial void SetId(int oldValue, ref int newValue); - /// - /// When provided in a partial class, allows value of Id to be changed before returning. - /// - partial void GetId(ref int result); - - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public int Id - { - get - { - int value = _Id; - GetId(ref value); - return (_Id = value); - } - protected set - { - int oldValue = _Id; - SetId(oldValue, ref value); - if (oldValue != value) + [Table("LibraryRoot")] + public partial class LibraryRoot + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected LibraryRoot() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static LibraryRoot CreateLibraryRootUnsafe() + { + return new LibraryRoot(); + } + + /// + /// Public constructor with required data + /// + /// Absolute Path + public LibraryRoot(string path) + { + if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); + this.Path = path; + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// Absolute Path + public static LibraryRoot Create(string path) + { + return new LibraryRoot(path); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for Path + /// + protected string _Path; + /// + /// When provided in a partial class, allows value of Path to be changed before setting. + /// + partial void SetPath(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Path to be changed before returning. + /// + partial void GetPath(ref string result); + + /// + /// Required, Max length = 65535 + /// Absolute Path + /// + [Required] + [MaxLength(65535)] + [StringLength(65535)] + public string Path + { + get + { + string value = _Path; + GetPath(ref value); + return (_Path = value); + } + set { - _Id = value; + string oldValue = _Path; + SetPath(oldValue, ref value); + if (oldValue != value) + { + _Path = value; + } } - } - } - - /// - /// Backing field for Path - /// - protected string _Path; - /// - /// When provided in a partial class, allows value of Path to be changed before setting. - /// - partial void SetPath(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Path to be changed before returning. - /// - partial void GetPath(ref string result); - - /// - /// Required, Max length = 65535 - /// Absolute Path - /// - [Required] - [MaxLength(65535)] - [StringLength(65535)] - public string Path - { - get - { - string value = _Path; - GetPath(ref value); - return (_Path = value); - } - set - { - string oldValue = _Path; - SetPath(oldValue, ref value); - if (oldValue != value) + } + + /// + /// Backing field for NetworkPath + /// + protected string _NetworkPath; + /// + /// When provided in a partial class, allows value of NetworkPath to be changed before setting. + /// + partial void SetNetworkPath(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of NetworkPath to be changed before returning. + /// + partial void GetNetworkPath(ref string result); + + /// + /// Max length = 65535 + /// Absolute network path, for example for transcoding sattelites. + /// + [MaxLength(65535)] + [StringLength(65535)] + public string NetworkPath + { + get { - _Path = value; + string value = _NetworkPath; + GetNetworkPath(ref value); + return (_NetworkPath = value); } - } - } - - /// - /// Backing field for NetworkPath - /// - protected string _NetworkPath; - /// - /// When provided in a partial class, allows value of NetworkPath to be changed before setting. - /// - partial void SetNetworkPath(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of NetworkPath to be changed before returning. - /// - partial void GetNetworkPath(ref string result); - - /// - /// Max length = 65535 - /// Absolute network path, for example for transcoding sattelites. - /// - [MaxLength(65535)] - [StringLength(65535)] - public string NetworkPath - { - get - { - string value = _NetworkPath; - GetNetworkPath(ref value); - return (_NetworkPath = value); - } - set - { - string oldValue = _NetworkPath; - SetNetworkPath(oldValue, ref value); - if (oldValue != value) + set { - _NetworkPath = value; + string oldValue = _NetworkPath; + SetNetworkPath(oldValue, ref value); + if (oldValue != value) + { + _NetworkPath = value; + } } - } - } - - /// - /// Required - /// - [ConcurrencyCheck] - [Required] - public byte[] Timestamp { get; set; } - - /************************************************************************* - * Navigation properties - *************************************************************************/ - - /// - /// Required - /// - public virtual global::Jellyfin.Data.Entities.Library Library { get; set; } - - } + } + + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } + + public void OnSavingChanges() + { + RowVersion++; + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + /// + /// 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 2a47a9632..dbb65a6f7 100644 --- a/Jellyfin.Data/Entities/MediaFile.cs +++ b/Jellyfin.Data/Entities/MediaFile.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,189 +9,197 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class MediaFile - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected MediaFile() - { - MediaFileStreams = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static MediaFile CreateMediaFileUnsafe() - { - return new MediaFile(); - } - - /// - /// Public constructor with required data - /// - /// Relative to the LibraryRoot - /// - /// - public MediaFile(string path, global::Jellyfin.Data.Enums.MediaFileKind kind, global::Jellyfin.Data.Entities.Release _release0) - { - if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); - this.Path = path; - - this.Kind = kind; - - if (_release0 == null) throw new ArgumentNullException(nameof(_release0)); - _release0.MediaFiles.Add(this); - - this.MediaFileStreams = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// Relative to the LibraryRoot - /// - /// - public static MediaFile Create(string path, global::Jellyfin.Data.Enums.MediaFileKind kind, global::Jellyfin.Data.Entities.Release _release0) - { - return new MediaFile(path, kind, _release0); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for Id - /// - internal int _Id; - /// - /// When provided in a partial class, allows value of Id to be changed before setting. - /// - partial void SetId(int oldValue, ref int newValue); - /// - /// When provided in a partial class, allows value of Id to be changed before returning. - /// - partial void GetId(ref int result); - - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public int Id - { - get - { - int value = _Id; - GetId(ref value); - return (_Id = value); - } - protected set - { - int oldValue = _Id; - SetId(oldValue, ref value); - if (oldValue != value) + [Table("MediaFile")] + public partial class MediaFile + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected MediaFile() + { + MediaFileStreams = new HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static MediaFile CreateMediaFileUnsafe() + { + return new MediaFile(); + } + + /// + /// Public constructor with required data + /// + /// Relative to the LibraryRoot + /// + /// + public MediaFile(string path, Enums.MediaFileKind kind, Release _release0) + { + if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); + this.Path = path; + + this.Kind = kind; + + if (_release0 == null) throw new ArgumentNullException(nameof(_release0)); + _release0.MediaFiles.Add(this); + + this.MediaFileStreams = new HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// Relative to the LibraryRoot + /// + /// + public static MediaFile Create(string path, Enums.MediaFileKind kind, Release _release0) + { + return new MediaFile(path, kind, _release0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for Path + /// + protected string _Path; + /// + /// When provided in a partial class, allows value of Path to be changed before setting. + /// + partial void SetPath(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Path to be changed before returning. + /// + partial void GetPath(ref string result); + + /// + /// Required, Max length = 65535 + /// Relative to the LibraryRoot + /// + [Required] + [MaxLength(65535)] + [StringLength(65535)] + public string Path + { + get + { + string value = _Path; + GetPath(ref value); + return (_Path = value); + } + set { - _Id = value; + string oldValue = _Path; + SetPath(oldValue, ref value); + if (oldValue != value) + { + _Path = value; + } } - } - } - - /// - /// Backing field for Path - /// - protected string _Path; - /// - /// When provided in a partial class, allows value of Path to be changed before setting. - /// - partial void SetPath(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Path to be changed before returning. - /// - partial void GetPath(ref string result); - - /// - /// Required, Max length = 65535 - /// Relative to the LibraryRoot - /// - [Required] - [MaxLength(65535)] - [StringLength(65535)] - public string Path - { - get - { - string value = _Path; - GetPath(ref value); - return (_Path = value); - } - set - { - string oldValue = _Path; - SetPath(oldValue, ref value); - if (oldValue != value) + } + + /// + /// Backing field for Kind + /// + protected Enums.MediaFileKind _Kind; + /// + /// When provided in a partial class, allows value of Kind to be changed before setting. + /// + partial void SetKind(Enums.MediaFileKind oldValue, ref Enums.MediaFileKind newValue); + /// + /// When provided in a partial class, allows value of Kind to be changed before returning. + /// + partial void GetKind(ref Enums.MediaFileKind result); + + /// + /// Required + /// + [Required] + public Enums.MediaFileKind Kind + { + get { - _Path = value; + Enums.MediaFileKind value = _Kind; + GetKind(ref value); + return (_Kind = value); } - } - } - - /// - /// Backing field for Kind - /// - protected global::Jellyfin.Data.Enums.MediaFileKind _Kind; - /// - /// When provided in a partial class, allows value of Kind to be changed before setting. - /// - partial void SetKind(global::Jellyfin.Data.Enums.MediaFileKind oldValue, ref global::Jellyfin.Data.Enums.MediaFileKind newValue); - /// - /// When provided in a partial class, allows value of Kind to be changed before returning. - /// - partial void GetKind(ref global::Jellyfin.Data.Enums.MediaFileKind result); - - /// - /// Required - /// - [Required] - public global::Jellyfin.Data.Enums.MediaFileKind Kind - { - get - { - global::Jellyfin.Data.Enums.MediaFileKind value = _Kind; - GetKind(ref value); - return (_Kind = value); - } - set - { - global::Jellyfin.Data.Enums.MediaFileKind oldValue = _Kind; - SetKind(oldValue, ref value); - if (oldValue != value) + set { - _Kind = value; + Enums.MediaFileKind oldValue = _Kind; + SetKind(oldValue, ref value); + if (oldValue != value) + { + _Kind = value; + } } - } - } + } + + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } - /// - /// Required - /// - [ConcurrencyCheck] - [Required] - public byte[] Timestamp { get; set; } + public void OnSavingChanges() + { + RowVersion++; + } - /************************************************************************* - * Navigation properties - *************************************************************************/ + /************************************************************************* + * Navigation properties + *************************************************************************/ - public virtual ICollection MediaFileStreams { get; protected set; } + [ForeignKey("MediaFileStream_MediaFileStreams_Id")] + public virtual ICollection MediaFileStreams { get; protected set; } - } + } } diff --git a/Jellyfin.Data/Entities/MediaFileStream.cs b/Jellyfin.Data/Entities/MediaFileStream.cs index 6593d3cf7..3ce18b8d7 100644 --- a/Jellyfin.Data/Entities/MediaFileStream.cs +++ b/Jellyfin.Data/Entities/MediaFileStream.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,140 +9,147 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class MediaFileStream - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected MediaFileStream() - { - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static MediaFileStream CreateMediaFileStreamUnsafe() - { - return new MediaFileStream(); - } - - /// - /// Public constructor with required data - /// - /// - /// - public MediaFileStream(int streamnumber, global::Jellyfin.Data.Entities.MediaFile _mediafile0) - { - this.StreamNumber = streamnumber; - - if (_mediafile0 == null) throw new ArgumentNullException(nameof(_mediafile0)); - _mediafile0.MediaFileStreams.Add(this); - - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// - /// - public static MediaFileStream Create(int streamnumber, global::Jellyfin.Data.Entities.MediaFile _mediafile0) - { - return new MediaFileStream(streamnumber, _mediafile0); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for Id - /// - internal int _Id; - /// - /// When provided in a partial class, allows value of Id to be changed before setting. - /// - partial void SetId(int oldValue, ref int newValue); - /// - /// When provided in a partial class, allows value of Id to be changed before returning. - /// - partial void GetId(ref int result); - - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public int Id - { - get - { - int value = _Id; - GetId(ref value); - return (_Id = value); - } - protected set - { - int oldValue = _Id; - SetId(oldValue, ref value); - if (oldValue != value) + [Table("MediaFileStream")] + public partial class MediaFileStream + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected MediaFileStream() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static MediaFileStream CreateMediaFileStreamUnsafe() + { + return new MediaFileStream(); + } + + /// + /// Public constructor with required data + /// + /// + /// + public MediaFileStream(int streamnumber, MediaFile _mediafile0) + { + this.StreamNumber = streamnumber; + + if (_mediafile0 == null) throw new ArgumentNullException(nameof(_mediafile0)); + _mediafile0.MediaFileStreams.Add(this); + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + public static MediaFileStream Create(int streamnumber, MediaFile _mediafile0) + { + return new MediaFileStream(streamnumber, _mediafile0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set { - _Id = value; + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } } - } - } - - /// - /// Backing field for StreamNumber - /// - protected int _StreamNumber; - /// - /// When provided in a partial class, allows value of StreamNumber to be changed before setting. - /// - partial void SetStreamNumber(int oldValue, ref int newValue); - /// - /// When provided in a partial class, allows value of StreamNumber to be changed before returning. - /// - partial void GetStreamNumber(ref int result); - - /// - /// Required - /// - [Required] - public int StreamNumber - { - get - { - int value = _StreamNumber; - GetStreamNumber(ref value); - return (_StreamNumber = value); - } - set - { - int oldValue = _StreamNumber; - SetStreamNumber(oldValue, ref value); - if (oldValue != value) + } + + /// + /// Backing field for StreamNumber + /// + protected int _StreamNumber; + /// + /// When provided in a partial class, allows value of StreamNumber to be changed before setting. + /// + partial void SetStreamNumber(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of StreamNumber to be changed before returning. + /// + partial void GetStreamNumber(ref int result); + + /// + /// Required + /// + [Required] + public int StreamNumber + { + get { - _StreamNumber = value; + int value = _StreamNumber; + GetStreamNumber(ref value); + return (_StreamNumber = value); } - } - } + set + { + int oldValue = _StreamNumber; + SetStreamNumber(oldValue, ref value); + if (oldValue != value) + { + _StreamNumber = value; + } + } + } + + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } - /// - /// Required - /// - [ConcurrencyCheck] - [Required] - public byte[] Timestamp { get; set; } + public void OnSavingChanges() + { + RowVersion++; + } - /************************************************************************* - * Navigation properties - *************************************************************************/ + /************************************************************************* + * Navigation properties + *************************************************************************/ - } + } } diff --git a/Jellyfin.Data/Entities/Metadata.cs b/Jellyfin.Data/Entities/Metadata.cs index 6057017e9..5cba24ee3 100644 --- a/Jellyfin.Data/Entities/Metadata.cs +++ b/Jellyfin.Data/Entities/Metadata.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,365 +9,377 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public abstract partial class Metadata - { - partial void Init(); - - /// - /// Default constructor. Protected due to being abstract. - /// - protected Metadata() - { - PersonRoles = new System.Collections.Generic.HashSet(); - Genres = new System.Collections.Generic.HashSet(); - Artwork = new System.Collections.Generic.HashSet(); - Ratings = new System.Collections.Generic.HashSet(); - Sources = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Public constructor with required data - /// - /// The title or name of the object - /// ISO-639-3 3-character language codes - protected Metadata(string title, string language, DateTime dateadded, DateTime datemodified) - { - if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); - this.Title = title; - - if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); - this.Language = language; - - this.PersonRoles = new System.Collections.Generic.HashSet(); - this.Genres = new System.Collections.Generic.HashSet(); - this.Artwork = new System.Collections.Generic.HashSet(); - this.Ratings = new System.Collections.Generic.HashSet(); - this.Sources = new System.Collections.Generic.HashSet(); - - Init(); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for Id - /// - internal int _Id; - /// - /// When provided in a partial class, allows value of Id to be changed before setting. - /// - partial void SetId(int oldValue, ref int newValue); - /// - /// When provided in a partial class, allows value of Id to be changed before returning. - /// - partial void GetId(ref int result); - - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public int Id - { - get - { - int value = _Id; - GetId(ref value); - return (_Id = value); - } - protected set - { - int oldValue = _Id; - SetId(oldValue, ref value); - if (oldValue != value) + [Table("Metadata")] + public abstract partial class Metadata + { + partial void Init(); + + /// + /// Default constructor. Protected due to being abstract. + /// + protected Metadata() + { + PersonRoles = new HashSet(); + Genres = new HashSet(); + Artwork = new HashSet(); + Ratings = new HashSet(); + Sources = new HashSet(); + + Init(); + } + + /// + /// Public constructor with required data + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + protected Metadata(string title, string language, DateTime dateadded, DateTime datemodified) + { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + this.Title = title; + + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; + + this.PersonRoles = new HashSet(); + this.Genres = new HashSet(); + this.Artwork = new HashSet(); + this.Ratings = new HashSet(); + this.Sources = new HashSet(); + + Init(); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for Title + /// + protected string _Title; + /// + /// When provided in a partial class, allows value of Title to be changed before setting. + /// + partial void SetTitle(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Title to be changed before returning. + /// + partial void GetTitle(ref string result); + + /// + /// Required, Max length = 1024 + /// The title or name of the object + /// + [Required] + [MaxLength(1024)] + [StringLength(1024)] + public string Title + { + get + { + string value = _Title; + GetTitle(ref value); + return (_Title = value); + } + set + { + string oldValue = _Title; + SetTitle(oldValue, ref value); + if (oldValue != value) + { + _Title = value; + } + } + } + + /// + /// Backing field for OriginalTitle + /// + protected string _OriginalTitle; + /// + /// When provided in a partial class, allows value of OriginalTitle to be changed before setting. + /// + partial void SetOriginalTitle(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of OriginalTitle to be changed before returning. + /// + partial void GetOriginalTitle(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string OriginalTitle + { + get { - _Id = value; + string value = _OriginalTitle; + GetOriginalTitle(ref value); + return (_OriginalTitle = value); } - } - } - - /// - /// Backing field for Title - /// - protected string _Title; - /// - /// When provided in a partial class, allows value of Title to be changed before setting. - /// - partial void SetTitle(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Title to be changed before returning. - /// - partial void GetTitle(ref string result); - - /// - /// Required, Max length = 1024 - /// The title or name of the object - /// - [Required] - [MaxLength(1024)] - [StringLength(1024)] - public string Title - { - get - { - string value = _Title; - GetTitle(ref value); - return (_Title = value); - } - set - { - string oldValue = _Title; - SetTitle(oldValue, ref value); - if (oldValue != value) + set { - _Title = value; + string oldValue = _OriginalTitle; + SetOriginalTitle(oldValue, ref value); + if (oldValue != value) + { + _OriginalTitle = value; + } } - } - } - - /// - /// Backing field for OriginalTitle - /// - protected string _OriginalTitle; - /// - /// When provided in a partial class, allows value of OriginalTitle to be changed before setting. - /// - partial void SetOriginalTitle(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of OriginalTitle to be changed before returning. - /// - partial void GetOriginalTitle(ref string result); - - /// - /// Max length = 1024 - /// - [MaxLength(1024)] - [StringLength(1024)] - public string OriginalTitle - { - get - { - string value = _OriginalTitle; - GetOriginalTitle(ref value); - return (_OriginalTitle = value); - } - set - { - string oldValue = _OriginalTitle; - SetOriginalTitle(oldValue, ref value); - if (oldValue != value) + } + + /// + /// Backing field for SortTitle + /// + protected string _SortTitle; + /// + /// When provided in a partial class, allows value of SortTitle to be changed before setting. + /// + partial void SetSortTitle(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of SortTitle to be changed before returning. + /// + partial void GetSortTitle(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string SortTitle + { + get { - _OriginalTitle = value; + string value = _SortTitle; + GetSortTitle(ref value); + return (_SortTitle = value); } - } - } - - /// - /// Backing field for SortTitle - /// - protected string _SortTitle; - /// - /// When provided in a partial class, allows value of SortTitle to be changed before setting. - /// - partial void SetSortTitle(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of SortTitle to be changed before returning. - /// - partial void GetSortTitle(ref string result); - - /// - /// Max length = 1024 - /// - [MaxLength(1024)] - [StringLength(1024)] - public string SortTitle - { - get - { - string value = _SortTitle; - GetSortTitle(ref value); - return (_SortTitle = value); - } - set - { - string oldValue = _SortTitle; - SetSortTitle(oldValue, ref value); - if (oldValue != value) + set { - _SortTitle = value; + string oldValue = _SortTitle; + SetSortTitle(oldValue, ref value); + if (oldValue != value) + { + _SortTitle = value; + } } - } - } - - /// - /// Backing field for Language - /// - protected string _Language; - /// - /// When provided in a partial class, allows value of Language to be changed before setting. - /// - partial void SetLanguage(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Language to be changed before returning. - /// - partial void GetLanguage(ref string result); - - /// - /// Required, Min length = 3, Max length = 3 - /// ISO-639-3 3-character language codes - /// - [Required] - [MinLength(3)] - [MaxLength(3)] - [StringLength(3)] - public string Language - { - get - { - string value = _Language; - GetLanguage(ref value); - return (_Language = value); - } - set - { - string oldValue = _Language; - SetLanguage(oldValue, ref value); - if (oldValue != value) + } + + /// + /// Backing field for Language + /// + protected string _Language; + /// + /// When provided in a partial class, allows value of Language to be changed before setting. + /// + partial void SetLanguage(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Language to be changed before returning. + /// + partial void GetLanguage(ref string result); + + /// + /// Required, Min length = 3, Max length = 3 + /// ISO-639-3 3-character language codes + /// + [Required] + [MinLength(3)] + [MaxLength(3)] + [StringLength(3)] + public string Language + { + get { - _Language = value; + string value = _Language; + GetLanguage(ref value); + return (_Language = value); } - } - } - - /// - /// Backing field for ReleaseDate - /// - protected DateTimeOffset? _ReleaseDate; - /// - /// When provided in a partial class, allows value of ReleaseDate to be changed before setting. - /// - partial void SetReleaseDate(DateTimeOffset? oldValue, ref DateTimeOffset? newValue); - /// - /// When provided in a partial class, allows value of ReleaseDate to be changed before returning. - /// - partial void GetReleaseDate(ref DateTimeOffset? result); - - public DateTimeOffset? ReleaseDate - { - get - { - DateTimeOffset? value = _ReleaseDate; - GetReleaseDate(ref value); - return (_ReleaseDate = value); - } - set - { - DateTimeOffset? oldValue = _ReleaseDate; - SetReleaseDate(oldValue, ref value); - if (oldValue != value) + set { - _ReleaseDate = value; + string oldValue = _Language; + SetLanguage(oldValue, ref value); + if (oldValue != value) + { + _Language = value; + } } - } - } - - /// - /// Backing field for DateAdded - /// - protected DateTime _DateAdded; - /// - /// When provided in a partial class, allows value of DateAdded to be changed before setting. - /// - partial void SetDateAdded(DateTime oldValue, ref DateTime newValue); - /// - /// When provided in a partial class, allows value of DateAdded to be changed before returning. - /// - partial void GetDateAdded(ref DateTime result); - - /// - /// Required - /// - [Required] - public DateTime DateAdded - { - get - { - DateTime value = _DateAdded; - GetDateAdded(ref value); - return (_DateAdded = value); - } - internal set - { - DateTime oldValue = _DateAdded; - SetDateAdded(oldValue, ref value); - if (oldValue != value) + } + + /// + /// Backing field for ReleaseDate + /// + protected DateTimeOffset? _ReleaseDate; + /// + /// When provided in a partial class, allows value of ReleaseDate to be changed before setting. + /// + partial void SetReleaseDate(DateTimeOffset? oldValue, ref DateTimeOffset? newValue); + /// + /// When provided in a partial class, allows value of ReleaseDate to be changed before returning. + /// + partial void GetReleaseDate(ref DateTimeOffset? result); + + public DateTimeOffset? ReleaseDate + { + get { - _DateAdded = value; + DateTimeOffset? value = _ReleaseDate; + GetReleaseDate(ref value); + return (_ReleaseDate = value); } - } - } - - /// - /// Backing field for DateModified - /// - protected DateTime _DateModified; - /// - /// When provided in a partial class, allows value of DateModified to be changed before setting. - /// - partial void SetDateModified(DateTime oldValue, ref DateTime newValue); - /// - /// When provided in a partial class, allows value of DateModified to be changed before returning. - /// - partial void GetDateModified(ref DateTime result); - - /// - /// Required - /// - [Required] - public DateTime DateModified - { - get - { - DateTime value = _DateModified; - GetDateModified(ref value); - return (_DateModified = value); - } - internal set - { - DateTime oldValue = _DateModified; - SetDateModified(oldValue, ref value); - if (oldValue != value) + set { - _DateModified = value; + DateTimeOffset? oldValue = _ReleaseDate; + SetReleaseDate(oldValue, ref value); + if (oldValue != value) + { + _ReleaseDate = value; + } } - } - } + } + + /// + /// Backing field for DateAdded + /// + protected DateTime _DateAdded; + /// + /// When provided in a partial class, allows value of DateAdded to be changed before setting. + /// + partial void SetDateAdded(DateTime oldValue, ref DateTime newValue); + /// + /// When provided in a partial class, allows value of DateAdded to be changed before returning. + /// + partial void GetDateAdded(ref DateTime result); + + /// + /// Required + /// + [Required] + public DateTime DateAdded + { + get + { + DateTime value = _DateAdded; + GetDateAdded(ref value); + return (_DateAdded = value); + } + internal set + { + DateTime oldValue = _DateAdded; + SetDateAdded(oldValue, ref value); + if (oldValue != value) + { + _DateAdded = value; + } + } + } + + /// + /// Backing field for DateModified + /// + protected DateTime _DateModified; + /// + /// When provided in a partial class, allows value of DateModified to be changed before setting. + /// + partial void SetDateModified(DateTime oldValue, ref DateTime newValue); + /// + /// When provided in a partial class, allows value of DateModified to be changed before returning. + /// + partial void GetDateModified(ref DateTime result); + + /// + /// Required + /// + [Required] + public DateTime DateModified + { + get + { + DateTime value = _DateModified; + GetDateModified(ref value); + return (_DateModified = value); + } + internal set + { + DateTime oldValue = _DateModified; + SetDateModified(oldValue, ref value); + if (oldValue != value) + { + _DateModified = value; + } + } + } + + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } - /// - /// Required - /// - [ConcurrencyCheck] - [Required] - public byte[] Timestamp { get; set; } + public void OnSavingChanges() + { + RowVersion++; + } - /************************************************************************* - * Navigation properties - *************************************************************************/ + /************************************************************************* + * Navigation properties + *************************************************************************/ - public virtual ICollection PersonRoles { get; protected set; } + [ForeignKey("PersonRole_PersonRoles_Id")] + public virtual ICollection PersonRoles { get; protected set; } - public virtual ICollection Genres { get; protected set; } + [ForeignKey("PersonRole_PersonRoles_Id")] + public virtual ICollection Genres { get; protected set; } - public virtual ICollection Artwork { get; protected set; } + [ForeignKey("PersonRole_PersonRoles_Id")] + public virtual ICollection Artwork { get; protected set; } - public virtual ICollection Ratings { get; protected set; } + [ForeignKey("PersonRole_PersonRoles_Id")] + public virtual ICollection Ratings { get; protected set; } - public virtual ICollection Sources { get; protected set; } + [ForeignKey("PersonRole_PersonRoles_Id")] + public virtual ICollection Sources { get; protected set; } - } + } } diff --git a/Jellyfin.Data/Entities/MetadataProvider.cs b/Jellyfin.Data/Entities/MetadataProvider.cs index 3a8f5854e..bc6e04277 100644 --- a/Jellyfin.Data/Entities/MetadataProvider.cs +++ b/Jellyfin.Data/Entities/MetadataProvider.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,138 +9,145 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class MetadataProvider - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected MetadataProvider() - { - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static MetadataProvider CreateMetadataProviderUnsafe() - { - return new MetadataProvider(); - } - - /// - /// Public constructor with required data - /// - /// - public MetadataProvider(string name) - { - if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); - this.Name = name; - - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// - public static MetadataProvider Create(string name) - { - return new MetadataProvider(name); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for Id - /// - internal int _Id; - /// - /// When provided in a partial class, allows value of Id to be changed before setting. - /// - partial void SetId(int oldValue, ref int newValue); - /// - /// When provided in a partial class, allows value of Id to be changed before returning. - /// - partial void GetId(ref int result); - - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public int Id - { - get - { - int value = _Id; - GetId(ref value); - return (_Id = value); - } - protected set - { - int oldValue = _Id; - SetId(oldValue, ref value); - if (oldValue != value) + [Table("MetadataProvider")] + public partial class MetadataProvider + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected MetadataProvider() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static MetadataProvider CreateMetadataProviderUnsafe() + { + return new MetadataProvider(); + } + + /// + /// Public constructor with required data + /// + /// + public MetadataProvider(string name) + { + if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); + this.Name = name; + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + public static MetadataProvider Create(string name) + { + return new MetadataProvider(name); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set { - _Id = value; + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } } - } - } - - /// - /// Backing field for Name - /// - protected string _Name; - /// - /// When provided in a partial class, allows value of Name to be changed before setting. - /// - partial void SetName(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Name to be changed before returning. - /// - partial void GetName(ref string result); - - /// - /// Required, Max length = 1024 - /// - [Required] - [MaxLength(1024)] - [StringLength(1024)] - public string Name - { - get - { - string value = _Name; - GetName(ref value); - return (_Name = value); - } - set - { - string oldValue = _Name; - SetName(oldValue, ref value); - if (oldValue != value) + } + + /// + /// Backing field for Name + /// + protected string _Name; + /// + /// When provided in a partial class, allows value of Name to be changed before setting. + /// + partial void SetName(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Name to be changed before returning. + /// + partial void GetName(ref string result); + + /// + /// Required, Max length = 1024 + /// + [Required] + [MaxLength(1024)] + [StringLength(1024)] + public string Name + { + get { - _Name = value; + string value = _Name; + GetName(ref value); + return (_Name = value); } - } - } + set + { + string oldValue = _Name; + SetName(oldValue, ref value); + if (oldValue != value) + { + _Name = value; + } + } + } + + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } - /// - /// Required - /// - [ConcurrencyCheck] - [Required] - public byte[] Timestamp { get; set; } + public void OnSavingChanges() + { + RowVersion++; + } - /************************************************************************* - * Navigation properties - *************************************************************************/ + /************************************************************************* + * Navigation properties + *************************************************************************/ - } + } } diff --git a/Jellyfin.Data/Entities/MetadataProviderId.cs b/Jellyfin.Data/Entities/MetadataProviderId.cs index 87ff19e26..d381856f3 100644 --- a/Jellyfin.Data/Entities/MetadataProviderId.cs +++ b/Jellyfin.Data/Entities/MetadataProviderId.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,169 +9,177 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class MetadataProviderId - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected MetadataProviderId() - { - // NOTE: This class has one-to-one associations with MetadataProviderId. - // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. - - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static MetadataProviderId CreateMetadataProviderIdUnsafe() - { - return new MetadataProviderId(); - } - - /// - /// Public constructor with required data - /// - /// - /// - /// - /// - /// - public MetadataProviderId(string providerid, global::Jellyfin.Data.Entities.Metadata _metadata0, global::Jellyfin.Data.Entities.Person _person1, global::Jellyfin.Data.Entities.PersonRole _personrole2, global::Jellyfin.Data.Entities.RatingSource _ratingsource3) - { - // NOTE: This class has one-to-one associations with MetadataProviderId. - // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. - - if (string.IsNullOrEmpty(providerid)) throw new ArgumentNullException(nameof(providerid)); - this.ProviderId = providerid; - - if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0)); - _metadata0.Sources.Add(this); - - if (_person1 == null) throw new ArgumentNullException(nameof(_person1)); - _person1.Sources.Add(this); - - if (_personrole2 == null) throw new ArgumentNullException(nameof(_personrole2)); - _personrole2.Sources.Add(this); - - if (_ratingsource3 == null) throw new ArgumentNullException(nameof(_ratingsource3)); - _ratingsource3.Source = this; - - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// - /// - /// - /// - /// - public static MetadataProviderId Create(string providerid, global::Jellyfin.Data.Entities.Metadata _metadata0, global::Jellyfin.Data.Entities.Person _person1, global::Jellyfin.Data.Entities.PersonRole _personrole2, global::Jellyfin.Data.Entities.RatingSource _ratingsource3) - { - return new MetadataProviderId(providerid, _metadata0, _person1, _personrole2, _ratingsource3); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for Id - /// - internal int _Id; - /// - /// When provided in a partial class, allows value of Id to be changed before setting. - /// - partial void SetId(int oldValue, ref int newValue); - /// - /// When provided in a partial class, allows value of Id to be changed before returning. - /// - partial void GetId(ref int result); - - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public int Id - { - get - { - int value = _Id; - GetId(ref value); - return (_Id = value); - } - protected set - { - int oldValue = _Id; - SetId(oldValue, ref value); - if (oldValue != value) + [Table("MetadataProviderId")] + public partial class MetadataProviderId + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected MetadataProviderId() + { + // NOTE: This class has one-to-one associations with MetadataProviderId. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static MetadataProviderId CreateMetadataProviderIdUnsafe() + { + return new MetadataProviderId(); + } + + /// + /// Public constructor with required data + /// + /// + /// + /// + /// + /// + public MetadataProviderId(string providerid, Metadata _metadata0, Person _person1, PersonRole _personrole2, RatingSource _ratingsource3) + { + // NOTE: This class has one-to-one associations with MetadataProviderId. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + if (string.IsNullOrEmpty(providerid)) throw new ArgumentNullException(nameof(providerid)); + this.ProviderId = providerid; + + if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0)); + _metadata0.Sources.Add(this); + + if (_person1 == null) throw new ArgumentNullException(nameof(_person1)); + _person1.Sources.Add(this); + + if (_personrole2 == null) throw new ArgumentNullException(nameof(_personrole2)); + _personrole2.Sources.Add(this); + + if (_ratingsource3 == null) throw new ArgumentNullException(nameof(_ratingsource3)); + _ratingsource3.Source = this; + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + /// + /// + /// + public static MetadataProviderId Create(string providerid, Metadata _metadata0, Person _person1, PersonRole _personrole2, RatingSource _ratingsource3) + { + return new MetadataProviderId(providerid, _metadata0, _person1, _personrole2, _ratingsource3); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for ProviderId + /// + protected string _ProviderId; + /// + /// When provided in a partial class, allows value of ProviderId to be changed before setting. + /// + partial void SetProviderId(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of ProviderId to be changed before returning. + /// + partial void GetProviderId(ref string result); + + /// + /// Required, Max length = 255 + /// + [Required] + [MaxLength(255)] + [StringLength(255)] + public string ProviderId + { + get { - _Id = value; + string value = _ProviderId; + GetProviderId(ref value); + return (_ProviderId = value); } - } - } - - /// - /// Backing field for ProviderId - /// - protected string _ProviderId; - /// - /// When provided in a partial class, allows value of ProviderId to be changed before setting. - /// - partial void SetProviderId(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of ProviderId to be changed before returning. - /// - partial void GetProviderId(ref string result); - - /// - /// Required, Max length = 255 - /// - [Required] - [MaxLength(255)] - [StringLength(255)] - public string ProviderId - { - get - { - string value = _ProviderId; - GetProviderId(ref value); - return (_ProviderId = value); - } - set - { - string oldValue = _ProviderId; - SetProviderId(oldValue, ref value); - if (oldValue != value) + set { - _ProviderId = value; + string oldValue = _ProviderId; + SetProviderId(oldValue, ref value); + if (oldValue != value) + { + _ProviderId = value; + } } - } - } - - /// - /// Required - /// - [ConcurrencyCheck] - [Required] - public byte[] Timestamp { get; set; } - - /************************************************************************* - * Navigation properties - *************************************************************************/ - - /// - /// Required - /// - public virtual global::Jellyfin.Data.Entities.MetadataProvider MetadataProvider { get; set; } - - } + } + + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } + + public void OnSavingChanges() + { + RowVersion++; + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + /// + /// 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 dfcc05a94..23a340b1b 100644 --- a/Jellyfin.Data/Entities/Movie.cs +++ b/Jellyfin.Data/Entities/Movie.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,64 +9,67 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class Movie: global::Jellyfin.Data.Entities.LibraryItem - { - partial void Init(); + [Table("Movie")] + public partial class Movie : LibraryItem + { + partial void Init(); - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Movie(): base() - { - Releases = new System.Collections.Generic.HashSet(); - MovieMetadata = new System.Collections.Generic.HashSet(); + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Movie() : base() + { + Releases = new HashSet(); + MovieMetadata = new HashSet(); - Init(); - } + Init(); + } - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static Movie CreateMovieUnsafe() - { - return new Movie(); - } + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Movie CreateMovieUnsafe() + { + return new Movie(); + } - /// - /// 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) - { - this.UrlId = urlid; + /// + /// 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) + { + this.UrlId = urlid; - this.Releases = new System.Collections.Generic.HashSet(); - this.MovieMetadata = new System.Collections.Generic.HashSet(); + this.Releases = new HashSet(); + this.MovieMetadata = new HashSet(); - Init(); - } + Init(); + } - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// This is whats gets displayed in the Urls and API requests. This could also be a string. - public static Movie Create(Guid urlid, DateTime dateadded) - { - return new Movie(urlid, dateadded); - } + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + public static Movie Create(Guid urlid, DateTime dateadded) + { + return new Movie(urlid, dateadded); + } - /************************************************************************* - * Properties - *************************************************************************/ + /************************************************************************* + * Properties + *************************************************************************/ - /************************************************************************* - * Navigation properties - *************************************************************************/ + /************************************************************************* + * Navigation properties + *************************************************************************/ - public virtual ICollection Releases { get; protected set; } + [ForeignKey("Release_Releases_Id")] + public virtual ICollection Releases { get; protected set; } - public virtual ICollection MovieMetadata { get; protected set; } + [ForeignKey("MovieMetadata_MovieMetadata_Id")] + public virtual ICollection MovieMetadata { get; protected set; } - } + } } diff --git a/Jellyfin.Data/Entities/MovieMetadata.cs b/Jellyfin.Data/Entities/MovieMetadata.cs index bd847da8f..090761877 100644 --- a/Jellyfin.Data/Entities/MovieMetadata.cs +++ b/Jellyfin.Data/Entities/MovieMetadata.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,219 +9,220 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class MovieMetadata: global::Jellyfin.Data.Entities.Metadata - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected MovieMetadata(): base() - { - Studios = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static MovieMetadata CreateMovieMetadataUnsafe() - { - return new MovieMetadata(); - } - - /// - /// Public constructor with required data - /// - /// The title or name of the object - /// ISO-639-3 3-character language codes - /// - public MovieMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Movie _movie0) - { - if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); - this.Title = title; - - if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); - this.Language = language; - - if (_movie0 == null) throw new ArgumentNullException(nameof(_movie0)); - _movie0.MovieMetadata.Add(this); - - this.Studios = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// The title or name of the object - /// ISO-639-3 3-character language codes - /// - public static MovieMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Movie _movie0) - { - return new MovieMetadata(title, language, dateadded, datemodified, _movie0); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for Outline - /// - protected string _Outline; - /// - /// When provided in a partial class, allows value of Outline to be changed before setting. - /// - partial void SetOutline(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Outline to be changed before returning. - /// - partial void GetOutline(ref string result); - - /// - /// Max length = 1024 - /// - [MaxLength(1024)] - [StringLength(1024)] - public string Outline - { - get - { - string value = _Outline; - GetOutline(ref value); - return (_Outline = value); - } - set - { - string oldValue = _Outline; - SetOutline(oldValue, ref value); - if (oldValue != value) + [Table("MovieMetadata")] + public partial class MovieMetadata : Metadata + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected MovieMetadata() : base() + { + Studios = new HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static MovieMetadata CreateMovieMetadataUnsafe() + { + return new MovieMetadata(); + } + + /// + /// Public constructor with required data + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public MovieMetadata(string title, string language, DateTime dateadded, DateTime datemodified, Movie _movie0) + { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + this.Title = title; + + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; + + if (_movie0 == null) throw new ArgumentNullException(nameof(_movie0)); + _movie0.MovieMetadata.Add(this); + + this.Studios = new HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public static MovieMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, Movie _movie0) + { + return new MovieMetadata(title, language, dateadded, datemodified, _movie0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Outline + /// + protected string _Outline; + /// + /// When provided in a partial class, allows value of Outline to be changed before setting. + /// + partial void SetOutline(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Outline to be changed before returning. + /// + partial void GetOutline(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Outline + { + get { - _Outline = value; + string value = _Outline; + GetOutline(ref value); + return (_Outline = value); } - } - } - - /// - /// Backing field for Plot - /// - protected string _Plot; - /// - /// When provided in a partial class, allows value of Plot to be changed before setting. - /// - partial void SetPlot(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Plot to be changed before returning. - /// - partial void GetPlot(ref string result); - - /// - /// Max length = 65535 - /// - [MaxLength(65535)] - [StringLength(65535)] - public string Plot - { - get - { - string value = _Plot; - GetPlot(ref value); - return (_Plot = value); - } - set - { - string oldValue = _Plot; - SetPlot(oldValue, ref value); - if (oldValue != value) + set { - _Plot = value; + string oldValue = _Outline; + SetOutline(oldValue, ref value); + if (oldValue != value) + { + _Outline = value; + } } - } - } - - /// - /// Backing field for Tagline - /// - protected string _Tagline; - /// - /// When provided in a partial class, allows value of Tagline to be changed before setting. - /// - partial void SetTagline(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Tagline to be changed before returning. - /// - partial void GetTagline(ref string result); - - /// - /// Max length = 1024 - /// - [MaxLength(1024)] - [StringLength(1024)] - public string Tagline - { - get - { - string value = _Tagline; - GetTagline(ref value); - return (_Tagline = value); - } - set - { - string oldValue = _Tagline; - SetTagline(oldValue, ref value); - if (oldValue != value) + } + + /// + /// Backing field for Plot + /// + protected string _Plot; + /// + /// When provided in a partial class, allows value of Plot to be changed before setting. + /// + partial void SetPlot(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Plot to be changed before returning. + /// + partial void GetPlot(ref string result); + + /// + /// Max length = 65535 + /// + [MaxLength(65535)] + [StringLength(65535)] + public string Plot + { + get { - _Tagline = value; + string value = _Plot; + GetPlot(ref value); + return (_Plot = value); } - } - } - - /// - /// Backing field for Country - /// - protected string _Country; - /// - /// When provided in a partial class, allows value of Country to be changed before setting. - /// - partial void SetCountry(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Country to be changed before returning. - /// - partial void GetCountry(ref string result); - - /// - /// Max length = 2 - /// - [MaxLength(2)] - [StringLength(2)] - public string Country - { - get - { - string value = _Country; - GetCountry(ref value); - return (_Country = value); - } - set - { - string oldValue = _Country; - SetCountry(oldValue, ref value); - if (oldValue != value) + set { - _Country = value; + string oldValue = _Plot; + SetPlot(oldValue, ref value); + if (oldValue != value) + { + _Plot = value; + } } - } - } - - /************************************************************************* - * Navigation properties - *************************************************************************/ + } + + /// + /// Backing field for Tagline + /// + protected string _Tagline; + /// + /// When provided in a partial class, allows value of Tagline to be changed before setting. + /// + partial void SetTagline(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Tagline to be changed before returning. + /// + partial void GetTagline(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Tagline + { + get + { + string value = _Tagline; + GetTagline(ref value); + return (_Tagline = value); + } + set + { + string oldValue = _Tagline; + SetTagline(oldValue, ref value); + if (oldValue != value) + { + _Tagline = value; + } + } + } + + /// + /// Backing field for Country + /// + protected string _Country; + /// + /// When provided in a partial class, allows value of Country to be changed before setting. + /// + partial void SetCountry(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Country to be changed before returning. + /// + partial void GetCountry(ref string result); + + /// + /// Max length = 2 + /// + [MaxLength(2)] + [StringLength(2)] + public string Country + { + get + { + string value = _Country; + GetCountry(ref value); + return (_Country = value); + } + set + { + string oldValue = _Country; + SetCountry(oldValue, ref value); + if (oldValue != value) + { + _Country = value; + } + } + } - public virtual ICollection Studios { get; protected set; } + /************************************************************************* + * Navigation properties + *************************************************************************/ + [ForeignKey("Company_Studios_Id")] + public virtual ICollection Studios { get; protected set; } - } + } } diff --git a/Jellyfin.Data/Entities/MusicAlbum.cs b/Jellyfin.Data/Entities/MusicAlbum.cs index 417f2595b..fc9c12286 100644 --- a/Jellyfin.Data/Entities/MusicAlbum.cs +++ b/Jellyfin.Data/Entities/MusicAlbum.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,64 +9,66 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class MusicAlbum: global::Jellyfin.Data.Entities.LibraryItem - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected MusicAlbum(): base() - { - MusicAlbumMetadata = new System.Collections.Generic.HashSet(); - Tracks = new System.Collections.Generic.HashSet(); + [Table("MusicAlbum")] + public partial class MusicAlbum : LibraryItem + { + partial void Init(); - Init(); - } + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected MusicAlbum() : base() + { + MusicAlbumMetadata = new HashSet(); + Tracks = new HashSet(); - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static MusicAlbum CreateMusicAlbumUnsafe() - { - return new MusicAlbum(); - } + Init(); + } - /// - /// 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) - { - this.UrlId = urlid; + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static MusicAlbum CreateMusicAlbumUnsafe() + { + return new MusicAlbum(); + } - this.MusicAlbumMetadata = new System.Collections.Generic.HashSet(); - this.Tracks = new System.Collections.Generic.HashSet(); + /// + /// 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) + { + this.UrlId = urlid; - Init(); - } + this.MusicAlbumMetadata = new HashSet(); + this.Tracks = new HashSet(); - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// This is whats gets displayed in the Urls and API requests. This could also be a string. - public static MusicAlbum Create(Guid urlid, DateTime dateadded) - { - return new MusicAlbum(urlid, dateadded); - } + Init(); + } - /************************************************************************* - * Properties - *************************************************************************/ + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + public static MusicAlbum Create(Guid urlid, DateTime dateadded) + { + return new MusicAlbum(urlid, dateadded); + } - /************************************************************************* - * Navigation properties - *************************************************************************/ + /************************************************************************* + * Properties + *************************************************************************/ - public virtual ICollection MusicAlbumMetadata { get; protected set; } + /************************************************************************* + * Navigation properties + *************************************************************************/ + [ForeignKey("MusicAlbumMetadata_MusicAlbumMetadata_Id")] + public virtual ICollection MusicAlbumMetadata { get; protected set; } - public virtual ICollection Tracks { get; protected set; } + [ForeignKey("Track_Tracks_Id")] + public virtual ICollection Tracks { get; protected set; } - } + } } diff --git a/Jellyfin.Data/Entities/MusicAlbumMetadata.cs b/Jellyfin.Data/Entities/MusicAlbumMetadata.cs index cd72ecba5..4bfe780d1 100644 --- a/Jellyfin.Data/Entities/MusicAlbumMetadata.cs +++ b/Jellyfin.Data/Entities/MusicAlbumMetadata.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,182 +9,184 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class MusicAlbumMetadata: global::Jellyfin.Data.Entities.Metadata - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected MusicAlbumMetadata(): base() - { - Labels = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static MusicAlbumMetadata CreateMusicAlbumMetadataUnsafe() - { - return new MusicAlbumMetadata(); - } - - /// - /// Public constructor with required data - /// - /// The title or name of the object - /// ISO-639-3 3-character language codes - /// - public MusicAlbumMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.MusicAlbum _musicalbum0) - { - if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); - this.Title = title; - - if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); - this.Language = language; - - if (_musicalbum0 == null) throw new ArgumentNullException(nameof(_musicalbum0)); - _musicalbum0.MusicAlbumMetadata.Add(this); - - this.Labels = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// The title or name of the object - /// ISO-639-3 3-character language codes - /// - public static MusicAlbumMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.MusicAlbum _musicalbum0) - { - return new MusicAlbumMetadata(title, language, dateadded, datemodified, _musicalbum0); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for Barcode - /// - protected string _Barcode; - /// - /// When provided in a partial class, allows value of Barcode to be changed before setting. - /// - partial void SetBarcode(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Barcode to be changed before returning. - /// - partial void GetBarcode(ref string result); - - /// - /// Max length = 255 - /// - [MaxLength(255)] - [StringLength(255)] - public string Barcode - { - get - { - string value = _Barcode; - GetBarcode(ref value); - return (_Barcode = value); - } - set - { - string oldValue = _Barcode; - SetBarcode(oldValue, ref value); - if (oldValue != value) + [Table("MusicAlbumMetadata")] + public partial class MusicAlbumMetadata : Metadata + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected MusicAlbumMetadata() : base() + { + Labels = new HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static MusicAlbumMetadata CreateMusicAlbumMetadataUnsafe() + { + return new MusicAlbumMetadata(); + } + + /// + /// Public constructor with required data + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public MusicAlbumMetadata(string title, string language, DateTime dateadded, DateTime datemodified, MusicAlbum _musicalbum0) + { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + this.Title = title; + + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; + + if (_musicalbum0 == null) throw new ArgumentNullException(nameof(_musicalbum0)); + _musicalbum0.MusicAlbumMetadata.Add(this); + + this.Labels = new HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public static MusicAlbumMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, MusicAlbum _musicalbum0) + { + return new MusicAlbumMetadata(title, language, dateadded, datemodified, _musicalbum0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Barcode + /// + protected string _Barcode; + /// + /// When provided in a partial class, allows value of Barcode to be changed before setting. + /// + partial void SetBarcode(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Barcode to be changed before returning. + /// + partial void GetBarcode(ref string result); + + /// + /// Max length = 255 + /// + [MaxLength(255)] + [StringLength(255)] + public string Barcode + { + get + { + string value = _Barcode; + GetBarcode(ref value); + return (_Barcode = value); + } + set + { + string oldValue = _Barcode; + SetBarcode(oldValue, ref value); + if (oldValue != value) + { + _Barcode = value; + } + } + } + + /// + /// Backing field for LabelNumber + /// + protected string _LabelNumber; + /// + /// When provided in a partial class, allows value of LabelNumber to be changed before setting. + /// + partial void SetLabelNumber(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of LabelNumber to be changed before returning. + /// + partial void GetLabelNumber(ref string result); + + /// + /// Max length = 255 + /// + [MaxLength(255)] + [StringLength(255)] + public string LabelNumber + { + get + { + string value = _LabelNumber; + GetLabelNumber(ref value); + return (_LabelNumber = value); + } + set { - _Barcode = value; + string oldValue = _LabelNumber; + SetLabelNumber(oldValue, ref value); + if (oldValue != value) + { + _LabelNumber = value; + } } - } - } - - /// - /// Backing field for LabelNumber - /// - protected string _LabelNumber; - /// - /// When provided in a partial class, allows value of LabelNumber to be changed before setting. - /// - partial void SetLabelNumber(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of LabelNumber to be changed before returning. - /// - partial void GetLabelNumber(ref string result); - - /// - /// Max length = 255 - /// - [MaxLength(255)] - [StringLength(255)] - public string LabelNumber - { - get - { - string value = _LabelNumber; - GetLabelNumber(ref value); - return (_LabelNumber = value); - } - set - { - string oldValue = _LabelNumber; - SetLabelNumber(oldValue, ref value); - if (oldValue != value) + } + + /// + /// Backing field for Country + /// + protected string _Country; + /// + /// When provided in a partial class, allows value of Country to be changed before setting. + /// + partial void SetCountry(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Country to be changed before returning. + /// + partial void GetCountry(ref string result); + + /// + /// Max length = 2 + /// + [MaxLength(2)] + [StringLength(2)] + public string Country + { + get { - _LabelNumber = value; + string value = _Country; + GetCountry(ref value); + return (_Country = value); } - } - } - - /// - /// Backing field for Country - /// - protected string _Country; - /// - /// When provided in a partial class, allows value of Country to be changed before setting. - /// - partial void SetCountry(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Country to be changed before returning. - /// - partial void GetCountry(ref string result); - - /// - /// Max length = 2 - /// - [MaxLength(2)] - [StringLength(2)] - public string Country - { - get - { - string value = _Country; - GetCountry(ref value); - return (_Country = value); - } - set - { - string oldValue = _Country; - SetCountry(oldValue, ref value); - if (oldValue != value) + set { - _Country = value; + string oldValue = _Country; + SetCountry(oldValue, ref value); + if (oldValue != value) + { + _Country = value; + } } - } - } + } - /************************************************************************* - * Navigation properties - *************************************************************************/ + /************************************************************************* + * Navigation properties + *************************************************************************/ - public virtual ICollection Labels { get; protected set; } + [ForeignKey("Company_Labels_Id")] + public virtual ICollection Labels { get; protected set; } - } + } } diff --git a/Jellyfin.Data/Entities/Permission.cs b/Jellyfin.Data/Entities/Permission.cs index a717fc83f..29ba9e1a4 100644 --- a/Jellyfin.Data/Entities/Permission.cs +++ b/Jellyfin.Data/Entities/Permission.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,132 +9,140 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class Permission - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Permission() - { - 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(global::Jellyfin.Data.Enums.PermissionKind kind, bool value, global::Jellyfin.Data.Entities.User _user0, global::Jellyfin.Data.Entities.Group _group1) - { - 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); - - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// - /// - /// - /// - public static Permission Create(global::Jellyfin.Data.Enums.PermissionKind kind, bool value, global::Jellyfin.Data.Entities.User _user0, global::Jellyfin.Data.Entities.Group _group1) - { - return new Permission(kind, value, _user0, _group1); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public int Id { get; protected set; } - - /// - /// Backing field for Kind - /// - protected global::Jellyfin.Data.Enums.PermissionKind _Kind; - /// - /// When provided in a partial class, allows value of Kind to be changed before setting. - /// - partial void SetKind(global::Jellyfin.Data.Enums.PermissionKind oldValue, ref global::Jellyfin.Data.Enums.PermissionKind newValue); - /// - /// When provided in a partial class, allows value of Kind to be changed before returning. - /// - partial void GetKind(ref global::Jellyfin.Data.Enums.PermissionKind result); - - /// - /// Required - /// - [Required] - public global::Jellyfin.Data.Enums.PermissionKind Kind - { - get - { - global::Jellyfin.Data.Enums.PermissionKind value = _Kind; - GetKind(ref value); - return (_Kind = value); - } - set - { - global::Jellyfin.Data.Enums.PermissionKind oldValue = _Kind; - SetKind(oldValue, ref value); - if (oldValue != value) + [Table("Permission")] + public partial class Permission + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Permission() + { + 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) + { + 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); + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + /// + /// + public static Permission Create(Enums.PermissionKind kind, bool value, User _user0, Group _group1) + { + return new Permission(kind, value, _user0, _group1); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; protected set; } + + /// + /// Backing field for Kind + /// + protected Enums.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); + /// + /// When provided in a partial class, allows value of Kind to be changed before returning. + /// + partial void GetKind(ref Enums.PermissionKind result); + + /// + /// Required + /// + [Required] + public Enums.PermissionKind Kind + { + get { - _Kind = value; - OnPropertyChanged(); + Enums.PermissionKind value = _Kind; + GetKind(ref value); + return (_Kind = value); } - } - } - - /// - /// Required - /// - [Required] - public bool Value { get; set; } - - /// - /// Concurrency token - /// - [Timestamp] - public Byte[] Timestamp { get; set; } - - /************************************************************************* - * Navigation properties - *************************************************************************/ - - public virtual event PropertyChangedEventHandler PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - } + set + { + Enums.PermissionKind oldValue = _Kind; + SetKind(oldValue, ref value); + if (oldValue != value) + { + _Kind = value; + OnPropertyChanged(); + } + } + } + + /// + /// Required + /// + [Required] + public bool Value { get; set; } + + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } + + public void OnSavingChanges() + { + RowVersion++; + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + public virtual event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + } } diff --git a/Jellyfin.Data/Entities/PermissionKind.cs b/Jellyfin.Data/Entities/PermissionKind.cs deleted file mode 100644 index 971298674..000000000 --- a/Jellyfin.Data/Entities/PermissionKind.cs +++ /dev/null @@ -1,40 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - -using System; - -namespace Jellyfin.Data.Enums -{ - public enum PermissionKind : Int32 - { - IsAdministrator, - IsHidden, - IsDisabled, - BlockUnrateditems, - EnbleSharedDeviceControl, - EnableRemoteAccess, - EnableLiveTvManagement, - EnableLiveTvAccess, - EnableMediaPlayback, - EnableAudioPlaybackTranscoding, - EnableVideoPlaybackTranscoding, - EnableContentDeletion, - EnableContentDownloading, - EnableSyncTranscoding, - EnableMediaConversion, - EnableAllDevices, - EnableAllChannels, - EnableAllFolders, - EnablePublicSharing, - AccessSchedules - } -} diff --git a/Jellyfin.Data/Entities/Person.cs b/Jellyfin.Data/Entities/Person.cs index 3437b9581..6a4ad5285 100644 --- a/Jellyfin.Data/Entities/Person.cs +++ b/Jellyfin.Data/Entities/Person.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,292 +9,299 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class Person - { - partial void Init(); + [Table("Person")] + public partial class Person + { + partial void Init(); - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Person() - { - Sources = new System.Collections.Generic.HashSet(); + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Person() + { + Sources = new HashSet(); - Init(); - } + Init(); + } - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static Person CreatePersonUnsafe() - { - return new Person(); - } + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Person CreatePersonUnsafe() + { + return new Person(); + } - /// - /// Public constructor with required data - /// - /// - /// - public Person(Guid urlid, string name, DateTime dateadded, DateTime datemodified) - { - this.UrlId = urlid; + /// + /// Public constructor with required data + /// + /// + /// + public Person(Guid urlid, string name, DateTime dateadded, DateTime datemodified) + { + this.UrlId = urlid; - if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); - this.Name = name; + if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); + this.Name = name; - this.Sources = new System.Collections.Generic.HashSet(); + this.Sources = new HashSet(); - Init(); - } + Init(); + } - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// - /// - public static Person Create(Guid urlid, string name, DateTime dateadded, DateTime datemodified) - { - return new Person(urlid, name, dateadded, datemodified); - } + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + public static Person Create(Guid urlid, string name, DateTime dateadded, DateTime datemodified) + { + return new Person(urlid, name, dateadded, datemodified); + } - /************************************************************************* - * Properties - *************************************************************************/ + /************************************************************************* + * Properties + *************************************************************************/ - /// - /// Backing field for Id - /// - internal int _Id; - /// - /// When provided in a partial class, allows value of Id to be changed before setting. - /// - partial void SetId(int oldValue, ref int newValue); - /// - /// When provided in a partial class, allows value of Id to be changed before returning. - /// - partial void GetId(ref int result); + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public int Id - { - get - { - int value = _Id; - GetId(ref value); - return (_Id = value); - } - protected set - { - int oldValue = _Id; - SetId(oldValue, ref value); - if (oldValue != value) + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set { - _Id = value; + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } } - } - } + } - /// - /// Backing field for UrlId - /// - protected Guid _UrlId; - /// - /// When provided in a partial class, allows value of UrlId to be changed before setting. - /// - partial void SetUrlId(Guid oldValue, ref Guid newValue); - /// - /// When provided in a partial class, allows value of UrlId to be changed before returning. - /// - partial void GetUrlId(ref Guid result); + /// + /// Backing field for UrlId + /// + protected Guid _UrlId; + /// + /// When provided in a partial class, allows value of UrlId to be changed before setting. + /// + partial void SetUrlId(Guid oldValue, ref Guid newValue); + /// + /// When provided in a partial class, allows value of UrlId to be changed before returning. + /// + partial void GetUrlId(ref Guid result); - /// - /// Required - /// - [Required] - public Guid UrlId - { - get - { - Guid value = _UrlId; - GetUrlId(ref value); - return (_UrlId = value); - } - set - { - Guid oldValue = _UrlId; - SetUrlId(oldValue, ref value); - if (oldValue != value) + /// + /// Required + /// + [Required] + public Guid UrlId + { + get { - _UrlId = value; + Guid value = _UrlId; + GetUrlId(ref value); + return (_UrlId = value); } - } - } + set + { + Guid oldValue = _UrlId; + SetUrlId(oldValue, ref value); + if (oldValue != value) + { + _UrlId = value; + } + } + } - /// - /// Backing field for Name - /// - protected string _Name; - /// - /// When provided in a partial class, allows value of Name to be changed before setting. - /// - partial void SetName(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Name to be changed before returning. - /// - partial void GetName(ref string result); + /// + /// Backing field for Name + /// + protected string _Name; + /// + /// When provided in a partial class, allows value of Name to be changed before setting. + /// + partial void SetName(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Name to be changed before returning. + /// + partial void GetName(ref string result); - /// - /// Required, Max length = 1024 - /// - [Required] - [MaxLength(1024)] - [StringLength(1024)] - public string Name - { - get - { - string value = _Name; - GetName(ref value); - return (_Name = value); - } - set - { - string oldValue = _Name; - SetName(oldValue, ref value); - if (oldValue != value) + /// + /// Required, Max length = 1024 + /// + [Required] + [MaxLength(1024)] + [StringLength(1024)] + public string Name + { + get + { + string value = _Name; + GetName(ref value); + return (_Name = value); + } + set { - _Name = value; + string oldValue = _Name; + SetName(oldValue, ref value); + if (oldValue != value) + { + _Name = value; + } } - } - } + } - /// - /// Backing field for SourceId - /// - protected string _SourceId; - /// - /// When provided in a partial class, allows value of SourceId to be changed before setting. - /// - partial void SetSourceId(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of SourceId to be changed before returning. - /// - partial void GetSourceId(ref string result); + /// + /// Backing field for SourceId + /// + protected string _SourceId; + /// + /// When provided in a partial class, allows value of SourceId to be changed before setting. + /// + partial void SetSourceId(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of SourceId to be changed before returning. + /// + partial void GetSourceId(ref string result); - /// - /// Max length = 255 - /// - [MaxLength(255)] - [StringLength(255)] - public string SourceId - { - get - { - string value = _SourceId; - GetSourceId(ref value); - return (_SourceId = value); - } - set - { - string oldValue = _SourceId; - SetSourceId(oldValue, ref value); - if (oldValue != value) + /// + /// Max length = 255 + /// + [MaxLength(255)] + [StringLength(255)] + public string SourceId + { + get { - _SourceId = value; + string value = _SourceId; + GetSourceId(ref value); + return (_SourceId = value); } - } - } + set + { + string oldValue = _SourceId; + SetSourceId(oldValue, ref value); + if (oldValue != value) + { + _SourceId = value; + } + } + } - /// - /// Backing field for DateAdded - /// - protected DateTime _DateAdded; - /// - /// When provided in a partial class, allows value of DateAdded to be changed before setting. - /// - partial void SetDateAdded(DateTime oldValue, ref DateTime newValue); - /// - /// When provided in a partial class, allows value of DateAdded to be changed before returning. - /// - partial void GetDateAdded(ref DateTime result); + /// + /// Backing field for DateAdded + /// + protected DateTime _DateAdded; + /// + /// When provided in a partial class, allows value of DateAdded to be changed before setting. + /// + partial void SetDateAdded(DateTime oldValue, ref DateTime newValue); + /// + /// When provided in a partial class, allows value of DateAdded to be changed before returning. + /// + partial void GetDateAdded(ref DateTime result); - /// - /// Required - /// - [Required] - public DateTime DateAdded - { - get - { - DateTime value = _DateAdded; - GetDateAdded(ref value); - return (_DateAdded = value); - } - internal set - { - DateTime oldValue = _DateAdded; - SetDateAdded(oldValue, ref value); - if (oldValue != value) + /// + /// Required + /// + [Required] + public DateTime DateAdded + { + get + { + DateTime value = _DateAdded; + GetDateAdded(ref value); + return (_DateAdded = value); + } + internal set { - _DateAdded = value; + DateTime oldValue = _DateAdded; + SetDateAdded(oldValue, ref value); + if (oldValue != value) + { + _DateAdded = value; + } } - } - } + } - /// - /// Backing field for DateModified - /// - protected DateTime _DateModified; - /// - /// When provided in a partial class, allows value of DateModified to be changed before setting. - /// - partial void SetDateModified(DateTime oldValue, ref DateTime newValue); - /// - /// When provided in a partial class, allows value of DateModified to be changed before returning. - /// - partial void GetDateModified(ref DateTime result); + /// + /// Backing field for DateModified + /// + protected DateTime _DateModified; + /// + /// When provided in a partial class, allows value of DateModified to be changed before setting. + /// + partial void SetDateModified(DateTime oldValue, ref DateTime newValue); + /// + /// When provided in a partial class, allows value of DateModified to be changed before returning. + /// + partial void GetDateModified(ref DateTime result); - /// - /// Required - /// - [Required] - public DateTime DateModified - { - get - { - DateTime value = _DateModified; - GetDateModified(ref value); - return (_DateModified = value); - } - internal set - { - DateTime oldValue = _DateModified; - SetDateModified(oldValue, ref value); - if (oldValue != value) + /// + /// Required + /// + [Required] + public DateTime DateModified + { + get + { + DateTime value = _DateModified; + GetDateModified(ref value); + return (_DateModified = value); + } + internal set { - _DateModified = value; + DateTime oldValue = _DateModified; + SetDateModified(oldValue, ref value); + if (oldValue != value) + { + _DateModified = value; + } } - } - } + } - /// - /// Required - /// - [ConcurrencyCheck] - [Required] - public byte[] Timestamp { get; set; } + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } - /************************************************************************* - * Navigation properties - *************************************************************************/ + public void OnSavingChanges() + { + RowVersion++; + } - public virtual ICollection Sources { get; protected set; } + /************************************************************************* + * Navigation properties + *************************************************************************/ + [ForeignKey("MetadataProviderId_Sources_Id")] + public virtual ICollection Sources { get; protected set; } - } + } } diff --git a/Jellyfin.Data/Entities/PersonRole.cs b/Jellyfin.Data/Entities/PersonRole.cs index d8e2dbc11..0c6cb2c6e 100644 --- a/Jellyfin.Data/Entities/PersonRole.cs +++ b/Jellyfin.Data/Entities/PersonRole.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,195 +9,206 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class PersonRole - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected PersonRole() - { - // NOTE: This class has one-to-one associations with PersonRole. - // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. - - Sources = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static PersonRole CreatePersonRoleUnsafe() - { - return new PersonRole(); - } - - /// - /// Public constructor with required data - /// - /// - /// - public PersonRole(global::Jellyfin.Data.Enums.PersonRoleType type, global::Jellyfin.Data.Entities.Metadata _metadata0) - { - // NOTE: This class has one-to-one associations with PersonRole. - // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. - - this.Type = type; - - if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0)); - _metadata0.PersonRoles.Add(this); - - this.Sources = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// - /// - public static PersonRole Create(global::Jellyfin.Data.Enums.PersonRoleType type, global::Jellyfin.Data.Entities.Metadata _metadata0) - { - return new PersonRole(type, _metadata0); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for Id - /// - internal int _Id; - /// - /// When provided in a partial class, allows value of Id to be changed before setting. - /// - partial void SetId(int oldValue, ref int newValue); - /// - /// When provided in a partial class, allows value of Id to be changed before returning. - /// - partial void GetId(ref int result); - - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public int Id - { - get - { - int value = _Id; - GetId(ref value); - return (_Id = value); - } - protected set - { - int oldValue = _Id; - SetId(oldValue, ref value); - if (oldValue != value) + [Table("PersonRole")] + public partial class PersonRole + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected PersonRole() + { + // NOTE: This class has one-to-one associations with PersonRole. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + Sources = new HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static PersonRole CreatePersonRoleUnsafe() + { + return new PersonRole(); + } + + /// + /// Public constructor with required data + /// + /// + /// + public PersonRole(Enums.PersonRoleType type, Metadata _metadata0) + { + // NOTE: This class has one-to-one associations with PersonRole. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + this.Type = type; + + if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0)); + _metadata0.PersonRoles.Add(this); + + this.Sources = new HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + public static PersonRole Create(Enums.PersonRoleType type, Metadata _metadata0) + { + return new PersonRole(type, _metadata0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for Role + /// + protected string _Role; + /// + /// When provided in a partial class, allows value of Role to be changed before setting. + /// + partial void SetRole(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Role to be changed before returning. + /// + partial void GetRole(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Role + { + get { - _Id = value; + string value = _Role; + GetRole(ref value); + return (_Role = value); } - } - } - - /// - /// Backing field for Role - /// - protected string _Role; - /// - /// When provided in a partial class, allows value of Role to be changed before setting. - /// - partial void SetRole(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Role to be changed before returning. - /// - partial void GetRole(ref string result); - - /// - /// Max length = 1024 - /// - [MaxLength(1024)] - [StringLength(1024)] - public string Role - { - get - { - string value = _Role; - GetRole(ref value); - return (_Role = value); - } - set - { - string oldValue = _Role; - SetRole(oldValue, ref value); - if (oldValue != value) + set { - _Role = value; + string oldValue = _Role; + SetRole(oldValue, ref value); + if (oldValue != value) + { + _Role = value; + } } - } - } - - /// - /// Backing field for Type - /// - protected global::Jellyfin.Data.Enums.PersonRoleType _Type; - /// - /// When provided in a partial class, allows value of Type to be changed before setting. - /// - partial void SetType(global::Jellyfin.Data.Enums.PersonRoleType oldValue, ref global::Jellyfin.Data.Enums.PersonRoleType newValue); - /// - /// When provided in a partial class, allows value of Type to be changed before returning. - /// - partial void GetType(ref global::Jellyfin.Data.Enums.PersonRoleType result); - - /// - /// Required - /// - [Required] - public global::Jellyfin.Data.Enums.PersonRoleType Type - { - get - { - global::Jellyfin.Data.Enums.PersonRoleType value = _Type; - GetType(ref value); - return (_Type = value); - } - set - { - global::Jellyfin.Data.Enums.PersonRoleType oldValue = _Type; - SetType(oldValue, ref value); - if (oldValue != value) + } + + /// + /// Backing field for Type + /// + protected Enums.PersonRoleType _Type; + /// + /// When provided in a partial class, allows value of Type to be changed before setting. + /// + partial void SetType(Enums.PersonRoleType oldValue, ref Enums.PersonRoleType newValue); + /// + /// When provided in a partial class, allows value of Type to be changed before returning. + /// + partial void GetType(ref Enums.PersonRoleType result); + + /// + /// Required + /// + [Required] + public Enums.PersonRoleType Type + { + get { - _Type = value; + Enums.PersonRoleType value = _Type; + GetType(ref value); + return (_Type = value); } - } - } + set + { + Enums.PersonRoleType oldValue = _Type; + SetType(oldValue, ref value); + if (oldValue != value) + { + _Type = value; + } + } + } + + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } + + public void OnSavingChanges() + { + RowVersion++; + } - /// - /// Required - /// - [ConcurrencyCheck] - [Required] - public byte[] Timestamp { get; set; } + /************************************************************************* + * Navigation properties + *************************************************************************/ - /************************************************************************* - * Navigation properties - *************************************************************************/ + /// + /// Required + /// + [ForeignKey("Person_Id")] - /// - /// Required - /// - public virtual global::Jellyfin.Data.Entities.Person Person { get; set; } + public virtual Person Person { get; set; } - public virtual global::Jellyfin.Data.Entities.Artwork Artwork { get; set; } + [ForeignKey("Artwork_Artwork_Id")] + public virtual Artwork Artwork { get; set; } - public virtual ICollection Sources { get; protected set; } + [ForeignKey("MetadataProviderId_Sources_Id")] + public virtual ICollection Sources { get; protected set; } - } + } } diff --git a/Jellyfin.Data/Entities/Photo.cs b/Jellyfin.Data/Entities/Photo.cs index 16c97fef5..89f976444 100644 --- a/Jellyfin.Data/Entities/Photo.cs +++ b/Jellyfin.Data/Entities/Photo.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,64 +9,66 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class Photo: global::Jellyfin.Data.Entities.LibraryItem - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Photo(): base() - { - PhotoMetadata = new System.Collections.Generic.HashSet(); - Releases = new System.Collections.Generic.HashSet(); + [Table("Photo")] + public partial class Photo : LibraryItem + { + partial void Init(); - Init(); - } + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Photo() : base() + { + PhotoMetadata = new HashSet(); + Releases = new HashSet(); - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static Photo CreatePhotoUnsafe() - { - return new Photo(); - } + Init(); + } - /// - /// 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) - { - this.UrlId = urlid; + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Photo CreatePhotoUnsafe() + { + return new Photo(); + } - this.PhotoMetadata = new System.Collections.Generic.HashSet(); - this.Releases = new System.Collections.Generic.HashSet(); + /// + /// 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) + { + this.UrlId = urlid; - Init(); - } + this.PhotoMetadata = new HashSet(); + this.Releases = new HashSet(); - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// This is whats gets displayed in the Urls and API requests. This could also be a string. - public static Photo Create(Guid urlid, DateTime dateadded) - { - return new Photo(urlid, dateadded); - } + Init(); + } - /************************************************************************* - * Properties - *************************************************************************/ + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + public static Photo Create(Guid urlid, DateTime dateadded) + { + return new Photo(urlid, dateadded); + } - /************************************************************************* - * Navigation properties - *************************************************************************/ + /************************************************************************* + * Properties + *************************************************************************/ - public virtual ICollection PhotoMetadata { get; protected set; } + /************************************************************************* + * Navigation properties + *************************************************************************/ + [ForeignKey("PhotoMetadata_PhotoMetadata_Id")] + public virtual ICollection PhotoMetadata { get; protected set; } - public virtual ICollection Releases { get; protected set; } + [ForeignKey("Release_Releases_Id")] + public virtual ICollection Releases { get; protected set; } - } + } } diff --git a/Jellyfin.Data/Entities/PhotoMetadata.cs b/Jellyfin.Data/Entities/PhotoMetadata.cs index 9c47d022e..b3f796839 100644 --- a/Jellyfin.Data/Entities/PhotoMetadata.cs +++ b/Jellyfin.Data/Entities/PhotoMetadata.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,66 +9,67 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class PhotoMetadata: global::Jellyfin.Data.Entities.Metadata - { - partial void Init(); + [Table("PhotoMetadata")] + public partial class PhotoMetadata : Metadata + { + partial void Init(); - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected PhotoMetadata(): base() - { - Init(); - } + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected PhotoMetadata() : base() + { + Init(); + } - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static PhotoMetadata CreatePhotoMetadataUnsafe() - { - return new PhotoMetadata(); - } + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static PhotoMetadata CreatePhotoMetadataUnsafe() + { + return new PhotoMetadata(); + } - /// - /// Public constructor with required data - /// - /// The title or name of the object - /// ISO-639-3 3-character language codes - /// - public PhotoMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Photo _photo0) - { - if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); - this.Title = title; + /// + /// Public constructor with required data + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public PhotoMetadata(string title, string language, DateTime dateadded, DateTime datemodified, Photo _photo0) + { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + this.Title = title; - if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); - this.Language = language; + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; - if (_photo0 == null) throw new ArgumentNullException(nameof(_photo0)); - _photo0.PhotoMetadata.Add(this); + if (_photo0 == null) throw new ArgumentNullException(nameof(_photo0)); + _photo0.PhotoMetadata.Add(this); - Init(); - } + Init(); + } - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// The title or name of the object - /// ISO-639-3 3-character language codes - /// - public static PhotoMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Photo _photo0) - { - return new PhotoMetadata(title, language, dateadded, datemodified, _photo0); - } + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public static PhotoMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, Photo _photo0) + { + return new PhotoMetadata(title, language, dateadded, datemodified, _photo0); + } - /************************************************************************* - * Properties - *************************************************************************/ + /************************************************************************* + * Properties + *************************************************************************/ - /************************************************************************* - * Navigation properties - *************************************************************************/ + /************************************************************************* + * Navigation properties + *************************************************************************/ - } + } } diff --git a/Jellyfin.Data/Entities/Preference.cs b/Jellyfin.Data/Entities/Preference.cs index 3d69ea2f3..8b3ddb568 100644 --- a/Jellyfin.Data/Entities/Preference.cs +++ b/Jellyfin.Data/Entities/Preference.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,97 +9,105 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class Preference - { - 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. - /// - public static Preference CreatePreferenceUnsafe() - { - return new Preference(); - } - - /// - /// Public constructor with required data - /// - /// - /// - /// - /// - public Preference(global::Jellyfin.Data.Enums.PreferenceKind kind, string value, global::Jellyfin.Data.Entities.User _user0, global::Jellyfin.Data.Entities.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.) - /// - /// - /// - /// - /// - public static Preference Create(global::Jellyfin.Data.Enums.PreferenceKind kind, string value, global::Jellyfin.Data.Entities.User _user0, global::Jellyfin.Data.Entities.Group _group1) - { - return new Preference(kind, value, _user0, _group1); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public int Id { get; protected set; } - - /// - /// Required - /// - [Required] - public global::Jellyfin.Data.Enums.PreferenceKind Kind { get; set; } - - /// - /// Required, Max length = 65535 - /// - [Required] - [MaxLength(65535)] - [StringLength(65535)] - public string Value { get; set; } - - /// - /// Concurrency token - /// - [Timestamp] - public Byte[] Timestamp { get; set; } - - /************************************************************************* - * Navigation properties - *************************************************************************/ - - } + [Table("Preference")] + public partial class Preference + { + 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. + /// + public static Preference CreatePreferenceUnsafe() + { + return new Preference(); + } + + /// + /// 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.) + /// + /// + /// + /// + /// + public static Preference Create(Enums.PreferenceKind kind, string value, User _user0, Group _group1) + { + return new Preference(kind, value, _user0, _group1); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; protected set; } + + /// + /// Required + /// + [Required] + public Enums.PreferenceKind Kind { get; set; } + + /// + /// Required, Max length = 65535 + /// + [Required] + [MaxLength(65535)] + [StringLength(65535)] + public string Value { get; set; } + + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } + + public void OnSavingChanges() + { + RowVersion++; + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + } } diff --git a/Jellyfin.Data/Entities/PreferenceKind.cs b/Jellyfin.Data/Entities/PreferenceKind.cs deleted file mode 100644 index e6673afb1..000000000 --- a/Jellyfin.Data/Entities/PreferenceKind.cs +++ /dev/null @@ -1,27 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - -using System; - -namespace Jellyfin.Data.Enums -{ - public enum PreferenceKind : Int32 - { - MaxParentalRating, - BlockedTags, - RemoteClientBitrateLimit, - EnabledDevices, - EnabledChannels, - EnabledFolders, - EnableContentDeletionFromFolders - } -} diff --git a/Jellyfin.Data/Entities/ProviderMapping.cs b/Jellyfin.Data/Entities/ProviderMapping.cs index e50a01489..0eb098a8f 100644 --- a/Jellyfin.Data/Entities/ProviderMapping.cs +++ b/Jellyfin.Data/Entities/ProviderMapping.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,113 +9,121 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class ProviderMapping - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected ProviderMapping() - { - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static ProviderMapping CreateProviderMappingUnsafe() - { - return new ProviderMapping(); - } - - /// - /// Public constructor with required data - /// - /// - /// - /// - /// - /// - public ProviderMapping(string providername, string providersecrets, string providerdata, global::Jellyfin.Data.Entities.User _user0, global::Jellyfin.Data.Entities.Group _group1) - { - if (string.IsNullOrEmpty(providername)) throw new ArgumentNullException(nameof(providername)); - this.ProviderName = providername; - - if (string.IsNullOrEmpty(providersecrets)) throw new ArgumentNullException(nameof(providersecrets)); - this.ProviderSecrets = providersecrets; - - if (string.IsNullOrEmpty(providerdata)) throw new ArgumentNullException(nameof(providerdata)); - this.ProviderData = providerdata; - - if (_user0 == null) throw new ArgumentNullException(nameof(_user0)); - _user0.ProviderMappings.Add(this); - - if (_group1 == null) throw new ArgumentNullException(nameof(_group1)); - _group1.ProviderMappings.Add(this); - - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// - /// - /// - /// - /// - public static ProviderMapping Create(string providername, string providersecrets, string providerdata, global::Jellyfin.Data.Entities.User _user0, global::Jellyfin.Data.Entities.Group _group1) - { - return new ProviderMapping(providername, providersecrets, providerdata, _user0, _group1); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public int Id { get; protected set; } - - /// - /// Required, Max length = 255 - /// - [Required] - [MaxLength(255)] - [StringLength(255)] - public string ProviderName { get; set; } - - /// - /// Required, Max length = 65535 - /// - [Required] - [MaxLength(65535)] - [StringLength(65535)] - public string ProviderSecrets { get; set; } - - /// - /// Required, Max length = 65535 - /// - [Required] - [MaxLength(65535)] - [StringLength(65535)] - public string ProviderData { get; set; } - - /// - /// Concurrency token - /// - [Timestamp] - public Byte[] Timestamp { get; set; } - - /************************************************************************* - * Navigation properties - *************************************************************************/ - - } + [Table("ProviderMapping")] + public partial class ProviderMapping + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected ProviderMapping() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static ProviderMapping CreateProviderMappingUnsafe() + { + return new ProviderMapping(); + } + + /// + /// Public constructor with required data + /// + /// + /// + /// + /// + /// + public ProviderMapping(string providername, string providersecrets, string providerdata, User _user0, Group _group1) + { + if (string.IsNullOrEmpty(providername)) throw new ArgumentNullException(nameof(providername)); + this.ProviderName = providername; + + if (string.IsNullOrEmpty(providersecrets)) throw new ArgumentNullException(nameof(providersecrets)); + this.ProviderSecrets = providersecrets; + + if (string.IsNullOrEmpty(providerdata)) throw new ArgumentNullException(nameof(providerdata)); + this.ProviderData = providerdata; + + if (_user0 == null) throw new ArgumentNullException(nameof(_user0)); + _user0.ProviderMappings.Add(this); + + if (_group1 == null) throw new ArgumentNullException(nameof(_group1)); + _group1.ProviderMappings.Add(this); + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + /// + /// + /// + public static ProviderMapping Create(string providername, string providersecrets, string providerdata, User _user0, Group _group1) + { + return new ProviderMapping(providername, providersecrets, providerdata, _user0, _group1); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; protected set; } + + /// + /// Required, Max length = 255 + /// + [Required] + [MaxLength(255)] + [StringLength(255)] + public string ProviderName { get; set; } + + /// + /// Required, Max length = 65535 + /// + [Required] + [MaxLength(65535)] + [StringLength(65535)] + public string ProviderSecrets { get; set; } + + /// + /// Required, Max length = 65535 + /// + [Required] + [MaxLength(65535)] + [StringLength(65535)] + public string ProviderData { get; set; } + + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } + + public void OnSavingChanges() + { + RowVersion++; + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + } } diff --git a/Jellyfin.Data/Entities/Rating.cs b/Jellyfin.Data/Entities/Rating.cs index b1098a1d7..46e8c3f11 100644 --- a/Jellyfin.Data/Entities/Rating.cs +++ b/Jellyfin.Data/Entities/Rating.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,177 +9,185 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class Rating - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Rating() - { - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static Rating CreateRatingUnsafe() - { - return new Rating(); - } - - /// - /// Public constructor with required data - /// - /// - /// - public Rating(double value, global::Jellyfin.Data.Entities.Metadata _metadata0) - { - this.Value = value; - - if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0)); - _metadata0.Ratings.Add(this); - - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// - /// - public static Rating Create(double value, global::Jellyfin.Data.Entities.Metadata _metadata0) - { - return new Rating(value, _metadata0); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for Id - /// - internal int _Id; - /// - /// When provided in a partial class, allows value of Id to be changed before setting. - /// - partial void SetId(int oldValue, ref int newValue); - /// - /// When provided in a partial class, allows value of Id to be changed before returning. - /// - partial void GetId(ref int result); - - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public int Id - { - get - { - int value = _Id; - GetId(ref value); - return (_Id = value); - } - protected set - { - int oldValue = _Id; - SetId(oldValue, ref value); - if (oldValue != value) + [Table("Rating")] + public partial class Rating + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Rating() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Rating CreateRatingUnsafe() + { + return new Rating(); + } + + /// + /// Public constructor with required data + /// + /// + /// + public Rating(double value, Metadata _metadata0) + { + this.Value = value; + + if (_metadata0 == null) throw new ArgumentNullException(nameof(_metadata0)); + _metadata0.Ratings.Add(this); + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + public static Rating Create(double value, Metadata _metadata0) + { + return new Rating(value, _metadata0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id + { + get + { + int value = _Id; + GetId(ref value); + return (_Id = value); + } + protected set + { + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } + } + } + + /// + /// Backing field for Value + /// + protected double _Value; + /// + /// When provided in a partial class, allows value of Value to be changed before setting. + /// + partial void SetValue(double oldValue, ref double newValue); + /// + /// When provided in a partial class, allows value of Value to be changed before returning. + /// + partial void GetValue(ref double result); + + /// + /// Required + /// + [Required] + public double Value + { + get + { + double value = _Value; + GetValue(ref value); + return (_Value = value); + } + set { - _Id = value; + double oldValue = _Value; + SetValue(oldValue, ref value); + if (oldValue != value) + { + _Value = value; + } } - } - } - - /// - /// Backing field for Value - /// - protected double _Value; - /// - /// When provided in a partial class, allows value of Value to be changed before setting. - /// - partial void SetValue(double oldValue, ref double newValue); - /// - /// When provided in a partial class, allows value of Value to be changed before returning. - /// - partial void GetValue(ref double result); - - /// - /// Required - /// - [Required] - public double Value - { - get - { - double value = _Value; - GetValue(ref value); - return (_Value = value); - } - set - { - double oldValue = _Value; - SetValue(oldValue, ref value); - if (oldValue != value) + } + + /// + /// Backing field for Votes + /// + protected int? _Votes; + /// + /// When provided in a partial class, allows value of Votes to be changed before setting. + /// + partial void SetVotes(int? oldValue, ref int? newValue); + /// + /// When provided in a partial class, allows value of Votes to be changed before returning. + /// + partial void GetVotes(ref int? result); + + public int? Votes + { + get { - _Value = value; + int? value = _Votes; + GetVotes(ref value); + return (_Votes = value); } - } - } - - /// - /// Backing field for Votes - /// - protected int? _Votes; - /// - /// When provided in a partial class, allows value of Votes to be changed before setting. - /// - partial void SetVotes(int? oldValue, ref int? newValue); - /// - /// When provided in a partial class, allows value of Votes to be changed before returning. - /// - partial void GetVotes(ref int? result); - - public int? Votes - { - get - { - int? value = _Votes; - GetVotes(ref value); - return (_Votes = value); - } - set - { - int? oldValue = _Votes; - SetVotes(oldValue, ref value); - if (oldValue != value) + set { - _Votes = value; + int? oldValue = _Votes; + SetVotes(oldValue, ref value); + if (oldValue != value) + { + _Votes = value; + } } - } - } - - /// - /// Required - /// - [ConcurrencyCheck] - [Required] - public byte[] Timestamp { get; set; } - - /************************************************************************* - * Navigation properties - *************************************************************************/ - - /// - /// If this is NULL it's the internal user rating. - /// - public virtual global::Jellyfin.Data.Entities.RatingSource RatingType { get; set; } - - } + } + + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } + + public void OnSavingChanges() + { + RowVersion++; + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + + /// + /// If this is NULL it's the internal user rating. + /// + [ForeignKey("RatingSource_RatingType_Id")] + public virtual RatingSource RatingType { get; set; } + + } } diff --git a/Jellyfin.Data/Entities/RatingSource.cs b/Jellyfin.Data/Entities/RatingSource.cs index 32d5634c2..7e60fac43 100644 --- a/Jellyfin.Data/Entities/RatingSource.cs +++ b/Jellyfin.Data/Entities/RatingSource.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,222 +9,229 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - /// - /// This is the entity to store review ratings, not age ratings - /// - public partial class RatingSource - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected RatingSource() - { - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static RatingSource CreateRatingSourceUnsafe() - { - return new RatingSource(); - } - - /// - /// Public constructor with required data - /// - /// - /// - /// - public RatingSource(double maximumvalue, double minimumvalue, global::Jellyfin.Data.Entities.Rating _rating0) - { - this.MaximumValue = maximumvalue; - - this.MinimumValue = minimumvalue; - - if (_rating0 == null) throw new ArgumentNullException(nameof(_rating0)); - _rating0.RatingType = this; - - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// - /// - /// - public static RatingSource Create(double maximumvalue, double minimumvalue, global::Jellyfin.Data.Entities.Rating _rating0) - { - return new RatingSource(maximumvalue, minimumvalue, _rating0); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for Id - /// - internal int _Id; - /// - /// When provided in a partial class, allows value of Id to be changed before setting. - /// - partial void SetId(int oldValue, ref int newValue); - /// - /// When provided in a partial class, allows value of Id to be changed before returning. - /// - partial void GetId(ref int result); - - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public int Id - { - get - { - int value = _Id; - GetId(ref value); - return (_Id = value); - } - protected set - { - int oldValue = _Id; - SetId(oldValue, ref value); - if (oldValue != value) + /// + /// This is the entity to store review ratings, not age ratings + /// + [Table("RatingSource")] + public partial class RatingSource + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected RatingSource() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static RatingSource CreateRatingSourceUnsafe() + { + return new RatingSource(); + } + + /// + /// Public constructor with required data + /// + /// + /// + /// + public RatingSource(double maximumvalue, double minimumvalue, Rating _rating0) + { + this.MaximumValue = maximumvalue; + + this.MinimumValue = minimumvalue; + + if (_rating0 == null) throw new ArgumentNullException(nameof(_rating0)); + _rating0.RatingType = this; + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + /// + public static RatingSource Create(double maximumvalue, double minimumvalue, Rating _rating0) + { + return new RatingSource(maximumvalue, minimumvalue, _rating0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id + { + get { - _Id = value; + int value = _Id; + GetId(ref value); + return (_Id = value); } - } - } - - /// - /// Backing field for Name - /// - protected string _Name; - /// - /// When provided in a partial class, allows value of Name to be changed before setting. - /// - partial void SetName(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Name to be changed before returning. - /// - partial void GetName(ref string result); - - /// - /// Max length = 1024 - /// - [MaxLength(1024)] - [StringLength(1024)] - public string Name - { - get - { - string value = _Name; - GetName(ref value); - return (_Name = value); - } - set - { - string oldValue = _Name; - SetName(oldValue, ref value); - if (oldValue != value) + protected set { - _Name = value; + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } } - } - } - - /// - /// Backing field for MaximumValue - /// - protected double _MaximumValue; - /// - /// When provided in a partial class, allows value of MaximumValue to be changed before setting. - /// - partial void SetMaximumValue(double oldValue, ref double newValue); - /// - /// When provided in a partial class, allows value of MaximumValue to be changed before returning. - /// - partial void GetMaximumValue(ref double result); - - /// - /// Required - /// - [Required] - public double MaximumValue - { - get - { - double value = _MaximumValue; - GetMaximumValue(ref value); - return (_MaximumValue = value); - } - set - { - double oldValue = _MaximumValue; - SetMaximumValue(oldValue, ref value); - if (oldValue != value) + } + + /// + /// Backing field for Name + /// + protected string _Name; + /// + /// When provided in a partial class, allows value of Name to be changed before setting. + /// + partial void SetName(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Name to be changed before returning. + /// + partial void GetName(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Name + { + get { - _MaximumValue = value; + string value = _Name; + GetName(ref value); + return (_Name = value); } - } - } - - /// - /// Backing field for MinimumValue - /// - protected double _MinimumValue; - /// - /// When provided in a partial class, allows value of MinimumValue to be changed before setting. - /// - partial void SetMinimumValue(double oldValue, ref double newValue); - /// - /// When provided in a partial class, allows value of MinimumValue to be changed before returning. - /// - partial void GetMinimumValue(ref double result); - - /// - /// Required - /// - [Required] - public double MinimumValue - { - get - { - double value = _MinimumValue; - GetMinimumValue(ref value); - return (_MinimumValue = value); - } - set - { - double oldValue = _MinimumValue; - SetMinimumValue(oldValue, ref value); - if (oldValue != value) + set { - _MinimumValue = value; + string oldValue = _Name; + SetName(oldValue, ref value); + if (oldValue != value) + { + _Name = value; + } } - } - } - - /// - /// Required - /// - [ConcurrencyCheck] - [Required] - public byte[] Timestamp { get; set; } - - /************************************************************************* - * Navigation properties - *************************************************************************/ - - public virtual global::Jellyfin.Data.Entities.MetadataProviderId Source { get; set; } - - } + } + + /// + /// Backing field for MaximumValue + /// + protected double _MaximumValue; + /// + /// When provided in a partial class, allows value of MaximumValue to be changed before setting. + /// + partial void SetMaximumValue(double oldValue, ref double newValue); + /// + /// When provided in a partial class, allows value of MaximumValue to be changed before returning. + /// + partial void GetMaximumValue(ref double result); + + /// + /// Required + /// + [Required] + public double MaximumValue + { + get + { + double value = _MaximumValue; + GetMaximumValue(ref value); + return (_MaximumValue = value); + } + set + { + double oldValue = _MaximumValue; + SetMaximumValue(oldValue, ref value); + if (oldValue != value) + { + _MaximumValue = value; + } + } + } + + /// + /// Backing field for MinimumValue + /// + protected double _MinimumValue; + /// + /// When provided in a partial class, allows value of MinimumValue to be changed before setting. + /// + partial void SetMinimumValue(double oldValue, ref double newValue); + /// + /// When provided in a partial class, allows value of MinimumValue to be changed before returning. + /// + partial void GetMinimumValue(ref double result); + + /// + /// Required + /// + [Required] + public double MinimumValue + { + get + { + double value = _MinimumValue; + GetMinimumValue(ref value); + return (_MinimumValue = value); + } + set + { + double oldValue = _MinimumValue; + SetMinimumValue(oldValue, ref value); + if (oldValue != value) + { + _MinimumValue = value; + } + } + } + + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } + + public void OnSavingChanges() + { + RowVersion++; + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + [ForeignKey("MetadataProviderId_Source_Id")] + public virtual MetadataProviderId Source { get; set; } + + } } diff --git a/Jellyfin.Data/Entities/Release.cs b/Jellyfin.Data/Entities/Release.cs index e02f70be8..91dd35a7f 100644 --- a/Jellyfin.Data/Entities/Release.cs +++ b/Jellyfin.Data/Entities/Release.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,177 +9,185 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class Release - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Release() - { - MediaFiles = new System.Collections.Generic.HashSet(); - Chapters = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static Release CreateReleaseUnsafe() - { - return new Release(); - } - - /// - /// Public constructor with required data - /// - /// - /// - /// - /// - /// - /// - /// - public Release(string name, global::Jellyfin.Data.Entities.Movie _movie0, global::Jellyfin.Data.Entities.Episode _episode1, global::Jellyfin.Data.Entities.Track _track2, global::Jellyfin.Data.Entities.CustomItem _customitem3, global::Jellyfin.Data.Entities.Book _book4, global::Jellyfin.Data.Entities.Photo _photo5) - { - if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); - this.Name = name; - - if (_movie0 == null) throw new ArgumentNullException(nameof(_movie0)); - _movie0.Releases.Add(this); - - if (_episode1 == null) throw new ArgumentNullException(nameof(_episode1)); - _episode1.Releases.Add(this); - - if (_track2 == null) throw new ArgumentNullException(nameof(_track2)); - _track2.Releases.Add(this); - - if (_customitem3 == null) throw new ArgumentNullException(nameof(_customitem3)); - _customitem3.Releases.Add(this); - - if (_book4 == null) throw new ArgumentNullException(nameof(_book4)); - _book4.Releases.Add(this); - - if (_photo5 == null) throw new ArgumentNullException(nameof(_photo5)); - _photo5.Releases.Add(this); - - this.MediaFiles = new System.Collections.Generic.HashSet(); - this.Chapters = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// - /// - /// - /// - /// - /// - /// - public static Release Create(string name, global::Jellyfin.Data.Entities.Movie _movie0, global::Jellyfin.Data.Entities.Episode _episode1, global::Jellyfin.Data.Entities.Track _track2, global::Jellyfin.Data.Entities.CustomItem _customitem3, global::Jellyfin.Data.Entities.Book _book4, global::Jellyfin.Data.Entities.Photo _photo5) - { - return new Release(name, _movie0, _episode1, _track2, _customitem3, _book4, _photo5); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for Id - /// - internal int _Id; - /// - /// When provided in a partial class, allows value of Id to be changed before setting. - /// - partial void SetId(int oldValue, ref int newValue); - /// - /// When provided in a partial class, allows value of Id to be changed before returning. - /// - partial void GetId(ref int result); - - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public int Id - { - get - { - int value = _Id; - GetId(ref value); - return (_Id = value); - } - protected set - { - int oldValue = _Id; - SetId(oldValue, ref value); - if (oldValue != value) + [Table("Release")] + public partial class Release + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Release() + { + MediaFiles = new HashSet(); + Chapters = new HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Release CreateReleaseUnsafe() + { + return new Release(); + } + + /// + /// Public constructor with required data + /// + /// + /// + /// + /// + /// + /// + /// + public Release(string name, Movie _movie0, Episode _episode1, Track _track2, CustomItem _customitem3, Book _book4, Photo _photo5) + { + if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); + this.Name = name; + + if (_movie0 == null) throw new ArgumentNullException(nameof(_movie0)); + _movie0.Releases.Add(this); + + if (_episode1 == null) throw new ArgumentNullException(nameof(_episode1)); + _episode1.Releases.Add(this); + + if (_track2 == null) throw new ArgumentNullException(nameof(_track2)); + _track2.Releases.Add(this); + + if (_customitem3 == null) throw new ArgumentNullException(nameof(_customitem3)); + _customitem3.Releases.Add(this); + + if (_book4 == null) throw new ArgumentNullException(nameof(_book4)); + _book4.Releases.Add(this); + + if (_photo5 == null) throw new ArgumentNullException(nameof(_photo5)); + _photo5.Releases.Add(this); + + this.MediaFiles = new HashSet(); + this.Chapters = new HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + /// + /// + /// + /// + /// + public static Release Create(string name, Movie _movie0, Episode _episode1, Track _track2, CustomItem _customitem3, Book _book4, Photo _photo5) + { + return new Release(name, _movie0, _episode1, _track2, _customitem3, _book4, _photo5); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Id + /// + internal int _Id; + /// + /// When provided in a partial class, allows value of Id to be changed before setting. + /// + partial void SetId(int oldValue, ref int newValue); + /// + /// When provided in a partial class, allows value of Id to be changed before returning. + /// + partial void GetId(ref int result); + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id + { + get { - _Id = value; + int value = _Id; + GetId(ref value); + return (_Id = value); } - } - } - - /// - /// Backing field for Name - /// - protected string _Name; - /// - /// When provided in a partial class, allows value of Name to be changed before setting. - /// - partial void SetName(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Name to be changed before returning. - /// - partial void GetName(ref string result); - - /// - /// Required, Max length = 1024 - /// - [Required] - [MaxLength(1024)] - [StringLength(1024)] - public string Name - { - get - { - string value = _Name; - GetName(ref value); - return (_Name = value); - } - set - { - string oldValue = _Name; - SetName(oldValue, ref value); - if (oldValue != value) + protected set { - _Name = value; + int oldValue = _Id; + SetId(oldValue, ref value); + if (oldValue != value) + { + _Id = value; + } } - } - } - - /// - /// Required - /// - [ConcurrencyCheck] - [Required] - public byte[] Timestamp { get; set; } - - /************************************************************************* - * Navigation properties - *************************************************************************/ - - public virtual ICollection MediaFiles { get; protected set; } - - public virtual ICollection Chapters { get; protected set; } - - } + } + + /// + /// Backing field for Name + /// + protected string _Name; + /// + /// When provided in a partial class, allows value of Name to be changed before setting. + /// + partial void SetName(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Name to be changed before returning. + /// + partial void GetName(ref string result); + + /// + /// Required, Max length = 1024 + /// + [Required] + [MaxLength(1024)] + [StringLength(1024)] + public string Name + { + get + { + string value = _Name; + GetName(ref value); + return (_Name = value); + } + set + { + string oldValue = _Name; + SetName(oldValue, ref value); + if (oldValue != value) + { + _Name = value; + } + } + } + + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } + + public void OnSavingChanges() + { + RowVersion++; + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + [ForeignKey("MediaFile_MediaFiles_Id")] + public virtual ICollection MediaFiles { get; protected set; } + + [ForeignKey("Chapter_Chapters_Id")] + public virtual ICollection Chapters { get; protected set; } + + } } diff --git a/Jellyfin.Data/Entities/Season.cs b/Jellyfin.Data/Entities/Season.cs index fdfdf2409..3928a4ba6 100644 --- a/Jellyfin.Data/Entities/Season.cs +++ b/Jellyfin.Data/Entities/Season.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,107 +9,109 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class Season: global::Jellyfin.Data.Entities.LibraryItem - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Season(): base() - { - // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem. - // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. - - SeasonMetadata = new System.Collections.Generic.HashSet(); - Episodes = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static Season CreateSeasonUnsafe() - { - return new Season(); - } - - /// - /// Public constructor with required data - /// - /// This is whats gets displayed in the Urls and API requests. This could also be a string. - /// - public Season(Guid urlid, DateTime dateadded, global::Jellyfin.Data.Entities.Series _series0) - { - // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem. - // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. - - this.UrlId = urlid; - - if (_series0 == null) throw new ArgumentNullException(nameof(_series0)); - _series0.Seasons.Add(this); - - this.SeasonMetadata = new System.Collections.Generic.HashSet(); - this.Episodes = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// This is whats gets displayed in the Urls and API requests. This could also be a string. - /// - public static Season Create(Guid urlid, DateTime dateadded, global::Jellyfin.Data.Entities.Series _series0) - { - return new Season(urlid, dateadded, _series0); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for SeasonNumber - /// - protected int? _SeasonNumber; - /// - /// When provided in a partial class, allows value of SeasonNumber to be changed before setting. - /// - partial void SetSeasonNumber(int? oldValue, ref int? newValue); - /// - /// When provided in a partial class, allows value of SeasonNumber to be changed before returning. - /// - partial void GetSeasonNumber(ref int? result); - - public int? SeasonNumber - { - get - { - int? value = _SeasonNumber; - GetSeasonNumber(ref value); - return (_SeasonNumber = value); - } - set - { - int? oldValue = _SeasonNumber; - SetSeasonNumber(oldValue, ref value); - if (oldValue != value) + [Table("Season")] + public partial class Season : LibraryItem + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Season() : base() + { + // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + SeasonMetadata = new HashSet(); + Episodes = new HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Season CreateSeasonUnsafe() + { + return new Season(); + } + + /// + /// Public constructor with required data + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + /// + public Season(Guid urlid, DateTime dateadded, Series _series0) + { + // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + this.UrlId = urlid; + + if (_series0 == null) throw new ArgumentNullException(nameof(_series0)); + _series0.Seasons.Add(this); + + this.SeasonMetadata = new HashSet(); + this.Episodes = new HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + /// + public static Season Create(Guid urlid, DateTime dateadded, Series _series0) + { + return new Season(urlid, dateadded, _series0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for SeasonNumber + /// + protected int? _SeasonNumber; + /// + /// When provided in a partial class, allows value of SeasonNumber to be changed before setting. + /// + partial void SetSeasonNumber(int? oldValue, ref int? newValue); + /// + /// When provided in a partial class, allows value of SeasonNumber to be changed before returning. + /// + partial void GetSeasonNumber(ref int? result); + + public int? SeasonNumber + { + get { - _SeasonNumber = value; + int? value = _SeasonNumber; + GetSeasonNumber(ref value); + return (_SeasonNumber = value); } - } - } - - /************************************************************************* - * Navigation properties - *************************************************************************/ + set + { + int? oldValue = _SeasonNumber; + SetSeasonNumber(oldValue, ref value); + if (oldValue != value) + { + _SeasonNumber = value; + } + } + } - public virtual ICollection SeasonMetadata { get; protected set; } + /************************************************************************* + * Navigation properties + *************************************************************************/ + [ForeignKey("SeasonMetadata_SeasonMetadata_Id")] + public virtual ICollection SeasonMetadata { get; protected set; } - public virtual ICollection Episodes { get; protected set; } + [ForeignKey("Episode_Episodes_Id")] + public virtual ICollection Episodes { get; protected set; } - } + } } diff --git a/Jellyfin.Data/Entities/SeasonMetadata.cs b/Jellyfin.Data/Entities/SeasonMetadata.cs index 5939cbbca..f0e669a49 100644 --- a/Jellyfin.Data/Entities/SeasonMetadata.cs +++ b/Jellyfin.Data/Entities/SeasonMetadata.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,103 +9,104 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class SeasonMetadata: global::Jellyfin.Data.Entities.Metadata - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected SeasonMetadata(): base() - { - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static SeasonMetadata CreateSeasonMetadataUnsafe() - { - return new SeasonMetadata(); - } - - /// - /// Public constructor with required data - /// - /// The title or name of the object - /// ISO-639-3 3-character language codes - /// - public SeasonMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Season _season0) - { - if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); - this.Title = title; - - if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); - this.Language = language; - - if (_season0 == null) throw new ArgumentNullException(nameof(_season0)); - _season0.SeasonMetadata.Add(this); - - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// The title or name of the object - /// ISO-639-3 3-character language codes - /// - public static SeasonMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Season _season0) - { - return new SeasonMetadata(title, language, dateadded, datemodified, _season0); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for Outline - /// - protected string _Outline; - /// - /// When provided in a partial class, allows value of Outline to be changed before setting. - /// - partial void SetOutline(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Outline to be changed before returning. - /// - partial void GetOutline(ref string result); - - /// - /// Max length = 1024 - /// - [MaxLength(1024)] - [StringLength(1024)] - public string Outline - { - get - { - string value = _Outline; - GetOutline(ref value); - return (_Outline = value); - } - set - { - string oldValue = _Outline; - SetOutline(oldValue, ref value); - if (oldValue != value) + [Table("SeasonMetadata")] + public partial class SeasonMetadata : Metadata + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected SeasonMetadata() : base() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static SeasonMetadata CreateSeasonMetadataUnsafe() + { + return new SeasonMetadata(); + } + + /// + /// Public constructor with required data + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public SeasonMetadata(string title, string language, DateTime dateadded, DateTime datemodified, Season _season0) + { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + this.Title = title; + + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; + + if (_season0 == null) throw new ArgumentNullException(nameof(_season0)); + _season0.SeasonMetadata.Add(this); + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public static SeasonMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, Season _season0) + { + return new SeasonMetadata(title, language, dateadded, datemodified, _season0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Outline + /// + protected string _Outline; + /// + /// When provided in a partial class, allows value of Outline to be changed before setting. + /// + partial void SetOutline(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Outline to be changed before returning. + /// + partial void GetOutline(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Outline + { + get + { + string value = _Outline; + GetOutline(ref value); + return (_Outline = value); + } + set { - _Outline = value; + string oldValue = _Outline; + SetOutline(oldValue, ref value); + if (oldValue != value) + { + _Outline = value; + } } - } - } + } - /************************************************************************* - * Navigation properties - *************************************************************************/ + /************************************************************************* + * Navigation properties + *************************************************************************/ - } + } } diff --git a/Jellyfin.Data/Entities/Series.cs b/Jellyfin.Data/Entities/Series.cs index a57064824..fecc229af 100644 --- a/Jellyfin.Data/Entities/Series.cs +++ b/Jellyfin.Data/Entities/Series.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,163 +9,165 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class Series: global::Jellyfin.Data.Entities.LibraryItem - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Series(): base() - { - SeriesMetadata = new System.Collections.Generic.HashSet(); - Seasons = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static Series CreateSeriesUnsafe() - { - return new Series(); - } - - /// - /// 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) - { - this.UrlId = urlid; - - this.SeriesMetadata = new System.Collections.Generic.HashSet(); - this.Seasons = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// This is whats gets displayed in the Urls and API requests. This could also be a string. - public static Series Create(Guid urlid, DateTime dateadded) - { - return new Series(urlid, dateadded); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for AirsDayOfWeek - /// - protected global::Jellyfin.Data.Enums.Weekday? _AirsDayOfWeek; - /// - /// When provided in a partial class, allows value of AirsDayOfWeek to be changed before setting. - /// - partial void SetAirsDayOfWeek(global::Jellyfin.Data.Enums.Weekday? oldValue, ref global::Jellyfin.Data.Enums.Weekday? newValue); - /// - /// When provided in a partial class, allows value of AirsDayOfWeek to be changed before returning. - /// - partial void GetAirsDayOfWeek(ref global::Jellyfin.Data.Enums.Weekday? result); - - public global::Jellyfin.Data.Enums.Weekday? AirsDayOfWeek - { - get - { - global::Jellyfin.Data.Enums.Weekday? value = _AirsDayOfWeek; - GetAirsDayOfWeek(ref value); - return (_AirsDayOfWeek = value); - } - set - { - global::Jellyfin.Data.Enums.Weekday? oldValue = _AirsDayOfWeek; - SetAirsDayOfWeek(oldValue, ref value); - if (oldValue != value) + [Table("Series")] + public partial class Series : LibraryItem + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Series() : base() + { + SeriesMetadata = new HashSet(); + Seasons = new HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Series CreateSeriesUnsafe() + { + return new Series(); + } + + /// + /// 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) + { + this.UrlId = urlid; + + this.SeriesMetadata = new HashSet(); + this.Seasons = new HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + public static Series Create(Guid urlid, DateTime dateadded) + { + return new Series(urlid, dateadded); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for AirsDayOfWeek + /// + protected Enums.Weekday? _AirsDayOfWeek; + /// + /// When provided in a partial class, allows value of AirsDayOfWeek to be changed before setting. + /// + partial void SetAirsDayOfWeek(Enums.Weekday? oldValue, ref Enums.Weekday? newValue); + /// + /// When provided in a partial class, allows value of AirsDayOfWeek to be changed before returning. + /// + partial void GetAirsDayOfWeek(ref Enums.Weekday? result); + + public Enums.Weekday? AirsDayOfWeek + { + get { - _AirsDayOfWeek = value; + Enums.Weekday? value = _AirsDayOfWeek; + GetAirsDayOfWeek(ref value); + return (_AirsDayOfWeek = value); } - } - } - - /// - /// Backing field for AirsTime - /// - protected DateTimeOffset? _AirsTime; - /// - /// When provided in a partial class, allows value of AirsTime to be changed before setting. - /// - partial void SetAirsTime(DateTimeOffset? oldValue, ref DateTimeOffset? newValue); - /// - /// When provided in a partial class, allows value of AirsTime to be changed before returning. - /// - partial void GetAirsTime(ref DateTimeOffset? result); - - /// - /// The time the show airs, ignore the date portion - /// - public DateTimeOffset? AirsTime - { - get - { - DateTimeOffset? value = _AirsTime; - GetAirsTime(ref value); - return (_AirsTime = value); - } - set - { - DateTimeOffset? oldValue = _AirsTime; - SetAirsTime(oldValue, ref value); - if (oldValue != value) + set { - _AirsTime = value; + Enums.Weekday? oldValue = _AirsDayOfWeek; + SetAirsDayOfWeek(oldValue, ref value); + if (oldValue != value) + { + _AirsDayOfWeek = value; + } } - } - } - - /// - /// Backing field for FirstAired - /// - protected DateTimeOffset? _FirstAired; - /// - /// When provided in a partial class, allows value of FirstAired to be changed before setting. - /// - partial void SetFirstAired(DateTimeOffset? oldValue, ref DateTimeOffset? newValue); - /// - /// When provided in a partial class, allows value of FirstAired to be changed before returning. - /// - partial void GetFirstAired(ref DateTimeOffset? result); - - public DateTimeOffset? FirstAired - { - get - { - DateTimeOffset? value = _FirstAired; - GetFirstAired(ref value); - return (_FirstAired = value); - } - set - { - DateTimeOffset? oldValue = _FirstAired; - SetFirstAired(oldValue, ref value); - if (oldValue != value) + } + + /// + /// Backing field for AirsTime + /// + protected DateTimeOffset? _AirsTime; + /// + /// When provided in a partial class, allows value of AirsTime to be changed before setting. + /// + partial void SetAirsTime(DateTimeOffset? oldValue, ref DateTimeOffset? newValue); + /// + /// When provided in a partial class, allows value of AirsTime to be changed before returning. + /// + partial void GetAirsTime(ref DateTimeOffset? result); + + /// + /// The time the show airs, ignore the date portion + /// + public DateTimeOffset? AirsTime + { + get { - _FirstAired = value; + DateTimeOffset? value = _AirsTime; + GetAirsTime(ref value); + return (_AirsTime = value); } - } - } - - /************************************************************************* - * Navigation properties - *************************************************************************/ + set + { + DateTimeOffset? oldValue = _AirsTime; + SetAirsTime(oldValue, ref value); + if (oldValue != value) + { + _AirsTime = value; + } + } + } + + /// + /// Backing field for FirstAired + /// + protected DateTimeOffset? _FirstAired; + /// + /// When provided in a partial class, allows value of FirstAired to be changed before setting. + /// + partial void SetFirstAired(DateTimeOffset? oldValue, ref DateTimeOffset? newValue); + /// + /// When provided in a partial class, allows value of FirstAired to be changed before returning. + /// + partial void GetFirstAired(ref DateTimeOffset? result); + + public DateTimeOffset? FirstAired + { + get + { + DateTimeOffset? value = _FirstAired; + GetFirstAired(ref value); + return (_FirstAired = value); + } + set + { + DateTimeOffset? oldValue = _FirstAired; + SetFirstAired(oldValue, ref value); + if (oldValue != value) + { + _FirstAired = value; + } + } + } - public virtual ICollection SeriesMetadata { get; protected set; } + /************************************************************************* + * Navigation properties + *************************************************************************/ + [ForeignKey("SeriesMetadata_SeriesMetadata_Id")] + public virtual ICollection SeriesMetadata { get; protected set; } - public virtual ICollection Seasons { get; protected set; } + [ForeignKey("Season_Seasons_Id")] + public virtual ICollection Seasons { get; protected set; } - } + } } diff --git a/Jellyfin.Data/Entities/SeriesMetadata.cs b/Jellyfin.Data/Entities/SeriesMetadata.cs index 9a91371df..15818f941 100644 --- a/Jellyfin.Data/Entities/SeriesMetadata.cs +++ b/Jellyfin.Data/Entities/SeriesMetadata.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,219 +9,220 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class SeriesMetadata: global::Jellyfin.Data.Entities.Metadata - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected SeriesMetadata(): base() - { - Networks = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static SeriesMetadata CreateSeriesMetadataUnsafe() - { - return new SeriesMetadata(); - } - - /// - /// Public constructor with required data - /// - /// The title or name of the object - /// ISO-639-3 3-character language codes - /// - public SeriesMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Series _series0) - { - if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); - this.Title = title; - - if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); - this.Language = language; - - if (_series0 == null) throw new ArgumentNullException(nameof(_series0)); - _series0.SeriesMetadata.Add(this); - - this.Networks = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// The title or name of the object - /// ISO-639-3 3-character language codes - /// - public static SeriesMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Series _series0) - { - return new SeriesMetadata(title, language, dateadded, datemodified, _series0); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for Outline - /// - protected string _Outline; - /// - /// When provided in a partial class, allows value of Outline to be changed before setting. - /// - partial void SetOutline(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Outline to be changed before returning. - /// - partial void GetOutline(ref string result); - - /// - /// Max length = 1024 - /// - [MaxLength(1024)] - [StringLength(1024)] - public string Outline - { - get - { - string value = _Outline; - GetOutline(ref value); - return (_Outline = value); - } - set - { - string oldValue = _Outline; - SetOutline(oldValue, ref value); - if (oldValue != value) + [Table("SeriesMetadata")] + public partial class SeriesMetadata : Metadata + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected SeriesMetadata() : base() + { + Networks = new HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static SeriesMetadata CreateSeriesMetadataUnsafe() + { + return new SeriesMetadata(); + } + + /// + /// Public constructor with required data + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public SeriesMetadata(string title, string language, DateTime dateadded, DateTime datemodified, Series _series0) + { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + this.Title = title; + + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; + + if (_series0 == null) throw new ArgumentNullException(nameof(_series0)); + _series0.SeriesMetadata.Add(this); + + this.Networks = new HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public static SeriesMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, Series _series0) + { + return new SeriesMetadata(title, language, dateadded, datemodified, _series0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for Outline + /// + protected string _Outline; + /// + /// When provided in a partial class, allows value of Outline to be changed before setting. + /// + partial void SetOutline(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Outline to be changed before returning. + /// + partial void GetOutline(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Outline + { + get { - _Outline = value; + string value = _Outline; + GetOutline(ref value); + return (_Outline = value); } - } - } - - /// - /// Backing field for Plot - /// - protected string _Plot; - /// - /// When provided in a partial class, allows value of Plot to be changed before setting. - /// - partial void SetPlot(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Plot to be changed before returning. - /// - partial void GetPlot(ref string result); - - /// - /// Max length = 65535 - /// - [MaxLength(65535)] - [StringLength(65535)] - public string Plot - { - get - { - string value = _Plot; - GetPlot(ref value); - return (_Plot = value); - } - set - { - string oldValue = _Plot; - SetPlot(oldValue, ref value); - if (oldValue != value) + set { - _Plot = value; + string oldValue = _Outline; + SetOutline(oldValue, ref value); + if (oldValue != value) + { + _Outline = value; + } } - } - } - - /// - /// Backing field for Tagline - /// - protected string _Tagline; - /// - /// When provided in a partial class, allows value of Tagline to be changed before setting. - /// - partial void SetTagline(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Tagline to be changed before returning. - /// - partial void GetTagline(ref string result); - - /// - /// Max length = 1024 - /// - [MaxLength(1024)] - [StringLength(1024)] - public string Tagline - { - get - { - string value = _Tagline; - GetTagline(ref value); - return (_Tagline = value); - } - set - { - string oldValue = _Tagline; - SetTagline(oldValue, ref value); - if (oldValue != value) + } + + /// + /// Backing field for Plot + /// + protected string _Plot; + /// + /// When provided in a partial class, allows value of Plot to be changed before setting. + /// + partial void SetPlot(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Plot to be changed before returning. + /// + partial void GetPlot(ref string result); + + /// + /// Max length = 65535 + /// + [MaxLength(65535)] + [StringLength(65535)] + public string Plot + { + get { - _Tagline = value; + string value = _Plot; + GetPlot(ref value); + return (_Plot = value); } - } - } - - /// - /// Backing field for Country - /// - protected string _Country; - /// - /// When provided in a partial class, allows value of Country to be changed before setting. - /// - partial void SetCountry(string oldValue, ref string newValue); - /// - /// When provided in a partial class, allows value of Country to be changed before returning. - /// - partial void GetCountry(ref string result); - - /// - /// Max length = 2 - /// - [MaxLength(2)] - [StringLength(2)] - public string Country - { - get - { - string value = _Country; - GetCountry(ref value); - return (_Country = value); - } - set - { - string oldValue = _Country; - SetCountry(oldValue, ref value); - if (oldValue != value) + set { - _Country = value; + string oldValue = _Plot; + SetPlot(oldValue, ref value); + if (oldValue != value) + { + _Plot = value; + } } - } - } - - /************************************************************************* - * Navigation properties - *************************************************************************/ + } + + /// + /// Backing field for Tagline + /// + protected string _Tagline; + /// + /// When provided in a partial class, allows value of Tagline to be changed before setting. + /// + partial void SetTagline(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Tagline to be changed before returning. + /// + partial void GetTagline(ref string result); + + /// + /// Max length = 1024 + /// + [MaxLength(1024)] + [StringLength(1024)] + public string Tagline + { + get + { + string value = _Tagline; + GetTagline(ref value); + return (_Tagline = value); + } + set + { + string oldValue = _Tagline; + SetTagline(oldValue, ref value); + if (oldValue != value) + { + _Tagline = value; + } + } + } + + /// + /// Backing field for Country + /// + protected string _Country; + /// + /// When provided in a partial class, allows value of Country to be changed before setting. + /// + partial void SetCountry(string oldValue, ref string newValue); + /// + /// When provided in a partial class, allows value of Country to be changed before returning. + /// + partial void GetCountry(ref string result); + + /// + /// Max length = 2 + /// + [MaxLength(2)] + [StringLength(2)] + public string Country + { + get + { + string value = _Country; + GetCountry(ref value); + return (_Country = value); + } + set + { + string oldValue = _Country; + SetCountry(oldValue, ref value); + if (oldValue != value) + { + _Country = value; + } + } + } - public virtual ICollection Networks { get; protected set; } + /************************************************************************* + * Navigation properties + *************************************************************************/ + [ForeignKey("Company_Networks_Id")] + public virtual ICollection Networks { get; protected set; } - } + } } diff --git a/Jellyfin.Data/Entities/Track.cs b/Jellyfin.Data/Entities/Track.cs index 1d3ad372f..50ee43042 100644 --- a/Jellyfin.Data/Entities/Track.cs +++ b/Jellyfin.Data/Entities/Track.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,107 +9,110 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class Track: global::Jellyfin.Data.Entities.LibraryItem - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Track(): base() - { - // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem. - // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. - - Releases = new System.Collections.Generic.HashSet(); - TrackMetadata = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static Track CreateTrackUnsafe() - { - return new Track(); - } - - /// - /// Public constructor with required data - /// - /// This is whats gets displayed in the Urls and API requests. This could also be a string. - /// - public Track(Guid urlid, DateTime dateadded, global::Jellyfin.Data.Entities.MusicAlbum _musicalbum0) - { - // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem. - // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. - - this.UrlId = urlid; - - if (_musicalbum0 == null) throw new ArgumentNullException(nameof(_musicalbum0)); - _musicalbum0.Tracks.Add(this); - - this.Releases = new System.Collections.Generic.HashSet(); - this.TrackMetadata = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// This is whats gets displayed in the Urls and API requests. This could also be a string. - /// - public static Track Create(Guid urlid, DateTime dateadded, global::Jellyfin.Data.Entities.MusicAlbum _musicalbum0) - { - return new Track(urlid, dateadded, _musicalbum0); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Backing field for TrackNumber - /// - protected int? _TrackNumber; - /// - /// When provided in a partial class, allows value of TrackNumber to be changed before setting. - /// - partial void SetTrackNumber(int? oldValue, ref int? newValue); - /// - /// When provided in a partial class, allows value of TrackNumber to be changed before returning. - /// - partial void GetTrackNumber(ref int? result); - - public int? TrackNumber - { - get - { - int? value = _TrackNumber; - GetTrackNumber(ref value); - return (_TrackNumber = value); - } - set - { - int? oldValue = _TrackNumber; - SetTrackNumber(oldValue, ref value); - if (oldValue != value) + [Table("Track")] + public partial class Track : LibraryItem + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Track() : base() + { + // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + Releases = new HashSet(); + TrackMetadata = new HashSet(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static Track CreateTrackUnsafe() + { + return new Track(); + } + + /// + /// Public constructor with required data + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + /// + public Track(Guid urlid, DateTime dateadded, MusicAlbum _musicalbum0) + { + // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem. + // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. + + this.UrlId = urlid; + + if (_musicalbum0 == null) throw new ArgumentNullException(nameof(_musicalbum0)); + _musicalbum0.Tracks.Add(this); + + this.Releases = new HashSet(); + this.TrackMetadata = new HashSet(); + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// This is whats gets displayed in the Urls and API requests. This could also be a string. + /// + public static Track Create(Guid urlid, DateTime dateadded, MusicAlbum _musicalbum0) + { + return new Track(urlid, dateadded, _musicalbum0); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Backing field for TrackNumber + /// + protected int? _TrackNumber; + /// + /// When provided in a partial class, allows value of TrackNumber to be changed before setting. + /// + partial void SetTrackNumber(int? oldValue, ref int? newValue); + /// + /// When provided in a partial class, allows value of TrackNumber to be changed before returning. + /// + partial void GetTrackNumber(ref int? result); + + public int? TrackNumber + { + get + { + int? value = _TrackNumber; + GetTrackNumber(ref value); + return (_TrackNumber = value); + } + set { - _TrackNumber = value; + int? oldValue = _TrackNumber; + SetTrackNumber(oldValue, ref value); + if (oldValue != value) + { + _TrackNumber = value; + } } - } - } + } - /************************************************************************* - * Navigation properties - *************************************************************************/ + /************************************************************************* + * Navigation properties + *************************************************************************/ - public virtual ICollection Releases { get; protected set; } + [ForeignKey("Release_Releases_Id")] + public virtual ICollection Releases { get; protected set; } - public virtual ICollection TrackMetadata { get; protected set; } + [ForeignKey("TrackMetadata_TrackMetadata_Id")] + public virtual ICollection TrackMetadata { get; protected set; } - } + } } diff --git a/Jellyfin.Data/Entities/TrackMetadata.cs b/Jellyfin.Data/Entities/TrackMetadata.cs index f4c61459c..84679ebb5 100644 --- a/Jellyfin.Data/Entities/TrackMetadata.cs +++ b/Jellyfin.Data/Entities/TrackMetadata.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,66 +9,67 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class TrackMetadata: global::Jellyfin.Data.Entities.Metadata - { - partial void Init(); + [Table("TrackMetadata")] + public partial class TrackMetadata : Metadata + { + partial void Init(); - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected TrackMetadata(): base() - { - Init(); - } + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected TrackMetadata() : base() + { + Init(); + } - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static TrackMetadata CreateTrackMetadataUnsafe() - { - return new TrackMetadata(); - } + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static TrackMetadata CreateTrackMetadataUnsafe() + { + return new TrackMetadata(); + } - /// - /// Public constructor with required data - /// - /// The title or name of the object - /// ISO-639-3 3-character language codes - /// - public TrackMetadata(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Track _track0) - { - if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); - this.Title = title; + /// + /// Public constructor with required data + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public TrackMetadata(string title, string language, DateTime dateadded, DateTime datemodified, Track _track0) + { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + this.Title = title; - if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); - this.Language = language; + if (string.IsNullOrEmpty(language)) throw new ArgumentNullException(nameof(language)); + this.Language = language; - if (_track0 == null) throw new ArgumentNullException(nameof(_track0)); - _track0.TrackMetadata.Add(this); + if (_track0 == null) throw new ArgumentNullException(nameof(_track0)); + _track0.TrackMetadata.Add(this); - Init(); - } + Init(); + } - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// The title or name of the object - /// ISO-639-3 3-character language codes - /// - public static TrackMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, global::Jellyfin.Data.Entities.Track _track0) - { - return new TrackMetadata(title, language, dateadded, datemodified, _track0); - } + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The title or name of the object + /// ISO-639-3 3-character language codes + /// + public static TrackMetadata Create(string title, string language, DateTime dateadded, DateTime datemodified, Track _track0) + { + return new TrackMetadata(title, language, dateadded, datemodified, _track0); + } - /************************************************************************* - * Properties - *************************************************************************/ + /************************************************************************* + * Properties + *************************************************************************/ - /************************************************************************* - * Navigation properties - *************************************************************************/ + /************************************************************************* + * Navigation properties + *************************************************************************/ - } + } } diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index 2ee3c8f4f..715969dbf 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -1,15 +1,3 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -21,222 +9,232 @@ using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - public partial class User - { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected User() - { - Groups = new System.Collections.Generic.HashSet(); - Permissions = new System.Collections.Generic.HashSet(); - ProviderMappings = new System.Collections.Generic.HashSet(); - Preferences = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static User CreateUserUnsafe() - { - return new User(); - } - - /// - /// Public constructor with required data - /// - /// - /// - /// - /// - /// - /// - /// - public User(string username, bool mustupdatepassword, string audiolanguagepreference, string authenticationproviderid, int invalidloginattemptcount, string subtitlemode, bool playdefaultaudiotrack) - { - 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 System.Collections.Generic.HashSet(); - this.Permissions = new System.Collections.Generic.HashSet(); - this.ProviderMappings = new System.Collections.Generic.HashSet(); - this.Preferences = new System.Collections.Generic.HashSet(); - - Init(); - } - - /// - /// 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) - { - return new User(username, mustupdatepassword, audiolanguagepreference, authenticationproviderid, invalidloginattemptcount, subtitlemode, playdefaultaudiotrack); - } - - /************************************************************************* - * Properties - *************************************************************************/ - - /// - /// Identity, Indexed, Required - /// - [Key] - [Required] - public Guid Id { get; protected set; } - - /// - /// Required - /// - [ConcurrencyCheck] - [Required] - public byte[] LastLoginTimestamp { get; set; } - - /// - /// Required, Max length = 255 - /// - [Required] - [MaxLength(255)] - [StringLength(255)] - public string Username { get; set; } - - /// - /// Max length = 65535 - /// - [MaxLength(65535)] - [StringLength(65535)] - public string Password { get; set; } - - /// - /// Required - /// - [Required] - public bool MustUpdatePassword { get; set; } - - /// - /// Required, Max length = 255 - /// - [Required] - [MaxLength(255)] - [StringLength(255)] - public string AudioLanguagePreference { get; set; } - - /// - /// Required, Max length = 255 - /// - [Required] - [MaxLength(255)] - [StringLength(255)] - public string AuthenticationProviderId { get; set; } - - /// - /// Max length = 65535 - /// - [MaxLength(65535)] - [StringLength(65535)] - public string GroupedFolders { get; set; } - - /// - /// Required - /// - [Required] - public int InvalidLoginAttemptCount { get; set; } - - /// - /// Max length = 65535 - /// - [MaxLength(65535)] - [StringLength(65535)] - public string LatestItemExcludes { get; set; } - - public int? LoginAttemptsBeforeLockout { 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; } - - /// - /// Required, Max length = 255 - /// - [Required] - [MaxLength(255)] - [StringLength(255)] - public string SubtitleMode { get; set; } - - /// - /// Required - /// - [Required] - public bool PlayDefaultAudioTrack { get; set; } - - /// - /// Max length = 255 - /// - [MaxLength(255)] - [StringLength(255)] - public string SubtitleLanguagePrefernce { get; set; } - - public bool? DisplayMissingEpisodes { get; set; } - - public bool? DisplayCollectionsView { get; set; } - - public bool? HidePlayedInLatest { get; set; } - - public bool? RememberAudioSelections { get; set; } - - public bool? RememberSubtitleSelections { get; set; } - - public bool? EnableNextEpisodeAutoPlay { get; set; } - - public bool? EnableUserPreferenceAccess { get; set; } - - /************************************************************************* - * Navigation properties - *************************************************************************/ - - public virtual ICollection Groups { get; protected set; } - - public virtual ICollection Permissions { get; protected set; } - - public virtual ICollection ProviderMappings { get; protected set; } - - public virtual ICollection Preferences { get; protected set; } - - } + [Table("User")] + public partial class User + { + 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(); + + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static User CreateUserUnsafe() + { + return new User(); + } + + /// + /// Public constructor with required data + /// + /// + /// + /// + /// + /// + /// + /// + public User(string username, bool mustupdatepassword, string audiolanguagepreference, string authenticationproviderid, int invalidloginattemptcount, string subtitlemode, bool playdefaultaudiotrack) + { + 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(); + } + + /// + /// 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) + { + return new User(username, mustupdatepassword, audiolanguagepreference, authenticationproviderid, invalidloginattemptcount, subtitlemode, playdefaultaudiotrack); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; protected set; } + + /// + /// Required, Max length = 255 + /// + [Required] + [MaxLength(255)] + [StringLength(255)] + public string Username { get; set; } + + /// + /// Max length = 65535 + /// + [MaxLength(65535)] + [StringLength(65535)] + public string Password { get; set; } + + /// + /// Required + /// + [Required] + public bool MustUpdatePassword { get; set; } + + /// + /// Required, Max length = 255 + /// + [Required] + [MaxLength(255)] + [StringLength(255)] + public string AudioLanguagePreference { get; set; } + + /// + /// Required, Max length = 255 + /// + [Required] + [MaxLength(255)] + [StringLength(255)] + public string AuthenticationProviderId { get; set; } + + /// + /// Max length = 65535 + /// + [MaxLength(65535)] + [StringLength(65535)] + public string GroupedFolders { get; set; } + + /// + /// Required + /// + [Required] + public int InvalidLoginAttemptCount { get; set; } + + /// + /// Max length = 65535 + /// + [MaxLength(65535)] + [StringLength(65535)] + public string LatestItemExcludes { get; set; } + + public int? LoginAttemptsBeforeLockout { 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; } + + /// + /// Required, Max length = 255 + /// + [Required] + [MaxLength(255)] + [StringLength(255)] + public string SubtitleMode { get; set; } + + /// + /// Required + /// + [Required] + public bool PlayDefaultAudioTrack { get; set; } + + /// + /// Max length = 255 + /// + [MaxLength(255)] + [StringLength(255)] + public string SubtitleLanguagePrefernce { get; set; } + + public bool? DisplayMissingEpisodes { get; set; } + + public bool? DisplayCollectionsView { get; set; } + + public bool? HidePlayedInLatest { get; set; } + + public bool? RememberAudioSelections { get; set; } + + public bool? RememberSubtitleSelections { get; set; } + + public bool? EnableNextEpisodeAutoPlay { get; set; } + + public bool? EnableUserPreferenceAccess { get; set; } + + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } + + public void OnSavingChanges() + { + RowVersion++; + } + + /************************************************************************* + * Navigation properties + *************************************************************************/ + [ForeignKey("Group_Groups_Id")] + public virtual ICollection Groups { get; protected set; } + + [ForeignKey("Permission_Permissions_Id")] + public virtual ICollection Permissions { get; protected set; } + + [ForeignKey("ProviderMapping_ProviderMappings_Id")] + public virtual ICollection ProviderMappings { get; protected set; } + + [ForeignKey("Preference_Preferences_Id")] + public virtual ICollection Preferences { get; protected set; } + + } } diff --git a/Jellyfin.Data/Enums/ArtKind.cs b/Jellyfin.Data/Enums/ArtKind.cs index 52e33048e..546e1533c 100644 --- a/Jellyfin.Data/Enums/ArtKind.cs +++ b/Jellyfin.Data/Enums/ArtKind.cs @@ -1,25 +1,13 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; namespace Jellyfin.Data.Enums { - public enum ArtKind : Int32 - { - Other, - Poster, - Banner, - Thumbnail, - Logo - } + public enum ArtKind : Int32 + { + Other, + Poster, + Banner, + Thumbnail, + Logo + } } diff --git a/Jellyfin.Data/Enums/MediaFileKind.cs b/Jellyfin.Data/Enums/MediaFileKind.cs index 34d1b20f5..d24920228 100644 --- a/Jellyfin.Data/Enums/MediaFileKind.cs +++ b/Jellyfin.Data/Enums/MediaFileKind.cs @@ -1,25 +1,13 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; namespace Jellyfin.Data.Enums { - public enum MediaFileKind : Int32 - { - Main, - Sidecar, - AdditionalPart, - AlternativeFormat, - AdditionalStream - } + public enum MediaFileKind : Int32 + { + Main, + Sidecar, + AdditionalPart, + AlternativeFormat, + AdditionalStream + } } diff --git a/Jellyfin.Data/Enums/PermissionKind.cs b/Jellyfin.Data/Enums/PermissionKind.cs new file mode 100644 index 000000000..4447fdb77 --- /dev/null +++ b/Jellyfin.Data/Enums/PermissionKind.cs @@ -0,0 +1,28 @@ +using System; + +namespace Jellyfin.Data.Enums +{ + public enum PermissionKind : Int32 + { + IsAdministrator, + IsHidden, + IsDisabled, + BlockUnrateditems, + EnbleSharedDeviceControl, + EnableRemoteAccess, + EnableLiveTvManagement, + EnableLiveTvAccess, + EnableMediaPlayback, + EnableAudioPlaybackTranscoding, + EnableVideoPlaybackTranscoding, + EnableContentDeletion, + EnableContentDownloading, + EnableSyncTranscoding, + EnableMediaConversion, + EnableAllDevices, + EnableAllChannels, + EnableAllFolders, + EnablePublicSharing, + AccessSchedules + } +} diff --git a/Jellyfin.Data/Enums/PersonRoleType.cs b/Jellyfin.Data/Enums/PersonRoleType.cs index f5c8f43c5..5621ffa4d 100644 --- a/Jellyfin.Data/Enums/PersonRoleType.cs +++ b/Jellyfin.Data/Enums/PersonRoleType.cs @@ -1,32 +1,20 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; namespace Jellyfin.Data.Enums { - public enum PersonRoleType : Int32 - { - Other, - Director, - Artist, - OriginalArtist, - Actor, - VoiceActor, - Producer, - Remixer, - Conductor, - Composer, - Author, - Editor - } + public enum PersonRoleType : Int32 + { + Other, + Director, + Artist, + OriginalArtist, + Actor, + VoiceActor, + Producer, + Remixer, + Conductor, + Composer, + Author, + Editor + } } diff --git a/Jellyfin.Data/Enums/PreferenceKind.cs b/Jellyfin.Data/Enums/PreferenceKind.cs new file mode 100644 index 000000000..e66a51cae --- /dev/null +++ b/Jellyfin.Data/Enums/PreferenceKind.cs @@ -0,0 +1,15 @@ +using System; + +namespace Jellyfin.Data.Enums +{ + public enum PreferenceKind : Int32 + { + MaxParentalRating, + BlockedTags, + RemoteClientBitrateLimit, + EnabledDevices, + EnabledChannels, + EnabledFolders, + EnableContentDeletionFromFolders + } +} diff --git a/Jellyfin.Data/Enums/Weekday.cs b/Jellyfin.Data/Enums/Weekday.cs index ce0c6e4ce..58523a6c7 100644 --- a/Jellyfin.Data/Enums/Weekday.cs +++ b/Jellyfin.Data/Enums/Weekday.cs @@ -1,27 +1,15 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - using System; namespace Jellyfin.Data.Enums { - public enum Weekday : Int32 - { - Sunday, - Monday, - Tuesday, - Wednesday, - Thursday, - Friday, - Saturday - } + public enum Weekday : Int32 + { + Sunday, + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday + } } diff --git a/Jellyfin.Data/Structs/.gitkeep b/Jellyfin.Data/Structs/.gitkeep deleted file mode 100644 index e69de29bb..000000000 -- cgit v1.2.3 From 032de931b14ded24bb1098a7eeec3d84561206e2 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 2 May 2020 18:32:22 -0400 Subject: Migrate activity db to EF Core --- .../Activity/ActivityLogEntryPoint.cs | 288 ++-- .../Activity/ActivityManager.cs | 70 - .../Activity/ActivityRepository.cs | 308 ---- Emby.Server.Implementations/ApplicationHost.cs | 14 +- .../Emby.Server.Implementations.csproj | 5 +- Jellyfin.Data/DbContexts/Jellyfin.cs | 1140 --------------- Jellyfin.Data/Entities/ActivityLog.cs | 153 ++ Jellyfin.Data/ISavingChanges.cs | 9 + Jellyfin.Data/Jellyfin.Data.csproj | 24 +- .../Activity/ActivityManager.cs | 103 ++ .../Jellyfin.Server.Implementations.csproj | 34 + Jellyfin.Server.Implementations/JellyfinDb.cs | 119 ++ .../JellyfinDbProvider.cs | 33 + .../20200430215054_InitialSchema.Designer.cs | 1513 ++++++++++++++++++++ .../Migrations/20200430215054_InitialSchema.cs | 1294 +++++++++++++++++ .../Migrations/DesignTimeJellyfinDbFactory.cs | 20 + .../Migrations/JellyfinDbModelSnapshot.cs | 1511 +++++++++++++++++++ Jellyfin.Server/Jellyfin.Server.csproj | 7 + Jellyfin.Server/Migrations/MigrationRunner.cs | 3 +- .../Migrations/Routines/MigrateActivityLogDb.cs | 109 ++ MediaBrowser.Api/Library/LibraryService.cs | 11 +- MediaBrowser.Api/System/ActivityLogService.cs | 2 +- MediaBrowser.Model/Activity/ActivityLogEntry.cs | 1 + MediaBrowser.Model/Activity/IActivityManager.cs | 15 +- MediaBrowser.Model/Activity/IActivityRepository.cs | 14 - MediaBrowser.Model/MediaBrowser.Model.csproj | 3 + MediaBrowser.sln | 46 +- 27 files changed, 5147 insertions(+), 1702 deletions(-) delete mode 100644 Emby.Server.Implementations/Activity/ActivityManager.cs delete mode 100644 Emby.Server.Implementations/Activity/ActivityRepository.cs delete mode 100644 Jellyfin.Data/DbContexts/Jellyfin.cs create mode 100644 Jellyfin.Data/Entities/ActivityLog.cs create mode 100644 Jellyfin.Data/ISavingChanges.cs create mode 100644 Jellyfin.Server.Implementations/Activity/ActivityManager.cs create mode 100644 Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj create mode 100644 Jellyfin.Server.Implementations/JellyfinDb.cs create mode 100644 Jellyfin.Server.Implementations/JellyfinDbProvider.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20200430215054_InitialSchema.Designer.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20200430215054_InitialSchema.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs create mode 100644 Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs delete mode 100644 MediaBrowser.Model/Activity/IActivityRepository.cs diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 4685a03ac..54894fd65 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -4,11 +4,11 @@ using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; @@ -104,47 +104,53 @@ namespace Emby.Server.Implementations.Activity return Task.CompletedTask; } - private void OnCameraImageUploaded(object sender, GenericEventArgs e) + private async void OnCameraImageUploaded(object sender, GenericEventArgs e) { - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format( + await CreateLogEntry(new ActivityLog( + string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("CameraImageUploadedFrom"), e.Argument.Device.Name), - Type = NotificationType.CameraImageUploaded.ToString() - }); + NotificationType.CameraImageUploaded.ToString(), + Guid.Empty, + DateTime.UtcNow, + LogLevel.Trace)) + .ConfigureAwait(false); } - private void OnUserLockedOut(object sender, GenericEventArgs e) + private async void OnUserLockedOut(object sender, GenericEventArgs e) { - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format( + await CreateLogEntry(new ActivityLog( + string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserLockedOutWithName"), e.Argument.Name), - Type = NotificationType.UserLockedOut.ToString(), - UserId = e.Argument.Id - }); + NotificationType.UserLockedOut.ToString(), + e.Argument.Id, + DateTime.UtcNow, + LogLevel.Trace)) + .ConfigureAwait(false); } - private void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e) + private async void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e) { - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format( + await CreateLogEntry(new ActivityLog( + string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"), e.Provider, - Notifications.NotificationEntryPoint.GetItemName(e.Item)), - Type = "SubtitleDownloadFailure", + Emby.Notifications.NotificationEntryPoint.GetItemName(e.Item)), + "SubtitleDownloadFailure", + Guid.Empty, + DateTime.UtcNow, + LogLevel.Trace) + { ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture), ShortOverview = e.Exception.Message - }); + }).ConfigureAwait(false); } - private void OnPlaybackStopped(object sender, PlaybackStopEventArgs e) + private async void OnPlaybackStopped(object sender, PlaybackStopEventArgs e) { var item = e.MediaInfo; @@ -167,20 +173,21 @@ namespace Emby.Server.Implementations.Activity var user = e.Users[0]; - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format( + await CreateLogEntry(new ActivityLog( + string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName), - Type = GetPlaybackStoppedNotificationType(item.MediaType), - UserId = user.Id - }); + GetPlaybackStoppedNotificationType(item.MediaType), + user.Id, + DateTime.UtcNow, + LogLevel.Trace)) + .ConfigureAwait(false); } - private void OnPlaybackStart(object sender, PlaybackProgressEventArgs e) + private async void OnPlaybackStart(object sender, PlaybackProgressEventArgs e) { var item = e.MediaInfo; @@ -203,17 +210,18 @@ namespace Emby.Server.Implementations.Activity var user = e.Users.First(); - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format( + await CreateLogEntry(new ActivityLog( + string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserStartedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName), - Type = GetPlaybackNotificationType(item.MediaType), - UserId = user.Id - }); + GetPlaybackNotificationType(item.MediaType), + user.Id, + DateTime.UtcNow, + LogLevel.Trace)) + .ConfigureAwait(false); } private static string GetItemName(BaseItemDto item) @@ -263,7 +271,7 @@ namespace Emby.Server.Implementations.Activity return null; } - private void OnSessionEnded(object sender, SessionEventArgs e) + private async void OnSessionEnded(object sender, SessionEventArgs e) { var session = e.SessionInfo; @@ -272,110 +280,120 @@ namespace Emby.Server.Implementations.Activity return; } - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format( + await CreateLogEntry(new ActivityLog( + string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserOfflineFromDevice"), session.UserName, session.DeviceName), - Type = "SessionEnded", + "SessionEnded", + session.UserId, + DateTime.UtcNow, + LogLevel.Trace) + { ShortOverview = string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("LabelIpAddressValue"), session.RemoteEndPoint), - UserId = session.UserId - }); + }).ConfigureAwait(false); } - private void OnAuthenticationSucceeded(object sender, GenericEventArgs e) + private async void OnAuthenticationSucceeded(object sender, GenericEventArgs e) { var user = e.Argument.User; - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format( + await CreateLogEntry(new ActivityLog( + string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("AuthenticationSucceededWithUserName"), user.Name), - Type = "AuthenticationSucceeded", + "AuthenticationSucceeded", + user.Id, + DateTime.UtcNow, + LogLevel.Trace) + { ShortOverview = string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("LabelIpAddressValue"), e.Argument.SessionInfo.RemoteEndPoint), - UserId = user.Id - }); + }).ConfigureAwait(false); } - private void OnAuthenticationFailed(object sender, GenericEventArgs e) + private async void OnAuthenticationFailed(object sender, GenericEventArgs e) { - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format( + await CreateLogEntry(new ActivityLog( + string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("FailedLoginAttemptWithUserName"), e.Argument.Username), - Type = "AuthenticationFailed", + "AuthenticationFailed", + Guid.Empty, + DateTime.UtcNow, + LogLevel.Error) + { ShortOverview = string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("LabelIpAddressValue"), e.Argument.RemoteEndPoint), - Severity = LogLevel.Error - }); + }).ConfigureAwait(false); } - private void OnUserPolicyUpdated(object sender, GenericEventArgs e) + private async void OnUserPolicyUpdated(object sender, GenericEventArgs e) { - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format( + await CreateLogEntry(new ActivityLog( + string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserPolicyUpdatedWithName"), e.Argument.Name), - Type = "UserPolicyUpdated", - UserId = e.Argument.Id - }); + "UserPolicyUpdated", + e.Argument.Id, + DateTime.UtcNow, + LogLevel.Trace)) + .ConfigureAwait(false); } - private void OnUserDeleted(object sender, GenericEventArgs e) + private async void OnUserDeleted(object sender, GenericEventArgs e) { - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format( + await CreateLogEntry(new ActivityLog( + string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserDeletedWithName"), e.Argument.Name), - Type = "UserDeleted" - }); + "UserDeleted", + Guid.Empty, + DateTime.UtcNow, + LogLevel.Trace)) + .ConfigureAwait(false); } - private void OnUserPasswordChanged(object sender, GenericEventArgs e) + private async void OnUserPasswordChanged(object sender, GenericEventArgs e) { - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format( + await CreateLogEntry(new ActivityLog( + string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserPasswordChangedWithName"), e.Argument.Name), - Type = "UserPasswordChanged", - UserId = e.Argument.Id - }); + "UserPasswordChanged", + e.Argument.Id, + DateTime.UtcNow, + LogLevel.Trace)).ConfigureAwait(false); } - private void OnUserCreated(object sender, GenericEventArgs e) + private async void OnUserCreated(object sender, GenericEventArgs e) { - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format( + await CreateLogEntry(new ActivityLog( + string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserCreatedWithName"), e.Argument.Name), - Type = "UserCreated", - UserId = e.Argument.Id - }); + "UserCreated", + e.Argument.Id, + DateTime.UtcNow, + LogLevel.Trace)) + .ConfigureAwait(false); } - private void OnSessionStarted(object sender, SessionEventArgs e) + private async void OnSessionStarted(object sender, SessionEventArgs e) { var session = e.SessionInfo; @@ -384,87 +402,100 @@ namespace Emby.Server.Implementations.Activity return; } - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format( + await CreateLogEntry(new ActivityLog( + string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserOnlineFromDevice"), session.UserName, session.DeviceName), - Type = "SessionStarted", + "SessionStarted", + session.UserId, + DateTime.UtcNow, + LogLevel.Trace) + { ShortOverview = string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("LabelIpAddressValue"), - session.RemoteEndPoint), - UserId = session.UserId - }); + session.RemoteEndPoint) + }).ConfigureAwait(false); } - private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, VersionInfo)> e) + private async void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, VersionInfo)> e) { - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format( + await CreateLogEntry(new ActivityLog( + string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("PluginUpdatedWithName"), e.Argument.Item1.Name), - Type = NotificationType.PluginUpdateInstalled.ToString(), + NotificationType.PluginUpdateInstalled.ToString(), + Guid.Empty, + DateTime.UtcNow, + LogLevel.Trace) + { ShortOverview = string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("VersionNumber"), e.Argument.Item2.version), Overview = e.Argument.Item2.changelog - }); + }).ConfigureAwait(false); } - private void OnPluginUninstalled(object sender, GenericEventArgs e) + private async void OnPluginUninstalled(object sender, GenericEventArgs e) { - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format( + await CreateLogEntry(new ActivityLog( + string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("PluginUninstalledWithName"), e.Argument.Name), - Type = NotificationType.PluginUninstalled.ToString() - }); + NotificationType.PluginUninstalled.ToString(), + Guid.Empty, + DateTime.UtcNow, + LogLevel.Trace)) + .ConfigureAwait(false); } - private void OnPluginInstalled(object sender, GenericEventArgs e) + private async void OnPluginInstalled(object sender, GenericEventArgs e) { - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format( + await CreateLogEntry(new ActivityLog( + string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("PluginInstalledWithName"), e.Argument.name), - Type = NotificationType.PluginInstalled.ToString(), + NotificationType.PluginInstalled.ToString(), + Guid.Empty, + DateTime.UtcNow, + LogLevel.Trace) + { ShortOverview = string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("VersionNumber"), e.Argument.version) - }); + }).ConfigureAwait(false); } - private void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e) + private async void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e) { var installationInfo = e.InstallationInfo; - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format( + await CreateLogEntry(new ActivityLog( + string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("NameInstallFailed"), installationInfo.Name), - Type = NotificationType.InstallationFailed.ToString(), + NotificationType.InstallationFailed.ToString(), + Guid.Empty, + DateTime.UtcNow, + LogLevel.Trace) + { ShortOverview = string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("VersionNumber"), installationInfo.Version), Overview = e.Exception.Message - }); + }).ConfigureAwait(false); } - private void OnTaskCompleted(object sender, TaskCompletionEventArgs e) + private async void OnTaskCompleted(object sender, TaskCompletionEventArgs e) { var result = e.Result; var task = e.Task; @@ -495,22 +526,21 @@ namespace Emby.Server.Implementations.Activity vals.Add(e.Result.LongErrorMessage); } - CreateLogEntry(new ActivityLogEntry + await CreateLogEntry(new ActivityLog( + string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name), + NotificationType.TaskFailed.ToString(), + Guid.Empty, + DateTime.UtcNow, + LogLevel.Error) { - Name = string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("ScheduledTaskFailedWithName"), - task.Name), - Type = NotificationType.TaskFailed.ToString(), Overview = string.Join(Environment.NewLine, vals), - ShortOverview = runningTime, - Severity = LogLevel.Error - }); + ShortOverview = runningTime + }).ConfigureAwait(false); } } - private void CreateLogEntry(ActivityLogEntry entry) - => _activityManager.Create(entry); + private async Task CreateLogEntry(ActivityLog entry) + => await _activityManager.CreateAsync(entry).ConfigureAwait(false); /// public void Dispose() @@ -558,7 +588,7 @@ namespace Emby.Server.Implementations.Activity { int years = days / DaysInYear; values.Add(CreateValueString(years, "year")); - days %= DaysInYear; + days = days % DaysInYear; } // Number of months @@ -566,7 +596,7 @@ namespace Emby.Server.Implementations.Activity { int months = days / DaysInMonth; values.Add(CreateValueString(months, "month")); - days %= DaysInMonth; + days = days % DaysInMonth; } // Number of days diff --git a/Emby.Server.Implementations/Activity/ActivityManager.cs b/Emby.Server.Implementations/Activity/ActivityManager.cs deleted file mode 100644 index 81bebae3d..000000000 --- a/Emby.Server.Implementations/Activity/ActivityManager.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Activity; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.Querying; - -namespace Emby.Server.Implementations.Activity -{ - /// - /// The activity log manager. - /// - public class ActivityManager : IActivityManager - { - private readonly IActivityRepository _repo; - private readonly IUserManager _userManager; - - /// - /// Initializes a new instance of the class. - /// - /// The activity repository. - /// The user manager. - public ActivityManager(IActivityRepository repo, IUserManager userManager) - { - _repo = repo; - _userManager = userManager; - } - - /// - public event EventHandler> EntryCreated; - - public void Create(ActivityLogEntry entry) - { - entry.Date = DateTime.UtcNow; - - _repo.Create(entry); - - EntryCreated?.Invoke(this, new GenericEventArgs(entry)); - } - - /// - public QueryResult GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit) - { - var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit); - - foreach (var item in result.Items) - { - if (item.UserId == Guid.Empty) - { - continue; - } - - var user = _userManager.GetUserById(item.UserId); - - if (user != null) - { - var dto = _userManager.GetUserDto(user); - item.UserPrimaryImageTag = dto.PrimaryImageTag; - } - } - - return result; - } - - /// - public QueryResult GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit) - { - return GetActivityLogEntries(minDate, null, startIndex, limit); - } - } -} diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs deleted file mode 100644 index 22796ba3f..000000000 --- a/Emby.Server.Implementations/Activity/ActivityRepository.cs +++ /dev/null @@ -1,308 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using Emby.Server.Implementations.Data; -using MediaBrowser.Controller; -using MediaBrowser.Model.Activity; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Querying; -using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; - -namespace Emby.Server.Implementations.Activity -{ - /// - /// The activity log repository. - /// - public class ActivityRepository : BaseSqliteRepository, IActivityRepository - { - private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog"; - - private readonly IFileSystem _fileSystem; - - /// - /// Initializes a new instance of the class. - /// - /// The logger. - /// The server application paths. - /// The filesystem. - public ActivityRepository(ILogger logger, IServerApplicationPaths appPaths, IFileSystem fileSystem) - : base(logger) - { - DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db"); - _fileSystem = fileSystem; - } - - /// - /// Initializes the . - /// - public void Initialize() - { - try - { - InitializeInternal(); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error loading database file. Will reset and retry."); - - _fileSystem.DeleteFile(DbFilePath); - - InitializeInternal(); - } - } - - private void InitializeInternal() - { - using var connection = GetConnection(); - connection.RunQueries(new[] - { - "create table if not exists ActivityLog (Id INTEGER PRIMARY KEY, Name TEXT NOT NULL, Overview TEXT, ShortOverview TEXT, Type TEXT NOT NULL, ItemId TEXT, UserId TEXT, DateCreated DATETIME NOT NULL, LogSeverity TEXT NOT NULL)", - "drop index if exists idx_ActivityLogEntries" - }); - - TryMigrate(connection); - } - - private void TryMigrate(ManagedConnection connection) - { - try - { - if (TableExists(connection, "ActivityLogEntries")) - { - connection.RunQueries(new[] - { - "INSERT INTO ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) SELECT Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity FROM ActivityLogEntries", - "drop table if exists ActivityLogEntries" - }); - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Error migrating activity log database"); - } - } - - /// - public void Create(ActivityLogEntry entry) - { - if (entry == null) - { - throw new ArgumentNullException(nameof(entry)); - } - - using var connection = GetConnection(); - connection.RunInTransaction(db => - { - using var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)"); - statement.TryBind("@Name", entry.Name); - - statement.TryBind("@Overview", entry.Overview); - statement.TryBind("@ShortOverview", entry.ShortOverview); - statement.TryBind("@Type", entry.Type); - statement.TryBind("@ItemId", entry.ItemId); - - if (entry.UserId.Equals(Guid.Empty)) - { - statement.TryBindNull("@UserId"); - } - else - { - statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture)); - } - - statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue()); - statement.TryBind("@LogSeverity", entry.Severity.ToString()); - - statement.MoveNext(); - }, TransactionMode); - } - - /// - /// Adds the provided to this repository. - /// - /// The activity log entry. - /// If entry is null. - public void Update(ActivityLogEntry entry) - { - if (entry == null) - { - throw new ArgumentNullException(nameof(entry)); - } - - using var connection = GetConnection(); - connection.RunInTransaction(db => - { - using var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id"); - statement.TryBind("@Id", entry.Id); - - statement.TryBind("@Name", entry.Name); - statement.TryBind("@Overview", entry.Overview); - statement.TryBind("@ShortOverview", entry.ShortOverview); - statement.TryBind("@Type", entry.Type); - statement.TryBind("@ItemId", entry.ItemId); - - if (entry.UserId.Equals(Guid.Empty)) - { - statement.TryBindNull("@UserId"); - } - else - { - statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture)); - } - - statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue()); - statement.TryBind("@LogSeverity", entry.Severity.ToString()); - - statement.MoveNext(); - }, TransactionMode); - } - - /// - public QueryResult GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit) - { - var commandText = BaseActivitySelectText; - var whereClauses = new List(); - - if (minDate.HasValue) - { - whereClauses.Add("DateCreated>=@DateCreated"); - } - - if (hasUserId.HasValue) - { - whereClauses.Add(hasUserId.Value ? "UserId not null" : "UserId is null"); - } - - var whereTextWithoutPaging = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); - - if (startIndex.HasValue && startIndex.Value > 0) - { - var pagingWhereText = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); - - whereClauses.Add( - string.Format( - CultureInfo.InvariantCulture, - "Id NOT IN (SELECT Id FROM ActivityLog {0} ORDER BY DateCreated DESC LIMIT {1})", - pagingWhereText, - startIndex.Value)); - } - - var whereText = whereClauses.Count == 0 ? - string.Empty : - " where " + string.Join(" AND ", whereClauses.ToArray()); - - commandText += whereText; - - commandText += " ORDER BY DateCreated DESC"; - - if (limit.HasValue) - { - commandText += " LIMIT " + limit.Value.ToString(CultureInfo.InvariantCulture); - } - - var statementTexts = new[] - { - commandText, - "select count (Id) from ActivityLog" + whereTextWithoutPaging - }; - - var list = new List(); - var result = new QueryResult(); - - using var connection = GetConnection(true); - connection.RunInTransaction( - db => - { - var statements = PrepareAll(db, statementTexts).ToList(); - - using (var statement = statements[0]) - { - if (minDate.HasValue) - { - statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue()); - } - - list.AddRange(statement.ExecuteQuery().Select(GetEntry)); - } - - using (var statement = statements[1]) - { - if (minDate.HasValue) - { - statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue()); - } - - result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First(); - } - }, - ReadTransactionMode); - - result.Items = list; - return result; - } - - private static ActivityLogEntry GetEntry(IReadOnlyList reader) - { - var index = 0; - - var info = new ActivityLogEntry - { - Id = reader[index].ToInt64() - }; - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.Name = reader[index].ToString(); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.Overview = reader[index].ToString(); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.ShortOverview = reader[index].ToString(); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.Type = reader[index].ToString(); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.ItemId = reader[index].ToString(); - } - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.UserId = new Guid(reader[index].ToString()); - } - - index++; - info.Date = reader[index].ReadDateTime(); - - index++; - if (reader[index].SQLiteType != SQLiteType.Null) - { - info.Severity = Enum.Parse(reader[index].ToString(), true); - } - - return info; - } - } -} diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ffc916b98..ddd9c7953 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -22,7 +22,6 @@ using Emby.Dlna.Ssdp; using Emby.Drawing; using Emby.Notifications; using Emby.Photos; -using Emby.Server.Implementations.Activity; using Emby.Server.Implementations.Archiving; using Emby.Server.Implementations.Channels; using Emby.Server.Implementations.Collections; @@ -47,6 +46,8 @@ using Emby.Server.Implementations.Session; using Emby.Server.Implementations.SocketSharp; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; +using Jellyfin.Server.Implementations; +using Jellyfin.Server.Implementations.Activity; using MediaBrowser.Api; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; @@ -94,7 +95,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; @@ -103,6 +103,7 @@ using MediaBrowser.WebDashboard.Api; using MediaBrowser.XbmcMetadata.Providers; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; @@ -553,6 +554,13 @@ namespace Emby.Server.Implementations return Logger; }); + // TODO: properly set up scoping and switch to AddDbContextPool + serviceCollection.AddDbContext( + options => options.UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"), + ServiceLifetime.Transient); + + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(_fileSystemManager); serviceCollection.AddSingleton(); @@ -663,7 +671,6 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); @@ -696,7 +703,6 @@ namespace Emby.Server.Implementations ((SqliteDisplayPreferencesRepository)Resolve()).Initialize(); ((AuthenticationRepository)Resolve()).Initialize(); ((SqliteUserRepository)Resolve()).Initialize(); - ((ActivityRepository)Resolve()).Initialize(); SetStaticProperties(); diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 44fc932e3..dccbe2a9a 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -1,4 +1,4 @@ - + @@ -9,6 +9,7 @@ + @@ -50,7 +51,7 @@ - netstandard2.1 + netcoreapp3.1 false true diff --git a/Jellyfin.Data/DbContexts/Jellyfin.cs b/Jellyfin.Data/DbContexts/Jellyfin.cs deleted file mode 100644 index fd488ce7d..000000000 --- a/Jellyfin.Data/DbContexts/Jellyfin.cs +++ /dev/null @@ -1,1140 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.ComponentModel.DataAnnotations.Schema; -using Microsoft.EntityFrameworkCore; - -namespace Jellyfin.Data.DbContexts -{ - /// - public partial class Jellyfin : DbContext - { - #region DbSets - public virtual Microsoft.EntityFrameworkCore.DbSet Artwork { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Books { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet BookMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Chapters { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Collections { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet CollectionItems { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Companies { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet CompanyMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet CustomItems { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet CustomItemMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Episodes { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet EpisodeMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Genres { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Groups { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Libraries { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet LibraryItems { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet LibraryRoot { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet MediaFiles { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet MediaFileStream { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Metadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet MetadataProviders { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet MetadataProviderIds { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Movies { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet MovieMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet MusicAlbums { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet MusicAlbumMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Permissions { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet People { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet PersonRoles { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Photo { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet PhotoMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Preferences { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet ProviderMappings { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Ratings { get; set; } - - /// - /// Repository for global::Jellyfin.Data.Entities.RatingSource - This is the entity to - /// store review ratings, not age ratings - /// - public virtual Microsoft.EntityFrameworkCore.DbSet RatingSources { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Releases { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Seasons { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet SeasonMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Series { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet SeriesMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Tracks { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet TrackMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Users { get; set; } - #endregion DbSets - - /// - /// Default connection string - /// - public static string ConnectionString { get; set; } = @"Data Source=jellyfin.db"; - - /// - public Jellyfin(DbContextOptions options) : base(options) - { - } - - partial void CustomInit(DbContextOptionsBuilder optionsBuilder); - - /// - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - CustomInit(optionsBuilder); - } - - partial void OnModelCreatingImpl(ModelBuilder modelBuilder); - partial void OnModelCreatedImpl(ModelBuilder modelBuilder); - - /// - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - OnModelCreatingImpl(modelBuilder); - - modelBuilder.HasDefaultSchema("jellyfin"); - - modelBuilder.Entity() - .ToTable("Artwork") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Path) - .HasMaxLength(65535) - .IsRequired() - .HasField("_Path") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Kind) - .IsRequired() - .HasField("_Kind") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity().HasIndex(t => t.Kind); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - - modelBuilder.Entity() - .HasMany(x => x.BookMetadata) - .WithOne() - .HasForeignKey("BookMetadata_BookMetadata_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Releases) - .WithOne() - .HasForeignKey("Release_Releases_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.ISBN) - .HasField("_ISBN") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.Publishers) - .WithOne() - .HasForeignKey("Company_Publishers_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("Chapter") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(1024) - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Language) - .HasMaxLength(3) - .IsRequired() - .HasField("_Language") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.TimeStart) - .IsRequired() - .HasField("_TimeStart") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.TimeEnd) - .HasField("_TimeEnd") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - - modelBuilder.Entity() - .ToTable("Collection") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(1024) - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasMany(x => x.CollectionItem) - .WithOne() - .HasForeignKey("CollectionItem_CollectionItem_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("CollectionItem") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasOne(x => x.LibraryItem) - .WithOne() - .HasForeignKey("LibraryItem_Id") - .IsRequired(); - modelBuilder.Entity() - .HasOne(x => x.Next) - .WithOne() - .HasForeignKey("CollectionItem_Next_Id") - .IsRequired(); - modelBuilder.Entity() - .HasOne(x => x.Previous) - .WithOne() - .HasForeignKey("CollectionItem_Previous_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("Company") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasMany(x => x.CompanyMetadata) - .WithOne() - .HasForeignKey("CompanyMetadata_CompanyMetadata_Id") - .IsRequired(); - modelBuilder.Entity() - .HasOne(x => x.Parent) - .WithOne() - .HasForeignKey("Company_Parent_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.Description) - .HasMaxLength(65535) - .HasField("_Description") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Headquarters) - .HasMaxLength(255) - .HasField("_Headquarters") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Country) - .HasMaxLength(2) - .HasField("_Country") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Homepage) - .HasMaxLength(1024) - .HasField("_Homepage") - .UsePropertyAccessMode(PropertyAccessMode.Property); - - modelBuilder.Entity() - .HasMany(x => x.CustomItemMetadata) - .WithOne() - .HasForeignKey("CustomItemMetadata_CustomItemMetadata_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Releases) - .WithOne() - .HasForeignKey("Release_Releases_Id") - .IsRequired(); - - - modelBuilder.Entity() - .Property(t => t.EpisodeNumber) - .HasField("_EpisodeNumber") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.Releases) - .WithOne() - .HasForeignKey("Release_Releases_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.EpisodeMetadata) - .WithOne() - .HasForeignKey("EpisodeMetadata_EpisodeMetadata_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.Outline) - .HasMaxLength(1024) - .HasField("_Outline") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Plot) - .HasMaxLength(65535) - .HasField("_Plot") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Tagline) - .HasMaxLength(1024) - .HasField("_Tagline") - .UsePropertyAccessMode(PropertyAccessMode.Property); - - modelBuilder.Entity() - .ToTable("Genre") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(255) - .IsRequired() - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity().HasIndex(t => t.Name) - .IsUnique(); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - - modelBuilder.Entity() - .ToTable("Groups") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(255) - .IsRequired(); - modelBuilder.Entity().Property("Timestamp").IsConcurrencyToken(); - modelBuilder.Entity() - .HasMany(x => x.GroupPermissions) - .WithOne() - .HasForeignKey("Permission_GroupPermissions_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.ProviderMappings) - .WithOne() - .HasForeignKey("ProviderMapping_ProviderMappings_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Preferences) - .WithOne() - .HasForeignKey("Preference_Preferences_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("Library") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(1024) - .IsRequired() - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - - modelBuilder.Entity() - .ToTable("LibraryItem") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.UrlId) - .IsRequired() - .HasField("_UrlId") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity().HasIndex(t => t.UrlId) - .IsUnique(); - modelBuilder.Entity() - .Property(t => t.DateAdded) - .IsRequired() - .HasField("_DateAdded") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasOne(x => x.LibraryRoot) - .WithOne() - .HasForeignKey("LibraryRoot_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("LibraryRoot") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Path) - .HasMaxLength(65535) - .IsRequired() - .HasField("_Path") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.NetworkPath) - .HasMaxLength(65535) - .HasField("_NetworkPath") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasOne(x => x.Library) - .WithOne() - .HasForeignKey("Library_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("MediaFile") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Path) - .HasMaxLength(65535) - .IsRequired() - .HasField("_Path") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Kind) - .IsRequired() - .HasField("_Kind") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasMany(x => x.MediaFileStreams) - .WithOne() - .HasForeignKey("MediaFileStream_MediaFileStreams_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("MediaFileStream") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.StreamNumber) - .IsRequired() - .HasField("_StreamNumber") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - - modelBuilder.Entity() - .ToTable("Metadata") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Title) - .HasMaxLength(1024) - .IsRequired() - .HasField("_Title") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.OriginalTitle) - .HasMaxLength(1024) - .HasField("_OriginalTitle") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.SortTitle) - .HasMaxLength(1024) - .HasField("_SortTitle") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Language) - .HasMaxLength(3) - .IsRequired() - .HasField("_Language") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.ReleaseDate) - .HasField("_ReleaseDate") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.DateAdded) - .IsRequired() - .HasField("_DateAdded") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.DateModified) - .IsRequired() - .HasField("_DateModified") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasMany(x => x.PersonRoles) - .WithOne() - .HasForeignKey("PersonRole_PersonRoles_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Genres) - .WithOne() - .HasForeignKey("Genre_Genres_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Artwork) - .WithOne() - .HasForeignKey("Artwork_Artwork_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Ratings) - .WithOne() - .HasForeignKey("Rating_Ratings_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Sources) - .WithOne() - .HasForeignKey("MetadataProviderId_Sources_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("MetadataProvider") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(1024) - .IsRequired() - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - - modelBuilder.Entity() - .ToTable("MetadataProviderId") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.ProviderId) - .HasMaxLength(255) - .IsRequired() - .HasField("_ProviderId") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasOne(x => x.MetadataProvider) - .WithOne() - .HasForeignKey("MetadataProvider_Id") - .IsRequired(); - - modelBuilder.Entity() - .HasMany(x => x.Releases) - .WithOne() - .HasForeignKey("Release_Releases_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.MovieMetadata) - .WithOne() - .HasForeignKey("MovieMetadata_MovieMetadata_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.Outline) - .HasMaxLength(1024) - .HasField("_Outline") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Plot) - .HasMaxLength(65535) - .HasField("_Plot") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Tagline) - .HasMaxLength(1024) - .HasField("_Tagline") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Country) - .HasMaxLength(2) - .HasField("_Country") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.Studios) - .WithOne() - .HasForeignKey("Company_Studios_Id") - .IsRequired(); - - modelBuilder.Entity() - .HasMany(x => x.MusicAlbumMetadata) - .WithOne() - .HasForeignKey("MusicAlbumMetadata_MusicAlbumMetadata_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Tracks) - .WithOne() - .HasForeignKey("Track_Tracks_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.Barcode) - .HasMaxLength(255) - .HasField("_Barcode") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.LabelNumber) - .HasMaxLength(255) - .HasField("_LabelNumber") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Country) - .HasMaxLength(2) - .HasField("_Country") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.Labels) - .WithOne() - .HasForeignKey("Company_Labels_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("Permissions") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Kind) - .IsRequired() - .HasField("_Kind") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Value) - .IsRequired(); - modelBuilder.Entity().Property("Timestamp").IsConcurrencyToken(); - - modelBuilder.Entity() - .ToTable("Person") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.UrlId) - .IsRequired() - .HasField("_UrlId") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(1024) - .IsRequired() - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.SourceId) - .HasMaxLength(255) - .HasField("_SourceId") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.DateAdded) - .IsRequired() - .HasField("_DateAdded") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.DateModified) - .IsRequired() - .HasField("_DateModified") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasMany(x => x.Sources) - .WithOne() - .HasForeignKey("MetadataProviderId_Sources_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("PersonRole") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Role) - .HasMaxLength(1024) - .HasField("_Role") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Type) - .IsRequired() - .HasField("_Type") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasOne(x => x.Person) - .WithOne() - .HasForeignKey("Person_Id") - .IsRequired() - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity() - .HasOne(x => x.Artwork) - .WithOne() - .HasForeignKey("Artwork_Artwork_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Sources) - .WithOne() - .HasForeignKey("MetadataProviderId_Sources_Id") - .IsRequired(); - - modelBuilder.Entity() - .HasMany(x => x.PhotoMetadata) - .WithOne() - .HasForeignKey("PhotoMetadata_PhotoMetadata_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Releases) - .WithOne() - .HasForeignKey("Release_Releases_Id") - .IsRequired(); - - - modelBuilder.Entity() - .ToTable("Preferences") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Kind) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.Value) - .HasMaxLength(65535) - .IsRequired(); - modelBuilder.Entity().Property("Timestamp").IsConcurrencyToken(); - - modelBuilder.Entity() - .ToTable("ProviderMappings") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.ProviderName) - .HasMaxLength(255) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.ProviderSecrets) - .HasMaxLength(65535) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.ProviderData) - .HasMaxLength(65535) - .IsRequired(); - modelBuilder.Entity().Property("Timestamp").IsConcurrencyToken(); - - modelBuilder.Entity() - .ToTable("Rating") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Value) - .IsRequired() - .HasField("_Value") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Votes) - .HasField("_Votes") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasOne(x => x.RatingType) - .WithOne() - .HasForeignKey("RatingSource_RatingType_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("RatingType") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(1024) - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.MaximumValue) - .IsRequired() - .HasField("_MaximumValue") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.MinimumValue) - .IsRequired() - .HasField("_MinimumValue") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasOne(x => x.Source) - .WithOne() - .HasForeignKey("MetadataProviderId_Source_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("Release") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(1024) - .IsRequired() - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasMany(x => x.MediaFiles) - .WithOne() - .HasForeignKey("MediaFile_MediaFiles_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Chapters) - .WithOne() - .HasForeignKey("Chapter_Chapters_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.SeasonNumber) - .HasField("_SeasonNumber") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.SeasonMetadata) - .WithOne() - .HasForeignKey("SeasonMetadata_SeasonMetadata_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Episodes) - .WithOne() - .HasForeignKey("Episode_Episodes_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.Outline) - .HasMaxLength(1024) - .HasField("_Outline") - .UsePropertyAccessMode(PropertyAccessMode.Property); - - modelBuilder.Entity() - .Property(t => t.AirsDayOfWeek) - .HasField("_AirsDayOfWeek") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.AirsTime) - .HasField("_AirsTime") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.FirstAired) - .HasField("_FirstAired") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.SeriesMetadata) - .WithOne() - .HasForeignKey("SeriesMetadata_SeriesMetadata_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Seasons) - .WithOne() - .HasForeignKey("Season_Seasons_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.Outline) - .HasMaxLength(1024) - .HasField("_Outline") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Plot) - .HasMaxLength(65535) - .HasField("_Plot") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Tagline) - .HasMaxLength(1024) - .HasField("_Tagline") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Country) - .HasMaxLength(2) - .HasField("_Country") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.Networks) - .WithOne() - .HasForeignKey("Company_Networks_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.TrackNumber) - .HasField("_TrackNumber") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.Releases) - .WithOne() - .HasForeignKey("Release_Releases_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.TrackMetadata) - .WithOne() - .HasForeignKey("TrackMetadata_TrackMetadata_Id") - .IsRequired(); - - - modelBuilder.Entity() - .ToTable("Users") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.LastLoginTimestamp) - .IsRequired() - .IsRowVersion(); - modelBuilder.Entity() - .Property(t => t.Username) - .HasMaxLength(255) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.Password) - .HasMaxLength(65535); - modelBuilder.Entity() - .Property(t => t.MustUpdatePassword) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.AudioLanguagePreference) - .HasMaxLength(255) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.AuthenticationProviderId) - .HasMaxLength(255) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.GroupedFolders) - .HasMaxLength(65535); - modelBuilder.Entity() - .Property(t => t.InvalidLoginAttemptCount) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.LatestItemExcludes) - .HasMaxLength(65535); - modelBuilder.Entity() - .Property(t => t.MyMediaExcludes) - .HasMaxLength(65535); - modelBuilder.Entity() - .Property(t => t.OrderedViews) - .HasMaxLength(65535); - modelBuilder.Entity() - .Property(t => t.SubtitleMode) - .HasMaxLength(255) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.PlayDefaultAudioTrack) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.SubtitleLanguagePrefernce) - .HasMaxLength(255); - modelBuilder.Entity() - .HasMany(x => x.Groups) - .WithOne() - .HasForeignKey("Group_Groups_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Permissions) - .WithOne() - .HasForeignKey("Permission_Permissions_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.ProviderMappings) - .WithOne() - .HasForeignKey("ProviderMapping_ProviderMappings_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Preferences) - .WithOne() - .HasForeignKey("Preference_Preferences_Id") - .IsRequired(); - - OnModelCreatedImpl(modelBuilder); - } - } -} diff --git a/Jellyfin.Data/Entities/ActivityLog.cs b/Jellyfin.Data/Entities/ActivityLog.cs new file mode 100644 index 000000000..633838991 --- /dev/null +++ b/Jellyfin.Data/Entities/ActivityLog.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Jellyfin.Data.Entities +{ + [Table("ActivityLog")] + public partial class ActivityLog + { + partial void Init(); + + /// + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected ActivityLog() + { + Init(); + } + + /// + /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// + public static ActivityLog CreateActivityLogUnsafe() + { + return new ActivityLog(); + } + + /// + /// Public constructor with required data + /// + /// + /// + /// + /// + /// + public ActivityLog(string name, string type, Guid userid, DateTime datecreated, Microsoft.Extensions.Logging.LogLevel logseverity) + { + if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); + this.Name = name; + + if (string.IsNullOrEmpty(type)) throw new ArgumentNullException(nameof(type)); + this.Type = type; + + this.UserId = userid; + + this.DateCreated = datecreated; + + this.LogSeverity = logseverity; + + + Init(); + } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// + /// + /// + /// + /// + public static ActivityLog Create(string name, string type, Guid userid, DateTime datecreated, Microsoft.Extensions.Logging.LogLevel logseverity) + { + return new ActivityLog(name, type, userid, datecreated, logseverity); + } + + /************************************************************************* + * Properties + *************************************************************************/ + + /// + /// Identity, Indexed, Required + /// + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; protected set; } + + /// + /// Required, Max length = 512 + /// + [Required] + [MaxLength(512)] + [StringLength(512)] + public string Name { get; set; } + + /// + /// Max length = 512 + /// + [MaxLength(512)] + [StringLength(512)] + public string Overview { get; set; } + + /// + /// Max length = 512 + /// + [MaxLength(512)] + [StringLength(512)] + public string ShortOverview { get; set; } + + /// + /// Required, Max length = 256 + /// + [Required] + [MaxLength(256)] + [StringLength(256)] + public string Type { get; set; } + + /// + /// Required + /// + [Required] + public Guid UserId { get; set; } + + /// + /// Max length = 256 + /// + [MaxLength(256)] + [StringLength(256)] + public string ItemId { get; set; } + + /// + /// Required + /// + [Required] + public DateTime DateCreated { get; set; } + + /// + /// Required + /// + [Required] + public Microsoft.Extensions.Logging.LogLevel LogSeverity { get; set; } + + /// + /// Required, ConcurrenyToken + /// + [ConcurrencyCheck] + [Required] + public uint RowVersion { get; set; } + + public void OnSavingChanges() + { + RowVersion++; + } + + } +} + diff --git a/Jellyfin.Data/ISavingChanges.cs b/Jellyfin.Data/ISavingChanges.cs new file mode 100644 index 000000000..5388b921d --- /dev/null +++ b/Jellyfin.Data/ISavingChanges.cs @@ -0,0 +1,9 @@ +#pragma warning disable CS1591 + +namespace Jellyfin.Data +{ + public interface ISavingChanges + { + void OnSavingChanges(); + } +} diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index 73ea593b0..8eae366ba 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -1,12 +1,30 @@ - netstandard2.0 + netstandard2.0;netstandard2.1 + false + true + + ../jellyfin.ruleset + + + + + + + + + + - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs new file mode 100644 index 000000000..d7bbf793c --- /dev/null +++ b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Querying; + +namespace Jellyfin.Server.Implementations.Activity +{ + /// + /// Manages the storage and retrieval of instances. + /// + public class ActivityManager : IActivityManager + { + private JellyfinDbProvider _provider; + + /// + /// Initializes a new instance of the class. + /// + /// The Jellyfin database provider. + public ActivityManager(JellyfinDbProvider provider) + { + _provider = provider; + } + + /// + public event EventHandler> EntryCreated; + + /// + public void Create(ActivityLog entry) + { + using var dbContext = _provider.CreateContext(); + dbContext.ActivityLogs.Add(entry); + dbContext.SaveChanges(); + + EntryCreated?.Invoke(this, new GenericEventArgs(ConvertToOldModel(entry))); + } + + /// + public async Task CreateAsync(ActivityLog entry) + { + using var dbContext = _provider.CreateContext(); + await dbContext.ActivityLogs.AddAsync(entry); + await dbContext.SaveChangesAsync().ConfigureAwait(false); + + EntryCreated?.Invoke(this, new GenericEventArgs(ConvertToOldModel(entry))); + } + + /// + public QueryResult GetPagedResult( + Func, IEnumerable> func, + int? startIndex, + int? limit) + { + using var dbContext = _provider.CreateContext(); + + var result = func.Invoke(dbContext.ActivityLogs).AsQueryable(); + + if (startIndex.HasValue) + { + result = result.Where(entry => entry.Id >= startIndex.Value); + } + + if (limit.HasValue) + { + result = result.OrderByDescending(entry => entry.DateCreated).Take(limit.Value); + } + + // This converts the objects from the new database model to the old for compatibility with the existing API. + var list = result.Select(entry => ConvertToOldModel(entry)).ToList(); + + return new QueryResult() + { + Items = list, + TotalRecordCount = list.Count + }; + } + + /// + public QueryResult GetPagedResult(int? startIndex, int? limit) + { + return GetPagedResult(logs => logs, startIndex, limit); + } + + private static ActivityLogEntry ConvertToOldModel(ActivityLog entry) + { + return new ActivityLogEntry + { + Id = entry.Id, + Name = entry.Name, + Overview = entry.Overview, + ShortOverview = entry.ShortOverview, + Type = entry.Type, + ItemId = entry.ItemId, + UserId = entry.UserId, + Date = entry.DateCreated, + Severity = entry.LogSeverity + }; + } + } +} diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj new file mode 100644 index 000000000..a31f28f64 --- /dev/null +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -0,0 +1,34 @@ + + + + netcoreapp3.1 + false + true + true + + + + ../jellyfin.ruleset + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs new file mode 100644 index 000000000..9c1a23877 --- /dev/null +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -0,0 +1,119 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1201 // Constuctors should not follow properties +#pragma warning disable SA1516 // Elements should be followed by a blank line +#pragma warning disable SA1623 // Property's documentation should begin with gets or sets +#pragma warning disable SA1629 // Documentation should end with a period +#pragma warning disable SA1648 // Inheritdoc should be used with inheriting class + +using System.Linq; +using Jellyfin.Data; +using Jellyfin.Data.Entities; +using Microsoft.EntityFrameworkCore; + +namespace Jellyfin.Server.Implementations +{ + /// + public partial class JellyfinDb : DbContext + { + public virtual DbSet ActivityLogs { get; set; } + public virtual DbSet Artwork { get; set; } + public virtual DbSet Books { get; set; } + public virtual DbSet BookMetadata { get; set; } + public virtual DbSet Chapters { get; set; } + public virtual DbSet Collections { get; set; } + public virtual DbSet CollectionItems { get; set; } + public virtual DbSet Companies { get; set; } + public virtual DbSet CompanyMetadata { get; set; } + public virtual DbSet CustomItems { get; set; } + public virtual DbSet CustomItemMetadata { get; set; } + 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; } + public virtual DbSet MediaFiles { get; set; } + public virtual DbSet MediaFileStream { get; set; } + public virtual DbSet Metadata { get; set; } + public virtual DbSet MetadataProviders { get; set; } + public virtual DbSet MetadataProviderIds { get; set; } + public virtual DbSet Movies { get; set; } + 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; } + + /// + /// Repository for global::Jellyfin.Data.Entities.RatingSource - This is the entity to + /// store review ratings, not age ratings + /// + public virtual DbSet RatingSources { get; set; } + public virtual DbSet Releases { get; set; } + public virtual DbSet Seasons { get; set; } + public virtual DbSet SeasonMetadata { get; set; } + public virtual DbSet Series { get; set; } + public virtual DbSet SeriesMetadata { get; set; } + public virtual DbSet Tracks { get; set; } + public virtual DbSet TrackMetadata { get; set; } + public virtual DbSet Users { get; set; } + + /// + /// Gets or sets the default connection string. + /// + public static string ConnectionString { get; set; } = @"Data Source=jellyfin.db"; + + /// + public JellyfinDb(DbContextOptions options) : base(options) + { + } + + partial void CustomInit(DbContextOptionsBuilder optionsBuilder); + + /// + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + CustomInit(optionsBuilder); + } + + partial void OnModelCreatingImpl(ModelBuilder modelBuilder); + partial void OnModelCreatedImpl(ModelBuilder modelBuilder); + + /// + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + OnModelCreatingImpl(modelBuilder); + + modelBuilder.HasDefaultSchema("jellyfin"); + + modelBuilder.Entity().HasIndex(t => t.Kind); + + modelBuilder.Entity().HasIndex(t => t.Name) + .IsUnique(); + + modelBuilder.Entity().HasIndex(t => t.UrlId) + .IsUnique(); + + OnModelCreatedImpl(modelBuilder); + } + + public override int SaveChanges() + { + foreach (var entity in ChangeTracker.Entries().Where(e => e.State == EntityState.Modified)) + { + var saveEntity = entity.Entity as ISavingChanges; + saveEntity.OnSavingChanges(); + } + + return base.SaveChanges(); + } + } +} diff --git a/Jellyfin.Server.Implementations/JellyfinDbProvider.cs b/Jellyfin.Server.Implementations/JellyfinDbProvider.cs new file mode 100644 index 000000000..8fdeab088 --- /dev/null +++ b/Jellyfin.Server.Implementations/JellyfinDbProvider.cs @@ -0,0 +1,33 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace Jellyfin.Server.Implementations +{ + /// + /// Factory class for generating new instances. + /// + public class JellyfinDbProvider + { + private readonly IServiceProvider _serviceProvider; + + /// + /// Initializes a new instance of the class. + /// + /// The application's service provider. + public JellyfinDbProvider(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + serviceProvider.GetService().Database.Migrate(); + } + + /// + /// Creates a new context. + /// + /// The newly created context. + public JellyfinDb CreateContext() + { + return _serviceProvider.GetService(); + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/20200430215054_InitialSchema.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200430215054_InitialSchema.Designer.cs new file mode 100644 index 000000000..3fb0fd51e --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200430215054_InitialSchema.Designer.cs @@ -0,0 +1,1513 @@ +#pragma warning disable CS1591 + +// +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("20200430215054_InitialSchema")] + partial class InitialSchema + { + 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.Artwork", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("PersonRole_PersonRoles_Id") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Kind"); + + b.HasIndex("PersonRole_PersonRoles_Id"); + + b.ToTable("Artwork"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Chapter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Chapter_Chapters_Id") + .HasColumnType("INTEGER"); + + b.Property("Language") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(3); + + b.Property("Name") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("TimeEnd") + .HasColumnType("INTEGER"); + + b.Property("TimeStart") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Chapter_Chapters_Id"); + + b.ToTable("Chapter"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Collection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Collection"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.CollectionItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CollectionItem_CollectionItem_Id") + .HasColumnType("INTEGER"); + + b.Property("CollectionItem_Next_Id") + .HasColumnType("INTEGER"); + + b.Property("CollectionItem_Previous_Id") + .HasColumnType("INTEGER"); + + b.Property("LibraryItem_Id") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CollectionItem_CollectionItem_Id"); + + b.HasIndex("CollectionItem_Next_Id"); + + b.HasIndex("CollectionItem_Previous_Id"); + + b.HasIndex("LibraryItem_Id"); + + b.ToTable("CollectionItem"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Company", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Company_Labels_Id") + .HasColumnType("INTEGER"); + + b.Property("Company_Networks_Id") + .HasColumnType("INTEGER"); + + b.Property("Company_Parent_Id") + .HasColumnType("INTEGER"); + + b.Property("Company_Publishers_Id") + .HasColumnType("INTEGER"); + + b.Property("Company_Studios_Id") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Company_Labels_Id"); + + b.HasIndex("Company_Networks_Id"); + + b.HasIndex("Company_Parent_Id"); + + b.HasIndex("Company_Publishers_Id"); + + b.HasIndex("Company_Studios_Id"); + + b.ToTable("Company"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Genre", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("PersonRole_PersonRoles_Id") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("PersonRole_PersonRoles_Id"); + + b.ToTable("Genre"); + }); + + 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.Library", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Library"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.LibraryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LibraryRoot_Id") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("UrlId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryRoot_Id"); + + b.HasIndex("UrlId") + .IsUnique(); + + b.ToTable("LibraryItem"); + + b.HasDiscriminator("Discriminator").HasValue("LibraryItem"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.LibraryRoot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Library_Id") + .HasColumnType("INTEGER"); + + b.Property("NetworkPath") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Library_Id"); + + b.ToTable("LibraryRoot"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MediaFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("MediaFile_MediaFiles_Id") + .HasColumnType("INTEGER"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MediaFile_MediaFiles_Id"); + + b.ToTable("MediaFile"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MediaFileStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("MediaFileStream_MediaFileStreams_Id") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("StreamNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MediaFileStream_MediaFileStreams_Id"); + + b.ToTable("MediaFileStream"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Metadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("DateModified") + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Language") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(3); + + b.Property("OriginalTitle") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SortTitle") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.HasKey("Id"); + + b.ToTable("Metadata"); + + b.HasDiscriminator("Discriminator").HasValue("Metadata"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MetadataProvider", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("MetadataProvider"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MetadataProviderId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("MetadataProviderId_Sources_Id") + .HasColumnType("INTEGER"); + + b.Property("MetadataProvider_Id") + .HasColumnType("INTEGER"); + + b.Property("PersonRole_PersonRoles_Id") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MetadataProviderId_Sources_Id"); + + b.HasIndex("MetadataProvider_Id"); + + b.HasIndex("PersonRole_PersonRoles_Id"); + + b.ToTable("MetadataProviderId"); + }); + + 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.Person", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("DateModified") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SourceId") + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("UrlId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Person"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.PersonRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Artwork_Artwork_Id") + .HasColumnType("INTEGER"); + + b.Property("PersonRole_PersonRoles_Id") + .HasColumnType("INTEGER"); + + b.Property("Person_Id") + .HasColumnType("INTEGER"); + + b.Property("Role") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Artwork_Artwork_Id"); + + b.HasIndex("PersonRole_PersonRoles_Id"); + + b.HasIndex("Person_Id"); + + b.ToTable("PersonRole"); + }); + + 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.Rating", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("PersonRole_PersonRoles_Id") + .HasColumnType("INTEGER"); + + b.Property("RatingSource_RatingType_Id") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("REAL"); + + b.Property("Votes") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PersonRole_PersonRoles_Id"); + + b.HasIndex("RatingSource_RatingType_Id"); + + b.ToTable("Rating"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.RatingSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("MaximumValue") + .HasColumnType("REAL"); + + b.Property("MetadataProviderId_Source_Id") + .HasColumnType("INTEGER"); + + b.Property("MinimumValue") + .HasColumnType("REAL"); + + b.Property("Name") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MetadataProviderId_Source_Id"); + + b.ToTable("RatingSource"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Release", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("Release_Releases_Id") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Release_Releases_Id"); + + b.ToTable("Release"); + }); + + 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.Book", b => + { + b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); + + b.ToTable("Book"); + + b.HasDiscriminator().HasValue("Book"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.CustomItem", b => + { + b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); + + b.ToTable("LibraryItem"); + + b.HasDiscriminator().HasValue("CustomItem"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Episode", b => + { + b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("Episode_Episodes_Id") + .HasColumnType("INTEGER"); + + b.HasIndex("Episode_Episodes_Id"); + + b.ToTable("Episode"); + + b.HasDiscriminator().HasValue("Episode"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Movie", b => + { + b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); + + b.ToTable("Movie"); + + b.HasDiscriminator().HasValue("Movie"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MusicAlbum", b => + { + b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); + + b.ToTable("MusicAlbum"); + + b.HasDiscriminator().HasValue("MusicAlbum"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Photo", b => + { + b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); + + b.ToTable("Photo"); + + b.HasDiscriminator().HasValue("Photo"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Season", b => + { + b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.Property("Season_Seasons_Id") + .HasColumnType("INTEGER"); + + b.HasIndex("Season_Seasons_Id"); + + b.ToTable("Season"); + + b.HasDiscriminator().HasValue("Season"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Series", b => + { + b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); + + b.Property("AirsDayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("AirsTime") + .HasColumnType("TEXT"); + + b.Property("FirstAired") + .HasColumnType("TEXT"); + + b.ToTable("Series"); + + b.HasDiscriminator().HasValue("Series"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Track", b => + { + b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); + + b.Property("TrackNumber") + .HasColumnType("INTEGER"); + + b.Property("Track_Tracks_Id") + .HasColumnType("INTEGER"); + + b.HasIndex("Track_Tracks_Id"); + + b.ToTable("Track"); + + b.HasDiscriminator().HasValue("Track"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.BookMetadata", b => + { + b.HasBaseType("Jellyfin.Data.Entities.Metadata"); + + b.Property("BookMetadata_BookMetadata_Id") + .HasColumnType("INTEGER"); + + b.Property("ISBN") + .HasColumnType("INTEGER"); + + b.HasIndex("BookMetadata_BookMetadata_Id"); + + b.ToTable("Metadata"); + + b.HasDiscriminator().HasValue("BookMetadata"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.CompanyMetadata", b => + { + b.HasBaseType("Jellyfin.Data.Entities.Metadata"); + + b.Property("CompanyMetadata_CompanyMetadata_Id") + .HasColumnType("INTEGER"); + + b.Property("Country") + .HasColumnType("TEXT") + .HasMaxLength(2); + + b.Property("Description") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("Headquarters") + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("Homepage") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.HasIndex("CompanyMetadata_CompanyMetadata_Id"); + + b.ToTable("CompanyMetadata"); + + b.HasDiscriminator().HasValue("CompanyMetadata"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemMetadata", b => + { + b.HasBaseType("Jellyfin.Data.Entities.Metadata"); + + b.Property("CustomItemMetadata_CustomItemMetadata_Id") + .HasColumnType("INTEGER"); + + b.HasIndex("CustomItemMetadata_CustomItemMetadata_Id"); + + b.ToTable("CustomItemMetadata"); + + b.HasDiscriminator().HasValue("CustomItemMetadata"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.EpisodeMetadata", b => + { + b.HasBaseType("Jellyfin.Data.Entities.Metadata"); + + b.Property("EpisodeMetadata_EpisodeMetadata_Id") + .HasColumnType("INTEGER"); + + b.Property("Outline") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("Plot") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("Tagline") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.HasIndex("EpisodeMetadata_EpisodeMetadata_Id"); + + b.ToTable("EpisodeMetadata"); + + b.HasDiscriminator().HasValue("EpisodeMetadata"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MovieMetadata", b => + { + b.HasBaseType("Jellyfin.Data.Entities.Metadata"); + + b.Property("Country") + .HasColumnName("MovieMetadata_Country") + .HasColumnType("TEXT") + .HasMaxLength(2); + + b.Property("MovieMetadata_MovieMetadata_Id") + .HasColumnType("INTEGER"); + + b.Property("Outline") + .HasColumnName("MovieMetadata_Outline") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("Plot") + .HasColumnName("MovieMetadata_Plot") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("Tagline") + .HasColumnName("MovieMetadata_Tagline") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.HasIndex("MovieMetadata_MovieMetadata_Id"); + + b.ToTable("MovieMetadata"); + + b.HasDiscriminator().HasValue("MovieMetadata"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MusicAlbumMetadata", b => + { + b.HasBaseType("Jellyfin.Data.Entities.Metadata"); + + b.Property("Barcode") + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("Country") + .HasColumnName("MusicAlbumMetadata_Country") + .HasColumnType("TEXT") + .HasMaxLength(2); + + b.Property("LabelNumber") + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("MusicAlbumMetadata_MusicAlbumMetadata_Id") + .HasColumnType("INTEGER"); + + b.HasIndex("MusicAlbumMetadata_MusicAlbumMetadata_Id"); + + b.ToTable("MusicAlbumMetadata"); + + b.HasDiscriminator().HasValue("MusicAlbumMetadata"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.PhotoMetadata", b => + { + b.HasBaseType("Jellyfin.Data.Entities.Metadata"); + + b.Property("PhotoMetadata_PhotoMetadata_Id") + .HasColumnType("INTEGER"); + + b.HasIndex("PhotoMetadata_PhotoMetadata_Id"); + + b.ToTable("PhotoMetadata"); + + b.HasDiscriminator().HasValue("PhotoMetadata"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.SeasonMetadata", b => + { + b.HasBaseType("Jellyfin.Data.Entities.Metadata"); + + b.Property("Outline") + .HasColumnName("SeasonMetadata_Outline") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("SeasonMetadata_SeasonMetadata_Id") + .HasColumnType("INTEGER"); + + b.HasIndex("SeasonMetadata_SeasonMetadata_Id"); + + b.ToTable("SeasonMetadata"); + + b.HasDiscriminator().HasValue("SeasonMetadata"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.SeriesMetadata", b => + { + b.HasBaseType("Jellyfin.Data.Entities.Metadata"); + + b.Property("Country") + .HasColumnName("SeriesMetadata_Country") + .HasColumnType("TEXT") + .HasMaxLength(2); + + b.Property("Outline") + .HasColumnName("SeriesMetadata_Outline") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("Plot") + .HasColumnName("SeriesMetadata_Plot") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("SeriesMetadata_SeriesMetadata_Id") + .HasColumnType("INTEGER"); + + b.Property("Tagline") + .HasColumnName("SeriesMetadata_Tagline") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.HasIndex("SeriesMetadata_SeriesMetadata_Id"); + + b.ToTable("SeriesMetadata"); + + b.HasDiscriminator().HasValue("SeriesMetadata"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.TrackMetadata", b => + { + b.HasBaseType("Jellyfin.Data.Entities.Metadata"); + + b.Property("TrackMetadata_TrackMetadata_Id") + .HasColumnType("INTEGER"); + + b.HasIndex("TrackMetadata_TrackMetadata_Id"); + + b.ToTable("TrackMetadata"); + + b.HasDiscriminator().HasValue("TrackMetadata"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Artwork", b => + { + b.HasOne("Jellyfin.Data.Entities.Metadata", null) + .WithMany("Artwork") + .HasForeignKey("PersonRole_PersonRoles_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Chapter", b => + { + b.HasOne("Jellyfin.Data.Entities.Release", null) + .WithMany("Chapters") + .HasForeignKey("Chapter_Chapters_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.CollectionItem", b => + { + b.HasOne("Jellyfin.Data.Entities.Collection", null) + .WithMany("CollectionItem") + .HasForeignKey("CollectionItem_CollectionItem_Id"); + + b.HasOne("Jellyfin.Data.Entities.CollectionItem", "Next") + .WithMany() + .HasForeignKey("CollectionItem_Next_Id"); + + b.HasOne("Jellyfin.Data.Entities.CollectionItem", "Previous") + .WithMany() + .HasForeignKey("CollectionItem_Previous_Id"); + + b.HasOne("Jellyfin.Data.Entities.LibraryItem", "LibraryItem") + .WithMany() + .HasForeignKey("LibraryItem_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Company", b => + { + b.HasOne("Jellyfin.Data.Entities.MusicAlbumMetadata", null) + .WithMany("Labels") + .HasForeignKey("Company_Labels_Id"); + + b.HasOne("Jellyfin.Data.Entities.SeriesMetadata", null) + .WithMany("Networks") + .HasForeignKey("Company_Networks_Id"); + + b.HasOne("Jellyfin.Data.Entities.Company", "Parent") + .WithMany() + .HasForeignKey("Company_Parent_Id"); + + b.HasOne("Jellyfin.Data.Entities.BookMetadata", null) + .WithMany("Publishers") + .HasForeignKey("Company_Publishers_Id"); + + b.HasOne("Jellyfin.Data.Entities.MovieMetadata", null) + .WithMany("Studios") + .HasForeignKey("Company_Studios_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Genre", b => + { + b.HasOne("Jellyfin.Data.Entities.Metadata", null) + .WithMany("Genres") + .HasForeignKey("PersonRole_PersonRoles_Id"); + }); + + 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.LibraryItem", b => + { + b.HasOne("Jellyfin.Data.Entities.LibraryRoot", "LibraryRoot") + .WithMany() + .HasForeignKey("LibraryRoot_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.LibraryRoot", b => + { + b.HasOne("Jellyfin.Data.Entities.Library", "Library") + .WithMany() + .HasForeignKey("Library_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MediaFile", b => + { + b.HasOne("Jellyfin.Data.Entities.Release", null) + .WithMany("MediaFiles") + .HasForeignKey("MediaFile_MediaFiles_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MediaFileStream", b => + { + b.HasOne("Jellyfin.Data.Entities.MediaFile", null) + .WithMany("MediaFileStreams") + .HasForeignKey("MediaFileStream_MediaFileStreams_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MetadataProviderId", b => + { + b.HasOne("Jellyfin.Data.Entities.Person", null) + .WithMany("Sources") + .HasForeignKey("MetadataProviderId_Sources_Id"); + + b.HasOne("Jellyfin.Data.Entities.PersonRole", null) + .WithMany("Sources") + .HasForeignKey("MetadataProviderId_Sources_Id"); + + b.HasOne("Jellyfin.Data.Entities.MetadataProvider", "MetadataProvider") + .WithMany() + .HasForeignKey("MetadataProvider_Id"); + + b.HasOne("Jellyfin.Data.Entities.Metadata", null) + .WithMany("Sources") + .HasForeignKey("PersonRole_PersonRoles_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.PersonRole", b => + { + b.HasOne("Jellyfin.Data.Entities.Artwork", "Artwork") + .WithMany() + .HasForeignKey("Artwork_Artwork_Id"); + + b.HasOne("Jellyfin.Data.Entities.Metadata", null) + .WithMany("PersonRoles") + .HasForeignKey("PersonRole_PersonRoles_Id"); + + b.HasOne("Jellyfin.Data.Entities.Person", "Person") + .WithMany() + .HasForeignKey("Person_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"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Rating", b => + { + b.HasOne("Jellyfin.Data.Entities.Metadata", null) + .WithMany("Ratings") + .HasForeignKey("PersonRole_PersonRoles_Id"); + + b.HasOne("Jellyfin.Data.Entities.RatingSource", "RatingType") + .WithMany() + .HasForeignKey("RatingSource_RatingType_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.RatingSource", b => + { + b.HasOne("Jellyfin.Data.Entities.MetadataProviderId", "Source") + .WithMany() + .HasForeignKey("MetadataProviderId_Source_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Release", b => + { + b.HasOne("Jellyfin.Data.Entities.Book", null) + .WithMany("Releases") + .HasForeignKey("Release_Releases_Id"); + + b.HasOne("Jellyfin.Data.Entities.CustomItem", null) + .WithMany("Releases") + .HasForeignKey("Release_Releases_Id") + .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id1"); + + b.HasOne("Jellyfin.Data.Entities.Episode", null) + .WithMany("Releases") + .HasForeignKey("Release_Releases_Id") + .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id2"); + + b.HasOne("Jellyfin.Data.Entities.Movie", null) + .WithMany("Releases") + .HasForeignKey("Release_Releases_Id") + .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id3"); + + b.HasOne("Jellyfin.Data.Entities.Photo", null) + .WithMany("Releases") + .HasForeignKey("Release_Releases_Id") + .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id4"); + + b.HasOne("Jellyfin.Data.Entities.Track", null) + .WithMany("Releases") + .HasForeignKey("Release_Releases_Id") + .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id5"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Episode", b => + { + b.HasOne("Jellyfin.Data.Entities.Season", null) + .WithMany("Episodes") + .HasForeignKey("Episode_Episodes_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Season", b => + { + b.HasOne("Jellyfin.Data.Entities.Series", null) + .WithMany("Seasons") + .HasForeignKey("Season_Seasons_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Track", b => + { + b.HasOne("Jellyfin.Data.Entities.MusicAlbum", null) + .WithMany("Tracks") + .HasForeignKey("Track_Tracks_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.BookMetadata", b => + { + b.HasOne("Jellyfin.Data.Entities.Book", null) + .WithMany("BookMetadata") + .HasForeignKey("BookMetadata_BookMetadata_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.CompanyMetadata", b => + { + b.HasOne("Jellyfin.Data.Entities.Company", null) + .WithMany("CompanyMetadata") + .HasForeignKey("CompanyMetadata_CompanyMetadata_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemMetadata", b => + { + b.HasOne("Jellyfin.Data.Entities.CustomItem", null) + .WithMany("CustomItemMetadata") + .HasForeignKey("CustomItemMetadata_CustomItemMetadata_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.EpisodeMetadata", b => + { + b.HasOne("Jellyfin.Data.Entities.Episode", null) + .WithMany("EpisodeMetadata") + .HasForeignKey("EpisodeMetadata_EpisodeMetadata_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MovieMetadata", b => + { + b.HasOne("Jellyfin.Data.Entities.Movie", null) + .WithMany("MovieMetadata") + .HasForeignKey("MovieMetadata_MovieMetadata_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MusicAlbumMetadata", b => + { + b.HasOne("Jellyfin.Data.Entities.MusicAlbum", null) + .WithMany("MusicAlbumMetadata") + .HasForeignKey("MusicAlbumMetadata_MusicAlbumMetadata_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.PhotoMetadata", b => + { + b.HasOne("Jellyfin.Data.Entities.Photo", null) + .WithMany("PhotoMetadata") + .HasForeignKey("PhotoMetadata_PhotoMetadata_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.SeasonMetadata", b => + { + b.HasOne("Jellyfin.Data.Entities.Season", null) + .WithMany("SeasonMetadata") + .HasForeignKey("SeasonMetadata_SeasonMetadata_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.SeriesMetadata", b => + { + b.HasOne("Jellyfin.Data.Entities.Series", null) + .WithMany("SeriesMetadata") + .HasForeignKey("SeriesMetadata_SeriesMetadata_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.TrackMetadata", b => + { + b.HasOne("Jellyfin.Data.Entities.Track", null) + .WithMany("TrackMetadata") + .HasForeignKey("TrackMetadata_TrackMetadata_Id"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/20200430215054_InitialSchema.cs b/Jellyfin.Server.Implementations/Migrations/20200430215054_InitialSchema.cs new file mode 100644 index 000000000..f6f2f1a81 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200430215054_InitialSchema.cs @@ -0,0 +1,1294 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1601 + +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Jellyfin.Server.Implementations.Migrations +{ + public partial class InitialSchema : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.EnsureSchema( + name: "jellyfin"); + + migrationBuilder.CreateTable( + name: "ActivityLog", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(maxLength: 512, nullable: false), + Overview = table.Column(maxLength: 512, nullable: true), + ShortOverview = table.Column(maxLength: 512, nullable: true), + Type = table.Column(maxLength: 256, nullable: false), + UserId = table.Column(nullable: false), + ItemId = table.Column(maxLength: 256, nullable: true), + DateCreated = table.Column(nullable: false), + LogSeverity = table.Column(nullable: false), + RowVersion = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ActivityLog", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Collection", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(maxLength: 1024, nullable: true), + RowVersion = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Collection", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Library", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(maxLength: 1024, nullable: false), + RowVersion = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Library", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "MetadataProvider", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(maxLength: 1024, nullable: false), + RowVersion = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MetadataProvider", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Person", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UrlId = table.Column(nullable: false), + Name = table.Column(maxLength: 1024, nullable: false), + SourceId = table.Column(maxLength: 255, nullable: true), + DateAdded = table.Column(nullable: false), + DateModified = table.Column(nullable: false), + RowVersion = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Person", x => x.Id); + }); + + 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: "LibraryRoot", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Path = table.Column(maxLength: 65535, nullable: false), + NetworkPath = table.Column(maxLength: 65535, nullable: true), + RowVersion = table.Column(nullable: false), + Library_Id = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_LibraryRoot", x => x.Id); + table.ForeignKey( + name: "FK_LibraryRoot_Library_Library_Id", + column: x => x.Library_Id, + principalSchema: "jellyfin", + principalTable: "Library", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + 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: "LibraryItem", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UrlId = table.Column(nullable: false), + DateAdded = table.Column(nullable: false), + RowVersion = table.Column(nullable: false), + LibraryRoot_Id = table.Column(nullable: true), + Discriminator = table.Column(nullable: false), + EpisodeNumber = table.Column(nullable: true), + Episode_Episodes_Id = table.Column(nullable: true), + SeasonNumber = table.Column(nullable: true), + Season_Seasons_Id = table.Column(nullable: true), + AirsDayOfWeek = table.Column(nullable: true), + AirsTime = table.Column(nullable: true), + FirstAired = table.Column(nullable: true), + TrackNumber = table.Column(nullable: true), + Track_Tracks_Id = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_LibraryItem", x => x.Id); + table.ForeignKey( + name: "FK_LibraryItem_LibraryItem_Episode_Episodes_Id", + column: x => x.Episode_Episodes_Id, + principalSchema: "jellyfin", + principalTable: "LibraryItem", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_LibraryItem_LibraryRoot_LibraryRoot_Id", + column: x => x.LibraryRoot_Id, + principalSchema: "jellyfin", + principalTable: "LibraryRoot", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_LibraryItem_LibraryItem_Season_Seasons_Id", + column: x => x.Season_Seasons_Id, + principalSchema: "jellyfin", + principalTable: "LibraryItem", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_LibraryItem_LibraryItem_Track_Tracks_Id", + column: x => x.Track_Tracks_Id, + principalSchema: "jellyfin", + principalTable: "LibraryItem", + 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.CreateTable( + name: "CollectionItem", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RowVersion = table.Column(nullable: false), + LibraryItem_Id = table.Column(nullable: true), + CollectionItem_Next_Id = table.Column(nullable: true), + CollectionItem_Previous_Id = table.Column(nullable: true), + CollectionItem_CollectionItem_Id = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CollectionItem", x => x.Id); + table.ForeignKey( + name: "FK_CollectionItem_Collection_CollectionItem_CollectionItem_Id", + column: x => x.CollectionItem_CollectionItem_Id, + principalSchema: "jellyfin", + principalTable: "Collection", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_CollectionItem_CollectionItem_CollectionItem_Next_Id", + column: x => x.CollectionItem_Next_Id, + principalSchema: "jellyfin", + principalTable: "CollectionItem", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_CollectionItem_CollectionItem_CollectionItem_Previous_Id", + column: x => x.CollectionItem_Previous_Id, + principalSchema: "jellyfin", + principalTable: "CollectionItem", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_CollectionItem_LibraryItem_LibraryItem_Id", + column: x => x.LibraryItem_Id, + principalSchema: "jellyfin", + principalTable: "LibraryItem", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Release", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(maxLength: 1024, nullable: false), + RowVersion = table.Column(nullable: false), + Release_Releases_Id = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Release", x => x.Id); + table.ForeignKey( + name: "FK_Release_LibraryItem_Release_Releases_Id", + column: x => x.Release_Releases_Id, + principalSchema: "jellyfin", + principalTable: "LibraryItem", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Release_LibraryItem_Release_Releases_Id1", + column: x => x.Release_Releases_Id, + principalSchema: "jellyfin", + principalTable: "LibraryItem", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Release_LibraryItem_Release_Releases_Id2", + column: x => x.Release_Releases_Id, + principalSchema: "jellyfin", + principalTable: "LibraryItem", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Release_LibraryItem_Release_Releases_Id3", + column: x => x.Release_Releases_Id, + principalSchema: "jellyfin", + principalTable: "LibraryItem", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Release_LibraryItem_Release_Releases_Id4", + column: x => x.Release_Releases_Id, + principalSchema: "jellyfin", + principalTable: "LibraryItem", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Release_LibraryItem_Release_Releases_Id5", + column: x => x.Release_Releases_Id, + principalSchema: "jellyfin", + principalTable: "LibraryItem", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Chapter", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(maxLength: 1024, nullable: true), + Language = table.Column(maxLength: 3, nullable: false), + TimeStart = table.Column(nullable: false), + TimeEnd = table.Column(nullable: true), + RowVersion = table.Column(nullable: false), + Chapter_Chapters_Id = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Chapter", x => x.Id); + table.ForeignKey( + name: "FK_Chapter_Release_Chapter_Chapters_Id", + column: x => x.Chapter_Chapters_Id, + principalSchema: "jellyfin", + principalTable: "Release", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "MediaFile", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Path = table.Column(maxLength: 65535, nullable: false), + Kind = table.Column(nullable: false), + RowVersion = table.Column(nullable: false), + MediaFile_MediaFiles_Id = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_MediaFile", x => x.Id); + table.ForeignKey( + name: "FK_MediaFile_Release_MediaFile_MediaFiles_Id", + column: x => x.MediaFile_MediaFiles_Id, + principalSchema: "jellyfin", + principalTable: "Release", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "MediaFileStream", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + StreamNumber = table.Column(nullable: false), + RowVersion = table.Column(nullable: false), + MediaFileStream_MediaFileStreams_Id = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_MediaFileStream", x => x.Id); + table.ForeignKey( + name: "FK_MediaFileStream_MediaFile_MediaFileStream_MediaFileStreams_Id", + column: x => x.MediaFileStream_MediaFileStreams_Id, + principalSchema: "jellyfin", + principalTable: "MediaFile", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "PersonRole", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Role = table.Column(maxLength: 1024, nullable: true), + Type = table.Column(nullable: false), + RowVersion = table.Column(nullable: false), + Person_Id = table.Column(nullable: true), + Artwork_Artwork_Id = table.Column(nullable: true), + PersonRole_PersonRoles_Id = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PersonRole", x => x.Id); + table.ForeignKey( + name: "FK_PersonRole_Person_Person_Id", + column: x => x.Person_Id, + principalSchema: "jellyfin", + principalTable: "Person", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Metadata", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Title = table.Column(maxLength: 1024, nullable: false), + OriginalTitle = table.Column(maxLength: 1024, nullable: true), + SortTitle = table.Column(maxLength: 1024, nullable: true), + Language = table.Column(maxLength: 3, nullable: false), + ReleaseDate = table.Column(nullable: true), + DateAdded = table.Column(nullable: false), + DateModified = table.Column(nullable: false), + RowVersion = table.Column(nullable: false), + Discriminator = table.Column(nullable: false), + ISBN = table.Column(nullable: true), + BookMetadata_BookMetadata_Id = table.Column(nullable: true), + Description = table.Column(maxLength: 65535, nullable: true), + Headquarters = table.Column(maxLength: 255, nullable: true), + Country = table.Column(maxLength: 2, nullable: true), + Homepage = table.Column(maxLength: 1024, nullable: true), + CompanyMetadata_CompanyMetadata_Id = table.Column(nullable: true), + CustomItemMetadata_CustomItemMetadata_Id = table.Column(nullable: true), + Outline = table.Column(maxLength: 1024, nullable: true), + Plot = table.Column(maxLength: 65535, nullable: true), + Tagline = table.Column(maxLength: 1024, nullable: true), + EpisodeMetadata_EpisodeMetadata_Id = table.Column(nullable: true), + MovieMetadata_Outline = table.Column(maxLength: 1024, nullable: true), + MovieMetadata_Plot = table.Column(maxLength: 65535, nullable: true), + MovieMetadata_Tagline = table.Column(maxLength: 1024, nullable: true), + MovieMetadata_Country = table.Column(maxLength: 2, nullable: true), + MovieMetadata_MovieMetadata_Id = table.Column(nullable: true), + Barcode = table.Column(maxLength: 255, nullable: true), + LabelNumber = table.Column(maxLength: 255, nullable: true), + MusicAlbumMetadata_Country = table.Column(maxLength: 2, nullable: true), + MusicAlbumMetadata_MusicAlbumMetadata_Id = table.Column(nullable: true), + PhotoMetadata_PhotoMetadata_Id = table.Column(nullable: true), + SeasonMetadata_Outline = table.Column(maxLength: 1024, nullable: true), + SeasonMetadata_SeasonMetadata_Id = table.Column(nullable: true), + SeriesMetadata_Outline = table.Column(maxLength: 1024, nullable: true), + SeriesMetadata_Plot = table.Column(maxLength: 65535, nullable: true), + SeriesMetadata_Tagline = table.Column(maxLength: 1024, nullable: true), + SeriesMetadata_Country = table.Column(maxLength: 2, nullable: true), + SeriesMetadata_SeriesMetadata_Id = table.Column(nullable: true), + TrackMetadata_TrackMetadata_Id = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Metadata", x => x.Id); + table.ForeignKey( + name: "FK_Metadata_LibraryItem_BookMetadata_BookMetadata_Id", + column: x => x.BookMetadata_BookMetadata_Id, + principalSchema: "jellyfin", + principalTable: "LibraryItem", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Metadata_LibraryItem_CustomItemMetadata_CustomItemMetadata_Id", + column: x => x.CustomItemMetadata_CustomItemMetadata_Id, + principalSchema: "jellyfin", + principalTable: "LibraryItem", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Metadata_LibraryItem_EpisodeMetadata_EpisodeMetadata_Id", + column: x => x.EpisodeMetadata_EpisodeMetadata_Id, + principalSchema: "jellyfin", + principalTable: "LibraryItem", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Metadata_LibraryItem_MovieMetadata_MovieMetadata_Id", + column: x => x.MovieMetadata_MovieMetadata_Id, + principalSchema: "jellyfin", + principalTable: "LibraryItem", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Metadata_LibraryItem_MusicAlbumMetadata_MusicAlbumMetadata_Id", + column: x => x.MusicAlbumMetadata_MusicAlbumMetadata_Id, + principalSchema: "jellyfin", + principalTable: "LibraryItem", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Metadata_LibraryItem_PhotoMetadata_PhotoMetadata_Id", + column: x => x.PhotoMetadata_PhotoMetadata_Id, + principalSchema: "jellyfin", + principalTable: "LibraryItem", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Metadata_LibraryItem_SeasonMetadata_SeasonMetadata_Id", + column: x => x.SeasonMetadata_SeasonMetadata_Id, + principalSchema: "jellyfin", + principalTable: "LibraryItem", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Metadata_LibraryItem_SeriesMetadata_SeriesMetadata_Id", + column: x => x.SeriesMetadata_SeriesMetadata_Id, + principalSchema: "jellyfin", + principalTable: "LibraryItem", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Metadata_LibraryItem_TrackMetadata_TrackMetadata_Id", + column: x => x.TrackMetadata_TrackMetadata_Id, + principalSchema: "jellyfin", + principalTable: "LibraryItem", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Artwork", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Path = table.Column(maxLength: 65535, nullable: false), + Kind = table.Column(nullable: false), + RowVersion = table.Column(nullable: false), + PersonRole_PersonRoles_Id = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Artwork", x => x.Id); + table.ForeignKey( + name: "FK_Artwork_Metadata_PersonRole_PersonRoles_Id", + column: x => x.PersonRole_PersonRoles_Id, + principalSchema: "jellyfin", + principalTable: "Metadata", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Company", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RowVersion = table.Column(nullable: false), + Company_Parent_Id = table.Column(nullable: true), + Company_Labels_Id = table.Column(nullable: true), + Company_Networks_Id = table.Column(nullable: true), + Company_Publishers_Id = table.Column(nullable: true), + Company_Studios_Id = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Company", x => x.Id); + table.ForeignKey( + name: "FK_Company_Metadata_Company_Labels_Id", + column: x => x.Company_Labels_Id, + principalSchema: "jellyfin", + principalTable: "Metadata", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Company_Metadata_Company_Networks_Id", + column: x => x.Company_Networks_Id, + principalSchema: "jellyfin", + principalTable: "Metadata", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Company_Company_Company_Parent_Id", + column: x => x.Company_Parent_Id, + principalSchema: "jellyfin", + principalTable: "Company", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Company_Metadata_Company_Publishers_Id", + column: x => x.Company_Publishers_Id, + principalSchema: "jellyfin", + principalTable: "Metadata", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Company_Metadata_Company_Studios_Id", + column: x => x.Company_Studios_Id, + principalSchema: "jellyfin", + principalTable: "Metadata", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Genre", + 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), + PersonRole_PersonRoles_Id = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Genre", x => x.Id); + table.ForeignKey( + name: "FK_Genre_Metadata_PersonRole_PersonRoles_Id", + column: x => x.PersonRole_PersonRoles_Id, + principalSchema: "jellyfin", + principalTable: "Metadata", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "MetadataProviderId", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ProviderId = table.Column(maxLength: 255, nullable: false), + RowVersion = table.Column(nullable: false), + MetadataProvider_Id = table.Column(nullable: true), + MetadataProviderId_Sources_Id = table.Column(nullable: true), + PersonRole_PersonRoles_Id = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_MetadataProviderId", x => x.Id); + table.ForeignKey( + name: "FK_MetadataProviderId_Person_MetadataProviderId_Sources_Id", + column: x => x.MetadataProviderId_Sources_Id, + principalSchema: "jellyfin", + principalTable: "Person", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_MetadataProviderId_PersonRole_MetadataProviderId_Sources_Id", + column: x => x.MetadataProviderId_Sources_Id, + principalSchema: "jellyfin", + principalTable: "PersonRole", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_MetadataProviderId_MetadataProvider_MetadataProvider_Id", + column: x => x.MetadataProvider_Id, + principalSchema: "jellyfin", + principalTable: "MetadataProvider", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_MetadataProviderId_Metadata_PersonRole_PersonRoles_Id", + column: x => x.PersonRole_PersonRoles_Id, + principalSchema: "jellyfin", + principalTable: "Metadata", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "RatingSource", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(maxLength: 1024, nullable: true), + MaximumValue = table.Column(nullable: false), + MinimumValue = table.Column(nullable: false), + RowVersion = table.Column(nullable: false), + MetadataProviderId_Source_Id = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_RatingSource", x => x.Id); + table.ForeignKey( + name: "FK_RatingSource_MetadataProviderId_MetadataProviderId_Source_Id", + column: x => x.MetadataProviderId_Source_Id, + principalSchema: "jellyfin", + principalTable: "MetadataProviderId", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Rating", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Value = table.Column(nullable: false), + Votes = table.Column(nullable: true), + RowVersion = table.Column(nullable: false), + RatingSource_RatingType_Id = table.Column(nullable: true), + PersonRole_PersonRoles_Id = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Rating", x => x.Id); + table.ForeignKey( + name: "FK_Rating_Metadata_PersonRole_PersonRoles_Id", + column: x => x.PersonRole_PersonRoles_Id, + principalSchema: "jellyfin", + principalTable: "Metadata", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Rating_RatingSource_RatingSource_RatingType_Id", + column: x => x.RatingSource_RatingType_Id, + principalSchema: "jellyfin", + principalTable: "RatingSource", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_Artwork_Kind", + schema: "jellyfin", + table: "Artwork", + column: "Kind"); + + migrationBuilder.CreateIndex( + name: "IX_Artwork_PersonRole_PersonRoles_Id", + schema: "jellyfin", + table: "Artwork", + column: "PersonRole_PersonRoles_Id"); + + migrationBuilder.CreateIndex( + name: "IX_Chapter_Chapter_Chapters_Id", + schema: "jellyfin", + table: "Chapter", + column: "Chapter_Chapters_Id"); + + migrationBuilder.CreateIndex( + name: "IX_CollectionItem_CollectionItem_CollectionItem_Id", + schema: "jellyfin", + table: "CollectionItem", + column: "CollectionItem_CollectionItem_Id"); + + migrationBuilder.CreateIndex( + name: "IX_CollectionItem_CollectionItem_Next_Id", + schema: "jellyfin", + table: "CollectionItem", + column: "CollectionItem_Next_Id"); + + migrationBuilder.CreateIndex( + name: "IX_CollectionItem_CollectionItem_Previous_Id", + schema: "jellyfin", + table: "CollectionItem", + column: "CollectionItem_Previous_Id"); + + migrationBuilder.CreateIndex( + name: "IX_CollectionItem_LibraryItem_Id", + schema: "jellyfin", + table: "CollectionItem", + column: "LibraryItem_Id"); + + migrationBuilder.CreateIndex( + name: "IX_Company_Company_Labels_Id", + schema: "jellyfin", + table: "Company", + column: "Company_Labels_Id"); + + migrationBuilder.CreateIndex( + name: "IX_Company_Company_Networks_Id", + schema: "jellyfin", + table: "Company", + column: "Company_Networks_Id"); + + migrationBuilder.CreateIndex( + name: "IX_Company_Company_Parent_Id", + schema: "jellyfin", + table: "Company", + column: "Company_Parent_Id"); + + migrationBuilder.CreateIndex( + name: "IX_Company_Company_Publishers_Id", + schema: "jellyfin", + table: "Company", + column: "Company_Publishers_Id"); + + migrationBuilder.CreateIndex( + name: "IX_Company_Company_Studios_Id", + schema: "jellyfin", + table: "Company", + column: "Company_Studios_Id"); + + migrationBuilder.CreateIndex( + name: "IX_Genre_Name", + schema: "jellyfin", + table: "Genre", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Genre_PersonRole_PersonRoles_Id", + schema: "jellyfin", + table: "Genre", + column: "PersonRole_PersonRoles_Id"); + + migrationBuilder.CreateIndex( + name: "IX_Group_Group_Groups_Id", + schema: "jellyfin", + table: "Group", + column: "Group_Groups_Id"); + + migrationBuilder.CreateIndex( + name: "IX_LibraryItem_Episode_Episodes_Id", + schema: "jellyfin", + table: "LibraryItem", + column: "Episode_Episodes_Id"); + + migrationBuilder.CreateIndex( + name: "IX_LibraryItem_LibraryRoot_Id", + schema: "jellyfin", + table: "LibraryItem", + column: "LibraryRoot_Id"); + + migrationBuilder.CreateIndex( + name: "IX_LibraryItem_UrlId", + schema: "jellyfin", + table: "LibraryItem", + column: "UrlId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_LibraryItem_Season_Seasons_Id", + schema: "jellyfin", + table: "LibraryItem", + column: "Season_Seasons_Id"); + + migrationBuilder.CreateIndex( + name: "IX_LibraryItem_Track_Tracks_Id", + schema: "jellyfin", + table: "LibraryItem", + column: "Track_Tracks_Id"); + + migrationBuilder.CreateIndex( + name: "IX_LibraryRoot_Library_Id", + schema: "jellyfin", + table: "LibraryRoot", + column: "Library_Id"); + + migrationBuilder.CreateIndex( + name: "IX_MediaFile_MediaFile_MediaFiles_Id", + schema: "jellyfin", + table: "MediaFile", + column: "MediaFile_MediaFiles_Id"); + + migrationBuilder.CreateIndex( + name: "IX_MediaFileStream_MediaFileStream_MediaFileStreams_Id", + schema: "jellyfin", + table: "MediaFileStream", + column: "MediaFileStream_MediaFileStreams_Id"); + + migrationBuilder.CreateIndex( + name: "IX_Metadata_BookMetadata_BookMetadata_Id", + schema: "jellyfin", + table: "Metadata", + column: "BookMetadata_BookMetadata_Id"); + + migrationBuilder.CreateIndex( + name: "IX_Metadata_CompanyMetadata_CompanyMetadata_Id", + schema: "jellyfin", + table: "Metadata", + column: "CompanyMetadata_CompanyMetadata_Id"); + + migrationBuilder.CreateIndex( + name: "IX_Metadata_CustomItemMetadata_CustomItemMetadata_Id", + schema: "jellyfin", + table: "Metadata", + column: "CustomItemMetadata_CustomItemMetadata_Id"); + + migrationBuilder.CreateIndex( + name: "IX_Metadata_EpisodeMetadata_EpisodeMetadata_Id", + schema: "jellyfin", + table: "Metadata", + column: "EpisodeMetadata_EpisodeMetadata_Id"); + + migrationBuilder.CreateIndex( + name: "IX_Metadata_MovieMetadata_MovieMetadata_Id", + schema: "jellyfin", + table: "Metadata", + column: "MovieMetadata_MovieMetadata_Id"); + + migrationBuilder.CreateIndex( + name: "IX_Metadata_MusicAlbumMetadata_MusicAlbumMetadata_Id", + schema: "jellyfin", + table: "Metadata", + column: "MusicAlbumMetadata_MusicAlbumMetadata_Id"); + + migrationBuilder.CreateIndex( + name: "IX_Metadata_PhotoMetadata_PhotoMetadata_Id", + schema: "jellyfin", + table: "Metadata", + column: "PhotoMetadata_PhotoMetadata_Id"); + + migrationBuilder.CreateIndex( + name: "IX_Metadata_SeasonMetadata_SeasonMetadata_Id", + schema: "jellyfin", + table: "Metadata", + column: "SeasonMetadata_SeasonMetadata_Id"); + + migrationBuilder.CreateIndex( + name: "IX_Metadata_SeriesMetadata_SeriesMetadata_Id", + schema: "jellyfin", + table: "Metadata", + column: "SeriesMetadata_SeriesMetadata_Id"); + + migrationBuilder.CreateIndex( + name: "IX_Metadata_TrackMetadata_TrackMetadata_Id", + schema: "jellyfin", + table: "Metadata", + column: "TrackMetadata_TrackMetadata_Id"); + + migrationBuilder.CreateIndex( + name: "IX_MetadataProviderId_MetadataProviderId_Sources_Id", + schema: "jellyfin", + table: "MetadataProviderId", + column: "MetadataProviderId_Sources_Id"); + + migrationBuilder.CreateIndex( + name: "IX_MetadataProviderId_MetadataProvider_Id", + schema: "jellyfin", + table: "MetadataProviderId", + column: "MetadataProvider_Id"); + + migrationBuilder.CreateIndex( + name: "IX_MetadataProviderId_PersonRole_PersonRoles_Id", + schema: "jellyfin", + table: "MetadataProviderId", + column: "PersonRole_PersonRoles_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_PersonRole_Artwork_Artwork_Id", + schema: "jellyfin", + table: "PersonRole", + column: "Artwork_Artwork_Id"); + + migrationBuilder.CreateIndex( + name: "IX_PersonRole_PersonRole_PersonRoles_Id", + schema: "jellyfin", + table: "PersonRole", + column: "PersonRole_PersonRoles_Id"); + + migrationBuilder.CreateIndex( + name: "IX_PersonRole_Person_Id", + schema: "jellyfin", + table: "PersonRole", + column: "Person_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"); + + migrationBuilder.CreateIndex( + name: "IX_Rating_PersonRole_PersonRoles_Id", + schema: "jellyfin", + table: "Rating", + column: "PersonRole_PersonRoles_Id"); + + migrationBuilder.CreateIndex( + name: "IX_Rating_RatingSource_RatingType_Id", + schema: "jellyfin", + table: "Rating", + column: "RatingSource_RatingType_Id"); + + migrationBuilder.CreateIndex( + name: "IX_RatingSource_MetadataProviderId_Source_Id", + schema: "jellyfin", + table: "RatingSource", + column: "MetadataProviderId_Source_Id"); + + migrationBuilder.CreateIndex( + name: "IX_Release_Release_Releases_Id", + schema: "jellyfin", + table: "Release", + column: "Release_Releases_Id"); + + migrationBuilder.AddForeignKey( + name: "FK_PersonRole_Metadata_PersonRole_PersonRoles_Id", + schema: "jellyfin", + table: "PersonRole", + column: "PersonRole_PersonRoles_Id", + principalSchema: "jellyfin", + principalTable: "Metadata", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_PersonRole_Artwork_Artwork_Artwork_Id", + schema: "jellyfin", + table: "PersonRole", + column: "Artwork_Artwork_Id", + principalSchema: "jellyfin", + principalTable: "Artwork", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_Metadata_Company_CompanyMetadata_CompanyMetadata_Id", + schema: "jellyfin", + table: "Metadata", + column: "CompanyMetadata_CompanyMetadata_Id", + principalSchema: "jellyfin", + principalTable: "Company", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Company_Metadata_Company_Labels_Id", + schema: "jellyfin", + table: "Company"); + + migrationBuilder.DropForeignKey( + name: "FK_Company_Metadata_Company_Networks_Id", + schema: "jellyfin", + table: "Company"); + + migrationBuilder.DropForeignKey( + name: "FK_Company_Metadata_Company_Publishers_Id", + schema: "jellyfin", + table: "Company"); + + migrationBuilder.DropForeignKey( + name: "FK_Company_Metadata_Company_Studios_Id", + schema: "jellyfin", + table: "Company"); + + migrationBuilder.DropTable( + name: "ActivityLog", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Chapter", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "CollectionItem", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Genre", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "MediaFileStream", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Permission", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Preference", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "ProviderMapping", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Rating", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Collection", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "MediaFile", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Group", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "RatingSource", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Release", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "User", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "MetadataProviderId", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "PersonRole", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "MetadataProvider", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Artwork", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Person", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Metadata", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "LibraryItem", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Company", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "LibraryRoot", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Library", + schema: "jellyfin"); + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs b/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs new file mode 100644 index 000000000..72a4a8c3b --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs @@ -0,0 +1,20 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; + +namespace Jellyfin.Server.Implementations.Migrations +{ + /// + /// The design time factory for . + /// This is only used for the creation of migrations and not during runtime. + /// + internal class DesignTimeJellyfinDbFactory : IDesignTimeDbContextFactory + { + public JellyfinDb CreateDbContext(string[] args) + { + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseSqlite("Data Source=jellyfin.db"); + + return new JellyfinDb(optionsBuilder.Options); + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs new file mode 100644 index 000000000..8cdd101af --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs @@ -0,0 +1,1511 @@ +#pragma warning disable CS1591 + +// +using System; +using Jellyfin.Server.Implementations; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Jellyfin.Server.Implementations.Migrations +{ + [DbContext(typeof(JellyfinDb))] + partial class JellyfinDbModelSnapshot : ModelSnapshot + { + protected override void BuildModel(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.Artwork", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("PersonRole_PersonRoles_Id") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Kind"); + + b.HasIndex("PersonRole_PersonRoles_Id"); + + b.ToTable("Artwork"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Chapter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Chapter_Chapters_Id") + .HasColumnType("INTEGER"); + + b.Property("Language") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(3); + + b.Property("Name") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("TimeEnd") + .HasColumnType("INTEGER"); + + b.Property("TimeStart") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Chapter_Chapters_Id"); + + b.ToTable("Chapter"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Collection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Collection"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.CollectionItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CollectionItem_CollectionItem_Id") + .HasColumnType("INTEGER"); + + b.Property("CollectionItem_Next_Id") + .HasColumnType("INTEGER"); + + b.Property("CollectionItem_Previous_Id") + .HasColumnType("INTEGER"); + + b.Property("LibraryItem_Id") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CollectionItem_CollectionItem_Id"); + + b.HasIndex("CollectionItem_Next_Id"); + + b.HasIndex("CollectionItem_Previous_Id"); + + b.HasIndex("LibraryItem_Id"); + + b.ToTable("CollectionItem"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Company", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Company_Labels_Id") + .HasColumnType("INTEGER"); + + b.Property("Company_Networks_Id") + .HasColumnType("INTEGER"); + + b.Property("Company_Parent_Id") + .HasColumnType("INTEGER"); + + b.Property("Company_Publishers_Id") + .HasColumnType("INTEGER"); + + b.Property("Company_Studios_Id") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Company_Labels_Id"); + + b.HasIndex("Company_Networks_Id"); + + b.HasIndex("Company_Parent_Id"); + + b.HasIndex("Company_Publishers_Id"); + + b.HasIndex("Company_Studios_Id"); + + b.ToTable("Company"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Genre", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("PersonRole_PersonRoles_Id") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("PersonRole_PersonRoles_Id"); + + b.ToTable("Genre"); + }); + + 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.Library", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Library"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.LibraryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LibraryRoot_Id") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("UrlId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryRoot_Id"); + + b.HasIndex("UrlId") + .IsUnique(); + + b.ToTable("LibraryItem"); + + b.HasDiscriminator("Discriminator").HasValue("LibraryItem"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.LibraryRoot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Library_Id") + .HasColumnType("INTEGER"); + + b.Property("NetworkPath") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Library_Id"); + + b.ToTable("LibraryRoot"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MediaFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("MediaFile_MediaFiles_Id") + .HasColumnType("INTEGER"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MediaFile_MediaFiles_Id"); + + b.ToTable("MediaFile"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MediaFileStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("MediaFileStream_MediaFileStreams_Id") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("StreamNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MediaFileStream_MediaFileStreams_Id"); + + b.ToTable("MediaFileStream"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Metadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("DateModified") + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Language") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(3); + + b.Property("OriginalTitle") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SortTitle") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.HasKey("Id"); + + b.ToTable("Metadata"); + + b.HasDiscriminator("Discriminator").HasValue("Metadata"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MetadataProvider", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("MetadataProvider"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MetadataProviderId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("MetadataProviderId_Sources_Id") + .HasColumnType("INTEGER"); + + b.Property("MetadataProvider_Id") + .HasColumnType("INTEGER"); + + b.Property("PersonRole_PersonRoles_Id") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MetadataProviderId_Sources_Id"); + + b.HasIndex("MetadataProvider_Id"); + + b.HasIndex("PersonRole_PersonRoles_Id"); + + b.ToTable("MetadataProviderId"); + }); + + 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.Person", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("DateModified") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SourceId") + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("UrlId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Person"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.PersonRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Artwork_Artwork_Id") + .HasColumnType("INTEGER"); + + b.Property("PersonRole_PersonRoles_Id") + .HasColumnType("INTEGER"); + + b.Property("Person_Id") + .HasColumnType("INTEGER"); + + b.Property("Role") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Artwork_Artwork_Id"); + + b.HasIndex("PersonRole_PersonRoles_Id"); + + b.HasIndex("Person_Id"); + + b.ToTable("PersonRole"); + }); + + 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.Rating", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("PersonRole_PersonRoles_Id") + .HasColumnType("INTEGER"); + + b.Property("RatingSource_RatingType_Id") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("REAL"); + + b.Property("Votes") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PersonRole_PersonRoles_Id"); + + b.HasIndex("RatingSource_RatingType_Id"); + + b.ToTable("Rating"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.RatingSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("MaximumValue") + .HasColumnType("REAL"); + + b.Property("MetadataProviderId_Source_Id") + .HasColumnType("INTEGER"); + + b.Property("MinimumValue") + .HasColumnType("REAL"); + + b.Property("Name") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MetadataProviderId_Source_Id"); + + b.ToTable("RatingSource"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Release", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("Release_Releases_Id") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Release_Releases_Id"); + + b.ToTable("Release"); + }); + + 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.Book", b => + { + b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); + + b.ToTable("Book"); + + b.HasDiscriminator().HasValue("Book"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.CustomItem", b => + { + b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); + + b.ToTable("LibraryItem"); + + b.HasDiscriminator().HasValue("CustomItem"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Episode", b => + { + b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("Episode_Episodes_Id") + .HasColumnType("INTEGER"); + + b.HasIndex("Episode_Episodes_Id"); + + b.ToTable("Episode"); + + b.HasDiscriminator().HasValue("Episode"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Movie", b => + { + b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); + + b.ToTable("Movie"); + + b.HasDiscriminator().HasValue("Movie"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MusicAlbum", b => + { + b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); + + b.ToTable("MusicAlbum"); + + b.HasDiscriminator().HasValue("MusicAlbum"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Photo", b => + { + b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); + + b.ToTable("Photo"); + + b.HasDiscriminator().HasValue("Photo"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Season", b => + { + b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.Property("Season_Seasons_Id") + .HasColumnType("INTEGER"); + + b.HasIndex("Season_Seasons_Id"); + + b.ToTable("Season"); + + b.HasDiscriminator().HasValue("Season"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Series", b => + { + b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); + + b.Property("AirsDayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("AirsTime") + .HasColumnType("TEXT"); + + b.Property("FirstAired") + .HasColumnType("TEXT"); + + b.ToTable("Series"); + + b.HasDiscriminator().HasValue("Series"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Track", b => + { + b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); + + b.Property("TrackNumber") + .HasColumnType("INTEGER"); + + b.Property("Track_Tracks_Id") + .HasColumnType("INTEGER"); + + b.HasIndex("Track_Tracks_Id"); + + b.ToTable("Track"); + + b.HasDiscriminator().HasValue("Track"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.BookMetadata", b => + { + b.HasBaseType("Jellyfin.Data.Entities.Metadata"); + + b.Property("BookMetadata_BookMetadata_Id") + .HasColumnType("INTEGER"); + + b.Property("ISBN") + .HasColumnType("INTEGER"); + + b.HasIndex("BookMetadata_BookMetadata_Id"); + + b.ToTable("Metadata"); + + b.HasDiscriminator().HasValue("BookMetadata"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.CompanyMetadata", b => + { + b.HasBaseType("Jellyfin.Data.Entities.Metadata"); + + b.Property("CompanyMetadata_CompanyMetadata_Id") + .HasColumnType("INTEGER"); + + b.Property("Country") + .HasColumnType("TEXT") + .HasMaxLength(2); + + b.Property("Description") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("Headquarters") + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("Homepage") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.HasIndex("CompanyMetadata_CompanyMetadata_Id"); + + b.ToTable("CompanyMetadata"); + + b.HasDiscriminator().HasValue("CompanyMetadata"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemMetadata", b => + { + b.HasBaseType("Jellyfin.Data.Entities.Metadata"); + + b.Property("CustomItemMetadata_CustomItemMetadata_Id") + .HasColumnType("INTEGER"); + + b.HasIndex("CustomItemMetadata_CustomItemMetadata_Id"); + + b.ToTable("CustomItemMetadata"); + + b.HasDiscriminator().HasValue("CustomItemMetadata"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.EpisodeMetadata", b => + { + b.HasBaseType("Jellyfin.Data.Entities.Metadata"); + + b.Property("EpisodeMetadata_EpisodeMetadata_Id") + .HasColumnType("INTEGER"); + + b.Property("Outline") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("Plot") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("Tagline") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.HasIndex("EpisodeMetadata_EpisodeMetadata_Id"); + + b.ToTable("EpisodeMetadata"); + + b.HasDiscriminator().HasValue("EpisodeMetadata"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MovieMetadata", b => + { + b.HasBaseType("Jellyfin.Data.Entities.Metadata"); + + b.Property("Country") + .HasColumnName("MovieMetadata_Country") + .HasColumnType("TEXT") + .HasMaxLength(2); + + b.Property("MovieMetadata_MovieMetadata_Id") + .HasColumnType("INTEGER"); + + b.Property("Outline") + .HasColumnName("MovieMetadata_Outline") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("Plot") + .HasColumnName("MovieMetadata_Plot") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("Tagline") + .HasColumnName("MovieMetadata_Tagline") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.HasIndex("MovieMetadata_MovieMetadata_Id"); + + b.ToTable("MovieMetadata"); + + b.HasDiscriminator().HasValue("MovieMetadata"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MusicAlbumMetadata", b => + { + b.HasBaseType("Jellyfin.Data.Entities.Metadata"); + + b.Property("Barcode") + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("Country") + .HasColumnName("MusicAlbumMetadata_Country") + .HasColumnType("TEXT") + .HasMaxLength(2); + + b.Property("LabelNumber") + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("MusicAlbumMetadata_MusicAlbumMetadata_Id") + .HasColumnType("INTEGER"); + + b.HasIndex("MusicAlbumMetadata_MusicAlbumMetadata_Id"); + + b.ToTable("MusicAlbumMetadata"); + + b.HasDiscriminator().HasValue("MusicAlbumMetadata"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.PhotoMetadata", b => + { + b.HasBaseType("Jellyfin.Data.Entities.Metadata"); + + b.Property("PhotoMetadata_PhotoMetadata_Id") + .HasColumnType("INTEGER"); + + b.HasIndex("PhotoMetadata_PhotoMetadata_Id"); + + b.ToTable("PhotoMetadata"); + + b.HasDiscriminator().HasValue("PhotoMetadata"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.SeasonMetadata", b => + { + b.HasBaseType("Jellyfin.Data.Entities.Metadata"); + + b.Property("Outline") + .HasColumnName("SeasonMetadata_Outline") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("SeasonMetadata_SeasonMetadata_Id") + .HasColumnType("INTEGER"); + + b.HasIndex("SeasonMetadata_SeasonMetadata_Id"); + + b.ToTable("SeasonMetadata"); + + b.HasDiscriminator().HasValue("SeasonMetadata"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.SeriesMetadata", b => + { + b.HasBaseType("Jellyfin.Data.Entities.Metadata"); + + b.Property("Country") + .HasColumnName("SeriesMetadata_Country") + .HasColumnType("TEXT") + .HasMaxLength(2); + + b.Property("Outline") + .HasColumnName("SeriesMetadata_Outline") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.Property("Plot") + .HasColumnName("SeriesMetadata_Plot") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("SeriesMetadata_SeriesMetadata_Id") + .HasColumnType("INTEGER"); + + b.Property("Tagline") + .HasColumnName("SeriesMetadata_Tagline") + .HasColumnType("TEXT") + .HasMaxLength(1024); + + b.HasIndex("SeriesMetadata_SeriesMetadata_Id"); + + b.ToTable("SeriesMetadata"); + + b.HasDiscriminator().HasValue("SeriesMetadata"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.TrackMetadata", b => + { + b.HasBaseType("Jellyfin.Data.Entities.Metadata"); + + b.Property("TrackMetadata_TrackMetadata_Id") + .HasColumnType("INTEGER"); + + b.HasIndex("TrackMetadata_TrackMetadata_Id"); + + b.ToTable("TrackMetadata"); + + b.HasDiscriminator().HasValue("TrackMetadata"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Artwork", b => + { + b.HasOne("Jellyfin.Data.Entities.Metadata", null) + .WithMany("Artwork") + .HasForeignKey("PersonRole_PersonRoles_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Chapter", b => + { + b.HasOne("Jellyfin.Data.Entities.Release", null) + .WithMany("Chapters") + .HasForeignKey("Chapter_Chapters_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.CollectionItem", b => + { + b.HasOne("Jellyfin.Data.Entities.Collection", null) + .WithMany("CollectionItem") + .HasForeignKey("CollectionItem_CollectionItem_Id"); + + b.HasOne("Jellyfin.Data.Entities.CollectionItem", "Next") + .WithMany() + .HasForeignKey("CollectionItem_Next_Id"); + + b.HasOne("Jellyfin.Data.Entities.CollectionItem", "Previous") + .WithMany() + .HasForeignKey("CollectionItem_Previous_Id"); + + b.HasOne("Jellyfin.Data.Entities.LibraryItem", "LibraryItem") + .WithMany() + .HasForeignKey("LibraryItem_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Company", b => + { + b.HasOne("Jellyfin.Data.Entities.MusicAlbumMetadata", null) + .WithMany("Labels") + .HasForeignKey("Company_Labels_Id"); + + b.HasOne("Jellyfin.Data.Entities.SeriesMetadata", null) + .WithMany("Networks") + .HasForeignKey("Company_Networks_Id"); + + b.HasOne("Jellyfin.Data.Entities.Company", "Parent") + .WithMany() + .HasForeignKey("Company_Parent_Id"); + + b.HasOne("Jellyfin.Data.Entities.BookMetadata", null) + .WithMany("Publishers") + .HasForeignKey("Company_Publishers_Id"); + + b.HasOne("Jellyfin.Data.Entities.MovieMetadata", null) + .WithMany("Studios") + .HasForeignKey("Company_Studios_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Genre", b => + { + b.HasOne("Jellyfin.Data.Entities.Metadata", null) + .WithMany("Genres") + .HasForeignKey("PersonRole_PersonRoles_Id"); + }); + + 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.LibraryItem", b => + { + b.HasOne("Jellyfin.Data.Entities.LibraryRoot", "LibraryRoot") + .WithMany() + .HasForeignKey("LibraryRoot_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.LibraryRoot", b => + { + b.HasOne("Jellyfin.Data.Entities.Library", "Library") + .WithMany() + .HasForeignKey("Library_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MediaFile", b => + { + b.HasOne("Jellyfin.Data.Entities.Release", null) + .WithMany("MediaFiles") + .HasForeignKey("MediaFile_MediaFiles_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MediaFileStream", b => + { + b.HasOne("Jellyfin.Data.Entities.MediaFile", null) + .WithMany("MediaFileStreams") + .HasForeignKey("MediaFileStream_MediaFileStreams_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MetadataProviderId", b => + { + b.HasOne("Jellyfin.Data.Entities.Person", null) + .WithMany("Sources") + .HasForeignKey("MetadataProviderId_Sources_Id"); + + b.HasOne("Jellyfin.Data.Entities.PersonRole", null) + .WithMany("Sources") + .HasForeignKey("MetadataProviderId_Sources_Id"); + + b.HasOne("Jellyfin.Data.Entities.MetadataProvider", "MetadataProvider") + .WithMany() + .HasForeignKey("MetadataProvider_Id"); + + b.HasOne("Jellyfin.Data.Entities.Metadata", null) + .WithMany("Sources") + .HasForeignKey("PersonRole_PersonRoles_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.PersonRole", b => + { + b.HasOne("Jellyfin.Data.Entities.Artwork", "Artwork") + .WithMany() + .HasForeignKey("Artwork_Artwork_Id"); + + b.HasOne("Jellyfin.Data.Entities.Metadata", null) + .WithMany("PersonRoles") + .HasForeignKey("PersonRole_PersonRoles_Id"); + + b.HasOne("Jellyfin.Data.Entities.Person", "Person") + .WithMany() + .HasForeignKey("Person_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"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Rating", b => + { + b.HasOne("Jellyfin.Data.Entities.Metadata", null) + .WithMany("Ratings") + .HasForeignKey("PersonRole_PersonRoles_Id"); + + b.HasOne("Jellyfin.Data.Entities.RatingSource", "RatingType") + .WithMany() + .HasForeignKey("RatingSource_RatingType_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.RatingSource", b => + { + b.HasOne("Jellyfin.Data.Entities.MetadataProviderId", "Source") + .WithMany() + .HasForeignKey("MetadataProviderId_Source_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Release", b => + { + b.HasOne("Jellyfin.Data.Entities.Book", null) + .WithMany("Releases") + .HasForeignKey("Release_Releases_Id"); + + b.HasOne("Jellyfin.Data.Entities.CustomItem", null) + .WithMany("Releases") + .HasForeignKey("Release_Releases_Id") + .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id1"); + + b.HasOne("Jellyfin.Data.Entities.Episode", null) + .WithMany("Releases") + .HasForeignKey("Release_Releases_Id") + .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id2"); + + b.HasOne("Jellyfin.Data.Entities.Movie", null) + .WithMany("Releases") + .HasForeignKey("Release_Releases_Id") + .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id3"); + + b.HasOne("Jellyfin.Data.Entities.Photo", null) + .WithMany("Releases") + .HasForeignKey("Release_Releases_Id") + .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id4"); + + b.HasOne("Jellyfin.Data.Entities.Track", null) + .WithMany("Releases") + .HasForeignKey("Release_Releases_Id") + .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id5"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Episode", b => + { + b.HasOne("Jellyfin.Data.Entities.Season", null) + .WithMany("Episodes") + .HasForeignKey("Episode_Episodes_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Season", b => + { + b.HasOne("Jellyfin.Data.Entities.Series", null) + .WithMany("Seasons") + .HasForeignKey("Season_Seasons_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Track", b => + { + b.HasOne("Jellyfin.Data.Entities.MusicAlbum", null) + .WithMany("Tracks") + .HasForeignKey("Track_Tracks_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.BookMetadata", b => + { + b.HasOne("Jellyfin.Data.Entities.Book", null) + .WithMany("BookMetadata") + .HasForeignKey("BookMetadata_BookMetadata_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.CompanyMetadata", b => + { + b.HasOne("Jellyfin.Data.Entities.Company", null) + .WithMany("CompanyMetadata") + .HasForeignKey("CompanyMetadata_CompanyMetadata_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemMetadata", b => + { + b.HasOne("Jellyfin.Data.Entities.CustomItem", null) + .WithMany("CustomItemMetadata") + .HasForeignKey("CustomItemMetadata_CustomItemMetadata_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.EpisodeMetadata", b => + { + b.HasOne("Jellyfin.Data.Entities.Episode", null) + .WithMany("EpisodeMetadata") + .HasForeignKey("EpisodeMetadata_EpisodeMetadata_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MovieMetadata", b => + { + b.HasOne("Jellyfin.Data.Entities.Movie", null) + .WithMany("MovieMetadata") + .HasForeignKey("MovieMetadata_MovieMetadata_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.MusicAlbumMetadata", b => + { + b.HasOne("Jellyfin.Data.Entities.MusicAlbum", null) + .WithMany("MusicAlbumMetadata") + .HasForeignKey("MusicAlbumMetadata_MusicAlbumMetadata_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.PhotoMetadata", b => + { + b.HasOne("Jellyfin.Data.Entities.Photo", null) + .WithMany("PhotoMetadata") + .HasForeignKey("PhotoMetadata_PhotoMetadata_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.SeasonMetadata", b => + { + b.HasOne("Jellyfin.Data.Entities.Season", null) + .WithMany("SeasonMetadata") + .HasForeignKey("SeasonMetadata_SeasonMetadata_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.SeriesMetadata", b => + { + b.HasOne("Jellyfin.Data.Entities.Series", null) + .WithMany("SeriesMetadata") + .HasForeignKey("SeriesMetadata_SeriesMetadata_Id"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.TrackMetadata", b => + { + b.HasOne("Jellyfin.Data.Entities.Track", null) + .WithMany("TrackMetadata") + .HasForeignKey("TrackMetadata_TrackMetadata_Id"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 88114d999..4194070aa 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -13,6 +13,9 @@ true true enable + + + True @@ -41,6 +44,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index b5ea04dca..82e304586 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -16,7 +16,8 @@ namespace Jellyfin.Server.Migrations internal static readonly IMigrationRoutine[] Migrations = { new Routines.DisableTranscodingThrottling(), - new Routines.CreateUserLoggingConfigFile() + new Routines.CreateUserLoggingConfigFile(), + new Routines.MigrateActivityLogDb() }; /// diff --git a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs new file mode 100644 index 000000000..9f1f5b92e --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs @@ -0,0 +1,109 @@ +#pragma warning disable CS1591 + +using System; +using System.IO; +using Emby.Server.Implementations.Data; +using Jellyfin.Data.Entities; +using Jellyfin.Server.Implementations; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using SQLitePCL.pretty; + +namespace Jellyfin.Server.Migrations.Routines +{ + public class MigrateActivityLogDb : IMigrationRoutine + { + private const string DbFilename = "activitylog.db"; + + public Guid Id => Guid.Parse("3793eb59-bc8c-456c-8b9f-bd5a62a42978"); + + public string Name => "MigrateActivityLogDatabase"; + + public void Perform(CoreAppHost host, ILogger logger) + { + var dataPath = host.ServerConfigurationManager.ApplicationPaths.DataPath; + using (var connection = SQLite3.Open( + Path.Combine(dataPath, DbFilename), + ConnectionFlags.ReadOnly, + null)) + { + logger.LogInformation("Migrating the database may take a while, do not stop Jellyfin."); + using var dbContext = host.ServiceProvider.GetService(); + + var queryResult = connection.Query("SELECT * FROM ActivityLog ORDER BY Id ASC"); + + // Make sure that the database is empty in case of failed migration due to power outages, etc. + dbContext.ActivityLogs.RemoveRange(dbContext.ActivityLogs); + dbContext.SaveChanges(); + // Reset the autoincrement counter + dbContext.Database.ExecuteSqlRaw("UPDATE sqlite_sequence SET seq = 0 WHERE name = 'ActivityLog';"); + dbContext.SaveChanges(); + + foreach (var entry in queryResult) + { + var newEntry = new ActivityLog( + entry[1].ToString(), + entry[4].ToString(), + entry[6].SQLiteType == SQLiteType.Null ? Guid.Empty : Guid.Parse(entry[6].ToString()), + entry[7].ReadDateTime(), + ParseLogLevel(entry[8].ToString())); + + if (entry[2].SQLiteType != SQLiteType.Null) + { + newEntry.Overview = entry[2].ToString(); + } + + if (entry[3].SQLiteType != SQLiteType.Null) + { + newEntry.ShortOverview = entry[3].ToString(); + } + + if (entry[5].SQLiteType != SQLiteType.Null) + { + newEntry.ItemId = entry[5].ToString(); + } + + dbContext.ActivityLogs.Add(newEntry); + dbContext.SaveChanges(); + } + } + + try + { + File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old")); + } + catch (IOException e) + { + logger.LogError(e, "Error renaming legacy activity log database to 'activitylog.db.old'"); + } + } + + private LogLevel ParseLogLevel(string entry) + { + if (string.Equals(entry, "Debug", StringComparison.OrdinalIgnoreCase)) + { + return LogLevel.Debug; + } + + if (string.Equals(entry, "Information", StringComparison.OrdinalIgnoreCase) + || string.Equals(entry, "Info", StringComparison.OrdinalIgnoreCase)) + { + return LogLevel.Information; + } + + if (string.Equals(entry, "Warning", StringComparison.OrdinalIgnoreCase) + || string.Equals(entry, "Warn", StringComparison.OrdinalIgnoreCase)) + { + return LogLevel.Warning; + } + + if (string.Equals(entry, "Error", StringComparison.OrdinalIgnoreCase)) + { + return LogLevel.Error; + } + + return LogLevel.Trace; + } + } +} diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index a54640b2f..997b1c45a 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -759,13 +759,14 @@ namespace MediaBrowser.Api.Library { try { - _activityManager.Create(new ActivityLogEntry + _activityManager.Create(new Jellyfin.Data.Entities.ActivityLog( + string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Name, item.Name), + "UserDownloadingContent", + auth.UserId, + DateTime.UtcNow, + LogLevel.Trace) { - Name = string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Name, item.Name), - Type = "UserDownloadingContent", ShortOverview = string.Format(_localization.GetLocalizedString("AppDeviceValues"), auth.Client, auth.Device), - UserId = auth.UserId - }); } catch diff --git a/MediaBrowser.Api/System/ActivityLogService.cs b/MediaBrowser.Api/System/ActivityLogService.cs index f95fa7ca0..0a5fc9433 100644 --- a/MediaBrowser.Api/System/ActivityLogService.cs +++ b/MediaBrowser.Api/System/ActivityLogService.cs @@ -53,7 +53,7 @@ namespace MediaBrowser.Api.System (DateTime?)null : DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); - var result = _activityManager.GetActivityLogEntries(minDate, request.HasUserId, request.StartIndex, request.Limit); + var result = _activityManager.GetPagedResult(request.StartIndex, request.Limit); return ToOptimizedResult(result); } diff --git a/MediaBrowser.Model/Activity/ActivityLogEntry.cs b/MediaBrowser.Model/Activity/ActivityLogEntry.cs index 80f01b66e..5ab904394 100644 --- a/MediaBrowser.Model/Activity/ActivityLogEntry.cs +++ b/MediaBrowser.Model/Activity/ActivityLogEntry.cs @@ -59,6 +59,7 @@ namespace MediaBrowser.Model.Activity /// Gets or sets the user primary image tag. /// /// The user primary image tag. + [Obsolete("UserPrimaryImageTag is not used.")] public string UserPrimaryImageTag { get; set; } /// diff --git a/MediaBrowser.Model/Activity/IActivityManager.cs b/MediaBrowser.Model/Activity/IActivityManager.cs index f336f5272..6742dc8fc 100644 --- a/MediaBrowser.Model/Activity/IActivityManager.cs +++ b/MediaBrowser.Model/Activity/IActivityManager.cs @@ -1,6 +1,10 @@ #pragma warning disable CS1591 using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Model.Events; using MediaBrowser.Model.Querying; @@ -10,10 +14,15 @@ namespace MediaBrowser.Model.Activity { event EventHandler> EntryCreated; - void Create(ActivityLogEntry entry); + void Create(ActivityLog entry); - QueryResult GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit); + Task CreateAsync(ActivityLog entry); - QueryResult GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? x, int? y); + QueryResult GetPagedResult(int? startIndex, int? limit); + + QueryResult GetPagedResult( + Func, IEnumerable> func, + int? startIndex, + int? limit); } } diff --git a/MediaBrowser.Model/Activity/IActivityRepository.cs b/MediaBrowser.Model/Activity/IActivityRepository.cs deleted file mode 100644 index 66144ec47..000000000 --- a/MediaBrowser.Model/Activity/IActivityRepository.cs +++ /dev/null @@ -1,14 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using MediaBrowser.Model.Querying; - -namespace MediaBrowser.Model.Activity -{ - public interface IActivityRepository - { - void Create(ActivityLogEntry entry); - - QueryResult GetActivityLogEntries(DateTime? minDate, bool? z, int? startIndex, int? limit); - } -} diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index b41d0af1d..5c6e313e0 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -37,6 +37,9 @@ + + + ../jellyfin.ruleset diff --git a/MediaBrowser.sln b/MediaBrowser.sln index a1dbe8047..6d01b0dcd 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.3 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30011.22 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}" EndProject @@ -46,23 +46,25 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Drawing.Skia", "Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Api", "Jellyfin.Api\Jellyfin.Api.csproj", "{DFBEFB4C-DA19-4143-98B7-27320C7F7163}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Api", "Jellyfin.Api\Jellyfin.Api.csproj", "{DFBEFB4C-DA19-4143-98B7-27320C7F7163}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Common.Tests", "tests\Jellyfin.Common.Tests\Jellyfin.Common.Tests.csproj", "{DF194677-DFD3-42AF-9F75-D44D5A416478}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Common.Tests", "tests\Jellyfin.Common.Tests\Jellyfin.Common.Tests.csproj", "{DF194677-DFD3-42AF-9F75-D44D5A416478}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.MediaEncoding.Tests", "tests\Jellyfin.MediaEncoding.Tests\Jellyfin.MediaEncoding.Tests.csproj", "{28464062-0939-4AA7-9F7B-24DDDA61A7C0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.MediaEncoding.Tests", "tests\Jellyfin.MediaEncoding.Tests\Jellyfin.MediaEncoding.Tests.csproj", "{28464062-0939-4AA7-9F7B-24DDDA61A7C0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Naming.Tests", "tests\Jellyfin.Naming.Tests\Jellyfin.Naming.Tests.csproj", "{3998657B-1CCC-49DD-A19F-275DC8495F57}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Naming.Tests", "tests\Jellyfin.Naming.Tests\Jellyfin.Naming.Tests.csproj", "{3998657B-1CCC-49DD-A19F-275DC8495F57}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Api.Tests", "tests\Jellyfin.Api.Tests\Jellyfin.Api.Tests.csproj", "{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Api.Tests", "tests\Jellyfin.Api.Tests\Jellyfin.Api.Tests.csproj", "{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Implementations.Tests", "tests\Jellyfin.Server.Implementations.Tests\Jellyfin.Server.Implementations.Tests.csproj", "{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Implementations.Tests", "tests\Jellyfin.Server.Implementations.Tests\Jellyfin.Server.Implementations.Tests.csproj", "{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Controller.Tests", "tests\Jellyfin.Controller.Tests\Jellyfin.Controller.Tests.csproj", "{462584F7-5023-4019-9EAC-B98CA458C0A0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Controller.Tests", "tests\Jellyfin.Controller.Tests\Jellyfin.Controller.Tests.csproj", "{462584F7-5023-4019-9EAC-B98CA458C0A0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Data", "Jellyfin.Data\Jellyfin.Data.csproj", "{F03299F2-469F-40EF-A655-3766F97A5702}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Data", "Jellyfin.Data\Jellyfin.Data.csproj", "{F03299F2-469F-40EF-A655-3766F97A5702}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server.Implementations", "Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj", "{DAE48069-6D86-4BA6-B148-D1D49B6DDA52}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -114,10 +116,6 @@ Global {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.ActiveCfg = Release|Any CPU {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.Build.0 = Release|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.Build.0 = Release|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -182,10 +180,22 @@ Global {F03299F2-469F-40EF-A655-3766F97A5702}.Debug|Any CPU.Build.0 = Debug|Any CPU {F03299F2-469F-40EF-A655-3766F97A5702}.Release|Any CPU.ActiveCfg = Release|Any CPU {F03299F2-469F-40EF-A655-3766F97A5702}.Release|Any CPU.Build.0 = Release|Any CPU + {DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DAE48069-6D86-4BA6-B148-D1D49B6DDA52}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {DF194677-DFD3-42AF-9F75-D44D5A416478} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {28464062-0939-4AA7-9F7B-24DDDA61A7C0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {3998657B-1CCC-49DD-A19F-275DC8495F57} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + {462584F7-5023-4019-9EAC-B98CA458C0A0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} EndGlobalSection @@ -207,12 +217,4 @@ Global $0.DotNetNamingPolicy = $2 $2.DirectoryNamespaceAssociation = PrefixedHierarchical EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {DF194677-DFD3-42AF-9F75-D44D5A416478} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} - {28464062-0939-4AA7-9F7B-24DDDA61A7C0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} - {3998657B-1CCC-49DD-A19F-275DC8495F57} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} - {A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} - {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} - {462584F7-5023-4019-9EAC-B98CA458C0A0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} - EndGlobalSection EndGlobal -- cgit v1.2.3 From 0017163f39438e2718f7c95b3fb65df5dde65e3d Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 2 May 2020 17:06:29 -0600 Subject: Update endpoint docs --- Jellyfin.Api/Controllers/DisplayPreferencesController.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs index 0d375e668..2837ea8e8 100644 --- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs +++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs @@ -34,7 +34,9 @@ namespace Jellyfin.Api.Controllers /// Display preferences id. /// User id. /// Client. - /// Display Preferences. + /// Display preferences retrieved. + /// Specified display preferences not found. + /// An containing the display preferences on success, or a if the display preferences could not be found. [HttpGet("{DisplayPreferencesId}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -59,7 +61,9 @@ namespace Jellyfin.Api.Controllers /// User Id. /// Client. /// New Display Preferences object. - /// Status. + /// Display preferences updated. + /// Specified display preferences not found. + /// An on success, or a if the display preferences could not be found. [HttpPost("{DisplayPreferencesId}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ModelStateDictionary), StatusCodes.Status400BadRequest)] -- cgit v1.2.3 From f67daa84b04ae6c8ffcc42c038a65ecb8a433861 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 2 May 2020 17:10:59 -0600 Subject: Update endpoint docs --- Jellyfin.Api/Controllers/ScheduledTasksController.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs index da7cfbc3a..ad70bf83b 100644 --- a/Jellyfin.Api/Controllers/ScheduledTasksController.cs +++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs @@ -33,7 +33,8 @@ namespace Jellyfin.Api.Controllers /// /// Optional filter tasks that are hidden, or not. /// Optional filter tasks that are enabled, or not. - /// Task list. + /// Scheduled tasks retrieved. + /// The list of scheduled tasks. [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] public IEnumerable GetTasks( @@ -65,7 +66,9 @@ namespace Jellyfin.Api.Controllers /// Get task by id. /// /// Task Id. - /// Task Info. + /// Task retrieved. + /// Task not found. + /// An containing the task on success, or a if the task could not be found. [HttpGet("{TaskID}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -87,7 +90,9 @@ namespace Jellyfin.Api.Controllers /// Start specified task. /// /// Task Id. - /// Status. + /// Task started. + /// Task not found. + /// An on success, or a if the file could not be found. [HttpPost("Running/{TaskID}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -109,7 +114,9 @@ namespace Jellyfin.Api.Controllers /// Stop specified task. /// /// Task Id. - /// Status. + /// Task stopped. + /// Task not found. + /// An on success, or a if the file could not be found. [HttpDelete("Running/{TaskID}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -132,7 +139,9 @@ namespace Jellyfin.Api.Controllers /// /// Task Id. /// Triggers. - /// Status. + /// Task triggers updated. + /// Task not found. + /// An on success, or a if the file could not be found. [HttpPost("{TaskID}/Triggers")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] -- cgit v1.2.3 From 7516e3ebbec82b732e8e4355ae108e7030e1e00e Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 2 May 2020 17:12:56 -0600 Subject: Update endpoint docs --- Jellyfin.Api/Controllers/AttachmentsController.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/AttachmentsController.cs b/Jellyfin.Api/Controllers/AttachmentsController.cs index b0cdfb86e..30fb951cf 100644 --- a/Jellyfin.Api/Controllers/AttachmentsController.cs +++ b/Jellyfin.Api/Controllers/AttachmentsController.cs @@ -41,7 +41,9 @@ namespace Jellyfin.Api.Controllers /// Video ID. /// Media Source ID. /// Attachment Index. - /// Attachment. + /// Attachment retrieved. + /// Video or attachment not found. + /// An containing the attachment stream on success, or a if the attachment could not be found. [HttpGet("{VideoID}/{MediaSourceID}/Attachments/{Index}")] [Produces("application/octet-stream")] [ProducesResponseType(StatusCodes.Status200OK)] -- cgit v1.2.3 From 25002483a3fd7f9d1c79c74338ac18c8eabfb0ed Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 2 May 2020 17:23:02 -0600 Subject: Update endpoint docs --- Jellyfin.Api/Controllers/DevicesController.cs | 53 +++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs index cebb51ccf..02cf1bc44 100644 --- a/Jellyfin.Api/Controllers/DevicesController.cs +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -46,11 +47,12 @@ namespace Jellyfin.Api.Controllers /// /// /// Gets or sets a value indicating whether [supports synchronize]. /// /// Gets or sets the user identifier. - /// Device Infos. + /// Devices retrieved. + /// An containing the list of devices. [HttpGet] [Authenticated(Roles = "Admin")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId) + public ActionResult> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId) { var deviceQuery = new DeviceQuery { SupportsSync = supportsSync, UserId = userId ?? Guid.Empty }; var devices = _deviceManager.GetDevices(deviceQuery); @@ -61,7 +63,9 @@ namespace Jellyfin.Api.Controllers /// Get info for a device. /// /// Device Id. - /// Device Info. + /// Device info retrieved. + /// Device not found. + /// An containing the device info on success, or a if the device could not be found. [HttpGet("Info")] [Authenticated(Roles = "Admin")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -81,7 +85,9 @@ namespace Jellyfin.Api.Controllers /// Get options for a device. /// /// Device Id. - /// Device Info. + /// Device options retrieved. + /// Device not found. + /// An containing the device info on success, or a if the device could not be found. [HttpGet("Options")] [Authenticated(Roles = "Admin")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -102,7 +108,9 @@ namespace Jellyfin.Api.Controllers /// /// Device Id. /// Device Options. - /// Status. + /// Device options updated. + /// Device not found. + /// An on success, or a if the device could not be found. [HttpPost("Options")] [Authenticated(Roles = "Admin")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -125,11 +133,19 @@ namespace Jellyfin.Api.Controllers /// Deletes a device. /// /// Device Id. - /// Status. + /// Device deleted. + /// Device not found. + /// An on success, or a if the device could not be found. [HttpDelete] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult DeleteDevice([FromQuery, BindRequired] string id) { + var existingDevice = _deviceManager.GetDevice(id); + if (existingDevice == null) + { + return NotFound(); + } + var sessions = _authenticationRepository.Get(new AuthenticationInfoQuery { DeviceId = id }).Items; foreach (var session in sessions) @@ -144,11 +160,19 @@ namespace Jellyfin.Api.Controllers /// Gets camera upload history for a device. /// /// Device Id. - /// Content Upload History. + /// Device upload history retrieved. + /// Device not found. + /// An containing the device upload history on success, or a if the device could not be found. [HttpGet("CameraUploads")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetCameraUploads([FromQuery, BindRequired] string id) { + var existingDevice = _deviceManager.GetDevice(id); + if (existingDevice == null) + { + return NotFound(); + } + var uploadHistory = _deviceManager.GetCameraUploadHistory(id); return uploadHistory; } @@ -160,7 +184,14 @@ namespace Jellyfin.Api.Controllers /// Album. /// Name. /// Id. - /// Status. + /// Contents uploaded. + /// No uploaded contents. + /// Device not found. + /// + /// An on success, + /// or a if the device could not be found + /// or a if the upload contains no files. + /// [HttpPost("CameraUploads")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] @@ -170,6 +201,12 @@ namespace Jellyfin.Api.Controllers [FromQuery, BindRequired] string name, [FromQuery, BindRequired] string id) { + var existingDevice = _deviceManager.GetDevice(id); + if (existingDevice == null) + { + return NotFound(); + } + Stream fileStream; string contentType; -- cgit v1.2.3 From 37dcbfbc3980ed962c28fe7f1ee9d8a78f65ec19 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 2 May 2020 19:26:24 -0400 Subject: Update code to only add implemented parts of the schema --- Jellyfin.Server.Implementations/JellyfinDb.cs | 8 +- .../20200430215054_InitialSchema.Designer.cs | 1513 -------------------- .../Migrations/20200430215054_InitialSchema.cs | 1294 ----------------- .../20200502231229_InitialSchema.Designer.cs | 73 + .../Migrations/20200502231229_InitialSchema.cs | 46 + .../Migrations/DesignTimeJellyfinDbFactory.cs | 3 + .../Migrations/JellyfinDbModelSnapshot.cs | 1445 +------------------ 7 files changed, 127 insertions(+), 4255 deletions(-) delete mode 100644 Jellyfin.Server.Implementations/Migrations/20200430215054_InitialSchema.Designer.cs delete mode 100644 Jellyfin.Server.Implementations/Migrations/20200430215054_InitialSchema.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20200502231229_InitialSchema.Designer.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20200502231229_InitialSchema.cs diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 9c1a23877..6fc8d251b 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -16,7 +16,7 @@ namespace Jellyfin.Server.Implementations public partial class JellyfinDb : DbContext { public virtual DbSet ActivityLogs { get; set; } - public virtual DbSet Artwork { get; set; } + /*public virtual DbSet Artwork { get; set; } public virtual DbSet Books { get; set; } public virtual DbSet BookMetadata { get; set; } public virtual DbSet Chapters { get; set; } @@ -63,7 +63,7 @@ namespace Jellyfin.Server.Implementations public virtual DbSet SeriesMetadata { get; set; } public virtual DbSet Tracks { get; set; } public virtual DbSet TrackMetadata { get; set; } - public virtual DbSet Users { get; set; } + public virtual DbSet Users { get; set; } */ /// /// Gets or sets the default connection string. @@ -94,13 +94,13 @@ namespace Jellyfin.Server.Implementations modelBuilder.HasDefaultSchema("jellyfin"); - modelBuilder.Entity().HasIndex(t => t.Kind); + /*modelBuilder.Entity().HasIndex(t => t.Kind); modelBuilder.Entity().HasIndex(t => t.Name) .IsUnique(); modelBuilder.Entity().HasIndex(t => t.UrlId) - .IsUnique(); + .IsUnique();*/ OnModelCreatedImpl(modelBuilder); } diff --git a/Jellyfin.Server.Implementations/Migrations/20200430215054_InitialSchema.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200430215054_InitialSchema.Designer.cs deleted file mode 100644 index 3fb0fd51e..000000000 --- a/Jellyfin.Server.Implementations/Migrations/20200430215054_InitialSchema.Designer.cs +++ /dev/null @@ -1,1513 +0,0 @@ -#pragma warning disable CS1591 - -// -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("20200430215054_InitialSchema")] - partial class InitialSchema - { - 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.Artwork", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Kind") - .HasColumnType("INTEGER"); - - b.Property("Path") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("PersonRole_PersonRoles_Id") - .HasColumnType("INTEGER"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Kind"); - - b.HasIndex("PersonRole_PersonRoles_Id"); - - b.ToTable("Artwork"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Chapter", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Chapter_Chapters_Id") - .HasColumnType("INTEGER"); - - b.Property("Language") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(3); - - b.Property("Name") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("TimeEnd") - .HasColumnType("INTEGER"); - - b.Property("TimeStart") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Chapter_Chapters_Id"); - - b.ToTable("Chapter"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Collection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.ToTable("Collection"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.CollectionItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CollectionItem_CollectionItem_Id") - .HasColumnType("INTEGER"); - - b.Property("CollectionItem_Next_Id") - .HasColumnType("INTEGER"); - - b.Property("CollectionItem_Previous_Id") - .HasColumnType("INTEGER"); - - b.Property("LibraryItem_Id") - .HasColumnType("INTEGER"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("CollectionItem_CollectionItem_Id"); - - b.HasIndex("CollectionItem_Next_Id"); - - b.HasIndex("CollectionItem_Previous_Id"); - - b.HasIndex("LibraryItem_Id"); - - b.ToTable("CollectionItem"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Company", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Company_Labels_Id") - .HasColumnType("INTEGER"); - - b.Property("Company_Networks_Id") - .HasColumnType("INTEGER"); - - b.Property("Company_Parent_Id") - .HasColumnType("INTEGER"); - - b.Property("Company_Publishers_Id") - .HasColumnType("INTEGER"); - - b.Property("Company_Studios_Id") - .HasColumnType("INTEGER"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Company_Labels_Id"); - - b.HasIndex("Company_Networks_Id"); - - b.HasIndex("Company_Parent_Id"); - - b.HasIndex("Company_Publishers_Id"); - - b.HasIndex("Company_Studios_Id"); - - b.ToTable("Company"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Genre", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.Property("PersonRole_PersonRoles_Id") - .HasColumnType("INTEGER"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.HasIndex("PersonRole_PersonRoles_Id"); - - b.ToTable("Genre"); - }); - - 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.Library", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.ToTable("Library"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.LibraryItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("DateAdded") - .HasColumnType("TEXT"); - - b.Property("Discriminator") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("LibraryRoot_Id") - .HasColumnType("INTEGER"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("UrlId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("LibraryRoot_Id"); - - b.HasIndex("UrlId") - .IsUnique(); - - b.ToTable("LibraryItem"); - - b.HasDiscriminator("Discriminator").HasValue("LibraryItem"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.LibraryRoot", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Library_Id") - .HasColumnType("INTEGER"); - - b.Property("NetworkPath") - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("Path") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Library_Id"); - - b.ToTable("LibraryRoot"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MediaFile", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Kind") - .HasColumnType("INTEGER"); - - b.Property("MediaFile_MediaFiles_Id") - .HasColumnType("INTEGER"); - - b.Property("Path") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("MediaFile_MediaFiles_Id"); - - b.ToTable("MediaFile"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MediaFileStream", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("MediaFileStream_MediaFileStreams_Id") - .HasColumnType("INTEGER"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("StreamNumber") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("MediaFileStream_MediaFileStreams_Id"); - - b.ToTable("MediaFileStream"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Metadata", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("DateAdded") - .HasColumnType("TEXT"); - - b.Property("DateModified") - .HasColumnType("TEXT"); - - b.Property("Discriminator") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Language") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(3); - - b.Property("OriginalTitle") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("ReleaseDate") - .HasColumnType("TEXT"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("SortTitle") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("Title") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.HasKey("Id"); - - b.ToTable("Metadata"); - - b.HasDiscriminator("Discriminator").HasValue("Metadata"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MetadataProvider", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.ToTable("MetadataProvider"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MetadataProviderId", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("MetadataProviderId_Sources_Id") - .HasColumnType("INTEGER"); - - b.Property("MetadataProvider_Id") - .HasColumnType("INTEGER"); - - b.Property("PersonRole_PersonRoles_Id") - .HasColumnType("INTEGER"); - - b.Property("ProviderId") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("MetadataProviderId_Sources_Id"); - - b.HasIndex("MetadataProvider_Id"); - - b.HasIndex("PersonRole_PersonRoles_Id"); - - b.ToTable("MetadataProviderId"); - }); - - 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.Person", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("DateAdded") - .HasColumnType("TEXT"); - - b.Property("DateModified") - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("SourceId") - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.Property("UrlId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Person"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.PersonRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Artwork_Artwork_Id") - .HasColumnType("INTEGER"); - - b.Property("PersonRole_PersonRoles_Id") - .HasColumnType("INTEGER"); - - b.Property("Person_Id") - .HasColumnType("INTEGER"); - - b.Property("Role") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Artwork_Artwork_Id"); - - b.HasIndex("PersonRole_PersonRoles_Id"); - - b.HasIndex("Person_Id"); - - b.ToTable("PersonRole"); - }); - - 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.Rating", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("PersonRole_PersonRoles_Id") - .HasColumnType("INTEGER"); - - b.Property("RatingSource_RatingType_Id") - .HasColumnType("INTEGER"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("Value") - .HasColumnType("REAL"); - - b.Property("Votes") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("PersonRole_PersonRoles_Id"); - - b.HasIndex("RatingSource_RatingType_Id"); - - b.ToTable("Rating"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.RatingSource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("MaximumValue") - .HasColumnType("REAL"); - - b.Property("MetadataProviderId_Source_Id") - .HasColumnType("INTEGER"); - - b.Property("MinimumValue") - .HasColumnType("REAL"); - - b.Property("Name") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("MetadataProviderId_Source_Id"); - - b.ToTable("RatingSource"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Release", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("Release_Releases_Id") - .HasColumnType("INTEGER"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Release_Releases_Id"); - - b.ToTable("Release"); - }); - - 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.Book", b => - { - b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); - - b.ToTable("Book"); - - b.HasDiscriminator().HasValue("Book"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.CustomItem", b => - { - b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); - - b.ToTable("LibraryItem"); - - b.HasDiscriminator().HasValue("CustomItem"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Episode", b => - { - b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); - - b.Property("EpisodeNumber") - .HasColumnType("INTEGER"); - - b.Property("Episode_Episodes_Id") - .HasColumnType("INTEGER"); - - b.HasIndex("Episode_Episodes_Id"); - - b.ToTable("Episode"); - - b.HasDiscriminator().HasValue("Episode"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Movie", b => - { - b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); - - b.ToTable("Movie"); - - b.HasDiscriminator().HasValue("Movie"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MusicAlbum", b => - { - b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); - - b.ToTable("MusicAlbum"); - - b.HasDiscriminator().HasValue("MusicAlbum"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Photo", b => - { - b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); - - b.ToTable("Photo"); - - b.HasDiscriminator().HasValue("Photo"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Season", b => - { - b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); - - b.Property("SeasonNumber") - .HasColumnType("INTEGER"); - - b.Property("Season_Seasons_Id") - .HasColumnType("INTEGER"); - - b.HasIndex("Season_Seasons_Id"); - - b.ToTable("Season"); - - b.HasDiscriminator().HasValue("Season"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Series", b => - { - b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); - - b.Property("AirsDayOfWeek") - .HasColumnType("INTEGER"); - - b.Property("AirsTime") - .HasColumnType("TEXT"); - - b.Property("FirstAired") - .HasColumnType("TEXT"); - - b.ToTable("Series"); - - b.HasDiscriminator().HasValue("Series"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Track", b => - { - b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); - - b.Property("TrackNumber") - .HasColumnType("INTEGER"); - - b.Property("Track_Tracks_Id") - .HasColumnType("INTEGER"); - - b.HasIndex("Track_Tracks_Id"); - - b.ToTable("Track"); - - b.HasDiscriminator().HasValue("Track"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.BookMetadata", b => - { - b.HasBaseType("Jellyfin.Data.Entities.Metadata"); - - b.Property("BookMetadata_BookMetadata_Id") - .HasColumnType("INTEGER"); - - b.Property("ISBN") - .HasColumnType("INTEGER"); - - b.HasIndex("BookMetadata_BookMetadata_Id"); - - b.ToTable("Metadata"); - - b.HasDiscriminator().HasValue("BookMetadata"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.CompanyMetadata", b => - { - b.HasBaseType("Jellyfin.Data.Entities.Metadata"); - - b.Property("CompanyMetadata_CompanyMetadata_Id") - .HasColumnType("INTEGER"); - - b.Property("Country") - .HasColumnType("TEXT") - .HasMaxLength(2); - - b.Property("Description") - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("Headquarters") - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.Property("Homepage") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.HasIndex("CompanyMetadata_CompanyMetadata_Id"); - - b.ToTable("CompanyMetadata"); - - b.HasDiscriminator().HasValue("CompanyMetadata"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemMetadata", b => - { - b.HasBaseType("Jellyfin.Data.Entities.Metadata"); - - b.Property("CustomItemMetadata_CustomItemMetadata_Id") - .HasColumnType("INTEGER"); - - b.HasIndex("CustomItemMetadata_CustomItemMetadata_Id"); - - b.ToTable("CustomItemMetadata"); - - b.HasDiscriminator().HasValue("CustomItemMetadata"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.EpisodeMetadata", b => - { - b.HasBaseType("Jellyfin.Data.Entities.Metadata"); - - b.Property("EpisodeMetadata_EpisodeMetadata_Id") - .HasColumnType("INTEGER"); - - b.Property("Outline") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("Plot") - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("Tagline") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.HasIndex("EpisodeMetadata_EpisodeMetadata_Id"); - - b.ToTable("EpisodeMetadata"); - - b.HasDiscriminator().HasValue("EpisodeMetadata"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MovieMetadata", b => - { - b.HasBaseType("Jellyfin.Data.Entities.Metadata"); - - b.Property("Country") - .HasColumnName("MovieMetadata_Country") - .HasColumnType("TEXT") - .HasMaxLength(2); - - b.Property("MovieMetadata_MovieMetadata_Id") - .HasColumnType("INTEGER"); - - b.Property("Outline") - .HasColumnName("MovieMetadata_Outline") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("Plot") - .HasColumnName("MovieMetadata_Plot") - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("Tagline") - .HasColumnName("MovieMetadata_Tagline") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.HasIndex("MovieMetadata_MovieMetadata_Id"); - - b.ToTable("MovieMetadata"); - - b.HasDiscriminator().HasValue("MovieMetadata"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MusicAlbumMetadata", b => - { - b.HasBaseType("Jellyfin.Data.Entities.Metadata"); - - b.Property("Barcode") - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.Property("Country") - .HasColumnName("MusicAlbumMetadata_Country") - .HasColumnType("TEXT") - .HasMaxLength(2); - - b.Property("LabelNumber") - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.Property("MusicAlbumMetadata_MusicAlbumMetadata_Id") - .HasColumnType("INTEGER"); - - b.HasIndex("MusicAlbumMetadata_MusicAlbumMetadata_Id"); - - b.ToTable("MusicAlbumMetadata"); - - b.HasDiscriminator().HasValue("MusicAlbumMetadata"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.PhotoMetadata", b => - { - b.HasBaseType("Jellyfin.Data.Entities.Metadata"); - - b.Property("PhotoMetadata_PhotoMetadata_Id") - .HasColumnType("INTEGER"); - - b.HasIndex("PhotoMetadata_PhotoMetadata_Id"); - - b.ToTable("PhotoMetadata"); - - b.HasDiscriminator().HasValue("PhotoMetadata"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.SeasonMetadata", b => - { - b.HasBaseType("Jellyfin.Data.Entities.Metadata"); - - b.Property("Outline") - .HasColumnName("SeasonMetadata_Outline") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("SeasonMetadata_SeasonMetadata_Id") - .HasColumnType("INTEGER"); - - b.HasIndex("SeasonMetadata_SeasonMetadata_Id"); - - b.ToTable("SeasonMetadata"); - - b.HasDiscriminator().HasValue("SeasonMetadata"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.SeriesMetadata", b => - { - b.HasBaseType("Jellyfin.Data.Entities.Metadata"); - - b.Property("Country") - .HasColumnName("SeriesMetadata_Country") - .HasColumnType("TEXT") - .HasMaxLength(2); - - b.Property("Outline") - .HasColumnName("SeriesMetadata_Outline") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("Plot") - .HasColumnName("SeriesMetadata_Plot") - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("SeriesMetadata_SeriesMetadata_Id") - .HasColumnType("INTEGER"); - - b.Property("Tagline") - .HasColumnName("SeriesMetadata_Tagline") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.HasIndex("SeriesMetadata_SeriesMetadata_Id"); - - b.ToTable("SeriesMetadata"); - - b.HasDiscriminator().HasValue("SeriesMetadata"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.TrackMetadata", b => - { - b.HasBaseType("Jellyfin.Data.Entities.Metadata"); - - b.Property("TrackMetadata_TrackMetadata_Id") - .HasColumnType("INTEGER"); - - b.HasIndex("TrackMetadata_TrackMetadata_Id"); - - b.ToTable("TrackMetadata"); - - b.HasDiscriminator().HasValue("TrackMetadata"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Artwork", b => - { - b.HasOne("Jellyfin.Data.Entities.Metadata", null) - .WithMany("Artwork") - .HasForeignKey("PersonRole_PersonRoles_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Chapter", b => - { - b.HasOne("Jellyfin.Data.Entities.Release", null) - .WithMany("Chapters") - .HasForeignKey("Chapter_Chapters_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.CollectionItem", b => - { - b.HasOne("Jellyfin.Data.Entities.Collection", null) - .WithMany("CollectionItem") - .HasForeignKey("CollectionItem_CollectionItem_Id"); - - b.HasOne("Jellyfin.Data.Entities.CollectionItem", "Next") - .WithMany() - .HasForeignKey("CollectionItem_Next_Id"); - - b.HasOne("Jellyfin.Data.Entities.CollectionItem", "Previous") - .WithMany() - .HasForeignKey("CollectionItem_Previous_Id"); - - b.HasOne("Jellyfin.Data.Entities.LibraryItem", "LibraryItem") - .WithMany() - .HasForeignKey("LibraryItem_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Company", b => - { - b.HasOne("Jellyfin.Data.Entities.MusicAlbumMetadata", null) - .WithMany("Labels") - .HasForeignKey("Company_Labels_Id"); - - b.HasOne("Jellyfin.Data.Entities.SeriesMetadata", null) - .WithMany("Networks") - .HasForeignKey("Company_Networks_Id"); - - b.HasOne("Jellyfin.Data.Entities.Company", "Parent") - .WithMany() - .HasForeignKey("Company_Parent_Id"); - - b.HasOne("Jellyfin.Data.Entities.BookMetadata", null) - .WithMany("Publishers") - .HasForeignKey("Company_Publishers_Id"); - - b.HasOne("Jellyfin.Data.Entities.MovieMetadata", null) - .WithMany("Studios") - .HasForeignKey("Company_Studios_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Genre", b => - { - b.HasOne("Jellyfin.Data.Entities.Metadata", null) - .WithMany("Genres") - .HasForeignKey("PersonRole_PersonRoles_Id"); - }); - - 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.LibraryItem", b => - { - b.HasOne("Jellyfin.Data.Entities.LibraryRoot", "LibraryRoot") - .WithMany() - .HasForeignKey("LibraryRoot_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.LibraryRoot", b => - { - b.HasOne("Jellyfin.Data.Entities.Library", "Library") - .WithMany() - .HasForeignKey("Library_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MediaFile", b => - { - b.HasOne("Jellyfin.Data.Entities.Release", null) - .WithMany("MediaFiles") - .HasForeignKey("MediaFile_MediaFiles_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MediaFileStream", b => - { - b.HasOne("Jellyfin.Data.Entities.MediaFile", null) - .WithMany("MediaFileStreams") - .HasForeignKey("MediaFileStream_MediaFileStreams_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MetadataProviderId", b => - { - b.HasOne("Jellyfin.Data.Entities.Person", null) - .WithMany("Sources") - .HasForeignKey("MetadataProviderId_Sources_Id"); - - b.HasOne("Jellyfin.Data.Entities.PersonRole", null) - .WithMany("Sources") - .HasForeignKey("MetadataProviderId_Sources_Id"); - - b.HasOne("Jellyfin.Data.Entities.MetadataProvider", "MetadataProvider") - .WithMany() - .HasForeignKey("MetadataProvider_Id"); - - b.HasOne("Jellyfin.Data.Entities.Metadata", null) - .WithMany("Sources") - .HasForeignKey("PersonRole_PersonRoles_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.PersonRole", b => - { - b.HasOne("Jellyfin.Data.Entities.Artwork", "Artwork") - .WithMany() - .HasForeignKey("Artwork_Artwork_Id"); - - b.HasOne("Jellyfin.Data.Entities.Metadata", null) - .WithMany("PersonRoles") - .HasForeignKey("PersonRole_PersonRoles_Id"); - - b.HasOne("Jellyfin.Data.Entities.Person", "Person") - .WithMany() - .HasForeignKey("Person_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"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Rating", b => - { - b.HasOne("Jellyfin.Data.Entities.Metadata", null) - .WithMany("Ratings") - .HasForeignKey("PersonRole_PersonRoles_Id"); - - b.HasOne("Jellyfin.Data.Entities.RatingSource", "RatingType") - .WithMany() - .HasForeignKey("RatingSource_RatingType_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.RatingSource", b => - { - b.HasOne("Jellyfin.Data.Entities.MetadataProviderId", "Source") - .WithMany() - .HasForeignKey("MetadataProviderId_Source_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Release", b => - { - b.HasOne("Jellyfin.Data.Entities.Book", null) - .WithMany("Releases") - .HasForeignKey("Release_Releases_Id"); - - b.HasOne("Jellyfin.Data.Entities.CustomItem", null) - .WithMany("Releases") - .HasForeignKey("Release_Releases_Id") - .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id1"); - - b.HasOne("Jellyfin.Data.Entities.Episode", null) - .WithMany("Releases") - .HasForeignKey("Release_Releases_Id") - .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id2"); - - b.HasOne("Jellyfin.Data.Entities.Movie", null) - .WithMany("Releases") - .HasForeignKey("Release_Releases_Id") - .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id3"); - - b.HasOne("Jellyfin.Data.Entities.Photo", null) - .WithMany("Releases") - .HasForeignKey("Release_Releases_Id") - .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id4"); - - b.HasOne("Jellyfin.Data.Entities.Track", null) - .WithMany("Releases") - .HasForeignKey("Release_Releases_Id") - .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id5"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Episode", b => - { - b.HasOne("Jellyfin.Data.Entities.Season", null) - .WithMany("Episodes") - .HasForeignKey("Episode_Episodes_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Season", b => - { - b.HasOne("Jellyfin.Data.Entities.Series", null) - .WithMany("Seasons") - .HasForeignKey("Season_Seasons_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Track", b => - { - b.HasOne("Jellyfin.Data.Entities.MusicAlbum", null) - .WithMany("Tracks") - .HasForeignKey("Track_Tracks_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.BookMetadata", b => - { - b.HasOne("Jellyfin.Data.Entities.Book", null) - .WithMany("BookMetadata") - .HasForeignKey("BookMetadata_BookMetadata_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.CompanyMetadata", b => - { - b.HasOne("Jellyfin.Data.Entities.Company", null) - .WithMany("CompanyMetadata") - .HasForeignKey("CompanyMetadata_CompanyMetadata_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemMetadata", b => - { - b.HasOne("Jellyfin.Data.Entities.CustomItem", null) - .WithMany("CustomItemMetadata") - .HasForeignKey("CustomItemMetadata_CustomItemMetadata_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.EpisodeMetadata", b => - { - b.HasOne("Jellyfin.Data.Entities.Episode", null) - .WithMany("EpisodeMetadata") - .HasForeignKey("EpisodeMetadata_EpisodeMetadata_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MovieMetadata", b => - { - b.HasOne("Jellyfin.Data.Entities.Movie", null) - .WithMany("MovieMetadata") - .HasForeignKey("MovieMetadata_MovieMetadata_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MusicAlbumMetadata", b => - { - b.HasOne("Jellyfin.Data.Entities.MusicAlbum", null) - .WithMany("MusicAlbumMetadata") - .HasForeignKey("MusicAlbumMetadata_MusicAlbumMetadata_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.PhotoMetadata", b => - { - b.HasOne("Jellyfin.Data.Entities.Photo", null) - .WithMany("PhotoMetadata") - .HasForeignKey("PhotoMetadata_PhotoMetadata_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.SeasonMetadata", b => - { - b.HasOne("Jellyfin.Data.Entities.Season", null) - .WithMany("SeasonMetadata") - .HasForeignKey("SeasonMetadata_SeasonMetadata_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.SeriesMetadata", b => - { - b.HasOne("Jellyfin.Data.Entities.Series", null) - .WithMany("SeriesMetadata") - .HasForeignKey("SeriesMetadata_SeriesMetadata_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.TrackMetadata", b => - { - b.HasOne("Jellyfin.Data.Entities.Track", null) - .WithMany("TrackMetadata") - .HasForeignKey("TrackMetadata_TrackMetadata_Id"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Jellyfin.Server.Implementations/Migrations/20200430215054_InitialSchema.cs b/Jellyfin.Server.Implementations/Migrations/20200430215054_InitialSchema.cs deleted file mode 100644 index f6f2f1a81..000000000 --- a/Jellyfin.Server.Implementations/Migrations/20200430215054_InitialSchema.cs +++ /dev/null @@ -1,1294 +0,0 @@ -#pragma warning disable CS1591 -#pragma warning disable SA1601 - -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Jellyfin.Server.Implementations.Migrations -{ - public partial class InitialSchema : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.EnsureSchema( - name: "jellyfin"); - - migrationBuilder.CreateTable( - name: "ActivityLog", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Name = table.Column(maxLength: 512, nullable: false), - Overview = table.Column(maxLength: 512, nullable: true), - ShortOverview = table.Column(maxLength: 512, nullable: true), - Type = table.Column(maxLength: 256, nullable: false), - UserId = table.Column(nullable: false), - ItemId = table.Column(maxLength: 256, nullable: true), - DateCreated = table.Column(nullable: false), - LogSeverity = table.Column(nullable: false), - RowVersion = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ActivityLog", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Collection", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Name = table.Column(maxLength: 1024, nullable: true), - RowVersion = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Collection", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Library", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Name = table.Column(maxLength: 1024, nullable: false), - RowVersion = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Library", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "MetadataProvider", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Name = table.Column(maxLength: 1024, nullable: false), - RowVersion = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_MetadataProvider", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Person", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - UrlId = table.Column(nullable: false), - Name = table.Column(maxLength: 1024, nullable: false), - SourceId = table.Column(maxLength: 255, nullable: true), - DateAdded = table.Column(nullable: false), - DateModified = table.Column(nullable: false), - RowVersion = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Person", x => x.Id); - }); - - 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: "LibraryRoot", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Path = table.Column(maxLength: 65535, nullable: false), - NetworkPath = table.Column(maxLength: 65535, nullable: true), - RowVersion = table.Column(nullable: false), - Library_Id = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_LibraryRoot", x => x.Id); - table.ForeignKey( - name: "FK_LibraryRoot_Library_Library_Id", - column: x => x.Library_Id, - principalSchema: "jellyfin", - principalTable: "Library", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - 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: "LibraryItem", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - UrlId = table.Column(nullable: false), - DateAdded = table.Column(nullable: false), - RowVersion = table.Column(nullable: false), - LibraryRoot_Id = table.Column(nullable: true), - Discriminator = table.Column(nullable: false), - EpisodeNumber = table.Column(nullable: true), - Episode_Episodes_Id = table.Column(nullable: true), - SeasonNumber = table.Column(nullable: true), - Season_Seasons_Id = table.Column(nullable: true), - AirsDayOfWeek = table.Column(nullable: true), - AirsTime = table.Column(nullable: true), - FirstAired = table.Column(nullable: true), - TrackNumber = table.Column(nullable: true), - Track_Tracks_Id = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_LibraryItem", x => x.Id); - table.ForeignKey( - name: "FK_LibraryItem_LibraryItem_Episode_Episodes_Id", - column: x => x.Episode_Episodes_Id, - principalSchema: "jellyfin", - principalTable: "LibraryItem", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_LibraryItem_LibraryRoot_LibraryRoot_Id", - column: x => x.LibraryRoot_Id, - principalSchema: "jellyfin", - principalTable: "LibraryRoot", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_LibraryItem_LibraryItem_Season_Seasons_Id", - column: x => x.Season_Seasons_Id, - principalSchema: "jellyfin", - principalTable: "LibraryItem", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_LibraryItem_LibraryItem_Track_Tracks_Id", - column: x => x.Track_Tracks_Id, - principalSchema: "jellyfin", - principalTable: "LibraryItem", - 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.CreateTable( - name: "CollectionItem", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - RowVersion = table.Column(nullable: false), - LibraryItem_Id = table.Column(nullable: true), - CollectionItem_Next_Id = table.Column(nullable: true), - CollectionItem_Previous_Id = table.Column(nullable: true), - CollectionItem_CollectionItem_Id = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_CollectionItem", x => x.Id); - table.ForeignKey( - name: "FK_CollectionItem_Collection_CollectionItem_CollectionItem_Id", - column: x => x.CollectionItem_CollectionItem_Id, - principalSchema: "jellyfin", - principalTable: "Collection", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_CollectionItem_CollectionItem_CollectionItem_Next_Id", - column: x => x.CollectionItem_Next_Id, - principalSchema: "jellyfin", - principalTable: "CollectionItem", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_CollectionItem_CollectionItem_CollectionItem_Previous_Id", - column: x => x.CollectionItem_Previous_Id, - principalSchema: "jellyfin", - principalTable: "CollectionItem", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_CollectionItem_LibraryItem_LibraryItem_Id", - column: x => x.LibraryItem_Id, - principalSchema: "jellyfin", - principalTable: "LibraryItem", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "Release", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Name = table.Column(maxLength: 1024, nullable: false), - RowVersion = table.Column(nullable: false), - Release_Releases_Id = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Release", x => x.Id); - table.ForeignKey( - name: "FK_Release_LibraryItem_Release_Releases_Id", - column: x => x.Release_Releases_Id, - principalSchema: "jellyfin", - principalTable: "LibraryItem", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Release_LibraryItem_Release_Releases_Id1", - column: x => x.Release_Releases_Id, - principalSchema: "jellyfin", - principalTable: "LibraryItem", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Release_LibraryItem_Release_Releases_Id2", - column: x => x.Release_Releases_Id, - principalSchema: "jellyfin", - principalTable: "LibraryItem", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Release_LibraryItem_Release_Releases_Id3", - column: x => x.Release_Releases_Id, - principalSchema: "jellyfin", - principalTable: "LibraryItem", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Release_LibraryItem_Release_Releases_Id4", - column: x => x.Release_Releases_Id, - principalSchema: "jellyfin", - principalTable: "LibraryItem", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Release_LibraryItem_Release_Releases_Id5", - column: x => x.Release_Releases_Id, - principalSchema: "jellyfin", - principalTable: "LibraryItem", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "Chapter", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Name = table.Column(maxLength: 1024, nullable: true), - Language = table.Column(maxLength: 3, nullable: false), - TimeStart = table.Column(nullable: false), - TimeEnd = table.Column(nullable: true), - RowVersion = table.Column(nullable: false), - Chapter_Chapters_Id = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Chapter", x => x.Id); - table.ForeignKey( - name: "FK_Chapter_Release_Chapter_Chapters_Id", - column: x => x.Chapter_Chapters_Id, - principalSchema: "jellyfin", - principalTable: "Release", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "MediaFile", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Path = table.Column(maxLength: 65535, nullable: false), - Kind = table.Column(nullable: false), - RowVersion = table.Column(nullable: false), - MediaFile_MediaFiles_Id = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_MediaFile", x => x.Id); - table.ForeignKey( - name: "FK_MediaFile_Release_MediaFile_MediaFiles_Id", - column: x => x.MediaFile_MediaFiles_Id, - principalSchema: "jellyfin", - principalTable: "Release", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "MediaFileStream", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - StreamNumber = table.Column(nullable: false), - RowVersion = table.Column(nullable: false), - MediaFileStream_MediaFileStreams_Id = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_MediaFileStream", x => x.Id); - table.ForeignKey( - name: "FK_MediaFileStream_MediaFile_MediaFileStream_MediaFileStreams_Id", - column: x => x.MediaFileStream_MediaFileStreams_Id, - principalSchema: "jellyfin", - principalTable: "MediaFile", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "PersonRole", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Role = table.Column(maxLength: 1024, nullable: true), - Type = table.Column(nullable: false), - RowVersion = table.Column(nullable: false), - Person_Id = table.Column(nullable: true), - Artwork_Artwork_Id = table.Column(nullable: true), - PersonRole_PersonRoles_Id = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_PersonRole", x => x.Id); - table.ForeignKey( - name: "FK_PersonRole_Person_Person_Id", - column: x => x.Person_Id, - principalSchema: "jellyfin", - principalTable: "Person", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "Metadata", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Title = table.Column(maxLength: 1024, nullable: false), - OriginalTitle = table.Column(maxLength: 1024, nullable: true), - SortTitle = table.Column(maxLength: 1024, nullable: true), - Language = table.Column(maxLength: 3, nullable: false), - ReleaseDate = table.Column(nullable: true), - DateAdded = table.Column(nullable: false), - DateModified = table.Column(nullable: false), - RowVersion = table.Column(nullable: false), - Discriminator = table.Column(nullable: false), - ISBN = table.Column(nullable: true), - BookMetadata_BookMetadata_Id = table.Column(nullable: true), - Description = table.Column(maxLength: 65535, nullable: true), - Headquarters = table.Column(maxLength: 255, nullable: true), - Country = table.Column(maxLength: 2, nullable: true), - Homepage = table.Column(maxLength: 1024, nullable: true), - CompanyMetadata_CompanyMetadata_Id = table.Column(nullable: true), - CustomItemMetadata_CustomItemMetadata_Id = table.Column(nullable: true), - Outline = table.Column(maxLength: 1024, nullable: true), - Plot = table.Column(maxLength: 65535, nullable: true), - Tagline = table.Column(maxLength: 1024, nullable: true), - EpisodeMetadata_EpisodeMetadata_Id = table.Column(nullable: true), - MovieMetadata_Outline = table.Column(maxLength: 1024, nullable: true), - MovieMetadata_Plot = table.Column(maxLength: 65535, nullable: true), - MovieMetadata_Tagline = table.Column(maxLength: 1024, nullable: true), - MovieMetadata_Country = table.Column(maxLength: 2, nullable: true), - MovieMetadata_MovieMetadata_Id = table.Column(nullable: true), - Barcode = table.Column(maxLength: 255, nullable: true), - LabelNumber = table.Column(maxLength: 255, nullable: true), - MusicAlbumMetadata_Country = table.Column(maxLength: 2, nullable: true), - MusicAlbumMetadata_MusicAlbumMetadata_Id = table.Column(nullable: true), - PhotoMetadata_PhotoMetadata_Id = table.Column(nullable: true), - SeasonMetadata_Outline = table.Column(maxLength: 1024, nullable: true), - SeasonMetadata_SeasonMetadata_Id = table.Column(nullable: true), - SeriesMetadata_Outline = table.Column(maxLength: 1024, nullable: true), - SeriesMetadata_Plot = table.Column(maxLength: 65535, nullable: true), - SeriesMetadata_Tagline = table.Column(maxLength: 1024, nullable: true), - SeriesMetadata_Country = table.Column(maxLength: 2, nullable: true), - SeriesMetadata_SeriesMetadata_Id = table.Column(nullable: true), - TrackMetadata_TrackMetadata_Id = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Metadata", x => x.Id); - table.ForeignKey( - name: "FK_Metadata_LibraryItem_BookMetadata_BookMetadata_Id", - column: x => x.BookMetadata_BookMetadata_Id, - principalSchema: "jellyfin", - principalTable: "LibraryItem", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Metadata_LibraryItem_CustomItemMetadata_CustomItemMetadata_Id", - column: x => x.CustomItemMetadata_CustomItemMetadata_Id, - principalSchema: "jellyfin", - principalTable: "LibraryItem", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Metadata_LibraryItem_EpisodeMetadata_EpisodeMetadata_Id", - column: x => x.EpisodeMetadata_EpisodeMetadata_Id, - principalSchema: "jellyfin", - principalTable: "LibraryItem", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Metadata_LibraryItem_MovieMetadata_MovieMetadata_Id", - column: x => x.MovieMetadata_MovieMetadata_Id, - principalSchema: "jellyfin", - principalTable: "LibraryItem", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Metadata_LibraryItem_MusicAlbumMetadata_MusicAlbumMetadata_Id", - column: x => x.MusicAlbumMetadata_MusicAlbumMetadata_Id, - principalSchema: "jellyfin", - principalTable: "LibraryItem", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Metadata_LibraryItem_PhotoMetadata_PhotoMetadata_Id", - column: x => x.PhotoMetadata_PhotoMetadata_Id, - principalSchema: "jellyfin", - principalTable: "LibraryItem", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Metadata_LibraryItem_SeasonMetadata_SeasonMetadata_Id", - column: x => x.SeasonMetadata_SeasonMetadata_Id, - principalSchema: "jellyfin", - principalTable: "LibraryItem", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Metadata_LibraryItem_SeriesMetadata_SeriesMetadata_Id", - column: x => x.SeriesMetadata_SeriesMetadata_Id, - principalSchema: "jellyfin", - principalTable: "LibraryItem", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Metadata_LibraryItem_TrackMetadata_TrackMetadata_Id", - column: x => x.TrackMetadata_TrackMetadata_Id, - principalSchema: "jellyfin", - principalTable: "LibraryItem", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "Artwork", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Path = table.Column(maxLength: 65535, nullable: false), - Kind = table.Column(nullable: false), - RowVersion = table.Column(nullable: false), - PersonRole_PersonRoles_Id = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Artwork", x => x.Id); - table.ForeignKey( - name: "FK_Artwork_Metadata_PersonRole_PersonRoles_Id", - column: x => x.PersonRole_PersonRoles_Id, - principalSchema: "jellyfin", - principalTable: "Metadata", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "Company", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - RowVersion = table.Column(nullable: false), - Company_Parent_Id = table.Column(nullable: true), - Company_Labels_Id = table.Column(nullable: true), - Company_Networks_Id = table.Column(nullable: true), - Company_Publishers_Id = table.Column(nullable: true), - Company_Studios_Id = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Company", x => x.Id); - table.ForeignKey( - name: "FK_Company_Metadata_Company_Labels_Id", - column: x => x.Company_Labels_Id, - principalSchema: "jellyfin", - principalTable: "Metadata", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Company_Metadata_Company_Networks_Id", - column: x => x.Company_Networks_Id, - principalSchema: "jellyfin", - principalTable: "Metadata", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Company_Company_Company_Parent_Id", - column: x => x.Company_Parent_Id, - principalSchema: "jellyfin", - principalTable: "Company", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Company_Metadata_Company_Publishers_Id", - column: x => x.Company_Publishers_Id, - principalSchema: "jellyfin", - principalTable: "Metadata", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Company_Metadata_Company_Studios_Id", - column: x => x.Company_Studios_Id, - principalSchema: "jellyfin", - principalTable: "Metadata", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "Genre", - 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), - PersonRole_PersonRoles_Id = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Genre", x => x.Id); - table.ForeignKey( - name: "FK_Genre_Metadata_PersonRole_PersonRoles_Id", - column: x => x.PersonRole_PersonRoles_Id, - principalSchema: "jellyfin", - principalTable: "Metadata", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "MetadataProviderId", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - ProviderId = table.Column(maxLength: 255, nullable: false), - RowVersion = table.Column(nullable: false), - MetadataProvider_Id = table.Column(nullable: true), - MetadataProviderId_Sources_Id = table.Column(nullable: true), - PersonRole_PersonRoles_Id = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_MetadataProviderId", x => x.Id); - table.ForeignKey( - name: "FK_MetadataProviderId_Person_MetadataProviderId_Sources_Id", - column: x => x.MetadataProviderId_Sources_Id, - principalSchema: "jellyfin", - principalTable: "Person", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_MetadataProviderId_PersonRole_MetadataProviderId_Sources_Id", - column: x => x.MetadataProviderId_Sources_Id, - principalSchema: "jellyfin", - principalTable: "PersonRole", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_MetadataProviderId_MetadataProvider_MetadataProvider_Id", - column: x => x.MetadataProvider_Id, - principalSchema: "jellyfin", - principalTable: "MetadataProvider", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_MetadataProviderId_Metadata_PersonRole_PersonRoles_Id", - column: x => x.PersonRole_PersonRoles_Id, - principalSchema: "jellyfin", - principalTable: "Metadata", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "RatingSource", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Name = table.Column(maxLength: 1024, nullable: true), - MaximumValue = table.Column(nullable: false), - MinimumValue = table.Column(nullable: false), - RowVersion = table.Column(nullable: false), - MetadataProviderId_Source_Id = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_RatingSource", x => x.Id); - table.ForeignKey( - name: "FK_RatingSource_MetadataProviderId_MetadataProviderId_Source_Id", - column: x => x.MetadataProviderId_Source_Id, - principalSchema: "jellyfin", - principalTable: "MetadataProviderId", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "Rating", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Value = table.Column(nullable: false), - Votes = table.Column(nullable: true), - RowVersion = table.Column(nullable: false), - RatingSource_RatingType_Id = table.Column(nullable: true), - PersonRole_PersonRoles_Id = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Rating", x => x.Id); - table.ForeignKey( - name: "FK_Rating_Metadata_PersonRole_PersonRoles_Id", - column: x => x.PersonRole_PersonRoles_Id, - principalSchema: "jellyfin", - principalTable: "Metadata", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_Rating_RatingSource_RatingSource_RatingType_Id", - column: x => x.RatingSource_RatingType_Id, - principalSchema: "jellyfin", - principalTable: "RatingSource", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateIndex( - name: "IX_Artwork_Kind", - schema: "jellyfin", - table: "Artwork", - column: "Kind"); - - migrationBuilder.CreateIndex( - name: "IX_Artwork_PersonRole_PersonRoles_Id", - schema: "jellyfin", - table: "Artwork", - column: "PersonRole_PersonRoles_Id"); - - migrationBuilder.CreateIndex( - name: "IX_Chapter_Chapter_Chapters_Id", - schema: "jellyfin", - table: "Chapter", - column: "Chapter_Chapters_Id"); - - migrationBuilder.CreateIndex( - name: "IX_CollectionItem_CollectionItem_CollectionItem_Id", - schema: "jellyfin", - table: "CollectionItem", - column: "CollectionItem_CollectionItem_Id"); - - migrationBuilder.CreateIndex( - name: "IX_CollectionItem_CollectionItem_Next_Id", - schema: "jellyfin", - table: "CollectionItem", - column: "CollectionItem_Next_Id"); - - migrationBuilder.CreateIndex( - name: "IX_CollectionItem_CollectionItem_Previous_Id", - schema: "jellyfin", - table: "CollectionItem", - column: "CollectionItem_Previous_Id"); - - migrationBuilder.CreateIndex( - name: "IX_CollectionItem_LibraryItem_Id", - schema: "jellyfin", - table: "CollectionItem", - column: "LibraryItem_Id"); - - migrationBuilder.CreateIndex( - name: "IX_Company_Company_Labels_Id", - schema: "jellyfin", - table: "Company", - column: "Company_Labels_Id"); - - migrationBuilder.CreateIndex( - name: "IX_Company_Company_Networks_Id", - schema: "jellyfin", - table: "Company", - column: "Company_Networks_Id"); - - migrationBuilder.CreateIndex( - name: "IX_Company_Company_Parent_Id", - schema: "jellyfin", - table: "Company", - column: "Company_Parent_Id"); - - migrationBuilder.CreateIndex( - name: "IX_Company_Company_Publishers_Id", - schema: "jellyfin", - table: "Company", - column: "Company_Publishers_Id"); - - migrationBuilder.CreateIndex( - name: "IX_Company_Company_Studios_Id", - schema: "jellyfin", - table: "Company", - column: "Company_Studios_Id"); - - migrationBuilder.CreateIndex( - name: "IX_Genre_Name", - schema: "jellyfin", - table: "Genre", - column: "Name", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_Genre_PersonRole_PersonRoles_Id", - schema: "jellyfin", - table: "Genre", - column: "PersonRole_PersonRoles_Id"); - - migrationBuilder.CreateIndex( - name: "IX_Group_Group_Groups_Id", - schema: "jellyfin", - table: "Group", - column: "Group_Groups_Id"); - - migrationBuilder.CreateIndex( - name: "IX_LibraryItem_Episode_Episodes_Id", - schema: "jellyfin", - table: "LibraryItem", - column: "Episode_Episodes_Id"); - - migrationBuilder.CreateIndex( - name: "IX_LibraryItem_LibraryRoot_Id", - schema: "jellyfin", - table: "LibraryItem", - column: "LibraryRoot_Id"); - - migrationBuilder.CreateIndex( - name: "IX_LibraryItem_UrlId", - schema: "jellyfin", - table: "LibraryItem", - column: "UrlId", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_LibraryItem_Season_Seasons_Id", - schema: "jellyfin", - table: "LibraryItem", - column: "Season_Seasons_Id"); - - migrationBuilder.CreateIndex( - name: "IX_LibraryItem_Track_Tracks_Id", - schema: "jellyfin", - table: "LibraryItem", - column: "Track_Tracks_Id"); - - migrationBuilder.CreateIndex( - name: "IX_LibraryRoot_Library_Id", - schema: "jellyfin", - table: "LibraryRoot", - column: "Library_Id"); - - migrationBuilder.CreateIndex( - name: "IX_MediaFile_MediaFile_MediaFiles_Id", - schema: "jellyfin", - table: "MediaFile", - column: "MediaFile_MediaFiles_Id"); - - migrationBuilder.CreateIndex( - name: "IX_MediaFileStream_MediaFileStream_MediaFileStreams_Id", - schema: "jellyfin", - table: "MediaFileStream", - column: "MediaFileStream_MediaFileStreams_Id"); - - migrationBuilder.CreateIndex( - name: "IX_Metadata_BookMetadata_BookMetadata_Id", - schema: "jellyfin", - table: "Metadata", - column: "BookMetadata_BookMetadata_Id"); - - migrationBuilder.CreateIndex( - name: "IX_Metadata_CompanyMetadata_CompanyMetadata_Id", - schema: "jellyfin", - table: "Metadata", - column: "CompanyMetadata_CompanyMetadata_Id"); - - migrationBuilder.CreateIndex( - name: "IX_Metadata_CustomItemMetadata_CustomItemMetadata_Id", - schema: "jellyfin", - table: "Metadata", - column: "CustomItemMetadata_CustomItemMetadata_Id"); - - migrationBuilder.CreateIndex( - name: "IX_Metadata_EpisodeMetadata_EpisodeMetadata_Id", - schema: "jellyfin", - table: "Metadata", - column: "EpisodeMetadata_EpisodeMetadata_Id"); - - migrationBuilder.CreateIndex( - name: "IX_Metadata_MovieMetadata_MovieMetadata_Id", - schema: "jellyfin", - table: "Metadata", - column: "MovieMetadata_MovieMetadata_Id"); - - migrationBuilder.CreateIndex( - name: "IX_Metadata_MusicAlbumMetadata_MusicAlbumMetadata_Id", - schema: "jellyfin", - table: "Metadata", - column: "MusicAlbumMetadata_MusicAlbumMetadata_Id"); - - migrationBuilder.CreateIndex( - name: "IX_Metadata_PhotoMetadata_PhotoMetadata_Id", - schema: "jellyfin", - table: "Metadata", - column: "PhotoMetadata_PhotoMetadata_Id"); - - migrationBuilder.CreateIndex( - name: "IX_Metadata_SeasonMetadata_SeasonMetadata_Id", - schema: "jellyfin", - table: "Metadata", - column: "SeasonMetadata_SeasonMetadata_Id"); - - migrationBuilder.CreateIndex( - name: "IX_Metadata_SeriesMetadata_SeriesMetadata_Id", - schema: "jellyfin", - table: "Metadata", - column: "SeriesMetadata_SeriesMetadata_Id"); - - migrationBuilder.CreateIndex( - name: "IX_Metadata_TrackMetadata_TrackMetadata_Id", - schema: "jellyfin", - table: "Metadata", - column: "TrackMetadata_TrackMetadata_Id"); - - migrationBuilder.CreateIndex( - name: "IX_MetadataProviderId_MetadataProviderId_Sources_Id", - schema: "jellyfin", - table: "MetadataProviderId", - column: "MetadataProviderId_Sources_Id"); - - migrationBuilder.CreateIndex( - name: "IX_MetadataProviderId_MetadataProvider_Id", - schema: "jellyfin", - table: "MetadataProviderId", - column: "MetadataProvider_Id"); - - migrationBuilder.CreateIndex( - name: "IX_MetadataProviderId_PersonRole_PersonRoles_Id", - schema: "jellyfin", - table: "MetadataProviderId", - column: "PersonRole_PersonRoles_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_PersonRole_Artwork_Artwork_Id", - schema: "jellyfin", - table: "PersonRole", - column: "Artwork_Artwork_Id"); - - migrationBuilder.CreateIndex( - name: "IX_PersonRole_PersonRole_PersonRoles_Id", - schema: "jellyfin", - table: "PersonRole", - column: "PersonRole_PersonRoles_Id"); - - migrationBuilder.CreateIndex( - name: "IX_PersonRole_Person_Id", - schema: "jellyfin", - table: "PersonRole", - column: "Person_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"); - - migrationBuilder.CreateIndex( - name: "IX_Rating_PersonRole_PersonRoles_Id", - schema: "jellyfin", - table: "Rating", - column: "PersonRole_PersonRoles_Id"); - - migrationBuilder.CreateIndex( - name: "IX_Rating_RatingSource_RatingType_Id", - schema: "jellyfin", - table: "Rating", - column: "RatingSource_RatingType_Id"); - - migrationBuilder.CreateIndex( - name: "IX_RatingSource_MetadataProviderId_Source_Id", - schema: "jellyfin", - table: "RatingSource", - column: "MetadataProviderId_Source_Id"); - - migrationBuilder.CreateIndex( - name: "IX_Release_Release_Releases_Id", - schema: "jellyfin", - table: "Release", - column: "Release_Releases_Id"); - - migrationBuilder.AddForeignKey( - name: "FK_PersonRole_Metadata_PersonRole_PersonRoles_Id", - schema: "jellyfin", - table: "PersonRole", - column: "PersonRole_PersonRoles_Id", - principalSchema: "jellyfin", - principalTable: "Metadata", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - - migrationBuilder.AddForeignKey( - name: "FK_PersonRole_Artwork_Artwork_Artwork_Id", - schema: "jellyfin", - table: "PersonRole", - column: "Artwork_Artwork_Id", - principalSchema: "jellyfin", - principalTable: "Artwork", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - - migrationBuilder.AddForeignKey( - name: "FK_Metadata_Company_CompanyMetadata_CompanyMetadata_Id", - schema: "jellyfin", - table: "Metadata", - column: "CompanyMetadata_CompanyMetadata_Id", - principalSchema: "jellyfin", - principalTable: "Company", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Company_Metadata_Company_Labels_Id", - schema: "jellyfin", - table: "Company"); - - migrationBuilder.DropForeignKey( - name: "FK_Company_Metadata_Company_Networks_Id", - schema: "jellyfin", - table: "Company"); - - migrationBuilder.DropForeignKey( - name: "FK_Company_Metadata_Company_Publishers_Id", - schema: "jellyfin", - table: "Company"); - - migrationBuilder.DropForeignKey( - name: "FK_Company_Metadata_Company_Studios_Id", - schema: "jellyfin", - table: "Company"); - - migrationBuilder.DropTable( - name: "ActivityLog", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "Chapter", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "CollectionItem", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "Genre", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "MediaFileStream", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "Permission", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "Preference", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "ProviderMapping", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "Rating", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "Collection", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "MediaFile", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "Group", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "RatingSource", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "Release", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "User", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "MetadataProviderId", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "PersonRole", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "MetadataProvider", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "Artwork", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "Person", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "Metadata", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "LibraryItem", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "Company", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "LibraryRoot", - schema: "jellyfin"); - - migrationBuilder.DropTable( - name: "Library", - schema: "jellyfin"); - } - } -} diff --git a/Jellyfin.Server.Implementations/Migrations/20200502231229_InitialSchema.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200502231229_InitialSchema.Designer.cs new file mode 100644 index 000000000..e1ee9b34a --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200502231229_InitialSchema.Designer.cs @@ -0,0 +1,73 @@ +#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("20200502231229_InitialSchema")] + partial class InitialSchema + { + 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"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/20200502231229_InitialSchema.cs b/Jellyfin.Server.Implementations/Migrations/20200502231229_InitialSchema.cs new file mode 100644 index 000000000..42fac865c --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200502231229_InitialSchema.cs @@ -0,0 +1,46 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1601 + +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Jellyfin.Server.Implementations.Migrations +{ + public partial class InitialSchema : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.EnsureSchema( + name: "jellyfin"); + + migrationBuilder.CreateTable( + name: "ActivityLog", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(maxLength: 512, nullable: false), + Overview = table.Column(maxLength: 512, nullable: true), + ShortOverview = table.Column(maxLength: 512, nullable: true), + Type = table.Column(maxLength: 256, nullable: false), + UserId = table.Column(nullable: false), + ItemId = table.Column(maxLength: 256, nullable: true), + DateCreated = table.Column(nullable: false), + LogSeverity = table.Column(nullable: false), + RowVersion = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ActivityLog", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ActivityLog", + schema: "jellyfin"); + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs b/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs index 72a4a8c3b..23a0fdc78 100644 --- a/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs +++ b/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs @@ -1,3 +1,6 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1601 + using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; diff --git a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs index 8cdd101af..27f5fe24b 100644 --- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs +++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs @@ -1,6 +1,4 @@ -#pragma warning disable CS1591 - -// +// using System; using Jellyfin.Server.Implementations; using Microsoft.EntityFrameworkCore; @@ -64,1447 +62,6 @@ namespace Jellyfin.Server.Implementations.Migrations b.ToTable("ActivityLog"); }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Artwork", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Kind") - .HasColumnType("INTEGER"); - - b.Property("Path") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("PersonRole_PersonRoles_Id") - .HasColumnType("INTEGER"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Kind"); - - b.HasIndex("PersonRole_PersonRoles_Id"); - - b.ToTable("Artwork"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Chapter", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Chapter_Chapters_Id") - .HasColumnType("INTEGER"); - - b.Property("Language") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(3); - - b.Property("Name") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("TimeEnd") - .HasColumnType("INTEGER"); - - b.Property("TimeStart") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Chapter_Chapters_Id"); - - b.ToTable("Chapter"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Collection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.ToTable("Collection"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.CollectionItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CollectionItem_CollectionItem_Id") - .HasColumnType("INTEGER"); - - b.Property("CollectionItem_Next_Id") - .HasColumnType("INTEGER"); - - b.Property("CollectionItem_Previous_Id") - .HasColumnType("INTEGER"); - - b.Property("LibraryItem_Id") - .HasColumnType("INTEGER"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("CollectionItem_CollectionItem_Id"); - - b.HasIndex("CollectionItem_Next_Id"); - - b.HasIndex("CollectionItem_Previous_Id"); - - b.HasIndex("LibraryItem_Id"); - - b.ToTable("CollectionItem"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Company", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Company_Labels_Id") - .HasColumnType("INTEGER"); - - b.Property("Company_Networks_Id") - .HasColumnType("INTEGER"); - - b.Property("Company_Parent_Id") - .HasColumnType("INTEGER"); - - b.Property("Company_Publishers_Id") - .HasColumnType("INTEGER"); - - b.Property("Company_Studios_Id") - .HasColumnType("INTEGER"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Company_Labels_Id"); - - b.HasIndex("Company_Networks_Id"); - - b.HasIndex("Company_Parent_Id"); - - b.HasIndex("Company_Publishers_Id"); - - b.HasIndex("Company_Studios_Id"); - - b.ToTable("Company"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Genre", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.Property("PersonRole_PersonRoles_Id") - .HasColumnType("INTEGER"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Name") - .IsUnique(); - - b.HasIndex("PersonRole_PersonRoles_Id"); - - b.ToTable("Genre"); - }); - - 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.Library", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.ToTable("Library"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.LibraryItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("DateAdded") - .HasColumnType("TEXT"); - - b.Property("Discriminator") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("LibraryRoot_Id") - .HasColumnType("INTEGER"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("UrlId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("LibraryRoot_Id"); - - b.HasIndex("UrlId") - .IsUnique(); - - b.ToTable("LibraryItem"); - - b.HasDiscriminator("Discriminator").HasValue("LibraryItem"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.LibraryRoot", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Library_Id") - .HasColumnType("INTEGER"); - - b.Property("NetworkPath") - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("Path") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Library_Id"); - - b.ToTable("LibraryRoot"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MediaFile", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Kind") - .HasColumnType("INTEGER"); - - b.Property("MediaFile_MediaFiles_Id") - .HasColumnType("INTEGER"); - - b.Property("Path") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("MediaFile_MediaFiles_Id"); - - b.ToTable("MediaFile"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MediaFileStream", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("MediaFileStream_MediaFileStreams_Id") - .HasColumnType("INTEGER"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("StreamNumber") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("MediaFileStream_MediaFileStreams_Id"); - - b.ToTable("MediaFileStream"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Metadata", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("DateAdded") - .HasColumnType("TEXT"); - - b.Property("DateModified") - .HasColumnType("TEXT"); - - b.Property("Discriminator") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Language") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(3); - - b.Property("OriginalTitle") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("ReleaseDate") - .HasColumnType("TEXT"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("SortTitle") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("Title") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.HasKey("Id"); - - b.ToTable("Metadata"); - - b.HasDiscriminator("Discriminator").HasValue("Metadata"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MetadataProvider", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.ToTable("MetadataProvider"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MetadataProviderId", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("MetadataProviderId_Sources_Id") - .HasColumnType("INTEGER"); - - b.Property("MetadataProvider_Id") - .HasColumnType("INTEGER"); - - b.Property("PersonRole_PersonRoles_Id") - .HasColumnType("INTEGER"); - - b.Property("ProviderId") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("MetadataProviderId_Sources_Id"); - - b.HasIndex("MetadataProvider_Id"); - - b.HasIndex("PersonRole_PersonRoles_Id"); - - b.ToTable("MetadataProviderId"); - }); - - 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.Person", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("DateAdded") - .HasColumnType("TEXT"); - - b.Property("DateModified") - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("SourceId") - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.Property("UrlId") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Person"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.PersonRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Artwork_Artwork_Id") - .HasColumnType("INTEGER"); - - b.Property("PersonRole_PersonRoles_Id") - .HasColumnType("INTEGER"); - - b.Property("Person_Id") - .HasColumnType("INTEGER"); - - b.Property("Role") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Artwork_Artwork_Id"); - - b.HasIndex("PersonRole_PersonRoles_Id"); - - b.HasIndex("Person_Id"); - - b.ToTable("PersonRole"); - }); - - 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.Rating", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("PersonRole_PersonRoles_Id") - .HasColumnType("INTEGER"); - - b.Property("RatingSource_RatingType_Id") - .HasColumnType("INTEGER"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.Property("Value") - .HasColumnType("REAL"); - - b.Property("Votes") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("PersonRole_PersonRoles_Id"); - - b.HasIndex("RatingSource_RatingType_Id"); - - b.ToTable("Rating"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.RatingSource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("MaximumValue") - .HasColumnType("REAL"); - - b.Property("MetadataProviderId_Source_Id") - .HasColumnType("INTEGER"); - - b.Property("MinimumValue") - .HasColumnType("REAL"); - - b.Property("Name") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("MetadataProviderId_Source_Id"); - - b.ToTable("RatingSource"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Release", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("Release_Releases_Id") - .HasColumnType("INTEGER"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Release_Releases_Id"); - - b.ToTable("Release"); - }); - - 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.Book", b => - { - b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); - - b.ToTable("Book"); - - b.HasDiscriminator().HasValue("Book"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.CustomItem", b => - { - b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); - - b.ToTable("LibraryItem"); - - b.HasDiscriminator().HasValue("CustomItem"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Episode", b => - { - b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); - - b.Property("EpisodeNumber") - .HasColumnType("INTEGER"); - - b.Property("Episode_Episodes_Id") - .HasColumnType("INTEGER"); - - b.HasIndex("Episode_Episodes_Id"); - - b.ToTable("Episode"); - - b.HasDiscriminator().HasValue("Episode"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Movie", b => - { - b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); - - b.ToTable("Movie"); - - b.HasDiscriminator().HasValue("Movie"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MusicAlbum", b => - { - b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); - - b.ToTable("MusicAlbum"); - - b.HasDiscriminator().HasValue("MusicAlbum"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Photo", b => - { - b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); - - b.ToTable("Photo"); - - b.HasDiscriminator().HasValue("Photo"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Season", b => - { - b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); - - b.Property("SeasonNumber") - .HasColumnType("INTEGER"); - - b.Property("Season_Seasons_Id") - .HasColumnType("INTEGER"); - - b.HasIndex("Season_Seasons_Id"); - - b.ToTable("Season"); - - b.HasDiscriminator().HasValue("Season"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Series", b => - { - b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); - - b.Property("AirsDayOfWeek") - .HasColumnType("INTEGER"); - - b.Property("AirsTime") - .HasColumnType("TEXT"); - - b.Property("FirstAired") - .HasColumnType("TEXT"); - - b.ToTable("Series"); - - b.HasDiscriminator().HasValue("Series"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Track", b => - { - b.HasBaseType("Jellyfin.Data.Entities.LibraryItem"); - - b.Property("TrackNumber") - .HasColumnType("INTEGER"); - - b.Property("Track_Tracks_Id") - .HasColumnType("INTEGER"); - - b.HasIndex("Track_Tracks_Id"); - - b.ToTable("Track"); - - b.HasDiscriminator().HasValue("Track"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.BookMetadata", b => - { - b.HasBaseType("Jellyfin.Data.Entities.Metadata"); - - b.Property("BookMetadata_BookMetadata_Id") - .HasColumnType("INTEGER"); - - b.Property("ISBN") - .HasColumnType("INTEGER"); - - b.HasIndex("BookMetadata_BookMetadata_Id"); - - b.ToTable("Metadata"); - - b.HasDiscriminator().HasValue("BookMetadata"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.CompanyMetadata", b => - { - b.HasBaseType("Jellyfin.Data.Entities.Metadata"); - - b.Property("CompanyMetadata_CompanyMetadata_Id") - .HasColumnType("INTEGER"); - - b.Property("Country") - .HasColumnType("TEXT") - .HasMaxLength(2); - - b.Property("Description") - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("Headquarters") - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.Property("Homepage") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.HasIndex("CompanyMetadata_CompanyMetadata_Id"); - - b.ToTable("CompanyMetadata"); - - b.HasDiscriminator().HasValue("CompanyMetadata"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemMetadata", b => - { - b.HasBaseType("Jellyfin.Data.Entities.Metadata"); - - b.Property("CustomItemMetadata_CustomItemMetadata_Id") - .HasColumnType("INTEGER"); - - b.HasIndex("CustomItemMetadata_CustomItemMetadata_Id"); - - b.ToTable("CustomItemMetadata"); - - b.HasDiscriminator().HasValue("CustomItemMetadata"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.EpisodeMetadata", b => - { - b.HasBaseType("Jellyfin.Data.Entities.Metadata"); - - b.Property("EpisodeMetadata_EpisodeMetadata_Id") - .HasColumnType("INTEGER"); - - b.Property("Outline") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("Plot") - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("Tagline") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.HasIndex("EpisodeMetadata_EpisodeMetadata_Id"); - - b.ToTable("EpisodeMetadata"); - - b.HasDiscriminator().HasValue("EpisodeMetadata"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MovieMetadata", b => - { - b.HasBaseType("Jellyfin.Data.Entities.Metadata"); - - b.Property("Country") - .HasColumnName("MovieMetadata_Country") - .HasColumnType("TEXT") - .HasMaxLength(2); - - b.Property("MovieMetadata_MovieMetadata_Id") - .HasColumnType("INTEGER"); - - b.Property("Outline") - .HasColumnName("MovieMetadata_Outline") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("Plot") - .HasColumnName("MovieMetadata_Plot") - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("Tagline") - .HasColumnName("MovieMetadata_Tagline") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.HasIndex("MovieMetadata_MovieMetadata_Id"); - - b.ToTable("MovieMetadata"); - - b.HasDiscriminator().HasValue("MovieMetadata"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MusicAlbumMetadata", b => - { - b.HasBaseType("Jellyfin.Data.Entities.Metadata"); - - b.Property("Barcode") - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.Property("Country") - .HasColumnName("MusicAlbumMetadata_Country") - .HasColumnType("TEXT") - .HasMaxLength(2); - - b.Property("LabelNumber") - .HasColumnType("TEXT") - .HasMaxLength(255); - - b.Property("MusicAlbumMetadata_MusicAlbumMetadata_Id") - .HasColumnType("INTEGER"); - - b.HasIndex("MusicAlbumMetadata_MusicAlbumMetadata_Id"); - - b.ToTable("MusicAlbumMetadata"); - - b.HasDiscriminator().HasValue("MusicAlbumMetadata"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.PhotoMetadata", b => - { - b.HasBaseType("Jellyfin.Data.Entities.Metadata"); - - b.Property("PhotoMetadata_PhotoMetadata_Id") - .HasColumnType("INTEGER"); - - b.HasIndex("PhotoMetadata_PhotoMetadata_Id"); - - b.ToTable("PhotoMetadata"); - - b.HasDiscriminator().HasValue("PhotoMetadata"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.SeasonMetadata", b => - { - b.HasBaseType("Jellyfin.Data.Entities.Metadata"); - - b.Property("Outline") - .HasColumnName("SeasonMetadata_Outline") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("SeasonMetadata_SeasonMetadata_Id") - .HasColumnType("INTEGER"); - - b.HasIndex("SeasonMetadata_SeasonMetadata_Id"); - - b.ToTable("SeasonMetadata"); - - b.HasDiscriminator().HasValue("SeasonMetadata"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.SeriesMetadata", b => - { - b.HasBaseType("Jellyfin.Data.Entities.Metadata"); - - b.Property("Country") - .HasColumnName("SeriesMetadata_Country") - .HasColumnType("TEXT") - .HasMaxLength(2); - - b.Property("Outline") - .HasColumnName("SeriesMetadata_Outline") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.Property("Plot") - .HasColumnName("SeriesMetadata_Plot") - .HasColumnType("TEXT") - .HasMaxLength(65535); - - b.Property("SeriesMetadata_SeriesMetadata_Id") - .HasColumnType("INTEGER"); - - b.Property("Tagline") - .HasColumnName("SeriesMetadata_Tagline") - .HasColumnType("TEXT") - .HasMaxLength(1024); - - b.HasIndex("SeriesMetadata_SeriesMetadata_Id"); - - b.ToTable("SeriesMetadata"); - - b.HasDiscriminator().HasValue("SeriesMetadata"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.TrackMetadata", b => - { - b.HasBaseType("Jellyfin.Data.Entities.Metadata"); - - b.Property("TrackMetadata_TrackMetadata_Id") - .HasColumnType("INTEGER"); - - b.HasIndex("TrackMetadata_TrackMetadata_Id"); - - b.ToTable("TrackMetadata"); - - b.HasDiscriminator().HasValue("TrackMetadata"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Artwork", b => - { - b.HasOne("Jellyfin.Data.Entities.Metadata", null) - .WithMany("Artwork") - .HasForeignKey("PersonRole_PersonRoles_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Chapter", b => - { - b.HasOne("Jellyfin.Data.Entities.Release", null) - .WithMany("Chapters") - .HasForeignKey("Chapter_Chapters_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.CollectionItem", b => - { - b.HasOne("Jellyfin.Data.Entities.Collection", null) - .WithMany("CollectionItem") - .HasForeignKey("CollectionItem_CollectionItem_Id"); - - b.HasOne("Jellyfin.Data.Entities.CollectionItem", "Next") - .WithMany() - .HasForeignKey("CollectionItem_Next_Id"); - - b.HasOne("Jellyfin.Data.Entities.CollectionItem", "Previous") - .WithMany() - .HasForeignKey("CollectionItem_Previous_Id"); - - b.HasOne("Jellyfin.Data.Entities.LibraryItem", "LibraryItem") - .WithMany() - .HasForeignKey("LibraryItem_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Company", b => - { - b.HasOne("Jellyfin.Data.Entities.MusicAlbumMetadata", null) - .WithMany("Labels") - .HasForeignKey("Company_Labels_Id"); - - b.HasOne("Jellyfin.Data.Entities.SeriesMetadata", null) - .WithMany("Networks") - .HasForeignKey("Company_Networks_Id"); - - b.HasOne("Jellyfin.Data.Entities.Company", "Parent") - .WithMany() - .HasForeignKey("Company_Parent_Id"); - - b.HasOne("Jellyfin.Data.Entities.BookMetadata", null) - .WithMany("Publishers") - .HasForeignKey("Company_Publishers_Id"); - - b.HasOne("Jellyfin.Data.Entities.MovieMetadata", null) - .WithMany("Studios") - .HasForeignKey("Company_Studios_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Genre", b => - { - b.HasOne("Jellyfin.Data.Entities.Metadata", null) - .WithMany("Genres") - .HasForeignKey("PersonRole_PersonRoles_Id"); - }); - - 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.LibraryItem", b => - { - b.HasOne("Jellyfin.Data.Entities.LibraryRoot", "LibraryRoot") - .WithMany() - .HasForeignKey("LibraryRoot_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.LibraryRoot", b => - { - b.HasOne("Jellyfin.Data.Entities.Library", "Library") - .WithMany() - .HasForeignKey("Library_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MediaFile", b => - { - b.HasOne("Jellyfin.Data.Entities.Release", null) - .WithMany("MediaFiles") - .HasForeignKey("MediaFile_MediaFiles_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MediaFileStream", b => - { - b.HasOne("Jellyfin.Data.Entities.MediaFile", null) - .WithMany("MediaFileStreams") - .HasForeignKey("MediaFileStream_MediaFileStreams_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MetadataProviderId", b => - { - b.HasOne("Jellyfin.Data.Entities.Person", null) - .WithMany("Sources") - .HasForeignKey("MetadataProviderId_Sources_Id"); - - b.HasOne("Jellyfin.Data.Entities.PersonRole", null) - .WithMany("Sources") - .HasForeignKey("MetadataProviderId_Sources_Id"); - - b.HasOne("Jellyfin.Data.Entities.MetadataProvider", "MetadataProvider") - .WithMany() - .HasForeignKey("MetadataProvider_Id"); - - b.HasOne("Jellyfin.Data.Entities.Metadata", null) - .WithMany("Sources") - .HasForeignKey("PersonRole_PersonRoles_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.PersonRole", b => - { - b.HasOne("Jellyfin.Data.Entities.Artwork", "Artwork") - .WithMany() - .HasForeignKey("Artwork_Artwork_Id"); - - b.HasOne("Jellyfin.Data.Entities.Metadata", null) - .WithMany("PersonRoles") - .HasForeignKey("PersonRole_PersonRoles_Id"); - - b.HasOne("Jellyfin.Data.Entities.Person", "Person") - .WithMany() - .HasForeignKey("Person_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"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Rating", b => - { - b.HasOne("Jellyfin.Data.Entities.Metadata", null) - .WithMany("Ratings") - .HasForeignKey("PersonRole_PersonRoles_Id"); - - b.HasOne("Jellyfin.Data.Entities.RatingSource", "RatingType") - .WithMany() - .HasForeignKey("RatingSource_RatingType_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.RatingSource", b => - { - b.HasOne("Jellyfin.Data.Entities.MetadataProviderId", "Source") - .WithMany() - .HasForeignKey("MetadataProviderId_Source_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Release", b => - { - b.HasOne("Jellyfin.Data.Entities.Book", null) - .WithMany("Releases") - .HasForeignKey("Release_Releases_Id"); - - b.HasOne("Jellyfin.Data.Entities.CustomItem", null) - .WithMany("Releases") - .HasForeignKey("Release_Releases_Id") - .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id1"); - - b.HasOne("Jellyfin.Data.Entities.Episode", null) - .WithMany("Releases") - .HasForeignKey("Release_Releases_Id") - .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id2"); - - b.HasOne("Jellyfin.Data.Entities.Movie", null) - .WithMany("Releases") - .HasForeignKey("Release_Releases_Id") - .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id3"); - - b.HasOne("Jellyfin.Data.Entities.Photo", null) - .WithMany("Releases") - .HasForeignKey("Release_Releases_Id") - .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id4"); - - b.HasOne("Jellyfin.Data.Entities.Track", null) - .WithMany("Releases") - .HasForeignKey("Release_Releases_Id") - .HasConstraintName("FK_Release_LibraryItem_Release_Releases_Id5"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Episode", b => - { - b.HasOne("Jellyfin.Data.Entities.Season", null) - .WithMany("Episodes") - .HasForeignKey("Episode_Episodes_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Season", b => - { - b.HasOne("Jellyfin.Data.Entities.Series", null) - .WithMany("Seasons") - .HasForeignKey("Season_Seasons_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.Track", b => - { - b.HasOne("Jellyfin.Data.Entities.MusicAlbum", null) - .WithMany("Tracks") - .HasForeignKey("Track_Tracks_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.BookMetadata", b => - { - b.HasOne("Jellyfin.Data.Entities.Book", null) - .WithMany("BookMetadata") - .HasForeignKey("BookMetadata_BookMetadata_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.CompanyMetadata", b => - { - b.HasOne("Jellyfin.Data.Entities.Company", null) - .WithMany("CompanyMetadata") - .HasForeignKey("CompanyMetadata_CompanyMetadata_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemMetadata", b => - { - b.HasOne("Jellyfin.Data.Entities.CustomItem", null) - .WithMany("CustomItemMetadata") - .HasForeignKey("CustomItemMetadata_CustomItemMetadata_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.EpisodeMetadata", b => - { - b.HasOne("Jellyfin.Data.Entities.Episode", null) - .WithMany("EpisodeMetadata") - .HasForeignKey("EpisodeMetadata_EpisodeMetadata_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MovieMetadata", b => - { - b.HasOne("Jellyfin.Data.Entities.Movie", null) - .WithMany("MovieMetadata") - .HasForeignKey("MovieMetadata_MovieMetadata_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.MusicAlbumMetadata", b => - { - b.HasOne("Jellyfin.Data.Entities.MusicAlbum", null) - .WithMany("MusicAlbumMetadata") - .HasForeignKey("MusicAlbumMetadata_MusicAlbumMetadata_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.PhotoMetadata", b => - { - b.HasOne("Jellyfin.Data.Entities.Photo", null) - .WithMany("PhotoMetadata") - .HasForeignKey("PhotoMetadata_PhotoMetadata_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.SeasonMetadata", b => - { - b.HasOne("Jellyfin.Data.Entities.Season", null) - .WithMany("SeasonMetadata") - .HasForeignKey("SeasonMetadata_SeasonMetadata_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.SeriesMetadata", b => - { - b.HasOne("Jellyfin.Data.Entities.Series", null) - .WithMany("SeriesMetadata") - .HasForeignKey("SeriesMetadata_SeriesMetadata_Id"); - }); - - modelBuilder.Entity("Jellyfin.Data.Entities.TrackMetadata", b => - { - b.HasOne("Jellyfin.Data.Entities.Track", null) - .WithMany("TrackMetadata") - .HasForeignKey("TrackMetadata_TrackMetadata_Id"); - }); #pragma warning restore 612, 618 } } -- cgit v1.2.3 From cbd4a64e670eda1c30be6000a8f6cceccc93ddfa Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 2 May 2020 18:46:27 -0600 Subject: Update endpoint docs --- .../Controllers/Images/ImageByNameController.cs | 27 ++++++++++++++-------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/Jellyfin.Api/Controllers/Images/ImageByNameController.cs b/Jellyfin.Api/Controllers/Images/ImageByNameController.cs index ce509b4e6..6160d54028 100644 --- a/Jellyfin.Api/Controllers/Images/ImageByNameController.cs +++ b/Jellyfin.Api/Controllers/Images/ImageByNameController.cs @@ -42,10 +42,11 @@ namespace Jellyfin.Api.Controllers.Images /// /// Get all general images. /// - /// General images. + /// Retrieved list of images. + /// An containing the list of images. [HttpGet("General")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult GetGeneralImages() + public ActionResult> GetGeneralImages() { return Ok(GetImageList(_applicationPaths.GeneralPath, false)); } @@ -55,7 +56,9 @@ namespace Jellyfin.Api.Controllers.Images /// /// The name of the image. /// Image Type (primary, backdrop, logo, etc). - /// Image Stream. + /// Image stream retrieved. + /// Image not found. + /// A containing the image contents on success, or a if the image could not be found. [HttpGet("General/{Name}/{Type}")] [Produces("application/octet-stream")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -82,10 +85,11 @@ namespace Jellyfin.Api.Controllers.Images /// /// Get all general images. /// - /// General images. + /// Retrieved list of images. + /// An containing the list of images. [HttpGet("Ratings")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult GetRatingImages() + public ActionResult> GetRatingImages() { return Ok(GetImageList(_applicationPaths.RatingsPath, false)); } @@ -95,7 +99,9 @@ namespace Jellyfin.Api.Controllers.Images /// /// The theme to get the image from. /// The name of the image. - /// Image Stream. + /// Image stream retrieved. + /// Image not found. + /// A containing the image contents on success, or a if the image could not be found. [HttpGet("Ratings/{Theme}/{Name}")] [Produces("application/octet-stream")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -110,7 +116,8 @@ namespace Jellyfin.Api.Controllers.Images /// /// Get all media info images. /// - /// Media Info images. + /// Image list retrieved. + /// An containing the list of images. [HttpGet("MediaInfo")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetMediaInfoImages() @@ -123,7 +130,9 @@ namespace Jellyfin.Api.Controllers.Images /// /// The theme to get the image from. /// The name of the image. - /// Image Stream. + /// Image stream retrieved. + /// Image not found. + /// A containing the image contents on success, or a if the image could not be found. [HttpGet("MediaInfo/{Theme}/{Name}")] [Produces("application/octet-stream")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -141,7 +150,7 @@ namespace Jellyfin.Api.Controllers.Images /// Path to begin search. /// Theme to search. /// File name to search for. - /// Image Stream. + /// A containing the image contents on success, or a if the image could not be found. private ActionResult GetImageFile(string basePath, string theme, string name) { var themeFolder = Path.Combine(basePath, theme); -- cgit v1.2.3 From 35dbcea9311589ea7b9a10ab02da557a2bfb46fc Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 2 May 2020 18:47:05 -0600 Subject: Return array -> ienumerable --- Jellyfin.Api/Controllers/Images/ImageByNameController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/Images/ImageByNameController.cs b/Jellyfin.Api/Controllers/Images/ImageByNameController.cs index 6160d54028..67ebaa4e0 100644 --- a/Jellyfin.Api/Controllers/Images/ImageByNameController.cs +++ b/Jellyfin.Api/Controllers/Images/ImageByNameController.cs @@ -120,7 +120,7 @@ namespace Jellyfin.Api.Controllers.Images /// An containing the list of images. [HttpGet("MediaInfo")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult GetMediaInfoImages() + public ActionResult> GetMediaInfoImages() { return Ok(GetImageList(_applicationPaths.MediaInfoImagesPath, false)); } -- cgit v1.2.3 From 7d3eaea3fa550f601218cc6531bbdb0a614dda95 Mon Sep 17 00:00:00 2001 From: Christoph Potas Date: Sun, 3 May 2020 18:17:55 +0200 Subject: + add bd tag to clean string regex Signed-off-by: Christoph Potas --- Emby.Naming/Common/NamingOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index a2d75d0b8..1b343790e 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -142,7 +142,7 @@ namespace Emby.Naming.Common CleanStrings = new[] { - @"[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|\[.*\])([ _\,\.\(\)\[\]\-]|$)", + @"[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|bd|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|\[.*\])([ _\,\.\(\)\[\]\-]|$)", @"(\[.*\])" }; -- cgit v1.2.3 From 74f9ddc419ff27cf3d5c3484d9b6c9f9e711483c Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sun, 3 May 2020 13:06:08 -0400 Subject: Delete DbContext class --- Jellyfin.Data/DbContexts/Jellyfin.cs | 1140 ---------------------------------- 1 file changed, 1140 deletions(-) delete mode 100644 Jellyfin.Data/DbContexts/Jellyfin.cs diff --git a/Jellyfin.Data/DbContexts/Jellyfin.cs b/Jellyfin.Data/DbContexts/Jellyfin.cs deleted file mode 100644 index fd488ce7d..000000000 --- a/Jellyfin.Data/DbContexts/Jellyfin.cs +++ /dev/null @@ -1,1140 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.ComponentModel.DataAnnotations.Schema; -using Microsoft.EntityFrameworkCore; - -namespace Jellyfin.Data.DbContexts -{ - /// - public partial class Jellyfin : DbContext - { - #region DbSets - public virtual Microsoft.EntityFrameworkCore.DbSet Artwork { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Books { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet BookMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Chapters { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Collections { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet CollectionItems { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Companies { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet CompanyMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet CustomItems { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet CustomItemMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Episodes { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet EpisodeMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Genres { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Groups { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Libraries { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet LibraryItems { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet LibraryRoot { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet MediaFiles { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet MediaFileStream { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Metadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet MetadataProviders { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet MetadataProviderIds { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Movies { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet MovieMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet MusicAlbums { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet MusicAlbumMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Permissions { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet People { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet PersonRoles { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Photo { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet PhotoMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Preferences { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet ProviderMappings { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Ratings { get; set; } - - /// - /// Repository for global::Jellyfin.Data.Entities.RatingSource - This is the entity to - /// store review ratings, not age ratings - /// - public virtual Microsoft.EntityFrameworkCore.DbSet RatingSources { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Releases { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Seasons { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet SeasonMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Series { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet SeriesMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Tracks { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet TrackMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Users { get; set; } - #endregion DbSets - - /// - /// Default connection string - /// - public static string ConnectionString { get; set; } = @"Data Source=jellyfin.db"; - - /// - public Jellyfin(DbContextOptions options) : base(options) - { - } - - partial void CustomInit(DbContextOptionsBuilder optionsBuilder); - - /// - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - CustomInit(optionsBuilder); - } - - partial void OnModelCreatingImpl(ModelBuilder modelBuilder); - partial void OnModelCreatedImpl(ModelBuilder modelBuilder); - - /// - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - OnModelCreatingImpl(modelBuilder); - - modelBuilder.HasDefaultSchema("jellyfin"); - - modelBuilder.Entity() - .ToTable("Artwork") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Path) - .HasMaxLength(65535) - .IsRequired() - .HasField("_Path") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Kind) - .IsRequired() - .HasField("_Kind") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity().HasIndex(t => t.Kind); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - - modelBuilder.Entity() - .HasMany(x => x.BookMetadata) - .WithOne() - .HasForeignKey("BookMetadata_BookMetadata_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Releases) - .WithOne() - .HasForeignKey("Release_Releases_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.ISBN) - .HasField("_ISBN") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.Publishers) - .WithOne() - .HasForeignKey("Company_Publishers_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("Chapter") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(1024) - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Language) - .HasMaxLength(3) - .IsRequired() - .HasField("_Language") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.TimeStart) - .IsRequired() - .HasField("_TimeStart") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.TimeEnd) - .HasField("_TimeEnd") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - - modelBuilder.Entity() - .ToTable("Collection") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(1024) - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasMany(x => x.CollectionItem) - .WithOne() - .HasForeignKey("CollectionItem_CollectionItem_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("CollectionItem") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasOne(x => x.LibraryItem) - .WithOne() - .HasForeignKey("LibraryItem_Id") - .IsRequired(); - modelBuilder.Entity() - .HasOne(x => x.Next) - .WithOne() - .HasForeignKey("CollectionItem_Next_Id") - .IsRequired(); - modelBuilder.Entity() - .HasOne(x => x.Previous) - .WithOne() - .HasForeignKey("CollectionItem_Previous_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("Company") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasMany(x => x.CompanyMetadata) - .WithOne() - .HasForeignKey("CompanyMetadata_CompanyMetadata_Id") - .IsRequired(); - modelBuilder.Entity() - .HasOne(x => x.Parent) - .WithOne() - .HasForeignKey("Company_Parent_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.Description) - .HasMaxLength(65535) - .HasField("_Description") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Headquarters) - .HasMaxLength(255) - .HasField("_Headquarters") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Country) - .HasMaxLength(2) - .HasField("_Country") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Homepage) - .HasMaxLength(1024) - .HasField("_Homepage") - .UsePropertyAccessMode(PropertyAccessMode.Property); - - modelBuilder.Entity() - .HasMany(x => x.CustomItemMetadata) - .WithOne() - .HasForeignKey("CustomItemMetadata_CustomItemMetadata_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Releases) - .WithOne() - .HasForeignKey("Release_Releases_Id") - .IsRequired(); - - - modelBuilder.Entity() - .Property(t => t.EpisodeNumber) - .HasField("_EpisodeNumber") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.Releases) - .WithOne() - .HasForeignKey("Release_Releases_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.EpisodeMetadata) - .WithOne() - .HasForeignKey("EpisodeMetadata_EpisodeMetadata_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.Outline) - .HasMaxLength(1024) - .HasField("_Outline") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Plot) - .HasMaxLength(65535) - .HasField("_Plot") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Tagline) - .HasMaxLength(1024) - .HasField("_Tagline") - .UsePropertyAccessMode(PropertyAccessMode.Property); - - modelBuilder.Entity() - .ToTable("Genre") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(255) - .IsRequired() - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity().HasIndex(t => t.Name) - .IsUnique(); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - - modelBuilder.Entity() - .ToTable("Groups") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(255) - .IsRequired(); - modelBuilder.Entity().Property("Timestamp").IsConcurrencyToken(); - modelBuilder.Entity() - .HasMany(x => x.GroupPermissions) - .WithOne() - .HasForeignKey("Permission_GroupPermissions_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.ProviderMappings) - .WithOne() - .HasForeignKey("ProviderMapping_ProviderMappings_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Preferences) - .WithOne() - .HasForeignKey("Preference_Preferences_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("Library") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(1024) - .IsRequired() - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - - modelBuilder.Entity() - .ToTable("LibraryItem") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.UrlId) - .IsRequired() - .HasField("_UrlId") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity().HasIndex(t => t.UrlId) - .IsUnique(); - modelBuilder.Entity() - .Property(t => t.DateAdded) - .IsRequired() - .HasField("_DateAdded") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasOne(x => x.LibraryRoot) - .WithOne() - .HasForeignKey("LibraryRoot_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("LibraryRoot") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Path) - .HasMaxLength(65535) - .IsRequired() - .HasField("_Path") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.NetworkPath) - .HasMaxLength(65535) - .HasField("_NetworkPath") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasOne(x => x.Library) - .WithOne() - .HasForeignKey("Library_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("MediaFile") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Path) - .HasMaxLength(65535) - .IsRequired() - .HasField("_Path") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Kind) - .IsRequired() - .HasField("_Kind") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasMany(x => x.MediaFileStreams) - .WithOne() - .HasForeignKey("MediaFileStream_MediaFileStreams_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("MediaFileStream") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.StreamNumber) - .IsRequired() - .HasField("_StreamNumber") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - - modelBuilder.Entity() - .ToTable("Metadata") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Title) - .HasMaxLength(1024) - .IsRequired() - .HasField("_Title") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.OriginalTitle) - .HasMaxLength(1024) - .HasField("_OriginalTitle") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.SortTitle) - .HasMaxLength(1024) - .HasField("_SortTitle") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Language) - .HasMaxLength(3) - .IsRequired() - .HasField("_Language") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.ReleaseDate) - .HasField("_ReleaseDate") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.DateAdded) - .IsRequired() - .HasField("_DateAdded") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.DateModified) - .IsRequired() - .HasField("_DateModified") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasMany(x => x.PersonRoles) - .WithOne() - .HasForeignKey("PersonRole_PersonRoles_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Genres) - .WithOne() - .HasForeignKey("Genre_Genres_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Artwork) - .WithOne() - .HasForeignKey("Artwork_Artwork_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Ratings) - .WithOne() - .HasForeignKey("Rating_Ratings_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Sources) - .WithOne() - .HasForeignKey("MetadataProviderId_Sources_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("MetadataProvider") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(1024) - .IsRequired() - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - - modelBuilder.Entity() - .ToTable("MetadataProviderId") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.ProviderId) - .HasMaxLength(255) - .IsRequired() - .HasField("_ProviderId") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasOne(x => x.MetadataProvider) - .WithOne() - .HasForeignKey("MetadataProvider_Id") - .IsRequired(); - - modelBuilder.Entity() - .HasMany(x => x.Releases) - .WithOne() - .HasForeignKey("Release_Releases_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.MovieMetadata) - .WithOne() - .HasForeignKey("MovieMetadata_MovieMetadata_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.Outline) - .HasMaxLength(1024) - .HasField("_Outline") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Plot) - .HasMaxLength(65535) - .HasField("_Plot") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Tagline) - .HasMaxLength(1024) - .HasField("_Tagline") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Country) - .HasMaxLength(2) - .HasField("_Country") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.Studios) - .WithOne() - .HasForeignKey("Company_Studios_Id") - .IsRequired(); - - modelBuilder.Entity() - .HasMany(x => x.MusicAlbumMetadata) - .WithOne() - .HasForeignKey("MusicAlbumMetadata_MusicAlbumMetadata_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Tracks) - .WithOne() - .HasForeignKey("Track_Tracks_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.Barcode) - .HasMaxLength(255) - .HasField("_Barcode") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.LabelNumber) - .HasMaxLength(255) - .HasField("_LabelNumber") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Country) - .HasMaxLength(2) - .HasField("_Country") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.Labels) - .WithOne() - .HasForeignKey("Company_Labels_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("Permissions") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Kind) - .IsRequired() - .HasField("_Kind") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Value) - .IsRequired(); - modelBuilder.Entity().Property("Timestamp").IsConcurrencyToken(); - - modelBuilder.Entity() - .ToTable("Person") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.UrlId) - .IsRequired() - .HasField("_UrlId") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(1024) - .IsRequired() - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.SourceId) - .HasMaxLength(255) - .HasField("_SourceId") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.DateAdded) - .IsRequired() - .HasField("_DateAdded") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.DateModified) - .IsRequired() - .HasField("_DateModified") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasMany(x => x.Sources) - .WithOne() - .HasForeignKey("MetadataProviderId_Sources_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("PersonRole") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Role) - .HasMaxLength(1024) - .HasField("_Role") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Type) - .IsRequired() - .HasField("_Type") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasOne(x => x.Person) - .WithOne() - .HasForeignKey("Person_Id") - .IsRequired() - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity() - .HasOne(x => x.Artwork) - .WithOne() - .HasForeignKey("Artwork_Artwork_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Sources) - .WithOne() - .HasForeignKey("MetadataProviderId_Sources_Id") - .IsRequired(); - - modelBuilder.Entity() - .HasMany(x => x.PhotoMetadata) - .WithOne() - .HasForeignKey("PhotoMetadata_PhotoMetadata_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Releases) - .WithOne() - .HasForeignKey("Release_Releases_Id") - .IsRequired(); - - - modelBuilder.Entity() - .ToTable("Preferences") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Kind) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.Value) - .HasMaxLength(65535) - .IsRequired(); - modelBuilder.Entity().Property("Timestamp").IsConcurrencyToken(); - - modelBuilder.Entity() - .ToTable("ProviderMappings") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.ProviderName) - .HasMaxLength(255) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.ProviderSecrets) - .HasMaxLength(65535) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.ProviderData) - .HasMaxLength(65535) - .IsRequired(); - modelBuilder.Entity().Property("Timestamp").IsConcurrencyToken(); - - modelBuilder.Entity() - .ToTable("Rating") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Value) - .IsRequired() - .HasField("_Value") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Votes) - .HasField("_Votes") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasOne(x => x.RatingType) - .WithOne() - .HasForeignKey("RatingSource_RatingType_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("RatingType") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(1024) - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.MaximumValue) - .IsRequired() - .HasField("_MaximumValue") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.MinimumValue) - .IsRequired() - .HasField("_MinimumValue") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasOne(x => x.Source) - .WithOne() - .HasForeignKey("MetadataProviderId_Source_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("Release") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(1024) - .IsRequired() - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasMany(x => x.MediaFiles) - .WithOne() - .HasForeignKey("MediaFile_MediaFiles_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Chapters) - .WithOne() - .HasForeignKey("Chapter_Chapters_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.SeasonNumber) - .HasField("_SeasonNumber") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.SeasonMetadata) - .WithOne() - .HasForeignKey("SeasonMetadata_SeasonMetadata_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Episodes) - .WithOne() - .HasForeignKey("Episode_Episodes_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.Outline) - .HasMaxLength(1024) - .HasField("_Outline") - .UsePropertyAccessMode(PropertyAccessMode.Property); - - modelBuilder.Entity() - .Property(t => t.AirsDayOfWeek) - .HasField("_AirsDayOfWeek") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.AirsTime) - .HasField("_AirsTime") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.FirstAired) - .HasField("_FirstAired") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.SeriesMetadata) - .WithOne() - .HasForeignKey("SeriesMetadata_SeriesMetadata_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Seasons) - .WithOne() - .HasForeignKey("Season_Seasons_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.Outline) - .HasMaxLength(1024) - .HasField("_Outline") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Plot) - .HasMaxLength(65535) - .HasField("_Plot") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Tagline) - .HasMaxLength(1024) - .HasField("_Tagline") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Country) - .HasMaxLength(2) - .HasField("_Country") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.Networks) - .WithOne() - .HasForeignKey("Company_Networks_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.TrackNumber) - .HasField("_TrackNumber") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.Releases) - .WithOne() - .HasForeignKey("Release_Releases_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.TrackMetadata) - .WithOne() - .HasForeignKey("TrackMetadata_TrackMetadata_Id") - .IsRequired(); - - - modelBuilder.Entity() - .ToTable("Users") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.LastLoginTimestamp) - .IsRequired() - .IsRowVersion(); - modelBuilder.Entity() - .Property(t => t.Username) - .HasMaxLength(255) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.Password) - .HasMaxLength(65535); - modelBuilder.Entity() - .Property(t => t.MustUpdatePassword) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.AudioLanguagePreference) - .HasMaxLength(255) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.AuthenticationProviderId) - .HasMaxLength(255) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.GroupedFolders) - .HasMaxLength(65535); - modelBuilder.Entity() - .Property(t => t.InvalidLoginAttemptCount) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.LatestItemExcludes) - .HasMaxLength(65535); - modelBuilder.Entity() - .Property(t => t.MyMediaExcludes) - .HasMaxLength(65535); - modelBuilder.Entity() - .Property(t => t.OrderedViews) - .HasMaxLength(65535); - modelBuilder.Entity() - .Property(t => t.SubtitleMode) - .HasMaxLength(255) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.PlayDefaultAudioTrack) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.SubtitleLanguagePrefernce) - .HasMaxLength(255); - modelBuilder.Entity() - .HasMany(x => x.Groups) - .WithOne() - .HasForeignKey("Group_Groups_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Permissions) - .WithOne() - .HasForeignKey("Permission_Permissions_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.ProviderMappings) - .WithOne() - .HasForeignKey("ProviderMapping_ProviderMappings_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Preferences) - .WithOne() - .HasForeignKey("Preference_Preferences_Id") - .IsRequired(); - - OnModelCreatedImpl(modelBuilder); - } - } -} -- cgit v1.2.3 From d7d8118b42c8abc8a4f12c4f2b0fb97cc6384ba7 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 3 May 2020 14:02:15 -0600 Subject: Fix xml docs --- Jellyfin.Api/Controllers/NotificationsController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs index 3cbb3a3a3..8d82ca10f 100644 --- a/Jellyfin.Api/Controllers/NotificationsController.cs +++ b/Jellyfin.Api/Controllers/NotificationsController.cs @@ -83,7 +83,7 @@ namespace Jellyfin.Api.Controllers /// /// Gets notification services. /// - /// All notification services returned. + /// All notification services returned. /// An containing a list of all notification services. [HttpGet("Services")] [ProducesResponseType(StatusCodes.Status200OK)] -- cgit v1.2.3 From a78184ef4423be8e320f642eb7b0155810d86cbd Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Mon, 4 May 2020 16:00:41 -0400 Subject: Add the user data to the schema --- .../Jellyfin.Server.Implementations.csproj | 2 + Jellyfin.Server.Implementations/JellyfinDb.cs | 12 +- .../20200504195702_UserSchema.Designer.cs | 324 +++++++++++++++++++++ .../Migrations/20200504195702_UserSchema.cs | 219 ++++++++++++++ .../Migrations/JellyfinDbModelSnapshot.cs | 251 ++++++++++++++++ 5 files changed, 802 insertions(+), 6 deletions(-) create mode 100644 Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.Designer.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.cs diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index a31f28f64..7ff978698 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -23,6 +23,8 @@ + + diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 6fc8d251b..8b6b7cacc 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -16,6 +16,11 @@ 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 ProviderMappings { 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; } @@ -29,7 +34,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; } @@ -42,13 +46,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; } /// @@ -62,8 +63,7 @@ namespace Jellyfin.Server.Implementations public virtual DbSet Series { get; set; } public virtual DbSet SeriesMetadata { get; set; } public virtual DbSet Tracks { get; set; } - public virtual DbSet TrackMetadata { get; set; } - public virtual DbSet Users { get; set; } */ + public virtual DbSet TrackMetadata { get; set; } */ /// /// Gets or sets the default connection string. diff --git a/Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.Designer.cs new file mode 100644 index 000000000..8313c6a3b --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.Designer.cs @@ -0,0 +1,324 @@ +#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 new file mode 100644 index 000000000..f24ccccbf --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200504195702_UserSchema.cs @@ -0,0 +1,219 @@ +#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/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs index 27f5fe24b..d3a7ed333 100644 --- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs +++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs @@ -62,6 +62,257 @@ namespace Jellyfin.Server.Implementations.Migrations 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 } } -- cgit v1.2.3 From 8bd356ab2077b5c1a90510e3e73b11698eca0331 Mon Sep 17 00:00:00 2001 From: Erik Rigtorp Date: Mon, 4 May 2020 20:19:10 -0700 Subject: Reduce number of TMDB lookups if filenames have punctuation chars Previosly TMDB would be queried with the raw name and always fail, then retry with the cleaned name. Now non-word chars are always cleaned out first. If first query fails, retry with more aggressive cleaning. --- MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs | 67 ++++++++++++++---------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs b/MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs index 223cef086..08c1afec2 100644 --- a/MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs +++ b/MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; +using System.Text.RegularExpressions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; @@ -19,6 +20,20 @@ namespace MediaBrowser.Providers.Tmdb.Movies public class TmdbSearch { private static readonly CultureInfo EnUs = new CultureInfo("en-US"); + + private static readonly Regex cleanEnclosed = new Regex(@"\p{Ps}.*\p{Pe}", RegexOptions.Compiled); + private static readonly Regex cleanNonWord = new Regex(@"[\W_]+", RegexOptions.Compiled); + private static readonly Regex cleanStopWords = new Regex(@"\b( # Start at word boundary + 19[0-9]{2}|20[0-9]{2}| # 1900-2099 + S[0-9]{2}| # Season + E[0-9]{2}| # Episode + (2160|1080|720|576|480)[ip]?| # Resolution + [xh]?264| # Encoding + (web|dvd|bd|hdtv|hd)rip| # *Rip + web|hdtv|mp4|bluray|ktr|dl|single|imageset|internal|doku|dubbed|retail|xxx|flac + ).* # Match rest of string", + RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase); + private const string Search3 = TmdbUtils.BaseTmdbApiUrl + @"3/search/{3}?api_key={1}&query={0}&language={2}"; private readonly ILogger _logger; @@ -61,19 +76,18 @@ namespace MediaBrowser.Providers.Tmdb.Movies var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); - if (!string.IsNullOrWhiteSpace(name)) - { - var parsedName = _libraryManager.ParseName(name); - var yearInName = parsedName.Year; - name = parsedName.Name; - year = year ?? yearInName; - } + // Does this mean we are reparsing already parsed ItemLookupInfo? + var parsedName = _libraryManager.ParseName(name); + var yearInName = parsedName.Year; + name = parsedName.Name; + year = year ?? yearInName; - _logger.LogInformation("MovieDbProvider: Finding id for item: " + name); + _logger.LogInformation("TmdbSearch: Finding id for item: {0} ({1})", name, year); var language = idInfo.MetadataLanguage.ToLowerInvariant(); - //nope - search for it - //var searchType = item is BoxSet ? "collection" : "movie"; + // Replace sequences of non-word characters with space + // TMDB expects a space separated list of words make sure that is the case + name = cleanNonWord.Replace(name, " ").Trim(); var results = await GetSearchResults(name, searchType, year, language, tmdbImageUrl, cancellationToken).ConfigureAwait(false); @@ -86,36 +100,35 @@ namespace MediaBrowser.Providers.Tmdb.Movies } } + // Ideally retrying alternatives should be done outside the search + // provider so that the retry logic can be common for all search + // providers if (results.Count == 0) { - // try with dot and _ turned to space - var originalName = name; - - name = name.Replace(",", " "); - name = name.Replace(".", " "); - name = name.Replace("_", " "); - name = name.Replace("-", " "); - name = name.Replace("!", " "); - name = name.Replace("?", " "); - - var parenthIndex = name.IndexOf('('); - if (parenthIndex != -1) - { - name = name.Substring(0, parenthIndex); - } + name = parsedName.Name; + + // Remove things enclosed in []{}() etc + name = cleanEnclosed.Replace(name, string.Empty); + // Replace sequences of non-word characters with space + name = cleanNonWord.Replace(name, " "); + + // Clean based on common stop words / tokens + name = cleanStopWords.Replace(name, string.Empty); + + // Trim whitespace name = name.Trim(); // Search again if the new name is different - if (!string.Equals(name, originalName)) + if (!string.Equals(name, parsedName.Name) && !string.IsNullOrWhiteSpace(name)) { + _logger.LogInformation("TmdbSearch: Finding id for item: {0} ({1})", name, year); results = await GetSearchResults(name, searchType, year, language, tmdbImageUrl, cancellationToken).ConfigureAwait(false); if (results.Count == 0 && !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase)) { //one more time, in english results = await GetSearchResults(name, searchType, year, "en", tmdbImageUrl, cancellationToken).ConfigureAwait(false); - } } } -- cgit v1.2.3 From f7c44565fc8f7cdaa7e7c95f174227ab90dd4afe Mon Sep 17 00:00:00 2001 From: Erik Rigtorp Date: Thu, 7 May 2020 15:47:46 -0700 Subject: Rename member variables to conform to coding standard --- MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs | 30 ++++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs b/MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs index 08c1afec2..47d501247 100644 --- a/MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs +++ b/MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs @@ -19,11 +19,11 @@ namespace MediaBrowser.Providers.Tmdb.Movies { public class TmdbSearch { - private static readonly CultureInfo EnUs = new CultureInfo("en-US"); + private static readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private static readonly Regex cleanEnclosed = new Regex(@"\p{Ps}.*\p{Pe}", RegexOptions.Compiled); - private static readonly Regex cleanNonWord = new Regex(@"[\W_]+", RegexOptions.Compiled); - private static readonly Regex cleanStopWords = new Regex(@"\b( # Start at word boundary + private static readonly Regex _cleanEnclosed = new Regex(@"\p{Ps}.*\p{Pe}", RegexOptions.Compiled); + private static readonly Regex _cleanNonWord = new Regex(@"[\W_]+", RegexOptions.Compiled); + private static readonly Regex _cleanStopWords = new Regex(@"\b( # Start at word boundary 19[0-9]{2}|20[0-9]{2}| # 1900-2099 S[0-9]{2}| # Season E[0-9]{2}| # Episode @@ -34,7 +34,7 @@ namespace MediaBrowser.Providers.Tmdb.Movies ).* # Match rest of string", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase); - private const string Search3 = TmdbUtils.BaseTmdbApiUrl + @"3/search/{3}?api_key={1}&query={0}&language={2}"; + private const string _searchURL = TmdbUtils.BaseTmdbApiUrl + @"3/search/{3}?api_key={1}&query={0}&language={2}"; private readonly ILogger _logger; private readonly IJsonSerializer _json; @@ -87,7 +87,7 @@ namespace MediaBrowser.Providers.Tmdb.Movies // Replace sequences of non-word characters with space // TMDB expects a space separated list of words make sure that is the case - name = cleanNonWord.Replace(name, " ").Trim(); + name = _cleanNonWord.Replace(name, " ").Trim(); var results = await GetSearchResults(name, searchType, year, language, tmdbImageUrl, cancellationToken).ConfigureAwait(false); @@ -108,13 +108,13 @@ namespace MediaBrowser.Providers.Tmdb.Movies name = parsedName.Name; // Remove things enclosed in []{}() etc - name = cleanEnclosed.Replace(name, string.Empty); + name = _cleanEnclosed.Replace(name, string.Empty); // Replace sequences of non-word characters with space - name = cleanNonWord.Replace(name, " "); + name = _cleanNonWord.Replace(name, " "); // Clean based on common stop words / tokens - name = cleanStopWords.Replace(name, string.Empty); + name = _cleanStopWords.Replace(name, string.Empty); // Trim whitespace name = name.Trim(); @@ -163,7 +163,7 @@ namespace MediaBrowser.Providers.Tmdb.Movies throw new ArgumentException("name"); } - var url3 = string.Format(Search3, WebUtility.UrlEncode(name), TmdbUtils.ApiKey, language, type); + var url3 = string.Format(_searchURL, WebUtility.UrlEncode(name), TmdbUtils.ApiKey, language, type); using (var response = await TmdbMovieProvider.Current.GetMovieDbResponse(new HttpRequestOptions { @@ -192,14 +192,14 @@ namespace MediaBrowser.Providers.Tmdb.Movies if (!string.IsNullOrWhiteSpace(i.Release_Date)) { // These dates are always in this exact format - if (DateTime.TryParseExact(i.Release_Date, "yyyy-MM-dd", EnUs, DateTimeStyles.None, out var r)) + if (DateTime.TryParseExact(i.Release_Date, "yyyy-MM-dd", _usCulture, DateTimeStyles.None, out var r)) { remoteResult.PremiereDate = r.ToUniversalTime(); remoteResult.ProductionYear = remoteResult.PremiereDate.Value.Year; } } - remoteResult.SetProviderId(MetadataProviders.Tmdb, i.Id.ToString(EnUs)); + remoteResult.SetProviderId(MetadataProviders.Tmdb, i.Id.ToString(_usCulture)); return remoteResult; @@ -216,7 +216,7 @@ namespace MediaBrowser.Providers.Tmdb.Movies throw new ArgumentException("name"); } - var url3 = string.Format(Search3, WebUtility.UrlEncode(name), TmdbUtils.ApiKey, language, "tv"); + var url3 = string.Format(_searchURL, WebUtility.UrlEncode(name), TmdbUtils.ApiKey, language, "tv"); using (var response = await TmdbMovieProvider.Current.GetMovieDbResponse(new HttpRequestOptions { @@ -245,14 +245,14 @@ namespace MediaBrowser.Providers.Tmdb.Movies if (!string.IsNullOrWhiteSpace(i.First_Air_Date)) { // These dates are always in this exact format - if (DateTime.TryParseExact(i.First_Air_Date, "yyyy-MM-dd", EnUs, DateTimeStyles.None, out var r)) + if (DateTime.TryParseExact(i.First_Air_Date, "yyyy-MM-dd", _usCulture, DateTimeStyles.None, out var r)) { remoteResult.PremiereDate = r.ToUniversalTime(); remoteResult.ProductionYear = remoteResult.PremiereDate.Value.Year; } } - remoteResult.SetProviderId(MetadataProviders.Tmdb, i.Id.ToString(EnUs)); + remoteResult.SetProviderId(MetadataProviders.Tmdb, i.Id.ToString(_usCulture)); return remoteResult; -- cgit v1.2.3 From a517bd2e52571dacf4a536f633d9102735422f13 Mon Sep 17 00:00:00 2001 From: Vasily Date: Fri, 8 May 2020 14:32:41 +0300 Subject: Re-raise the exception that caused LiveTV stream to not open --- .../LiveTv/TunerHosts/SharedHttpStream.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index 0e600202a..f13b65722 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -118,6 +118,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts //OpenedMediaSource.SupportsDirectStream = true; //OpenedMediaSource.SupportsTranscoding = true; await taskCompletionSource.Task.ConfigureAwait(false); + if (taskCompletionSource.Task.Exception != null) + { + // Error happened during opening the stream, re-raise the exception to inform the caller + throw taskCompletionSource.Task.Exception; + } } private Task StartStreaming(HttpResponseInfo response, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) @@ -139,12 +144,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts cancellationToken).ConfigureAwait(false); } } - catch (OperationCanceledException) + catch (OperationCanceledException ex) { + Logger.LogWarning(ex, "Copying of {0} to {1} was canceled", GetType().Name, TempFilePath); + openTaskCompletionSource.TrySetException(ex); } catch (Exception ex) { - Logger.LogError(ex, "Error copying live stream."); + Logger.LogError(ex, "Error copying live stream {0} to {1}.", GetType().Name, TempFilePath); + openTaskCompletionSource.TrySetException(ex); } EnableStreamSharing = false; -- cgit v1.2.3 From 2b1b9a64b6b7a3bac4d96642cda7a0c55d5cae74 Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 8 May 2020 08:40:37 -0600 Subject: Add OperationId to SwaggerGen --- Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index a354f45aa..344ef6a5f 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using System.Text.Json.Serialization; using Jellyfin.Api; using Jellyfin.Api.Auth; @@ -14,6 +15,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; namespace Jellyfin.Server.Extensions { @@ -112,6 +114,10 @@ namespace Jellyfin.Server.Extensions // Order actions by route path, then by http method. c.OrderActionsBy(description => $"{description.ActionDescriptor.RouteValues["controller"]}_{description.HttpMethod}"); + + // Use method name as operationId + c.CustomOperationIds(description => + description.TryGetMethodInfo(out MethodInfo methodInfo) ? methodInfo.Name : null); }); } } -- cgit v1.2.3 From 3401d55f41cac1840b8332b2b0843f58c09ad848 Mon Sep 17 00:00:00 2001 From: Vasily Date: Fri, 8 May 2020 23:11:43 +0300 Subject: Fixed yet another case of hanging on a bad stream --- .../LiveTv/TunerHosts/SharedHttpStream.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index f13b65722..e41ced28b 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Net.Http; using System.Threading; @@ -123,6 +124,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts // Error happened during opening the stream, re-raise the exception to inform the caller throw taskCompletionSource.Task.Exception; } + 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)); + } } private Task StartStreaming(HttpResponseInfo response, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) @@ -146,7 +154,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } catch (OperationCanceledException ex) { - Logger.LogWarning(ex, "Copying of {0} to {1} was canceled", GetType().Name, TempFilePath); + Logger.LogInformation("Copying of {0} to {1} was canceled", GetType().Name, TempFilePath); openTaskCompletionSource.TrySetException(ex); } catch (Exception ex) @@ -154,6 +162,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts Logger.LogError(ex, "Error copying live stream {0} to {1}.", GetType().Name, TempFilePath); openTaskCompletionSource.TrySetException(ex); } + openTaskCompletionSource.TrySetResult(false); EnableStreamSharing = false; await DeleteTempFiles(new List { TempFilePath }).ConfigureAwait(false); -- cgit v1.2.3 From 0e8505fb961aa0e29528a8404ca6bc25a90f75a1 Mon Sep 17 00:00:00 2001 From: newton181 Date: Fri, 8 May 2020 21:51:54 +0000 Subject: Translated using Weblate (Spanish (Mexico)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es_MX/ --- Emby.Server.Implementations/Localization/Core/es-MX.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json index e0bbe90b3..d93920f43 100644 --- a/Emby.Server.Implementations/Localization/Core/es-MX.json +++ b/Emby.Server.Implementations/Localization/Core/es-MX.json @@ -11,7 +11,7 @@ "Collections": "Colecciones", "DeviceOfflineWithName": "{0} se ha desconectado", "DeviceOnlineWithName": "{0} está conectado", - "FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión de {0}", + "FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión desde {0}", "Favorites": "Favoritos", "Folders": "Carpetas", "Genres": "Géneros", -- cgit v1.2.3 From 58122cc06785739e67885b6aded0ef434ca1c6de Mon Sep 17 00:00:00 2001 From: andra5 Date: Fri, 8 May 2020 21:22:30 +0000 Subject: Translated using Weblate (German (Swiss)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/gsw/ --- .../Localization/Core/gsw.json | 81 ++++++++++++---------- 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/gsw.json b/Emby.Server.Implementations/Localization/Core/gsw.json index 9611e33f5..c8291a202 100644 --- a/Emby.Server.Implementations/Localization/Core/gsw.json +++ b/Emby.Server.Implementations/Localization/Core/gsw.json @@ -1,41 +1,41 @@ { - "Albums": "Albom", - "AppDeviceValues": "App: {0}, Grät: {1}", - "Application": "Aawändig", - "Artists": "Könstler", - "AuthenticationSucceededWithUserName": "{0} het sech aagmäudet", - "Books": "Büecher", - "CameraImageUploadedFrom": "Es nöis Foti esch ufeglade worde vo {0}", - "Channels": "Kanäu", - "ChapterNameValue": "Kapitu {0}", - "Collections": "Sammlige", - "DeviceOfflineWithName": "{0} esch offline gange", - "DeviceOnlineWithName": "{0} esch online cho", - "FailedLoginAttemptWithUserName": "Fäugschlagne Aamäudeversuech vo {0}", - "Favorites": "Favorite", + "Albums": "Alben", + "AppDeviceValues": "App: {0}, Gerät: {1}", + "Application": "Anwendung", + "Artists": "Künstler", + "AuthenticationSucceededWithUserName": "{0} hat sich angemeldet", + "Books": "Bücher", + "CameraImageUploadedFrom": "Ein neues Foto wurde von {0} hochgeladen", + "Channels": "Kanäle", + "ChapterNameValue": "Kapitel {0}", + "Collections": "Sammlungen", + "DeviceOfflineWithName": "{0} wurde getrennt", + "DeviceOnlineWithName": "{0} ist verbunden", + "FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}", + "Favorites": "Favoriten", "Folders": "Ordner", "Genres": "Genres", - "HeaderAlbumArtists": "Albom-Könstler", + "HeaderAlbumArtists": "Album-Künstler", "HeaderCameraUploads": "Kamera-Uploads", - "HeaderContinueWatching": "Wiiterluege", - "HeaderFavoriteAlbums": "Lieblingsalbe", - "HeaderFavoriteArtists": "Lieblings-Interprete", - "HeaderFavoriteEpisodes": "Lieblingsepisode", - "HeaderFavoriteShows": "Lieblingsserie", + "HeaderContinueWatching": "weiter schauen", + "HeaderFavoriteAlbums": "Lieblingsalben", + "HeaderFavoriteArtists": "Lieblings-Künstler", + "HeaderFavoriteEpisodes": "Lieblingsepisoden", + "HeaderFavoriteShows": "Lieblingsserien", "HeaderFavoriteSongs": "Lieblingslieder", - "HeaderLiveTV": "Live-Färnseh", - "HeaderNextUp": "Als nächts", - "HeaderRecordingGroups": "Ufnahmegruppe", - "HomeVideos": "Heimfilmli", - "Inherit": "Hinzuefüege", - "ItemAddedWithName": "{0} esch de Bibliothek dezuegfüegt worde", - "ItemRemovedWithName": "{0} esch vo de Bibliothek entfärnt worde", - "LabelIpAddressValue": "IP-Adrässe: {0}", - "LabelRunningTimeValue": "Loufziit: {0}", - "Latest": "Nöischti", - "MessageApplicationUpdated": "Jellyfin Server esch aktualisiert worde", - "MessageApplicationUpdatedTo": "Jellyfin Server esch of Version {0} aktualisiert worde", - "MessageNamedServerConfigurationUpdatedWithValue": "De Serveriistöuigsberiich {0} esch aktualisiert worde", + "HeaderLiveTV": "Live-Fernseh", + "HeaderNextUp": "Als Nächstes", + "HeaderRecordingGroups": "Aufnahme-Gruppen", + "HomeVideos": "Heimvideos", + "Inherit": "Vererben", + "ItemAddedWithName": "{0} wurde der Bibliothek hinzugefügt", + "ItemRemovedWithName": "{0} wurde aus der Bibliothek entfernt", + "LabelIpAddressValue": "IP-Adresse: {0}", + "LabelRunningTimeValue": "Laufzeit: {0}", + "Latest": "Neueste", + "MessageApplicationUpdated": "Jellyfin-Server wurde aktualisiert", + "MessageApplicationUpdatedTo": "Jellyfin-Server wurde auf Version {0} aktualisiert", + "MessageNamedServerConfigurationUpdatedWithValue": "Der Server-Einstellungsbereich {0} wurde aktualisiert", "MessageServerConfigurationUpdated": "Serveriistöuige send aktualisiert worde", "MixedContent": "Gmeschti Inhäut", "Movies": "Film", @@ -50,7 +50,7 @@ "NotificationOptionAudioPlayback": "Audiowedergab gstartet", "NotificationOptionAudioPlaybackStopped": "Audiwedergab gstoppt", "NotificationOptionCameraImageUploaded": "Foti ueglade", - "NotificationOptionInstallationFailed": "Installationsfäuer", + "NotificationOptionInstallationFailed": "Installationsfehler", "NotificationOptionNewLibraryContent": "Nöie Inhaut hinzuegfüegt", "NotificationOptionPluginError": "Plugin-Fäuer", "NotificationOptionPluginInstalled": "Plugin installiert", @@ -92,5 +92,16 @@ "UserStoppedPlayingItemWithValues": "{0} het d'Wedergab vo {1} of {2} gstoppt", "ValueHasBeenAddedToLibrary": "{0} esch dinnere Biblithek hinzuegfüegt worde", "ValueSpecialEpisodeName": "Extra - {0}", - "VersionNumber": "Version {0}" + "VersionNumber": "Version {0}", + "TaskCleanLogs": "Lösche Log Pfad", + "TaskRefreshLibraryDescription": "Scanne alle Bibliotheken für hinzugefügte Datein und erneuere Metadaten.", + "TaskRefreshLibrary": "Scanne alle Bibliotheken", + "TaskRefreshChapterImagesDescription": "Kreiert Vorschaubilder für Videos welche Kapitel haben.", + "TaskRefreshChapterImages": "Extrahiere Kapitel-Bilder", + "TaskCleanCacheDescription": "Löscht Zwischenspeicherdatein die nicht länger von System gebraucht werden.", + "TaskCleanCache": "Leere Cache Pfad", + "TasksChannelsCategory": "Internet Kanäle", + "TasksApplicationCategory": "Applikation", + "TasksLibraryCategory": "Bibliothek", + "TasksMaintenanceCategory": "Verwaltung" } -- 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(-) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index c819c163a..4c33ff71b 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 7a316b070..d1ee22ea8 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 02cf08cd7..8cc3d1fac 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 eb61da7f3..7074e2225 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 2eaf9ce83..4b6e16762 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 930968d9f..9a26ffd99 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 8e886a2cb..c01fead83 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 5b08eac0a..34eae4062 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 433d6d8bc..fbc208d27 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 20e76932d..9f40f9577 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 cae769db0..ba97641f6 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 diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 8419014c2..730323c22 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 6a64209c1..aab745de4 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 000000000..9c9758de1 --- /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 000000000..d3197d97b --- /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 8cc3d1fac..000000000 --- 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 7074e2225..000000000 --- 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 000000000..bcdc833e4 --- /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 000000000..4a9307e62 --- /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 4b6e16762..000000000 --- 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 9a26ffd99..000000000 --- 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 39c065b89..4c2f834cb 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 000000000..087748de0 --- /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 000000000..a3975c334 --- /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 000000000..de1fcd259 --- /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 000000000..6c962ec85 --- /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 c01fead83..000000000 --- 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 7630428d7..000000000 --- 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 34eae4062..000000000 --- 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 fbc208d27..000000000 --- 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 cddf68c42..d891a8167 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 000000000..7b833506b --- /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 000000000..895702f3d --- /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 000000000..89d245787 --- /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 000000000..d67b6bd55 --- /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 000000000..9de23194e --- /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 000000000..f1e175fde --- /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 000000000..0f06e381f --- /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 000000000..113719871 --- /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 000000000..0a6036154 --- /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 50ad70630..000000000 --- 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 cc49e92a9..000000000 --- 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 9f40f9577..000000000 --- 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 8d8a2646a..000000000 --- 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 ba97641f6..000000000 --- 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 b3d49d09e..000000000 --- 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 d9f391403..000000000 --- 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 02e4774d0..000000000 --- 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 f7887dc33..000000000 --- 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 cf576c358..3e027e831 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(-) diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index d1ee22ea8..3704445ab 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 9c9758de1..c7bd242a7 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 d3197d97b..93cec1304 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 bcdc833e4..9137faf9f 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 fb766ab57..b371a59e9 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 087748de0..bda49bd1b 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 6c962ec85..006fb687b 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 7b833506b..f28ecf16d 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 f33876e7e351129ea2296b537b38f9c87fd67b70 Mon Sep 17 00:00:00 2001 From: timothyc824 Date: Sat, 9 May 2020 14:13:12 +0000 Subject: Translated using Weblate (Chinese (Hong Kong)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hant_HK/ --- Emby.Server.Implementations/Localization/Core/zh-HK.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json index 224748e61..a67a67582 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-HK.json +++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json @@ -1,6 +1,6 @@ { "Albums": "專輯", - "AppDeviceValues": "軟體: {0}, 設備: {1}", + "AppDeviceValues": "軟件: {0}, 設備: {1}", "Application": "應用程式", "Artists": "藝人", "AuthenticationSucceededWithUserName": "{0} 授權成功", @@ -92,5 +92,8 @@ "UserStoppedPlayingItemWithValues": "{0} 已在 {2} 上停止播放 {1}", "ValueHasBeenAddedToLibrary": "{0} 已添加到你的媒體庫", "ValueSpecialEpisodeName": "特典 - {0}", - "VersionNumber": "版本{0}" + "VersionNumber": "版本{0}", + "TaskDownloadMissingSubtitles": "下載遺失的字幕", + "TaskUpdatePlugins": "更新插件", + "TasksApplicationCategory": "應用程式" } -- cgit v1.2.3 From a262ecd9c77b0e906288de0890aee2d5dcad6ae4 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Sat, 9 May 2020 19:49:55 +0200 Subject: Add positionning cues to WebVTT writer --- MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs b/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs index 2e328ba63..de35acbba 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/VttWriter.cs @@ -7,14 +7,25 @@ using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.MediaEncoding.Subtitles { + /// + /// Subtitle writer for the WebVTT format. + /// public class VttWriter : ISubtitleWriter { + /// public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken) { using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) { writer.WriteLine("WEBVTT"); writer.WriteLine(string.Empty); + writer.WriteLine("REGION"); + writer.WriteLine("id:subtitle"); + writer.WriteLine("width:80%"); + writer.WriteLine("lines:3"); + writer.WriteLine("regionanchor:50%,100%"); + writer.WriteLine("viewportanchor:50%,90%"); + writer.WriteLine(string.Empty); foreach (var trackEvent in info.TrackEvents) { cancellationToken.ThrowIfCancellationRequested(); @@ -22,13 +33,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles var startTime = TimeSpan.FromTicks(trackEvent.StartPositionTicks); var endTime = TimeSpan.FromTicks(trackEvent.EndPositionTicks); - // make sure the start and end times are different and seqential + // make sure the start and end times are different and sequential if (endTime.TotalMilliseconds <= startTime.TotalMilliseconds) { endTime = startTime.Add(TimeSpan.FromMilliseconds(1)); } - writer.WriteLine(@"{0:hh\:mm\:ss\.fff} --> {1:hh\:mm\:ss\.fff}", startTime, endTime); + writer.WriteLine(@"{0:hh\:mm\:ss\.fff} --> {1:hh\:mm\:ss\.fff} region:subtitle", startTime, endTime); var text = trackEvent.Text; -- cgit v1.2.3 From 9137069f6de03d8606928ca70f18c3e14aeaac71 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Sun, 10 May 2020 13:53:04 +0200 Subject: Add more information to TmdbSeriesProvider --- .../Tmdb/TV/TmdbSeriesProvider.cs | 66 ++++++++++++++-------- 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs index 7195dc42a..4697133e6 100644 --- a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs @@ -26,11 +26,6 @@ namespace MediaBrowser.Providers.Tmdb.TV { public class TmdbSeriesProvider : IRemoteMetadataProvider, IHasOrder { - private const string GetTvInfo3 = TmdbUtils.BaseTmdbApiUrl + @"3/tv/{0}?api_key={1}&append_to_response=credits,images,keywords,external_ids,videos,content_ratings"; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - internal static TmdbSeriesProvider Current { get; private set; } - private readonly IJsonSerializer _jsonSerializer; private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _configurationManager; @@ -39,6 +34,11 @@ namespace MediaBrowser.Providers.Tmdb.TV private readonly IHttpClient _httpClient; private readonly ILibraryManager _libraryManager; + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + private const string GetTvInfo3 = TmdbUtils.BaseTmdbApiUrl + @"3/tv/{0}?api_key={1}&append_to_response=credits,images,keywords,external_ids,videos,content_ratings"; + + internal static TmdbSeriesProvider Current { get; private set; } + public TmdbSeriesProvider( IJsonSerializer jsonSerializer, IFileSystem fileSystem, @@ -217,10 +217,9 @@ namespace MediaBrowser.Providers.Tmdb.TV var series = seriesResult.Item; series.Name = seriesInfo.Name; + series.OriginalTitle = seriesInfo.Original_Name; series.SetProviderId(MetadataProviders.Tmdb, seriesInfo.Id.ToString(_usCulture)); - //series.VoteCount = seriesInfo.vote_count; - string voteAvg = seriesInfo.Vote_Average.ToString(CultureInfo.InvariantCulture); if (float.TryParse(voteAvg, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out float rating)) @@ -240,7 +239,7 @@ namespace MediaBrowser.Providers.Tmdb.TV series.Genres = seriesInfo.Genres.Select(i => i.Name).ToArray(); } - //series.HomePageUrl = seriesInfo.homepage; + series.HomePageUrl = seriesInfo.Homepage; series.RunTimeTicks = seriesInfo.Episode_Run_Time.Select(i => TimeSpan.FromMinutes(i).Ticks).FirstOrDefault(); @@ -308,29 +307,50 @@ namespace MediaBrowser.Providers.Tmdb.TV seriesResult.ResetPeople(); var tmdbImageUrl = settings.images.GetImageUrl("original"); - if (seriesInfo.Credits != null && seriesInfo.Credits.Cast != null) + if (seriesInfo.Credits != null) { - foreach (var actor in seriesInfo.Credits.Cast.OrderBy(a => a.Order)) + if (seriesInfo.Credits.Cast != null) { - var personInfo = new PersonInfo + foreach (var actor in seriesInfo.Credits.Cast.OrderBy(a => a.Order)) { - Name = actor.Name.Trim(), - Role = actor.Character, - Type = PersonType.Actor, - SortOrder = actor.Order - }; + var personInfo = new PersonInfo {Name = actor.Name.Trim(), Role = actor.Character, Type = PersonType.Actor, SortOrder = actor.Order}; - if (!string.IsNullOrWhiteSpace(actor.Profile_Path)) - { - personInfo.ImageUrl = tmdbImageUrl + actor.Profile_Path; + if (!string.IsNullOrWhiteSpace(actor.Profile_Path)) + { + personInfo.ImageUrl = tmdbImageUrl + actor.Profile_Path; + } + + if (actor.Id > 0) + { + personInfo.SetProviderId(MetadataProviders.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture)); + } + + seriesResult.AddPerson(personInfo); } + } - if (actor.Id > 0) + if (seriesInfo.Credits.Crew != null) + { + var keepTypes = new[] { - personInfo.SetProviderId(MetadataProviders.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture)); - } + PersonType.Director, + PersonType.Writer, + PersonType.Producer + }; + + foreach (var person in seriesInfo.Credits.Crew) + { + // Normalize this + var type = TmdbUtils.MapCrewToPersonType(person); - seriesResult.AddPerson(personInfo); + if (!keepTypes.Contains(type, StringComparer.OrdinalIgnoreCase) && + !keepTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + { + continue; + } + + seriesResult.AddPerson(new PersonInfo { Name = person.Name.Trim(), Role = person.Job, Type = type }); + } } } } -- cgit v1.2.3 From d5ad53e4bb6bc60e9ab9e9f2a71a772bfe67c286 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Sun, 10 May 2020 15:16:19 +0200 Subject: Add Director to role mapper for TMDb --- MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs | 8 +++++++- MediaBrowser.Providers/Tmdb/TmdbUtils.cs | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs index 4697133e6..bee4dba16 100644 --- a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs @@ -313,7 +313,13 @@ namespace MediaBrowser.Providers.Tmdb.TV { foreach (var actor in seriesInfo.Credits.Cast.OrderBy(a => a.Order)) { - var personInfo = new PersonInfo {Name = actor.Name.Trim(), Role = actor.Character, Type = PersonType.Actor, SortOrder = actor.Order}; + var personInfo = new PersonInfo + { + Name = actor.Name.Trim(), + Role = actor.Character, + Type = PersonType.Actor, + SortOrder = actor.Order + }; if (!string.IsNullOrWhiteSpace(actor.Profile_Path)) { diff --git a/MediaBrowser.Providers/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Tmdb/TmdbUtils.cs index 035b99c1a..cf740fe54 100644 --- a/MediaBrowser.Providers/Tmdb/TmdbUtils.cs +++ b/MediaBrowser.Providers/Tmdb/TmdbUtils.cs @@ -14,6 +14,12 @@ namespace MediaBrowser.Providers.Tmdb public static string MapCrewToPersonType(Crew crew) { + if (crew.Department.Equals("production", StringComparison.InvariantCultureIgnoreCase) + && crew.Job.IndexOf("director", StringComparison.InvariantCultureIgnoreCase) != -1) + { + return PersonType.Director; + } + if (crew.Department.Equals("production", StringComparison.InvariantCultureIgnoreCase) && crew.Job.IndexOf("producer", StringComparison.InvariantCultureIgnoreCase) != -1) { -- cgit v1.2.3 From 55cfa96b9f8127c6327702fe98407d771bb987b7 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sun, 10 May 2020 10:54:41 -0400 Subject: Apply review suggestions --- Jellyfin.Data/DbContexts/Jellyfin.cs | 1140 -------------------- Jellyfin.Data/Entities/Artwork.cs | 7 - Jellyfin.Data/Entities/Book.cs | 8 +- Jellyfin.Data/Entities/BookMetadata.cs | 7 +- Jellyfin.Data/Entities/Chapter.cs | 6 - Jellyfin.Data/Entities/Collection.cs | 6 - Jellyfin.Data/Entities/CollectionItem.cs | 6 - Jellyfin.Data/Entities/Company.cs | 5 - Jellyfin.Data/Entities/CompanyMetadata.cs | 9 +- Jellyfin.Data/Entities/CustomItem.cs | 7 +- Jellyfin.Data/Entities/CustomItemMetadata.cs | 10 +- Jellyfin.Data/Entities/Episode.cs | 8 +- Jellyfin.Data/Entities/EpisodeMetadata.cs | 9 +- Jellyfin.Data/Entities/Genre.cs | 6 - Jellyfin.Data/Entities/Group.cs | 5 - Jellyfin.Data/Entities/Library.cs | 6 - Jellyfin.Data/Entities/LibraryItem.cs | 6 - Jellyfin.Data/Entities/LibraryRoot.cs | 6 - Jellyfin.Data/Entities/MediaFile.cs | 5 - Jellyfin.Data/Entities/MediaFileStream.cs | 6 - Jellyfin.Data/Entities/Metadata.cs | 5 - Jellyfin.Data/Entities/MetadataProvider.cs | 6 - Jellyfin.Data/Entities/MetadataProviderId.cs | 6 - Jellyfin.Data/Entities/Movie.cs | 8 +- Jellyfin.Data/Entities/MovieMetadata.cs | 7 +- Jellyfin.Data/Entities/MusicAlbum.cs | 8 +- Jellyfin.Data/Entities/MusicAlbumMetadata.cs | 7 +- Jellyfin.Data/Entities/Permission.cs | 4 - Jellyfin.Data/Entities/Person.cs | 5 - Jellyfin.Data/Entities/PersonRole.cs | 5 - Jellyfin.Data/Entities/Photo.cs | 8 +- Jellyfin.Data/Entities/PhotoMetadata.cs | 9 +- Jellyfin.Data/Entities/Preference.cs | 6 - Jellyfin.Data/Entities/ProviderMapping.cs | 6 - Jellyfin.Data/Entities/Rating.cs | 6 - Jellyfin.Data/Entities/RatingSource.cs | 6 - Jellyfin.Data/Entities/Release.cs | 5 - Jellyfin.Data/Entities/Season.cs | 8 +- Jellyfin.Data/Entities/SeasonMetadata.cs | 8 +- Jellyfin.Data/Entities/Series.cs | 8 +- Jellyfin.Data/Entities/SeriesMetadata.cs | 7 +- Jellyfin.Data/Entities/Track.cs | 8 +- Jellyfin.Data/Entities/TrackMetadata.cs | 9 +- Jellyfin.Data/Entities/User.cs | 5 - Jellyfin.Data/Enums/ArtKind.cs | 4 +- Jellyfin.Data/Enums/MediaFileKind.cs | 4 +- Jellyfin.Data/Enums/PermissionKind.cs | 4 +- Jellyfin.Data/Enums/PersonRoleType.cs | 4 +- Jellyfin.Data/Enums/PreferenceKind.cs | 4 +- Jellyfin.Data/Enums/Weekday.cs | 4 +- Jellyfin.Data/ISavingChanges.cs | 9 + .../Jellyfin.Server.Implementations.csproj | 34 + Jellyfin.Server.Implementations/JellyfinDb.cs | 115 ++ MediaBrowser.sln | 6 + 54 files changed, 189 insertions(+), 1427 deletions(-) delete mode 100644 Jellyfin.Data/DbContexts/Jellyfin.cs create mode 100644 Jellyfin.Data/ISavingChanges.cs create mode 100644 Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj create mode 100644 Jellyfin.Server.Implementations/JellyfinDb.cs diff --git a/Jellyfin.Data/DbContexts/Jellyfin.cs b/Jellyfin.Data/DbContexts/Jellyfin.cs deleted file mode 100644 index fd488ce7d..000000000 --- a/Jellyfin.Data/DbContexts/Jellyfin.cs +++ /dev/null @@ -1,1140 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated from a template. -// -// Manual changes to this file may cause unexpected behavior in your application. -// Manual changes to this file will be overwritten if the code is regenerated. -// -// Produced by Entity Framework Visual Editor -// https://github.com/msawczyn/EFDesigner -// -//------------------------------------------------------------------------------ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.ComponentModel.DataAnnotations.Schema; -using Microsoft.EntityFrameworkCore; - -namespace Jellyfin.Data.DbContexts -{ - /// - public partial class Jellyfin : DbContext - { - #region DbSets - public virtual Microsoft.EntityFrameworkCore.DbSet Artwork { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Books { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet BookMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Chapters { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Collections { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet CollectionItems { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Companies { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet CompanyMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet CustomItems { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet CustomItemMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Episodes { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet EpisodeMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Genres { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Groups { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Libraries { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet LibraryItems { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet LibraryRoot { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet MediaFiles { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet MediaFileStream { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Metadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet MetadataProviders { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet MetadataProviderIds { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Movies { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet MovieMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet MusicAlbums { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet MusicAlbumMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Permissions { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet People { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet PersonRoles { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Photo { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet PhotoMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Preferences { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet ProviderMappings { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Ratings { get; set; } - - /// - /// Repository for global::Jellyfin.Data.Entities.RatingSource - This is the entity to - /// store review ratings, not age ratings - /// - public virtual Microsoft.EntityFrameworkCore.DbSet RatingSources { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Releases { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Seasons { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet SeasonMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Series { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet SeriesMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Tracks { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet TrackMetadata { get; set; } - public virtual Microsoft.EntityFrameworkCore.DbSet Users { get; set; } - #endregion DbSets - - /// - /// Default connection string - /// - public static string ConnectionString { get; set; } = @"Data Source=jellyfin.db"; - - /// - public Jellyfin(DbContextOptions options) : base(options) - { - } - - partial void CustomInit(DbContextOptionsBuilder optionsBuilder); - - /// - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - CustomInit(optionsBuilder); - } - - partial void OnModelCreatingImpl(ModelBuilder modelBuilder); - partial void OnModelCreatedImpl(ModelBuilder modelBuilder); - - /// - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - OnModelCreatingImpl(modelBuilder); - - modelBuilder.HasDefaultSchema("jellyfin"); - - modelBuilder.Entity() - .ToTable("Artwork") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Path) - .HasMaxLength(65535) - .IsRequired() - .HasField("_Path") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Kind) - .IsRequired() - .HasField("_Kind") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity().HasIndex(t => t.Kind); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - - modelBuilder.Entity() - .HasMany(x => x.BookMetadata) - .WithOne() - .HasForeignKey("BookMetadata_BookMetadata_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Releases) - .WithOne() - .HasForeignKey("Release_Releases_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.ISBN) - .HasField("_ISBN") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.Publishers) - .WithOne() - .HasForeignKey("Company_Publishers_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("Chapter") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(1024) - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Language) - .HasMaxLength(3) - .IsRequired() - .HasField("_Language") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.TimeStart) - .IsRequired() - .HasField("_TimeStart") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.TimeEnd) - .HasField("_TimeEnd") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - - modelBuilder.Entity() - .ToTable("Collection") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(1024) - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasMany(x => x.CollectionItem) - .WithOne() - .HasForeignKey("CollectionItem_CollectionItem_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("CollectionItem") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasOne(x => x.LibraryItem) - .WithOne() - .HasForeignKey("LibraryItem_Id") - .IsRequired(); - modelBuilder.Entity() - .HasOne(x => x.Next) - .WithOne() - .HasForeignKey("CollectionItem_Next_Id") - .IsRequired(); - modelBuilder.Entity() - .HasOne(x => x.Previous) - .WithOne() - .HasForeignKey("CollectionItem_Previous_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("Company") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasMany(x => x.CompanyMetadata) - .WithOne() - .HasForeignKey("CompanyMetadata_CompanyMetadata_Id") - .IsRequired(); - modelBuilder.Entity() - .HasOne(x => x.Parent) - .WithOne() - .HasForeignKey("Company_Parent_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.Description) - .HasMaxLength(65535) - .HasField("_Description") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Headquarters) - .HasMaxLength(255) - .HasField("_Headquarters") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Country) - .HasMaxLength(2) - .HasField("_Country") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Homepage) - .HasMaxLength(1024) - .HasField("_Homepage") - .UsePropertyAccessMode(PropertyAccessMode.Property); - - modelBuilder.Entity() - .HasMany(x => x.CustomItemMetadata) - .WithOne() - .HasForeignKey("CustomItemMetadata_CustomItemMetadata_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Releases) - .WithOne() - .HasForeignKey("Release_Releases_Id") - .IsRequired(); - - - modelBuilder.Entity() - .Property(t => t.EpisodeNumber) - .HasField("_EpisodeNumber") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.Releases) - .WithOne() - .HasForeignKey("Release_Releases_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.EpisodeMetadata) - .WithOne() - .HasForeignKey("EpisodeMetadata_EpisodeMetadata_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.Outline) - .HasMaxLength(1024) - .HasField("_Outline") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Plot) - .HasMaxLength(65535) - .HasField("_Plot") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Tagline) - .HasMaxLength(1024) - .HasField("_Tagline") - .UsePropertyAccessMode(PropertyAccessMode.Property); - - modelBuilder.Entity() - .ToTable("Genre") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(255) - .IsRequired() - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity().HasIndex(t => t.Name) - .IsUnique(); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - - modelBuilder.Entity() - .ToTable("Groups") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(255) - .IsRequired(); - modelBuilder.Entity().Property("Timestamp").IsConcurrencyToken(); - modelBuilder.Entity() - .HasMany(x => x.GroupPermissions) - .WithOne() - .HasForeignKey("Permission_GroupPermissions_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.ProviderMappings) - .WithOne() - .HasForeignKey("ProviderMapping_ProviderMappings_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Preferences) - .WithOne() - .HasForeignKey("Preference_Preferences_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("Library") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(1024) - .IsRequired() - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - - modelBuilder.Entity() - .ToTable("LibraryItem") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.UrlId) - .IsRequired() - .HasField("_UrlId") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity().HasIndex(t => t.UrlId) - .IsUnique(); - modelBuilder.Entity() - .Property(t => t.DateAdded) - .IsRequired() - .HasField("_DateAdded") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasOne(x => x.LibraryRoot) - .WithOne() - .HasForeignKey("LibraryRoot_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("LibraryRoot") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Path) - .HasMaxLength(65535) - .IsRequired() - .HasField("_Path") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.NetworkPath) - .HasMaxLength(65535) - .HasField("_NetworkPath") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasOne(x => x.Library) - .WithOne() - .HasForeignKey("Library_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("MediaFile") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Path) - .HasMaxLength(65535) - .IsRequired() - .HasField("_Path") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Kind) - .IsRequired() - .HasField("_Kind") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasMany(x => x.MediaFileStreams) - .WithOne() - .HasForeignKey("MediaFileStream_MediaFileStreams_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("MediaFileStream") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.StreamNumber) - .IsRequired() - .HasField("_StreamNumber") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - - modelBuilder.Entity() - .ToTable("Metadata") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Title) - .HasMaxLength(1024) - .IsRequired() - .HasField("_Title") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.OriginalTitle) - .HasMaxLength(1024) - .HasField("_OriginalTitle") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.SortTitle) - .HasMaxLength(1024) - .HasField("_SortTitle") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Language) - .HasMaxLength(3) - .IsRequired() - .HasField("_Language") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.ReleaseDate) - .HasField("_ReleaseDate") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.DateAdded) - .IsRequired() - .HasField("_DateAdded") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.DateModified) - .IsRequired() - .HasField("_DateModified") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasMany(x => x.PersonRoles) - .WithOne() - .HasForeignKey("PersonRole_PersonRoles_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Genres) - .WithOne() - .HasForeignKey("Genre_Genres_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Artwork) - .WithOne() - .HasForeignKey("Artwork_Artwork_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Ratings) - .WithOne() - .HasForeignKey("Rating_Ratings_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Sources) - .WithOne() - .HasForeignKey("MetadataProviderId_Sources_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("MetadataProvider") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(1024) - .IsRequired() - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - - modelBuilder.Entity() - .ToTable("MetadataProviderId") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.ProviderId) - .HasMaxLength(255) - .IsRequired() - .HasField("_ProviderId") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasOne(x => x.MetadataProvider) - .WithOne() - .HasForeignKey("MetadataProvider_Id") - .IsRequired(); - - modelBuilder.Entity() - .HasMany(x => x.Releases) - .WithOne() - .HasForeignKey("Release_Releases_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.MovieMetadata) - .WithOne() - .HasForeignKey("MovieMetadata_MovieMetadata_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.Outline) - .HasMaxLength(1024) - .HasField("_Outline") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Plot) - .HasMaxLength(65535) - .HasField("_Plot") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Tagline) - .HasMaxLength(1024) - .HasField("_Tagline") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Country) - .HasMaxLength(2) - .HasField("_Country") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.Studios) - .WithOne() - .HasForeignKey("Company_Studios_Id") - .IsRequired(); - - modelBuilder.Entity() - .HasMany(x => x.MusicAlbumMetadata) - .WithOne() - .HasForeignKey("MusicAlbumMetadata_MusicAlbumMetadata_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Tracks) - .WithOne() - .HasForeignKey("Track_Tracks_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.Barcode) - .HasMaxLength(255) - .HasField("_Barcode") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.LabelNumber) - .HasMaxLength(255) - .HasField("_LabelNumber") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Country) - .HasMaxLength(2) - .HasField("_Country") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.Labels) - .WithOne() - .HasForeignKey("Company_Labels_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("Permissions") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Kind) - .IsRequired() - .HasField("_Kind") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Value) - .IsRequired(); - modelBuilder.Entity().Property("Timestamp").IsConcurrencyToken(); - - modelBuilder.Entity() - .ToTable("Person") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.UrlId) - .IsRequired() - .HasField("_UrlId") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(1024) - .IsRequired() - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.SourceId) - .HasMaxLength(255) - .HasField("_SourceId") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.DateAdded) - .IsRequired() - .HasField("_DateAdded") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.DateModified) - .IsRequired() - .HasField("_DateModified") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasMany(x => x.Sources) - .WithOne() - .HasForeignKey("MetadataProviderId_Sources_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("PersonRole") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Role) - .HasMaxLength(1024) - .HasField("_Role") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Type) - .IsRequired() - .HasField("_Type") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasOne(x => x.Person) - .WithOne() - .HasForeignKey("Person_Id") - .IsRequired() - .OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity() - .HasOne(x => x.Artwork) - .WithOne() - .HasForeignKey("Artwork_Artwork_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Sources) - .WithOne() - .HasForeignKey("MetadataProviderId_Sources_Id") - .IsRequired(); - - modelBuilder.Entity() - .HasMany(x => x.PhotoMetadata) - .WithOne() - .HasForeignKey("PhotoMetadata_PhotoMetadata_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Releases) - .WithOne() - .HasForeignKey("Release_Releases_Id") - .IsRequired(); - - - modelBuilder.Entity() - .ToTable("Preferences") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Kind) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.Value) - .HasMaxLength(65535) - .IsRequired(); - modelBuilder.Entity().Property("Timestamp").IsConcurrencyToken(); - - modelBuilder.Entity() - .ToTable("ProviderMappings") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.ProviderName) - .HasMaxLength(255) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.ProviderSecrets) - .HasMaxLength(65535) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.ProviderData) - .HasMaxLength(65535) - .IsRequired(); - modelBuilder.Entity().Property("Timestamp").IsConcurrencyToken(); - - modelBuilder.Entity() - .ToTable("Rating") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Value) - .IsRequired() - .HasField("_Value") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Votes) - .HasField("_Votes") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasOne(x => x.RatingType) - .WithOne() - .HasForeignKey("RatingSource_RatingType_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("RatingType") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(1024) - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.MaximumValue) - .IsRequired() - .HasField("_MaximumValue") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.MinimumValue) - .IsRequired() - .HasField("_MinimumValue") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasOne(x => x.Source) - .WithOne() - .HasForeignKey("MetadataProviderId_Source_Id") - .IsRequired(); - - modelBuilder.Entity() - .ToTable("Release") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .HasField("_Id") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.Name) - .HasMaxLength(1024) - .IsRequired() - .HasField("_Name") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Timestamp) - .IsRequired() - .HasField("_Timestamp") - .UsePropertyAccessMode(PropertyAccessMode.Property) - .IsRowVersion(); - modelBuilder.Entity() - .HasMany(x => x.MediaFiles) - .WithOne() - .HasForeignKey("MediaFile_MediaFiles_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Chapters) - .WithOne() - .HasForeignKey("Chapter_Chapters_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.SeasonNumber) - .HasField("_SeasonNumber") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.SeasonMetadata) - .WithOne() - .HasForeignKey("SeasonMetadata_SeasonMetadata_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Episodes) - .WithOne() - .HasForeignKey("Episode_Episodes_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.Outline) - .HasMaxLength(1024) - .HasField("_Outline") - .UsePropertyAccessMode(PropertyAccessMode.Property); - - modelBuilder.Entity() - .Property(t => t.AirsDayOfWeek) - .HasField("_AirsDayOfWeek") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.AirsTime) - .HasField("_AirsTime") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.FirstAired) - .HasField("_FirstAired") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.SeriesMetadata) - .WithOne() - .HasForeignKey("SeriesMetadata_SeriesMetadata_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Seasons) - .WithOne() - .HasForeignKey("Season_Seasons_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.Outline) - .HasMaxLength(1024) - .HasField("_Outline") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Plot) - .HasMaxLength(65535) - .HasField("_Plot") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Tagline) - .HasMaxLength(1024) - .HasField("_Tagline") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .Property(t => t.Country) - .HasMaxLength(2) - .HasField("_Country") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.Networks) - .WithOne() - .HasForeignKey("Company_Networks_Id") - .IsRequired(); - - modelBuilder.Entity() - .Property(t => t.TrackNumber) - .HasField("_TrackNumber") - .UsePropertyAccessMode(PropertyAccessMode.Property); - modelBuilder.Entity() - .HasMany(x => x.Releases) - .WithOne() - .HasForeignKey("Release_Releases_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.TrackMetadata) - .WithOne() - .HasForeignKey("TrackMetadata_TrackMetadata_Id") - .IsRequired(); - - - modelBuilder.Entity() - .ToTable("Users") - .HasKey(t => t.Id); - modelBuilder.Entity() - .Property(t => t.Id) - .IsRequired() - .ValueGeneratedOnAdd(); - modelBuilder.Entity() - .Property(t => t.LastLoginTimestamp) - .IsRequired() - .IsRowVersion(); - modelBuilder.Entity() - .Property(t => t.Username) - .HasMaxLength(255) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.Password) - .HasMaxLength(65535); - modelBuilder.Entity() - .Property(t => t.MustUpdatePassword) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.AudioLanguagePreference) - .HasMaxLength(255) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.AuthenticationProviderId) - .HasMaxLength(255) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.GroupedFolders) - .HasMaxLength(65535); - modelBuilder.Entity() - .Property(t => t.InvalidLoginAttemptCount) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.LatestItemExcludes) - .HasMaxLength(65535); - modelBuilder.Entity() - .Property(t => t.MyMediaExcludes) - .HasMaxLength(65535); - modelBuilder.Entity() - .Property(t => t.OrderedViews) - .HasMaxLength(65535); - modelBuilder.Entity() - .Property(t => t.SubtitleMode) - .HasMaxLength(255) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.PlayDefaultAudioTrack) - .IsRequired(); - modelBuilder.Entity() - .Property(t => t.SubtitleLanguagePrefernce) - .HasMaxLength(255); - modelBuilder.Entity() - .HasMany(x => x.Groups) - .WithOne() - .HasForeignKey("Group_Groups_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Permissions) - .WithOne() - .HasForeignKey("Permission_Permissions_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.ProviderMappings) - .WithOne() - .HasForeignKey("ProviderMapping_ProviderMappings_Id") - .IsRequired(); - modelBuilder.Entity() - .HasMany(x => x.Preferences) - .WithOne() - .HasForeignKey("Preference_Preferences_Id") - .IsRequired(); - - OnModelCreatedImpl(modelBuilder); - } - } -} diff --git a/Jellyfin.Data/Entities/Artwork.cs b/Jellyfin.Data/Entities/Artwork.cs index da31d686e..bf3029368 100644 --- a/Jellyfin.Data/Entities/Artwork.cs +++ b/Jellyfin.Data/Entities/Artwork.cs @@ -1,15 +1,8 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("Artwork")] public partial class Artwork { partial void Init(); diff --git a/Jellyfin.Data/Entities/Book.cs b/Jellyfin.Data/Entities/Book.cs index 7dda26f41..42d24e31d 100644 --- a/Jellyfin.Data/Entities/Book.cs +++ b/Jellyfin.Data/Entities/Book.cs @@ -1,15 +1,9 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("Book")] public partial class Book : LibraryItem { partial void Init(); @@ -17,7 +11,7 @@ namespace Jellyfin.Data.Entities /// /// Default constructor. Protected due to required properties, but present because EF needs it. /// - protected Book() : base() + protected Book() { BookMetadata = new HashSet(); Releases = new HashSet(); diff --git a/Jellyfin.Data/Entities/BookMetadata.cs b/Jellyfin.Data/Entities/BookMetadata.cs index 8afd37163..d52fe7605 100644 --- a/Jellyfin.Data/Entities/BookMetadata.cs +++ b/Jellyfin.Data/Entities/BookMetadata.cs @@ -1,11 +1,6 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { @@ -16,7 +11,7 @@ namespace Jellyfin.Data.Entities /// /// Default constructor. Protected due to required properties, but present because EF needs it. /// - protected BookMetadata() : base() + protected BookMetadata() { Publishers = new HashSet(); diff --git a/Jellyfin.Data/Entities/Chapter.cs b/Jellyfin.Data/Entities/Chapter.cs index 1ee6a9c07..d48cb9b62 100644 --- a/Jellyfin.Data/Entities/Chapter.cs +++ b/Jellyfin.Data/Entities/Chapter.cs @@ -1,15 +1,9 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("Chapter")] public partial class Chapter { partial void Init(); diff --git a/Jellyfin.Data/Entities/Collection.cs b/Jellyfin.Data/Entities/Collection.cs index d3ccb13f5..e2fa3a5bd 100644 --- a/Jellyfin.Data/Entities/Collection.cs +++ b/Jellyfin.Data/Entities/Collection.cs @@ -1,15 +1,9 @@ -using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("Collection")] public partial class Collection { partial void Init(); diff --git a/Jellyfin.Data/Entities/CollectionItem.cs b/Jellyfin.Data/Entities/CollectionItem.cs index f40158b20..4a3d06639 100644 --- a/Jellyfin.Data/Entities/CollectionItem.cs +++ b/Jellyfin.Data/Entities/CollectionItem.cs @@ -1,15 +1,9 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("CollectionItem")] public partial class CollectionItem { partial void Init(); diff --git a/Jellyfin.Data/Entities/Company.cs b/Jellyfin.Data/Entities/Company.cs index 5b8a21423..0650271c6 100644 --- a/Jellyfin.Data/Entities/Company.cs +++ b/Jellyfin.Data/Entities/Company.cs @@ -1,15 +1,10 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("Company")] public partial class Company { partial void Init(); diff --git a/Jellyfin.Data/Entities/CompanyMetadata.cs b/Jellyfin.Data/Entities/CompanyMetadata.cs index 18357b326..b3ec9c1a7 100644 --- a/Jellyfin.Data/Entities/CompanyMetadata.cs +++ b/Jellyfin.Data/Entities/CompanyMetadata.cs @@ -1,15 +1,8 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("CompanyMetadata")] public partial class CompanyMetadata : Metadata { partial void Init(); @@ -17,7 +10,7 @@ namespace Jellyfin.Data.Entities /// /// Default constructor. Protected due to required properties, but present because EF needs it. /// - protected CompanyMetadata() : base() + protected CompanyMetadata() { Init(); } diff --git a/Jellyfin.Data/Entities/CustomItem.cs b/Jellyfin.Data/Entities/CustomItem.cs index 29f4b62a6..2006717bf 100644 --- a/Jellyfin.Data/Entities/CustomItem.cs +++ b/Jellyfin.Data/Entities/CustomItem.cs @@ -1,11 +1,6 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { @@ -16,7 +11,7 @@ namespace Jellyfin.Data.Entities /// /// Default constructor. Protected due to required properties, but present because EF needs it. /// - protected CustomItem() : base() + protected CustomItem() { CustomItemMetadata = new HashSet(); Releases = new HashSet(); diff --git a/Jellyfin.Data/Entities/CustomItemMetadata.cs b/Jellyfin.Data/Entities/CustomItemMetadata.cs index 8fbccfdd8..e09e4467a 100644 --- a/Jellyfin.Data/Entities/CustomItemMetadata.cs +++ b/Jellyfin.Data/Entities/CustomItemMetadata.cs @@ -1,15 +1,7 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("CustomItemMetadata")] public partial class CustomItemMetadata : Metadata { partial void Init(); @@ -17,7 +9,7 @@ namespace Jellyfin.Data.Entities /// /// Default constructor. Protected due to required properties, but present because EF needs it. /// - protected CustomItemMetadata() : base() + protected CustomItemMetadata() { Init(); } diff --git a/Jellyfin.Data/Entities/Episode.cs b/Jellyfin.Data/Entities/Episode.cs index be358a0fd..6f6baa14d 100644 --- a/Jellyfin.Data/Entities/Episode.cs +++ b/Jellyfin.Data/Entities/Episode.cs @@ -1,15 +1,9 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("Episode")] public partial class Episode : LibraryItem { partial void Init(); @@ -17,7 +11,7 @@ namespace Jellyfin.Data.Entities /// /// Default constructor. Protected due to required properties, but present because EF needs it. /// - protected Episode() : base() + protected Episode() { // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem. // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. diff --git a/Jellyfin.Data/Entities/EpisodeMetadata.cs b/Jellyfin.Data/Entities/EpisodeMetadata.cs index a1f4adf7b..e5431bf22 100644 --- a/Jellyfin.Data/Entities/EpisodeMetadata.cs +++ b/Jellyfin.Data/Entities/EpisodeMetadata.cs @@ -1,15 +1,8 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("EpisodeMetadata")] public partial class EpisodeMetadata : Metadata { partial void Init(); @@ -17,7 +10,7 @@ namespace Jellyfin.Data.Entities /// /// Default constructor. Protected due to required properties, but present because EF needs it. /// - protected EpisodeMetadata() : base() + protected EpisodeMetadata() { Init(); } diff --git a/Jellyfin.Data/Entities/Genre.cs b/Jellyfin.Data/Entities/Genre.cs index d38265d80..38f289a8e 100644 --- a/Jellyfin.Data/Entities/Genre.cs +++ b/Jellyfin.Data/Entities/Genre.cs @@ -1,15 +1,9 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("Genre")] public partial class Genre { partial void Init(); diff --git a/Jellyfin.Data/Entities/Group.cs b/Jellyfin.Data/Entities/Group.cs index 4b58120fc..54f9f4905 100644 --- a/Jellyfin.Data/Entities/Group.cs +++ b/Jellyfin.Data/Entities/Group.cs @@ -1,15 +1,10 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("Group")] public partial class Group { partial void Init(); diff --git a/Jellyfin.Data/Entities/Library.cs b/Jellyfin.Data/Entities/Library.cs index f3faa8699..c11c09e91 100644 --- a/Jellyfin.Data/Entities/Library.cs +++ b/Jellyfin.Data/Entities/Library.cs @@ -1,15 +1,9 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("Library")] public partial class Library { partial void Init(); diff --git a/Jellyfin.Data/Entities/LibraryItem.cs b/Jellyfin.Data/Entities/LibraryItem.cs index 29547562b..af6c640b9 100644 --- a/Jellyfin.Data/Entities/LibraryItem.cs +++ b/Jellyfin.Data/Entities/LibraryItem.cs @@ -1,15 +1,9 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("LibraryItem")] public abstract partial class LibraryItem { partial void Init(); diff --git a/Jellyfin.Data/Entities/LibraryRoot.cs b/Jellyfin.Data/Entities/LibraryRoot.cs index 932e3edb8..bbc23e1c9 100644 --- a/Jellyfin.Data/Entities/LibraryRoot.cs +++ b/Jellyfin.Data/Entities/LibraryRoot.cs @@ -1,15 +1,9 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("LibraryRoot")] public partial class LibraryRoot { partial void Init(); diff --git a/Jellyfin.Data/Entities/MediaFile.cs b/Jellyfin.Data/Entities/MediaFile.cs index dbb65a6f7..719539e5c 100644 --- a/Jellyfin.Data/Entities/MediaFile.cs +++ b/Jellyfin.Data/Entities/MediaFile.cs @@ -1,15 +1,10 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("MediaFile")] public partial class MediaFile { partial void Init(); diff --git a/Jellyfin.Data/Entities/MediaFileStream.cs b/Jellyfin.Data/Entities/MediaFileStream.cs index 3ce18b8d7..7b3399731 100644 --- a/Jellyfin.Data/Entities/MediaFileStream.cs +++ b/Jellyfin.Data/Entities/MediaFileStream.cs @@ -1,15 +1,9 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("MediaFileStream")] public partial class MediaFileStream { partial void Init(); diff --git a/Jellyfin.Data/Entities/Metadata.cs b/Jellyfin.Data/Entities/Metadata.cs index 5cba24ee3..467ee6822 100644 --- a/Jellyfin.Data/Entities/Metadata.cs +++ b/Jellyfin.Data/Entities/Metadata.cs @@ -1,15 +1,10 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("Metadata")] public abstract partial class Metadata { partial void Init(); diff --git a/Jellyfin.Data/Entities/MetadataProvider.cs b/Jellyfin.Data/Entities/MetadataProvider.cs index bc6e04277..4e4f107fb 100644 --- a/Jellyfin.Data/Entities/MetadataProvider.cs +++ b/Jellyfin.Data/Entities/MetadataProvider.cs @@ -1,15 +1,9 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("MetadataProvider")] public partial class MetadataProvider { partial void Init(); diff --git a/Jellyfin.Data/Entities/MetadataProviderId.cs b/Jellyfin.Data/Entities/MetadataProviderId.cs index d381856f3..926f223de 100644 --- a/Jellyfin.Data/Entities/MetadataProviderId.cs +++ b/Jellyfin.Data/Entities/MetadataProviderId.cs @@ -1,15 +1,9 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("MetadataProviderId")] public partial class MetadataProviderId { partial void Init(); diff --git a/Jellyfin.Data/Entities/Movie.cs b/Jellyfin.Data/Entities/Movie.cs index 23a340b1b..b359b42fc 100644 --- a/Jellyfin.Data/Entities/Movie.cs +++ b/Jellyfin.Data/Entities/Movie.cs @@ -1,15 +1,9 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("Movie")] public partial class Movie : LibraryItem { partial void Init(); @@ -17,7 +11,7 @@ namespace Jellyfin.Data.Entities /// /// Default constructor. Protected due to required properties, but present because EF needs it. /// - protected Movie() : base() + protected Movie() { Releases = new HashSet(); MovieMetadata = new HashSet(); diff --git a/Jellyfin.Data/Entities/MovieMetadata.cs b/Jellyfin.Data/Entities/MovieMetadata.cs index 090761877..319ae94e5 100644 --- a/Jellyfin.Data/Entities/MovieMetadata.cs +++ b/Jellyfin.Data/Entities/MovieMetadata.cs @@ -1,15 +1,10 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("MovieMetadata")] public partial class MovieMetadata : Metadata { partial void Init(); @@ -17,7 +12,7 @@ namespace Jellyfin.Data.Entities /// /// Default constructor. Protected due to required properties, but present because EF needs it. /// - protected MovieMetadata() : base() + protected MovieMetadata() { Studios = new HashSet(); diff --git a/Jellyfin.Data/Entities/MusicAlbum.cs b/Jellyfin.Data/Entities/MusicAlbum.cs index fc9c12286..00cb8fe00 100644 --- a/Jellyfin.Data/Entities/MusicAlbum.cs +++ b/Jellyfin.Data/Entities/MusicAlbum.cs @@ -1,15 +1,9 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("MusicAlbum")] public partial class MusicAlbum : LibraryItem { partial void Init(); @@ -17,7 +11,7 @@ namespace Jellyfin.Data.Entities /// /// Default constructor. Protected due to required properties, but present because EF needs it. /// - protected MusicAlbum() : base() + protected MusicAlbum() { MusicAlbumMetadata = new HashSet(); Tracks = new HashSet(); diff --git a/Jellyfin.Data/Entities/MusicAlbumMetadata.cs b/Jellyfin.Data/Entities/MusicAlbumMetadata.cs index 4bfe780d1..b52ca6564 100644 --- a/Jellyfin.Data/Entities/MusicAlbumMetadata.cs +++ b/Jellyfin.Data/Entities/MusicAlbumMetadata.cs @@ -1,15 +1,10 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("MusicAlbumMetadata")] public partial class MusicAlbumMetadata : Metadata { partial void Init(); @@ -17,7 +12,7 @@ namespace Jellyfin.Data.Entities /// /// Default constructor. Protected due to required properties, but present because EF needs it. /// - protected MusicAlbumMetadata() : base() + protected MusicAlbumMetadata() { Labels = new HashSet(); diff --git a/Jellyfin.Data/Entities/Permission.cs b/Jellyfin.Data/Entities/Permission.cs index 29ba9e1a4..0b5b52cbd 100644 --- a/Jellyfin.Data/Entities/Permission.cs +++ b/Jellyfin.Data/Entities/Permission.cs @@ -1,15 +1,11 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("Permission")] public partial class Permission { partial void Init(); diff --git a/Jellyfin.Data/Entities/Person.cs b/Jellyfin.Data/Entities/Person.cs index 6a4ad5285..d893b7e39 100644 --- a/Jellyfin.Data/Entities/Person.cs +++ b/Jellyfin.Data/Entities/Person.cs @@ -1,15 +1,10 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("Person")] public partial class Person { partial void Init(); diff --git a/Jellyfin.Data/Entities/PersonRole.cs b/Jellyfin.Data/Entities/PersonRole.cs index 0c6cb2c6e..9bd12c7fb 100644 --- a/Jellyfin.Data/Entities/PersonRole.cs +++ b/Jellyfin.Data/Entities/PersonRole.cs @@ -1,15 +1,10 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("PersonRole")] public partial class PersonRole { partial void Init(); diff --git a/Jellyfin.Data/Entities/Photo.cs b/Jellyfin.Data/Entities/Photo.cs index 89f976444..7abe62891 100644 --- a/Jellyfin.Data/Entities/Photo.cs +++ b/Jellyfin.Data/Entities/Photo.cs @@ -1,15 +1,9 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("Photo")] public partial class Photo : LibraryItem { partial void Init(); @@ -17,7 +11,7 @@ namespace Jellyfin.Data.Entities /// /// Default constructor. Protected due to required properties, but present because EF needs it. /// - protected Photo() : base() + protected Photo() { PhotoMetadata = new HashSet(); Releases = new HashSet(); diff --git a/Jellyfin.Data/Entities/PhotoMetadata.cs b/Jellyfin.Data/Entities/PhotoMetadata.cs index b3f796839..c5502f707 100644 --- a/Jellyfin.Data/Entities/PhotoMetadata.cs +++ b/Jellyfin.Data/Entities/PhotoMetadata.cs @@ -1,15 +1,8 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("PhotoMetadata")] public partial class PhotoMetadata : Metadata { partial void Init(); @@ -17,7 +10,7 @@ namespace Jellyfin.Data.Entities /// /// Default constructor. Protected due to required properties, but present because EF needs it. /// - protected PhotoMetadata() : base() + protected PhotoMetadata() { Init(); } diff --git a/Jellyfin.Data/Entities/Preference.cs b/Jellyfin.Data/Entities/Preference.cs index 8b3ddb568..505f52e6b 100644 --- a/Jellyfin.Data/Entities/Preference.cs +++ b/Jellyfin.Data/Entities/Preference.cs @@ -1,15 +1,9 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("Preference")] public partial class Preference { partial void Init(); diff --git a/Jellyfin.Data/Entities/ProviderMapping.cs b/Jellyfin.Data/Entities/ProviderMapping.cs index 0eb098a8f..6197bd97b 100644 --- a/Jellyfin.Data/Entities/ProviderMapping.cs +++ b/Jellyfin.Data/Entities/ProviderMapping.cs @@ -1,15 +1,9 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("ProviderMapping")] public partial class ProviderMapping { partial void Init(); diff --git a/Jellyfin.Data/Entities/Rating.cs b/Jellyfin.Data/Entities/Rating.cs index 46e8c3f11..f70ea8b33 100644 --- a/Jellyfin.Data/Entities/Rating.cs +++ b/Jellyfin.Data/Entities/Rating.cs @@ -1,15 +1,9 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("Rating")] public partial class Rating { partial void Init(); diff --git a/Jellyfin.Data/Entities/RatingSource.cs b/Jellyfin.Data/Entities/RatingSource.cs index 7e60fac43..070f1ae27 100644 --- a/Jellyfin.Data/Entities/RatingSource.cs +++ b/Jellyfin.Data/Entities/RatingSource.cs @@ -1,18 +1,12 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { /// /// This is the entity to store review ratings, not age ratings /// - [Table("RatingSource")] public partial class RatingSource { partial void Init(); diff --git a/Jellyfin.Data/Entities/Release.cs b/Jellyfin.Data/Entities/Release.cs index 91dd35a7f..d1928fcf7 100644 --- a/Jellyfin.Data/Entities/Release.cs +++ b/Jellyfin.Data/Entities/Release.cs @@ -1,15 +1,10 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("Release")] public partial class Release { partial void Init(); diff --git a/Jellyfin.Data/Entities/Season.cs b/Jellyfin.Data/Entities/Season.cs index 3928a4ba6..96e89cde0 100644 --- a/Jellyfin.Data/Entities/Season.cs +++ b/Jellyfin.Data/Entities/Season.cs @@ -1,15 +1,9 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("Season")] public partial class Season : LibraryItem { partial void Init(); @@ -17,7 +11,7 @@ namespace Jellyfin.Data.Entities /// /// Default constructor. Protected due to required properties, but present because EF needs it. /// - protected Season() : base() + protected Season() { // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem. // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. diff --git a/Jellyfin.Data/Entities/SeasonMetadata.cs b/Jellyfin.Data/Entities/SeasonMetadata.cs index f0e669a49..64ecbfbfa 100644 --- a/Jellyfin.Data/Entities/SeasonMetadata.cs +++ b/Jellyfin.Data/Entities/SeasonMetadata.cs @@ -1,15 +1,9 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("SeasonMetadata")] public partial class SeasonMetadata : Metadata { partial void Init(); @@ -17,7 +11,7 @@ namespace Jellyfin.Data.Entities /// /// Default constructor. Protected due to required properties, but present because EF needs it. /// - protected SeasonMetadata() : base() + protected SeasonMetadata() { Init(); } diff --git a/Jellyfin.Data/Entities/Series.cs b/Jellyfin.Data/Entities/Series.cs index fecc229af..097b9958e 100644 --- a/Jellyfin.Data/Entities/Series.cs +++ b/Jellyfin.Data/Entities/Series.cs @@ -1,15 +1,9 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("Series")] public partial class Series : LibraryItem { partial void Init(); @@ -17,7 +11,7 @@ namespace Jellyfin.Data.Entities /// /// Default constructor. Protected due to required properties, but present because EF needs it. /// - protected Series() : base() + protected Series() { SeriesMetadata = new HashSet(); Seasons = new HashSet(); diff --git a/Jellyfin.Data/Entities/SeriesMetadata.cs b/Jellyfin.Data/Entities/SeriesMetadata.cs index 15818f941..52691783f 100644 --- a/Jellyfin.Data/Entities/SeriesMetadata.cs +++ b/Jellyfin.Data/Entities/SeriesMetadata.cs @@ -1,15 +1,10 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("SeriesMetadata")] public partial class SeriesMetadata : Metadata { partial void Init(); @@ -17,7 +12,7 @@ namespace Jellyfin.Data.Entities /// /// Default constructor. Protected due to required properties, but present because EF needs it. /// - protected SeriesMetadata() : base() + protected SeriesMetadata() { Networks = new HashSet(); diff --git a/Jellyfin.Data/Entities/Track.cs b/Jellyfin.Data/Entities/Track.cs index 50ee43042..079d73d2b 100644 --- a/Jellyfin.Data/Entities/Track.cs +++ b/Jellyfin.Data/Entities/Track.cs @@ -1,15 +1,9 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("Track")] public partial class Track : LibraryItem { partial void Init(); @@ -17,7 +11,7 @@ namespace Jellyfin.Data.Entities /// /// Default constructor. Protected due to required properties, but present because EF needs it. /// - protected Track() : base() + protected Track() { // NOTE: This class has one-to-one associations with LibraryRoot, LibraryItem and CollectionItem. // One-to-one associations are not validated in constructors since this causes a scenario where each one must be constructed before the other. diff --git a/Jellyfin.Data/Entities/TrackMetadata.cs b/Jellyfin.Data/Entities/TrackMetadata.cs index 84679ebb5..86c9161f6 100644 --- a/Jellyfin.Data/Entities/TrackMetadata.cs +++ b/Jellyfin.Data/Entities/TrackMetadata.cs @@ -1,15 +1,8 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("TrackMetadata")] public partial class TrackMetadata : Metadata { partial void Init(); @@ -17,7 +10,7 @@ namespace Jellyfin.Data.Entities /// /// Default constructor. Protected due to required properties, but present because EF needs it. /// - protected TrackMetadata() : base() + protected TrackMetadata() { Init(); } diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index 715969dbf..a81d5215b 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -1,15 +1,10 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; namespace Jellyfin.Data.Entities { - [Table("User")] public partial class User { partial void Init(); diff --git a/Jellyfin.Data/Enums/ArtKind.cs b/Jellyfin.Data/Enums/ArtKind.cs index 546e1533c..6b69d68b2 100644 --- a/Jellyfin.Data/Enums/ArtKind.cs +++ b/Jellyfin.Data/Enums/ArtKind.cs @@ -1,8 +1,6 @@ -using System; - namespace Jellyfin.Data.Enums { - public enum ArtKind : Int32 + public enum ArtKind { Other, Poster, diff --git a/Jellyfin.Data/Enums/MediaFileKind.cs b/Jellyfin.Data/Enums/MediaFileKind.cs index d24920228..12f48c558 100644 --- a/Jellyfin.Data/Enums/MediaFileKind.cs +++ b/Jellyfin.Data/Enums/MediaFileKind.cs @@ -1,8 +1,6 @@ -using System; - namespace Jellyfin.Data.Enums { - public enum MediaFileKind : Int32 + public enum MediaFileKind { Main, Sidecar, diff --git a/Jellyfin.Data/Enums/PermissionKind.cs b/Jellyfin.Data/Enums/PermissionKind.cs index 4447fdb77..1506471e8 100644 --- a/Jellyfin.Data/Enums/PermissionKind.cs +++ b/Jellyfin.Data/Enums/PermissionKind.cs @@ -1,8 +1,6 @@ -using System; - namespace Jellyfin.Data.Enums { - public enum PermissionKind : Int32 + public enum PermissionKind { IsAdministrator, IsHidden, diff --git a/Jellyfin.Data/Enums/PersonRoleType.cs b/Jellyfin.Data/Enums/PersonRoleType.cs index 5621ffa4d..6e52f2c85 100644 --- a/Jellyfin.Data/Enums/PersonRoleType.cs +++ b/Jellyfin.Data/Enums/PersonRoleType.cs @@ -1,8 +1,6 @@ -using System; - namespace Jellyfin.Data.Enums { - public enum PersonRoleType : Int32 + public enum PersonRoleType { Other, Director, diff --git a/Jellyfin.Data/Enums/PreferenceKind.cs b/Jellyfin.Data/Enums/PreferenceKind.cs index e66a51cae..cd2cb791a 100644 --- a/Jellyfin.Data/Enums/PreferenceKind.cs +++ b/Jellyfin.Data/Enums/PreferenceKind.cs @@ -1,8 +1,6 @@ -using System; - namespace Jellyfin.Data.Enums { - public enum PreferenceKind : Int32 + public enum PreferenceKind { MaxParentalRating, BlockedTags, diff --git a/Jellyfin.Data/Enums/Weekday.cs b/Jellyfin.Data/Enums/Weekday.cs index 58523a6c7..b80a03a33 100644 --- a/Jellyfin.Data/Enums/Weekday.cs +++ b/Jellyfin.Data/Enums/Weekday.cs @@ -1,8 +1,6 @@ -using System; - namespace Jellyfin.Data.Enums { - public enum Weekday : Int32 + public enum Weekday { Sunday, Monday, diff --git a/Jellyfin.Data/ISavingChanges.cs b/Jellyfin.Data/ISavingChanges.cs new file mode 100644 index 000000000..f392dae6a --- /dev/null +++ b/Jellyfin.Data/ISavingChanges.cs @@ -0,0 +1,9 @@ +#pragma warning disable CS1591 + +namespace Jellyfin.Data +{ + public interface ISavingChanges + { + void OnSavingChanges(); + } +} diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj new file mode 100644 index 000000000..a31f28f64 --- /dev/null +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -0,0 +1,34 @@ + + + + netcoreapp3.1 + false + true + true + + + + ../jellyfin.ruleset + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs new file mode 100644 index 000000000..76343edf9 --- /dev/null +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -0,0 +1,115 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1201 // Constuctors should not follow properties +#pragma warning disable SA1516 // Elements should be followed by a blank line +#pragma warning disable SA1623 // Property's documentation should begin with gets or sets +#pragma warning disable SA1629 // Documentation should end with a period +#pragma warning disable SA1648 // Inheritdoc should be used with inheriting class + +using System.Linq; +using Jellyfin.Data; +using Jellyfin.Data.Entities; +using Microsoft.EntityFrameworkCore; + +namespace Jellyfin.Server.Implementations +{ + /// + public partial class JellyfinDb : DbContext + { + /*public virtual DbSet Artwork { get; set; } + public virtual DbSet Books { get; set; } + public virtual DbSet BookMetadata { get; set; } + public virtual DbSet Chapters { get; set; } + public virtual DbSet Collections { get; set; } + public virtual DbSet CollectionItems { get; set; } + public virtual DbSet Companies { get; set; } + public virtual DbSet CompanyMetadata { get; set; } + public virtual DbSet CustomItems { get; set; } + public virtual DbSet CustomItemMetadata { get; set; } + 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; } + public virtual DbSet MediaFiles { get; set; } + public virtual DbSet MediaFileStream { get; set; } + public virtual DbSet Metadata { get; set; } + public virtual DbSet MetadataProviders { get; set; } + public virtual DbSet MetadataProviderIds { get; set; } + public virtual DbSet Movies { get; set; } + 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; } + /// + /// Repository for global::Jellyfin.Data.Entities.RatingSource - This is the entity to + /// store review ratings, not age ratings + /// + public virtual DbSet RatingSources { get; set; } + public virtual DbSet Releases { get; set; } + public virtual DbSet Seasons { get; set; } + public virtual DbSet SeasonMetadata { get; set; } + public virtual DbSet Series { get; set; } + public virtual DbSet SeriesMetadata { get; set; } + public virtual DbSet Tracks { get; set; } + public virtual DbSet TrackMetadata { get; set; } + public virtual DbSet Users { get; set; } */ + + /// + /// Gets or sets the default connection string. + /// + public static string ConnectionString { get; set; } = @"Data Source=jellyfin.db"; + + /// + public JellyfinDb(DbContextOptions options) : base(options) + { + } + + partial void CustomInit(DbContextOptionsBuilder optionsBuilder); + + /// + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + CustomInit(optionsBuilder); + } + + partial void OnModelCreatingImpl(ModelBuilder modelBuilder); + partial void OnModelCreatedImpl(ModelBuilder modelBuilder); + + /// + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + OnModelCreatingImpl(modelBuilder); + + modelBuilder.HasDefaultSchema("jellyfin"); + + /*modelBuilder.Entity().HasIndex(t => t.Kind); + modelBuilder.Entity().HasIndex(t => t.Name) + .IsUnique(); + modelBuilder.Entity().HasIndex(t => t.UrlId) + .IsUnique();*/ + + OnModelCreatedImpl(modelBuilder); + } + + public override int SaveChanges() + { + foreach (var entity in ChangeTracker.Entries().Where(e => e.State == EntityState.Modified)) + { + var saveEntity = entity.Entity as ISavingChanges; + saveEntity.OnSavingChanges(); + } + + return base.SaveChanges(); + } + } +} diff --git a/MediaBrowser.sln b/MediaBrowser.sln index a1dbe8047..a514d2e2b 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -64,6 +64,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Controller.Tests", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Data", "Jellyfin.Data\Jellyfin.Data.csproj", "{F03299F2-469F-40EF-A655-3766F97A5702}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Implementations", "Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj", "{22C7DA3A-94F2-4E86-9CE6-86AB02B4F843}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -182,6 +184,10 @@ Global {F03299F2-469F-40EF-A655-3766F97A5702}.Debug|Any CPU.Build.0 = Debug|Any CPU {F03299F2-469F-40EF-A655-3766F97A5702}.Release|Any CPU.ActiveCfg = Release|Any CPU {F03299F2-469F-40EF-A655-3766F97A5702}.Release|Any CPU.Build.0 = Release|Any CPU + {22C7DA3A-94F2-4E86-9CE6-86AB02B4F843}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22C7DA3A-94F2-4E86-9CE6-86AB02B4F843}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22C7DA3A-94F2-4E86-9CE6-86AB02B4F843}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22C7DA3A-94F2-4E86-9CE6-86AB02B4F843}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE -- cgit v1.2.3 From 43c22a58229892836df645031fb570f37994e19e Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 10 May 2020 14:36:11 -0400 Subject: Add GetLoopbackHttpApiUrl() helper method to replace forceHttps functionality Also refactor to use return a Uri instead of a string and use UriBuilder under the hood --- Emby.Server.Implementations/ApplicationHost.cs | 30 +++++++++++----------- .../Browser/BrowserLauncher.cs | 2 +- .../LiveTv/EmbyTV/EmbyTV.cs | 2 +- .../TunerHosts/HdHomerun/HdHomerunUdpStream.cs | 2 +- .../LiveTv/TunerHosts/SharedHttpStream.cs | 2 +- MediaBrowser.Controller/IServerApplicationHost.cs | 25 ++++++++++++++---- 6 files changed, 39 insertions(+), 24 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 8f20a4921..2201c3cfc 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1229,28 +1229,28 @@ namespace Emby.Server.Implementations str.CopyTo(span.Slice(1)); span[^1] = ']'; - return GetLocalApiUrl(span); + return GetLocalApiUrl(span).ToString(); } - return GetLocalApiUrl(ipAddress.ToString()); + return GetLocalApiUrl(ipAddress.ToString()).ToString(); } /// - public string GetLocalApiUrl(ReadOnlySpan host) + public Uri GetLoopbackHttpApiUrl() { - var url = new StringBuilder(64); - url.Append(ListenWithHttps ? "https://" : "http://") - .Append(host) - .Append(':') - .Append(ListenWithHttps ? HttpsPort : HttpPort); - - string baseUrl = ServerConfigurationManager.Configuration.BaseUrl; - if (baseUrl.Length != 0) - { - url.Append(baseUrl); - } + return GetLocalApiUrl("127.0.0.1", Uri.UriSchemeHttp, HttpPort); + } - return url.ToString(); + /// + public Uri GetLocalApiUrl(ReadOnlySpan host, string scheme = null, int? port = null) + { + return new UriBuilder + { + Scheme = scheme ?? (ListenWithHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp), + Host = host.ToString(), + Port = port ?? (ListenWithHttps ? HttpsPort : HttpPort), + Path = ServerConfigurationManager.Configuration.BaseUrl + }.Uri; } public Task> GetLocalIpAddresses(CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/Browser/BrowserLauncher.cs b/Emby.Server.Implementations/Browser/BrowserLauncher.cs index 96096e142..ccb733b3c 100644 --- a/Emby.Server.Implementations/Browser/BrowserLauncher.cs +++ b/Emby.Server.Implementations/Browser/BrowserLauncher.cs @@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Browser { try { - string baseUrl = appHost.GetLocalApiUrl("localhost"); + Uri baseUrl = appHost.GetLocalApiUrl("localhost"); appHost.LaunchUrl(baseUrl + url); } catch (Exception ex) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 900f12062..3efe1ee25 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1059,7 +1059,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { var stream = new MediaSourceInfo { - EncoderPath = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveRecordings/" + info.Id + "/stream", + EncoderPath = _appHost.GetLoopbackHttpApiUrl() + "/LiveTv/LiveRecordings/" + info.Id + "/stream", EncoderProtocol = MediaProtocol.Http, Path = info.Path, Protocol = MediaProtocol.File, diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 03ee5bfb6..82b1f3cf1 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -121,7 +121,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun //OpenedMediaSource.Path = tempFile; //OpenedMediaSource.ReadAtNativeFramerate = true; - MediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"; + MediaSource.Path = _appHost.GetLoopbackHttpApiUrl() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"; MediaSource.Protocol = MediaProtocol.Http; //OpenedMediaSource.SupportsDirectPlay = false; //OpenedMediaSource.SupportsDirectStream = true; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index d63588bbd..083fcd029 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -106,7 +106,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts //OpenedMediaSource.Path = tempFile; //OpenedMediaSource.ReadAtNativeFramerate = true; - MediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"; + MediaSource.Path = _appHost.GetLoopbackHttpApiUrl() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"; MediaSource.Protocol = MediaProtocol.Http; //OpenedMediaSource.Path = TempFilePath; diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 8537e4180..0028bd689 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -65,26 +65,41 @@ namespace MediaBrowser.Controller /// /// Gets a local (LAN) URL that can be used to access the API. The hostname used is the first valid configured - /// IP address that can be found via . + /// IP address that can be found via . HTTPS will be preferred when available. /// /// A cancellation token that can be used to cancel the task. /// The server URL. Task GetLocalApiUrl(CancellationToken cancellationToken); /// - /// Gets a local (LAN) URL that can be used to access the API. + /// Gets a local (LAN) URL that can be used to access the API using the loop-back IP address (127.0.0.1) + /// over HTTP (not HTTPS). /// - /// The hostname to use in the URL. /// The API URL. - string GetLocalApiUrl(ReadOnlySpan hostname); + public Uri GetLoopbackHttpApiUrl(); /// - /// Gets a local (LAN) URL that can be used to access the API. + /// Gets a local (LAN) URL that can be used to access the API. HTTPS will be preferred when available. /// /// The IP address to use as the hostname in the URL. /// The API URL. string GetLocalApiUrl(IPAddress address); + /// + /// Gets a local (LAN) URL that can be used to access the API. + /// + /// The hostname to use in the URL. + /// + /// The scheme to use for the URL. If null, the scheme will be selected automatically, + /// preferring HTTPS, if available. + /// + /// + /// The port to use for the URL. If null, the port will be selected automatically, + /// preferring the HTTPS port, if available. + /// + /// The API URL. + Uri GetLocalApiUrl(ReadOnlySpan hostname, string scheme = null, int? port = null); + /// /// Open a URL in an external browser window. /// -- cgit v1.2.3 From 3abf870c1e321dbeb484e4e255000a27760d7bc9 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 10 May 2020 18:07:56 -0400 Subject: Do not include a double slash in URLs when a base URL is not set --- Emby.Server.Implementations/ApplicationHost.cs | 15 ++++++++------- Emby.Server.Implementations/Browser/BrowserLauncher.cs | 10 +++++----- MediaBrowser.Controller/IServerApplicationHost.cs | 4 ++-- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 10a7e879e..693b049cd 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1236,28 +1236,30 @@ namespace Emby.Server.Implementations str.CopyTo(span.Slice(1)); span[^1] = ']'; - return GetLocalApiUrl(span).ToString(); + return GetLocalApiUrl(span); } - return GetLocalApiUrl(ipAddress.ToString()).ToString(); + return GetLocalApiUrl(ipAddress.ToString()); } /// - public Uri GetLoopbackHttpApiUrl() + public string GetLoopbackHttpApiUrl() { return GetLocalApiUrl("127.0.0.1", Uri.UriSchemeHttp, HttpPort); } /// - public Uri GetLocalApiUrl(ReadOnlySpan host, string scheme = null, int? port = null) + public string GetLocalApiUrl(ReadOnlySpan host, string scheme = null, int? port = null) { + // NOTE: If no BaseUrl is set then UriBuilder appends a trailing slash, but if there is no BaseUrl it does + // not. For consistency, always trim the trailing slash. return new UriBuilder { Scheme = scheme ?? (ListenWithHttps ? Uri.UriSchemeHttps : Uri.UriSchemeHttp), Host = host.ToString(), Port = port ?? (ListenWithHttps ? HttpsPort : HttpPort), Path = ServerConfigurationManager.Configuration.BaseUrl - }.Uri; + }.ToString().TrimEnd('/'); } public Task> GetLocalIpAddresses(CancellationToken cancellationToken) @@ -1333,8 +1335,7 @@ namespace Emby.Server.Implementations return true; } - var apiUrl = GetLocalApiUrl(address); - apiUrl += "/system/ping"; + var apiUrl = GetLocalApiUrl(address) + "/system/ping"; if (_validAddressResults.TryGetValue(apiUrl, out var cachedResult)) { diff --git a/Emby.Server.Implementations/Browser/BrowserLauncher.cs b/Emby.Server.Implementations/Browser/BrowserLauncher.cs index ccb733b3c..7f7c6a0be 100644 --- a/Emby.Server.Implementations/Browser/BrowserLauncher.cs +++ b/Emby.Server.Implementations/Browser/BrowserLauncher.cs @@ -31,18 +31,18 @@ namespace Emby.Server.Implementations.Browser /// Opens the specified URL in an external browser window. Any exceptions will be logged, but ignored. /// /// The application host. - /// The URL. - private static void TryOpenUrl(IServerApplicationHost appHost, string url) + /// The URL to open, relative to the server base URL. + private static void TryOpenUrl(IServerApplicationHost appHost, string relativeUrl) { try { - Uri baseUrl = appHost.GetLocalApiUrl("localhost"); - appHost.LaunchUrl(baseUrl + url); + string baseUrl = appHost.GetLocalApiUrl("localhost"); + appHost.LaunchUrl(baseUrl + relativeUrl); } catch (Exception ex) { var logger = appHost.Resolve(); - logger?.LogError(ex, "Failed to open browser window with URL {URL}", url); + logger?.LogError(ex, "Failed to open browser window with URL {URL}", relativeUrl); } } } diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 0028bd689..4f0ff1ee1 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -76,7 +76,7 @@ namespace MediaBrowser.Controller /// over HTTP (not HTTPS). /// /// The API URL. - public Uri GetLoopbackHttpApiUrl(); + string GetLoopbackHttpApiUrl(); /// /// Gets a local (LAN) URL that can be used to access the API. HTTPS will be preferred when available. @@ -98,7 +98,7 @@ namespace MediaBrowser.Controller /// preferring the HTTPS port, if available. /// /// The API URL. - Uri GetLocalApiUrl(ReadOnlySpan hostname, string scheme = null, int? port = null); + string GetLocalApiUrl(ReadOnlySpan hostname, string scheme = null, int? port = null); /// /// Open a URL in an external browser window. -- cgit v1.2.3 From a10aec695671cd2e90ff67bd7bc6e18b450a8b3f Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 10 May 2020 18:17:12 -0400 Subject: Fix merge --- Jellyfin.Server/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index fba1db584..b9895386f 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -294,7 +294,7 @@ namespace Jellyfin.Server { _logger.LogInformation("Kestrel listening on {IpAddress}", address); options.Listen(address, appHost.HttpPort); - if (appHost.ListenWithHttps && appHost.Certificate != null) + if (appHost.ListenWithHttps) { options.Listen(address, appHost.HttpsPort, listenOptions => { -- cgit v1.2.3 From 5b2973b01ad1176655de83cea3b6de4dcda0eefb Mon Sep 17 00:00:00 2001 From: Federico Antoniazzi Date: Mon, 11 May 2020 10:56:05 +0000 Subject: Translated using Weblate (Italian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/it/ --- Emby.Server.Implementations/Localization/Core/it.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json index 0758bbe9c..6ce97cca3 100644 --- a/Emby.Server.Implementations/Localization/Core/it.json +++ b/Emby.Server.Implementations/Localization/Core/it.json @@ -5,7 +5,7 @@ "Artists": "Artisti", "AuthenticationSucceededWithUserName": "{0} autenticato con successo", "Books": "Libri", - "CameraImageUploadedFrom": "È stata caricata una nuova immagine della fotocamera dal device {0}", + "CameraImageUploadedFrom": "È stata caricata una nuova fotografia {0}", "Channels": "Canali", "ChapterNameValue": "Capitolo {0}", "Collections": "Collezioni", -- cgit v1.2.3 From 6c855e5f81e0b9ee84e1669fea3a31801add9a4f Mon Sep 17 00:00:00 2001 From: millallo Date: Mon, 11 May 2020 20:17:41 +0000 Subject: Translated using Weblate (Italian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/it/ --- Emby.Server.Implementations/Localization/Core/it.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json index 6ce97cca3..7f5a56e86 100644 --- a/Emby.Server.Implementations/Localization/Core/it.json +++ b/Emby.Server.Implementations/Localization/Core/it.json @@ -5,7 +5,7 @@ "Artists": "Artisti", "AuthenticationSucceededWithUserName": "{0} autenticato con successo", "Books": "Libri", - "CameraImageUploadedFrom": "È stata caricata una nuova fotografia {0}", + "CameraImageUploadedFrom": "È stata caricata una nuova fotografia da {0}", "Channels": "Canali", "ChapterNameValue": "Capitolo {0}", "Collections": "Collezioni", -- cgit v1.2.3 From b961c3c9ae21b2b9238228b978bbec212c1ff187 Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Tue, 12 May 2020 15:05:58 +0200 Subject: Address suggestions --- .../Tmdb/TV/TmdbSeriesProvider.cs | 10 +++++-- MediaBrowser.Providers/Tmdb/TmdbUtils.cs | 31 ++++++++++++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs index bee4dba16..ffc4a66d1 100644 --- a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs @@ -26,6 +26,8 @@ namespace MediaBrowser.Providers.Tmdb.TV { public class TmdbSeriesProvider : IRemoteMetadataProvider, IHasOrder { + private const string GetTvInfo3 = TmdbUtils.BaseTmdbApiUrl + @"3/tv/{0}?api_key={1}&append_to_response=credits,images,keywords,external_ids,videos,content_ratings"; + private readonly IJsonSerializer _jsonSerializer; private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _configurationManager; @@ -35,7 +37,6 @@ namespace MediaBrowser.Providers.Tmdb.TV private readonly ILibraryManager _libraryManager; private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private const string GetTvInfo3 = TmdbUtils.BaseTmdbApiUrl + @"3/tv/{0}?api_key={1}&append_to_response=credits,images,keywords,external_ids,videos,content_ratings"; internal static TmdbSeriesProvider Current { get; private set; } @@ -355,7 +356,12 @@ namespace MediaBrowser.Providers.Tmdb.TV continue; } - seriesResult.AddPerson(new PersonInfo { Name = person.Name.Trim(), Role = person.Job, Type = type }); + seriesResult.AddPerson(new PersonInfo + { + Name = person.Name.Trim(), + Role = person.Job, + Type = type + }); } } } diff --git a/MediaBrowser.Providers/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Tmdb/TmdbUtils.cs index cf740fe54..7dacc7404 100644 --- a/MediaBrowser.Providers/Tmdb/TmdbUtils.cs +++ b/MediaBrowser.Providers/Tmdb/TmdbUtils.cs @@ -4,24 +4,51 @@ using MediaBrowser.Providers.Tmdb.Models.General; namespace MediaBrowser.Providers.Tmdb { + /// + /// Utilities for the TMDb provider + /// public static class TmdbUtils { + /// + /// URL of the TMDB instance to use. + /// public const string BaseTmdbUrl = "https://www.themoviedb.org/"; + + /// + /// URL of the TMDB API instance to use. + /// public const string BaseTmdbApiUrl = "https://api.themoviedb.org/"; + + /// + /// Name of the provider. + /// public const string ProviderName = "TheMovieDb"; + + /// + /// API key to use when performing an API call. + /// public const string ApiKey = "4219e299c89411838049ab0dab19ebd5"; + + /// + /// Value of the Accept header for requests to the provider. + /// public const string AcceptHeader = "application/json,image/*"; + /// + /// Maps the TMDB provided roles for crew members to Jellyfin roles. + /// + /// Crew member to map against the Jellyfin person types. + /// The Jellyfin person type. public static string MapCrewToPersonType(Crew crew) { if (crew.Department.Equals("production", StringComparison.InvariantCultureIgnoreCase) - && crew.Job.IndexOf("director", StringComparison.InvariantCultureIgnoreCase) != -1) + && crew.Job.Contains("director", StringComparison.InvariantCultureIgnoreCase)) { return PersonType.Director; } if (crew.Department.Equals("production", StringComparison.InvariantCultureIgnoreCase) - && crew.Job.IndexOf("producer", StringComparison.InvariantCultureIgnoreCase) != -1) + && crew.Job.Contains("producer", StringComparison.InvariantCultureIgnoreCase)) { return PersonType.Producer; } -- cgit v1.2.3 From 32c118222647f121c0b17055c0ef158763c0b5d2 Mon Sep 17 00:00:00 2001 From: rapmue Date: Tue, 12 May 2020 13:55:09 +0000 Subject: Translated using Weblate (German (Swiss)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/gsw/ --- Emby.Server.Implementations/Localization/Core/gsw.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/gsw.json b/Emby.Server.Implementations/Localization/Core/gsw.json index c8291a202..8780a884b 100644 --- a/Emby.Server.Implementations/Localization/Core/gsw.json +++ b/Emby.Server.Implementations/Localization/Core/gsw.json @@ -103,5 +103,16 @@ "TasksChannelsCategory": "Internet Kanäle", "TasksApplicationCategory": "Applikation", "TasksLibraryCategory": "Bibliothek", - "TasksMaintenanceCategory": "Verwaltung" + "TasksMaintenanceCategory": "Verwaltung", + "TaskDownloadMissingSubtitlesDescription": "Durchsucht das Internet nach fehlenden Untertiteln, basierend auf den Metadaten Einstellungen.", + "TaskDownloadMissingSubtitles": "Lade fehlende Untertitel herunter", + "TaskRefreshChannelsDescription": "Aktualisiert Internet Kanal Informationen.", + "TaskRefreshChannels": "Aktualisiere Kanäle", + "TaskCleanTranscodeDescription": "Löscht Transkodierdateien welche älter als ein Tag sind.", + "TaskCleanTranscode": "Räume Transcodier Verzeichnis auf", + "TaskUpdatePluginsDescription": "Lädt Aktualisierungen für Erweiterungen herunter und installiert diese, für welche automatische Aktualisierungen konfiguriert sind.", + "TaskUpdatePlugins": "Aktualisiere Erweiterungen", + "TaskRefreshPeopleDescription": "Aktualisiert Metadaten für Schausteller und Regisseure in deiner Bibliothek.", + "TaskRefreshPeople": "Aktualisiere Schauspieler", + "TaskCleanLogsDescription": "Löscht Log Dateien die älter als {0} Tage sind." } -- cgit v1.2.3 From bac4bf96a0e642f80f35bd6cdf3de17a9302b6c6 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 12 May 2020 12:50:17 -0400 Subject: Fix build errors --- Jellyfin.Server/Migrations/MigrationRunner.cs | 2 +- .../Migrations/Routines/MigrateActivityLogDb.cs | 23 ++++++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index c4927f877..c7fa2af0c 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -18,7 +18,7 @@ namespace Jellyfin.Server.Migrations { typeof(Routines.DisableTranscodingThrottling), typeof(Routines.CreateUserLoggingConfigFile), - typeof(Routines.MigrateActivityLogDb() + typeof(Routines.MigrateActivityLogDb) }; /// diff --git a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs index 9f1f5b92e..fe7ef01ee 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs @@ -5,8 +5,8 @@ using System.IO; using Emby.Server.Implementations.Data; using Jellyfin.Data.Entities; using Jellyfin.Server.Implementations; +using MediaBrowser.Controller; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SQLitePCL.pretty; @@ -16,20 +16,31 @@ namespace Jellyfin.Server.Migrations.Routines { private const string DbFilename = "activitylog.db"; + private readonly ILogger _logger; + private readonly JellyfinDbProvider _provider; + private readonly IServerApplicationPaths _paths; + + public MigrateActivityLogDb(ILogger logger, IServerApplicationPaths paths, JellyfinDbProvider provider) + { + _logger = logger; + _provider = provider; + _paths = paths; + } + public Guid Id => Guid.Parse("3793eb59-bc8c-456c-8b9f-bd5a62a42978"); public string Name => "MigrateActivityLogDatabase"; - public void Perform(CoreAppHost host, ILogger logger) + public void Perform() { - var dataPath = host.ServerConfigurationManager.ApplicationPaths.DataPath; + var dataPath = _paths.DataPath; using (var connection = SQLite3.Open( Path.Combine(dataPath, DbFilename), ConnectionFlags.ReadOnly, null)) { - logger.LogInformation("Migrating the database may take a while, do not stop Jellyfin."); - using var dbContext = host.ServiceProvider.GetService(); + _logger.LogInformation("Migrating the database may take a while, do not stop Jellyfin."); + using var dbContext = _provider.CreateContext(); var queryResult = connection.Query("SELECT * FROM ActivityLog ORDER BY Id ASC"); @@ -75,7 +86,7 @@ namespace Jellyfin.Server.Migrations.Routines } catch (IOException e) { - logger.LogError(e, "Error renaming legacy activity log database to 'activitylog.db.old'"); + _logger.LogError(e, "Error renaming legacy activity log database to 'activitylog.db.old'"); } } -- cgit v1.2.3 From 62420a6eb195f3119dc640b134d689d0de193a85 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 12 May 2020 16:03:15 -0400 Subject: Remove support for injecting ILogger directly --- Emby.Server.Implementations/ApplicationHost.cs | 7 ------- .../Library/Resolvers/Audio/MusicAlbumResolver.cs | 4 ++-- .../Library/Resolvers/Audio/MusicArtistResolver.cs | 6 +++--- .../LiveTv/TunerHosts/M3UTunerHost.cs | 4 ++-- MediaBrowser.Api/Movies/TrailersService.cs | 12 ++++++++--- MediaBrowser.Api/UserLibrary/ItemsService.cs | 2 +- .../Plugins/Omdb/OmdbEpisodeProvider.cs | 24 +++++++++++++--------- .../Plugins/Omdb/OmdbItemProvider.cs | 15 ++++++++------ 8 files changed, 40 insertions(+), 34 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ffc916b98..b6e75b386 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -546,13 +546,6 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); - // TODO: Remove support for injecting ILogger completely - serviceCollection.AddSingleton((provider) => - { - Logger.LogWarning("Injecting ILogger directly is deprecated and should be replaced with ILogger"); - return Logger; - }); - serviceCollection.AddSingleton(_fileSystemManager); serviceCollection.AddSingleton(); diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index 85b1b6e32..6c9ba7c27 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio /// public class MusicAlbumResolver : ItemResolver { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly ILibraryManager _libraryManager; @@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio /// The logger. /// The file system. /// The library manager. - public MusicAlbumResolver(ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager) + public MusicAlbumResolver(ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager) { _logger = logger; _fileSystem = fileSystem; diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs index 681db4896..5f5cd0e92 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs @@ -15,7 +15,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio /// public class MusicArtistResolver : ItemResolver { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly ILibraryManager _libraryManager; private readonly IServerConfigurationManager _config; @@ -23,12 +23,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio /// /// Initializes a new instance of the class. /// - /// The logger. + /// The logger for the created instances. /// The file system. /// The library manager. /// The configuration manager. public MusicArtistResolver( - ILogger logger, + ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager, IServerConfigurationManager config) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index f5dda79db..f7c9c736e 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -35,7 +35,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public M3UTunerHost( IServerConfigurationManager config, IMediaSourceManager mediaSourceManager, - ILogger logger, + ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem, IHttpClient httpClient, @@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return Task.FromResult(list); } - private static readonly string[] _disallowedSharedStreamExtensions = new string[] + private static readonly string[] _disallowedSharedStreamExtensions = { ".mkv", ".mp4", diff --git a/MediaBrowser.Api/Movies/TrailersService.cs b/MediaBrowser.Api/Movies/TrailersService.cs index 8adf9c621..0b5334235 100644 --- a/MediaBrowser.Api/Movies/TrailersService.cs +++ b/MediaBrowser.Api/Movies/TrailersService.cs @@ -33,13 +33,18 @@ namespace MediaBrowser.Api.Movies /// private readonly ILibraryManager _libraryManager; + /// + /// The logger for the created instances. + /// + private readonly ILogger _logger; + private readonly IDtoService _dtoService; private readonly ILocalizationManager _localizationManager; private readonly IJsonSerializer _json; private readonly IAuthorizationContext _authContext; public TrailersService( - ILogger logger, + ILoggerFactory loggerFactory, IServerConfigurationManager serverConfigurationManager, IHttpResultFactory httpResultFactory, IUserManager userManager, @@ -48,7 +53,7 @@ namespace MediaBrowser.Api.Movies ILocalizationManager localizationManager, IJsonSerializer json, IAuthorizationContext authContext) - : base(logger, serverConfigurationManager, httpResultFactory) + : base(loggerFactory.CreateLogger(), serverConfigurationManager, httpResultFactory) { _userManager = userManager; _libraryManager = libraryManager; @@ -56,6 +61,7 @@ namespace MediaBrowser.Api.Movies _localizationManager = localizationManager; _json = json; _authContext = authContext; + _logger = loggerFactory.CreateLogger(); } public object Get(Getrailers request) @@ -66,7 +72,7 @@ namespace MediaBrowser.Api.Movies getItems.IncludeItemTypes = "Trailer"; return new ItemsService( - Logger, + _logger, ServerConfigurationManager, ResultFactory, _userManager, diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index c4d44042b..f3c0441e1 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -59,7 +59,7 @@ namespace MediaBrowser.Api.UserLibrary /// The localization. /// The dto service. public ItemsService( - ILogger logger, + ILogger logger, IServerConfigurationManager serverConfigurationManager, IHttpResultFactory httpResultFactory, IUserManager userManager, diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs index 37160dd2c..f0328e8d8 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs @@ -11,13 +11,10 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Serialization; -using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Plugins.Omdb { - public class OmdbEpisodeProvider : - IRemoteMetadataProvider, - IHasOrder + public class OmdbEpisodeProvider : IRemoteMetadataProvider, IHasOrder { private readonly IJsonSerializer _jsonSerializer; private readonly IHttpClient _httpClient; @@ -26,16 +23,27 @@ namespace MediaBrowser.Providers.Plugins.Omdb private readonly IServerConfigurationManager _configurationManager; private readonly IApplicationHost _appHost; - public OmdbEpisodeProvider(IJsonSerializer jsonSerializer, IApplicationHost appHost, IHttpClient httpClient, ILogger logger, ILibraryManager libraryManager, IFileSystem fileSystem, IServerConfigurationManager configurationManager) + public OmdbEpisodeProvider( + IJsonSerializer jsonSerializer, + IApplicationHost appHost, + IHttpClient httpClient, + ILibraryManager libraryManager, + IFileSystem fileSystem, + IServerConfigurationManager configurationManager) { _jsonSerializer = jsonSerializer; _httpClient = httpClient; _fileSystem = fileSystem; _configurationManager = configurationManager; _appHost = appHost; - _itemProvider = new OmdbItemProvider(jsonSerializer, _appHost, httpClient, logger, libraryManager, fileSystem, configurationManager); + _itemProvider = new OmdbItemProvider(jsonSerializer, _appHost, httpClient, libraryManager, fileSystem, configurationManager); } + // After TheTvDb + public int Order => 1; + + public string Name => "The Open Movie Database"; + public Task> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken) { return _itemProvider.GetSearchResults(searchInfo, "episode", cancellationToken); @@ -66,10 +74,6 @@ namespace MediaBrowser.Providers.Plugins.Omdb return result; } - // After TheTvDb - public int Order => 1; - - public string Name => "The Open Movie Database"; public Task GetImageResponse(string url, CancellationToken cancellationToken) { diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs index 3aadda5d0..64a75955a 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs @@ -17,7 +17,6 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Serialization; -using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Plugins.Omdb { @@ -26,22 +25,27 @@ namespace MediaBrowser.Providers.Plugins.Omdb { private readonly IJsonSerializer _jsonSerializer; private readonly IHttpClient _httpClient; - private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _configurationManager; private readonly IApplicationHost _appHost; - public OmdbItemProvider(IJsonSerializer jsonSerializer, IApplicationHost appHost, IHttpClient httpClient, ILogger logger, ILibraryManager libraryManager, IFileSystem fileSystem, IServerConfigurationManager configurationManager) + public OmdbItemProvider( + IJsonSerializer jsonSerializer, + IApplicationHost appHost, + IHttpClient httpClient, + ILibraryManager libraryManager, + IFileSystem fileSystem, + IServerConfigurationManager configurationManager) { _jsonSerializer = jsonSerializer; _httpClient = httpClient; - _logger = logger; _libraryManager = libraryManager; _fileSystem = fileSystem; _configurationManager = configurationManager; _appHost = appHost; } + // After primary option public int Order => 2; @@ -80,7 +84,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb var parsedName = _libraryManager.ParseName(name); var yearInName = parsedName.Year; name = parsedName.Name; - year = year ?? yearInName; + year ??= yearInName; } if (string.IsNullOrWhiteSpace(imdbId)) @@ -312,6 +316,5 @@ namespace MediaBrowser.Providers.Plugins.Omdb /// The results. public List Search { get; set; } } - } } -- cgit v1.2.3 From e69ba3531e2cc93c79ccc10b828d563c96753d10 Mon Sep 17 00:00:00 2001 From: D Z Date: Tue, 12 May 2020 20:42:04 +0000 Subject: Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/he/ --- Emby.Server.Implementations/Localization/Core/he.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json index 8abe31d2a..4e54b9f7a 100644 --- a/Emby.Server.Implementations/Localization/Core/he.json +++ b/Emby.Server.Implementations/Localization/Core/he.json @@ -99,5 +99,13 @@ "TaskCleanCache": "נקה תיקיית מטמון", "TasksApplicationCategory": "יישום", "TasksLibraryCategory": "ספרייה", - "TasksMaintenanceCategory": "תחזוקה" + "TasksMaintenanceCategory": "תחזוקה", + "TaskUpdatePlugins": "עדכן תוספים", + "TaskRefreshPeopleDescription": "מעדכן מטא נתונים עבור שחקנים ובמאים בספריית המדיה שלך.", + "TaskRefreshPeople": "רענן אנשים", + "TaskCleanLogsDescription": "מוחק קבצי יומן בני יותר מ- {0} ימים.", + "TaskCleanLogs": "נקה תיקיית יומן", + "TaskRefreshLibraryDescription": "סורק את ספריית המדיה שלך אחר קבצים חדשים ומרענן מטא נתונים.", + "TaskRefreshChapterImagesDescription": "יוצר תמונות ממוזערות לסרטונים שיש להם פרקים.", + "TasksChannelsCategory": "ערוצי אינטרנט" } -- cgit v1.2.3 From 92299be64cf00fd65ccec2e84cdb1c7f41c73de9 Mon Sep 17 00:00:00 2001 From: tanvir-ahmed-siddique Date: Tue, 12 May 2020 21:31:54 +0000 Subject: Translated using Weblate (Bengali) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/bn/ --- Emby.Server.Implementations/Localization/Core/bn.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/bn.json b/Emby.Server.Implementations/Localization/Core/bn.json index ef7792356..4949b10e6 100644 --- a/Emby.Server.Implementations/Localization/Core/bn.json +++ b/Emby.Server.Implementations/Localization/Core/bn.json @@ -91,5 +91,7 @@ "HeaderNextUp": "এরপরে আসছে", "HeaderLiveTV": "লাইভ টিভি", "HeaderFavoriteSongs": "প্রিয় গানগুলো", - "HeaderFavoriteShows": "প্রিয় শোগুলো" + "HeaderFavoriteShows": "প্রিয় শোগুলো", + "TasksLibraryCategory": "গ্রন্থাগার", + "TasksMaintenanceCategory": "রক্ষণাবেক্ষণ" } -- 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 diff --git a/Emby.Dlna/ContentDirectory/ContentDirectory.cs b/Emby.Dlna/ContentDirectory/ContentDirectory.cs index 64cd308a2..ea577a905 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 28888f031..32bda2fe1 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 f7d840c62..24932ced9 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 43e983054..ff37407a2 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 0b3bbe29e..b9172d2a8 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 788750796..221db5423 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 639a5e1aa..9a9bc4415 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 54894fd65..3d3859078 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 ddd9c7953..52b48d081 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 138832fb8..cb320dcb1 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 7c518d483..61963b633 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 d474f1c6b..000000000 --- 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 ca5cd6fdd..74b8ffc18 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 22955850a..000000000 --- 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 0c3f26974..000000000 --- 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 579cb895e..d6bce93f8 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 c4b65d265..da40bca4c 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 8e3236407..7ece52cad 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 41c0c5115..75dde5598 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 54f4b67e6..000000000 --- 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 e1dbb663b..436d723f0 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 256b24924..ad7b76d4f 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 129faeaab..9558cb4c6 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 166952c64..3f8a64f99 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 52c8facc3..000000000 --- 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 6c6fbd86f..000000000 --- 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 dc61aacd7..000000000 --- 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 0b86b2db7..e7cd7512c 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 01fe98f3a..25af69058 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 e27145a1d..a177138b7 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 1ec578371..ad8c70f5e 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 623675157..3cc294371 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 73f59f8cd..57a1a00d9 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 66de05a6a..c9feca7e3 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 da3f3dd25..6f383e65f 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 d99d0eff2..4845fdc0d 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 eb74ce1bd..99846db61 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 4c2f24e6f..905a1ea99 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 26f7d9d2d..4929e8897 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 afc9b8f3d..f965d83f3 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 000000000..33410b732 --- /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 000000000..7966cdb50 --- /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 000000000..336c13b36 --- /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 715969dbf..1aaa8a180 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 000000000..a33cd9d1c --- /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 4447fdb77..df18261e6 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 e66a51cae..34e20ead6 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 000000000..c8fc21159 --- /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 000000000..5259e7739 --- /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 8b6b7cacc..7e1314690 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 000000000..024500bf8 --- /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 000000000..80ab3ce00 --- /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 000000000..d33034ab2 --- /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 000000000..a11ca128a --- /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 000000000..1ed11cfcb --- /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 000000000..23d2299cd --- /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 1a1d86362..a5b504dfa 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 5eb72cdb1..ac61cd491 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 2e9b3e6cb..f9976122c 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 997b1c45a..783fc6073 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 5fe4c0cca..279fd6ee9 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 46da8b909..b97a0dca1 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 cacec8d64..e00b06e38 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 928ca1612..65f6ccd4d 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 db24eaca6..0ef39c2d7 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 020bb5042..d986eea65 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 91f85db6f..51fa1eb71 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 cd8e8dfbe..0c23d8b29 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 c4a52d5f5..75a33350e 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 c4d44042b..cbceb3625 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 d0faca163..fb8bda190 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 7fa750adb..f75852885 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 78fc6c694..7bf4f88f4 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 f5571065f..c0324a384 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 2639960e7..d9b814f69 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 cdf2ca69e..d6a1fc84e 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 cfe8493d3..f51c73bd7 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 77d567631..117af110a 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 36c746624..bdb12402a 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 df9050de5..9f505be93 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 ba693a065..5ac4f05c0 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 a700d0be4..9065cb27f 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 c216176e7..fbadeafad 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 dcad2554b..c4a2929dc 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 8a79e0783..000000000 --- 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 a468e0c35..03644b0c6 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 bd96059e3..6a2cafcba 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 feaf8c45a..1c1bde3e4 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 11dc472b6..38359afcc 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 603242063..1b9d4614e 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 49229fa4b..0a89da46d 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 9c8a469e2..0d1fec62f 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 2475b2b7e..4aed5fbdc 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 0b8be90cd..c646e8ae6 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 53601a610..000000000 --- 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 8a68f830c..9d211540d 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 4ce9ec6f8..b44e7c191 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 435a1e8da..0ad8e6b71 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 d9d1ca8c7..aa7001611 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 2e1c97f67..ada570bfd 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 0ceabd0e6..57368778a 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 554dd0895..0618837bc 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 eb735d31a..15da560ef 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 be7b4ce59..1e385dcb9 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 b0302d04c..83c0e3297 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 e02c387e4..99fd18bf9 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 60391bb83..f3ff8bd04 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 13df85aed..e6dc4bdda 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 61a330675..2559bc248 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 1127a08de..dc4361fc3 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 3e004763d..4361e253b 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 9132404a0..61fc7e6e6 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 5c3c19f6b..421ac3fe2 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 8c6019923..2bc751758 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 3f46468b3..a1029589b 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 cd23e5223..000000000 --- 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 3b08e72b9..03bdf1eaf 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 254b27460..955db0278 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 771027103..32e62db14 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 1e2df37bf..f079bc7ea 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 120c47dbc..7bd355449 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 71b16cfba..000000000 --- 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 f0aa2b98c..000000000 --- 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 e1d1a363d..000000000 --- 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 a475c9910..79cf0a065 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 79a128e9b..b3c46ace2 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 ae2b3fd4e..8d94d3971 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 3ab621ba4..6bed38780 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 cfff89767..f4e875a24 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 fb6c91b6c..000000000 --- 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 3b3d03c8b..0cb8d8be6 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 51cdb30741e5ad3d6ec9dc8a5383dc84d60f747a Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Wed, 13 May 2020 09:46:29 -0400 Subject: Apply documentation suggestions from code review Co-authored-by: Vasily --- MediaBrowser.Controller/IServerApplicationHost.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 4f0ff1ee1..db330210a 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -72,7 +72,7 @@ namespace MediaBrowser.Controller Task GetLocalApiUrl(CancellationToken cancellationToken); /// - /// Gets a local (LAN) URL that can be used to access the API using the loop-back IP address (127.0.0.1) + /// Gets a localhost URL that can be used to access the API using the loop-back IP address (127.0.0.1) /// over HTTP (not HTTPS). /// /// The API URL. @@ -87,6 +87,7 @@ namespace MediaBrowser.Controller /// /// Gets a local (LAN) URL that can be used to access the API. + /// Note: if passing non-null scheme or port it is up to the caller to ensure they form the correct pair. /// /// The hostname to use in the URL. /// -- cgit v1.2.3 From 512725a7d1c1b45fa588d76046c262b96614f58a Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Wed, 13 May 2020 18:02:54 +0200 Subject: Fix style issue in TmdbSeriesProvider --- MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs index ffc4a66d1..6e3c26c26 100644 --- a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs @@ -350,8 +350,8 @@ namespace MediaBrowser.Providers.Tmdb.TV // Normalize this var type = TmdbUtils.MapCrewToPersonType(person); - if (!keepTypes.Contains(type, StringComparer.OrdinalIgnoreCase) && - !keepTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + if (!keepTypes.Contains(type, StringComparer.OrdinalIgnoreCase) + && !keepTypes.Contains(person.Job ?? string.Empty, StringComparer.OrdinalIgnoreCase)) { continue; } -- cgit v1.2.3 From 511d20a100398baca38f24adfabc56f6f3cfac9c Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 13 May 2020 15:03:35 -0400 Subject: Apply review suggestions --- .../Activity/ActivityLogEntryPoint.cs | 110 ++++++--------------- .../Devices/DeviceManager.cs | 105 -------------------- Jellyfin.Data/Entities/ActivityLog.cs | 47 +++++---- .../Activity/ActivityManager.cs | 2 +- .../Migrations/Routines/MigrateActivityLogDb.cs | 67 ++++++------- MediaBrowser.Api/Devices/DeviceService.cs | 36 ------- MediaBrowser.Api/Library/LibraryService.cs | 4 +- MediaBrowser.Api/System/ActivityLogService.cs | 6 -- MediaBrowser.Controller/Devices/IDeviceManager.cs | 21 ---- MediaBrowser.sln | 4 +- 10 files changed, 88 insertions(+), 314 deletions(-) diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 54894fd65..3983824a3 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -8,7 +8,6 @@ using Jellyfin.Data.Entities; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; @@ -30,7 +29,7 @@ namespace Emby.Server.Implementations.Activity /// public sealed class ActivityLogEntryPoint : IServerEntryPoint { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IInstallationManager _installationManager; private readonly ISessionManager _sessionManager; private readonly ITaskManager _taskManager; @@ -38,14 +37,12 @@ namespace Emby.Server.Implementations.Activity private readonly ILocalizationManager _localization; private readonly ISubtitleManager _subManager; private readonly IUserManager _userManager; - private readonly IDeviceManager _deviceManager; /// /// Initializes a new instance of the class. /// /// The logger. /// The session manager. - /// The device manager. /// The task manager. /// The activity manager. /// The localization manager. @@ -55,7 +52,6 @@ namespace Emby.Server.Implementations.Activity public ActivityLogEntryPoint( ILogger logger, ISessionManager sessionManager, - IDeviceManager deviceManager, ITaskManager taskManager, IActivityManager activityManager, ILocalizationManager localization, @@ -65,7 +61,6 @@ namespace Emby.Server.Implementations.Activity { _logger = logger; _sessionManager = sessionManager; - _deviceManager = deviceManager; _taskManager = taskManager; _activityManager = activityManager; _localization = localization; @@ -99,36 +94,18 @@ namespace Emby.Server.Implementations.Activity _userManager.UserPolicyUpdated += OnUserPolicyUpdated; _userManager.UserLockedOut += OnUserLockedOut; - _deviceManager.CameraImageUploaded += OnCameraImageUploaded; - return Task.CompletedTask; } - private async void OnCameraImageUploaded(object sender, GenericEventArgs e) - { - await CreateLogEntry(new ActivityLog( - string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("CameraImageUploadedFrom"), - e.Argument.Device.Name), - NotificationType.CameraImageUploaded.ToString(), - Guid.Empty, - DateTime.UtcNow, - LogLevel.Trace)) - .ConfigureAwait(false); - } - private async void OnUserLockedOut(object sender, GenericEventArgs e) { await CreateLogEntry(new ActivityLog( - string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("UserLockedOutWithName"), - e.Argument.Name), - NotificationType.UserLockedOut.ToString(), - e.Argument.Id, - DateTime.UtcNow, - LogLevel.Trace)) + string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("UserLockedOutWithName"), + e.Argument.Name), + NotificationType.UserLockedOut.ToString(), + e.Argument.Id)) .ConfigureAwait(false); } @@ -139,11 +116,9 @@ namespace Emby.Server.Implementations.Activity CultureInfo.InvariantCulture, _localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"), e.Provider, - Emby.Notifications.NotificationEntryPoint.GetItemName(e.Item)), + Notifications.NotificationEntryPoint.GetItemName(e.Item)), "SubtitleDownloadFailure", - Guid.Empty, - DateTime.UtcNow, - LogLevel.Trace) + Guid.Empty) { ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture), ShortOverview = e.Exception.Message @@ -181,9 +156,7 @@ namespace Emby.Server.Implementations.Activity GetItemName(item), e.DeviceName), GetPlaybackStoppedNotificationType(item.MediaType), - user.Id, - DateTime.UtcNow, - LogLevel.Trace)) + user.Id)) .ConfigureAwait(false); } @@ -218,9 +191,7 @@ namespace Emby.Server.Implementations.Activity GetItemName(item), e.DeviceName), GetPlaybackNotificationType(item.MediaType), - user.Id, - DateTime.UtcNow, - LogLevel.Trace)) + user.Id)) .ConfigureAwait(false); } @@ -287,9 +258,7 @@ namespace Emby.Server.Implementations.Activity session.UserName, session.DeviceName), "SessionEnded", - session.UserId, - DateTime.UtcNow, - LogLevel.Trace) + session.UserId) { ShortOverview = string.Format( CultureInfo.InvariantCulture, @@ -308,9 +277,7 @@ namespace Emby.Server.Implementations.Activity _localization.GetLocalizedString("AuthenticationSucceededWithUserName"), user.Name), "AuthenticationSucceeded", - user.Id, - DateTime.UtcNow, - LogLevel.Trace) + user.Id) { ShortOverview = string.Format( CultureInfo.InvariantCulture, @@ -327,10 +294,9 @@ namespace Emby.Server.Implementations.Activity _localization.GetLocalizedString("FailedLoginAttemptWithUserName"), e.Argument.Username), "AuthenticationFailed", - Guid.Empty, - DateTime.UtcNow, - LogLevel.Error) + Guid.Empty) { + LogSeverity = LogLevel.Error, ShortOverview = string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("LabelIpAddressValue"), @@ -346,9 +312,7 @@ namespace Emby.Server.Implementations.Activity _localization.GetLocalizedString("UserPolicyUpdatedWithName"), e.Argument.Name), "UserPolicyUpdated", - e.Argument.Id, - DateTime.UtcNow, - LogLevel.Trace)) + e.Argument.Id)) .ConfigureAwait(false); } @@ -360,9 +324,7 @@ namespace Emby.Server.Implementations.Activity _localization.GetLocalizedString("UserDeletedWithName"), e.Argument.Name), "UserDeleted", - Guid.Empty, - DateTime.UtcNow, - LogLevel.Trace)) + Guid.Empty)) .ConfigureAwait(false); } @@ -374,9 +336,8 @@ namespace Emby.Server.Implementations.Activity _localization.GetLocalizedString("UserPasswordChangedWithName"), e.Argument.Name), "UserPasswordChanged", - e.Argument.Id, - DateTime.UtcNow, - LogLevel.Trace)).ConfigureAwait(false); + e.Argument.Id)) + .ConfigureAwait(false); } private async void OnUserCreated(object sender, GenericEventArgs e) @@ -387,9 +348,7 @@ namespace Emby.Server.Implementations.Activity _localization.GetLocalizedString("UserCreatedWithName"), e.Argument.Name), "UserCreated", - e.Argument.Id, - DateTime.UtcNow, - LogLevel.Trace)) + e.Argument.Id)) .ConfigureAwait(false); } @@ -409,9 +368,7 @@ namespace Emby.Server.Implementations.Activity session.UserName, session.DeviceName), "SessionStarted", - session.UserId, - DateTime.UtcNow, - LogLevel.Trace) + session.UserId) { ShortOverview = string.Format( CultureInfo.InvariantCulture, @@ -428,9 +385,7 @@ namespace Emby.Server.Implementations.Activity _localization.GetLocalizedString("PluginUpdatedWithName"), e.Argument.Item1.Name), NotificationType.PluginUpdateInstalled.ToString(), - Guid.Empty, - DateTime.UtcNow, - LogLevel.Trace) + Guid.Empty) { ShortOverview = string.Format( CultureInfo.InvariantCulture, @@ -448,9 +403,7 @@ namespace Emby.Server.Implementations.Activity _localization.GetLocalizedString("PluginUninstalledWithName"), e.Argument.Name), NotificationType.PluginUninstalled.ToString(), - Guid.Empty, - DateTime.UtcNow, - LogLevel.Trace)) + Guid.Empty)) .ConfigureAwait(false); } @@ -462,9 +415,7 @@ namespace Emby.Server.Implementations.Activity _localization.GetLocalizedString("PluginInstalledWithName"), e.Argument.name), NotificationType.PluginInstalled.ToString(), - Guid.Empty, - DateTime.UtcNow, - LogLevel.Trace) + Guid.Empty) { ShortOverview = string.Format( CultureInfo.InvariantCulture, @@ -483,9 +434,7 @@ namespace Emby.Server.Implementations.Activity _localization.GetLocalizedString("NameInstallFailed"), installationInfo.Name), NotificationType.InstallationFailed.ToString(), - Guid.Empty, - DateTime.UtcNow, - LogLevel.Trace) + Guid.Empty) { ShortOverview = string.Format( CultureInfo.InvariantCulture, @@ -529,10 +478,9 @@ namespace Emby.Server.Implementations.Activity await CreateLogEntry(new ActivityLog( string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name), NotificationType.TaskFailed.ToString(), - Guid.Empty, - DateTime.UtcNow, - LogLevel.Error) + Guid.Empty) { + LogSeverity = LogLevel.Error, Overview = string.Join(Environment.NewLine, vals), ShortOverview = runningTime }).ConfigureAwait(false); @@ -567,8 +515,6 @@ namespace Emby.Server.Implementations.Activity _userManager.UserDeleted -= OnUserDeleted; _userManager.UserPolicyUpdated -= OnUserPolicyUpdated; _userManager.UserLockedOut -= OnUserLockedOut; - - _deviceManager.CameraImageUploaded -= OnCameraImageUploaded; } /// @@ -588,7 +534,7 @@ namespace Emby.Server.Implementations.Activity { int years = days / DaysInYear; values.Add(CreateValueString(years, "year")); - days = days % DaysInYear; + days %= DaysInYear; } // Number of months diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index 579cb895e..7017be6eb 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -34,7 +34,6 @@ namespace Emby.Server.Implementations.Devices private readonly IJsonSerializer _json; private readonly IUserManager _userManager; private readonly IFileSystem _fileSystem; - private readonly ILibraryMonitor _libraryMonitor; private readonly IServerConfigurationManager _config; private readonly ILibraryManager _libraryManager; private readonly ILocalizationManager _localizationManager; @@ -43,9 +42,6 @@ namespace Emby.Server.Implementations.Devices public event EventHandler>> DeviceOptionsUpdated; - public event EventHandler> CameraImageUploaded; - - private readonly object _cameraUploadSyncLock = new object(); private readonly object _capabilitiesSyncLock = new object(); public DeviceManager( @@ -55,13 +51,11 @@ namespace Emby.Server.Implementations.Devices ILocalizationManager localizationManager, IUserManager userManager, IFileSystem fileSystem, - ILibraryMonitor libraryMonitor, IServerConfigurationManager config) { _json = json; _userManager = userManager; _fileSystem = fileSystem; - _libraryMonitor = libraryMonitor; _config = config; _libraryManager = libraryManager; _localizationManager = localizationManager; @@ -194,105 +188,6 @@ namespace Emby.Server.Implementations.Devices return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N", CultureInfo.InvariantCulture)); } - public ContentUploadHistory GetCameraUploadHistory(string deviceId) - { - var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json"); - - lock (_cameraUploadSyncLock) - { - try - { - return _json.DeserializeFromFile(path); - } - catch (IOException) - { - return new ContentUploadHistory - { - DeviceId = deviceId - }; - } - } - } - - public async Task AcceptCameraUpload(string deviceId, Stream stream, LocalFileInfo file) - { - var device = GetDevice(deviceId, false); - var uploadPathInfo = GetUploadPath(device); - - var path = uploadPathInfo.Item1; - - if (!string.IsNullOrWhiteSpace(file.Album)) - { - path = Path.Combine(path, _fileSystem.GetValidFilename(file.Album)); - } - - path = Path.Combine(path, file.Name); - path = Path.ChangeExtension(path, MimeTypes.ToExtension(file.MimeType) ?? "jpg"); - - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - await EnsureLibraryFolder(uploadPathInfo.Item2, uploadPathInfo.Item3).ConfigureAwait(false); - - _libraryMonitor.ReportFileSystemChangeBeginning(path); - - try - { - using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read)) - { - await stream.CopyToAsync(fs).ConfigureAwait(false); - } - - AddCameraUpload(deviceId, file); - } - finally - { - _libraryMonitor.ReportFileSystemChangeComplete(path, true); - } - - if (CameraImageUploaded != null) - { - CameraImageUploaded?.Invoke(this, new GenericEventArgs - { - Argument = new CameraImageUploadInfo - { - Device = device, - FileInfo = file - } - }); - } - } - - private void AddCameraUpload(string deviceId, LocalFileInfo file) - { - var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json"); - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - lock (_cameraUploadSyncLock) - { - ContentUploadHistory history; - - try - { - history = _json.DeserializeFromFile(path); - } - catch (IOException) - { - history = new ContentUploadHistory - { - DeviceId = deviceId - }; - } - - history.DeviceId = deviceId; - - var list = history.FilesUploaded.ToList(); - list.Add(file); - history.FilesUploaded = list.ToArray(); - - _json.SerializeToFile(history, path); - } - } - internal Task EnsureLibraryFolder(string path, string name) { var existingFolders = _libraryManager diff --git a/Jellyfin.Data/Entities/ActivityLog.cs b/Jellyfin.Data/Entities/ActivityLog.cs index 633838991..df3026a77 100644 --- a/Jellyfin.Data/Entities/ActivityLog.cs +++ b/Jellyfin.Data/Entities/ActivityLog.cs @@ -1,15 +1,10 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Runtime.CompilerServices; +using Microsoft.Extensions.Logging; namespace Jellyfin.Data.Entities { - [Table("ActivityLog")] public partial class ActivityLog { partial void Init(); @@ -35,23 +30,26 @@ namespace Jellyfin.Data.Entities /// /// /// - /// + /// /// - /// - public ActivityLog(string name, string type, Guid userid, DateTime datecreated, Microsoft.Extensions.Logging.LogLevel logseverity) + /// + public ActivityLog(string name, string type, Guid userId) { - if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); - this.Name = name; - - if (string.IsNullOrEmpty(type)) throw new ArgumentNullException(nameof(type)); - this.Type = type; - - this.UserId = userid; + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentNullException(nameof(name)); + } - this.DateCreated = datecreated; - - this.LogSeverity = logseverity; + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentNullException(nameof(type)); + } + this.Name = name; + this.Type = type; + this.UserId = userId; + this.DateCreated = DateTime.UtcNow; + this.LogSeverity = LogLevel.Trace; Init(); } @@ -61,12 +59,12 @@ namespace Jellyfin.Data.Entities /// /// /// - /// + /// /// /// - public static ActivityLog Create(string name, string type, Guid userid, DateTime datecreated, Microsoft.Extensions.Logging.LogLevel logseverity) + public static ActivityLog Create(string name, string type, Guid userId) { - return new ActivityLog(name, type, userid, datecreated, logseverity); + return new ActivityLog(name, type, userId); } /************************************************************************* @@ -134,10 +132,10 @@ namespace Jellyfin.Data.Entities /// Required /// [Required] - public Microsoft.Extensions.Logging.LogLevel LogSeverity { get; set; } + public LogLevel LogSeverity { get; set; } /// - /// Required, ConcurrenyToken + /// Required, ConcurrencyToken. /// [ConcurrencyCheck] [Required] @@ -147,7 +145,6 @@ namespace Jellyfin.Data.Entities { RowVersion++; } - } } diff --git a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs index d7bbf793c..531b529dc 100644 --- a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs +++ b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs @@ -14,7 +14,7 @@ namespace Jellyfin.Server.Implementations.Activity /// public class ActivityManager : IActivityManager { - private JellyfinDbProvider _provider; + private readonly JellyfinDbProvider _provider; /// /// Initializes a new instance of the class. diff --git a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs index fe7ef01ee..faa163d19 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs @@ -1,6 +1,5 @@ -#pragma warning disable CS1591 - using System; +using System.Collections.Generic; using System.IO; using Emby.Server.Implementations.Data; using Jellyfin.Data.Entities; @@ -12,6 +11,9 @@ using SQLitePCL.pretty; namespace Jellyfin.Server.Migrations.Routines { + /// + /// The migration routine for migrating the activity log database to EF Core. + /// public class MigrateActivityLogDb : IMigrationRoutine { private const string DbFilename = "activitylog.db"; @@ -20,6 +22,12 @@ namespace Jellyfin.Server.Migrations.Routines private readonly JellyfinDbProvider _provider; private readonly IServerApplicationPaths _paths; + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The server application paths. + /// The database provider. public MigrateActivityLogDb(ILogger logger, IServerApplicationPaths paths, JellyfinDbProvider provider) { _logger = logger; @@ -27,19 +35,35 @@ namespace Jellyfin.Server.Migrations.Routines _paths = paths; } + /// public Guid Id => Guid.Parse("3793eb59-bc8c-456c-8b9f-bd5a62a42978"); + /// public string Name => "MigrateActivityLogDatabase"; + /// public void Perform() { + var logLevelDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "None", LogLevel.None }, + { "Trace", LogLevel.Trace }, + { "Debug", LogLevel.Debug }, + { "Information", LogLevel.Information }, + { "Info", LogLevel.Information }, + { "Warn", LogLevel.Warning }, + { "Warning", LogLevel.Warning }, + { "Error", LogLevel.Error }, + { "Critical", LogLevel.Critical } + }; + var dataPath = _paths.DataPath; using (var connection = SQLite3.Open( Path.Combine(dataPath, DbFilename), ConnectionFlags.ReadOnly, null)) { - _logger.LogInformation("Migrating the database may take a while, do not stop Jellyfin."); + _logger.LogWarning("Migrating the activity database may take a while, do not stop Jellyfin."); using var dbContext = _provider.CreateContext(); var queryResult = connection.Query("SELECT * FROM ActivityLog ORDER BY Id ASC"); @@ -56,9 +80,11 @@ namespace Jellyfin.Server.Migrations.Routines var newEntry = new ActivityLog( entry[1].ToString(), entry[4].ToString(), - entry[6].SQLiteType == SQLiteType.Null ? Guid.Empty : Guid.Parse(entry[6].ToString()), - entry[7].ReadDateTime(), - ParseLogLevel(entry[8].ToString())); + entry[6].SQLiteType == SQLiteType.Null ? Guid.Empty : Guid.Parse(entry[6].ToString())) + { + DateCreated = entry[7].ReadDateTime(), + LogSeverity = logLevelDictionary[entry[8].ToString()] + }; if (entry[2].SQLiteType != SQLiteType.Null) { @@ -75,6 +101,8 @@ namespace Jellyfin.Server.Migrations.Routines newEntry.ItemId = entry[5].ToString(); } + // Since code references the Id of the entries, this needs to be inserted in order. + // In order to do that, this is needed because EF Core doesn't provide a way to guarantee ordering for bulk inserts. dbContext.ActivityLogs.Add(newEntry); dbContext.SaveChanges(); } @@ -89,32 +117,5 @@ namespace Jellyfin.Server.Migrations.Routines _logger.LogError(e, "Error renaming legacy activity log database to 'activitylog.db.old'"); } } - - private LogLevel ParseLogLevel(string entry) - { - if (string.Equals(entry, "Debug", StringComparison.OrdinalIgnoreCase)) - { - return LogLevel.Debug; - } - - if (string.Equals(entry, "Information", StringComparison.OrdinalIgnoreCase) - || string.Equals(entry, "Info", StringComparison.OrdinalIgnoreCase)) - { - return LogLevel.Information; - } - - if (string.Equals(entry, "Warning", StringComparison.OrdinalIgnoreCase) - || string.Equals(entry, "Warn", StringComparison.OrdinalIgnoreCase)) - { - return LogLevel.Warning; - } - - if (string.Equals(entry, "Error", StringComparison.OrdinalIgnoreCase)) - { - return LogLevel.Error; - } - - return LogLevel.Trace; - } } } diff --git a/MediaBrowser.Api/Devices/DeviceService.cs b/MediaBrowser.Api/Devices/DeviceService.cs index 7004a2559..53eb9667d 100644 --- a/MediaBrowser.Api/Devices/DeviceService.cs +++ b/MediaBrowser.Api/Devices/DeviceService.cs @@ -1,5 +1,4 @@ using System.IO; -using System.Threading.Tasks; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Net; @@ -116,11 +115,6 @@ namespace MediaBrowser.Api.Devices return _deviceManager.GetDeviceOptions(request.Id); } - public object Get(GetCameraUploads request) - { - return ToOptimizedResult(_deviceManager.GetCameraUploadHistory(request.DeviceId)); - } - public void Delete(DeleteDevice request) { var sessions = _authRepo.Get(new AuthenticationInfoQuery @@ -134,35 +128,5 @@ namespace MediaBrowser.Api.Devices _sessionManager.Logout(session); } } - - public Task Post(PostCameraUpload request) - { - var deviceId = Request.QueryString["DeviceId"]; - var album = Request.QueryString["Album"]; - var id = Request.QueryString["Id"]; - var name = Request.QueryString["Name"]; - var req = Request.Response.HttpContext.Request; - - if (req.HasFormContentType) - { - var file = req.Form.Files.Count == 0 ? null : req.Form.Files[0]; - - return _deviceManager.AcceptCameraUpload(deviceId, file.OpenReadStream(), new LocalFileInfo - { - MimeType = file.ContentType, - Album = album, - Name = name, - Id = id - }); - } - - return _deviceManager.AcceptCameraUpload(deviceId, request.RequestStream, new LocalFileInfo - { - MimeType = Request.ContentType, - Album = album, - Name = name, - Id = id - }); - } } } diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 997b1c45a..93852e970 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -762,9 +762,7 @@ namespace MediaBrowser.Api.Library _activityManager.Create(new Jellyfin.Data.Entities.ActivityLog( string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Name, item.Name), "UserDownloadingContent", - auth.UserId, - DateTime.UtcNow, - LogLevel.Trace) + auth.UserId) { ShortOverview = string.Format(_localization.GetLocalizedString("AppDeviceValues"), auth.Client, auth.Device), }); diff --git a/MediaBrowser.Api/System/ActivityLogService.cs b/MediaBrowser.Api/System/ActivityLogService.cs index 0a5fc9433..f2c37d711 100644 --- a/MediaBrowser.Api/System/ActivityLogService.cs +++ b/MediaBrowser.Api/System/ActivityLogService.cs @@ -1,5 +1,3 @@ -using System; -using System.Globalization; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Activity; @@ -49,10 +47,6 @@ namespace MediaBrowser.Api.System public object Get(GetActivityLogs request) { - DateTime? minDate = string.IsNullOrWhiteSpace(request.MinDate) ? - (DateTime?)null : - DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); - var result = _activityManager.GetPagedResult(request.StartIndex, request.Limit); return ToOptimizedResult(result); diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs index 77d567631..4256bdb10 100644 --- a/MediaBrowser.Controller/Devices/IDeviceManager.cs +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -11,11 +11,6 @@ namespace MediaBrowser.Controller.Devices { public interface IDeviceManager { - /// - /// Occurs when [camera image uploaded]. - /// - event EventHandler> CameraImageUploaded; - /// /// Saves the capabilities. /// @@ -45,22 +40,6 @@ namespace MediaBrowser.Controller.Devices /// IEnumerable<DeviceInfo>. QueryResult GetDevices(DeviceQuery query); - /// - /// Gets the upload history. - /// - /// The device identifier. - /// ContentUploadHistory. - ContentUploadHistory GetCameraUploadHistory(string deviceId); - - /// - /// Accepts the upload. - /// - /// The device identifier. - /// The stream. - /// The file. - /// Task. - Task AcceptCameraUpload(string deviceId, Stream stream, LocalFileInfo file); - /// /// Determines whether this instance [can access device] the specified user identifier. /// diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 210e2b644..e100c0b1c 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30011.22 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.3 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server", "Jellyfin.Server\Jellyfin.Server.csproj", "{07E39F42-A2C6-4B32-AF8C-725F957A73FF}" EndProject -- cgit v1.2.3 From 1e9b2613c690f4afe561eb8401705834bb51b6b8 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 13 May 2020 15:35:14 -0400 Subject: Remove more unused code --- .../Devices/DeviceManager.cs | 58 +--------------------- MediaBrowser.Controller/Devices/IDeviceManager.cs | 2 - 2 files changed, 2 insertions(+), 58 deletions(-) diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index 7017be6eb..158cc6a74 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -20,7 +20,6 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Net; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; @@ -220,41 +219,6 @@ namespace Emby.Server.Implementations.Devices return _libraryManager.AddVirtualFolder(name, CollectionType.HomeVideos, libraryOptions, true); } - private Tuple GetUploadPath(DeviceInfo device) - { - var config = _config.GetUploadOptions(); - var path = config.CameraUploadPath; - - if (string.IsNullOrWhiteSpace(path)) - { - path = DefaultCameraUploadsPath; - } - - var topLibraryPath = path; - - if (config.EnableCameraUploadSubfolders) - { - path = Path.Combine(path, _fileSystem.GetValidFilename(device.Name)); - } - - return new Tuple(path, topLibraryPath, null); - } - - internal string GetUploadsPath() - { - var config = _config.GetUploadOptions(); - var path = config.CameraUploadPath; - - if (string.IsNullOrWhiteSpace(path)) - { - path = DefaultCameraUploadsPath; - } - - return path; - } - - private string DefaultCameraUploadsPath => Path.Combine(_config.CommonApplicationPaths.DataPath, "camerauploads"); - public bool CanAccessDevice(User user, string deviceId) { if (user == null) @@ -311,27 +275,9 @@ namespace Emby.Server.Implementations.Devices _logger = logger; } - public async Task RunAsync() + public Task RunAsync() { - if (!_config.Configuration.CameraUploadUpgraded && _config.Configuration.IsStartupWizardCompleted) - { - var path = _deviceManager.GetUploadsPath(); - - if (Directory.Exists(path)) - { - try - { - await _deviceManager.EnsureLibraryFolder(path, null).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error creating camera uploads library"); - } - - _config.Configuration.CameraUploadUpgraded = true; - _config.SaveConfiguration(); - } - } + return Task.CompletedTask; } #region IDisposable Support diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs index 4256bdb10..ef3f43c75 100644 --- a/MediaBrowser.Controller/Devices/IDeviceManager.cs +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -1,6 +1,4 @@ using System; -using System.IO; -using System.Threading.Tasks; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Devices; using MediaBrowser.Model.Events; -- cgit v1.2.3 From 992574291821ba417ac624aeb0bf0022159b5c30 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 13 May 2020 17:55:31 -0400 Subject: Implement more review suggestions --- .../Devices/DeviceManager.cs | 129 --------------------- .../Migrations/Routines/MigrateActivityLogDb.cs | 9 +- 2 files changed, 7 insertions(+), 131 deletions(-) diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index 158cc6a74..2283f2433 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -5,26 +5,18 @@ 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.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Security; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Devices; -using MediaBrowser.Model.Entities; using MediaBrowser.Model.Events; -using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; using MediaBrowser.Model.Users; -using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Devices { @@ -32,10 +24,7 @@ namespace Emby.Server.Implementations.Devices { private readonly IJsonSerializer _json; private readonly IUserManager _userManager; - private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _config; - private readonly ILibraryManager _libraryManager; - private readonly ILocalizationManager _localizationManager; private readonly IAuthenticationRepository _authRepo; private readonly Dictionary _capabilitiesCache; @@ -46,18 +35,12 @@ namespace Emby.Server.Implementations.Devices public DeviceManager( IAuthenticationRepository authRepo, IJsonSerializer json, - ILibraryManager libraryManager, - ILocalizationManager localizationManager, IUserManager userManager, - IFileSystem fileSystem, IServerConfigurationManager config) { _json = json; _userManager = userManager; - _fileSystem = fileSystem; _config = config; - _libraryManager = libraryManager; - _localizationManager = localizationManager; _authRepo = authRepo; _capabilitiesCache = new Dictionary(StringComparer.OrdinalIgnoreCase); } @@ -187,38 +170,6 @@ namespace Emby.Server.Implementations.Devices return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N", CultureInfo.InvariantCulture)); } - internal Task EnsureLibraryFolder(string path, string name) - { - var existingFolders = _libraryManager - .RootFolder - .Children - .OfType() - .Where(i => _fileSystem.AreEqual(path, i.Path) || _fileSystem.ContainsSubPath(i.Path, path)) - .ToList(); - - if (existingFolders.Count > 0) - { - return Task.CompletedTask; - } - - Directory.CreateDirectory(path); - - var libraryOptions = new LibraryOptions - { - PathInfos = new[] { new MediaPathInfo { Path = path } }, - EnablePhotos = true, - EnableRealtimeMonitor = false, - SaveLocalMetadata = true - }; - - if (string.IsNullOrWhiteSpace(name)) - { - name = _localizationManager.GetLocalizedString("HeaderCameraUploads"); - } - - return _libraryManager.AddVirtualFolder(name, CollectionType.HomeVideos, libraryOptions, true); - } - public bool CanAccessDevice(User user, string deviceId) { if (user == null) @@ -258,84 +209,4 @@ namespace Emby.Server.Implementations.Devices return policy.EnabledDevices.Contains(id, StringComparer.OrdinalIgnoreCase); } } - - public class DeviceManagerEntryPoint : IServerEntryPoint - { - private readonly DeviceManager _deviceManager; - private readonly IServerConfigurationManager _config; - private ILogger _logger; - - public DeviceManagerEntryPoint( - IDeviceManager deviceManager, - IServerConfigurationManager config, - ILogger logger) - { - _deviceManager = (DeviceManager)deviceManager; - _config = config; - _logger = logger; - } - - public Task RunAsync() - { - return Task.CompletedTask; - } - - #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - // TODO: dispose managed state (managed objects). - } - - // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. - // TODO: set large fields to null. - - disposedValue = true; - } - } - - // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. - // ~DeviceManagerEntryPoint() { - // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - // Dispose(false); - // } - - // This code added to correctly implement the disposable pattern. - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - // TODO: uncomment the following line if the finalizer is overridden above. - // GC.SuppressFinalize(this); - } - #endregion - } - - public class DevicesConfigStore : IConfigurationFactory - { - public IEnumerable GetConfigurations() - { - return new ConfigurationStore[] - { - new ConfigurationStore - { - Key = "devices", - ConfigurationType = typeof(DevicesOptions) - } - }; - } - } - - public static class UploadConfigExtension - { - public static DevicesOptions GetUploadOptions(this IConfigurationManager config) - { - return config.GetConfiguration("devices"); - } - } } diff --git a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs index faa163d19..1d684804d 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs @@ -77,13 +77,18 @@ namespace Jellyfin.Server.Migrations.Routines foreach (var entry in queryResult) { + if (!logLevelDictionary.TryGetValue(entry[8].ToString(), out var severity)) + { + severity = LogLevel.Trace; + } + var newEntry = new ActivityLog( entry[1].ToString(), entry[4].ToString(), entry[6].SQLiteType == SQLiteType.Null ? Guid.Empty : Guid.Parse(entry[6].ToString())) { DateCreated = entry[7].ReadDateTime(), - LogSeverity = logLevelDictionary[entry[8].ToString()] + LogSeverity = severity }; if (entry[2].SQLiteType != SQLiteType.Null) @@ -102,7 +107,7 @@ namespace Jellyfin.Server.Migrations.Routines } // Since code references the Id of the entries, this needs to be inserted in order. - // In order to do that, this is needed because EF Core doesn't provide a way to guarantee ordering for bulk inserts. + // In order to do that, we insert one by one because EF Core doesn't provide a way to guarantee ordering for bulk inserts. dbContext.ActivityLogs.Add(newEntry); dbContext.SaveChanges(); } -- cgit v1.2.3 From 2849d2b134691539ff990774073a8c03f2014918 Mon Sep 17 00:00:00 2001 From: aled Date: Wed, 13 May 2020 23:59:19 +0100 Subject: Fix compile warnings in Jellyfin.Naming.Tests --- .../Subtitles/SubtitleParserTests.cs | 6 ++--- .../TV/AbsoluteEpisodeNumberTests.cs | 2 +- .../Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs | 12 +++++----- .../TV/EpisodeNumberWithoutSeasonTests.cs | 2 +- .../TV/EpisodeWithoutSeasonTests.cs | 6 ++--- .../Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs | 2 +- .../Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs | 6 ++--- tests/Jellyfin.Naming.Tests/Video/Format3DTests.cs | 4 ++-- .../Video/MultiVersionTests.cs | 28 +++++++++++----------- tests/Jellyfin.Naming.Tests/Video/StubTests.cs | 4 ++-- .../Video/VideoListResolverTests.cs | 2 +- .../Video/VideoResolverTests.cs | 22 ++++++++--------- 12 files changed, 48 insertions(+), 48 deletions(-) diff --git a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs index 40d80607c..d11809de1 100644 --- a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs +++ b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs @@ -23,9 +23,9 @@ namespace Jellyfin.Naming.Tests.Subtitles var result = parser.ParseFile(input); - Assert.Equal(language, result.Language, true); - Assert.Equal(isDefault, result.IsDefault); - Assert.Equal(isForced, result.IsForced); + Assert.Equal(language, result?.Language, true); + Assert.Equal(isDefault, result?.IsDefault); + Assert.Equal(isForced, result?.IsForced); } [Theory] diff --git a/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs index 553d06681..356ba216d 100644 --- a/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/AbsoluteEpisodeNumberTests.cs @@ -21,7 +21,7 @@ namespace Jellyfin.Naming.Tests.TV var result = new EpisodeResolver(options) .Resolve(path, false, null, null, true); - Assert.Equal(episodeNumber, result.EpisodeNumber); + Assert.Equal(episodeNumber, result?.EpisodeNumber); } } } diff --git a/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs index 6ecffe80b..8e58b9243 100644 --- a/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs @@ -23,12 +23,12 @@ namespace Jellyfin.Naming.Tests.TV var result = new EpisodeResolver(options) .Resolve(path, false); - Assert.Null(result.SeasonNumber); - Assert.Null(result.EpisodeNumber); - Assert.Equal(year, result.Year); - Assert.Equal(month, result.Month); - Assert.Equal(day, result.Day); - Assert.Equal(seriesName, result.SeriesName, true); + Assert.Null(result?.SeasonNumber); + Assert.Null(result?.EpisodeNumber); + Assert.Equal(year, result?.Year); + Assert.Equal(month, result?.Month); + Assert.Equal(day, result?.Day); + Assert.Equal(seriesName, result?.SeriesName, true); } } } diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs index 0c7d9520e..e8348f6fe 100644 --- a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs @@ -30,7 +30,7 @@ namespace Jellyfin.Naming.Tests.TV var result = new EpisodeResolver(options) .Resolve(path, false); - Assert.Equal(episodeNumber, result.EpisodeNumber); + Assert.Equal(episodeNumber, result?.EpisodeNumber); } } } diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs index 364eb7ff8..d0418a49e 100644 --- a/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeWithoutSeasonTests.cs @@ -19,9 +19,9 @@ namespace Jellyfin.Naming.Tests.TV var result = new EpisodeResolver(options) .Resolve(path, false); - Assert.Equal(seasonNumber, result.SeasonNumber); - Assert.Equal(episodeNumber, result.EpisodeNumber); - Assert.Equal(seriesName, result.SeriesName, true); + Assert.Equal(seasonNumber, result?.SeasonNumber); + Assert.Equal(episodeNumber, result?.EpisodeNumber); + Assert.Equal(seriesName, result?.SeriesName, ignoreCase: true); } } } diff --git a/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs index 9eaf897b9..4837e3a3b 100644 --- a/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/SeasonNumberTests.cs @@ -59,7 +59,7 @@ namespace Jellyfin.Naming.Tests.TV var result = new EpisodeResolver(_namingOptions) .Resolve(path, false); - Assert.Equal(expected, result.SeasonNumber); + Assert.Equal(expected, result?.SeasonNumber); } } } diff --git a/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs index de253ce37..40b41b9f3 100644 --- a/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/SimpleEpisodeTests.cs @@ -31,9 +31,9 @@ namespace Jellyfin.Naming.Tests.TV var result = new EpisodeResolver(options) .Resolve(path, false); - Assert.Equal(seasonNumber, result.SeasonNumber); - Assert.Equal(episodeNumber, result.EpisodeNumber); - Assert.Equal(seriesName, result.SeriesName, true); + Assert.Equal(seasonNumber, result?.SeasonNumber); + Assert.Equal(episodeNumber, result?.EpisodeNumber); + Assert.Equal(seriesName, result?.SeriesName, true); } } } diff --git a/tests/Jellyfin.Naming.Tests/Video/Format3DTests.cs b/tests/Jellyfin.Naming.Tests/Video/Format3DTests.cs index d2b3d6ff0..69de96a47 100644 --- a/tests/Jellyfin.Naming.Tests/Video/Format3DTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/Format3DTests.cs @@ -25,8 +25,8 @@ namespace Jellyfin.Naming.Tests.Video var result = new VideoResolver(_namingOptions).ResolveFile(@"C:/Users/media/Desktop/Video Test/Movies/Oblivion/Oblivion.3d.hsbs.mkv"); - Assert.Equal("hsbs", result.Format3D); - Assert.Equal("Oblivion", result.Name); + Assert.Equal("hsbs", result?.Format3D); + Assert.Equal("Oblivion", result?.Name); } [Fact] diff --git a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs index 03fe32b6e..4b1ab6c88 100644 --- a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs @@ -12,7 +12,7 @@ namespace Jellyfin.Naming.Tests.Video // FIXME // [Fact] - public void TestMultiEdition1() + private void TestMultiEdition1() { var files = new[] { @@ -37,7 +37,7 @@ namespace Jellyfin.Naming.Tests.Video // FIXME // [Fact] - public void TestMultiEdition2() + private void TestMultiEdition2() { var files = new[] { @@ -85,7 +85,7 @@ namespace Jellyfin.Naming.Tests.Video // FIXME // [Fact] - public void TestLetterFolders() + private void TestLetterFolders() { var files = new[] { @@ -114,7 +114,7 @@ namespace Jellyfin.Naming.Tests.Video // FIXME // [Fact] - public void TestMultiVersionLimit() + private void TestMultiVersionLimit() { var files = new[] { @@ -144,7 +144,7 @@ namespace Jellyfin.Naming.Tests.Video // FIXME // [Fact] - public void TestMultiVersionLimit2() + private void TestMultiVersionLimit2() { var files = new[] { @@ -175,7 +175,7 @@ namespace Jellyfin.Naming.Tests.Video // FIXME // [Fact] - public void TestMultiVersion3() + private void TestMultiVersion3() { var files = new[] { @@ -202,7 +202,7 @@ namespace Jellyfin.Naming.Tests.Video // FIXME // [Fact] - public void TestMultiVersion4() + private void TestMultiVersion4() { // Test for false positive @@ -231,7 +231,7 @@ namespace Jellyfin.Naming.Tests.Video // FIXME // [Fact] - public void TestMultiVersion5() + private void TestMultiVersion5() { var files = new[] { @@ -264,7 +264,7 @@ namespace Jellyfin.Naming.Tests.Video // FIXME // [Fact] - public void TestMultiVersion6() + private void TestMultiVersion6() { var files = new[] { @@ -297,7 +297,7 @@ namespace Jellyfin.Naming.Tests.Video // FIXME // [Fact] - public void TestMultiVersion7() + private void TestMultiVersion7() { var files = new[] { @@ -319,7 +319,7 @@ namespace Jellyfin.Naming.Tests.Video // FIXME // [Fact] - public void TestMultiVersion8() + private void TestMultiVersion8() { // This is not actually supported yet @@ -353,7 +353,7 @@ namespace Jellyfin.Naming.Tests.Video // FIXME // [Fact] - public void TestMultiVersion9() + private void TestMultiVersion9() { // Test for false positive @@ -382,7 +382,7 @@ namespace Jellyfin.Naming.Tests.Video // FIXME // [Fact] - public void TestMultiVersion10() + private void TestMultiVersion10() { var files = new[] { @@ -406,7 +406,7 @@ namespace Jellyfin.Naming.Tests.Video // FIXME // [Fact] - public void TestMultiVersion11() + private void TestMultiVersion11() { // Currently not supported but we should probably handle this. diff --git a/tests/Jellyfin.Naming.Tests/Video/StubTests.cs b/tests/Jellyfin.Naming.Tests/Video/StubTests.cs index e31d97e2e..30ba94136 100644 --- a/tests/Jellyfin.Naming.Tests/Video/StubTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/StubTests.cs @@ -31,10 +31,10 @@ namespace Jellyfin.Naming.Tests.Video var result = new VideoResolver(_namingOptions).ResolveFile(@"C:/Users/media/Desktop/Video Test/Movies/Oblivion/Oblivion.dvd.disc"); - Assert.Equal("Oblivion", result.Name); + Assert.Equal("Oblivion", result?.Name); } - private void Test(string path, bool isStub, string stubType) + private void Test(string path, bool isStub, string? stubType) { var isStubResult = StubResolver.TryResolveFile(path, _namingOptions, out var stubTypeResult); diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs index 566dc9f7c..4832d1593 100644 --- a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs @@ -11,7 +11,7 @@ namespace Jellyfin.Naming.Tests.Video private readonly NamingOptions _namingOptions = new NamingOptions(); // FIXME // [Fact] - public void TestStackAndExtras() + private void TestStackAndExtras() { // No stacking here because there is no part/disc/etc var files = new[] diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs index 114735cee..a901c5470 100644 --- a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs @@ -184,17 +184,17 @@ namespace Jellyfin.Naming.Tests.Video var result = new VideoResolver(_namingOptions).ResolveFile(expectedResult.Path); Assert.NotNull(result); - Assert.Equal(result.Path, expectedResult.Path); - Assert.Equal(result.Container, expectedResult.Container); - Assert.Equal(result.Name, expectedResult.Name); - Assert.Equal(result.Year, expectedResult.Year); - Assert.Equal(result.ExtraType, expectedResult.ExtraType); - Assert.Equal(result.Format3D, expectedResult.Format3D); - Assert.Equal(result.Is3D, expectedResult.Is3D); - Assert.Equal(result.IsStub, expectedResult.IsStub); - Assert.Equal(result.StubType, expectedResult.StubType); - Assert.Equal(result.IsDirectory, expectedResult.IsDirectory); - Assert.Equal(result.FileNameWithoutExtension, expectedResult.FileNameWithoutExtension); + Assert.Equal(result?.Path, expectedResult.Path); + Assert.Equal(result?.Container, expectedResult.Container); + Assert.Equal(result?.Name, expectedResult.Name); + Assert.Equal(result?.Year, expectedResult.Year); + Assert.Equal(result?.ExtraType, expectedResult.ExtraType); + Assert.Equal(result?.Format3D, expectedResult.Format3D); + Assert.Equal(result?.Is3D, expectedResult.Is3D); + Assert.Equal(result?.IsStub, expectedResult.IsStub); + Assert.Equal(result?.StubType, expectedResult.StubType); + Assert.Equal(result?.IsDirectory, expectedResult.IsDirectory); + Assert.Equal(result?.FileNameWithoutExtension, expectedResult.FileNameWithoutExtension); } } } -- cgit v1.2.3 From 428e1b04fc942b66dafaa04081d3b9dc5de62f1d Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Thu, 14 May 2020 18:11:32 +0200 Subject: Add color transfer to ffprobe results --- MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs | 7 +++++++ MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs index 0b2f1d231..fa51e61a2 100644 --- a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs +++ b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs @@ -278,5 +278,12 @@ namespace MediaBrowser.MediaEncoding.Probing /// The disposition. [JsonPropertyName("disposition")] public IReadOnlyDictionary Disposition { get; set; } + + /// + /// Gets or sets the color transfer. + /// + /// The color transfer. + [JsonPropertyName("color_transfer")] + public string ColorTransfer { get; set; } } } diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index b24d97f4e..41daa22d6 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -695,6 +695,11 @@ namespace MediaBrowser.MediaEncoding.Probing { stream.RefFrames = streamInfo.Refs; } + + if (!string.IsNullOrEmpty(streamInfo.ColorTransfer)) + { + stream.ColorTransfer = streamInfo.ColorTransfer; + } } else { -- cgit v1.2.3 From 234292453f9b05f1fd6c2a00280a1a4b4254a4fa Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Thu, 14 May 2020 18:44:51 +0200 Subject: Add HLG to the video range detection --- MediaBrowser.Model/Entities/MediaStream.cs | 36 +++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index e7e8d7cec..dd17623bd 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -34,8 +34,22 @@ namespace MediaBrowser.Model.Entities /// The language. public string Language { get; set; } + /// + /// Gets or sets the color transfer. + /// + /// The color transfer. public string ColorTransfer { get; set; } + + /// + /// Gets or sets the color primaries. + /// + /// The color primaries. public string ColorPrimaries { get; set; } + + /// + /// Gets or sets the color space. + /// + /// The color space. public string ColorSpace { get; set; } /// @@ -44,11 +58,28 @@ namespace MediaBrowser.Model.Entities /// The comment. public string Comment { get; set; } + /// + /// Gets or sets the time base. + /// + /// The time base. public string TimeBase { get; set; } + + /// + /// Gets or sets the codec time base. + /// + /// The codec time base. public string CodecTimeBase { get; set; } + /// + /// Gets or sets the title. + /// + /// The title. public string Title { get; set; } + /// + /// Gets or sets the video range. + /// + /// The video range. public string VideoRange { get @@ -60,7 +91,8 @@ namespace MediaBrowser.Model.Entities var colorTransfer = ColorTransfer; - if (string.Equals(colorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(colorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) + || string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) { return "HDR"; } @@ -70,7 +102,9 @@ namespace MediaBrowser.Model.Entities } public string localizedUndefined { get; set; } + public string localizedDefault { get; set; } + public string localizedForced { get; set; } public string DisplayTitle -- cgit v1.2.3 From 2e18142bb32554cf162827ab1ca7a8040107baea Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Thu, 14 May 2020 18:52:42 +0200 Subject: Add color primaries to ffprobe output --- MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs | 7 +++++++ MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs index fa51e61a2..d7b0e0e64 100644 --- a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs +++ b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs @@ -285,5 +285,12 @@ namespace MediaBrowser.MediaEncoding.Probing /// The color transfer. [JsonPropertyName("color_transfer")] public string ColorTransfer { get; set; } + + /// + /// Gets or sets the color transfer. + /// + /// The color transfer. + [JsonPropertyName("color_primaries")] + public string ColorPrimaries { get; set; } } } diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 41daa22d6..d3f8094b9 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -700,6 +700,11 @@ namespace MediaBrowser.MediaEncoding.Probing { stream.ColorTransfer = streamInfo.ColorTransfer; } + + if (!string.IsNullOrEmpty(streamInfo.ColorPrimaries)) + { + stream.ColorPrimaries = streamInfo.ColorPrimaries; + } } else { -- cgit v1.2.3 From 3ff6e3ff65200094db881436be4deb9b7aee1763 Mon Sep 17 00:00:00 2001 From: aled Date: Thu, 14 May 2020 18:59:10 +0100 Subject: Add code analyzers to Jellyfin.Naming.Tests and fix resulting warnings --- .../Jellyfin.Naming.Tests.csproj | 12 +++++++++++- tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs | 2 -- .../TV/EpisodeNumberWithoutSeasonTests.cs | 1 - .../TV/EpisodePathParserTest.cs | 3 +-- .../Jellyfin.Naming.Tests/Video/MultiVersionTests.cs | 15 --------------- tests/Jellyfin.Naming.Tests/Video/StackTests.cs | 10 +++++----- .../Video/VideoListResolverTests.cs | 20 +------------------- .../Video/VideoResolverTests.cs | 1 - 8 files changed, 18 insertions(+), 46 deletions(-) diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj index ac0c970c1..8b14cf800 100644 --- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj +++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj @@ -1,4 +1,4 @@ - + @@ -21,5 +21,15 @@ + + + + + + + + + ../jellyfin-tests.ruleset + diff --git a/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs b/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs index 8e58b9243..2937914b9 100644 --- a/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/DailyEpisodeTests.cs @@ -6,8 +6,6 @@ namespace Jellyfin.Naming.Tests.TV { public class DailyEpisodeTests { - - [Theory] [InlineData(@"/server/anything_1996.11.14.mp4", "anything", 1996, 11, 14)] [InlineData(@"/server/anything_1996-11-14.mp4", "anything", 1996, 11, 14)] diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs index e8348f6fe..8bd1a43d6 100644 --- a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs +++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberWithoutSeasonTests.cs @@ -6,7 +6,6 @@ namespace Jellyfin.Naming.Tests.TV { public class EpisodeNumberWithoutSeasonTests { - [Theory] [InlineData(8, @"The Simpsons/The Simpsons.S25E08.Steal this episode.mp4")] [InlineData(2, @"The Simpsons/The Simpsons - 02 - Ep Name.avi")] diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs index 4b5606715..03aeb7f76 100644 --- a/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs +++ b/tests/Jellyfin.Naming.Tests/TV/EpisodePathParserTest.cs @@ -1,4 +1,4 @@ -using Emby.Naming.Common; +using Emby.Naming.Common; using Emby.Naming.TV; using Xunit; @@ -35,7 +35,6 @@ namespace Jellyfin.Naming.Tests.TV // TODO: [InlineData("Watchmen (2019)/Watchmen 1x03 [WEBDL-720p][EAC3 5.1][h264][-TBS] - She Was Killed by Space Junk.mkv", "Watchmen (2019)", 1, 3)] // TODO: [InlineData("/The.Legend.of.Condor.Heroes.2017.V2.web-dl.1080p.h264.aac-hdctv/The.Legend.of.Condor.Heroes.2017.E07.V2.web-dl.1080p.h264.aac-hdctv.mkv", "The Legend of Condor Heroes 2017", 1, 7)] public void ParseEpisodesCorrectly(string path, string name, int season, int episode) - { NamingOptions o = new NamingOptions(); EpisodePathParser p = new EpisodePathParser(o); diff --git a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs index 4b1ab6c88..4198d69ff 100644 --- a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs @@ -28,7 +28,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Single(result); @@ -53,7 +52,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Single(result); @@ -76,7 +74,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Single(result); @@ -104,7 +101,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Equal(7, result.Count); @@ -134,7 +130,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Single(result); @@ -165,7 +160,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Equal(9, result.Count); @@ -192,7 +186,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Equal(5, result.Count); @@ -221,7 +214,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Equal(5, result.Count); @@ -251,7 +243,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Single(result); @@ -284,7 +275,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Single(result); @@ -311,7 +301,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Equal(2, result.Count); @@ -340,7 +329,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Single(result); @@ -372,7 +360,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Equal(5, result.Count); @@ -396,7 +383,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Single(result); @@ -422,7 +408,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Single(result); diff --git a/tests/Jellyfin.Naming.Tests/Video/StackTests.cs b/tests/Jellyfin.Naming.Tests/Video/StackTests.cs index 3630a07e4..8794d3ebe 100644 --- a/tests/Jellyfin.Naming.Tests/Video/StackTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/StackTests.cs @@ -368,11 +368,11 @@ namespace Jellyfin.Naming.Tests.Video { var files = new[] { - new FileSystemMetadata{FullName = "Bad Boys (2006) part1.mkv", IsDirectory = false}, - new FileSystemMetadata{FullName = "Bad Boys (2006) part2.mkv", IsDirectory = false}, - new FileSystemMetadata{FullName = "300 (2006) part2", IsDirectory = true}, - new FileSystemMetadata{FullName = "300 (2006) part3", IsDirectory = true}, - new FileSystemMetadata{FullName = "300 (2006) part1", IsDirectory = true} + new FileSystemMetadata { FullName = "Bad Boys (2006) part1.mkv", IsDirectory = false }, + new FileSystemMetadata { FullName = "Bad Boys (2006) part2.mkv", IsDirectory = false }, + new FileSystemMetadata { FullName = "300 (2006) part2", IsDirectory = true }, + new FileSystemMetadata { FullName = "300 (2006) part3", IsDirectory = true }, + new FileSystemMetadata { FullName = "300 (2006) part1", IsDirectory = true } }; var resolver = GetResolver(); diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs index 4832d1593..12c4a50fe 100644 --- a/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/VideoListResolverTests.cs @@ -9,6 +9,7 @@ namespace Jellyfin.Naming.Tests.Video public class VideoListResolverTests { private readonly NamingOptions _namingOptions = new NamingOptions(); + // FIXME // [Fact] private void TestStackAndExtras() @@ -45,7 +46,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Equal(5, result.Count); @@ -74,7 +74,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Single(result); @@ -95,7 +94,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Single(result); @@ -116,7 +114,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Single(result); @@ -138,7 +135,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Single(result); @@ -159,7 +155,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Single(result); @@ -184,7 +179,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Equal(5, result.Count); @@ -205,7 +199,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = true, FullName = i - }).ToList()).ToList(); Assert.Single(result); @@ -227,7 +220,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = true, FullName = i - }).ToList()).ToList(); Assert.Equal(2, result.Count); @@ -249,7 +241,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Single(result); @@ -271,7 +262,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Single(result); @@ -294,7 +284,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Single(result); @@ -317,7 +306,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Equal(2, result.Count); @@ -337,7 +325,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Single(result); @@ -357,7 +344,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Single(result); @@ -378,7 +364,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Single(result); @@ -399,7 +384,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Single(result); @@ -422,7 +406,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Equal(4, result.Count); @@ -443,7 +426,6 @@ namespace Jellyfin.Naming.Tests.Video { IsDirectory = false, FullName = i - }).ToList()).ToList(); Assert.Single(result); diff --git a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs index a901c5470..99828b2eb 100644 --- a/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs +++ b/tests/Jellyfin.Naming.Tests/Video/VideoResolverTests.cs @@ -176,7 +176,6 @@ namespace Jellyfin.Naming.Tests.Video }; } - [Theory] [MemberData(nameof(GetResolveFileTestData))] public void ResolveFile_ValidFileName_Success(VideoFileInfo expectedResult) -- cgit v1.2.3 From fa1fef109911c734657f854f259681000a75f13a Mon Sep 17 00:00:00 2001 From: Erik Rigtorp Date: Thu, 14 May 2020 11:56:25 -0700 Subject: Update MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs Co-authored-by: Vasily --- MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs b/MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs index 47d501247..b6c722659 100644 --- a/MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs +++ b/MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs @@ -80,7 +80,7 @@ namespace MediaBrowser.Providers.Tmdb.Movies var parsedName = _libraryManager.ParseName(name); var yearInName = parsedName.Year; name = parsedName.Name; - year = year ?? yearInName; + year ??= yearInName; _logger.LogInformation("TmdbSearch: Finding id for item: {0} ({1})", name, year); var language = idInfo.MetadataLanguage.ToLowerInvariant(); -- cgit v1.2.3 From de351839033815ad0e1ee15e3e0b5cc095065d25 Mon Sep 17 00:00:00 2001 From: Erik Rigtorp Date: Thu, 14 May 2020 11:56:31 -0700 Subject: Update MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs Co-authored-by: Vasily --- MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs b/MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs index b6c722659..aa42fd81a 100644 --- a/MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs +++ b/MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs @@ -100,7 +100,7 @@ namespace MediaBrowser.Providers.Tmdb.Movies } } - // Ideally retrying alternatives should be done outside the search + // TODO: retrying alternatives should be done outside the search // provider so that the retry logic can be common for all search // providers if (results.Count == 0) -- cgit v1.2.3 From 4eb4ad3be7909f7a42aadcd442c0c7b77ce63c01 Mon Sep 17 00:00:00 2001 From: artiume Date: Thu, 14 May 2020 17:03:53 -0400 Subject: Update Books Resolver File Types --- Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs index 0b93ebeb8..503de0b4e 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Books/BookResolver.cs @@ -11,7 +11,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books { public class BookResolver : MediaBrowser.Controller.Resolvers.ItemResolver { - private readonly string[] _validExtensions = { ".pdf", ".epub", ".mobi", ".cbr", ".cbz", ".azw3" }; + private readonly string[] _validExtensions = { ".azw", ".azw3", ".cb7", ".cbr", ".cbt", ".cbz", ".epub", ".mobi", ".opf", ".pdf" }; protected override Book Resolve(ItemResolveArgs args) { -- cgit v1.2.3 From b94afc597c4d51f67552c9ba2c25bdb8df6d8599 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 14 May 2020 17:13:45 -0400 Subject: Address review comments --- Emby.Server.Implementations/ApplicationHost.cs | 11 --- .../Configuration/ServerConfigurationManager.cs | 6 -- .../Emby.Server.Implementations.csproj | 3 +- Jellyfin.Data/Entities/ActivityLog.cs | 83 +++++++++++----------- Jellyfin.Data/Jellyfin.Data.csproj | 6 +- .../Activity/ActivityManager.cs | 14 ++-- .../Jellyfin.Server.Implementations.csproj | 8 +++ Jellyfin.Server.Implementations/JellyfinDb.cs | 2 +- .../JellyfinDbProvider.cs | 2 +- .../20200502231229_InitialSchema.Designer.cs | 73 ------------------- .../Migrations/20200502231229_InitialSchema.cs | 46 ------------ .../20200514181226_AddActivityLog.Designer.cs | 72 +++++++++++++++++++ .../Migrations/20200514181226_AddActivityLog.cs | 46 ++++++++++++ .../Migrations/DesignTimeJellyfinDbFactory.cs | 7 +- .../Migrations/JellyfinDbModelSnapshot.cs | 4 +- Jellyfin.Server/CoreAppHost.cs | 14 ++++ Jellyfin.Server/Jellyfin.Server.csproj | 8 +-- MediaBrowser.Api/System/ActivityLogService.cs | 13 +++- MediaBrowser.Model/Activity/IActivityManager.cs | 3 +- .../Configuration/ServerConfiguration.cs | 2 - MediaBrowser.Model/Devices/DeviceOptions.cs | 9 +++ MediaBrowser.Model/Devices/DevicesOptions.cs | 23 ------ 22 files changed, 221 insertions(+), 234 deletions(-) delete mode 100644 Jellyfin.Server.Implementations/Migrations/20200502231229_InitialSchema.Designer.cs delete mode 100644 Jellyfin.Server.Implementations/Migrations/20200502231229_InitialSchema.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20200514181226_AddActivityLog.Designer.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20200514181226_AddActivityLog.cs create mode 100644 MediaBrowser.Model/Devices/DeviceOptions.cs delete mode 100644 MediaBrowser.Model/Devices/DevicesOptions.cs diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 371b5a5b9..8e5c3c9cf 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -46,8 +46,6 @@ using Emby.Server.Implementations.Session; using Emby.Server.Implementations.SocketSharp; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; -using Jellyfin.Server.Implementations; -using Jellyfin.Server.Implementations.Activity; using MediaBrowser.Api; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; @@ -547,13 +545,6 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); - // TODO: properly set up scoping and switch to AddDbContextPool - serviceCollection.AddDbContext( - options => options.UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"), - ServiceLifetime.Transient); - - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(_fileSystemManager); serviceCollection.AddSingleton(); @@ -664,8 +655,6 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs index a6eaf2d0a..305e67e8c 100644 --- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -193,12 +193,6 @@ namespace Emby.Server.Implementations.Configuration changed = true; } - if (!config.CameraUploadUpgraded) - { - config.CameraUploadUpgraded = true; - changed = true; - } - if (!config.CollectionsUpgraded) { config.CollectionsUpgraded = true; diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index dccbe2a9a..896e4310e 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -9,7 +9,6 @@ - @@ -51,7 +50,7 @@ - netcoreapp3.1 + netstandard2.1 false true diff --git a/Jellyfin.Data/Entities/ActivityLog.cs b/Jellyfin.Data/Entities/ActivityLog.cs index df3026a77..8fbf6eaab 100644 --- a/Jellyfin.Data/Entities/ActivityLog.cs +++ b/Jellyfin.Data/Entities/ActivityLog.cs @@ -5,34 +5,18 @@ using Microsoft.Extensions.Logging; namespace Jellyfin.Data.Entities { - public partial class ActivityLog + /// + /// An entity referencing an activity log entry. + /// + public partial class ActivityLog : ISavingChanges { - partial void Init(); - - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected ActivityLog() - { - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static ActivityLog CreateActivityLogUnsafe() - { - return new ActivityLog(); - } - /// - /// Public constructor with required data + /// Initializes a new instance of the class. + /// Public constructor with required data. /// - /// - /// - /// - /// - /// + /// The name. + /// The type. + /// The user id. public ActivityLog(string name, string type, Guid userId) { if (string.IsNullOrEmpty(name)) @@ -54,14 +38,21 @@ namespace Jellyfin.Data.Entities Init(); } + /// + /// Initializes a new instance of the class. + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected ActivityLog() + { + Init(); + } + /// /// Static create function (for use in LINQ queries, etc.) /// - /// - /// - /// - /// - /// + /// The name. + /// The type. + /// The user's id. public static ActivityLog Create(string name, string type, Guid userId) { return new ActivityLog(name, type, userId); @@ -72,7 +63,8 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Identity, Indexed, Required + /// Gets the identity of this instance. + /// This is the key in the backing database. /// [Key] [Required] @@ -80,7 +72,8 @@ namespace Jellyfin.Data.Entities public int Id { get; protected set; } /// - /// Required, Max length = 512 + /// Gets or sets the name. + /// Required, Max length = 512. /// [Required] [MaxLength(512)] @@ -88,21 +81,24 @@ namespace Jellyfin.Data.Entities public string Name { get; set; } /// - /// Max length = 512 + /// Gets or sets the overview. + /// Max length = 512. /// [MaxLength(512)] [StringLength(512)] public string Overview { get; set; } /// - /// Max length = 512 + /// Gets or sets the short overview. + /// Max length = 512. /// [MaxLength(512)] [StringLength(512)] public string ShortOverview { get; set; } /// - /// Required, Max length = 256 + /// Gets or sets the type. + /// Required, Max length = 256. /// [Required] [MaxLength(256)] @@ -110,41 +106,48 @@ namespace Jellyfin.Data.Entities public string Type { get; set; } /// - /// Required + /// Gets or sets the user id. + /// Required. /// [Required] public Guid UserId { get; set; } /// - /// Max length = 256 + /// Gets or sets the item id. + /// Max length = 256. /// [MaxLength(256)] [StringLength(256)] public string ItemId { get; set; } /// - /// Required + /// Gets or sets the date created. This should be in UTC. + /// Required. /// [Required] public DateTime DateCreated { get; set; } /// - /// Required + /// Gets or sets the log severity. Default is . + /// Required. /// [Required] public LogLevel LogSeverity { get; set; } /// + /// Gets or sets the row version. /// Required, ConcurrencyToken. /// [ConcurrencyCheck] [Required] public uint RowVersion { get; set; } + partial void Init(); + + /// public void OnSavingChanges() { RowVersion++; } } } - diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index 8eae366ba..b2a3f7eb3 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -17,14 +17,10 @@ - + - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs index 531b529dc..0b398b60c 100644 --- a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs +++ b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs @@ -50,31 +50,31 @@ namespace Jellyfin.Server.Implementations.Activity /// public QueryResult GetPagedResult( - Func, IEnumerable> func, + Func, IQueryable> func, int? startIndex, int? limit) { using var dbContext = _provider.CreateContext(); - var result = func.Invoke(dbContext.ActivityLogs).AsQueryable(); + var query = func(dbContext.ActivityLogs).OrderByDescending(entry => entry.DateCreated).AsQueryable(); if (startIndex.HasValue) { - result = result.Where(entry => entry.Id >= startIndex.Value); + query = query.Skip(startIndex.Value); } if (limit.HasValue) { - result = result.OrderByDescending(entry => entry.DateCreated).Take(limit.Value); + query = query.Take(limit.Value); } // This converts the objects from the new database model to the old for compatibility with the existing API. - var list = result.Select(entry => ConvertToOldModel(entry)).ToList(); + var list = query.AsEnumerable().Select(ConvertToOldModel).ToList(); - return new QueryResult() + return new QueryResult { Items = list, - TotalRecordCount = list.Count + TotalRecordCount = dbContext.ActivityLogs.Count() }; } diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index a31f28f64..149ca5020 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -25,6 +25,14 @@ + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 6fc8d251b..23714b24a 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -110,7 +110,7 @@ namespace Jellyfin.Server.Implementations foreach (var entity in ChangeTracker.Entries().Where(e => e.State == EntityState.Modified)) { var saveEntity = entity.Entity as ISavingChanges; - saveEntity.OnSavingChanges(); + saveEntity?.OnSavingChanges(); } return base.SaveChanges(); diff --git a/Jellyfin.Server.Implementations/JellyfinDbProvider.cs b/Jellyfin.Server.Implementations/JellyfinDbProvider.cs index 8fdeab088..eab531d38 100644 --- a/Jellyfin.Server.Implementations/JellyfinDbProvider.cs +++ b/Jellyfin.Server.Implementations/JellyfinDbProvider.cs @@ -27,7 +27,7 @@ namespace Jellyfin.Server.Implementations /// The newly created context. public JellyfinDb CreateContext() { - return _serviceProvider.GetService(); + return _serviceProvider.GetRequiredService(); } } } diff --git a/Jellyfin.Server.Implementations/Migrations/20200502231229_InitialSchema.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200502231229_InitialSchema.Designer.cs deleted file mode 100644 index e1ee9b34a..000000000 --- a/Jellyfin.Server.Implementations/Migrations/20200502231229_InitialSchema.Designer.cs +++ /dev/null @@ -1,73 +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("20200502231229_InitialSchema")] - partial class InitialSchema - { - 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"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Jellyfin.Server.Implementations/Migrations/20200502231229_InitialSchema.cs b/Jellyfin.Server.Implementations/Migrations/20200502231229_InitialSchema.cs deleted file mode 100644 index 42fac865c..000000000 --- a/Jellyfin.Server.Implementations/Migrations/20200502231229_InitialSchema.cs +++ /dev/null @@ -1,46 +0,0 @@ -#pragma warning disable CS1591 -#pragma warning disable SA1601 - -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Jellyfin.Server.Implementations.Migrations -{ - public partial class InitialSchema : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.EnsureSchema( - name: "jellyfin"); - - migrationBuilder.CreateTable( - name: "ActivityLog", - schema: "jellyfin", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Name = table.Column(maxLength: 512, nullable: false), - Overview = table.Column(maxLength: 512, nullable: true), - ShortOverview = table.Column(maxLength: 512, nullable: true), - Type = table.Column(maxLength: 256, nullable: false), - UserId = table.Column(nullable: false), - ItemId = table.Column(maxLength: 256, nullable: true), - DateCreated = table.Column(nullable: false), - LogSeverity = table.Column(nullable: false), - RowVersion = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ActivityLog", x => x.Id); - }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "ActivityLog", - schema: "jellyfin"); - } - } -} diff --git a/Jellyfin.Server.Implementations/Migrations/20200514181226_AddActivityLog.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200514181226_AddActivityLog.Designer.cs new file mode 100644 index 000000000..98a83b745 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200514181226_AddActivityLog.Designer.cs @@ -0,0 +1,72 @@ +#pragma warning disable CS1591 + +// +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("20200514181226_AddActivityLog")] + partial class AddActivityLog + { + 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("ActivityLogs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/20200514181226_AddActivityLog.cs b/Jellyfin.Server.Implementations/Migrations/20200514181226_AddActivityLog.cs new file mode 100644 index 000000000..5e0b454d8 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200514181226_AddActivityLog.cs @@ -0,0 +1,46 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1601 + +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Jellyfin.Server.Implementations.Migrations +{ + public partial class AddActivityLog : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.EnsureSchema( + name: "jellyfin"); + + migrationBuilder.CreateTable( + name: "ActivityLogs", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(maxLength: 512, nullable: false), + Overview = table.Column(maxLength: 512, nullable: true), + ShortOverview = table.Column(maxLength: 512, nullable: true), + Type = table.Column(maxLength: 256, nullable: false), + UserId = table.Column(nullable: false), + ItemId = table.Column(maxLength: 256, nullable: true), + DateCreated = table.Column(nullable: false), + LogSeverity = table.Column(nullable: false), + RowVersion = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ActivityLogs", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ActivityLogs", + schema: "jellyfin"); + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs b/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs index 23a0fdc78..a1b58eb5a 100644 --- a/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs +++ b/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs @@ -1,6 +1,3 @@ -#pragma warning disable CS1591 -#pragma warning disable SA1601 - using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; @@ -15,7 +12,9 @@ namespace Jellyfin.Server.Implementations.Migrations public JellyfinDb CreateDbContext(string[] args) { var optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder.UseSqlite("Data Source=jellyfin.db"); + optionsBuilder.UseSqlite( + "Data Source=jellyfin.db", + opt => opt.MigrationsAssembly("Jellyfin.Migrations")); return new JellyfinDb(optionsBuilder.Options); } diff --git a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs index 27f5fe24b..1e7ffd235 100644 --- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs +++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs @@ -1,9 +1,7 @@ // using System; -using Jellyfin.Server.Implementations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Jellyfin.Server.Implementations.Migrations { @@ -60,7 +58,7 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasKey("Id"); - b.ToTable("ActivityLog"); + b.ToTable("ActivityLogs"); }); #pragma warning restore 612, 618 } diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index f678e714c..331a32c73 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -1,12 +1,17 @@ using System; using System.Collections.Generic; +using System.IO; using System.Reflection; using Emby.Drawing; using Emby.Server.Implementations; using Jellyfin.Drawing.Skia; +using Jellyfin.Server.Implementations; +using Jellyfin.Server.Implementations.Activity; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Drawing; +using MediaBrowser.Model.Activity; using MediaBrowser.Model.IO; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -56,6 +61,15 @@ namespace Jellyfin.Server Logger.LogWarning($"Skia not available. Will fallback to {nameof(NullImageEncoder)}."); } + // TODO: Set up scoping and use AddDbContextPool + serviceCollection.AddDbContext( + options => options.UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"), + ServiceLifetime.Transient); + + serviceCollection.AddSingleton(); + + serviceCollection.AddSingleton(); + base.RegisterServices(serviceCollection); } diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 4194070aa..9eec6ed4e 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -13,9 +13,6 @@ true true enable - - - True @@ -44,10 +41,6 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - @@ -67,6 +60,7 @@ + diff --git a/MediaBrowser.Api/System/ActivityLogService.cs b/MediaBrowser.Api/System/ActivityLogService.cs index f2c37d711..a6bacad4f 100644 --- a/MediaBrowser.Api/System/ActivityLogService.cs +++ b/MediaBrowser.Api/System/ActivityLogService.cs @@ -1,3 +1,7 @@ +using System; +using System.Globalization; +using System.Linq; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Activity; @@ -47,7 +51,14 @@ namespace MediaBrowser.Api.System public object Get(GetActivityLogs request) { - var result = _activityManager.GetPagedResult(request.StartIndex, request.Limit); + DateTime? minDate = string.IsNullOrWhiteSpace(request.MinDate) ? + (DateTime?)null : + DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); + + var filterFunc = new Func, IQueryable>( + entries => entries.Where(entry => entry.DateCreated >= minDate)); + + var result = _activityManager.GetPagedResult(filterFunc, request.StartIndex, request.Limit); return ToOptimizedResult(result); } diff --git a/MediaBrowser.Model/Activity/IActivityManager.cs b/MediaBrowser.Model/Activity/IActivityManager.cs index 6742dc8fc..9dab5e77b 100644 --- a/MediaBrowser.Model/Activity/IActivityManager.cs +++ b/MediaBrowser.Model/Activity/IActivityManager.cs @@ -1,7 +1,6 @@ #pragma warning disable CS1591 using System; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Jellyfin.Data.Entities; @@ -21,7 +20,7 @@ namespace MediaBrowser.Model.Activity QueryResult GetPagedResult(int? startIndex, int? limit); QueryResult GetPagedResult( - Func, IEnumerable> func, + Func, IQueryable> func, int? startIndex, int? limit); } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 22a42322a..1f5981f10 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -79,8 +79,6 @@ namespace MediaBrowser.Model.Configuration public bool EnableRemoteAccess { get; set; } - public bool CameraUploadUpgraded { get; set; } - public bool CollectionsUpgraded { get; set; } /// diff --git a/MediaBrowser.Model/Devices/DeviceOptions.cs b/MediaBrowser.Model/Devices/DeviceOptions.cs new file mode 100644 index 000000000..8b77fd7fc --- /dev/null +++ b/MediaBrowser.Model/Devices/DeviceOptions.cs @@ -0,0 +1,9 @@ +#pragma warning disable CS1591 + +namespace MediaBrowser.Model.Devices +{ + public class DeviceOptions + { + public string CustomName { get; set; } + } +} diff --git a/MediaBrowser.Model/Devices/DevicesOptions.cs b/MediaBrowser.Model/Devices/DevicesOptions.cs deleted file mode 100644 index 02570650e..000000000 --- a/MediaBrowser.Model/Devices/DevicesOptions.cs +++ /dev/null @@ -1,23 +0,0 @@ -#pragma warning disable CS1591 - -using System; - -namespace MediaBrowser.Model.Devices -{ - public class DevicesOptions - { - public string[] EnabledCameraUploadDevices { get; set; } - public string CameraUploadPath { get; set; } - public bool EnableCameraUploadSubfolders { get; set; } - - public DevicesOptions() - { - EnabledCameraUploadDevices = Array.Empty(); - } - } - - public class DeviceOptions - { - public string CustomName { get; set; } - } -} -- cgit v1.2.3 From 953777f1ba4858f5186086e97910fcb88bfe3d61 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 14 May 2020 18:12:51 -0400 Subject: Removed unnecessary usings --- Emby.Server.Implementations/ApplicationHost.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 272a9355a..e6410f857 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -80,7 +80,6 @@ using MediaBrowser.Controller.Subtitles; using MediaBrowser.Controller.TV; using MediaBrowser.LocalMetadata.Savers; using MediaBrowser.MediaEncoding.BdInfo; -using MediaBrowser.Model.Activity; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Dlna; @@ -99,7 +98,6 @@ using MediaBrowser.Providers.Subtitles; using MediaBrowser.WebDashboard.Api; using MediaBrowser.XbmcMetadata.Providers; using Microsoft.AspNetCore.Http; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Prometheus.DotNetRuntime; -- cgit v1.2.3 From ce16651dbd43908770180054c4525e2188d95ed0 Mon Sep 17 00:00:00 2001 From: Vasily Date: Fri, 15 May 2020 01:55:00 +0300 Subject: Fix a check broken by https://github.com/jellyfin/jellyfin/pull/2105 --- Emby.Naming/Video/VideoListResolver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs index 7f755fd25..948fe037b 100644 --- a/Emby.Naming/Video/VideoListResolver.cs +++ b/Emby.Naming/Video/VideoListResolver.cs @@ -227,7 +227,7 @@ namespace Emby.Naming.Video } return remainingFiles - .Where(i => i.ExtraType == null) + .Where(i => i.ExtraType != null) .Where(i => baseNames.Any(b => i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase))) .ToList(); -- cgit v1.2.3 From 3cb6fd8a2754837c787213c008ad84a973eb7cab Mon Sep 17 00:00:00 2001 From: Frank Riley Date: Thu, 7 May 2020 20:36:50 -0700 Subject: Fix #3083: Set the Access-Control-Allow-Origin header to the request origin/host header if possible --- .../HttpServer/HttpListenerHost.cs | 35 +++++++++++++++++++--- .../HttpServer/ResponseFilter.cs | 19 +++++++++--- MediaBrowser.Controller/Net/IHttpServer.cs | 8 +++++ 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 81e793f5c..48cec8741 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -28,6 +28,7 @@ using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; using ServiceStack.Text.Jsv; namespace Emby.Server.Implementations.HttpServer @@ -454,9 +455,10 @@ namespace Emby.Server.Implementations.HttpServer if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase)) { httpRes.StatusCode = 200; - httpRes.Headers.Add("Access-Control-Allow-Origin", "*"); - httpRes.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); - httpRes.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization"); + foreach(KeyValuePair header in GetCorsHeaders(httpReq)) + { + httpRes.Headers.Add(header.Key, header.Value); + } httpRes.ContentType = "text/plain"; await httpRes.WriteAsync(string.Empty, cancellationToken).ConfigureAwait(false); return; @@ -576,6 +578,31 @@ namespace Emby.Server.Implementations.HttpServer } } + /// + /// Get the default CORS headers + /// + /// + /// + public IDictionary GetCorsHeaders(IRequest req) + { + var origin = req.Headers["Origin"]; + if (origin == StringValues.Empty) + { + origin = req.Headers["Host"]; + if (origin == StringValues.Empty) + { + origin = "*"; + } + } + + var headers = new Dictionary(); + 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; + } + // Entry point for HttpListener public ServiceHandler GetServiceHandler(IHttpRequest httpReq) { @@ -622,7 +649,7 @@ namespace Emby.Server.Implementations.HttpServer ResponseFilters = new Action[] { - new ResponseFilter(_logger).FilterResponse + new ResponseFilter(this, _logger).FilterResponse }; } diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs index 4089aa578..2d4b31ef4 100644 --- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.Text; +using MediaBrowser.Controller.Net; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -13,14 +15,17 @@ namespace Emby.Server.Implementations.HttpServer /// public class ResponseFilter { + private readonly IHttpServer _server; private readonly ILogger _logger; /// /// Initializes a new instance of the class. /// + /// The HTTP server. /// The logger. - public ResponseFilter(ILogger logger) + public ResponseFilter(IHttpServer server, ILogger logger) { + _server = server; _logger = logger; } @@ -32,10 +37,16 @@ namespace Emby.Server.Implementations.HttpServer /// The dto. public void FilterResponse(IRequest req, HttpResponse res, object dto) { + foreach(KeyValuePair header in _server.GetCorsHeaders(req)) + { + res.Headers.Add(header.Key, header.Value); + } // Try to prevent compatibility view - res.Headers.Add("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization"); - res.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); - res.Headers.Add("Access-Control-Allow-Origin", "*"); + res.Headers["Access-Control-Allow-Headers"] = ("Accept, Accept-Language, Authorization, Cache-Control, " + + "Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, " + + "Content-Type, Cookie, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, " + + "Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, " + + "X-Emby-Authorization"); if (dto is Exception exception) { diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs index f1c441761..eb2b4670a 100644 --- a/MediaBrowser.Controller/Net/IHttpServer.cs +++ b/MediaBrowser.Controller/Net/IHttpServer.cs @@ -2,6 +2,7 @@ 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 @@ -38,5 +39,12 @@ namespace MediaBrowser.Controller.Net /// /// Task RequestHandler(HttpContext context); + + /// + /// Get the default CORS headers + /// + /// + /// + IDictionary GetCorsHeaders(IRequest req); } } -- cgit v1.2.3 From c70c58923667a3c626b4112f783f755f91442d0b Mon Sep 17 00:00:00 2001 From: Frank Riley Date: Wed, 13 May 2020 15:57:40 -0700 Subject: Update Emby.Server.Implementations/HttpServer/HttpListenerHost.cs from review Co-authored-by: Cody Robibero --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 48cec8741..958bb1e1d 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -455,9 +455,9 @@ namespace Emby.Server.Implementations.HttpServer if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase)) { httpRes.StatusCode = 200; - foreach(KeyValuePair header in GetCorsHeaders(httpReq)) + foreach(var (key, value) in GetCorsHeaders(httpReq)) { - httpRes.Headers.Add(header.Key, header.Value); + httpRes.Headers.Add(key, value); } httpRes.ContentType = "text/plain"; await httpRes.WriteAsync(string.Empty, cancellationToken).ConfigureAwait(false); -- cgit v1.2.3 From 6990af811ad65816a0534f75e889dc9c22632876 Mon Sep 17 00:00:00 2001 From: Frank Riley Date: Thu, 14 May 2020 06:28:33 -0700 Subject: Use simpler dictionary iterator. --- Emby.Server.Implementations/HttpServer/ResponseFilter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs index 2d4b31ef4..c94e905af 100644 --- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs @@ -37,9 +37,9 @@ namespace Emby.Server.Implementations.HttpServer /// The dto. public void FilterResponse(IRequest req, HttpResponse res, object dto) { - foreach(KeyValuePair header in _server.GetCorsHeaders(req)) + foreach(var (key, value) in _server.GetCorsHeaders(req)) { - res.Headers.Add(header.Key, header.Value); + res.Headers.Add(key, value); } // Try to prevent compatibility view res.Headers["Access-Control-Allow-Headers"] = ("Accept, Accept-Language, Authorization, Cache-Control, " + -- cgit v1.2.3 From 9ee10d22c8ccbeb9eb4112b1a9f520d5ed998013 Mon Sep 17 00:00:00 2001 From: Frank Riley Date: Thu, 14 May 2020 16:03:45 -0700 Subject: Rename function --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 4 ++-- Emby.Server.Implementations/HttpServer/ResponseFilter.cs | 2 +- MediaBrowser.Controller/Net/IHttpServer.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 958bb1e1d..794d55c04 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -455,7 +455,7 @@ namespace Emby.Server.Implementations.HttpServer if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase)) { httpRes.StatusCode = 200; - foreach(var (key, value) in GetCorsHeaders(httpReq)) + foreach(var (key, value) in GetDefaultCorsHeaders(httpReq)) { httpRes.Headers.Add(key, value); } @@ -583,7 +583,7 @@ namespace Emby.Server.Implementations.HttpServer /// /// /// - public IDictionary GetCorsHeaders(IRequest req) + public IDictionary GetDefaultCorsHeaders(IRequest req) { var origin = req.Headers["Origin"]; if (origin == StringValues.Empty) diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs index c94e905af..85c3db9b2 100644 --- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs @@ -37,7 +37,7 @@ namespace Emby.Server.Implementations.HttpServer /// The dto. public void FilterResponse(IRequest req, HttpResponse res, object dto) { - foreach(var (key, value) in _server.GetCorsHeaders(req)) + foreach(var (key, value) in _server.GetDefaultCorsHeaders(req)) { res.Headers.Add(key, value); } diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs index eb2b4670a..efb5f4ac3 100644 --- a/MediaBrowser.Controller/Net/IHttpServer.cs +++ b/MediaBrowser.Controller/Net/IHttpServer.cs @@ -45,6 +45,6 @@ namespace MediaBrowser.Controller.Net /// /// /// - IDictionary GetCorsHeaders(IRequest req); + IDictionary GetDefaultCorsHeaders(IRequest req); } } -- cgit v1.2.3 From 7c571345353417db6990700b350fd22a2778ee4f Mon Sep 17 00:00:00 2001 From: Vasily Date: Fri, 15 May 2020 02:30:28 +0300 Subject: Implement a cleanup migration --- Jellyfin.Server/Migrations/MigrationRunner.cs | 3 +- .../Migrations/Routines/RemoveBuggedExtras.cs | 50 ++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 Jellyfin.Server/Migrations/Routines/RemoveBuggedExtras.cs diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index ca1748282..7a881be0f 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -17,7 +17,8 @@ namespace Jellyfin.Server.Migrations private static readonly Type[] _migrationTypes = { typeof(Routines.DisableTranscodingThrottling), - typeof(Routines.CreateUserLoggingConfigFile) + typeof(Routines.CreateUserLoggingConfigFile), + typeof(Routines.RemoveBuggedExtras) }; /// diff --git a/Jellyfin.Server/Migrations/Routines/RemoveBuggedExtras.cs b/Jellyfin.Server/Migrations/Routines/RemoveBuggedExtras.cs new file mode 100644 index 000000000..7e45f99f9 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/RemoveBuggedExtras.cs @@ -0,0 +1,50 @@ +using System; +using System.IO; + +using MediaBrowser.Controller; +using Microsoft.Extensions.Logging; +using SQLitePCL.pretty; + +namespace Jellyfin.Server.Migrations.Routines +{ + /// + /// Remove duplicate entries which were caused by a bug where a file was considered to be an "Extra" to itself. + /// + internal class RemoveBuggedExtras : IMigrationRoutine + { + private const string DbFilename = "library.db"; + private readonly ILogger _logger; + private readonly IServerApplicationPaths _paths; + + public RemoveBuggedExtras(ILogger logger, IServerApplicationPaths paths) + { + _logger = logger; + _paths = paths; + } + + /// + public Guid Id => Guid.Parse("{ACBE17B7-8435-4A83-8B64-6FCF162CB9BD}"); + + /// + public string Name => "RemoveBuggedExtras"; + + /// + public void Perform() + { + var dataPath = _paths.DataPath; + using (var connection = SQLite3.Open( + Path.Combine(dataPath, DbFilename), + ConnectionFlags.ReadWrite, + null)) + { + var queryResult = connection.Query("SELECT t1.Path FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video'"); + var bads = string.Join(", ", queryResult.SelectScalarString()); + if (bads.Length != 0) + { + _logger.LogInformation("Removing found duplicated extras for the following items: {0}", bads); + connection.Execute("DELETE FROM TypedBaseItems WHERE rowid IN (SELECT t1.rowid FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video')"); + } + } + } + } +} -- cgit v1.2.3 From e02e041b231dbe2b158fa1c75098bdd08e0abad1 Mon Sep 17 00:00:00 2001 From: Erik Rigtorp Date: Thu, 14 May 2020 16:55:55 -0700 Subject: If second cleaning results in same name skip lookup --- MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs b/MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs index aa42fd81a..bf6394608 100644 --- a/MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs +++ b/MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs @@ -76,7 +76,7 @@ namespace MediaBrowser.Providers.Tmdb.Movies var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); - // Does this mean we are reparsing already parsed ItemLookupInfo? + // TODO: Investigate: Does this mean we are reparsing already parsed ItemLookupInfo? var parsedName = _libraryManager.ParseName(name); var yearInName = parsedName.Year; name = parsedName.Name; @@ -105,30 +105,30 @@ namespace MediaBrowser.Providers.Tmdb.Movies // providers if (results.Count == 0) { - name = parsedName.Name; + var name2 = parsedName.Name; // Remove things enclosed in []{}() etc - name = _cleanEnclosed.Replace(name, string.Empty); + name2 = _cleanEnclosed.Replace(name2, string.Empty); // Replace sequences of non-word characters with space - name = _cleanNonWord.Replace(name, " "); + name2 = _cleanNonWord.Replace(name2, " "); // Clean based on common stop words / tokens - name = _cleanStopWords.Replace(name, string.Empty); + name2 = _cleanStopWords.Replace(name2, string.Empty); // Trim whitespace - name = name.Trim(); + name2 = name2.Trim(); // Search again if the new name is different - if (!string.Equals(name, parsedName.Name) && !string.IsNullOrWhiteSpace(name)) + if (!string.Equals(name2, name) && !string.IsNullOrWhiteSpace(name2)) { - _logger.LogInformation("TmdbSearch: Finding id for item: {0} ({1})", name, year); - results = await GetSearchResults(name, searchType, year, language, tmdbImageUrl, cancellationToken).ConfigureAwait(false); + _logger.LogInformation("TmdbSearch: Finding id for item: {0} ({1})", name2, year); + results = await GetSearchResults(name2, searchType, year, language, tmdbImageUrl, cancellationToken).ConfigureAwait(false); if (results.Count == 0 && !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase)) { //one more time, in english - results = await GetSearchResults(name, searchType, year, "en", tmdbImageUrl, cancellationToken).ConfigureAwait(false); + results = await GetSearchResults(name2, searchType, year, "en", tmdbImageUrl, cancellationToken).ConfigureAwait(false); } } } -- cgit v1.2.3 From dcaffd3812b3995e2158f4ea587599249016c03c Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 14 May 2020 20:11:34 -0400 Subject: Fix regressions introduced by #3098 --- MediaBrowser.Api/BaseApiService.cs | 4 ++-- MediaBrowser.Api/Library/LibraryService.cs | 6 +++++- MediaBrowser.Api/Movies/MoviesService.cs | 2 +- MediaBrowser.Api/Playback/BaseStreamingService.cs | 2 +- MediaBrowser.Api/Playback/Hls/BaseHlsService.cs | 2 +- MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 2 +- MediaBrowser.Api/Playback/MediaInfoService.cs | 2 +- MediaBrowser.Api/Playback/Progressive/AudioService.cs | 2 +- .../Playback/Progressive/BaseProgressiveStreamingService.cs | 2 +- MediaBrowser.Api/Playback/UniversalAudioService.cs | 9 ++++++--- MediaBrowser.Api/UserLibrary/ArtistsService.cs | 2 +- MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs | 2 +- 12 files changed, 22 insertions(+), 15 deletions(-) diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 1a1d86362..2cd68ac1b 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -21,7 +21,7 @@ namespace MediaBrowser.Api public abstract class BaseApiService : IService, IRequiresRequest { public BaseApiService( - ILogger logger, + ILogger logger, IServerConfigurationManager serverConfigurationManager, IHttpResultFactory httpResultFactory) { @@ -34,7 +34,7 @@ namespace MediaBrowser.Api /// Gets the logger. /// /// The logger. - protected ILogger Logger { get; } + protected ILogger Logger { get; } /// /// Gets or sets the server configuration manager. diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index a54640b2f..2d1977d2e 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -319,11 +319,14 @@ namespace MediaBrowser.Api.Library private readonly ILocalizationManager _localization; private readonly ILibraryMonitor _libraryMonitor; + private readonly ILogger _moviesServiceLogger; + /// /// Initializes a new instance of the class. /// public LibraryService( ILogger logger, + ILogger moviesServiceLogger, IServerConfigurationManager serverConfigurationManager, IHttpResultFactory httpResultFactory, IProviderManager providerManager, @@ -344,6 +347,7 @@ namespace MediaBrowser.Api.Library _activityManager = activityManager; _localization = localization; _libraryMonitor = libraryMonitor; + _moviesServiceLogger = moviesServiceLogger; } private string[] GetRepresentativeItemTypes(string contentType) @@ -543,7 +547,7 @@ namespace MediaBrowser.Api.Library if (item is Movie || (program != null && program.IsMovie) || item is Trailer) { return new MoviesService( - Logger, + _moviesServiceLogger, ServerConfigurationManager, ResultFactory, _userManager, diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index 46da8b909..cdd027634 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -82,7 +82,7 @@ namespace MediaBrowser.Api.Movies /// Initializes a new instance of the class. /// public MoviesService( - ILogger logger, + ILogger logger, IServerConfigurationManager serverConfigurationManager, IHttpResultFactory httpResultFactory, IUserManager userManager, diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 928ca1612..f796aa486 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -81,7 +81,7 @@ namespace MediaBrowser.Api.Playback /// Initializes a new instance of the class. /// protected BaseStreamingService( - ILogger logger, + ILogger logger, IServerConfigurationManager serverConfigurationManager, IHttpResultFactory httpResultFactory, IUserManager userManager, diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 4213193ba..627421aac 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Api.Playback.Hls public abstract class BaseHlsService : BaseStreamingService { public BaseHlsService( - ILogger logger, + ILogger logger, IServerConfigurationManager serverConfigurationManager, IHttpResultFactory httpResultFactory, IUserManager userManager, diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 7f74e85e9..061316cb8 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -94,7 +94,7 @@ namespace MediaBrowser.Api.Playback.Hls public class DynamicHlsService : BaseHlsService { public DynamicHlsService( - ILogger logger, + ILogger logger, IServerConfigurationManager serverConfigurationManager, IHttpResultFactory httpResultFactory, IUserManager userManager, diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index db24eaca6..e2d771ec6 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -79,7 +79,7 @@ namespace MediaBrowser.Api.Playback private readonly IAuthorizationContext _authContext; public MediaInfoService( - ILogger logger, + ILogger logger, IServerConfigurationManager serverConfigurationManager, IHttpResultFactory httpResultFactory, IMediaSourceManager mediaSourceManager, diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs index 8d1e3a3f2..34c7986ca 100644 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs @@ -33,7 +33,7 @@ namespace MediaBrowser.Api.Playback.Progressive public class AudioService : BaseProgressiveStreamingService { public AudioService( - ILogger logger, + ILogger logger, IServerConfigurationManager serverConfigurationManager, IHttpResultFactory httpResultFactory, IHttpClient httpClient, diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index ed68219c9..c7bf055fb 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -28,7 +28,7 @@ namespace MediaBrowser.Api.Playback.Progressive protected IHttpClient HttpClient { get; private set; } public BaseProgressiveStreamingService( - ILogger logger, + ILogger logger, IServerConfigurationManager serverConfigurationManager, IHttpResultFactory httpResultFactory, IHttpClient httpClient, diff --git a/MediaBrowser.Api/Playback/UniversalAudioService.cs b/MediaBrowser.Api/Playback/UniversalAudioService.cs index cebd4b49a..a3b319d44 100644 --- a/MediaBrowser.Api/Playback/UniversalAudioService.cs +++ b/MediaBrowser.Api/Playback/UniversalAudioService.cs @@ -75,9 +75,11 @@ namespace MediaBrowser.Api.Playback public class UniversalAudioService : BaseApiService { private readonly EncodingHelper _encodingHelper; + private readonly ILoggerFactory _loggerFactory; public UniversalAudioService( ILogger logger, + ILoggerFactory loggerFactory, IServerConfigurationManager serverConfigurationManager, IHttpResultFactory httpResultFactory, IHttpClient httpClient, @@ -108,6 +110,7 @@ namespace MediaBrowser.Api.Playback AuthorizationContext = authorizationContext; NetworkManager = networkManager; _encodingHelper = encodingHelper; + _loggerFactory = loggerFactory; } protected IHttpClient HttpClient { get; private set; } @@ -233,7 +236,7 @@ namespace MediaBrowser.Api.Playback AuthorizationContext.GetAuthorizationInfo(Request).DeviceId = request.DeviceId; var mediaInfoService = new MediaInfoService( - Logger, + _loggerFactory.CreateLogger(), ServerConfigurationManager, ResultFactory, MediaSourceManager, @@ -277,7 +280,7 @@ namespace MediaBrowser.Api.Playback if (!isStatic && string.Equals(mediaSource.TranscodingSubProtocol, "hls", StringComparison.OrdinalIgnoreCase)) { var service = new DynamicHlsService( - Logger, + _loggerFactory.CreateLogger(), ServerConfigurationManager, ResultFactory, UserManager, @@ -331,7 +334,7 @@ namespace MediaBrowser.Api.Playback else { var service = new AudioService( - Logger, + _loggerFactory.CreateLogger(), ServerConfigurationManager, ResultFactory, HttpClient, diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs index 3d08d5437..bef91d54d 100644 --- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs +++ b/MediaBrowser.Api/UserLibrary/ArtistsService.cs @@ -51,7 +51,7 @@ namespace MediaBrowser.Api.UserLibrary public class ArtistsService : BaseItemsByNameService { public ArtistsService( - ILogger logger, + ILogger logger, IServerConfigurationManager serverConfigurationManager, IHttpResultFactory httpResultFactory, IUserManager userManager, diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index c4a52d5f5..559082ff4 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -28,7 +28,7 @@ namespace MediaBrowser.Api.UserLibrary /// The user data repository. /// The dto service. protected BaseItemsByNameService( - ILogger logger, + ILogger> logger, IServerConfigurationManager serverConfigurationManager, IHttpResultFactory httpResultFactory, IUserManager userManager, -- cgit v1.2.3 From ef533015ae8d2da48848cd97f847b4764bf13c57 Mon Sep 17 00:00:00 2001 From: Odd Stråbø Date: Fri, 15 May 2020 00:48:58 +0000 Subject: Translated using Weblate (Norwegian Bokmål) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/nb_NO/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Emby.Server.Implementations/Localization/Core/nb.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json index 50d0d083c..5637ce346 100644 --- a/Emby.Server.Implementations/Localization/Core/nb.json +++ b/Emby.Server.Implementations/Localization/Core/nb.json @@ -97,5 +97,9 @@ "TasksApplicationCategory": "Applikasjon", "TasksLibraryCategory": "Bibliotek", "TasksMaintenanceCategory": "Vedlikehold", - "TaskCleanCache": "Tøm buffer katalog" + "TaskCleanCache": "Tøm buffer katalog", + "TaskRefreshLibrary": "Skann mediebibliotek", + "TaskRefreshChapterImagesDescription": "Lager forhåndsvisningsbilder for videoer som har kapitler.", + "TaskRefreshChapterImages": "Trekk ut Kapittelbilder", + "TaskCleanCacheDescription": "Sletter mellomlagrede filer som ikke lengre trengs av systemet." } -- cgit v1.2.3 From 9ed8c6cb11d2221157db59d1f13f26ea00224877 Mon Sep 17 00:00:00 2001 From: artiume Date: Fri, 15 May 2020 07:59:46 -0400 Subject: Add opf mimetype --- MediaBrowser.Model/Net/MimeTypes.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs index fe2fbe7e4..814151948 100644 --- a/MediaBrowser.Model/Net/MimeTypes.cs +++ b/MediaBrowser.Model/Net/MimeTypes.cs @@ -67,6 +67,7 @@ namespace MediaBrowser.Model.Net { ".m3u8", "application/x-mpegURL" }, { ".map", "application/x-javascript" }, { ".mobi", "application/x-mobipocket-ebook" }, + { ".odf", "application/oebps-package+xml" }, { ".pdf", "application/pdf" }, { ".rar", "application/vnd.rar" }, { ".srt", "application/x-subrip" }, -- cgit v1.2.3 From eb3ce3fb8789ed697471f1fe14d2f85591527b56 Mon Sep 17 00:00:00 2001 From: artiume Date: Thu, 14 May 2020 12:42:22 -0400 Subject: Fix Progressive Stream 'P' capitalization --- MediaBrowser.Model/Entities/MediaStream.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index e7e8d7cec..4a179776d 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -199,7 +199,7 @@ namespace MediaBrowser.Model.Entities { return "1440I"; } - return "1440P"; + return "1440p"; } if (width >= 1900 || height >= 1000) { @@ -207,7 +207,7 @@ namespace MediaBrowser.Model.Entities { return "1080I"; } - return "1080P"; + return "1080p"; } if (width >= 1260 || height >= 700) { @@ -215,7 +215,7 @@ namespace MediaBrowser.Model.Entities { return "720I"; } - return "720P"; + return "720p"; } if (width >= 700 || height >= 440) { @@ -224,7 +224,7 @@ namespace MediaBrowser.Model.Entities { return "480I"; } - return "480P"; + return "480p"; } return "SD"; -- cgit v1.2.3 From d41cdb3b7a6ab9ec4b9b286ffe1a63f3e4e0996c Mon Sep 17 00:00:00 2001 From: artiume Date: Thu, 14 May 2020 13:27:23 -0400 Subject: Update Interlace --- MediaBrowser.Model/Entities/MediaStream.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index 4a179776d..784d659b6 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -197,7 +197,7 @@ namespace MediaBrowser.Model.Entities { if (i.IsInterlaced) { - return "1440I"; + return "1440i"; } return "1440p"; } @@ -213,7 +213,7 @@ namespace MediaBrowser.Model.Entities { if (i.IsInterlaced) { - return "720I"; + return "720i"; } return "720p"; } @@ -222,7 +222,7 @@ namespace MediaBrowser.Model.Entities if (i.IsInterlaced) { - return "480I"; + return "480i"; } return "480p"; } -- cgit v1.2.3 From 527029af92585fbed5ff9bd619314ed0c31fe65b Mon Sep 17 00:00:00 2001 From: artiume Date: Fri, 15 May 2020 08:30:58 -0400 Subject: Update MediaStream.cs --- MediaBrowser.Model/Entities/MediaStream.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index 784d659b6..4a269c55b 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -205,7 +205,7 @@ namespace MediaBrowser.Model.Entities { if (i.IsInterlaced) { - return "1080I"; + return "1080i"; } return "1080p"; } -- cgit v1.2.3 From a765272c179219bb8931cd4e90234a16d2114327 Mon Sep 17 00:00:00 2001 From: artiume Date: Fri, 15 May 2020 09:38:26 -0400 Subject: Update MimeTypes.cs --- MediaBrowser.Model/Net/MimeTypes.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs index 814151948..b491a015c 100644 --- a/MediaBrowser.Model/Net/MimeTypes.cs +++ b/MediaBrowser.Model/Net/MimeTypes.cs @@ -67,7 +67,7 @@ namespace MediaBrowser.Model.Net { ".m3u8", "application/x-mpegURL" }, { ".map", "application/x-javascript" }, { ".mobi", "application/x-mobipocket-ebook" }, - { ".odf", "application/oebps-package+xml" }, + { ".opf", "application/oebps-package+xml" }, { ".pdf", "application/pdf" }, { ".rar", "application/vnd.rar" }, { ".srt", "application/x-subrip" }, -- cgit v1.2.3 From e8f45248aab753797ee9b2e3d8d58c2a243fc598 Mon Sep 17 00:00:00 2001 From: gion Date: Fri, 15 May 2020 18:47:16 +0200 Subject: Fix code issues --- MediaBrowser.Api/SyncPlay/SyncPlayService.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs index 9137faf9f..8f552ef89 100644 --- a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs +++ b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs @@ -175,8 +175,7 @@ namespace MediaBrowser.Api.SyncPlay Guid groupId; Guid playingItemId = Guid.Empty; - var valid = Guid.TryParse(request.GroupId, out groupId); - if (!valid) + if (!Guid.TryParse(request.GroupId, out groupId)) { Logger.LogError("JoinGroup: {0} is not a valid format for GroupId. Ignoring request.", request.GroupId); return; @@ -185,8 +184,7 @@ namespace MediaBrowser.Api.SyncPlay // Both null and empty strings mean that client isn't playing anything if (!String.IsNullOrEmpty(request.PlayingItemId)) { - valid = Guid.TryParse(request.PlayingItemId, out playingItemId); - if (!valid) + if (!Guid.TryParse(request.PlayingItemId, out playingItemId)) { Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId); return; @@ -224,8 +222,7 @@ namespace MediaBrowser.Api.SyncPlay if (!String.IsNullOrEmpty(request.FilterItemId)) { - var valid = Guid.TryParse(request.FilterItemId, out filterItemId); - if (!valid) + if (!Guid.TryParse(request.FilterItemId, out filterItemId)) { Logger.LogWarning("ListGroups: {0} is not a valid format for FilterItemId. Ignoring filter.", request.FilterItemId); } -- cgit v1.2.3 From a5dee3680880d525a6c507deb7dd284b08b7ebdd Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 15 May 2020 12:51:18 -0400 Subject: Apply more review suggestions --- Jellyfin.Data/Entities/ActivityLog.cs | 3 ++- Jellyfin.Server.Implementations/Activity/ActivityManager.cs | 7 +++---- Jellyfin.Server.Implementations/JellyfinDb.cs | 7 ++++--- .../Migrations/DesignTimeJellyfinDbFactory.cs | 4 +--- Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs | 5 ++--- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/Jellyfin.Data/Entities/ActivityLog.cs b/Jellyfin.Data/Entities/ActivityLog.cs index 8fbf6eaab..522c20664 100644 --- a/Jellyfin.Data/Entities/ActivityLog.cs +++ b/Jellyfin.Data/Entities/ActivityLog.cs @@ -53,6 +53,7 @@ namespace Jellyfin.Data.Entities /// The name. /// The type. /// The user's id. + /// The new instance. public static ActivityLog Create(string name, string type, Guid userId) { return new ActivityLog(name, type, userId); @@ -63,7 +64,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Gets the identity of this instance. + /// Gets or sets the identity of this instance. /// This is the key in the backing database. /// [Key] diff --git a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs index 0b398b60c..65ceee32b 100644 --- a/Jellyfin.Server.Implementations/Activity/ActivityManager.cs +++ b/Jellyfin.Server.Implementations/Activity/ActivityManager.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Jellyfin.Data.Entities; @@ -56,7 +55,7 @@ namespace Jellyfin.Server.Implementations.Activity { using var dbContext = _provider.CreateContext(); - var query = func(dbContext.ActivityLogs).OrderByDescending(entry => entry.DateCreated).AsQueryable(); + var query = func(dbContext.ActivityLogs.OrderByDescending(entry => entry.DateCreated)); if (startIndex.HasValue) { @@ -69,12 +68,12 @@ namespace Jellyfin.Server.Implementations.Activity } // This converts the objects from the new database model to the old for compatibility with the existing API. - var list = query.AsEnumerable().Select(ConvertToOldModel).ToList(); + var list = query.Select(ConvertToOldModel).ToList(); return new QueryResult { Items = list, - TotalRecordCount = dbContext.ActivityLogs.Count() + TotalRecordCount = func(dbContext.ActivityLogs).Count() }; } diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 23714b24a..ec09a619f 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -107,10 +107,11 @@ namespace Jellyfin.Server.Implementations public override int SaveChanges() { - foreach (var entity in ChangeTracker.Entries().Where(e => e.State == EntityState.Modified)) + foreach (var saveEntity in ChangeTracker.Entries() + .Where(e => e.State == EntityState.Modified) + .OfType()) { - var saveEntity = entity.Entity as ISavingChanges; - saveEntity?.OnSavingChanges(); + saveEntity.OnSavingChanges(); } return base.SaveChanges(); diff --git a/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs b/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs index a1b58eb5a..72a4a8c3b 100644 --- a/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs +++ b/Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs @@ -12,9 +12,7 @@ namespace Jellyfin.Server.Implementations.Migrations public JellyfinDb CreateDbContext(string[] args) { var optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder.UseSqlite( - "Data Source=jellyfin.db", - opt => opt.MigrationsAssembly("Jellyfin.Migrations")); + optionsBuilder.UseSqlite("Data Source=jellyfin.db"); return new JellyfinDb(optionsBuilder.Options); } diff --git a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs index 1d684804d..6f4fc4f28 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs @@ -106,11 +106,10 @@ namespace Jellyfin.Server.Migrations.Routines newEntry.ItemId = entry[5].ToString(); } - // Since code references the Id of the entries, this needs to be inserted in order. - // In order to do that, we insert one by one because EF Core doesn't provide a way to guarantee ordering for bulk inserts. dbContext.ActivityLogs.Add(newEntry); - dbContext.SaveChanges(); } + + dbContext.SaveChanges(); } try -- 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(-) diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index a5293b41c..3af18f681 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 8f552ef89..8064ea7dc 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 a7c2e524a9571f4b6747908db94d0241d73ae5f3 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 15 May 2020 14:08:46 -0400 Subject: Apply more review suggestions --- .../Migrations/Routines/MigrateActivityLogDb.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs index 6f4fc4f28..079b5b5dc 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using Emby.Server.Implementations.Data; using Jellyfin.Data.Entities; using Jellyfin.Server.Implementations; @@ -75,7 +76,7 @@ namespace Jellyfin.Server.Migrations.Routines dbContext.Database.ExecuteSqlRaw("UPDATE sqlite_sequence SET seq = 0 WHERE name = 'ActivityLog';"); dbContext.SaveChanges(); - foreach (var entry in queryResult) + var newEntries = queryResult.Select(entry => { if (!logLevelDictionary.TryGetValue(entry[8].ToString(), out var severity)) { @@ -93,28 +94,35 @@ namespace Jellyfin.Server.Migrations.Routines if (entry[2].SQLiteType != SQLiteType.Null) { - newEntry.Overview = entry[2].ToString(); + newEntry.Overview = entry[2].ToString(); } if (entry[3].SQLiteType != SQLiteType.Null) { - newEntry.ShortOverview = entry[3].ToString(); + newEntry.ShortOverview = entry[3].ToString(); } if (entry[5].SQLiteType != SQLiteType.Null) { - newEntry.ItemId = entry[5].ToString(); + newEntry.ItemId = entry[5].ToString(); } - dbContext.ActivityLogs.Add(newEntry); - } + return newEntry; + }); + dbContext.ActivityLogs.AddRange(newEntries); dbContext.SaveChanges(); } try { 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) { -- cgit v1.2.3 From 034fe97eebb709d87d7642151d6e0e5ec5f2a391 Mon Sep 17 00:00:00 2001 From: Vasily Date: Fri, 15 May 2020 21:32:56 +0300 Subject: Apply suggestions from code review Co-authored-by: Mark Monteiro --- Jellyfin.Server/Migrations/Routines/RemoveBuggedExtras.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Server/Migrations/Routines/RemoveBuggedExtras.cs b/Jellyfin.Server/Migrations/Routines/RemoveBuggedExtras.cs index 7e45f99f9..512bec0bf 100644 --- a/Jellyfin.Server/Migrations/Routines/RemoveBuggedExtras.cs +++ b/Jellyfin.Server/Migrations/Routines/RemoveBuggedExtras.cs @@ -10,7 +10,7 @@ namespace Jellyfin.Server.Migrations.Routines /// /// Remove duplicate entries which were caused by a bug where a file was considered to be an "Extra" to itself. /// - internal class RemoveBuggedExtras : IMigrationRoutine + internal class RemoveDuplicateExtras : IMigrationRoutine { private const string DbFilename = "library.db"; private readonly ILogger _logger; @@ -41,7 +41,7 @@ namespace Jellyfin.Server.Migrations.Routines var bads = string.Join(", ", queryResult.SelectScalarString()); if (bads.Length != 0) { - _logger.LogInformation("Removing found duplicated extras for the following items: {0}", bads); + _logger.LogInformation("Removing found duplicated extras for the following items: {DuplicateExtras}", bads); connection.Execute("DELETE FROM TypedBaseItems WHERE rowid IN (SELECT t1.rowid FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video')"); } } -- cgit v1.2.3 From 79dee27299bda60f67e98eda8c309b1f25e0893b Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 15 May 2020 14:33:36 -0400 Subject: Fixed indentation --- Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs index 079b5b5dc..b3cc29708 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs @@ -94,17 +94,17 @@ namespace Jellyfin.Server.Migrations.Routines if (entry[2].SQLiteType != SQLiteType.Null) { - newEntry.Overview = entry[2].ToString(); + newEntry.Overview = entry[2].ToString(); } if (entry[3].SQLiteType != SQLiteType.Null) { - newEntry.ShortOverview = entry[3].ToString(); + newEntry.ShortOverview = entry[3].ToString(); } if (entry[5].SQLiteType != SQLiteType.Null) { - newEntry.ItemId = entry[5].ToString(); + newEntry.ItemId = entry[5].ToString(); } return newEntry; -- cgit v1.2.3 From 43dc604e8739614752a0c4e8a4ff00af5d74085d Mon Sep 17 00:00:00 2001 From: Vasily Date: Fri, 15 May 2020 21:49:45 +0300 Subject: Fixed compilation, added backing db before removing extras --- Jellyfin.Server/Migrations/MigrationRunner.cs | 2 +- .../Migrations/Routines/RemoveBuggedExtras.cs | 50 --------------- .../Migrations/Routines/RemoveDuplicateExtras.cs | 71 ++++++++++++++++++++++ 3 files changed, 72 insertions(+), 51 deletions(-) delete mode 100644 Jellyfin.Server/Migrations/Routines/RemoveBuggedExtras.cs create mode 100644 Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 7a881be0f..394170065 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -18,7 +18,7 @@ namespace Jellyfin.Server.Migrations { typeof(Routines.DisableTranscodingThrottling), typeof(Routines.CreateUserLoggingConfigFile), - typeof(Routines.RemoveBuggedExtras) + typeof(Routines.RemoveDuplicateExtras) }; /// diff --git a/Jellyfin.Server/Migrations/Routines/RemoveBuggedExtras.cs b/Jellyfin.Server/Migrations/Routines/RemoveBuggedExtras.cs deleted file mode 100644 index 512bec0bf..000000000 --- a/Jellyfin.Server/Migrations/Routines/RemoveBuggedExtras.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.IO; - -using MediaBrowser.Controller; -using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; - -namespace Jellyfin.Server.Migrations.Routines -{ - /// - /// Remove duplicate entries which were caused by a bug where a file was considered to be an "Extra" to itself. - /// - internal class RemoveDuplicateExtras : IMigrationRoutine - { - private const string DbFilename = "library.db"; - private readonly ILogger _logger; - private readonly IServerApplicationPaths _paths; - - public RemoveBuggedExtras(ILogger logger, IServerApplicationPaths paths) - { - _logger = logger; - _paths = paths; - } - - /// - public Guid Id => Guid.Parse("{ACBE17B7-8435-4A83-8B64-6FCF162CB9BD}"); - - /// - public string Name => "RemoveBuggedExtras"; - - /// - public void Perform() - { - var dataPath = _paths.DataPath; - using (var connection = SQLite3.Open( - Path.Combine(dataPath, DbFilename), - ConnectionFlags.ReadWrite, - null)) - { - var queryResult = connection.Query("SELECT t1.Path FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video'"); - var bads = string.Join(", ", queryResult.SelectScalarString()); - if (bads.Length != 0) - { - _logger.LogInformation("Removing found duplicated extras for the following items: {DuplicateExtras}", bads); - connection.Execute("DELETE FROM TypedBaseItems WHERE rowid IN (SELECT t1.rowid FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video')"); - } - } - } - } -} diff --git a/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs b/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs new file mode 100644 index 000000000..46de2d5c3 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs @@ -0,0 +1,71 @@ +using System; +using System.Globalization; +using System.IO; + +using MediaBrowser.Controller; +using Microsoft.Extensions.Logging; +using SQLitePCL.pretty; + +namespace Jellyfin.Server.Migrations.Routines +{ + /// + /// Remove duplicate entries which were caused by a bug where a file was considered to be an "Extra" to itself. + /// + internal class RemoveDuplicateExtras : IMigrationRoutine + { + private const string DbFilename = "library.db"; + private readonly ILogger _logger; + private readonly IServerApplicationPaths _paths; + + public RemoveDuplicateExtras(ILogger logger, IServerApplicationPaths paths) + { + _logger = logger; + _paths = paths; + } + + /// + public Guid Id => Guid.Parse("{ACBE17B7-8435-4A83-8B64-6FCF162CB9BD}"); + + /// + public string Name => "RemoveDuplicateExtras"; + + /// + public void Perform() + { + var dataPath = _paths.DataPath; + var dbPath = Path.Combine(dataPath, DbFilename); + using (var connection = SQLite3.Open( + dbPath, + ConnectionFlags.ReadWrite, + null)) + { + var queryResult = connection.Query("SELECT t1.Path FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video'"); + var bads = string.Join(", ", queryResult.SelectScalarString()); + if (bads.Length != 0) + { + _logger.LogInformation("Found duplicate extras, making {Library} backup", DbFilename); + for (int i = 1; ; i++) + { + var bakPath = string.Format(CultureInfo.InvariantCulture, "{0}.bak{1}", dbPath, i); + if (!File.Exists(bakPath)) + { + try + { + File.Copy(dbPath, bakPath); + break; + } + catch (Exception ex) + { + _logger.LogError(ex, "Cannot make a backup of {Library}", DbFilename); + throw; + } + } + } + + _logger.LogInformation("Removing found duplicated extras for the following items: {DuplicateExtras}", bads); + connection.Execute("DELETE FROM TypedBaseItems WHERE rowid IN (SELECT t1.rowid FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video')"); + } + } + } + } +} -- cgit v1.2.3 From 6e68702799b2b3de9660babad6a66493d16fec72 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Fri, 15 May 2020 15:10:41 -0400 Subject: Do not run DELETE command if no extras are detected Also log a message if no extras were detected Also log the path used for the database backup Also add some comments to explain the migration --- .../Migrations/Routines/RemoveDuplicateExtras.cs | 44 +++++++++++++--------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs b/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs index 46de2d5c3..e95536388 100644 --- a/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs +++ b/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs @@ -39,32 +39,40 @@ namespace Jellyfin.Server.Migrations.Routines ConnectionFlags.ReadWrite, null)) { + // Query the database for the ids of duplicate extras var queryResult = connection.Query("SELECT t1.Path FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video'"); var bads = string.Join(", ", queryResult.SelectScalarString()); - if (bads.Length != 0) + + // Do nothing if no duplicate extras were detected + if (bads.Length == 0) + { + _logger.LogInformation("No duplicate extras detected, skipping migration."); + return; + } + + // Back up the database before deleting any entries + for (int i = 1; ; i++) { - _logger.LogInformation("Found duplicate extras, making {Library} backup", DbFilename); - for (int i = 1; ; i++) + var bakPath = string.Format(CultureInfo.InvariantCulture, "{0}.bak{1}", dbPath, i); + if (!File.Exists(bakPath)) { - var bakPath = string.Format(CultureInfo.InvariantCulture, "{0}.bak{1}", dbPath, i); - if (!File.Exists(bakPath)) + try { - try - { - File.Copy(dbPath, bakPath); - break; - } - catch (Exception ex) - { - _logger.LogError(ex, "Cannot make a backup of {Library}", DbFilename); - throw; - } + File.Copy(dbPath, bakPath); + _logger.LogInformation("Library database backed up to {BackupPath}", bakPath); + break; + } + catch (Exception ex) + { + _logger.LogError(ex, "Cannot make a backup of {Library} at path {BackupPath}", DbFilename, bakPath); + throw; } } - - _logger.LogInformation("Removing found duplicated extras for the following items: {DuplicateExtras}", bads); - connection.Execute("DELETE FROM TypedBaseItems WHERE rowid IN (SELECT t1.rowid FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video')"); } + + // Delete all duplicate extras + _logger.LogInformation("Removing found duplicated extras for the following items: {DuplicateExtras}", bads); + connection.Execute("DELETE FROM TypedBaseItems WHERE rowid IN (SELECT t1.rowid FROM TypedBaseItems AS t1, TypedBaseItems AS t2 WHERE t1.Path=t2.Path AND t1.Type!=t2.Type AND t1.Type='MediaBrowser.Controller.Entities.Video')"); } } } -- cgit v1.2.3 From 9cad5980594fce9a765260cae72bbea4615c7529 Mon Sep 17 00:00:00 2001 From: Erik Rigtorp Date: Thu, 30 Apr 2020 17:15:38 -0700 Subject: Fix container image build by installing python --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6e834d4e0..d3fb138a8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ ARG DOTNET_VERSION=3.1 FROM node:alpine as web-builder ARG JELLYFIN_WEB_VERSION=master -RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm \ +RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && cd jellyfin-web-* \ && yarn install \ -- cgit v1.2.3 From f144acdc9614bed64d7f8842356293a94a3b754a Mon Sep 17 00:00:00 2001 From: Erik Rigtorp Date: Tue, 12 May 2020 14:33:06 -0700 Subject: Use glob patterns to ignore files --- .../Emby.Server.Implementations.csproj | 1 + Emby.Server.Implementations/IO/LibraryMonitor.cs | 40 +----------- .../Library/CoreResolutionIgnoreRule.cs | 47 +------------- .../Library/IgnorePatterns.cs | 73 ++++++++++++++++++++++ .../Library/IgnorePatternsTests.cs | 21 +++++++ 5 files changed, 100 insertions(+), 82 deletions(-) create mode 100644 Emby.Server.Implementations/Library/IgnorePatterns.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Library/IgnorePatternsTests.cs diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 44fc932e3..bab9e1c17 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -43,6 +43,7 @@ + diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index 5a1eb43bc..eb5e190aa 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -11,6 +11,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.IO; +using Emby.Server.Implementations.Library; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.IO @@ -37,38 +38,6 @@ namespace Emby.Server.Implementations.IO /// private readonly ConcurrentDictionary _tempIgnoredPaths = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - /// - /// Any file name ending in any of these will be ignored by the watchers. - /// - private static readonly HashSet _alwaysIgnoreFiles = new HashSet(StringComparer.OrdinalIgnoreCase) - { - "small.jpg", - "albumart.jpg", - - // WMC temp recording directories that will constantly be written to - "TempRec", - "TempSBE" - }; - - private static readonly string[] _alwaysIgnoreSubstrings = new string[] - { - // Synology - "eaDir", - "#recycle", - ".wd_tv", - ".actors" - }; - - private static readonly HashSet _alwaysIgnoreExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) - { - // thumbs.db - ".db", - - // bts sync files - ".bts", - ".sync" - }; - /// /// Add the path to our temporary ignore list. Use when writing to a path within our listening scope. /// @@ -395,12 +364,7 @@ namespace Emby.Server.Implementations.IO throw new ArgumentNullException(nameof(path)); } - var filename = Path.GetFileName(path); - - var monitorPath = !string.IsNullOrEmpty(filename) && - !_alwaysIgnoreFiles.Contains(filename) && - !_alwaysIgnoreExtensions.Contains(Path.GetExtension(path)) && - _alwaysIgnoreSubstrings.All(i => path.IndexOf(i, StringComparison.OrdinalIgnoreCase) == -1); + var monitorPath = !IgnorePatterns.ShouldIgnore(path); // Ignore certain files var tempIgnorePaths = _tempIgnoredPaths.Keys.ToList(); diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs index bc1398332..218e5a0c6 100644 --- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs +++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs @@ -1,7 +1,5 @@ using System; using System.IO; -using System.Linq; -using System.Text.RegularExpressions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; @@ -16,32 +14,6 @@ namespace Emby.Server.Implementations.Library { private readonly ILibraryManager _libraryManager; - /// - /// Any folder named in this list will be ignored - /// - private static readonly string[] _ignoreFolders = - { - "metadata", - "ps3_update", - "ps3_vprm", - "extrafanart", - "extrathumbs", - ".actors", - ".wd_tv", - - // Synology - "@eaDir", - "eaDir", - "#recycle", - - // Qnap - "@Recycle", - ".@__thumb", - "$RECYCLE.BIN", - "System Volume Information", - ".grab", - }; - /// /// Initializes a new instance of the class. /// @@ -60,23 +32,15 @@ namespace Emby.Server.Implementations.Library return false; } - var filename = fileInfo.Name; - - // Ignore hidden files on UNIX - if (Environment.OSVersion.Platform != PlatformID.Win32NT - && filename[0] == '.') + if (IgnorePatterns.ShouldIgnore(fileInfo.FullName)) { return true; } + var filename = fileInfo.Name; + if (fileInfo.IsDirectory) { - // Ignore any folders in our list - if (_ignoreFolders.Contains(filename, StringComparer.OrdinalIgnoreCase)) - { - return true; - } - if (parent != null) { // Ignore trailer folders but allow it at the collection level @@ -109,11 +73,6 @@ namespace Emby.Server.Implementations.Library return true; } } - - // Ignore samples - Match m = Regex.Match(filename, @"\bsample\b", RegexOptions.IgnoreCase); - - return m.Success; } return false; diff --git a/Emby.Server.Implementations/Library/IgnorePatterns.cs b/Emby.Server.Implementations/Library/IgnorePatterns.cs new file mode 100644 index 000000000..49a36495a --- /dev/null +++ b/Emby.Server.Implementations/Library/IgnorePatterns.cs @@ -0,0 +1,73 @@ +using System.Linq; +using DotNet.Globbing; + +namespace Emby.Server.Implementations.Library +{ + /// + /// Glob patterns for files to ignore + /// + public static class IgnorePatterns + { + /// + /// Files matching these glob patterns will be ignored + /// + public static readonly string[] Patterns = new string[] + { + "**/small.jpg", + "**/albumart.jpg", + "**/*sample*", + + // Directories + "**/metadata/**", + "**/ps3_update/**", + "**/ps3_vprm/**", + "**/extrafanart/**", + "**/extrathumbs/**", + "**/.actors/**", + "**/.wd_tv/**", + + // WMC temp recording directories that will constantly be written to + "**/TempRec/**", + "**/TempSBE/**", + + // Synology + "**/eaDir/**", + "**/@eaDir/**", + "**/#recycle/**", + + // Qnap + "**/@Recycle/**", + "**/.@__thumb/**", + "**/$RECYCLE.BIN/**", + "**/System Volume Information/**", + "**/.grab/**", + + // Unix hidden files and directories + "**/.*/**", + + // thumbs.db + "**/thumbs.db", + + // bts sync files + "**/*.bts", + "**/*.sync", + }; + + private static readonly GlobOptions _globOptions = new GlobOptions + { + Evaluation = { + CaseInsensitive = true + } + }; + + private static readonly Glob[] _globs = Patterns.Select(p => Glob.Parse(p, _globOptions)).ToArray(); + + /// + /// Returns true if the supplied path should be ignored + /// + public static bool ShouldIgnore(string path) + { + return _globs.Any(g => g.IsMatch(path)); + } + } +} diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/IgnorePatternsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/IgnorePatternsTests.cs new file mode 100644 index 000000000..26dee38c6 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/IgnorePatternsTests.cs @@ -0,0 +1,21 @@ +using Emby.Server.Implementations.Library; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.Library +{ + public class IgnorePatternsTests + { + [Theory] + [InlineData("/media/small.jpg", true)] + [InlineData("/media/movies/#Recycle/test.txt", true)] + [InlineData("/media/movies/#recycle/", true)] + [InlineData("thumbs.db", true)] + [InlineData(@"C:\media\movies\movie.avi", false)] + [InlineData("/media/.hiddendir/file.mp4", true)] + [InlineData("/media/dir/.hiddenfile.mp4", true)] + public void PathIgnored(string path, bool expected) + { + Assert.Equal(expected, IgnorePatterns.ShouldIgnore(path)); + } + } +} -- cgit v1.2.3 From 9314434bbf79250f1e545b459c545f57d5acc67c Mon Sep 17 00:00:00 2001 From: MrTimscampi Date: Sat, 16 May 2020 17:35:34 +0200 Subject: Fix suggestions --- MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs | 4 ++-- MediaBrowser.Model/Entities/MediaStream.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs index d7b0e0e64..a2ea0766a 100644 --- a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs +++ b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs @@ -287,9 +287,9 @@ namespace MediaBrowser.MediaEncoding.Probing public string ColorTransfer { get; set; } /// - /// Gets or sets the color transfer. + /// Gets or sets the color primaries. /// - /// The color transfer. + /// The color primaries. [JsonPropertyName("color_primaries")] public string ColorPrimaries { get; set; } } diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index dd17623bd..d340f9ef7 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -92,7 +92,7 @@ namespace MediaBrowser.Model.Entities var colorTransfer = ColorTransfer; if (string.Equals(colorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) - || string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) + || string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) { return "HDR"; } -- cgit v1.2.3 From a33a589dba2e20790ebc2323a91645af73cbf6d3 Mon Sep 17 00:00:00 2001 From: Franco Castillo Date: Sat, 16 May 2020 18:15:27 +0000 Subject: Translated using Weblate (Spanish (Argentina)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es_AR/ --- .../Localization/Core/es-AR.json | 28 +++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/es-AR.json b/Emby.Server.Implementations/Localization/Core/es-AR.json index 1b6c6b5ae..fc9a10f27 100644 --- a/Emby.Server.Implementations/Localization/Core/es-AR.json +++ b/Emby.Server.Implementations/Localization/Core/es-AR.json @@ -24,7 +24,7 @@ "HeaderFavoriteShows": "Programas favoritos", "HeaderFavoriteSongs": "Canciones favoritas", "HeaderLiveTV": "TV en vivo", - "HeaderNextUp": "A Continuación", + "HeaderNextUp": "Siguiente", "HeaderRecordingGroups": "Grupos de grabación", "HomeVideos": "Videos caseros", "Inherit": "Heredar", @@ -44,7 +44,7 @@ "NameInstallFailed": "{0} instalación fallida", "NameSeasonNumber": "Temporada {0}", "NameSeasonUnknown": "Temporada desconocida", - "NewVersionIsAvailable": "Una nueva versión del Servidor Jellyfin está disponible para descargar.", + "NewVersionIsAvailable": "Una nueva versión del servidor Jellyfin está disponible para descargar.", "NotificationOptionApplicationUpdateAvailable": "Actualización de la aplicación disponible", "NotificationOptionApplicationUpdateInstalled": "Actualización de la aplicación instalada", "NotificationOptionAudioPlayback": "Se inició la reproducción de audio", @@ -56,7 +56,7 @@ "NotificationOptionPluginInstalled": "Complemento instalado", "NotificationOptionPluginUninstalled": "Complemento desinstalado", "NotificationOptionPluginUpdateInstalled": "Actualización de complemento instalada", - "NotificationOptionServerRestartRequired": "Se necesita reiniciar el Servidor", + "NotificationOptionServerRestartRequired": "Se necesita reiniciar el servidor", "NotificationOptionTaskFailed": "Falla de tarea programada", "NotificationOptionUserLockedOut": "Usuario bloqueado", "NotificationOptionVideoPlayback": "Se inició la reproducción de video", @@ -71,7 +71,7 @@ "ScheduledTaskFailedWithName": "{0} falló", "ScheduledTaskStartedWithName": "{0} iniciado", "ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado", - "Shows": "Series", + "Shows": "Programas", "Songs": "Canciones", "StartupEmbyServerIsLoading": "El servidor Jellyfin se está cargando. Vuelve a intentarlo en breve.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", @@ -94,25 +94,25 @@ "ValueSpecialEpisodeName": "Especial - {0}", "VersionNumber": "Versión {0}", "TaskDownloadMissingSubtitlesDescription": "Busca en internet los subtítulos que falten basándose en la configuración de los metadatos.", - "TaskDownloadMissingSubtitles": "Descargar subtítulos extraviados", + "TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes", "TaskRefreshChannelsDescription": "Actualizar información de canales de internet.", "TaskRefreshChannels": "Actualizar canales", "TaskCleanTranscodeDescription": "Eliminar archivos transcodificados con mas de un día de antigüedad.", - "TaskCleanTranscode": "Limpiar directorio de Transcodificado", + "TaskCleanTranscode": "Limpiar directorio de transcodificación", "TaskUpdatePluginsDescription": "Descargar e instalar actualizaciones para complementos que estén configurados en actualizar automáticamente.", "TaskUpdatePlugins": "Actualizar complementos", - "TaskRefreshPeopleDescription": "Actualizar metadatos de actores y directores en su librería multimedia.", + "TaskRefreshPeopleDescription": "Actualizar metadatos de actores y directores en su biblioteca multimedia.", "TaskRefreshPeople": "Actualizar personas", "TaskCleanLogsDescription": "Eliminar archivos de registro que tengan mas de {0} días de antigüedad.", "TaskCleanLogs": "Limpiar directorio de registros", - "TaskRefreshLibraryDescription": "Escanear su librería multimedia por nuevos archivos y refrescar metadatos.", - "TaskRefreshLibrary": "Escanear librería multimedia", + "TaskRefreshLibraryDescription": "Escanear su biblioteca multimedia por nuevos archivos y refrescar metadatos.", + "TaskRefreshLibrary": "Escanear biblioteca multimedia", "TaskRefreshChapterImagesDescription": "Crear miniaturas de videos que tengan capítulos.", - "TaskRefreshChapterImages": "Extraer imágenes de capitulo", - "TaskCleanCacheDescription": "Eliminar archivos de cache que no se necesiten en el sistema.", - "TaskCleanCache": "Limpiar directorio Cache", - "TasksChannelsCategory": "Canales de Internet", - "TasksApplicationCategory": "Solicitud", + "TaskRefreshChapterImages": "Extraer imágenes de capítulo", + "TaskCleanCacheDescription": "Eliminar archivos de caché que no se necesiten en el sistema.", + "TaskCleanCache": "Limpiar directorio caché", + "TasksChannelsCategory": "Canales de internet", + "TasksApplicationCategory": "Aplicación", "TasksLibraryCategory": "Biblioteca", "TasksMaintenanceCategory": "Mantenimiento" } -- cgit v1.2.3 From e75b2cd33568b3cae2a344190226b1d83a12230f Mon Sep 17 00:00:00 2001 From: Samuel Gagnon Date: Sat, 16 May 2020 16:53:30 +0000 Subject: Translated using Weblate (French (Canada)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr_CA/ --- .../Localization/Core/fr-CA.json | 31 +++++++++++----------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/fr-CA.json b/Emby.Server.Implementations/Localization/Core/fr-CA.json index c2349ba5b..3dcfa6844 100644 --- a/Emby.Server.Implementations/Localization/Core/fr-CA.json +++ b/Emby.Server.Implementations/Localization/Core/fr-CA.json @@ -96,21 +96,22 @@ "TasksLibraryCategory": "Bibliothèque", "TasksMaintenanceCategory": "Entretien", "TaskDownloadMissingSubtitlesDescription": "Recherche l'internet pour des sous-titres manquants à base de métadonnées configurées.", - "TaskDownloadMissingSubtitles": "Télécharger des sous-titres manquants", - "TaskRefreshChannelsDescription": "Rafraîchit des informations des chaines d'internet.", + "TaskDownloadMissingSubtitles": "Télécharger les sous-titres manquants", + "TaskRefreshChannelsDescription": "Rafraîchit des informations des chaines internet.", "TaskRefreshChannels": "Rafraîchir des chaines", - "TaskCleanTranscodeDescription": "Retirer des fichiers de transcodage de plus qu'un jour.", - "TaskCleanTranscode": "Nettoyer le directoire de transcodage", - "TaskUpdatePluginsDescription": "Télécharger et installer des mises à jours des plugins qui sont configurés m.à.j. automisés.", - "TaskUpdatePlugins": "Mise à jour des plugins", - "TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et réalisateurs dans votre bibliothèque.", + "TaskCleanTranscodeDescription": "Supprime les fichiers de transcodage de plus d'un jour.", + "TaskCleanTranscode": "Nettoyer le répertoire de transcodage", + "TaskUpdatePluginsDescription": "Télécharger et installer les mises à jours des extensions qui sont configurés pour les m.à.j. automisés.", + "TaskUpdatePlugins": "Mise à jour des extensions", + "TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et réalisateurs dans votre bibliothèque de médias.", "TaskRefreshPeople": "Rafraîchir les acteurs", - "TaskCleanLogsDescription": "Retire les données qui ont plus que {0} jours.", - "TaskCleanLogs": "Nettoyer les données de directoire", - "TaskRefreshLibraryDescription": "Analyse votre bibliothèque média pour des nouveaux fichiers et rafraîchit les métadonnées.", - "TaskRefreshChapterImages": "Extraire des images du chapitre", - "TaskRefreshChapterImagesDescription": "Créer des vignettes pour des vidéos qui ont des chapitres", - "TaskRefreshLibrary": "Analyser la bibliothèque de média", - "TaskCleanCache": "Nettoyer le cache de directoire", - "TasksApplicationCategory": "Application" + "TaskCleanLogsDescription": "Supprime les journaux qui ont plus que {0} jours.", + "TaskCleanLogs": "Nettoyer le répertoire des journaux", + "TaskRefreshLibraryDescription": "Analyse votre bibliothèque média pour trouver de nouveaux fichiers et rafraîchit les métadonnées.", + "TaskRefreshChapterImages": "Extraire les images de chapitre", + "TaskRefreshChapterImagesDescription": "Créer des vignettes pour les vidéos qui ont des chapitres", + "TaskRefreshLibrary": "Analyser la bibliothèque de médias", + "TaskCleanCache": "Nettoyer le répertoire des fichiers temporaires", + "TasksApplicationCategory": "Application", + "TaskCleanCacheDescription": "Supprime les fichiers temporaires qui ne sont plus nécessaire pour le système." } -- cgit v1.2.3 From 3bc07e7c56880de8b75cbd36c79deff6decf8e77 Mon Sep 17 00:00:00 2001 From: Nathan Kessler Date: Sun, 17 May 2020 10:48:30 -0400 Subject: Fix 500 error causing first-time setup wizard to hang --- Jellyfin.Api/Auth/CustomAuthenticationHandler.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs index 26f7d9d2d..aab1141ee 100644 --- a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs +++ b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs @@ -1,3 +1,4 @@ +using System.Security.Authentication; using System.Security.Claims; using System.Text.Encodings.Web; using System.Threading.Tasks; @@ -59,6 +60,10 @@ namespace Jellyfin.Api.Auth return Task.FromResult(AuthenticateResult.Success(ticket)); } + catch (AuthenticationException ex) + { + return Task.FromResult(AuthenticateResult.Fail(ex)); + } catch (SecurityException ex) { return Task.FromResult(AuthenticateResult.Fail(ex)); -- cgit v1.2.3 From 3ed76d7e083940b53011c7a11a52cdb71d7aa715 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 17 May 2020 13:33:38 -0400 Subject: Update to .NET Core 3.1.4 --- Emby.Server.Implementations/Emby.Server.Implementations.csproj | 8 ++++---- Jellyfin.Api/Jellyfin.Api.csproj | 2 +- Jellyfin.Data/Jellyfin.Data.csproj | 4 ++-- .../Jellyfin.Server.Implementations.csproj | 7 +++++-- Jellyfin.Server/Jellyfin.Server.csproj | 4 ++-- MediaBrowser.Common/MediaBrowser.Common.csproj | 4 ++-- MediaBrowser.Controller/MediaBrowser.Controller.csproj | 4 ++-- MediaBrowser.Model/MediaBrowser.Model.csproj | 4 ++-- MediaBrowser.Providers/MediaBrowser.Providers.csproj | 4 ++-- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 2 +- tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj | 2 +- 11 files changed, 24 insertions(+), 21 deletions(-) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 896e4310e..e95228b70 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -34,10 +34,10 @@ - - - - + + + + diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index a582a209c..25d5d0c89 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -13,7 +13,7 @@ - + diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index b2a3f7eb3..9157c3ead 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -19,8 +19,8 @@ - - + + diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 149ca5020..8486fc2df 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -26,8 +26,11 @@ - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 9eec6ed4e..c93aa837e 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -41,8 +41,8 @@ - - + + diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 69864106c..a597b9052 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -17,8 +17,8 @@ - - + + diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 4e7d02737..223bbe1de 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 5c6e313e0..461f59672 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -21,9 +21,9 @@ - + - + diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 1b3df63b6..5073b4015 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -16,8 +16,8 @@ - - + + diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index fb76f34d0..9c4b7b0b0 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -16,7 +16,7 @@ - + diff --git a/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj b/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj index f30e48690..60c392314 100644 --- a/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj +++ b/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj @@ -8,7 +8,7 @@ - + -- cgit v1.2.3 From 526e47c3624aca76234006b031b74e595f295cc8 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 17 May 2020 14:22:36 -0400 Subject: Clean up documentation --- .../Providers/ExternalIdMediaType.cs | 60 +++++++++++++++++----- MediaBrowser.Controller/Providers/IExternalId.cs | 21 ++++++-- MediaBrowser.Model/Providers/ExternalIdInfo.cs | 2 +- 3 files changed, 63 insertions(+), 20 deletions(-) diff --git a/MediaBrowser.Controller/Providers/ExternalIdMediaType.cs b/MediaBrowser.Controller/Providers/ExternalIdMediaType.cs index 470f1e24c..dc87aefc8 100644 --- a/MediaBrowser.Controller/Providers/ExternalIdMediaType.cs +++ b/MediaBrowser.Controller/Providers/ExternalIdMediaType.cs @@ -1,45 +1,77 @@ namespace MediaBrowser.Controller.Providers { - /// The specific media type of an . + /// + /// The specific media type of an . + /// + /// + /// This is used as a translation key for clients. + /// public enum ExternalIdMediaType { - /// There is no specific media type + /// + /// There is no specific media type associated with the external id, or the external provider only has one + /// id type so there is no need to be specific. + /// None, - /// A music album + /// + /// A music album. + /// Album, - /// The artist of a music album + /// + /// The artist of a music album. + /// AlbumArtist, - /// The artist of a media item + /// + /// The artist of a media item. + /// Artist, - /// A boxed set of media + /// + /// A boxed set of media. + /// BoxSet, - /// A series episode + /// + /// A series episode. + /// Episode, - /// A movie + /// + /// A movie. + /// Movie, - /// An alternative artist apart from the main artist + /// + /// An alternative artist apart from the main artist. + /// OtherArtist, - /// A person + /// + /// A person. + /// Person, - /// A release group + /// + /// A release group. + /// ReleaseGroup, - /// A single season of a series + /// + /// A single season of a series. + /// Season, - /// A series + /// + /// A series. + /// Series, - /// A music track + /// + /// A music track. + /// Track } } diff --git a/MediaBrowser.Controller/Providers/IExternalId.cs b/MediaBrowser.Controller/Providers/IExternalId.cs index c877ffe1f..f362c42eb 100644 --- a/MediaBrowser.Controller/Providers/IExternalId.cs +++ b/MediaBrowser.Controller/Providers/IExternalId.cs @@ -5,19 +5,30 @@ namespace MediaBrowser.Controller.Providers /// Represents and identifier for an external provider. public interface IExternalId { - /// Gets the name used to identify this provider + /// + /// Gets the display name of the provider associated with this ID type. + /// string Name { get; } - /// Gets the unique key to distinguish this provider/type pair. This should be unique across providers. + /// + /// Gets the unique key to distinguish this provider/type pair. This should be unique across providers. + /// + // TODO: This property is not actually unique at the moment. It should be updated to be unique. string Key { get; } - /// Gets the specific media type for this id. + /// + /// Gets the specific media type for this id. + /// ExternalIdMediaType Type { get; } - /// Gets the url format string for this id. + /// + /// Gets the URL format string for this id. + /// string UrlFormatString { get; } - /// Determines whether this id supports a given item type. + /// + /// Determines whether this id supports a given item type. + /// /// The item. /// True if this item is supported, otherwise false. bool Supports(IHasProviderIds item); diff --git a/MediaBrowser.Model/Providers/ExternalIdInfo.cs b/MediaBrowser.Model/Providers/ExternalIdInfo.cs index befcc309b..ca0c857c4 100644 --- a/MediaBrowser.Model/Providers/ExternalIdInfo.cs +++ b/MediaBrowser.Model/Providers/ExternalIdInfo.cs @@ -6,7 +6,7 @@ namespace MediaBrowser.Model.Providers public class ExternalIdInfo { /// - /// Gets or sets the name of the external id provider (IE: IMDB, MusicBrainz, etc). + /// Gets or sets the display name of the external id provider (IE: IMDB, MusicBrainz, etc). /// public string Name { get; set; } -- cgit v1.2.3 From e5c857ac3639c2aba34e59437e501bfdd6b1ba02 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 17 May 2020 15:29:53 -0400 Subject: Rename external id type 'None' to 'General' --- MediaBrowser.Controller/Providers/ExternalIdMediaType.cs | 2 +- MediaBrowser.Providers/Manager/ProviderManager.cs | 2 +- MediaBrowser.Providers/Movies/MovieExternalIds.cs | 2 +- MediaBrowser.Providers/Music/MusicExternalIds.cs | 2 +- MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs | 2 +- MediaBrowser.Providers/TV/TvExternalIds.cs | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.Controller/Providers/ExternalIdMediaType.cs b/MediaBrowser.Controller/Providers/ExternalIdMediaType.cs index dc87aefc8..43c2b2f22 100644 --- a/MediaBrowser.Controller/Providers/ExternalIdMediaType.cs +++ b/MediaBrowser.Controller/Providers/ExternalIdMediaType.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.Providers /// There is no specific media type associated with the external id, or the external provider only has one /// id type so there is no need to be specific. /// - None, + General, /// /// A music album. diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index b0380ae31..49083c01b 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -908,7 +908,7 @@ namespace MediaBrowser.Providers.Manager { Name = i.Name, Key = i.Key, - Type = i.Type == ExternalIdMediaType.None ? null : i.Type.ToString(), + Type = i.Type == ExternalIdMediaType.General ? null : i.Type.ToString(), UrlFormatString = i.UrlFormatString }); } diff --git a/MediaBrowser.Providers/Movies/MovieExternalIds.cs b/MediaBrowser.Providers/Movies/MovieExternalIds.cs index a7b359a2d..1098b9882 100644 --- a/MediaBrowser.Providers/Movies/MovieExternalIds.cs +++ b/MediaBrowser.Providers/Movies/MovieExternalIds.cs @@ -16,7 +16,7 @@ namespace MediaBrowser.Providers.Movies public string Key => MetadataProviders.Imdb.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.None; + public ExternalIdMediaType Type => ExternalIdMediaType.General; /// public string UrlFormatString => "https://www.imdb.com/title/{0}"; diff --git a/MediaBrowser.Providers/Music/MusicExternalIds.cs b/MediaBrowser.Providers/Music/MusicExternalIds.cs index 19879411e..351cdb92c 100644 --- a/MediaBrowser.Providers/Music/MusicExternalIds.cs +++ b/MediaBrowser.Providers/Music/MusicExternalIds.cs @@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Music public string Key => "IMVDb"; /// - public ExternalIdMediaType Type => ExternalIdMediaType.None; + public ExternalIdMediaType Type => ExternalIdMediaType.General; /// public string UrlFormatString => null; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs b/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs index cd65acb76..150149a6b 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs @@ -13,7 +13,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public string Key => MetadataProviders.AudioDbAlbum.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.None; + public ExternalIdMediaType Type => ExternalIdMediaType.General; /// public string UrlFormatString => "https://www.theaudiodb.com/album/{0}"; diff --git a/MediaBrowser.Providers/TV/TvExternalIds.cs b/MediaBrowser.Providers/TV/TvExternalIds.cs index 75d8b6bf5..ccdf8bd29 100644 --- a/MediaBrowser.Providers/TV/TvExternalIds.cs +++ b/MediaBrowser.Providers/TV/TvExternalIds.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.TV public string Key => MetadataProviders.Zap2It.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.None; + public ExternalIdMediaType Type => ExternalIdMediaType.General; /// public string UrlFormatString => "http://tvlistings.zap2it.com/overview.html?programSeriesId={0}"; @@ -32,7 +32,7 @@ namespace MediaBrowser.Providers.TV public string Key => MetadataProviders.Tvdb.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.None; + public ExternalIdMediaType Type => ExternalIdMediaType.General; /// public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=series&id={0}"; -- cgit v1.2.3 From 61e65d032ecb1d2bf614e018f4a0dd925300cfde Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sun, 17 May 2020 20:43:54 +0100 Subject: Update MediaBrowser.Common/Net/INetworkManager.cs Co-authored-by: Patrick Barron <18354464+barronpm@users.noreply.github.com> --- MediaBrowser.Common/Net/INetworkManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 19314ada8..783b7c60c 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -62,7 +62,7 @@ namespace MediaBrowser.Common.Net IPAddress[] GetLocalIpAddresses(); /// - /// Checks if the give address false within the ranges givin in [subnets]. The addresses in subnets can be hosts or subnets in the CIDR format. + /// Checks if the given address falls within the ranges given in [subnets]. The addresses in subnets can be hosts or subnets in the CIDR format. /// /// The address to check /// If true, check against addresses in the LAN settings which have [] arroud and return true if it matches the address give in address -- cgit v1.2.3 From 5e1be0d4f0ac6ec4aa5f30ab617b544a38420c1a Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sun, 17 May 2020 20:44:19 +0100 Subject: Update MediaBrowser.Common/Net/INetworkManager.cs Co-authored-by: Patrick Barron <18354464+barronpm@users.noreply.github.com> --- MediaBrowser.Common/Net/INetworkManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 783b7c60c..1fec0390d 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -65,7 +65,7 @@ namespace MediaBrowser.Common.Net /// Checks if the given address falls within the ranges given in [subnets]. The addresses in subnets can be hosts or subnets in the CIDR format. /// /// The address to check - /// If true, check against addresses in the LAN settings which have [] arroud and return true if it matches the address give in address + /// If true, check against addresses in the LAN settings surrounded by brackets ([]) /// falseif the address isn't in the subnets, true otherwise bool IsAddressInSubnets(string addressString, string[] subnets); -- cgit v1.2.3 From d5a924772b0b25808beb3405a041a037bbc679c8 Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sun, 17 May 2020 20:44:35 +0100 Subject: Update MediaBrowser.Common/Net/INetworkManager.cs Co-authored-by: Patrick Barron <18354464+barronpm@users.noreply.github.com> --- MediaBrowser.Common/Net/INetworkManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 1fec0390d..56b253b2d 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -66,7 +66,7 @@ namespace MediaBrowser.Common.Net /// /// The address to check /// If true, check against addresses in the LAN settings surrounded by brackets ([]) - /// falseif the address isn't in the subnets, true otherwise + /// trueif the address is in at least one of the given subnets, false otherwise. bool IsAddressInSubnets(string addressString, string[] subnets); /// -- cgit v1.2.3 From 3a5333228fc28c511c7a2d44ea0bc036c0474ccf Mon Sep 17 00:00:00 2001 From: BaronGreenback Date: Sun, 17 May 2020 20:44:44 +0100 Subject: Update Emby.Server.Implementations/Networking/NetworkManager.cs Co-authored-by: Patrick Barron <18354464+barronpm@users.noreply.github.com> --- Emby.Server.Implementations/Networking/NetworkManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index b14b84541..f2cbdbaa5 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -200,7 +200,7 @@ namespace Emby.Server.Implementations.Networking } /// - /// Checks if the give address false within the ranges givin in [subnets]. The addresses in subnets can be hosts or subnets in the CIDR format. + /// Checks if the give address falls within the ranges given in [subnets]. The addresses in subnets can be hosts or subnets in the CIDR format. /// /// IPAddress version of the address. /// The address to check. -- cgit v1.2.3 From 422d5b2b68bdce4da385a19382a7a52060cb10b2 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 17 May 2020 15:57:24 -0400 Subject: Move ExternalIdMediaType enum to MediaBrowser.Model --- .../Providers/ExternalIdMediaType.cs | 77 ---------------------- MediaBrowser.Controller/Providers/IExternalId.cs | 1 + .../Providers/ExternalIdMediaType.cs | 77 ++++++++++++++++++++++ MediaBrowser.Providers/Movies/MovieExternalIds.cs | 1 + MediaBrowser.Providers/Music/MusicExternalIds.cs | 1 + .../Plugins/AudioDb/ExternalIds.cs | 1 + .../Plugins/MusicBrainz/ExternalIds.cs | 1 + MediaBrowser.Providers/TV/TvExternalIds.cs | 1 + .../Tmdb/BoxSets/TmdbBoxSetExternalId.cs | 1 + .../Tmdb/Movies/TmdbMovieExternalId.cs | 1 + .../Tmdb/People/TmdbPersonExternalId.cs | 1 + .../Tmdb/TV/TmdbSeriesExternalId.cs | 1 + 12 files changed, 87 insertions(+), 77 deletions(-) delete mode 100644 MediaBrowser.Controller/Providers/ExternalIdMediaType.cs create mode 100644 MediaBrowser.Model/Providers/ExternalIdMediaType.cs diff --git a/MediaBrowser.Controller/Providers/ExternalIdMediaType.cs b/MediaBrowser.Controller/Providers/ExternalIdMediaType.cs deleted file mode 100644 index 43c2b2f22..000000000 --- a/MediaBrowser.Controller/Providers/ExternalIdMediaType.cs +++ /dev/null @@ -1,77 +0,0 @@ -namespace MediaBrowser.Controller.Providers -{ - /// - /// The specific media type of an . - /// - /// - /// This is used as a translation key for clients. - /// - public enum ExternalIdMediaType - { - /// - /// There is no specific media type associated with the external id, or the external provider only has one - /// id type so there is no need to be specific. - /// - General, - - /// - /// A music album. - /// - Album, - - /// - /// The artist of a music album. - /// - AlbumArtist, - - /// - /// The artist of a media item. - /// - Artist, - - /// - /// A boxed set of media. - /// - BoxSet, - - /// - /// A series episode. - /// - Episode, - - /// - /// A movie. - /// - Movie, - - /// - /// An alternative artist apart from the main artist. - /// - OtherArtist, - - /// - /// A person. - /// - Person, - - /// - /// A release group. - /// - ReleaseGroup, - - /// - /// A single season of a series. - /// - Season, - - /// - /// A series. - /// - Series, - - /// - /// A music track. - /// - Track - } -} diff --git a/MediaBrowser.Controller/Providers/IExternalId.cs b/MediaBrowser.Controller/Providers/IExternalId.cs index f362c42eb..0fcf0c09d 100644 --- a/MediaBrowser.Controller/Providers/IExternalId.cs +++ b/MediaBrowser.Controller/Providers/IExternalId.cs @@ -1,4 +1,5 @@ using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Controller.Providers { diff --git a/MediaBrowser.Model/Providers/ExternalIdMediaType.cs b/MediaBrowser.Model/Providers/ExternalIdMediaType.cs new file mode 100644 index 000000000..8c5356c92 --- /dev/null +++ b/MediaBrowser.Model/Providers/ExternalIdMediaType.cs @@ -0,0 +1,77 @@ +namespace MediaBrowser.Model.Providers +{ + /// + /// The specific media type of an . + /// + /// + /// This is used as a translation key for clients. + /// + public enum ExternalIdMediaType + { + /// + /// There is no specific media type associated with the external id, or the external provider only has one + /// id type so there is no need to be specific. + /// + General, + + /// + /// A music album. + /// + Album, + + /// + /// The artist of a music album. + /// + AlbumArtist, + + /// + /// The artist of a media item. + /// + Artist, + + /// + /// A boxed set of media. + /// + BoxSet, + + /// + /// A series episode. + /// + Episode, + + /// + /// A movie. + /// + Movie, + + /// + /// An alternative artist apart from the main artist. + /// + OtherArtist, + + /// + /// A person. + /// + Person, + + /// + /// A release group. + /// + ReleaseGroup, + + /// + /// A single season of a series. + /// + Season, + + /// + /// A series. + /// + Series, + + /// + /// A music track. + /// + Track + } +} diff --git a/MediaBrowser.Providers/Movies/MovieExternalIds.cs b/MediaBrowser.Providers/Movies/MovieExternalIds.cs index 1098b9882..a82394a93 100644 --- a/MediaBrowser.Providers/Movies/MovieExternalIds.cs +++ b/MediaBrowser.Providers/Movies/MovieExternalIds.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Movies { diff --git a/MediaBrowser.Providers/Music/MusicExternalIds.cs b/MediaBrowser.Providers/Music/MusicExternalIds.cs index 351cdb92c..12323bcbd 100644 --- a/MediaBrowser.Providers/Music/MusicExternalIds.cs +++ b/MediaBrowser.Providers/Music/MusicExternalIds.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Music { diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs b/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs index 150149a6b..126a79a08 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.AudioDb { diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs index 7d74a8d35..5756cb838 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; using MediaBrowser.Providers.Plugins.MusicBrainz; namespace MediaBrowser.Providers.Music diff --git a/MediaBrowser.Providers/TV/TvExternalIds.cs b/MediaBrowser.Providers/TV/TvExternalIds.cs index ccdf8bd29..4c005666f 100644 --- a/MediaBrowser.Providers/TV/TvExternalIds.cs +++ b/MediaBrowser.Providers/TV/TvExternalIds.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; using MediaBrowser.Providers.Plugins.TheTvdb; namespace MediaBrowser.Providers.TV diff --git a/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetExternalId.cs b/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetExternalId.cs index a83cde93c..6e131029c 100644 --- a/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetExternalId.cs +++ b/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetExternalId.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Tmdb.BoxSets { diff --git a/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieExternalId.cs b/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieExternalId.cs index f9ea00067..cffd33542 100644 --- a/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieExternalId.cs +++ b/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieExternalId.cs @@ -3,6 +3,7 @@ using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Tmdb.Movies { diff --git a/MediaBrowser.Providers/Tmdb/People/TmdbPersonExternalId.cs b/MediaBrowser.Providers/Tmdb/People/TmdbPersonExternalId.cs index 854fd4156..460740254 100644 --- a/MediaBrowser.Providers/Tmdb/People/TmdbPersonExternalId.cs +++ b/MediaBrowser.Providers/Tmdb/People/TmdbPersonExternalId.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Tmdb.People { diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesExternalId.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesExternalId.cs index 770448c7f..8adcc4d46 100644 --- a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesExternalId.cs +++ b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesExternalId.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Tmdb.TV { -- cgit v1.2.3 From 67edf1b7f5f423ab5fa53644bacdba6974b430db Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 17 May 2020 15:59:13 -0400 Subject: Do not convert 'Type' value to string unnecessarily, and do not replace 'General' type with null --- MediaBrowser.Controller/Providers/IExternalId.cs | 3 +++ MediaBrowser.Model/Providers/ExternalIdInfo.cs | 9 +++++---- MediaBrowser.Providers/Manager/ProviderManager.cs | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.Controller/Providers/IExternalId.cs b/MediaBrowser.Controller/Providers/IExternalId.cs index 0fcf0c09d..149c58847 100644 --- a/MediaBrowser.Controller/Providers/IExternalId.cs +++ b/MediaBrowser.Controller/Providers/IExternalId.cs @@ -20,6 +20,9 @@ namespace MediaBrowser.Controller.Providers /// /// Gets the specific media type for this id. /// + /// + /// This can be used along with the to localize the external id on the client. + /// ExternalIdMediaType Type { get; } /// diff --git a/MediaBrowser.Model/Providers/ExternalIdInfo.cs b/MediaBrowser.Model/Providers/ExternalIdInfo.cs index ca0c857c4..493c6136e 100644 --- a/MediaBrowser.Model/Providers/ExternalIdInfo.cs +++ b/MediaBrowser.Model/Providers/ExternalIdInfo.cs @@ -16,11 +16,12 @@ namespace MediaBrowser.Model.Providers public string Key { get; set; } /// - /// Gets or sets the media type (Album, Artist, etc). - /// This can be null if there is no specific type. - /// This string is also used to localize the media type on the client. + /// Gets or sets the specific media type for this id. /// - public string Type { get; set; } + /// + /// This can be used along with the to localize the external id on the client. + /// + public ExternalIdMediaType Type { get; set; } /// /// Gets or sets the URL format string. diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 49083c01b..4ab758123 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -908,7 +908,7 @@ namespace MediaBrowser.Providers.Manager { Name = i.Name, Key = i.Key, - Type = i.Type == ExternalIdMediaType.General ? null : i.Type.ToString(), + Type = i.Type, UrlFormatString = i.UrlFormatString }); } -- cgit v1.2.3 From c82f7eeca14839cd21e1904f79007c0d24c85f8f Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 17 May 2020 16:24:28 -0400 Subject: Clean up some doc comments --- MediaBrowser.Controller/Providers/IExternalId.cs | 9 ++++++--- MediaBrowser.Model/Providers/ExternalIdInfo.cs | 4 +++- MediaBrowser.Model/Providers/ExternalIdMediaType.cs | 8 ++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/MediaBrowser.Controller/Providers/IExternalId.cs b/MediaBrowser.Controller/Providers/IExternalId.cs index 149c58847..ae87aab9e 100644 --- a/MediaBrowser.Controller/Providers/IExternalId.cs +++ b/MediaBrowser.Controller/Providers/IExternalId.cs @@ -3,7 +3,9 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Controller.Providers { - /// Represents and identifier for an external provider. + /// + /// Represents an identifier for an external provider. + /// public interface IExternalId { /// @@ -14,11 +16,12 @@ namespace MediaBrowser.Controller.Providers /// /// Gets the unique key to distinguish this provider/type pair. This should be unique across providers. /// - // TODO: This property is not actually unique at the moment. It should be updated to be unique. + // TODO: This property is not actually unique across the concrete types at the moment. It should be updated to be unique. string Key { get; } /// - /// Gets the specific media type for this id. + /// Gets the specific media type for this id. This is used to distinguish between the different + /// external id types for providers with multiple ids. /// /// /// This can be used along with the to localize the external id on the client. diff --git a/MediaBrowser.Model/Providers/ExternalIdInfo.cs b/MediaBrowser.Model/Providers/ExternalIdInfo.cs index 493c6136e..92a639546 100644 --- a/MediaBrowser.Model/Providers/ExternalIdInfo.cs +++ b/MediaBrowser.Model/Providers/ExternalIdInfo.cs @@ -13,10 +13,12 @@ namespace MediaBrowser.Model.Providers /// /// Gets or sets the unique key for this id. This key should be unique across all providers. /// + // TODO: This property is not actually unique across the concrete types at the moment. It should be updated to be unique. public string Key { get; set; } /// - /// Gets or sets the specific media type for this id. + /// Gets or sets the specific media type for this id. This is used to distinguish between the different + /// external id types for providers with multiple ids. /// /// /// This can be used along with the to localize the external id on the client. diff --git a/MediaBrowser.Model/Providers/ExternalIdMediaType.cs b/MediaBrowser.Model/Providers/ExternalIdMediaType.cs index 8c5356c92..881cd77fc 100644 --- a/MediaBrowser.Model/Providers/ExternalIdMediaType.cs +++ b/MediaBrowser.Model/Providers/ExternalIdMediaType.cs @@ -1,16 +1,16 @@ namespace MediaBrowser.Model.Providers { /// - /// The specific media type of an . + /// The specific media type of an . /// /// - /// This is used as a translation key for clients. + /// Client applications may use this as a translation key. /// public enum ExternalIdMediaType { /// - /// There is no specific media type associated with the external id, or the external provider only has one - /// id type so there is no need to be specific. + /// There is no specific media type associated with the external id, or this is the default id for the external + /// provider so there is no need to specify a type. /// General, -- cgit v1.2.3 From d06fee75b65def754ce58e8322a59bf8c942b82f Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 17 May 2020 17:35:43 -0400 Subject: Rename Name to ProviderName --- MediaBrowser.Controller/Providers/IExternalId.cs | 4 ++-- MediaBrowser.Model/Providers/ExternalIdInfo.cs | 1 + MediaBrowser.Providers/Manager/ProviderManager.cs | 6 +++--- MediaBrowser.Providers/Movies/MovieExternalIds.cs | 4 ++-- MediaBrowser.Providers/Music/MusicExternalIds.cs | 2 +- MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs | 8 ++++---- MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs | 12 ++++++------ MediaBrowser.Providers/TV/TvExternalIds.cs | 8 ++++---- MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetExternalId.cs | 2 +- MediaBrowser.Providers/Tmdb/Movies/TmdbMovieExternalId.cs | 2 +- MediaBrowser.Providers/Tmdb/People/TmdbPersonExternalId.cs | 2 +- MediaBrowser.Providers/Tmdb/TV/TmdbSeriesExternalId.cs | 2 +- 12 files changed, 27 insertions(+), 26 deletions(-) diff --git a/MediaBrowser.Controller/Providers/IExternalId.cs b/MediaBrowser.Controller/Providers/IExternalId.cs index ae87aab9e..96d1e4622 100644 --- a/MediaBrowser.Controller/Providers/IExternalId.cs +++ b/MediaBrowser.Controller/Providers/IExternalId.cs @@ -11,7 +11,7 @@ namespace MediaBrowser.Controller.Providers /// /// Gets the display name of the provider associated with this ID type. /// - string Name { get; } + string ProviderName { get; } /// /// Gets the unique key to distinguish this provider/type pair. This should be unique across providers. @@ -24,7 +24,7 @@ namespace MediaBrowser.Controller.Providers /// external id types for providers with multiple ids. /// /// - /// This can be used along with the to localize the external id on the client. + /// This can be used along with the to localize the external id on the client. /// ExternalIdMediaType Type { get; } diff --git a/MediaBrowser.Model/Providers/ExternalIdInfo.cs b/MediaBrowser.Model/Providers/ExternalIdInfo.cs index 92a639546..445c86d73 100644 --- a/MediaBrowser.Model/Providers/ExternalIdInfo.cs +++ b/MediaBrowser.Model/Providers/ExternalIdInfo.cs @@ -8,6 +8,7 @@ namespace MediaBrowser.Model.Providers /// /// Gets or sets the display name of the external id provider (IE: IMDB, MusicBrainz, etc). /// + // TODO: This should be renamed to ProviderName public string Name { get; set; } /// diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 4ab758123..95133a9a7 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -96,7 +96,7 @@ namespace MediaBrowser.Providers.Manager _metadataServices = metadataServices.OrderBy(i => i.Order).ToArray(); _metadataProviders = metadataProviders.ToArray(); - _externalIds = externalIds.OrderBy(i => i.Name).ToArray(); + _externalIds = externalIds.OrderBy(i => i.ProviderName).ToArray(); _savers = metadataSavers.Where(i => { @@ -891,7 +891,7 @@ namespace MediaBrowser.Providers.Manager return new ExternalUrl { - Name = i.Name, + Name = i.ProviderName, Url = string.Format( CultureInfo.InvariantCulture, i.UrlFormatString, @@ -906,7 +906,7 @@ namespace MediaBrowser.Providers.Manager return GetExternalIds(item) .Select(i => new ExternalIdInfo { - Name = i.Name, + Name = i.ProviderName, Key = i.Key, Type = i.Type, UrlFormatString = i.UrlFormatString diff --git a/MediaBrowser.Providers/Movies/MovieExternalIds.cs b/MediaBrowser.Providers/Movies/MovieExternalIds.cs index a82394a93..2b0c0d1c2 100644 --- a/MediaBrowser.Providers/Movies/MovieExternalIds.cs +++ b/MediaBrowser.Providers/Movies/MovieExternalIds.cs @@ -11,7 +11,7 @@ namespace MediaBrowser.Providers.Movies public class ImdbExternalId : IExternalId { /// - public string Name => "IMDb"; + public string ProviderName => "IMDb"; /// public string Key => MetadataProviders.Imdb.ToString(); @@ -38,7 +38,7 @@ namespace MediaBrowser.Providers.Movies public class ImdbPersonExternalId : IExternalId { /// - public string Name => "IMDb"; + public string ProviderName => "IMDb"; /// public string Key => MetadataProviders.Imdb.ToString(); diff --git a/MediaBrowser.Providers/Music/MusicExternalIds.cs b/MediaBrowser.Providers/Music/MusicExternalIds.cs index 12323bcbd..4490d0f05 100644 --- a/MediaBrowser.Providers/Music/MusicExternalIds.cs +++ b/MediaBrowser.Providers/Music/MusicExternalIds.cs @@ -8,7 +8,7 @@ namespace MediaBrowser.Providers.Music public class ImvdbId : IExternalId { /// - public string Name => "IMVDb"; + public string ProviderName => "IMVDb"; /// public string Key => "IMVDb"; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs b/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs index 126a79a08..e299eb3ee 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs @@ -8,7 +8,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public class AudioDbAlbumExternalId : IExternalId { /// - public string Name => "TheAudioDb"; + public string ProviderName => "TheAudioDb"; /// public string Key => MetadataProviders.AudioDbAlbum.ToString(); @@ -26,7 +26,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public class AudioDbOtherAlbumExternalId : IExternalId { /// - public string Name => "TheAudioDb"; + public string ProviderName => "TheAudioDb"; /// public string Key => MetadataProviders.AudioDbAlbum.ToString(); @@ -44,7 +44,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public class AudioDbArtistExternalId : IExternalId { /// - public string Name => "TheAudioDb"; + public string ProviderName => "TheAudioDb"; /// public string Key => MetadataProviders.AudioDbArtist.ToString(); @@ -62,7 +62,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public class AudioDbOtherArtistExternalId : IExternalId { /// - public string Name => "TheAudioDb"; + public string ProviderName => "TheAudioDb"; /// public string Key => MetadataProviders.AudioDbArtist.ToString(); diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs index 5756cb838..247e87fd5 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.Providers.Music public class MusicBrainzReleaseGroupExternalId : IExternalId { /// - public string Name => "MusicBrainz"; + public string ProviderName => "MusicBrainz"; /// public string Key => MetadataProviders.MusicBrainzReleaseGroup.ToString(); @@ -27,7 +27,7 @@ namespace MediaBrowser.Providers.Music public class MusicBrainzAlbumArtistExternalId : IExternalId { /// - public string Name => "MusicBrainz"; + public string ProviderName => "MusicBrainz"; /// public string Key => MetadataProviders.MusicBrainzAlbumArtist.ToString(); @@ -45,7 +45,7 @@ namespace MediaBrowser.Providers.Music public class MusicBrainzAlbumExternalId : IExternalId { /// - public string Name => "MusicBrainz"; + public string ProviderName => "MusicBrainz"; /// public string Key => MetadataProviders.MusicBrainzAlbum.ToString(); @@ -63,7 +63,7 @@ namespace MediaBrowser.Providers.Music public class MusicBrainzArtistExternalId : IExternalId { /// - public string Name => "MusicBrainz"; + public string ProviderName => "MusicBrainz"; /// public string Key => MetadataProviders.MusicBrainzArtist.ToString(); @@ -81,7 +81,7 @@ namespace MediaBrowser.Providers.Music public class MusicBrainzOtherArtistExternalId : IExternalId { /// - public string Name => "MusicBrainz"; + public string ProviderName => "MusicBrainz"; /// @@ -100,7 +100,7 @@ namespace MediaBrowser.Providers.Music public class MusicBrainzTrackId : IExternalId { /// - public string Name => "MusicBrainz"; + public string ProviderName => "MusicBrainz"; /// public string Key => MetadataProviders.MusicBrainzTrack.ToString(); diff --git a/MediaBrowser.Providers/TV/TvExternalIds.cs b/MediaBrowser.Providers/TV/TvExternalIds.cs index 4c005666f..12ad3d8a2 100644 --- a/MediaBrowser.Providers/TV/TvExternalIds.cs +++ b/MediaBrowser.Providers/TV/TvExternalIds.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.Providers.TV public class Zap2ItExternalId : IExternalId { /// - public string Name => "Zap2It"; + public string ProviderName => "Zap2It"; /// public string Key => MetadataProviders.Zap2It.ToString(); @@ -27,7 +27,7 @@ namespace MediaBrowser.Providers.TV public class TvdbExternalId : IExternalId { /// - public string Name => "TheTVDB"; + public string ProviderName => "TheTVDB"; /// public string Key => MetadataProviders.Tvdb.ToString(); @@ -46,7 +46,7 @@ namespace MediaBrowser.Providers.TV public class TvdbSeasonExternalId : IExternalId { /// - public string Name => "TheTVDB"; + public string ProviderName => "TheTVDB"; /// public string Key => MetadataProviders.Tvdb.ToString(); @@ -64,7 +64,7 @@ namespace MediaBrowser.Providers.TV public class TvdbEpisodeExternalId : IExternalId { /// - public string Name => "TheTVDB"; + public string ProviderName => "TheTVDB"; /// public string Key => MetadataProviders.Tvdb.ToString(); diff --git a/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetExternalId.cs b/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetExternalId.cs index 6e131029c..1d3c80536 100644 --- a/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetExternalId.cs +++ b/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetExternalId.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.Providers.Tmdb.BoxSets public class TmdbBoxSetExternalId : IExternalId { /// - public string Name => TmdbUtils.ProviderName; + public string ProviderName => TmdbUtils.ProviderName; /// public string Key => MetadataProviders.TmdbCollection.ToString(); diff --git a/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieExternalId.cs b/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieExternalId.cs index cffd33542..75e71dda4 100644 --- a/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieExternalId.cs +++ b/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieExternalId.cs @@ -10,7 +10,7 @@ namespace MediaBrowser.Providers.Tmdb.Movies public class TmdbMovieExternalId : IExternalId { /// - public string Name => TmdbUtils.ProviderName; + public string ProviderName => TmdbUtils.ProviderName; /// public string Key => MetadataProviders.Tmdb.ToString(); diff --git a/MediaBrowser.Providers/Tmdb/People/TmdbPersonExternalId.cs b/MediaBrowser.Providers/Tmdb/People/TmdbPersonExternalId.cs index 460740254..a8685d669 100644 --- a/MediaBrowser.Providers/Tmdb/People/TmdbPersonExternalId.cs +++ b/MediaBrowser.Providers/Tmdb/People/TmdbPersonExternalId.cs @@ -8,7 +8,7 @@ namespace MediaBrowser.Providers.Tmdb.People public class TmdbPersonExternalId : IExternalId { /// - public string Name => TmdbUtils.ProviderName; + public string ProviderName => TmdbUtils.ProviderName; /// public string Key => MetadataProviders.Tmdb.ToString(); diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesExternalId.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesExternalId.cs index 8adcc4d46..fd6dd9b41 100644 --- a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesExternalId.cs +++ b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesExternalId.cs @@ -8,7 +8,7 @@ namespace MediaBrowser.Providers.Tmdb.TV public class TmdbSeriesExternalId : IExternalId { /// - public string Name => TmdbUtils.ProviderName; + public string ProviderName => TmdbUtils.ProviderName; /// public string Key => MetadataProviders.Tmdb.ToString(); -- cgit v1.2.3 From 634bc73c9a641646b633fbc560a288207f5eac4b Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sun, 17 May 2020 18:07:37 -0400 Subject: DO not use developer exception page when exception stack trace should be ignored --- .../HttpServer/HttpListenerHost.cs | 29 +++++++++++----------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 794d55c04..718078ae1 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -210,16 +210,8 @@ namespace Emby.Server.Implementations.HttpServer } } - private async Task ErrorHandler(Exception ex, IRequest httpReq, int statusCode, string urlToLog) + private async Task ErrorHandler(Exception ex, IRequest httpReq, int statusCode, string urlToLog, bool ignoreStackTrace) { - bool ignoreStackTrace = - ex is SocketException - || ex is IOException - || ex is OperationCanceledException - || ex is SecurityException - || ex is AuthenticationException - || ex is FileNotFoundException; - if (ignoreStackTrace) { _logger.LogError("Error processing request: {Message}. URL: {Url}", ex.Message.TrimEnd('.'), urlToLog); @@ -504,15 +496,24 @@ namespace Emby.Server.Implementations.HttpServer { var requestInnerEx = GetActualException(requestEx); var statusCode = GetStatusCode(requestInnerEx); - - // Do not handle 500 server exceptions manually when in development mode - // The framework-defined development exception page will be returned instead - if (statusCode == 500 && _hostEnvironment.IsDevelopment()) + bool ignoreStackTrace = + requestInnerEx is SocketException + || requestInnerEx is IOException + || requestInnerEx is OperationCanceledException + || requestInnerEx is SecurityException + || requestInnerEx is AuthenticationException + || requestInnerEx is FileNotFoundException; + + // Do not handle 500 server exceptions manually when in development mode. + // Instead, re-throw the exception so it can be handled by the DeveloperExceptionPageMiddleware. + // However, do not use the DeveloperExceptionPageMiddleware when the stack trace should be ignored, + // because it will log the stack trace when it handles the exception. + if (statusCode == 500 && !ignoreStackTrace && _hostEnvironment.IsDevelopment() ) { throw; } - await ErrorHandler(requestInnerEx, httpReq, statusCode, urlToLog).ConfigureAwait(false); + await ErrorHandler(requestInnerEx, httpReq, statusCode, urlToLog, ignoreStackTrace).ConfigureAwait(false); } catch (Exception handlerException) { -- cgit v1.2.3 From 37f55b5c217db5343ab196094f67dc84e71d4ef0 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 17 May 2020 19:56:02 -0600 Subject: apply doc suggestions --- Jellyfin.Api/Controllers/StartupController.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index 66e4774aa..ed1dc1ede 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -34,7 +34,7 @@ namespace Jellyfin.Api.Controllers /// Completes the startup wizard. /// /// Startup wizard completed. - /// Status. + /// An indicating success. [HttpPost("Complete")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult CompleteWizard() @@ -49,7 +49,7 @@ namespace Jellyfin.Api.Controllers /// Gets the initial startup wizard configuration. /// /// Initial startup wizard configuration retrieved. - /// The initial startup wizard configuration. + /// An containing the initial startup wizard configuration. [HttpGet("Configuration")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetStartupConfiguration() @@ -71,7 +71,7 @@ namespace Jellyfin.Api.Controllers /// The metadata country code. /// The preferred language for metadata. /// Configuration saved. - /// Status. + /// An indicating success. [HttpPost("Configuration")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult UpdateInitialConfiguration( @@ -92,7 +92,7 @@ namespace Jellyfin.Api.Controllers /// Enable remote access. /// Enable UPnP. /// Configuration saved. - /// Status. + /// An indicating success. [HttpPost("RemoteAccess")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult SetRemoteAccess([FromForm] bool enableRemoteAccess, [FromForm] bool enableAutomaticPortMapping) @@ -121,7 +121,10 @@ namespace Jellyfin.Api.Controllers /// /// The DTO containing username and password. /// Updated user name and password. - /// The async task. + /// + /// A that represents the asynchronous update operation. + /// The task result contains an indicating success. + /// [HttpPost("User")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task UpdateUser([FromForm] StartupUserDto startupUserDto) -- cgit v1.2.3 From 989ddbcafdfcbe32bdf16a30ebec9554d6ca548a Mon Sep 17 00:00:00 2001 From: erikasne6152 Date: Mon, 18 May 2020 11:31:19 +0000 Subject: Translated using Weblate (Lithuanian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/lt/ --- .../Localization/Core/lt-LT.json | 24 +++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json index 01a740187..35053766b 100644 --- a/Emby.Server.Implementations/Localization/Core/lt-LT.json +++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json @@ -92,5 +92,27 @@ "UserStoppedPlayingItemWithValues": "{0} baigė leisti {1} į {2}", "ValueHasBeenAddedToLibrary": "{0} pridėtas į mediateką", "ValueSpecialEpisodeName": "Ypatinga - {0}", - "VersionNumber": "Version {0}" + "VersionNumber": "Version {0}", + "TaskUpdatePluginsDescription": "Atsisiųsti ir įdiegti atnaujinimus priedams kuriem yra nustatytas automatiškas atnaujinimas.", + "TaskUpdatePlugins": "Atnaujinti Priedus", + "TaskDownloadMissingSubtitlesDescription": "Ieško internete trūkstamų subtitrų remiantis metaduomenų konfigūracija.", + "TaskCleanTranscodeDescription": "Ištrina dienos senumo perkodavimo failus.", + "TaskCleanTranscode": "Išvalyti Perkodavimo Direktorija", + "TaskRefreshLibraryDescription": "Ieškoti naujų failų jūsų mediatekoje ir atnaujina metaduomenis.", + "TaskRefreshLibrary": "Skenuoti Mediateka", + "TaskDownloadMissingSubtitles": "Atsisiųsti trūkstamus subtitrus", + "TaskRefreshChannelsDescription": "Atnaujina internetinių kanalų informacija.", + "TaskRefreshChannels": "Atnaujinti Kanalus", + "TaskRefreshPeopleDescription": "Atnaujina metaduomenis apie aktorius ir režisierius jūsų mediatekoje.", + "TaskRefreshPeople": "Atnaujinti Žmones", + "TaskCleanLogsDescription": "Ištrina žurnalo failus kurie yra senesni nei {0} dienos.", + "TaskCleanLogs": "Išvalyti Žurnalą", + "TaskRefreshChapterImagesDescription": "Sukuria miniatiūras vaizdo įrašam, kurie turi scenas.", + "TaskRefreshChapterImages": "Ištraukti Scenų Paveikslus", + "TaskCleanCache": "Išvalyti Talpyklą", + "TaskCleanCacheDescription": "Ištrina talpyklos failus, kurių daugiau nereikia sistemai.", + "TasksChannelsCategory": "Internetiniai Kanalai", + "TasksApplicationCategory": "Programa", + "TasksLibraryCategory": "Mediateka", + "TasksMaintenanceCategory": "Priežiūra" } -- cgit v1.2.3 From c70e38288c26be6a69ab5fdd334bca9bc30ab5ef Mon Sep 17 00:00:00 2001 From: Vasily Date: Mon, 18 May 2020 17:01:29 +0300 Subject: Apply suggestions from code review Co-authored-by: dkanada --- Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index e41ced28b..efec58fab 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -121,15 +121,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts await taskCompletionSource.Task.ConfigureAwait(false); if (taskCompletionSource.Task.Exception != null) { - // Error happened during opening the stream, re-raise the exception to inform the caller + // Error happened while opening the stream so raise the exception again to inform the caller throw taskCompletionSource.Task.Exception; } + 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)); } } @@ -162,6 +161,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts Logger.LogError(ex, "Error copying live stream {0} to {1}.", GetType().Name, TempFilePath); openTaskCompletionSource.TrySetException(ex); } + openTaskCompletionSource.TrySetResult(false); EnableStreamSharing = false; -- cgit v1.2.3 From 5eec3a13429d5fae6a944531d77602d3c198d023 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Mon, 18 May 2020 10:47:01 -0400 Subject: Remove extra whitespace Co-authored-by: dkanada --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 718078ae1..043812290 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -508,7 +508,7 @@ namespace Emby.Server.Implementations.HttpServer // Instead, re-throw the exception so it can be handled by the DeveloperExceptionPageMiddleware. // However, do not use the DeveloperExceptionPageMiddleware when the stack trace should be ignored, // because it will log the stack trace when it handles the exception. - if (statusCode == 500 && !ignoreStackTrace && _hostEnvironment.IsDevelopment() ) + if (statusCode == 500 && !ignoreStackTrace && _hostEnvironment.IsDevelopment()) { throw; } -- cgit v1.2.3 From 85f04af04c7d33477df5486ae80b6fa9a2a2bfd7 Mon Sep 17 00:00:00 2001 From: ConfusedPolarBear <33811686+ConfusedPolarBear@users.noreply.github.com> Date: Mon, 18 May 2020 14:30:23 -0500 Subject: Reuse existing CORS function --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 9554b9f46..9a4e858a5 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -497,9 +497,9 @@ namespace Emby.Server.Implementations.HttpServer var requestInnerEx = GetActualException(requestEx); var statusCode = GetStatusCode(requestInnerEx); - if (!httpRes.Headers.ContainsKey("Access-Control-Allow-Origin")) + foreach (var (key, value) in GetDefaultCorsHeaders(httpReq)) { - httpRes.Headers.Add("Access-Control-Allow-Origin", "*"); + httpRes.Headers.Add(key, value); } bool ignoreStackTrace = -- cgit v1.2.3 From b9fc0d26287e46017515e4ac3e569ca2c60f622f Mon Sep 17 00:00:00 2001 From: Jesús Higueras Date: Mon, 23 Mar 2020 20:05:49 +0100 Subject: Add BlurHash support to backend --- Emby.Drawing/ImageProcessor.cs | 4 ++ Emby.Drawing/NullImageEncoder.cs | 6 +++ .../Data/SqliteItemRepository.cs | 17 +++++++- Emby.Server.Implementations/Dto/DtoService.cs | 7 ++++ .../Library/LibraryManager.cs | 30 +++++++++++++- Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj | 2 + Jellyfin.Drawing.Skia/SkiaEncoder.cs | 48 +++++++++++++++++++++- MediaBrowser.Api/Images/ImageService.cs | 7 +++- MediaBrowser.Controller/Drawing/IImageEncoder.cs | 7 ++++ MediaBrowser.Controller/Drawing/IImageProcessor.cs | 7 ++++ MediaBrowser.Controller/Entities/BaseItem.cs | 1 + MediaBrowser.Controller/Entities/ItemImageInfo.cs | 6 +++ MediaBrowser.Model/Dto/BaseItemDto.cs | 6 +++ MediaBrowser.Model/Dto/ImageInfo.cs | 6 +++ 14 files changed, 149 insertions(+), 5 deletions(-) diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 0b3bbe29e..1237b603b 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -313,6 +313,10 @@ namespace Emby.Drawing public ImageDimensions GetImageDimensions(string path) => _imageEncoder.GetImageSize(path); + /// + public string GetImageHash(string path) + => _imageEncoder.GetImageHash(path); + /// public string GetImageCacheTag(BaseItem item, ItemImageInfo image) => (item.Path + image.DateModified.Ticks).GetMD5().ToString("N", CultureInfo.InvariantCulture); diff --git a/Emby.Drawing/NullImageEncoder.cs b/Emby.Drawing/NullImageEncoder.cs index 5af7f1622..fa89b4c63 100644 --- a/Emby.Drawing/NullImageEncoder.cs +++ b/Emby.Drawing/NullImageEncoder.cs @@ -42,5 +42,11 @@ namespace Emby.Drawing { throw new NotImplementedException(); } + + /// + public string GetImageHash(string inputPath) + { + throw new NotImplementedException(); + } } } diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index ca5cd6fdd..5a43a138b 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1144,12 +1144,18 @@ namespace Emby.Server.Implementations.Data var delimeter = "*"; var path = image.Path; + var hash = image.Hash; if (path == null) { path = string.Empty; } + if (hash == null) + { + hash = string.Empty; + } + return GetPathToSave(path) + delimeter + image.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) + @@ -1158,7 +1164,11 @@ namespace Emby.Server.Implementations.Data delimeter + image.Width.ToString(CultureInfo.InvariantCulture) + delimeter + - image.Height.ToString(CultureInfo.InvariantCulture); + image.Height.ToString(CultureInfo.InvariantCulture) + + delimeter + + // Replace delimiters with other characters. + // This can be removed when we migrate to a proper DB. + hash.Replace('*', '/').Replace('|', '\\'); } public ItemImageInfo ItemImageInfoFromValueString(string value) @@ -1192,6 +1202,11 @@ namespace Emby.Server.Implementations.Data image.Width = width; image.Height = height; } + + if (parts.Length >= 6) + { + image.Hash = parts[5].Replace('/', '*').Replace('\\', '|'); + } } return image; diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index c4b65d265..a34a3a192 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -718,6 +718,7 @@ namespace Emby.Server.Implementations.Dto if (options.EnableImages) { dto.ImageTags = new Dictionary(); + dto.ImageHashes = new Dictionary(); // Prevent implicitly captured closure var currentItem = item; @@ -732,6 +733,12 @@ namespace Emby.Server.Implementations.Dto { dto.ImageTags[image.Type] = tag; } + + var hash = image.Hash; + if (hash != null && hash.Length > 0) + { + dto.ImageHashes[tag] = image.Hash; + } } } } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 0b86b2db7..bc35b0410 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -21,6 +21,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -35,6 +36,7 @@ using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; @@ -109,6 +111,18 @@ namespace Emby.Server.Implementations.Library /// The comparers. private IBaseItemComparer[] Comparers { get; set; } + /// + /// Gets or sets the active item repository + /// + /// The item repository. + public IItemRepository ItemRepository { get; set; } + + /// + /// Gets or sets the active image processor + /// + /// The image processor. + public IImageProcessor ImageProcessor { get; set; } + /// /// Occurs when [item added]. /// @@ -1817,7 +1831,19 @@ namespace Emby.Server.Implementations.Library public void UpdateImages(BaseItem item) { - _itemRepository.SaveImages(item); + item.ImageInfos + .Where(i => (i.Width == 0 || i.Height == 0)) + .ToList() + .ForEach(x => + { + string blurhash = ImageProcessor.GetImageHash(x.Path); + ImageDimensions size = ImageProcessor.GetImageDimensions(item, x, true); + x.Width = size.Width; + x.Height = size.Height; + x.Hash = blurhash; + }); + + ItemRepository.SaveImages(item); RegisterItem(item); } @@ -1839,7 +1865,7 @@ namespace Emby.Server.Implementations.Library item.DateLastSaved = DateTime.UtcNow; - RegisterItem(item); + UpdateImages(item); } _itemRepository.SaveItems(itemsList, cancellationToken); diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj index a6e1f490a..9f0e3a004 100644 --- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj +++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj @@ -21,6 +21,8 @@ + + diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 5c7462ee2..1d7307863 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -1,7 +1,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Blurhash.Core; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Extensions; @@ -15,7 +19,7 @@ namespace Jellyfin.Drawing.Skia /// /// Image encoder that uses to manipulate images. /// - public class SkiaEncoder : IImageEncoder + public class SkiaEncoder : CoreEncoder, IImageEncoder { private static readonly HashSet _transparentImageTypes = new HashSet(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" }; @@ -229,6 +233,48 @@ namespace Jellyfin.Drawing.Skia } } + /// + /// The path is null. + /// The path is not valid. + /// The file at the specified path could not be used to generate a codec. + [SuppressMessage("Microsoft.Performance", "CA1814:PreferJaggedArraysOverMultidimensional")] + public string GetImageHash(string path) + { + if (path == null) + { + throw new ArgumentNullException(nameof(path)); + } + + if (!File.Exists(path)) + { + throw new FileNotFoundException("File not found", path); + } + + using (var bitmap = GetBitmap(path, false, false, null)) + { + if (bitmap == null) + { + throw new ArgumentOutOfRangeException($"Skia unable to read image {path}"); + } + + var width = bitmap.Width; + var height = bitmap.Height; + var pixels = new Pixel[width, height]; + Parallel.ForEach(Enumerable.Range(0, height), y => + { + for (var x = 0; x < width; x++) + { + var color = bitmap.GetPixel(x, y); + pixels[x, y].Red = MathUtils.SRgbToLinear(color.Red); + pixels[x, y].Green = MathUtils.SRgbToLinear(color.Green); + pixels[x, y].Blue = MathUtils.SRgbToLinear(color.Blue); + } + }); + + return CoreEncode(pixels, 4, 4); + } + } + private static bool HasDiacritics(string text) => !string.Equals(text, text.RemoveDiacritics(), StringComparison.Ordinal); diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 2e9b3e6cb..ecfe2ed76 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -323,6 +323,7 @@ namespace MediaBrowser.Api.Images { int? width = null; int? height = null; + string? blurhash = null; long length = 0; try @@ -332,7 +333,10 @@ namespace MediaBrowser.Api.Images var fileInfo = _fileSystem.GetFileInfo(info.Path); length = fileInfo.Length; - ImageDimensions size = _imageProcessor.GetImageDimensions(item, info); + blurhash = _imageProcessor.GetImageHash(info.Path); + info.Hash = blurhash; // TODO: this doesn't seem like the right thing to do + + ImageDimensions size = _imageProcessor.GetImageDimensions(item, info, true); _libraryManager.UpdateImages(item); width = size.Width; height = size.Height; @@ -358,6 +362,7 @@ namespace MediaBrowser.Api.Images ImageType = info.Type, ImageTag = _imageProcessor.GetImageCacheTag(item, info), Size = length, + Hash = blurhash, Width = width, Height = height }; diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs index 88e67b648..1d3f0d3b4 100644 --- a/MediaBrowser.Controller/Drawing/IImageEncoder.cs +++ b/MediaBrowser.Controller/Drawing/IImageEncoder.cs @@ -43,6 +43,13 @@ namespace MediaBrowser.Controller.Drawing /// The image dimensions. ImageDimensions GetImageSize(string path); + /// + /// Get the blurhash of an image. + /// + /// The filepath of the image. + /// The blurhash. + string GetImageHash(string path); + /// /// Encode an image. /// diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index 36c746624..be5906cbc 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -32,6 +32,13 @@ namespace MediaBrowser.Controller.Drawing /// ImageDimensions ImageDimensions GetImageDimensions(string path); + /// + /// Gets the blurhash of the image. + /// + /// Path to the image file. + /// BlurHash + String GetImageHash(string path); + /// /// Gets the dimensions of the image. /// diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 7ed8fa767..e5f6ea09d 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -2222,6 +2222,7 @@ namespace MediaBrowser.Controller.Entities existingImage.DateModified = image.DateModified; existingImage.Width = image.Width; existingImage.Height = image.Height; + existingImage.Hash = image.Hash; } else { diff --git a/MediaBrowser.Controller/Entities/ItemImageInfo.cs b/MediaBrowser.Controller/Entities/ItemImageInfo.cs index fc46dec2e..ba0297107 100644 --- a/MediaBrowser.Controller/Entities/ItemImageInfo.cs +++ b/MediaBrowser.Controller/Entities/ItemImageInfo.cs @@ -28,6 +28,12 @@ namespace MediaBrowser.Controller.Entities public int Height { get; set; } + /// + /// Gets or sets the blurhash. + /// + /// The blurhash. + public string Hash { get; set; } + [JsonIgnore] public bool IsLocalFile => Path == null || !Path.StartsWith("http", StringComparison.OrdinalIgnoreCase); } diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 607355d8d..8c6c9683a 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -510,6 +510,12 @@ namespace MediaBrowser.Model.Dto /// The series thumb image tag. public string SeriesThumbImageTag { get; set; } + /// + /// Gets or sets the blurhash for the image tags. + /// + /// The blurhashes. + public Dictionary ImageHashes { get; set; } + /// /// Gets or sets the series studio. /// diff --git a/MediaBrowser.Model/Dto/ImageInfo.cs b/MediaBrowser.Model/Dto/ImageInfo.cs index 57942ac23..39bdc09ed 100644 --- a/MediaBrowser.Model/Dto/ImageInfo.cs +++ b/MediaBrowser.Model/Dto/ImageInfo.cs @@ -30,6 +30,12 @@ namespace MediaBrowser.Model.Dto /// The path. public string Path { get; set; } + /// + /// Gets or sets the blurhash. + /// + /// The blurhash. + public string Hash { get; set; } + /// /// Gets or sets the height. /// -- cgit v1.2.3 From fe480caf5486a21d7ef152009e5c3e08364e0f33 Mon Sep 17 00:00:00 2001 From: Jesús Higueras Date: Wed, 25 Mar 2020 17:26:53 +0100 Subject: Add endpoint to update all items in library --- .../Library/LibraryManager.cs | 28 ++++++++++++++++++++++ MediaBrowser.Api/ItemUpdateService.cs | 6 +++++ MediaBrowser.Controller/Library/ILibraryManager.cs | 1 + 3 files changed, 35 insertions(+) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index bc35b0410..0a97e007c 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1911,6 +1911,34 @@ namespace Emby.Server.Implementations.Library UpdateItems(new[] { item }, parent, updateReason, cancellationToken); } + /// + /// Updates everything in the database. + /// + public void UpdateAll() + { + Task.Run(() => + { + var items = ItemRepository.GetItemList(new InternalItemsQuery { + Recursive = true + }); + foreach (var item in items) + { + _logger.LogDebug("Updating item {Name} ({ItemId})", + item.Name, + item.Id); + try + { + item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); + } + catch (Exception ex) + { + _logger.LogError(ex, "Updating item {ItemId} failed", item.Id); + } + } + _logger.LogDebug("All items have been updated"); + }); + } + /// /// Reports the item removed. /// diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index 2db6d717a..808627b41 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -198,6 +198,12 @@ namespace MediaBrowser.Api public void Post(UpdateItem request) { + if (request.ItemId == "*") + { + // Special case: Refresh everything in database. Probably not a great idea to run often. + _libraryManager.UpdateAll(); + return; + } var item = _libraryManager.GetItemById(request.ItemId); var newLockData = request.LockData ?? false; diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 2e1c97f67..559a5415f 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -196,6 +196,7 @@ namespace MediaBrowser.Controller.Library /// void UpdateItems(IEnumerable items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken); void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken); + void UpdateAll(); /// /// Retrieves the item. -- cgit v1.2.3 From 02da312f8aaf9975f31291fd65687f637e38530c Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 19 May 2020 00:22:52 +0300 Subject: Fix compilation after rebase --- Emby.Server.Implementations/Library/LibraryManager.cs | 2 +- MediaBrowser.Api/Images/ImageService.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 0a97e007c..c31fd5bf9 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1837,7 +1837,7 @@ namespace Emby.Server.Implementations.Library .ForEach(x => { string blurhash = ImageProcessor.GetImageHash(x.Path); - ImageDimensions size = ImageProcessor.GetImageDimensions(item, x, true); + ImageDimensions size = ImageProcessor.GetImageDimensions(item, x); x.Width = size.Width; x.Height = size.Height; x.Hash = blurhash; diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index ecfe2ed76..09b99781b 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -336,7 +336,7 @@ namespace MediaBrowser.Api.Images blurhash = _imageProcessor.GetImageHash(info.Path); info.Hash = blurhash; // TODO: this doesn't seem like the right thing to do - ImageDimensions size = _imageProcessor.GetImageDimensions(item, info, true); + ImageDimensions size = _imageProcessor.GetImageDimensions(item, info); _libraryManager.UpdateImages(item); width = size.Width; height = size.Height; -- cgit v1.2.3 From 949e4d3e64a73102d0a87c8b34918397a2cec303 Mon Sep 17 00:00:00 2001 From: ConfusedPolarBear <33811686+ConfusedPolarBear@users.noreply.github.com> Date: Mon, 18 May 2020 16:54:36 -0500 Subject: Apply suggestions from code review --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 9a4e858a5..7de4f168c 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -499,7 +499,10 @@ namespace Emby.Server.Implementations.HttpServer foreach (var (key, value) in GetDefaultCorsHeaders(httpReq)) { - httpRes.Headers.Add(key, value); + if (!httpRes.Headers.ContainsKey(key)) + { + httpRes.Headers.Add(key, value); + } } bool ignoreStackTrace = -- cgit v1.2.3 From 4395644efcf3ae25fd657a5cbdd7db0a0fffa9df Mon Sep 17 00:00:00 2001 From: Lukáš Kucharczyk Date: Mon, 18 May 2020 19:27:56 +0000 Subject: Translated using Weblate (Czech) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/cs/ --- Emby.Server.Implementations/Localization/Core/cs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json index 992bb9df3..464ca28ca 100644 --- a/Emby.Server.Implementations/Localization/Core/cs.json +++ b/Emby.Server.Implementations/Localization/Core/cs.json @@ -23,7 +23,7 @@ "HeaderFavoriteEpisodes": "Oblíbené epizody", "HeaderFavoriteShows": "Oblíbené seriály", "HeaderFavoriteSongs": "Oblíbená hudba", - "HeaderLiveTV": "Živá TV", + "HeaderLiveTV": "Televize", "HeaderNextUp": "Nadcházející", "HeaderRecordingGroups": "Skupiny nahrávek", "HomeVideos": "Domáci videa", -- cgit v1.2.3 From 585e9e6220c50bac5c224ac1ee3a90ce625ef3c6 Mon Sep 17 00:00:00 2001 From: NaorManna Date: Tue, 19 May 2020 06:38:03 +0000 Subject: Translated using Weblate (Hebrew) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/he/ --- Emby.Server.Implementations/Localization/Core/he.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json index 4e54b9f7a..682f5325b 100644 --- a/Emby.Server.Implementations/Localization/Core/he.json +++ b/Emby.Server.Implementations/Localization/Core/he.json @@ -107,5 +107,12 @@ "TaskCleanLogs": "נקה תיקיית יומן", "TaskRefreshLibraryDescription": "סורק את ספריית המדיה שלך אחר קבצים חדשים ומרענן מטא נתונים.", "TaskRefreshChapterImagesDescription": "יוצר תמונות ממוזערות לסרטונים שיש להם פרקים.", - "TasksChannelsCategory": "ערוצי אינטרנט" + "TasksChannelsCategory": "ערוצי אינטרנט", + "TaskDownloadMissingSubtitlesDescription": "חפש באינטרנט עבור הכתוביות החסרות בהתבסס על המטה-דיאטה.", + "TaskDownloadMissingSubtitles": "הורד כתוביות חסרות.", + "TaskRefreshChannelsDescription": "רענן פרטי ערוץ אינטרנטי.", + "TaskRefreshChannels": "רענן ערוץ", + "TaskCleanTranscodeDescription": "מחק קבצי transcode שנוצרו מלפני יותר מיום.", + "TaskCleanTranscode": "נקה תקיית Transcode", + "TaskUpdatePluginsDescription": "הורד והתקן עדכונים עבור תוספים שמוגדרים לעדכון אוטומטי." } -- cgit v1.2.3 From bfb644d5f50fff9fd8ba5fe4504c73ec5be851af Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 19 May 2020 13:45:39 +0300 Subject: Fix nullref exception --- .vscode/tasks.json | 13 ++++++++++++- Emby.Server.Implementations/Library/LibraryManager.cs | 10 ++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ac517e10c..7475617c9 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -10,6 +10,17 @@ "${workspaceFolder}/Jellyfin.Server/Jellyfin.Server.csproj" ], "problemMatcher": "$msCompile" + }, + { + "label": "api tests", + "command": "dotnet", + "type": "process", + "args": [ + "test", + "${workspaceFolder}/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj" + ], + "problemMatcher": "$msCompile" } + ] -} \ No newline at end of file +} diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index c31fd5bf9..e1cb282cc 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -111,12 +111,6 @@ namespace Emby.Server.Implementations.Library /// The comparers. private IBaseItemComparer[] Comparers { get; set; } - /// - /// Gets or sets the active item repository - /// - /// The item repository. - public IItemRepository ItemRepository { get; set; } - /// /// Gets or sets the active image processor /// @@ -1843,7 +1837,7 @@ namespace Emby.Server.Implementations.Library x.Hash = blurhash; }); - ItemRepository.SaveImages(item); + _itemRepository.SaveImages(item); RegisterItem(item); } @@ -1918,7 +1912,7 @@ namespace Emby.Server.Implementations.Library { Task.Run(() => { - var items = ItemRepository.GetItemList(new InternalItemsQuery { + var items = _itemRepository.GetItemList(new InternalItemsQuery { Recursive = true }); foreach (var item in items) -- cgit v1.2.3 From f18293bf762c86581153ab8d9b1b6267421178a9 Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 19 May 2020 13:46:00 +0300 Subject: Switch to BlurHashSharp lib which should be faster --- Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj | 4 ++-- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 28 +++------------------- 2 files changed, 5 insertions(+), 27 deletions(-) diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj index 9f0e3a004..221346b68 100644 --- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj +++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj @@ -18,11 +18,11 @@ + + - - diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 1d7307863..d2da0cf17 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -5,7 +5,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; -using Blurhash.Core; +using BlurHashSharp.SkiaSharp; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Extensions; @@ -19,7 +19,7 @@ namespace Jellyfin.Drawing.Skia /// /// Image encoder that uses to manipulate images. /// - public class SkiaEncoder : CoreEncoder, IImageEncoder + public class SkiaEncoder : IImageEncoder { private static readonly HashSet _transparentImageTypes = new HashSet(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" }; @@ -250,29 +250,7 @@ namespace Jellyfin.Drawing.Skia throw new FileNotFoundException("File not found", path); } - using (var bitmap = GetBitmap(path, false, false, null)) - { - if (bitmap == null) - { - throw new ArgumentOutOfRangeException($"Skia unable to read image {path}"); - } - - var width = bitmap.Width; - var height = bitmap.Height; - var pixels = new Pixel[width, height]; - Parallel.ForEach(Enumerable.Range(0, height), y => - { - for (var x = 0; x < width; x++) - { - var color = bitmap.GetPixel(x, y); - pixels[x, y].Red = MathUtils.SRgbToLinear(color.Red); - pixels[x, y].Green = MathUtils.SRgbToLinear(color.Green); - pixels[x, y].Blue = MathUtils.SRgbToLinear(color.Blue); - } - }); - - return CoreEncode(pixels, 4, 4); - } + return BlurHashSharp.SkiaSharp.BlurHashEncoder.Encode(4, 4, path); } private static bool HasDiacritics(string text) -- cgit v1.2.3 From a226a4ee03d974615a6fa26b936a93458a255b70 Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 19 May 2020 14:50:14 +0300 Subject: Compute hash only when one is not computed in DB, small optimizations here and there --- .../Data/SqliteItemRepository.cs | 16 ++---- .../Library/LibraryManager.cs | 59 ++++++++-------------- MediaBrowser.Api/Images/ImageService.cs | 22 ++++---- MediaBrowser.Api/ItemUpdateService.cs | 6 --- MediaBrowser.Controller/Library/ILibraryManager.cs | 2 +- 5 files changed, 37 insertions(+), 68 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 5a43a138b..10eb96b10 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1141,20 +1141,10 @@ namespace Emby.Server.Implementations.Data public string ToValueString(ItemImageInfo image) { - var delimeter = "*"; + const string delimeter = "*"; - var path = image.Path; - var hash = image.Hash; - - if (path == null) - { - path = string.Empty; - } - - if (hash == null) - { - hash = string.Empty; - } + var path = image.Path ?? string.Empty; + var hash = image.Hash ?? string.Empty; return GetPathToSave(path) + delimeter + diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index e1cb282cc..c48664a31 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1825,17 +1825,26 @@ namespace Emby.Server.Implementations.Library public void UpdateImages(BaseItem item) { - item.ImageInfos - .Where(i => (i.Width == 0 || i.Height == 0)) - .ToList() - .ForEach(x => - { - string blurhash = ImageProcessor.GetImageHash(x.Path); - ImageDimensions size = ImageProcessor.GetImageDimensions(item, x); - x.Width = size.Width; - x.Height = size.Height; - x.Hash = blurhash; - }); + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + var outdated = item.ImageInfos + .Where(i => (i.Width == 0 || i.Height == 0 || string.IsNullOrEmpty(i.Hash))) + .ToList(); + if (outdated.Count == 0) + { + return; + } + + outdated.ForEach(img => + { + ImageDimensions size = ImageProcessor.GetImageDimensions(item, img); + img.Width = size.Width; + img.Height = size.Height; + img.Hash = ImageProcessor.GetImageHash(img.Path); + }); _itemRepository.SaveImages(item); @@ -1905,34 +1914,6 @@ namespace Emby.Server.Implementations.Library UpdateItems(new[] { item }, parent, updateReason, cancellationToken); } - /// - /// Updates everything in the database. - /// - public void UpdateAll() - { - Task.Run(() => - { - var items = _itemRepository.GetItemList(new InternalItemsQuery { - Recursive = true - }); - foreach (var item in items) - { - _logger.LogDebug("Updating item {Name} ({ItemId})", - item.Name, - item.Id); - try - { - item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); - } - catch (Exception ex) - { - _logger.LogError(ex, "Updating item {ItemId} failed", item.Id); - } - } - _logger.LogDebug("All items have been updated"); - }); - } - /// /// Reports the item removed. /// diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 09b99781b..559db550b 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Extensions; @@ -280,9 +281,16 @@ namespace MediaBrowser.Api.Images public List GetItemImageInfos(BaseItem item) { var list = new List(); - var itemImages = item.ImageInfos; + if (itemImages.Length == 0) + { + // short-circuit + return list; + } + + _libraryManager.UpdateImages(item); // this makes sure dimensions and hashes are correct + foreach (var image in itemImages) { if (!item.AllowsMultipleImages(image.Type)) @@ -323,7 +331,7 @@ namespace MediaBrowser.Api.Images { int? width = null; int? height = null; - string? blurhash = null; + string blurhash = null; long length = 0; try @@ -333,13 +341,9 @@ namespace MediaBrowser.Api.Images var fileInfo = _fileSystem.GetFileInfo(info.Path); length = fileInfo.Length; - blurhash = _imageProcessor.GetImageHash(info.Path); - info.Hash = blurhash; // TODO: this doesn't seem like the right thing to do - - ImageDimensions size = _imageProcessor.GetImageDimensions(item, info); - _libraryManager.UpdateImages(item); - width = size.Width; - height = size.Height; + blurhash = info.Hash; + width = info.Width; + height = info.Height; if (width <= 0 || height <= 0) { diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index 808627b41..2db6d717a 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -198,12 +198,6 @@ namespace MediaBrowser.Api public void Post(UpdateItem request) { - if (request.ItemId == "*") - { - // Special case: Refresh everything in database. Probably not a great idea to run often. - _libraryManager.UpdateAll(); - return; - } var item = _libraryManager.GetItemById(request.ItemId); var newLockData = request.LockData ?? false; diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 559a5415f..81160efec 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -195,8 +195,8 @@ namespace MediaBrowser.Controller.Library /// Updates the item. /// void UpdateItems(IEnumerable items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken); + void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken); - void UpdateAll(); /// /// Retrieves the item. -- cgit v1.2.3 From 186b7f303cd6f95ca64e020c2838dfe2028ea54c Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 19 May 2020 14:56:52 +0300 Subject: More small optimizations --- Emby.Server.Implementations/Dto/DtoService.cs | 5 ++--- Emby.Server.Implementations/Library/LibraryManager.cs | 3 ++- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 6 +----- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index a34a3a192..07105786b 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -722,8 +722,7 @@ namespace Emby.Server.Implementations.Dto // Prevent implicitly captured closure var currentItem = item; - foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type)) - .ToList()) + foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type))) { if (options.GetImageLimit(image.Type) > 0) { @@ -735,7 +734,7 @@ namespace Emby.Server.Implementations.Dto } var hash = image.Hash; - if (hash != null && hash.Length > 0) + if (!string.IsNullOrEmpty(hash)) { dto.ImageHashes[tag] = image.Hash; } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index c48664a31..9f412b725 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1831,10 +1831,11 @@ namespace Emby.Server.Implementations.Library } var outdated = item.ImageInfos - .Where(i => (i.Width == 0 || i.Height == 0 || string.IsNullOrEmpty(i.Hash))) + .Where(i => (i.IsLocalFile && (i.Width == 0 || i.Height == 0 || string.IsNullOrEmpty(i.Hash)))) .ToList(); if (outdated.Count == 0) { + RegisterItem(item); return; } diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index d2da0cf17..99091ea57 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; -using System.Linq; -using System.Threading.Tasks; using BlurHashSharp.SkiaSharp; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; @@ -237,7 +234,6 @@ namespace Jellyfin.Drawing.Skia /// The path is null. /// The path is not valid. /// The file at the specified path could not be used to generate a codec. - [SuppressMessage("Microsoft.Performance", "CA1814:PreferJaggedArraysOverMultidimensional")] public string GetImageHash(string path) { if (path == null) @@ -250,7 +246,7 @@ namespace Jellyfin.Drawing.Skia throw new FileNotFoundException("File not found", path); } - return BlurHashSharp.SkiaSharp.BlurHashEncoder.Encode(4, 4, path); + return BlurHashEncoder.Encode(4, 4, path); } private static bool HasDiacritics(string text) -- cgit v1.2.3 From 2b1ae7ac5834054866aa485b18f85f1793f7b8b4 Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 19 May 2020 15:42:50 +0300 Subject: Fix code smells --- Emby.Drawing/NullImageEncoder.cs | 2 +- MediaBrowser.Controller/Drawing/IImageProcessor.cs | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Emby.Drawing/NullImageEncoder.cs b/Emby.Drawing/NullImageEncoder.cs index fa89b4c63..54de7212a 100644 --- a/Emby.Drawing/NullImageEncoder.cs +++ b/Emby.Drawing/NullImageEncoder.cs @@ -44,7 +44,7 @@ namespace Emby.Drawing } /// - public string GetImageHash(string inputPath) + public string GetImageHash(string path) { throw new NotImplementedException(); } diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index be5906cbc..e38eaf760 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -32,13 +32,6 @@ namespace MediaBrowser.Controller.Drawing /// ImageDimensions ImageDimensions GetImageDimensions(string path); - /// - /// Gets the blurhash of the image. - /// - /// Path to the image file. - /// BlurHash - String GetImageHash(string path); - /// /// Gets the dimensions of the image. /// @@ -47,6 +40,13 @@ namespace MediaBrowser.Controller.Drawing /// ImageDimensions ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info); + /// + /// Gets the blurhash of the image. + /// + /// Path to the image file. + /// BlurHash + String GetImageHash(string path); + /// /// Gets the image cache tag. /// @@ -54,6 +54,7 @@ namespace MediaBrowser.Controller.Drawing /// The image. /// Guid. string GetImageCacheTag(BaseItem item, ItemImageInfo image); + string GetImageCacheTag(BaseItem item, ChapterInfo info); /// -- cgit v1.2.3 From c4f8ba55f2b3424be4a6ff1044d13327fe36b687 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 19 May 2020 08:28:02 -0600 Subject: Rename to AttachmentsController -> VideoAttachmentsController --- Jellyfin.Api/Controllers/AttachmentsController.cs | 85 ---------------------- .../Controllers/VideoAttachmentsController.cs | 85 ++++++++++++++++++++++ 2 files changed, 85 insertions(+), 85 deletions(-) delete mode 100644 Jellyfin.Api/Controllers/AttachmentsController.cs create mode 100644 Jellyfin.Api/Controllers/VideoAttachmentsController.cs diff --git a/Jellyfin.Api/Controllers/AttachmentsController.cs b/Jellyfin.Api/Controllers/AttachmentsController.cs deleted file mode 100644 index 30fb951cf..000000000 --- a/Jellyfin.Api/Controllers/AttachmentsController.cs +++ /dev/null @@ -1,85 +0,0 @@ -#nullable enable - -using System; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; - -namespace Jellyfin.Api.Controllers -{ - /// - /// Attachments controller. - /// - [Route("Videos")] - [Authorize] - public class AttachmentsController : Controller - { - private readonly ILibraryManager _libraryManager; - private readonly IAttachmentExtractor _attachmentExtractor; - - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - /// Instance of the interface. - public AttachmentsController( - ILibraryManager libraryManager, - IAttachmentExtractor attachmentExtractor) - { - _libraryManager = libraryManager; - _attachmentExtractor = attachmentExtractor; - } - - /// - /// Get video attachment. - /// - /// Video ID. - /// Media Source ID. - /// Attachment Index. - /// Attachment retrieved. - /// Video or attachment not found. - /// An containing the attachment stream on success, or a if the attachment could not be found. - [HttpGet("{VideoID}/{MediaSourceID}/Attachments/{Index}")] - [Produces("application/octet-stream")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> GetAttachment( - [FromRoute] Guid videoId, - [FromRoute] string mediaSourceId, - [FromRoute] int index) - { - try - { - var item = _libraryManager.GetItemById(videoId); - if (item == null) - { - return NotFound(); - } - - var (attachment, stream) = await _attachmentExtractor.GetAttachment( - item, - mediaSourceId, - index, - CancellationToken.None) - .ConfigureAwait(false); - - var contentType = "application/octet-stream"; - if (string.IsNullOrWhiteSpace(attachment.MimeType)) - { - contentType = attachment.MimeType; - } - - return new FileStreamResult(stream, contentType); - } - catch (ResourceNotFoundException e) - { - return StatusCode(StatusCodes.Status404NotFound, e.Message); - } - } - } -} diff --git a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs new file mode 100644 index 000000000..69e847373 --- /dev/null +++ b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs @@ -0,0 +1,85 @@ +#nullable enable + +using System; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers +{ + /// + /// Attachments controller. + /// + [Route("Videos")] + [Authorize] + public class VideoAttachmentsController : Controller + { + private readonly ILibraryManager _libraryManager; + private readonly IAttachmentExtractor _attachmentExtractor; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + public VideoAttachmentsController( + ILibraryManager libraryManager, + IAttachmentExtractor attachmentExtractor) + { + _libraryManager = libraryManager; + _attachmentExtractor = attachmentExtractor; + } + + /// + /// Get video attachment. + /// + /// Video ID. + /// Media Source ID. + /// Attachment Index. + /// Attachment retrieved. + /// Video or attachment not found. + /// An containing the attachment stream on success, or a if the attachment could not be found. + [HttpGet("{VideoID}/{MediaSourceID}/Attachments/{Index}")] + [Produces("application/octet-stream")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> GetAttachment( + [FromRoute] Guid videoId, + [FromRoute] string mediaSourceId, + [FromRoute] int index) + { + try + { + var item = _libraryManager.GetItemById(videoId); + if (item == null) + { + return NotFound(); + } + + var (attachment, stream) = await _attachmentExtractor.GetAttachment( + item, + mediaSourceId, + index, + CancellationToken.None) + .ConfigureAwait(false); + + var contentType = "application/octet-stream"; + if (string.IsNullOrWhiteSpace(attachment.MimeType)) + { + contentType = attachment.MimeType; + } + + return new FileStreamResult(stream, contentType); + } + catch (ResourceNotFoundException e) + { + return StatusCode(StatusCodes.Status404NotFound, e.Message); + } + } + } +} -- cgit v1.2.3 From 45d750f10657da8f7914999098ecffcdbfedbd2d Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 17:36:05 -0600 Subject: Move AttachmentsService to AttachmentsController --- Jellyfin.Api/Controllers/AttachmentsController.cs | 86 +++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 Jellyfin.Api/Controllers/AttachmentsController.cs diff --git a/Jellyfin.Api/Controllers/AttachmentsController.cs b/Jellyfin.Api/Controllers/AttachmentsController.cs new file mode 100644 index 000000000..5d48a79b9 --- /dev/null +++ b/Jellyfin.Api/Controllers/AttachmentsController.cs @@ -0,0 +1,86 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Controller.Net; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers +{ + /// + /// Attachments controller. + /// + [Route("Videos")] + [Authenticated] + public class AttachmentsController : Controller + { + private readonly ILibraryManager _libraryManager; + private readonly IAttachmentExtractor _attachmentExtractor; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + public AttachmentsController( + ILibraryManager libraryManager, + IAttachmentExtractor attachmentExtractor) + { + _libraryManager = libraryManager; + _attachmentExtractor = attachmentExtractor; + } + + /// + /// Get video attachment. + /// + /// Video ID. + /// Media Source ID. + /// Attachment Index. + /// Attachment. + [HttpGet("{VideoID}/{MediaSourceID}/Attachments/{Index}")] + [Produces("application/octet-stream")] + [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public async Task GetAttachment( + [FromRoute] Guid videoId, + [FromRoute] string mediaSourceId, + [FromRoute] int index) + { + try + { + var item = _libraryManager.GetItemById(videoId); + if (item == null) + { + return NotFound(); + } + + var (attachment, stream) = await _attachmentExtractor.GetAttachment( + item, + mediaSourceId, + index, + CancellationToken.None) + .ConfigureAwait(false); + + var contentType = "application/octet-stream"; + if (string.IsNullOrWhiteSpace(attachment.MimeType)) + { + contentType = attachment.MimeType; + } + + return new FileStreamResult(stream, contentType); + } + catch (ResourceNotFoundException e) + { + return StatusCode(StatusCodes.Status404NotFound, e.Message); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + } +} -- cgit v1.2.3 From 8eac528815bc7ab673b361f48b90b3b28ccbc070 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 19 Apr 2020 17:37:15 -0600 Subject: nullable --- Jellyfin.Api/Controllers/AttachmentsController.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Jellyfin.Api/Controllers/AttachmentsController.cs b/Jellyfin.Api/Controllers/AttachmentsController.cs index 5d48a79b9..f4c1a761f 100644 --- a/Jellyfin.Api/Controllers/AttachmentsController.cs +++ b/Jellyfin.Api/Controllers/AttachmentsController.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; using System.Threading; using System.Threading.Tasks; -- cgit v1.2.3 From 84fcb4926ccce968a920bed7324ef6f037c4f5e1 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 07:52:33 -0600 Subject: Remove exception handler --- Jellyfin.Api/Controllers/AttachmentsController.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Jellyfin.Api/Controllers/AttachmentsController.cs b/Jellyfin.Api/Controllers/AttachmentsController.cs index f4c1a761f..aeeaf5cbd 100644 --- a/Jellyfin.Api/Controllers/AttachmentsController.cs +++ b/Jellyfin.Api/Controllers/AttachmentsController.cs @@ -79,10 +79,6 @@ namespace Jellyfin.Api.Controllers { return StatusCode(StatusCodes.Status404NotFound, e.Message); } - catch (Exception e) - { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); - } } } } -- cgit v1.2.3 From 15e9fbb923b8aa91692cd9c8c68ec7dde638c1e2 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 13:57:11 -0600 Subject: move to ActionResult --- Jellyfin.Api/Controllers/AttachmentsController.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Api/Controllers/AttachmentsController.cs b/Jellyfin.Api/Controllers/AttachmentsController.cs index aeeaf5cbd..351401de1 100644 --- a/Jellyfin.Api/Controllers/AttachmentsController.cs +++ b/Jellyfin.Api/Controllers/AttachmentsController.cs @@ -44,10 +44,9 @@ namespace Jellyfin.Api.Controllers /// Attachment. [HttpGet("{VideoID}/{MediaSourceID}/Attachments/{Index}")] [Produces("application/octet-stream")] - [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public async Task GetAttachment( + public async Task> GetAttachment( [FromRoute] Guid videoId, [FromRoute] string mediaSourceId, [FromRoute] int index) -- cgit v1.2.3 From 177339e8d5f3ad9eea6a3d6cd068e58d637e443d Mon Sep 17 00:00:00 2001 From: crobibero Date: Thu, 23 Apr 2020 10:04:37 -0600 Subject: Fix Authorize attributes --- Jellyfin.Api/Controllers/AttachmentsController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/AttachmentsController.cs b/Jellyfin.Api/Controllers/AttachmentsController.cs index 351401de1..b0cdfb86e 100644 --- a/Jellyfin.Api/Controllers/AttachmentsController.cs +++ b/Jellyfin.Api/Controllers/AttachmentsController.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -16,7 +16,7 @@ namespace Jellyfin.Api.Controllers /// Attachments controller. /// [Route("Videos")] - [Authenticated] + [Authorize] public class AttachmentsController : Controller { private readonly ILibraryManager _libraryManager; -- cgit v1.2.3 From 26a2bea179b8c2d8b772b714e6296c03b5c1e0d3 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 2 May 2020 17:12:56 -0600 Subject: Update endpoint docs --- Jellyfin.Api/Controllers/AttachmentsController.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/AttachmentsController.cs b/Jellyfin.Api/Controllers/AttachmentsController.cs index b0cdfb86e..30fb951cf 100644 --- a/Jellyfin.Api/Controllers/AttachmentsController.cs +++ b/Jellyfin.Api/Controllers/AttachmentsController.cs @@ -41,7 +41,9 @@ namespace Jellyfin.Api.Controllers /// Video ID. /// Media Source ID. /// Attachment Index. - /// Attachment. + /// Attachment retrieved. + /// Video or attachment not found. + /// An containing the attachment stream on success, or a if the attachment could not be found. [HttpGet("{VideoID}/{MediaSourceID}/Attachments/{Index}")] [Produces("application/octet-stream")] [ProducesResponseType(StatusCodes.Status200OK)] -- cgit v1.2.3 From a7a725173da0be952e0a7407f9f42f1ea1123f84 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 19 May 2020 08:28:02 -0600 Subject: Rename to AttachmentsController -> VideoAttachmentsController --- Jellyfin.Api/Controllers/AttachmentsController.cs | 85 ----------------------- 1 file changed, 85 deletions(-) delete mode 100644 Jellyfin.Api/Controllers/AttachmentsController.cs diff --git a/Jellyfin.Api/Controllers/AttachmentsController.cs b/Jellyfin.Api/Controllers/AttachmentsController.cs deleted file mode 100644 index 30fb951cf..000000000 --- a/Jellyfin.Api/Controllers/AttachmentsController.cs +++ /dev/null @@ -1,85 +0,0 @@ -#nullable enable - -using System; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; - -namespace Jellyfin.Api.Controllers -{ - /// - /// Attachments controller. - /// - [Route("Videos")] - [Authorize] - public class AttachmentsController : Controller - { - private readonly ILibraryManager _libraryManager; - private readonly IAttachmentExtractor _attachmentExtractor; - - /// - /// Initializes a new instance of the class. - /// - /// Instance of the interface. - /// Instance of the interface. - public AttachmentsController( - ILibraryManager libraryManager, - IAttachmentExtractor attachmentExtractor) - { - _libraryManager = libraryManager; - _attachmentExtractor = attachmentExtractor; - } - - /// - /// Get video attachment. - /// - /// Video ID. - /// Media Source ID. - /// Attachment Index. - /// Attachment retrieved. - /// Video or attachment not found. - /// An containing the attachment stream on success, or a if the attachment could not be found. - [HttpGet("{VideoID}/{MediaSourceID}/Attachments/{Index}")] - [Produces("application/octet-stream")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> GetAttachment( - [FromRoute] Guid videoId, - [FromRoute] string mediaSourceId, - [FromRoute] int index) - { - try - { - var item = _libraryManager.GetItemById(videoId); - if (item == null) - { - return NotFound(); - } - - var (attachment, stream) = await _attachmentExtractor.GetAttachment( - item, - mediaSourceId, - index, - CancellationToken.None) - .ConfigureAwait(false); - - var contentType = "application/octet-stream"; - if (string.IsNullOrWhiteSpace(attachment.MimeType)) - { - contentType = attachment.MimeType; - } - - return new FileStreamResult(stream, contentType); - } - catch (ResourceNotFoundException e) - { - return StatusCode(StatusCodes.Status404NotFound, e.Message); - } - } - } -} -- cgit v1.2.3 From 1c471d58551043dab3c808952d9834163cac3078 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 19 May 2020 09:01:00 -0600 Subject: Clean UpdateDisplayPreferences endpoint --- Jellyfin.Api/Controllers/DisplayPreferencesController.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs index 2837ea8e8..579b5df5d 100644 --- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs +++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs @@ -74,14 +74,9 @@ namespace Jellyfin.Api.Controllers [FromQuery, BindRequired] string client, [FromBody, BindRequired] DisplayPreferences displayPreferences) { - if (!ModelState.IsValid) - { - return BadRequest(ModelState); - } - if (displayPreferencesId == null) { - // do nothing. + // TODO - refactor so parameter doesn't exist or is actually used. } _displayPreferencesRepository.SaveDisplayPreferences( -- cgit v1.2.3 From c998935d29d04a55babdeb0adcf1d1091611b1e3 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 19 May 2020 09:06:37 -0600 Subject: Apply review suggestions --- Jellyfin.Api/Controllers/ScheduledTasksController.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs index ad70bf83b..3e3359ec7 100644 --- a/Jellyfin.Api/Controllers/ScheduledTasksController.cs +++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs @@ -3,8 +3,9 @@ using System; using System.Collections.Generic; using System.Linq; -using MediaBrowser.Controller.Net; +using Jellyfin.Api.Constants; using MediaBrowser.Model.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; @@ -14,7 +15,7 @@ namespace Jellyfin.Api.Controllers /// /// Scheduled Tasks Controller. /// - // [Authenticated] + [Authorize(Policy = Policies.RequiresElevation)] public class ScheduledTasksController : BaseJellyfinApiController { private readonly ITaskManager _taskManager; @@ -82,8 +83,7 @@ namespace Jellyfin.Api.Controllers return NotFound(); } - var result = ScheduledTaskHelpers.GetTaskInfo(task); - return Ok(result); + return ScheduledTaskHelpers.GetTaskInfo(task); } /// -- cgit v1.2.3 From 98bd61e36443452a280dc9d3543baecc10b561ed Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 19 May 2020 09:14:37 -0600 Subject: Clean up routes --- Jellyfin.Api/Controllers/Images/ImageByNameController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Api/Controllers/Images/ImageByNameController.cs b/Jellyfin.Api/Controllers/Images/ImageByNameController.cs index 67ebaa4e0..62fcb5a2a 100644 --- a/Jellyfin.Api/Controllers/Images/ImageByNameController.cs +++ b/Jellyfin.Api/Controllers/Images/ImageByNameController.cs @@ -48,7 +48,7 @@ namespace Jellyfin.Api.Controllers.Images [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetGeneralImages() { - return Ok(GetImageList(_applicationPaths.GeneralPath, false)); + return GetImageList(_applicationPaths.GeneralPath, false); } /// @@ -91,7 +91,7 @@ namespace Jellyfin.Api.Controllers.Images [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetRatingImages() { - return Ok(GetImageList(_applicationPaths.RatingsPath, false)); + return GetImageList(_applicationPaths.RatingsPath, false); } /// @@ -122,7 +122,7 @@ namespace Jellyfin.Api.Controllers.Images [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetMediaInfoImages() { - return Ok(GetImageList(_applicationPaths.MediaInfoImagesPath, false)); + return GetImageList(_applicationPaths.MediaInfoImagesPath, false); } /// -- cgit v1.2.3 From 2923013c6ed0c5c4e7325893be0822d8fcd9de47 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 19 May 2020 09:23:28 -0600 Subject: Clean Remote Image Controller. --- .../Controllers/Images/RemoteImageController.cs | 38 +++++++++++++--------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/Jellyfin.Api/Controllers/Images/RemoteImageController.cs b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs index a0754ed4e..665db561b 100644 --- a/Jellyfin.Api/Controllers/Images/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; @@ -9,12 +10,12 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using MediaBrowser.Model.Providers; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; @@ -25,7 +26,7 @@ namespace Jellyfin.Api.Controllers.Images /// Remote Images Controller. /// [Route("Images")] - [Authenticated] + [Authorize] public class RemoteImageController : BaseJellyfinApiController { private readonly IProviderManager _providerManager; @@ -60,7 +61,9 @@ namespace Jellyfin.Api.Controllers.Images /// Optional. The record index to start at. All items with a lower index will be dropped from the results. /// Optional. The maximum number of records to return. /// Optional. The image provider to use. - /// Optinal. Include all languages. + /// Optional. Include all languages. + /// Remote Images returned. + /// Item not found. /// Remote Image Result. [HttpGet("{Id}/RemoteImages")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -116,18 +119,20 @@ namespace Jellyfin.Api.Controllers.Images } result.Images = imageArray; - return Ok(result); + return result; } /// /// Gets available remote image providers for an item. /// /// Item Id. - /// List of providers. + /// Returned remote image providers. + /// Item not found. + /// List of remote image providers. [HttpGet("{Id}/RemoteImages/Providers")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult GetRemoteImageProviders([FromRoute] string id) + public ActionResult> GetRemoteImageProviders([FromRoute] string id) { var item = _libraryManager.GetItemById(id); if (item == null) @@ -135,14 +140,15 @@ namespace Jellyfin.Api.Controllers.Images return NotFound(); } - var providers = _providerManager.GetRemoteImageProviderInfo(item); - return Ok(providers); + return Ok(_providerManager.GetRemoteImageProviderInfo(item)); } /// /// Gets a remote image. /// /// The image url. + /// Remote image returned. + /// Remote image not found. /// Image Stream. [HttpGet("Remote")] [Produces("application/octet-stream")] @@ -154,7 +160,7 @@ namespace Jellyfin.Api.Controllers.Images var pointerCachePath = GetFullCachePath(urlHash.ToString()); string? contentPath = null; - bool hasFile = false; + var hasFile = false; try { @@ -166,11 +172,11 @@ namespace Jellyfin.Api.Controllers.Images } catch (FileNotFoundException) { - // Means the file isn't cached yet + // The file isn't cached yet } catch (IOException) { - // Means the file isn't cached yet + // The file isn't cached yet } if (!hasFile) @@ -194,7 +200,9 @@ namespace Jellyfin.Api.Controllers.Images /// Item Id. /// The image type. /// The image url. - /// Status. + /// Remote image downloaded. + /// Remote image not found. + /// Download status. [HttpPost("{Id}/RemoteImages/Download")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -245,10 +253,10 @@ namespace Jellyfin.Api.Controllers.Images var fullCachePath = GetFullCachePath(urlHash + "." + ext); Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath)); - using (var stream = result.Content) + await using (var stream = result.Content) { - using var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); - await stream.CopyToAsync(filestream).ConfigureAwait(false); + await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); + await stream.CopyToAsync(fileStream).ConfigureAwait(false); } Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath)); -- cgit v1.2.3 From 839de72f9a320bfe09cdd9c2fcab6806f3106916 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 19 May 2020 09:24:04 -0600 Subject: Fix authentication attribute --- Jellyfin.Api/Controllers/Images/ImageByNameController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/Images/ImageByNameController.cs b/Jellyfin.Api/Controllers/Images/ImageByNameController.cs index 62fcb5a2a..dadb34438 100644 --- a/Jellyfin.Api/Controllers/Images/ImageByNameController.cs +++ b/Jellyfin.Api/Controllers/Images/ImageByNameController.cs @@ -7,10 +7,10 @@ using System.Linq; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dto; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -20,7 +20,7 @@ namespace Jellyfin.Api.Controllers.Images /// Images By Name Controller. /// [Route("Images")] - [Authenticated] + [Authorize] public class ImageByNameController : BaseJellyfinApiController { private readonly IServerApplicationPaths _applicationPaths; -- cgit v1.2.3 From 6dbbfcbfbef28e8866aa0144170e1edfff1a2bcb Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 19 May 2020 09:29:59 -0600 Subject: update xml docs --- Jellyfin.Api/Controllers/ConfigurationController.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs index b508ac054..992cb0087 100644 --- a/Jellyfin.Api/Controllers/ConfigurationController.cs +++ b/Jellyfin.Api/Controllers/ConfigurationController.cs @@ -44,6 +44,7 @@ namespace Jellyfin.Api.Controllers /// /// Gets application configuration. /// + /// Application configuration returned. /// Application configuration. [HttpGet("Configuration")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -56,7 +57,8 @@ namespace Jellyfin.Api.Controllers /// Updates application configuration. /// /// Configuration. - /// Status. + /// Configuration updated. + /// Update status. [HttpPost("Configuration")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status200OK)] @@ -70,6 +72,7 @@ namespace Jellyfin.Api.Controllers /// Gets a named configuration. /// /// Configuration key. + /// Configuration returned. /// Configuration. [HttpGet("Configuration/{Key}")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -82,7 +85,8 @@ namespace Jellyfin.Api.Controllers /// Updates named configuration. /// /// Configuration key. - /// Status. + /// Named configuration updated. + /// Update status. [HttpPost("Configuration/{Key}")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status200OK)] @@ -103,7 +107,8 @@ namespace Jellyfin.Api.Controllers /// /// Gets a default MetadataOptions object. /// - /// MetadataOptions. + /// Metadata options returned. + /// Default MetadataOptions. [HttpGet("Configuration/MetadataOptions/Default")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status200OK)] @@ -116,6 +121,7 @@ namespace Jellyfin.Api.Controllers /// Updates the path to the media encoder. /// /// Media encoder path form body. + /// Media encoder path updated. /// Status. [HttpPost("MediaEncoder/Path")] [Authorize(Policy = Policies.FirstTimeSetupOrElevated)] -- cgit v1.2.3 From 6c376f18f7f094d41a0d3e5387ce83e5b0b66c4a Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 19 May 2020 09:47:02 -0600 Subject: update xml docs --- Jellyfin.Api/Controllers/ChannelsController.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs index 4e2621b7b..733f1e6d8 100644 --- a/Jellyfin.Api/Controllers/ChannelsController.cs +++ b/Jellyfin.Api/Controllers/ChannelsController.cs @@ -13,6 +13,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Model.Channels; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -21,6 +22,7 @@ namespace Jellyfin.Api.Controllers /// /// Channels Controller. /// + [Authorize] public class ChannelsController : BaseJellyfinApiController { private readonly IChannelManager _channelManager; @@ -46,6 +48,7 @@ namespace Jellyfin.Api.Controllers /// Optional. Filter by channels that support getting latest items. /// Optional. Filter by channels that support media deletion. /// Optional. Filter by channels that are favorite. + /// Channels returned. /// Channels. [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] @@ -71,6 +74,7 @@ namespace Jellyfin.Api.Controllers /// /// Get all channel features. /// + /// All channel features returned. /// Channel features. [HttpGet("Features")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -83,6 +87,7 @@ namespace Jellyfin.Api.Controllers /// Get channel features. /// /// Channel id. + /// Channel features returned. /// Channel features. [HttpGet("{Id}/Features")] public ActionResult GetChannelFeatures([FromRoute] string id) @@ -102,6 +107,7 @@ namespace Jellyfin.Api.Controllers /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. /// Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime. /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. + /// Channel items returned. /// Channel items. [HttpGet("{Id}/Items")] public async Task>> GetChannelItems( @@ -175,6 +181,7 @@ namespace Jellyfin.Api.Controllers /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. /// Optional. Specify one or more channel id's, comma delimited. + /// Latest channel items returned. /// Latest channel items. public async Task>> GetLatestChannelItems( [FromQuery] Guid? userId, -- cgit v1.2.3 From e03c97d7cdfad65a48bc0aff6ca0e45f9b3ec3cd Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 19 May 2020 10:02:52 -0600 Subject: update xml docs --- Jellyfin.Api/Controllers/EnvironmentController.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/EnvironmentController.cs b/Jellyfin.Api/Controllers/EnvironmentController.cs index 139c1af08..78c206ba1 100644 --- a/Jellyfin.Api/Controllers/EnvironmentController.cs +++ b/Jellyfin.Api/Controllers/EnvironmentController.cs @@ -40,7 +40,8 @@ namespace Jellyfin.Api.Controllers /// The path. /// An optional filter to include or exclude files from the results. true/false. /// An optional filter to include or exclude folders from the results. true/false. - /// File system entries. + /// Directory contents returned. + /// Directory contents. [HttpGet("DirectoryContents")] [ProducesResponseType(StatusCodes.Status200OK)] public IEnumerable GetDirectoryContents( @@ -79,7 +80,9 @@ namespace Jellyfin.Api.Controllers /// Validates path. /// /// Validate request object. - /// Status. + /// Path validated. + /// Path not found. + /// Validation status. [HttpPost("ValidatePath")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -132,6 +135,7 @@ namespace Jellyfin.Api.Controllers /// /// Gets network paths. /// + /// Empty array returned. /// List of entries. [Obsolete("This endpoint is obsolete.")] [HttpGet("NetworkShares")] @@ -144,6 +148,7 @@ namespace Jellyfin.Api.Controllers /// /// Gets available drives from the server's file system. /// + /// List of entries returned. /// List of entries. [HttpGet("Drives")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -189,6 +194,7 @@ namespace Jellyfin.Api.Controllers /// /// Get Default directory browser. /// + /// Default directory browser returned. /// Default directory browser. [HttpGet("DefaultDirectoryBrowser")] [ProducesResponseType(StatusCodes.Status200OK)] -- cgit v1.2.3 From a11a1934399b8cbce0487ced49d2f8e7065b436a Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 19 May 2020 10:04:09 -0600 Subject: Remove CameraUpload endpoints --- Jellyfin.Api/Controllers/DevicesController.cs | 83 --------------------------- 1 file changed, 83 deletions(-) diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs index 02cf1bc44..64dc2322d 100644 --- a/Jellyfin.Api/Controllers/DevicesController.cs +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -2,9 +2,6 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; @@ -155,85 +152,5 @@ namespace Jellyfin.Api.Controllers return Ok(); } - - /// - /// Gets camera upload history for a device. - /// - /// Device Id. - /// Device upload history retrieved. - /// Device not found. - /// An containing the device upload history on success, or a if the device could not be found. - [HttpGet("CameraUploads")] - [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult GetCameraUploads([FromQuery, BindRequired] string id) - { - var existingDevice = _deviceManager.GetDevice(id); - if (existingDevice == null) - { - return NotFound(); - } - - var uploadHistory = _deviceManager.GetCameraUploadHistory(id); - return uploadHistory; - } - - /// - /// Uploads content. - /// - /// Device Id. - /// Album. - /// Name. - /// Id. - /// Contents uploaded. - /// No uploaded contents. - /// Device not found. - /// - /// An on success, - /// or a if the device could not be found - /// or a if the upload contains no files. - /// - [HttpPost("CameraUploads")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task PostCameraUploadAsync( - [FromQuery, BindRequired] string deviceId, - [FromQuery, BindRequired] string album, - [FromQuery, BindRequired] string name, - [FromQuery, BindRequired] string id) - { - var existingDevice = _deviceManager.GetDevice(id); - if (existingDevice == null) - { - return NotFound(); - } - - Stream fileStream; - string contentType; - - if (Request.HasFormContentType) - { - if (Request.Form.Files.Any()) - { - fileStream = Request.Form.Files[0].OpenReadStream(); - contentType = Request.Form.Files[0].ContentType; - } - else - { - return BadRequest(); - } - } - else - { - fileStream = Request.Body; - contentType = Request.ContentType; - } - - await _deviceManager.AcceptCameraUpload( - deviceId, - fileStream, - new LocalFileInfo { MimeType = contentType, Album = album, Name = name, Id = id }).ConfigureAwait(false); - - return Ok(); - } } } -- cgit v1.2.3 From cf78edc979b626ff11ff88889f618cba50c5ee5f Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 19 May 2020 10:05:23 -0600 Subject: Fix Authorize attributes --- Jellyfin.Api/Controllers/DevicesController.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs index 64dc2322d..b22b5f985 100644 --- a/Jellyfin.Api/Controllers/DevicesController.cs +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -2,11 +2,13 @@ using System; using System.Collections.Generic; +using Jellyfin.Api.Constants; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Devices; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; @@ -16,7 +18,7 @@ namespace Jellyfin.Api.Controllers /// /// Devices Controller. /// - [Authenticated] + [Authorize] public class DevicesController : BaseJellyfinApiController { private readonly IDeviceManager _deviceManager; @@ -47,7 +49,7 @@ namespace Jellyfin.Api.Controllers /// Devices retrieved. /// An containing the list of devices. [HttpGet] - [Authenticated(Roles = "Admin")] + [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId) { @@ -64,7 +66,7 @@ namespace Jellyfin.Api.Controllers /// Device not found. /// An containing the device info on success, or a if the device could not be found. [HttpGet("Info")] - [Authenticated(Roles = "Admin")] + [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GetDeviceInfo([FromQuery, BindRequired] string id) @@ -86,7 +88,7 @@ namespace Jellyfin.Api.Controllers /// Device not found. /// An containing the device info on success, or a if the device could not be found. [HttpGet("Options")] - [Authenticated(Roles = "Admin")] + [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GetDeviceOptions([FromQuery, BindRequired] string id) @@ -109,7 +111,7 @@ namespace Jellyfin.Api.Controllers /// Device not found. /// An on success, or a if the device could not be found. [HttpPost("Options")] - [Authenticated(Roles = "Admin")] + [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult UpdateDeviceOptions( -- cgit v1.2.3 From cdb25e355c6ebf9ba09f44a7bb7e35286e50976e Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 19 May 2020 10:06:25 -0600 Subject: Fix return value --- Jellyfin.Api/Controllers/DevicesController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs index b22b5f985..a46d3f937 100644 --- a/Jellyfin.Api/Controllers/DevicesController.cs +++ b/Jellyfin.Api/Controllers/DevicesController.cs @@ -8,6 +8,7 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Devices; +using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -51,11 +52,10 @@ namespace Jellyfin.Api.Controllers [HttpGet] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId) + public ActionResult> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId) { var deviceQuery = new DeviceQuery { SupportsSync = supportsSync, UserId = userId ?? Guid.Empty }; - var devices = _deviceManager.GetDevices(deviceQuery); - return Ok(devices); + return _deviceManager.GetDevices(deviceQuery); } /// -- cgit v1.2.3 From 24543b04c110b7cdf275c314ac61065dc36b25e8 Mon Sep 17 00:00:00 2001 From: Bruce Date: Tue, 19 May 2020 18:13:42 +0100 Subject: Applying review suggestion to documentation --- Jellyfin.Api/Controllers/PackageController.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs index b5ee47ee4..f37319c19 100644 --- a/Jellyfin.Api/Controllers/PackageController.cs +++ b/Jellyfin.Api/Controllers/PackageController.cs @@ -1,4 +1,5 @@ #nullable enable + using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -32,10 +33,10 @@ namespace Jellyfin.Api.Controllers } /// - /// Gets a package by name or assembly guid. + /// Gets a package by name or assembly GUID. /// /// The name of the package. - /// The guid of the associated assembly. + /// The GUID of the associated assembly. /// A containing package information. [HttpGet("/{Name}")] [ProducesResponseType(typeof(PackageInfo), StatusCodes.Status200OK)] @@ -69,7 +70,7 @@ namespace Jellyfin.Api.Controllers /// Installs a package. /// /// Package name. - /// Guid of the associated assembly. + /// GUID of the associated assembly. /// Optional version. Defaults to latest version. /// Package found. /// Package not found. -- cgit v1.2.3 From 2f2bceb1104d8ea669ca21fc40200247aca956ed Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 19 May 2020 12:56:57 -0600 Subject: Remove default parameter values --- Jellyfin.Api/Controllers/ScheduledTasksController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs index 3e3359ec7..19cce974e 100644 --- a/Jellyfin.Api/Controllers/ScheduledTasksController.cs +++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs @@ -39,8 +39,8 @@ namespace Jellyfin.Api.Controllers [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] public IEnumerable GetTasks( - [FromQuery] bool? isHidden = false, - [FromQuery] bool? isEnabled = false) + [FromQuery] bool? isHidden, + [FromQuery] bool? isEnabled) { IEnumerable tasks = _taskManager.ScheduledTasks.OrderBy(o => o.Name); -- cgit v1.2.3 From b28dd47a0fc5b18111678acede335474f9007b8f Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 19 May 2020 12:58:09 -0600 Subject: implement review suggestions --- Jellyfin.Api/Controllers/VideoAttachmentsController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs index 69e847373..a10dd4059 100644 --- a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs +++ b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs @@ -78,7 +78,7 @@ namespace Jellyfin.Api.Controllers } catch (ResourceNotFoundException e) { - return StatusCode(StatusCodes.Status404NotFound, e.Message); + return NotFound(e.Message); } } } -- cgit v1.2.3 From 51d54a8ca40f987bce877ad1d7dc78b1cb26b8a3 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 19 May 2020 13:02:02 -0600 Subject: Fix return content type --- Jellyfin.Api/Controllers/VideoAttachmentsController.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs index a10dd4059..596d21190 100644 --- a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs +++ b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs @@ -1,11 +1,13 @@ #nullable enable using System; +using System.Net.Mime; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Net; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -17,7 +19,7 @@ namespace Jellyfin.Api.Controllers /// [Route("Videos")] [Authorize] - public class VideoAttachmentsController : Controller + public class VideoAttachmentsController : BaseJellyfinApiController { private readonly ILibraryManager _libraryManager; private readonly IAttachmentExtractor _attachmentExtractor; @@ -45,7 +47,7 @@ namespace Jellyfin.Api.Controllers /// Video or attachment not found. /// An containing the attachment stream on success, or a if the attachment could not be found. [HttpGet("{VideoID}/{MediaSourceID}/Attachments/{Index}")] - [Produces("application/octet-stream")] + [Produces(MediaTypeNames.Application.Octet)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> GetAttachment( @@ -68,11 +70,9 @@ namespace Jellyfin.Api.Controllers CancellationToken.None) .ConfigureAwait(false); - var contentType = "application/octet-stream"; - if (string.IsNullOrWhiteSpace(attachment.MimeType)) - { - contentType = attachment.MimeType; - } + var contentType = string.IsNullOrWhiteSpace(attachment.MimeType) + ? MediaTypeNames.Application.Octet + : attachment.MimeType; return new FileStreamResult(stream, contentType); } -- cgit v1.2.3 From 2689865858c779491c98066df3f1e7d894f7c3b8 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 19 May 2020 13:02:35 -0600 Subject: Remove unused using --- Jellyfin.Api/Controllers/VideoAttachmentsController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs index 596d21190..86d9322fe 100644 --- a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs +++ b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Net; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -- cgit v1.2.3 From 070edd9e5bff63d3f158b6ca8b37095adc686492 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 19 May 2020 13:13:07 -0600 Subject: Fix MediaType usage --- Jellyfin.Api/Controllers/Images/ImageByNameController.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Api/Controllers/Images/ImageByNameController.cs b/Jellyfin.Api/Controllers/Images/ImageByNameController.cs index dadb34438..fa6080977 100644 --- a/Jellyfin.Api/Controllers/Images/ImageByNameController.cs +++ b/Jellyfin.Api/Controllers/Images/ImageByNameController.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net.Mime; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -60,7 +61,7 @@ namespace Jellyfin.Api.Controllers.Images /// Image not found. /// A containing the image contents on success, or a if the image could not be found. [HttpGet("General/{Name}/{Type}")] - [Produces("application/octet-stream")] + [Produces(MediaTypeNames.Application.Octet)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GetGeneralImage([FromRoute] string name, [FromRoute] string type) @@ -103,7 +104,7 @@ namespace Jellyfin.Api.Controllers.Images /// Image not found. /// A containing the image contents on success, or a if the image could not be found. [HttpGet("Ratings/{Theme}/{Name}")] - [Produces("application/octet-stream")] + [Produces(MediaTypeNames.Application.Octet)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GetRatingImage( @@ -134,7 +135,7 @@ namespace Jellyfin.Api.Controllers.Images /// Image not found. /// A containing the image contents on success, or a if the image could not be found. [HttpGet("MediaInfo/{Theme}/{Name}")] - [Produces("application/octet-stream")] + [Produces(MediaTypeNames.Application.Octet)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GetMediaInfoImage( -- cgit v1.2.3 From 5f0c37d5745cbf2632d377905a0763f0254bca08 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 19 May 2020 13:22:09 -0600 Subject: Fix DefaultDirectoryBrowserInfo naming --- Jellyfin.Api/Controllers/EnvironmentController.cs | 4 ++-- .../Models/EnvironmentDtos/DefaultDirectoryBrowserInfo.cs | 13 ------------- .../EnvironmentDtos/DefaultDirectoryBrowserInfoDto.cs | 13 +++++++++++++ 3 files changed, 15 insertions(+), 15 deletions(-) delete mode 100644 Jellyfin.Api/Models/EnvironmentDtos/DefaultDirectoryBrowserInfo.cs create mode 100644 Jellyfin.Api/Models/EnvironmentDtos/DefaultDirectoryBrowserInfoDto.cs diff --git a/Jellyfin.Api/Controllers/EnvironmentController.cs b/Jellyfin.Api/Controllers/EnvironmentController.cs index 78c206ba1..8d9d2642f 100644 --- a/Jellyfin.Api/Controllers/EnvironmentController.cs +++ b/Jellyfin.Api/Controllers/EnvironmentController.cs @@ -198,9 +198,9 @@ namespace Jellyfin.Api.Controllers /// Default directory browser. [HttpGet("DefaultDirectoryBrowser")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult GetDefaultDirectoryBrowser() + public ActionResult GetDefaultDirectoryBrowser() { - return new DefaultDirectoryBrowserInfo(); + return new DefaultDirectoryBrowserInfoDto(); } } } diff --git a/Jellyfin.Api/Models/EnvironmentDtos/DefaultDirectoryBrowserInfo.cs b/Jellyfin.Api/Models/EnvironmentDtos/DefaultDirectoryBrowserInfo.cs deleted file mode 100644 index 6b1c750bf..000000000 --- a/Jellyfin.Api/Models/EnvironmentDtos/DefaultDirectoryBrowserInfo.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Jellyfin.Api.Models.EnvironmentDtos -{ - /// - /// Default directory browser info. - /// - public class DefaultDirectoryBrowserInfo - { - /// - /// Gets or sets the path. - /// - public string Path { get; set; } - } -} diff --git a/Jellyfin.Api/Models/EnvironmentDtos/DefaultDirectoryBrowserInfoDto.cs b/Jellyfin.Api/Models/EnvironmentDtos/DefaultDirectoryBrowserInfoDto.cs new file mode 100644 index 000000000..a86815b81 --- /dev/null +++ b/Jellyfin.Api/Models/EnvironmentDtos/DefaultDirectoryBrowserInfoDto.cs @@ -0,0 +1,13 @@ +namespace Jellyfin.Api.Models.EnvironmentDtos +{ + /// + /// Default directory browser info. + /// + public class DefaultDirectoryBrowserInfoDto + { + /// + /// Gets or sets the path. + /// + public string Path { get; set; } + } +} -- cgit v1.2.3 From 0efb81b21ee05c9cbab4101de826250d0d698cf1 Mon Sep 17 00:00:00 2001 From: artiume Date: Tue, 19 May 2020 21:45:48 -0400 Subject: Add lost+found to ignore list https://forum.jellyfin.org/t/library-not-loading/2086 --- Emby.Server.Implementations/Library/IgnorePatterns.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Emby.Server.Implementations/Library/IgnorePatterns.cs b/Emby.Server.Implementations/Library/IgnorePatterns.cs index 49a36495a..d12b5855b 100644 --- a/Emby.Server.Implementations/Library/IgnorePatterns.cs +++ b/Emby.Server.Implementations/Library/IgnorePatterns.cs @@ -25,6 +25,7 @@ namespace Emby.Server.Implementations.Library "**/extrathumbs/**", "**/.actors/**", "**/.wd_tv/**", + "**/lost+found/**", // WMC temp recording directories that will constantly be written to "**/TempRec/**", -- cgit v1.2.3 From d7b2c2a17626e75ebb202fc9fa1186a88f670eec Mon Sep 17 00:00:00 2001 From: Neil Burrows Date: Wed, 20 May 2020 09:05:51 +0100 Subject: Renaming variable and refactoring IF statement --- Emby.Server.Implementations/IStartupOptions.cs | 4 +++- Emby.Server.Implementations/Udp/UdpServer.cs | 16 ++++------------ Jellyfin.Server/StartupOptions.cs | 9 +++++---- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index a3a047057..5d22bfddc 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -1,3 +1,5 @@ +using System; + namespace Emby.Server.Implementations { public interface IStartupOptions @@ -40,6 +42,6 @@ namespace Emby.Server.Implementations /// /// Gets the value of the --auto-discover-publish-url command line option. /// - string AutoDiscoverPublishUrl { get; } + Uri PublishedServerUrl { get; } } } diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index 57228d208..1ae3888dc 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -27,7 +27,7 @@ namespace Emby.Server.Implementations.Udp /// /// Address Override Configuration Key /// - public const string AddressOverrideConfigKey = "AutoDiscoverAddressOverride"; + public const string AddressOverrideConfigKey = "PublishedServerUrl"; private Socket _udpSocket; private IPEndPoint _endpoint; @@ -47,17 +47,9 @@ namespace Emby.Server.Implementations.Udp private async Task RespondToV2Message(string messageText, EndPoint endpoint, CancellationToken cancellationToken) { - string localUrl; - - if (!string.IsNullOrEmpty(_config[AddressOverrideConfigKey])) - { - localUrl = _config[AddressOverrideConfigKey]; - } - else - { - localUrl = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false); - } - + string localUrl = !string.IsNullOrEmpty(_config[AddressOverrideConfigKey]) + ? _config[AddressOverrideConfigKey] + : await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false); if (!string.IsNullOrEmpty(localUrl)) { diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index 135ba9d7f..cc250b06e 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using CommandLine; using Emby.Server.Implementations; @@ -83,8 +84,8 @@ namespace Jellyfin.Server public string? PluginManifestUrl { get; set; } /// - [Option("auto-discover-publish-url", Required = false, HelpText = "Jellyfin Server URL to publish via auto discover process")] - public string? AutoDiscoverPublishUrl { get; set; } + [Option("published-server-url", Required = false, HelpText = "Jellyfin Server URL to publish via auto discover process")] + public Uri? PublishedServerUrl { get; set; } /// /// Gets the command line options as a dictionary that can be used in the .NET configuration system. @@ -104,9 +105,9 @@ namespace Jellyfin.Server config.Add(ConfigurationExtensions.HostWebClientKey, bool.FalseString); } - if (AutoDiscoverPublishUrl != null) + if (PublishedServerUrl != null) { - config.Add(UdpServer.AddressOverrideConfigKey, AutoDiscoverPublishUrl); + config.Add(UdpServer.AddressOverrideConfigKey, PublishedServerUrl.ToString()); } return config; -- cgit v1.2.3 From fb068b76a12bf9de5de75a0b8079effcd0336ecf Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 20 May 2020 07:18:51 -0600 Subject: Use correct MediaTypeName --- Jellyfin.Api/Controllers/Images/RemoteImageController.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/Images/RemoteImageController.cs b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs index 665db561b..1155cc653 100644 --- a/Jellyfin.Api/Controllers/Images/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net.Mime; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Extensions; @@ -151,7 +152,7 @@ namespace Jellyfin.Api.Controllers.Images /// Remote image not found. /// Image Stream. [HttpGet("Remote")] - [Produces("application/octet-stream")] + [Produces(MediaTypeNames.Application.Octet)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> GetRemoteImage([FromQuery, BindRequired] string imageUrl) -- cgit v1.2.3 From a646a91e158de8f26cc24e6b87d4a665942c28d9 Mon Sep 17 00:00:00 2001 From: Neil Burrows Date: Wed, 20 May 2020 14:29:18 +0100 Subject: Update Emby.Server.Implementations/IStartupOptions.cs Co-authored-by: Cody Robibero --- Emby.Server.Implementations/IStartupOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index 5d22bfddc..acae702f3 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -40,7 +40,7 @@ namespace Emby.Server.Implementations string PluginManifestUrl { get; } /// - /// Gets the value of the --auto-discover-publish-url command line option. + /// Gets the value of the --published-server-url command line option. /// Uri PublishedServerUrl { get; } } -- 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 diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index b2fe6a3ab..56b103763 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 000000000..63d0321b7 --- /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 84b16f1c7..54c26a8a5 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 7966cdb50..711e94dd1 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 54f9f4905..017fb2234 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 0b5b52cbd..136e7abd3 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 505f52e6b..56a07d440 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 17913959e..7252ef230 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 000000000..a77e51e1e --- /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 9ac97a131..8eb35ec87 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 eab531d38..8f5c19900 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 8313c6a3b..000000000 --- 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 f24ccccbf..000000000 --- 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 000000000..36c58c8ca --- /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 000000000..55c6f371c --- /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 0fb0ba803..46714e865 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 024500bf8..000000000 --- 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 80ab3ce00..000000000 --- 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 d33034ab2..000000000 --- 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 a11ca128a..000000000 --- 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 73905ff70..000000000 --- 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 000000000..38494727c --- /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 000000000..60b48ec76 --- /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 000000000..d94a27b9d --- /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 000000000..e430808bf --- /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 000000000..ddc05055b --- /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 331a32c73..b4c5fd4ea 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 ea16c5573..9eec6ed4e 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 b19d7f7fc..0271098f4 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 8d94d3971..bc9724027 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 e7b297c67baee73a737c61170ad6a55c2f1d08d3 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 19 May 2020 15:58:25 -0400 Subject: Add some missing properties --- Jellyfin.Server.Implementations/Users/UserManager.cs | 16 +++++++++++++--- Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs | 1 - 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index ddc05055b..2d0caee29 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -10,9 +10,11 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using MediaBrowser.Common; using MediaBrowser.Common.Cryptography; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Configuration; @@ -29,6 +31,8 @@ namespace Jellyfin.Server.Implementations.Users private readonly JellyfinDbProvider _dbProvider; private readonly ICryptoProvider _cryptoProvider; private readonly INetworkManager _networkManager; + private readonly IApplicationHost _appHost; + private readonly IImageProcessor _imageProcessor; private readonly ILogger _logger; private IAuthenticationProvider[] _authenticationProviders; @@ -41,11 +45,15 @@ namespace Jellyfin.Server.Implementations.Users JellyfinDbProvider dbProvider, ICryptoProvider cryptoProvider, INetworkManager networkManager, + IApplicationHost appHost, + IImageProcessor imageProcessor, ILogger logger) { _dbProvider = dbProvider; _cryptoProvider = cryptoProvider; _networkManager = networkManager; + _appHost = appHost; + _imageProcessor = imageProcessor; _logger = logger; } @@ -123,8 +131,7 @@ namespace Jellyfin.Server.Implementations.Users 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))) + if (Users.Any(u => u.Id != user.Id && u.Username.Equals(newName, StringComparison.OrdinalIgnoreCase))) { throw new ArgumentException(string.Format( CultureInfo.InvariantCulture, @@ -248,11 +255,14 @@ namespace Jellyfin.Server.Implementations.Users { return new UserDto { + Name = user.Username, Id = user.Id, + ServerId = _appHost.SystemId, HasPassword = user.Password == null, EnableAutoLogin = user.EnableAutoLogin, LastLoginDate = user.LastLoginDate, LastActivityDate = user.LastActivityDate, + PrimaryImageTag = user.ProfileImage != null ? _imageProcessor.GetImageCacheTag(user) : null, Configuration = new UserConfiguration { SubtitleMode = user.SubtitleMode, @@ -265,7 +275,7 @@ namespace Jellyfin.Server.Implementations.Users RememberAudioSelections = user.RememberAudioSelections, EnableNextEpisodeAutoPlay = user.EnableNextEpisodeAutoPlay, RememberSubtitleSelections = user.RememberSubtitleSelections, - SubtitleLanguagePreference = user.SubtitleLanguagePreference, + SubtitleLanguagePreference = user.SubtitleLanguagePreference ?? string.Empty, OrderedViews = user.GetPreference(PreferenceKind.OrderedViews), GroupedFolders = user.GetPreference(PreferenceKind.GroupedFolders), MyMediaExcludes = user.GetPreference(PreferenceKind.MyMediaExcludes), diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index 0271098f4..9a428a747 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -9,7 +9,6 @@ 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 SQLitePCL.pretty; using JsonSerializer = System.Text.Json.JsonSerializer; -- cgit v1.2.3 From d27b2481a0765a97fde6101b4b3898200d60f0eb Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 19 May 2020 16:22:45 -0400 Subject: Fix an issue causing multiple permissions/preferences objects to be created. --- Jellyfin.Data/Entities/User.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index 7252ef230..334e6306d 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -77,8 +77,6 @@ namespace Jellyfin.Data.Entities Preferences = new HashSet(); AccessSchedules = new HashSet(); - AddDefaultPermissions(); - AddDefaultPreferences(); Init(); } -- cgit v1.2.3 From d35a7ba8bd5d49124cef0fb844080a3109cf61b7 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 19 May 2020 19:05:17 -0400 Subject: Fix more issues --- .../HttpServer/Security/AuthService.cs | 13 +++++++------ Jellyfin.Data/Entities/Group.cs | 7 +++---- Jellyfin.Data/Entities/User.cs | 16 +++++++++------- Jellyfin.Data/Jellyfin.Data.csproj | 1 + Jellyfin.Server/CoreAppHost.cs | 6 ++++-- MediaBrowser.Api/UserService.cs | 3 ++- 6 files changed, 26 insertions(+), 20 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index ad7b76d4f..eace86d51 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.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; @@ -44,14 +45,14 @@ namespace Emby.Server.Implementations.HttpServer.Security ValidateUser(request, authAttribtues); } - public Jellyfin.Data.Entities.User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes) + public User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes) { var req = new WebSocketSharpRequest(request, null, request.Path, _logger); var user = ValidateUser(req, authAttributes); return user; } - private Jellyfin.Data.Entities.User ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues) + private User ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues) { // This code is executed before the service var auth = _authorizationContext.GetAuthorizationInfo(request); @@ -104,9 +105,9 @@ namespace Emby.Server.Implementations.HttpServer.Security } private void ValidateUserAccess( - Jellyfin.Data.Entities.User user, + User user, IRequest request, - IAuthenticationAttributes authAttribtues, + IAuthenticationAttributes authAttributes, AuthorizationInfo auth) { if (user.HasPermission(PermissionKind.IsDisabled)) @@ -120,7 +121,7 @@ namespace Emby.Server.Implementations.HttpServer.Security } if (!user.HasPermission(PermissionKind.IsAdministrator) - && !authAttribtues.EscapeParentalControl + && !authAttributes.EscapeParentalControl && !user.IsParentalScheduleAllowed()) { request.Response.Headers.Add("X-Application-Error-Code", "ParentalControl"); @@ -178,7 +179,7 @@ namespace Emby.Server.Implementations.HttpServer.Security return false; } - private static void ValidateRoles(string[] roles, Jellyfin.Data.Entities.User user) + private static void ValidateRoles(string[] roles, User user) { if (roles.Contains("admin", StringComparer.OrdinalIgnoreCase)) { diff --git a/Jellyfin.Data/Entities/Group.cs b/Jellyfin.Data/Entities/Group.cs index 017fb2234..ecef4102c 100644 --- a/Jellyfin.Data/Entities/Group.cs +++ b/Jellyfin.Data/Entities/Group.cs @@ -89,14 +89,13 @@ namespace Jellyfin.Data.Entities *************************************************************************/ [ForeignKey("Permission_GroupPermissions_Id")] - public ICollection Permissions { get; protected set; } + public virtual ICollection Permissions { get; protected set; } [ForeignKey("ProviderMapping_ProviderMappings_Id")] - public ICollection ProviderMappings { get; protected set; } + public virtual ICollection ProviderMappings { get; protected set; } [ForeignKey("Preference_Preferences_Id")] - public ICollection Preferences { get; protected set; } - + public virtual ICollection Preferences { get; protected set; } } } diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index 334e6306d..bd1cde31c 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -222,7 +222,7 @@ namespace Jellyfin.Data.Entities [Required] public long InternalId { get; set; } - public ImageInfo ProfileImage { get; set; } + public virtual ImageInfo ProfileImage { get; set; } /// /// Gets or sets the row version. @@ -241,24 +241,26 @@ namespace Jellyfin.Data.Entities * Navigation properties *************************************************************************/ [ForeignKey("Group_Groups_Guid")] - public ICollection Groups { get; protected set; } + public virtual ICollection Groups { get; protected set; } [ForeignKey("Permission_Permissions_Guid")] - public ICollection Permissions { get; protected set; } + public virtual ICollection Permissions { get; protected set; } [ForeignKey("ProviderMapping_ProviderMappings_Id")] - public ICollection ProviderMappings { get; protected set; } + public virtual ICollection ProviderMappings { get; protected set; } [ForeignKey("Preference_Preferences_Guid")] - public ICollection Preferences { get; protected set; } + public virtual ICollection Preferences { get; protected set; } - public ICollection AccessSchedules { get; protected set; } + public virtual ICollection AccessSchedules { get; protected set; } partial void Init(); public bool HasPermission(PermissionKind permission) { - return Permissions.First(p => p.Kind == permission).Value; + var list = Permissions.Where(p => p.Kind == permission); + + return list.First().Value; } public void SetPermission(PermissionKind kind, bool value) diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index b2a3f7eb3..97495297e 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -21,6 +21,7 @@ + diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index b4c5fd4ea..81ae38467 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -65,8 +65,10 @@ namespace Jellyfin.Server // TODO: Set up scoping and use AddDbContextPool serviceCollection.AddDbContext( - options => options.UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"), - ServiceLifetime.Transient); + options => options + .UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}") + .UseLazyLoadingProxies(), + ServiceLifetime.Transient); serviceCollection.AddSingleton(); diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 8d4450c1a..e19ec47f8 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -276,7 +276,8 @@ namespace MediaBrowser.Api { var result = _userManager .Users - .Where(item => !item.HasPermission(PermissionKind.IsDisabled)); + .Where(user => !user.HasPermission(PermissionKind.IsDisabled)) + .AsQueryable(); if (ServerConfigurationManager.Configuration.IsStartupWizardCompleted) { -- cgit v1.2.3 From 292993d8efabf0380d7b6a53e074324b4c92f377 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 19 May 2020 19:44:55 -0400 Subject: Document various classes. --- Jellyfin.Data/Entities/Preference.cs | 23 ++++-- Jellyfin.Data/Enums/PermissionKind.cs | 86 ++++++++++++++++++++++ Jellyfin.Data/Enums/PreferenceKind.cs | 50 +++++++++++++ .../Users/UserManager.cs | 38 +++++++++- 4 files changed, 190 insertions(+), 7 deletions(-) diff --git a/Jellyfin.Data/Entities/Preference.cs b/Jellyfin.Data/Entities/Preference.cs index 56a07d440..0ca9d7eff 100644 --- a/Jellyfin.Data/Entities/Preference.cs +++ b/Jellyfin.Data/Entities/Preference.cs @@ -35,30 +35,42 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Identity, Indexed, Required + /// Gets or sets the id of this preference. /// + /// + /// Identity, Indexed, Required. + /// [Key] [Required] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; protected set; } /// - /// Required + /// Gets or sets the type of this preference. /// + /// + /// Required. + /// [Required] - public PreferenceKind Kind { get; set; } + public PreferenceKind Kind { get; protected set; } /// - /// Required, Max length = 65535 + /// Gets or sets the value of this preference. /// + /// + /// Required, Max length = 65535. + /// [Required] [MaxLength(65535)] [StringLength(65535)] public string Value { get; set; } /// - /// Required, ConcurrencyToken. + /// Gets or sets the row version. /// + /// + /// Required, ConcurrencyToken. + /// [ConcurrencyCheck] [Required] public uint RowVersion { get; set; } @@ -81,4 +93,3 @@ namespace Jellyfin.Data.Entities } } } - diff --git a/Jellyfin.Data/Enums/PermissionKind.cs b/Jellyfin.Data/Enums/PermissionKind.cs index df18261e6..8b1472f97 100644 --- a/Jellyfin.Data/Enums/PermissionKind.cs +++ b/Jellyfin.Data/Enums/PermissionKind.cs @@ -1,27 +1,113 @@ namespace Jellyfin.Data.Enums { + /// + /// The types of user permissions. + /// public enum PermissionKind { + /// + /// Whether the user is an administrator. + /// IsAdministrator, + + /// + /// Whether the user is hidden. + /// IsHidden, + + /// + /// Whether the user is disabled. + /// IsDisabled, + + /// + /// Whether the user can control shared devices. + /// EnableSharedDeviceControl, + + /// + /// Whether the user can access the server remotely. + /// EnableRemoteAccess, + + /// + /// Whether the user can manage live tv. + /// EnableLiveTvManagement, + + /// + /// Whether the user can access live tv. + /// EnableLiveTvAccess, + + /// + /// Whether the user can play media. + /// EnableMediaPlayback, + + /// + /// Whether the server should transcode audio for the user if requested. + /// EnableAudioPlaybackTranscoding, + + /// + /// Whether the server should transcode video for the user if requested. + /// EnableVideoPlaybackTranscoding, + + /// + /// Whether the user can delete content. + /// EnableContentDeletion, + + /// + /// Whether the user can download content. + /// EnableContentDownloading, + + /// + /// Whether to enable sync transcoding for the user. + /// EnableSyncTranscoding, + + /// + /// Whether the user can do media conversion. + /// EnableMediaConversion, + + /// + /// Whether the user has access to all devices. + /// EnableAllDevices, + + /// + /// Whether the user has access to all channels. + /// EnableAllChannels, + + /// + /// Whether the user has access to all folders. + /// EnableAllFolders, + + /// + /// Whether to enable public sharing for the user. + /// EnablePublicSharing, + + /// + /// Whether the user can remotely control other users. + /// EnableRemoteControlOfOtherUsers, + + /// + /// Whether the user is permitted to do playback remuxing. + /// EnablePlaybackRemuxing, + + /// + /// Whether the server should force transcoding on remote connections for the user. + /// ForceRemoteSourceTranscoding } } diff --git a/Jellyfin.Data/Enums/PreferenceKind.cs b/Jellyfin.Data/Enums/PreferenceKind.cs index ea1221e1a..e0e9cfe04 100644 --- a/Jellyfin.Data/Enums/PreferenceKind.cs +++ b/Jellyfin.Data/Enums/PreferenceKind.cs @@ -1,18 +1,68 @@ namespace Jellyfin.Data.Enums { + /// + /// The types of user preferences. + /// public enum PreferenceKind { + /// + /// A list of blocked tags. + /// BlockedTags, + + /// + /// A list of blocked channels. + /// BlockedChannels, + + /// + /// A list of blocked media folders. + /// BlockedMediaFolders, + + /// + /// A list of enabled devices. + /// EnabledDevices, + + /// + /// A list of enabled channels + /// EnabledChannels, + + /// + /// A list of enabled folders. + /// EnabledFolders, + + /// + /// A list of folders to allow content deletion from. + /// EnableContentDeletionFromFolders, + + /// + /// A list of latest items to exclude. + /// LatestItemExcludes, + + /// + /// A list of media to exclude. + /// MyMediaExcludes, + + /// + /// A list of grouped folders. + /// GroupedFolders, + + /// + /// A list of unrated items to block. + /// BlockUnratedItems, + + /// + /// A list of ordered views. + /// OrderedViews } } diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 2d0caee29..291574155 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -1,5 +1,4 @@ #pragma warning disable CA1307 -#pragma warning disable CS1591 using System; using System.Collections.Generic; @@ -26,6 +25,9 @@ using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Implementations.Users { + /// + /// Manages the creation and retrieval of instances. + /// public class UserManager : IUserManager { private readonly JellyfinDbProvider _dbProvider; @@ -41,6 +43,15 @@ namespace Jellyfin.Server.Implementations.Users private IPasswordResetProvider[] _passwordResetProviders; private DefaultPasswordResetProvider _defaultPasswordResetProvider; + /// + /// Initializes a new instance of the class. + /// + /// The database provider. + /// The cryptography provider. + /// The network manager. + /// The application host. + /// The image processor. + /// The logger. public UserManager( JellyfinDbProvider dbProvider, ICryptoProvider cryptoProvider, @@ -57,6 +68,7 @@ namespace Jellyfin.Server.Implementations.Users _logger = logger; } + /// public event EventHandler> OnUserPasswordChanged; /// @@ -68,8 +80,10 @@ namespace Jellyfin.Server.Implementations.Users /// public event EventHandler> OnUserDeleted; + /// public event EventHandler> OnUserLockedOut; + /// public IEnumerable Users { get @@ -79,6 +93,7 @@ namespace Jellyfin.Server.Implementations.Users } } + /// public IEnumerable UsersIds { get @@ -88,6 +103,7 @@ namespace Jellyfin.Server.Implementations.Users } } + /// public User GetUserById(Guid id) { if (id == Guid.Empty) @@ -100,6 +116,7 @@ namespace Jellyfin.Server.Implementations.Users return dbContext.Users.Find(id); } + /// public User GetUserByName(string name) { if (string.IsNullOrWhiteSpace(name)) @@ -114,6 +131,7 @@ namespace Jellyfin.Server.Implementations.Users return dbContext.Users.FirstOrDefault(u => string.Equals(u.Username, name)); } + /// public async Task RenameUser(User user, string newName) { if (user == null) @@ -145,6 +163,7 @@ namespace Jellyfin.Server.Implementations.Users OnUserUpdated?.Invoke(this, new GenericEventArgs(user)); } + /// public void UpdateUser(User user) { var dbContext = _dbProvider.CreateContext(); @@ -152,6 +171,7 @@ namespace Jellyfin.Server.Implementations.Users dbContext.SaveChanges(); } + /// public async Task UpdateUserAsync(User user) { var dbContext = _dbProvider.CreateContext(); @@ -160,6 +180,7 @@ namespace Jellyfin.Server.Implementations.Users await dbContext.SaveChangesAsync().ConfigureAwait(false); } + /// public User CreateUser(string name) { if (!IsValidUsername(name)) @@ -178,6 +199,7 @@ namespace Jellyfin.Server.Implementations.Users return newUser; } + /// public void DeleteUser(User user) { if (user == null) @@ -220,16 +242,19 @@ namespace Jellyfin.Server.Implementations.Users 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) @@ -243,6 +268,7 @@ namespace Jellyfin.Server.Implementations.Users OnUserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); } + /// public void ChangeEasyPassword(User user, string newPassword, string newPasswordSha1) { GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordSha1); @@ -251,6 +277,7 @@ namespace Jellyfin.Server.Implementations.Users OnUserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); } + /// public UserDto GetUserDto(User user, string remoteEndPoint = null) { return new UserDto @@ -321,6 +348,7 @@ namespace Jellyfin.Server.Implementations.Users }; } + /// public PublicUserDto GetPublicUserDto(User user, string remoteEndPoint = null) { if (user == null) @@ -343,6 +371,7 @@ namespace Jellyfin.Server.Implementations.Users }; } + /// public async Task AuthenticateUser( string username, string password, @@ -469,6 +498,7 @@ namespace Jellyfin.Server.Implementations.Users return success ? user : null; } + /// public async Task StartForgotPasswordProcess(string enteredUsername, bool isInNetwork) { var user = string.IsNullOrWhiteSpace(enteredUsername) ? null : GetUserByName(enteredUsername); @@ -488,6 +518,7 @@ namespace Jellyfin.Server.Implementations.Users }; } + /// public async Task RedeemPasswordResetPin(string pin) { foreach (var provider in _passwordResetProviders) @@ -507,6 +538,7 @@ namespace Jellyfin.Server.Implementations.Users }; } + /// public void AddParts(IEnumerable authenticationProviders, IEnumerable passwordResetProviders) { _authenticationProviders = authenticationProviders.ToArray(); @@ -517,6 +549,7 @@ namespace Jellyfin.Server.Implementations.Users _defaultPasswordResetProvider = _passwordResetProviders.OfType().First(); } + /// public NameIdPair[] GetAuthenticationProviders() { return _authenticationProviders @@ -531,6 +564,7 @@ namespace Jellyfin.Server.Implementations.Users .ToArray(); } + /// public NameIdPair[] GetPasswordResetProviders() { return _passwordResetProviders @@ -545,6 +579,7 @@ namespace Jellyfin.Server.Implementations.Users .ToArray(); } + /// public void UpdateConfiguration(Guid userId, UserConfiguration config) { var user = GetUserById(userId); @@ -568,6 +603,7 @@ namespace Jellyfin.Server.Implementations.Users UpdateUser(user); } + /// public void UpdatePolicy(Guid userId, UserPolicy policy) { var user = GetUserById(userId); -- cgit v1.2.3 From 1d1a145ad4386b39b983d539001abfccf5f53b23 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 19 May 2020 22:12:03 -0400 Subject: Fix issues and add profile image support --- Jellyfin.Data/Entities/ImageInfo.cs | 13 +++++++++++-- Jellyfin.Data/Entities/Permission.cs | 18 ++++++++++-------- Jellyfin.Data/Entities/User.cs | 8 +++++--- Jellyfin.Server.Implementations/Users/UserManager.cs | 5 ++++- Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs | 17 +++++++++++++++-- MediaBrowser.Api/Images/ImageService.cs | 3 ++- MediaBrowser.Providers/Manager/ImageSaver.cs | 9 ++------- MediaBrowser.Providers/Manager/ProviderManager.cs | 2 +- 8 files changed, 50 insertions(+), 25 deletions(-) diff --git a/Jellyfin.Data/Entities/ImageInfo.cs b/Jellyfin.Data/Entities/ImageInfo.cs index 336c13b36..659369545 100644 --- a/Jellyfin.Data/Entities/ImageInfo.cs +++ b/Jellyfin.Data/Entities/ImageInfo.cs @@ -1,24 +1,33 @@ using System; using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace Jellyfin.Data.Entities { public class ImageInfo { - public ImageInfo(string path) + public ImageInfo(string path, int width, int height) { Path = path; + Width = width; + Height = height; LastModified = DateTime.UtcNow; } [Key] [Required] - + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; protected set; } [Required] public string Path { get; set; } + [Required] + public int Width { get; set; } + + [Required] + public int Height { get; set; } + [Required] public DateTime LastModified { get; set; } } diff --git a/Jellyfin.Data/Entities/Permission.cs b/Jellyfin.Data/Entities/Permission.cs index 136e7abd3..706128028 100644 --- a/Jellyfin.Data/Entities/Permission.cs +++ b/Jellyfin.Data/Entities/Permission.cs @@ -12,14 +12,7 @@ namespace Jellyfin.Data.Entities partial void Init(); /// - /// Default constructor. Protected due to required properties, but present because EF needs it. - /// - protected Permission() - { - Init(); - } - - /// + /// Initializes a new instance of the class. /// Public constructor with required data /// /// @@ -33,6 +26,15 @@ namespace Jellyfin.Data.Entities Init(); } + /// + /// Initializes a new instance of the class. + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected Permission() + { + Init(); + } + /// /// Static create function (for use in LINQ queries, etc.) /// diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index bd1cde31c..783823a17 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -25,7 +25,7 @@ namespace Jellyfin.Data.Entities /// /// The username for the new user. /// The authentication provider's Id - public User(string username, string authenticationProviderId) + public User(string username, string authenticationProviderId, string passwordResetProviderId) { if (string.IsNullOrEmpty(username)) { @@ -39,6 +39,7 @@ namespace Jellyfin.Data.Entities Username = username; AuthenticationProviderId = authenticationProviderId; + PasswordResetProviderId = passwordResetProviderId; Groups = new HashSet(); Permissions = new HashSet(); @@ -85,10 +86,11 @@ namespace Jellyfin.Data.Entities /// /// The username for the created user. /// The Id of the user's authentication provider. + /// The Id of the user's password reset provider. /// The created instance. - public static User Create(string username, string authenticationProviderId) + public static User Create(string username, string authenticationProviderId, string passwordResetProviderId) { - return new User(username, authenticationProviderId); + return new User(username, authenticationProviderId, passwordResetProviderId); } /************************************************************************* diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 291574155..18616f75f 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -190,7 +190,10 @@ namespace Jellyfin.Server.Implementations.Users var dbContext = _dbProvider.CreateContext(); - var newUser = new User(name, _defaultAuthenticationProvider.GetType().FullName); + var newUser = new User( + name, + _defaultAuthenticationProvider.GetType().FullName, + _defaultPasswordResetProvider.GetType().FullName); dbContext.Users.Add(newUser); dbContext.SaveChanges(); diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index 9a428a747..987c85f4c 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -7,6 +7,7 @@ using Jellyfin.Data.Enums; using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations.Users; using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Users; using Microsoft.Extensions.Logging; @@ -84,9 +85,9 @@ namespace Jellyfin.Server.Migrations.Routines StringComparison.Ordinal) ?? typeof(DefaultAuthenticationProvider).FullName; - policy.PasswordResetProviderId ??= typeof(DefaultPasswordResetProvider).FullName; + policy.PasswordResetProviderId = typeof(DefaultPasswordResetProvider).FullName; - var user = new User(mockup.Name, policy.AuthenticationProviderId) + var user = new User(mockup.Name, policy.AuthenticationProviderId, string.Empty) { Id = entry[1].ReadGuidFromBlob(), InternalId = entry[0].ToInt64(), @@ -113,6 +114,16 @@ namespace Jellyfin.Server.Migrations.Routines LastActivityDate = mockup.LastActivityDate ?? DateTime.MinValue }; + if (mockup.ImageInfos.Length > 0) + { + ItemImageInfo info = mockup.ImageInfos[0]; + + user.ProfileImage = new ImageInfo(info.Path, info.Width, info.Height) + { + LastModified = info.DateModified + }; + } + user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator); user.SetPermission(PermissionKind.IsHidden, policy.IsHidden); user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled); @@ -184,6 +195,8 @@ namespace Jellyfin.Server.Migrations.Routines public DateTime? LastActivityDate { get; set; } public string Name { get; set; } + + public ItemImageInfo[] ImageInfos { get; set; } } } } diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 5bdf1618b..1392184df 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -17,6 +17,7 @@ using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; +using MediaBrowser.Model.Net; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; @@ -887,7 +888,7 @@ namespace MediaBrowser.Api.Images var userDataPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath, user.Username); await _providerManager - .SaveImage(user, memoryStream, mimeType, Path.Combine(userDataPath, _imageProcessor.GetImageCacheTag(user))) + .SaveImage(user, memoryStream, mimeType, Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType))) .ConfigureAwait(false); await _userManager.UpdateUserAsync(user); } diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 6bed38780..3c94f6215 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -131,7 +131,7 @@ namespace MediaBrowser.Providers.Manager var currentImage = GetCurrentImage(item, type, index); var currentImageIsLocalFile = currentImage != null && currentImage.IsLocalFile; - var currentImagePath = currentImage == null ? null : currentImage.Path; + var currentImagePath = currentImage?.Path; var savedPaths = new List(); @@ -179,13 +179,8 @@ namespace MediaBrowser.Providers.Manager } } - public async Task SaveImage(User user, Stream source, string mimeType, string path) + public async Task SaveImage(User user, Stream source, string path) { - if (string.IsNullOrEmpty(mimeType)) - { - throw new ArgumentNullException(nameof(mimeType)); - } - await SaveImageToLocation(source, path, path, CancellationToken.None).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index f4e875a24..be8fe3e1c 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -191,7 +191,7 @@ namespace MediaBrowser.Providers.Manager public Task SaveImage(User user, Stream source, string mimeType, string path) { return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger) - .SaveImage(user, source, mimeType, path); + .SaveImage(user, source, path); } public async Task> GetAvailableRemoteImages(BaseItem item, RemoteImageQuery query, CancellationToken cancellationToken) -- cgit v1.2.3 From 64c14beb27348daf0f440b5b1bc31ccb2987f9ff Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 20 May 2020 00:23:56 -0400 Subject: Fix default permissions and HasPassword property --- Jellyfin.Data/Entities/User.cs | 6 +++--- Jellyfin.Server.Implementations/Users/UserManager.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index 783823a17..52bbe8b18 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -325,10 +325,10 @@ namespace Jellyfin.Data.Entities { 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.IsHidden, true)); + Permissions.Add(new Permission(PermissionKind.EnableAllChannels, true)); Permissions.Add(new Permission(PermissionKind.EnableAllDevices, true)); - Permissions.Add(new Permission(PermissionKind.EnableAllFolders, false)); + Permissions.Add(new Permission(PermissionKind.EnableAllFolders, true)); Permissions.Add(new Permission(PermissionKind.EnableContentDeletion, false)); Permissions.Add(new Permission(PermissionKind.EnableContentDownloading, true)); Permissions.Add(new Permission(PermissionKind.EnableMediaConversion, true)); diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 18616f75f..4dd41792d 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -288,7 +288,7 @@ namespace Jellyfin.Server.Implementations.Users Name = user.Username, Id = user.Id, ServerId = _appHost.SystemId, - HasPassword = user.Password == null, + HasPassword = user.Password != null, EnableAutoLogin = user.EnableAutoLogin, LastLoginDate = user.LastLoginDate, LastActivityDate = user.LastActivityDate, -- cgit v1.2.3 From 623dcde65c25e6d7f64f6e9fc354208b708c17b8 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 20 May 2020 09:36:12 -0400 Subject: Manually specify enum values --- Jellyfin.Data/Enums/PermissionKind.cs | 42 +++++++++++++++++------------------ Jellyfin.Data/Enums/PreferenceKind.cs | 24 ++++++++++---------- Jellyfin.Data/Enums/Weekday.cs | 14 ++++++------ 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/Jellyfin.Data/Enums/PermissionKind.cs b/Jellyfin.Data/Enums/PermissionKind.cs index 8b1472f97..7d5200874 100644 --- a/Jellyfin.Data/Enums/PermissionKind.cs +++ b/Jellyfin.Data/Enums/PermissionKind.cs @@ -8,106 +8,106 @@ namespace Jellyfin.Data.Enums /// /// Whether the user is an administrator. /// - IsAdministrator, + IsAdministrator = 0, /// /// Whether the user is hidden. /// - IsHidden, + IsHidden = 1, /// /// Whether the user is disabled. /// - IsDisabled, + IsDisabled = 2, /// /// Whether the user can control shared devices. /// - EnableSharedDeviceControl, + EnableSharedDeviceControl = 3, /// /// Whether the user can access the server remotely. /// - EnableRemoteAccess, + EnableRemoteAccess = 4, /// /// Whether the user can manage live tv. /// - EnableLiveTvManagement, + EnableLiveTvManagement = 5, /// /// Whether the user can access live tv. /// - EnableLiveTvAccess, + EnableLiveTvAccess = 6, /// /// Whether the user can play media. /// - EnableMediaPlayback, + EnableMediaPlayback = 7, /// /// Whether the server should transcode audio for the user if requested. /// - EnableAudioPlaybackTranscoding, + EnableAudioPlaybackTranscoding = 8, /// /// Whether the server should transcode video for the user if requested. /// - EnableVideoPlaybackTranscoding, + EnableVideoPlaybackTranscoding = 9, /// /// Whether the user can delete content. /// - EnableContentDeletion, + EnableContentDeletion = 10, /// /// Whether the user can download content. /// - EnableContentDownloading, + EnableContentDownloading = 11, /// /// Whether to enable sync transcoding for the user. /// - EnableSyncTranscoding, + EnableSyncTranscoding = 12, /// /// Whether the user can do media conversion. /// - EnableMediaConversion, + EnableMediaConversion = 13, /// /// Whether the user has access to all devices. /// - EnableAllDevices, + EnableAllDevices = 14, /// /// Whether the user has access to all channels. /// - EnableAllChannels, + EnableAllChannels = 15, /// /// Whether the user has access to all folders. /// - EnableAllFolders, + EnableAllFolders = 16, /// /// Whether to enable public sharing for the user. /// - EnablePublicSharing, + EnablePublicSharing = 17, /// /// Whether the user can remotely control other users. /// - EnableRemoteControlOfOtherUsers, + EnableRemoteControlOfOtherUsers = 18, /// /// Whether the user is permitted to do playback remuxing. /// - EnablePlaybackRemuxing, + EnablePlaybackRemuxing = 19, /// /// Whether the server should force transcoding on remote connections for the user. /// - ForceRemoteSourceTranscoding + ForceRemoteSourceTranscoding = 20 } } diff --git a/Jellyfin.Data/Enums/PreferenceKind.cs b/Jellyfin.Data/Enums/PreferenceKind.cs index e0e9cfe04..de8eecc73 100644 --- a/Jellyfin.Data/Enums/PreferenceKind.cs +++ b/Jellyfin.Data/Enums/PreferenceKind.cs @@ -8,61 +8,61 @@ namespace Jellyfin.Data.Enums /// /// A list of blocked tags. /// - BlockedTags, + BlockedTags = 0, /// /// A list of blocked channels. /// - BlockedChannels, + BlockedChannels = 1, /// /// A list of blocked media folders. /// - BlockedMediaFolders, + BlockedMediaFolders = 2, /// /// A list of enabled devices. /// - EnabledDevices, + EnabledDevices = 3, /// /// A list of enabled channels /// - EnabledChannels, + EnabledChannels = 4, /// /// A list of enabled folders. /// - EnabledFolders, + EnabledFolders = 5, /// /// A list of folders to allow content deletion from. /// - EnableContentDeletionFromFolders, + EnableContentDeletionFromFolders = 6, /// /// A list of latest items to exclude. /// - LatestItemExcludes, + LatestItemExcludes = 7, /// /// A list of media to exclude. /// - MyMediaExcludes, + MyMediaExcludes = 8, /// /// A list of grouped folders. /// - GroupedFolders, + GroupedFolders = 9, /// /// A list of unrated items to block. /// - BlockUnratedItems, + BlockUnratedItems = 10, /// /// A list of ordered views. /// - OrderedViews + OrderedViews = 11 } } diff --git a/Jellyfin.Data/Enums/Weekday.cs b/Jellyfin.Data/Enums/Weekday.cs index b80a03a33..b799fd811 100644 --- a/Jellyfin.Data/Enums/Weekday.cs +++ b/Jellyfin.Data/Enums/Weekday.cs @@ -2,12 +2,12 @@ namespace Jellyfin.Data.Enums { public enum Weekday { - Sunday, - Monday, - Tuesday, - Wednesday, - Thursday, - Friday, - Saturday + Sunday = 0, + Monday = 1, + Tuesday = 2, + Wednesday = 3, + Thursday = 4, + Friday = 5, + Saturday = 6 } } -- cgit v1.2.3 From 7d9d54d2ecad14eaec14b00ca73c479d4d64c34f Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 20 May 2020 12:09:52 -0400 Subject: Fix profile images. --- Jellyfin.Data/Entities/ImageInfo.cs | 12 +++--------- Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs | 2 +- MediaBrowser.Api/Images/ImageService.cs | 13 ++++++++++++- MediaBrowser.Controller/Drawing/ImageHelper.cs | 1 + MediaBrowser.Model/Entities/ImageType.cs | 7 ++++++- 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/Jellyfin.Data/Entities/ImageInfo.cs b/Jellyfin.Data/Entities/ImageInfo.cs index 659369545..8bbce95e4 100644 --- a/Jellyfin.Data/Entities/ImageInfo.cs +++ b/Jellyfin.Data/Entities/ImageInfo.cs @@ -6,11 +6,9 @@ namespace Jellyfin.Data.Entities { public class ImageInfo { - public ImageInfo(string path, int width, int height) + public ImageInfo(string path) { Path = path; - Width = width; - Height = height; LastModified = DateTime.UtcNow; } @@ -20,14 +18,10 @@ namespace Jellyfin.Data.Entities public int Id { get; protected set; } [Required] + [MaxLength(512)] + [StringLength(512)] public string Path { get; set; } - [Required] - public int Width { get; set; } - - [Required] - public int Height { get; set; } - [Required] public DateTime LastModified { get; set; } } diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index 987c85f4c..a1895247f 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -118,7 +118,7 @@ namespace Jellyfin.Server.Migrations.Routines { ItemImageInfo info = mockup.ImageInfos[0]; - user.ProfileImage = new ImageInfo(info.Path, info.Width, info.Height) + user.ProfileImage = new ImageInfo(info.Path) { LastModified = info.DateModified }; diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 1392184df..149c4cb9b 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -471,8 +471,17 @@ namespace MediaBrowser.Api.Images AssertCanUpdateUser(_authContext, _userManager, userId, true); var user = _userManager.GetUserById(userId); + try + { + File.Delete(user.ProfileImage.Path); + } + catch (IOException e) + { + // TODO: Log this + } user.ProfileImage = null; + _userManager.UpdateUser(user); } /// @@ -639,6 +648,7 @@ namespace MediaBrowser.Api.Images IDictionary headers, bool isHeadRequest) { + info.Type = ImageType.Profile; var options = new ImageProcessingOptions { CropWhiteSpace = true, @@ -886,9 +896,10 @@ namespace MediaBrowser.Api.Images // Handle image/png; charset=utf-8 mimeType = mimeType.Split(';').FirstOrDefault(); var userDataPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath, user.Username); + user.ProfileImage = new Jellyfin.Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType))); await _providerManager - .SaveImage(user, memoryStream, mimeType, Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType))) + .SaveImage(user, memoryStream, mimeType, user.ProfileImage.Path) .ConfigureAwait(false); await _userManager.UpdateUserAsync(user); } diff --git a/MediaBrowser.Controller/Drawing/ImageHelper.cs b/MediaBrowser.Controller/Drawing/ImageHelper.cs index d5a5f547e..c87a248b5 100644 --- a/MediaBrowser.Controller/Drawing/ImageHelper.cs +++ b/MediaBrowser.Controller/Drawing/ImageHelper.cs @@ -57,6 +57,7 @@ namespace MediaBrowser.Controller.Drawing case ImageType.BoxRear: case ImageType.Disc: case ImageType.Menu: + case ImageType.Profile: return 1; case ImageType.Logo: return 2.58; diff --git a/MediaBrowser.Model/Entities/ImageType.cs b/MediaBrowser.Model/Entities/ImageType.cs index d89a4b3ad..6ea9ee419 100644 --- a/MediaBrowser.Model/Entities/ImageType.cs +++ b/MediaBrowser.Model/Entities/ImageType.cs @@ -63,6 +63,11 @@ namespace MediaBrowser.Model.Entities /// /// The box rear. /// - BoxRear = 11 + BoxRear = 11, + + /// + /// The user profile image. + /// + Profile = 12 } } -- cgit v1.2.3 From c464f700dbfa72d6f88310023759050867577e6a Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 20 May 2020 13:07:53 -0400 Subject: Remove redundant qualifiers --- Emby.Dlna/ContentDirectory/ContentDirectory.cs | 7 ++- Emby.Dlna/ContentDirectory/ControlHandler.cs | 72 ++++++++++++---------- Emby.Dlna/Didl/DidlBuilder.cs | 23 ++++--- Emby.Dlna/PlayTo/PlayToController.cs | 4 +- Emby.Notifications/NotificationManager.cs | 7 ++- .../Channels/ChannelManager.cs | 10 ++- .../Collections/CollectionManager.cs | 5 +- Emby.Server.Implementations/Dto/DtoService.cs | 29 +++++---- .../EntryPoints/LibraryChangedNotifier.cs | 3 +- .../HttpServer/Security/SessionContext.cs | 6 +- .../Library/LibraryManager.cs | 23 ++++--- .../Library/MediaSourceManager.cs | 15 ++--- .../Library/MusicManager.cs | 18 +++--- .../Library/SearchEngine.cs | 7 ++- .../Library/UserDataManager.cs | 11 ++-- .../Library/UserViewManager.cs | 5 +- .../LiveTv/LiveTvManager.cs | 25 ++++---- .../Playlists/ManualPlaylistsFolder.cs | 5 +- .../Playlists/PlaylistManager.cs | 7 ++- .../Sorting/DateLastMediaAddedComparer.cs | 3 +- .../Sorting/DatePlayedComparer.cs | 3 +- .../Sorting/IsFavoriteOrLikeComparer.cs | 3 +- .../Sorting/IsPlayedComparer.cs | 3 +- .../Sorting/IsUnplayedComparer.cs | 3 +- .../Sorting/PlayCountComparer.cs | 3 +- Emby.Server.Implementations/TV/TVSeriesManager.cs | 8 ++- .../Users/DefaultAuthenticationProvider.cs | 15 ++--- .../Users/InvalidAuthProvider.cs | 9 +-- MediaBrowser.Api/FilterService.cs | 3 +- MediaBrowser.Api/Library/LibraryService.cs | 15 +++-- MediaBrowser.Api/Movies/MoviesService.cs | 26 ++------ MediaBrowser.Api/Music/InstantMixService.cs | 3 +- MediaBrowser.Api/SuggestionsService.cs | 3 +- .../UserLibrary/BaseItemsByNameService.cs | 5 +- MediaBrowser.Api/UserLibrary/ItemsService.cs | 7 ++- MediaBrowser.Api/UserLibrary/PlaystateService.cs | 4 +- MediaBrowser.Controller/Channels/Channel.cs | 5 +- .../Collections/ICollectionManager.cs | 3 +- MediaBrowser.Controller/Drawing/IImageProcessor.cs | 3 +- MediaBrowser.Controller/Dto/IDtoService.cs | 9 +-- .../Entities/Audio/MusicAlbum.cs | 5 +- .../Entities/Audio/MusicArtist.cs | 13 ++-- MediaBrowser.Controller/Entities/BaseItem.cs | 45 +++++++------- MediaBrowser.Controller/Entities/Folder.cs | 40 ++++++------ .../Entities/InternalItemsQuery.cs | 7 ++- MediaBrowser.Controller/Entities/Movies/BoxSet.cs | 15 ++--- MediaBrowser.Controller/Entities/TV/Season.cs | 13 ++-- MediaBrowser.Controller/Entities/TV/Series.cs | 19 +++--- MediaBrowser.Controller/Entities/UserRootFolder.cs | 5 +- MediaBrowser.Controller/Entities/UserView.cs | 27 ++++---- .../Entities/UserViewBuilder.cs | 46 +++++++------- MediaBrowser.Controller/Library/ILibraryManager.cs | 18 +++--- .../Library/IMediaSourceManager.cs | 7 ++- MediaBrowser.Controller/Library/IMusicManager.cs | 7 ++- .../Library/IUserDataManager.cs | 9 +-- .../Library/PlaybackProgressEventArgs.cs | 5 +- MediaBrowser.Controller/LiveTv/ILiveTvManager.cs | 13 ++-- .../MediaEncoding/EncodingJobInfo.cs | 3 +- MediaBrowser.Controller/Net/IAuthService.cs | 5 +- .../Notifications/INotificationService.cs | 4 +- MediaBrowser.Controller/Playlists/Playlist.cs | 17 ++--- .../Notifications/NotificationOptions.cs | 3 +- .../Auth/CustomAuthenticationHandlerTests.cs | 7 ++- 63 files changed, 415 insertions(+), 336 deletions(-) diff --git a/Emby.Dlna/ContentDirectory/ContentDirectory.cs b/Emby.Dlna/ContentDirectory/ContentDirectory.cs index ea577a905..b1ce7e8ec 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectory.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectory.cs @@ -4,12 +4,12 @@ using System; using System.Linq; using System.Threading.Tasks; using Emby.Dlna.Service; +using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.TV; @@ -33,7 +33,8 @@ namespace Emby.Dlna.ContentDirectory private readonly IMediaEncoder _mediaEncoder; private readonly ITVSeriesManager _tvSeriesManager; - public ContentDirectory(IDlnaManager dlna, + public ContentDirectory( + IDlnaManager dlna, IUserDataManager userDataManager, IImageProcessor imageProcessor, ILibraryManager libraryManager, @@ -106,7 +107,7 @@ namespace Emby.Dlna.ContentDirectory .ProcessControlRequestAsync(request); } - private Jellyfin.Data.Entities.User GetUser(DeviceProfile profile) + private User GetUser(DeviceProfile profile) { if (!string.IsNullOrEmpty(profile.UserId)) { diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 32bda2fe1..27585eafa 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Xml; using Emby.Dlna.Didl; using Emby.Dlna.Service; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Drawing; @@ -17,7 +18,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.LiveTv; using MediaBrowser.Controller.MediaEncoding; @@ -28,6 +28,12 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; +using Book = MediaBrowser.Controller.Entities.Book; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using Genre = MediaBrowser.Controller.Entities.Genre; +using Movie = MediaBrowser.Controller.Entities.Movies.Movie; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; +using Series = MediaBrowser.Controller.Entities.TV.Series; namespace Emby.Dlna.ContentDirectory { @@ -36,7 +42,7 @@ namespace Emby.Dlna.ContentDirectory private readonly ILibraryManager _libraryManager; private readonly IUserDataManager _userDataManager; private readonly IServerConfigurationManager _config; - private readonly Jellyfin.Data.Entities.User _user; + private readonly User _user; private readonly IUserViewManager _userViewManager; private readonly ITVSeriesManager _tvSeriesManager; @@ -59,7 +65,7 @@ namespace Emby.Dlna.ContentDirectory string accessToken, IImageProcessor imageProcessor, IUserDataManager userDataManager, - Jellyfin.Data.Entities.User user, + User user, int systemUpdateId, IServerConfigurationManager config, ILocalizationManager localization, @@ -432,7 +438,7 @@ namespace Emby.Dlna.ContentDirectory xmlWriter.WriteElementString("UpdateID", _systemUpdateId.ToString(CultureInfo.InvariantCulture)); } - private QueryResult GetChildrenSorted(BaseItem item, Jellyfin.Data.Entities.User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetChildrenSorted(BaseItem item, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit) { var folder = (Folder)item; @@ -489,7 +495,7 @@ namespace Emby.Dlna.ContentDirectory return new DtoOptions(true); } - private QueryResult GetUserItems(BaseItem item, StubType? stubType, Jellyfin.Data.Entities.User user, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetUserItems(BaseItem item, StubType? stubType, User user, SortCriteria sort, int? startIndex, int? limit) { if (item is MusicGenre) { @@ -558,7 +564,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(queryResult); } - private QueryResult GetLiveTvChannels(Jellyfin.Data.Entities.User user, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetLiveTvChannels(User user, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) { @@ -574,7 +580,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMusicFolders(BaseItem item, Jellyfin.Data.Entities.User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetMusicFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) { @@ -692,7 +698,7 @@ namespace Emby.Dlna.ContentDirectory }; } - private QueryResult GetMovieFolders(BaseItem item, Jellyfin.Data.Entities.User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetMovieFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) { @@ -766,7 +772,7 @@ namespace Emby.Dlna.ContentDirectory }; } - private QueryResult GetFolders(Jellyfin.Data.Entities.User user, int? startIndex, int? limit) + private QueryResult GetFolders(User user, int? startIndex, int? limit) { var folders = _libraryManager.GetUserRootFolder().GetChildren(user, true) .OrderBy(i => i.SortName) @@ -783,7 +789,7 @@ namespace Emby.Dlna.ContentDirectory }, startIndex, limit); } - private QueryResult GetTvFolders(BaseItem item, Jellyfin.Data.Entities.User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetTvFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) { @@ -871,7 +877,7 @@ namespace Emby.Dlna.ContentDirectory }; } - private QueryResult GetMovieContinueWatching(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetMovieContinueWatching(BaseItem parent, User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -891,7 +897,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetSeries(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetSeries(BaseItem parent, User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -904,7 +910,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMovieMovies(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetMovieMovies(BaseItem parent, User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -917,7 +923,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMovieCollections(Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetMovieCollections(User user, InternalItemsQuery query) { query.Recursive = true; //query.Parent = parent; @@ -930,7 +936,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMusicAlbums(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetMusicAlbums(BaseItem parent, User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -943,7 +949,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMusicSongs(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetMusicSongs(BaseItem parent, User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -956,7 +962,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetFavoriteSongs(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetFavoriteSongs(BaseItem parent, User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -969,7 +975,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetFavoriteSeries(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetFavoriteSeries(BaseItem parent, User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -982,7 +988,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetFavoriteEpisodes(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetFavoriteEpisodes(BaseItem parent, User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -995,7 +1001,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMovieFavorites(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetMovieFavorites(BaseItem parent, User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -1008,7 +1014,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetFavoriteAlbums(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetFavoriteAlbums(BaseItem parent, User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -1021,7 +1027,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetGenres(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetGenres(BaseItem parent, User user, InternalItemsQuery query) { var genresResult = _libraryManager.GetGenres(new InternalItemsQuery(user) { @@ -1039,7 +1045,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMusicGenres(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetMusicGenres(BaseItem parent, User user, InternalItemsQuery query) { var genresResult = _libraryManager.GetMusicGenres(new InternalItemsQuery(user) { @@ -1057,7 +1063,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMusicAlbumArtists(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetMusicAlbumArtists(BaseItem parent, User user, InternalItemsQuery query) { var artists = _libraryManager.GetAlbumArtists(new InternalItemsQuery(user) { @@ -1075,7 +1081,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMusicArtists(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetMusicArtists(BaseItem parent, User user, InternalItemsQuery query) { var artists = _libraryManager.GetArtists(new InternalItemsQuery(user) { @@ -1093,7 +1099,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetFavoriteArtists(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetFavoriteArtists(BaseItem parent, User user, InternalItemsQuery query) { var artists = _libraryManager.GetArtists(new InternalItemsQuery(user) { @@ -1112,7 +1118,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMusicPlaylists(Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetMusicPlaylists(User user, InternalItemsQuery query) { query.Parent = null; query.IncludeItemTypes = new[] { nameof(Playlist) }; @@ -1124,7 +1130,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMusicLatest(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetMusicLatest(BaseItem parent, User user, InternalItemsQuery query) { query.OrderBy = Array.Empty<(string, SortOrder)>(); @@ -1154,7 +1160,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetTvLatest(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetTvLatest(BaseItem parent, User user, InternalItemsQuery query) { query.OrderBy = Array.Empty<(string, SortOrder)>(); @@ -1170,7 +1176,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(items); } - private QueryResult GetMovieLatest(BaseItem parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetMovieLatest(BaseItem parent, User user, InternalItemsQuery query) { query.OrderBy = Array.Empty<(string, SortOrder)>(); @@ -1187,7 +1193,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(items); } - private QueryResult GetMusicArtistItems(BaseItem item, Guid parentId, Jellyfin.Data.Entities.User user, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetMusicArtistItems(BaseItem item, Guid parentId, User user, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) { @@ -1207,7 +1213,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetGenreItems(BaseItem item, Guid parentId, Jellyfin.Data.Entities.User user, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetGenreItems(BaseItem item, Guid parentId, User user, SortCriteria sort, int? startIndex, int? limit) { var query = new InternalItemsQuery(user) { @@ -1231,7 +1237,7 @@ namespace Emby.Dlna.ContentDirectory return ToResult(result); } - private QueryResult GetMusicGenreItems(BaseItem item, Guid parentId, Jellyfin.Data.Entities.User user, SortCriteria sort, int? startIndex, int? limit) + private QueryResult GetMusicGenreItems(BaseItem item, Guid parentId, 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 24932ced9..6cedb3ef0 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -7,12 +7,12 @@ using System.Linq; using System.Text; using System.Xml; using Emby.Dlna.ContentDirectory; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Drawing; 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.MediaEncoding; using MediaBrowser.Controller.Playlists; @@ -22,6 +22,13 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Net; using Microsoft.Extensions.Logging; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using Genre = MediaBrowser.Controller.Entities.Genre; +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; +using XmlAttribute = MediaBrowser.Model.Dlna.XmlAttribute; namespace Emby.Dlna.Didl { @@ -38,7 +45,7 @@ namespace Emby.Dlna.Didl private readonly IImageProcessor _imageProcessor; private readonly string _serverAddress; private readonly string _accessToken; - private readonly Jellyfin.Data.Entities.User _user; + private readonly User _user; private readonly IUserDataManager _userDataManager; private readonly ILocalizationManager _localization; private readonly IMediaSourceManager _mediaSourceManager; @@ -48,7 +55,7 @@ namespace Emby.Dlna.Didl public DidlBuilder( DeviceProfile profile, - Jellyfin.Data.Entities.User user, + User user, IImageProcessor imageProcessor, string serverAddress, string accessToken, @@ -77,7 +84,7 @@ namespace Emby.Dlna.Didl return url + "&dlnaheaders=true"; } - public string GetItemDidl(BaseItem item, Jellyfin.Data.Entities.User user, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo) + public string GetItemDidl(BaseItem item, User user, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo) { var settings = new XmlWriterSettings { @@ -131,7 +138,7 @@ namespace Emby.Dlna.Didl public void WriteItemElement( XmlWriter writer, BaseItem item, - Jellyfin.Data.Entities.User user, + User user, BaseItem context, StubType? contextStubType, string deviceId, @@ -420,7 +427,6 @@ namespace Emby.Dlna.Didl case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows"); case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes"); case StubType.Series: return _localization.GetLocalizedString("Shows"); - default: break; } } @@ -662,14 +668,14 @@ namespace Emby.Dlna.Didl writer.WriteFullEndElement(); } - private void AddSamsungBookmarkInfo(BaseItem item, Jellyfin.Data.Entities.User user, XmlWriter writer, StreamInfo streamInfo) + private void AddSamsungBookmarkInfo(BaseItem item, User user, XmlWriter writer, StreamInfo streamInfo) { if (!item.SupportsPositionTicksResume || item is Folder) { return; } - MediaBrowser.Model.Dlna.XmlAttribute secAttribute = null; + XmlAttribute secAttribute = null; foreach (var attribute in _profile.XmlRootAttributes) { if (string.Equals(attribute.Name, "xmlns:sec", StringComparison.OrdinalIgnoreCase)) @@ -994,7 +1000,6 @@ namespace Emby.Dlna.Didl } AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN"); - } private void AddImageResElement( diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index db786833f..1c38fca16 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Emby.Dlna.Didl; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; @@ -22,6 +23,7 @@ using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Session; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; +using Photo = MediaBrowser.Controller.Entities.Photo; namespace Emby.Dlna.PlayTo { @@ -443,7 +445,7 @@ namespace Emby.Dlna.PlayTo private PlaylistItem CreatePlaylistItem( BaseItem item, - Jellyfin.Data.Entities.User user, + User user, long startPostionTicks, string mediaSourceId, int? audioStreamIndex, diff --git a/Emby.Notifications/NotificationManager.cs b/Emby.Notifications/NotificationManager.cs index 9a9bc4415..2dc4fd929 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.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; @@ -82,7 +83,7 @@ namespace Emby.Notifications private Task SendNotification( NotificationRequest request, INotificationService service, - IEnumerable users, + IEnumerable users, string title, string description, CancellationToken cancellationToken) @@ -130,7 +131,7 @@ namespace Emby.Notifications INotificationService service, string title, string description, - Jellyfin.Data.Entities.User user, + User user, CancellationToken cancellationToken) { var notification = new UserNotification @@ -155,7 +156,7 @@ namespace Emby.Notifications } } - private bool IsEnabledForUser(INotificationService service, Jellyfin.Data.Entities.User user) + private bool IsEnabledForUser(INotificationService service, User user) { try { diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index cb320dcb1..d6d47d63a 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Channels; @@ -13,8 +14,6 @@ using MediaBrowser.Controller.Configuration; 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.Model.Channels; @@ -24,6 +23,11 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; +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 Emby.Server.Implementations.Channels { @@ -793,7 +797,7 @@ namespace Emby.Server.Implementations.Channels private async Task GetChannelItems( IChannel channel, - Jellyfin.Data.Entities.User user, + 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 61963b633..1b33c9fac 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Configuration; @@ -121,7 +122,7 @@ namespace Emby.Server.Implementations.Collections return EnsureLibraryFolder(GetCollectionsFolderPath(), createIfNeeded); } - private IEnumerable GetCollections(Jellyfin.Data.Entities.User user) + private IEnumerable GetCollections(User user) { var folder = GetCollectionsFolder(false).Result; @@ -325,7 +326,7 @@ namespace Emby.Server.Implementations.Collections } /// - public IEnumerable CollapseItemsWithinBoxSets(IEnumerable items, Jellyfin.Data.Entities.User user) + public IEnumerable CollapseItemsWithinBoxSets(IEnumerable items, User user) { var results = new Dictionary(); diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index da40bca4c..aeb5b993b 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.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Common; using MediaBrowser.Controller.Channels; @@ -13,8 +14,6 @@ using MediaBrowser.Controller.Drawing; 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.LiveTv; using MediaBrowser.Controller.Persistence; @@ -25,6 +24,14 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; +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 Person = MediaBrowser.Controller.Entities.Person; +using Photo = MediaBrowser.Controller.Entities.Photo; +using Season = MediaBrowser.Controller.Entities.TV.Season; +using Series = MediaBrowser.Controller.Entities.TV.Series; namespace Emby.Server.Implementations.Dto { @@ -75,7 +82,7 @@ namespace Emby.Server.Implementations.Dto /// The owner. /// Task{DtoBaseItem}. /// item - public BaseItemDto GetBaseItemDto(BaseItem item, ItemFields[] fields, Jellyfin.Data.Entities.User user = null, BaseItem owner = null) + public BaseItemDto GetBaseItemDto(BaseItem item, ItemFields[] fields, User user = null, BaseItem owner = null) { var options = new DtoOptions { @@ -86,7 +93,7 @@ namespace Emby.Server.Implementations.Dto } /// - public IReadOnlyList GetBaseItemDtos(IReadOnlyList items, DtoOptions options, Jellyfin.Data.Entities.User user = null, BaseItem owner = null) + public IReadOnlyList GetBaseItemDtos(IReadOnlyList items, DtoOptions options, User user = null, BaseItem owner = null) { var returnItems = new BaseItemDto[items.Count]; var programTuples = new List<(BaseItem, BaseItemDto)>(); @@ -139,7 +146,7 @@ namespace Emby.Server.Implementations.Dto return returnItems; } - public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, Jellyfin.Data.Entities.User user = null, BaseItem owner = null) + public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null) { var dto = GetBaseItemDtoInternal(item, options, user, owner); if (item is LiveTvChannel tvChannel) @@ -173,7 +180,7 @@ namespace Emby.Server.Implementations.Dto return dto; } - private static IList GetTaggedItems(IItemByName byName, Jellyfin.Data.Entities.User user, DtoOptions options) + private static IList GetTaggedItems(IItemByName byName, User user, DtoOptions options) { return byName.GetTaggedItems( new InternalItemsQuery(user) @@ -183,7 +190,7 @@ namespace Emby.Server.Implementations.Dto }); } - private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, Jellyfin.Data.Entities.User user = null, BaseItem owner = null) + private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null) { var dto = new BaseItemDto { @@ -316,7 +323,7 @@ namespace Emby.Server.Implementations.Dto } } - public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List taggedItems, Jellyfin.Data.Entities.User user = null) + public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List taggedItems, User user = null) { var dto = GetBaseItemDtoInternal(item, options, user); @@ -328,7 +335,7 @@ namespace Emby.Server.Implementations.Dto return dto; } - private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IList taggedItems, Jellyfin.Data.Entities.User user = null) + private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IList taggedItems, User user = null) { if (item is MusicArtist) { @@ -364,7 +371,7 @@ namespace Emby.Server.Implementations.Dto /// /// Attaches the user specific info. /// - private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, Jellyfin.Data.Entities.User user, DtoOptions options) + private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, DtoOptions options) { if (item.IsFolder) { @@ -423,7 +430,7 @@ namespace Emby.Server.Implementations.Dto } } - private static int GetChildCount(Folder folder, Jellyfin.Data.Entities.User user) + private static int GetChildCount(Folder folder, 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 7ece52cad..5c83e6ae9 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -446,7 +447,7 @@ namespace Emby.Server.Implementations.EntryPoints /// The user. /// if set to true [include if not found]. /// IEnumerable{``0}. - private IEnumerable TranslatePhysicalItemToUserLibrary(T item, Jellyfin.Data.Entities.User user, bool includeIfNotFound = false) + private IEnumerable TranslatePhysicalItemToUserLibrary(T item, User user, bool includeIfNotFound = false) where T : BaseItem { // If the physical root changed, return the user root diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index 3f8a64f99..03fcfa53d 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -1,7 +1,7 @@ #pragma warning disable CS1591 using System; -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; @@ -42,14 +42,14 @@ namespace Emby.Server.Implementations.HttpServer.Security return GetSession((IRequest)requestContext); } - public Jellyfin.Data.Entities.User GetUser(IRequest requestContext) + public User GetUser(IRequest requestContext) { var session = GetSession(requestContext); return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId); } - public Jellyfin.Data.Entities.User GetUser(object requestContext) + public User GetUser(object requestContext) { return GetUser((IRequest)requestContext); } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index e7cd7512c..ebba4fb9d 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.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; @@ -25,7 +26,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; @@ -45,6 +45,9 @@ using MediaBrowser.Model.Querying; using MediaBrowser.Model.Tasks; using MediaBrowser.Providers.MediaInfo; using Microsoft.Extensions.Logging; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using Genre = MediaBrowser.Controller.Entities.Genre; +using Person = MediaBrowser.Controller.Entities.Person; using SortOrder = MediaBrowser.Model.Entities.SortOrder; using VideoResolver = Emby.Naming.Video.VideoResolver; @@ -1471,7 +1474,7 @@ namespace Emby.Server.Implementations.Library query.Parent = null; } - private void AddUserToQuery(InternalItemsQuery query, Jellyfin.Data.Entities.User user, bool allowExternalContent = true) + private void AddUserToQuery(InternalItemsQuery query, User user, bool allowExternalContent = true) { if (query.AncestorIds.Length == 0 && query.ParentId.Equals(Guid.Empty) && @@ -1492,7 +1495,7 @@ namespace Emby.Server.Implementations.Library } } - private IEnumerable GetTopParentIdsForQuery(BaseItem item, Jellyfin.Data.Entities.User user) + private IEnumerable GetTopParentIdsForQuery(BaseItem item, User user) { if (item is UserView view) { @@ -1559,7 +1562,7 @@ namespace Emby.Server.Implementations.Library /// The item. /// The user. /// IEnumerable{System.String}. - public async Task> GetIntros(BaseItem item, Jellyfin.Data.Entities.User user) + public async Task> GetIntros(BaseItem item, User user) { var tasks = IntroProviders .OrderBy(i => i.GetType().Name.Contains("Default", StringComparison.OrdinalIgnoreCase) ? 1 : 0) @@ -1581,7 +1584,7 @@ namespace Emby.Server.Implementations.Library /// The item. /// The user. /// Task<IEnumerable<IntroInfo>>. - private async Task> GetIntros(IIntroProvider provider, BaseItem item, Jellyfin.Data.Entities.User user) + private async Task> GetIntros(IIntroProvider provider, BaseItem item, User user) { try { @@ -1682,7 +1685,7 @@ namespace Emby.Server.Implementations.Library /// The sort by. /// The sort order. /// IEnumerable{BaseItem}. - public IEnumerable Sort(IEnumerable items, Jellyfin.Data.Entities.User user, IEnumerable sortBy, SortOrder sortOrder) + public IEnumerable Sort(IEnumerable items, User user, IEnumerable sortBy, SortOrder sortOrder) { var isFirst = true; @@ -1705,7 +1708,7 @@ namespace Emby.Server.Implementations.Library return orderedItems ?? items; } - public IEnumerable Sort(IEnumerable items, Jellyfin.Data.Entities.User user, IEnumerable> orderByList) + public IEnumerable Sort(IEnumerable items, User user, IEnumerable> orderByList) { var isFirst = true; @@ -1742,7 +1745,7 @@ namespace Emby.Server.Implementations.Library /// The name. /// The user. /// IBaseItemComparer. - private IBaseItemComparer GetComparer(string name, Jellyfin.Data.Entities.User user) + private IBaseItemComparer GetComparer(string name, User user) { var comparer = Comparers.FirstOrDefault(c => string.Equals(name, c.Name, StringComparison.OrdinalIgnoreCase)); @@ -2074,7 +2077,7 @@ namespace Emby.Server.Implementations.Library private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24); public UserView GetNamedView( - Jellyfin.Data.Entities.User user, + User user, string name, string viewType, string sortName) @@ -2127,7 +2130,7 @@ namespace Emby.Server.Implementations.Library } public UserView GetNamedView( - Jellyfin.Data.Entities.User user, + 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 25af69058..b063db630 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.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; @@ -145,7 +146,7 @@ namespace Emby.Server.Implementations.Library }); } - public async Task> GetPlaybackMediaSources(BaseItem item, Jellyfin.Data.Entities.User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken) + public async Task> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken) { var mediaSources = GetStaticMediaSources(item, enablePathSubstitution, user); @@ -309,7 +310,7 @@ namespace Emby.Server.Implementations.Library return sources.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase)); } - public List GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, Jellyfin.Data.Entities.User user = null) + public List GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, User user = null) { if (item == null) { @@ -347,7 +348,7 @@ namespace Emby.Server.Implementations.Library return new string[] { language }; } - private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, Jellyfin.Data.Entities.User user, bool allowRememberingSelection) + private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection) { if (userData.SubtitleStreamIndex.HasValue && user.RememberSubtitleSelections @@ -380,7 +381,7 @@ namespace Emby.Server.Implementations.Library MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLangage); } - private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, Jellyfin.Data.Entities.User user, bool allowRememberingSelection) + private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection) { if (userData.AudioStreamIndex.HasValue && user.RememberAudioSelections && allowRememberingSelection) { @@ -400,7 +401,7 @@ namespace Emby.Server.Implementations.Library source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.PlayDefaultAudioTrack); } - public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, Jellyfin.Data.Entities.User user) + public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, 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; @@ -538,7 +539,7 @@ namespace Emby.Server.Implementations.Library mediaSource.RunTimeTicks = null; } - var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Audio); + var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); if (audioStream == null || audioStream.Index == -1) { @@ -549,7 +550,7 @@ namespace Emby.Server.Implementations.Library mediaSource.DefaultAudioStreamIndex = audioStream.Index; } - var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Video); + var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); if (videoStream != null) { if (!videoStream.BitRate.HasValue) diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index ad8c70f5e..0bdc59914 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -10,6 +11,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; namespace Emby.Server.Implementations.Library { @@ -22,7 +24,7 @@ namespace Emby.Server.Implementations.Library _libraryManager = libraryManager; } - public List GetInstantMixFromSong(Audio item, Jellyfin.Data.Entities.User user, DtoOptions dtoOptions) + public List GetInstantMixFromSong(Audio item, User user, DtoOptions dtoOptions) { var list = new List /// The user. - public Jellyfin.Data.Entities.User User { get; set; } + public 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 57a1a00d9..5e527ea25 100644 --- a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs @@ -1,4 +1,5 @@ using System; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; @@ -15,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting /// Gets or sets the user. /// /// The user. - public Jellyfin.Data.Entities.User User { get; set; } + public 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 c9feca7e3..8ae0a613b 100644 --- a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs @@ -1,3 +1,4 @@ +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; @@ -11,7 +12,7 @@ namespace Emby.Server.Implementations.Sorting /// Gets or sets the user. /// /// The user. - public Jellyfin.Data.Entities.User User { get; set; } + public 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 6f383e65f..3b3c04922 100644 --- a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs @@ -1,3 +1,4 @@ +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; @@ -11,7 +12,7 @@ namespace Emby.Server.Implementations.Sorting /// Gets or sets the user. /// /// The user. - public Jellyfin.Data.Entities.User User { get; set; } + public 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 4845fdc0d..5719356c7 100644 --- a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs @@ -1,3 +1,4 @@ +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; @@ -11,7 +12,7 @@ namespace Emby.Server.Implementations.Sorting /// Gets or sets the user. /// /// The user. - public Jellyfin.Data.Entities.User User { get; set; } + public 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 99846db61..afbaaf6ab 100644 --- a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs +++ b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs @@ -1,3 +1,4 @@ +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; @@ -14,7 +15,7 @@ namespace Emby.Server.Implementations.Sorting /// Gets or sets the user. /// /// The user. - public Jellyfin.Data.Entities.User User { get; set; } + public 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 905a1ea99..570de49db 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -2,15 +2,17 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using Series = MediaBrowser.Controller.Entities.TV.Series; namespace Emby.Server.Implementations.TV { @@ -139,7 +141,7 @@ namespace Emby.Server.Implementations.TV return GetResult(episodes, request); } - public IEnumerable GetNextUpEpisodes(NextUpQuery request, Jellyfin.Data.Entities.User user, IEnumerable seriesKeys, DtoOptions dtoOptions) + public IEnumerable GetNextUpEpisodes(NextUpQuery request, User user, IEnumerable seriesKeys, DtoOptions dtoOptions) { // Avoid implicitly captured closure var currentUser = user; @@ -188,7 +190,7 @@ namespace Emby.Server.Implementations.TV /// Gets the next up. /// /// Task{Episode}. - private Tuple> GetNextUp(string seriesKey, Jellyfin.Data.Entities.User user, DtoOptions dtoOptions) + private Tuple> GetNextUp(string seriesKey, User user, DtoOptions dtoOptions) { var lastWatchedEpisode = _libraryManager.GetItemList(new InternalItemsQuery(user) { diff --git a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs index 38494727c..df730731a 100644 --- a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using System.Text; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Common; using MediaBrowser.Common.Cryptography; using MediaBrowser.Controller.Authentication; @@ -42,7 +43,7 @@ namespace Jellyfin.Server.Implementations.Users /// // 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) + public Task Authenticate(string username, string password, User resolvedUser) { if (resolvedUser == null) { @@ -93,11 +94,11 @@ namespace Jellyfin.Server.Implementations.Users } /// - public bool HasPassword(Data.Entities.User user) + public bool HasPassword(User user) => !string.IsNullOrEmpty(user.Password); /// - public Task ChangePassword(Data.Entities.User user, string newPassword) + public Task ChangePassword(User user, string newPassword) { if (string.IsNullOrEmpty(newPassword)) { @@ -112,7 +113,7 @@ namespace Jellyfin.Server.Implementations.Users } /// - public void ChangeEasyPassword(Data.Entities.User user, string newPassword, string newPasswordHash) + public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) { if (newPassword != null) { @@ -128,7 +129,7 @@ namespace Jellyfin.Server.Implementations.Users } /// - public string GetEasyPasswordHash(Data.Entities.User user) + public string GetEasyPasswordHash(User user) { return string.IsNullOrEmpty(user.EasyPassword) ? null @@ -141,7 +142,7 @@ namespace Jellyfin.Server.Implementations.Users /// The user. /// The string to hash. /// The hashed string. - public string GetHashedString(Data.Entities.User user, string str) + public string GetHashedString(User user, string str) { if (string.IsNullOrEmpty(user.Password)) { @@ -167,7 +168,7 @@ namespace Jellyfin.Server.Implementations.Users /// The user. /// The string to hash. /// The hashed string. - public ReadOnlySpan GetHashed(Data.Entities.User user, string str) + public ReadOnlySpan GetHashed(User user, string str) { if (string.IsNullOrEmpty(user.Password)) { diff --git a/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs b/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs index e430808bf..b6e65b559 100644 --- a/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs +++ b/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Authentication; namespace Jellyfin.Server.Implementations.Users @@ -21,25 +22,25 @@ namespace Jellyfin.Server.Implementations.Users } /// - public bool HasPassword(Data.Entities.User user) + public bool HasPassword(User user) { return true; } /// - public Task ChangePassword(Data.Entities.User user, string newPassword) + public Task ChangePassword(User user, string newPassword) { return Task.CompletedTask; } /// - public void ChangeEasyPassword(Data.Entities.User user, string newPassword, string newPasswordHash) + public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) { // Nothing here } /// - public string GetEasyPasswordHash(Data.Entities.User user) + public string GetEasyPasswordHash(User user) { return string.Empty; } diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs index ac61cd491..bd67ec41f 100644 --- a/MediaBrowser.Api/FilterService.cs +++ b/MediaBrowser.Api/FilterService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -220,7 +221,7 @@ namespace MediaBrowser.Api return result; } - private InternalItemsQuery GetItemsQuery(GetQueryFiltersLegacy request, Jellyfin.Data.Entities.User user) + private InternalItemsQuery GetItemsQuery(GetQueryFiltersLegacy request, User user) { var query = new InternalItemsQuery { diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 50977c48f..3f7fb36e5 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -6,6 +6,7 @@ using System.Net; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Api.Movies; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; @@ -14,7 +15,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.Net; using MediaBrowser.Controller.Providers; @@ -27,6 +27,11 @@ using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; +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 Series = MediaBrowser.Controller.Entities.TV.Series; namespace MediaBrowser.Api.Library { @@ -759,11 +764,11 @@ namespace MediaBrowser.Api.Library }); } - private void LogDownload(BaseItem item, Jellyfin.Data.Entities.User user, AuthorizationInfo auth) + private void LogDownload(BaseItem item, User user, AuthorizationInfo auth) { try { - _activityManager.Create(new Jellyfin.Data.Entities.ActivityLog( + _activityManager.Create(new ActivityLog( string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Username, item.Name), "UserDownloadingContent", auth.UserId) @@ -842,7 +847,7 @@ namespace MediaBrowser.Api.Library return baseItemDtos; } - private BaseItem TranslateParentItem(BaseItem item, Jellyfin.Data.Entities.User user) + private BaseItem TranslateParentItem(BaseItem item, User user) { return item.GetParent() is AggregateFolder ? _libraryManager.GetUserRootFolder().GetChildren(user, true) @@ -884,7 +889,7 @@ namespace MediaBrowser.Api.Library return ToOptimizedResult(counts); } - private int GetCount(Type type, Jellyfin.Data.Entities.User user, GetItemCounts request) + private int GetCount(Type type, User user, GetItemCounts request) { var query = new InternalItemsQuery(user) { diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index aaabe07f3..a5a6cd633 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -2,11 +2,11 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Net; @@ -15,6 +15,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; +using Movie = MediaBrowser.Controller.Entities.Movies.Movie; namespace MediaBrowser.Api.Movies { @@ -148,12 +149,7 @@ namespace MediaBrowser.Api.Movies return result; } - private IEnumerable GetRecommendationCategories( - Jellyfin.Data.Entities.User user, - string parentId, - int categoryLimit, - int itemLimit, - DtoOptions dtoOptions) + private IEnumerable GetRecommendationCategories(User user, string parentId, int categoryLimit, int itemLimit, DtoOptions dtoOptions) { var categories = new List(); @@ -257,7 +253,7 @@ namespace MediaBrowser.Api.Movies } private IEnumerable GetWithDirector( - Jellyfin.Data.Entities.User user, + User user, IEnumerable names, int itemLimit, DtoOptions dtoOptions, @@ -303,12 +299,7 @@ namespace MediaBrowser.Api.Movies } } - private IEnumerable GetWithActor( - Jellyfin.Data.Entities.User user, - IEnumerable names, - int itemLimit, - DtoOptions dtoOptions, - RecommendationType type) + private IEnumerable GetWithActor(User user, IEnumerable names, int itemLimit, DtoOptions dtoOptions, RecommendationType type) { var itemTypes = new List { typeof(Movie).Name }; if (ServerConfigurationManager.Configuration.EnableExternalContentInSuggestions) @@ -349,12 +340,7 @@ namespace MediaBrowser.Api.Movies } } - private IEnumerable GetSimilarTo( - Jellyfin.Data.Entities.User user, - List baselineItems, - int itemLimit, - DtoOptions dtoOptions, - RecommendationType type) + private IEnumerable GetSimilarTo(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 e00b06e38..7d10c9427 100644 --- a/MediaBrowser.Api/Music/InstantMixService.cs +++ b/MediaBrowser.Api/Music/InstantMixService.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -171,7 +172,7 @@ namespace MediaBrowser.Api.Music return GetResult(items, user, request, dtoOptions); } - private object GetResult(List items, Jellyfin.Data.Entities.User user, BaseGetSimilarItems request, DtoOptions dtoOptions) + private object GetResult(List items, User user, BaseGetSimilarItems request, DtoOptions dtoOptions) { var list = items; diff --git a/MediaBrowser.Api/SuggestionsService.cs b/MediaBrowser.Api/SuggestionsService.cs index 51fa1eb71..32d3bde5c 100644 --- a/MediaBrowser.Api/SuggestionsService.cs +++ b/MediaBrowser.Api/SuggestionsService.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -78,7 +79,7 @@ namespace MediaBrowser.Api }; } - private QueryResult GetItems(GetSuggestedItems request, Jellyfin.Data.Entities.User user, DtoOptions dtoOptions) + private QueryResult GetItems(GetSuggestedItems request, User user, DtoOptions dtoOptions) { return _libraryManager.GetItemsResult(new InternalItemsQuery(user) { diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 11d984a9a..a1ec08467 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -94,7 +95,7 @@ namespace MediaBrowser.Api.UserLibrary { var dtoOptions = GetDtoOptions(AuthorizationContext, request); - Jellyfin.Data.Entities.User user = null; + User user = null; BaseItem parentItem; if (!request.UserId.Equals(Guid.Empty)) @@ -246,7 +247,7 @@ namespace MediaBrowser.Api.UserLibrary { var dtoOptions = GetDtoOptions(AuthorizationContext, request); - Jellyfin.Data.Entities.User user = null; + 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 ca9f7aa82..49d534c36 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -2,11 +2,11 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dto; @@ -15,6 +15,7 @@ using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; namespace MediaBrowser.Api.UserLibrary { @@ -180,7 +181,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// Gets the items to serialize. /// - private QueryResult GetQueryResult(GetItems request, DtoOptions dtoOptions, Jellyfin.Data.Entities.User user) + private QueryResult GetQueryResult(GetItems request, DtoOptions dtoOptions, User user) { if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase) || string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase)) @@ -255,7 +256,7 @@ namespace MediaBrowser.Api.UserLibrary }; } - private InternalItemsQuery GetItemsQuery(GetItems request, DtoOptions dtoOptions, Jellyfin.Data.Entities.User user) + private InternalItemsQuery GetItemsQuery(GetItems request, DtoOptions dtoOptions, User user) { var query = new InternalItemsQuery(user) { diff --git a/MediaBrowser.Api/UserLibrary/PlaystateService.cs b/MediaBrowser.Api/UserLibrary/PlaystateService.cs index fb8bda190..ab231626b 100644 --- a/MediaBrowser.Api/UserLibrary/PlaystateService.cs +++ b/MediaBrowser.Api/UserLibrary/PlaystateService.cs @@ -1,8 +1,8 @@ using System; using System.Globalization; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; @@ -437,7 +437,7 @@ namespace MediaBrowser.Api.UserLibrary /// if set to true [was played]. /// The date played. /// Task. - private UserItemDataDto UpdatePlayedStatus(Jellyfin.Data.Entities.User user, string itemId, bool wasPlayed, DateTime? datePlayed) + private UserItemDataDto UpdatePlayedStatus(User user, string itemId, bool wasPlayed, DateTime? datePlayed) { var item = _libraryManager.GetItemById(itemId); diff --git a/MediaBrowser.Controller/Channels/Channel.cs b/MediaBrowser.Controller/Channels/Channel.cs index d6a1fc84e..dbb047804 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.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Entities; @@ -12,7 +13,7 @@ namespace MediaBrowser.Controller.Channels { public class Channel : Folder { - public override bool IsVisible(Jellyfin.Data.Entities.User user) + public override bool IsVisible(User user) { if (user.GetPreference(PreferenceKind.BlockedChannels) != null) { @@ -77,7 +78,7 @@ namespace MediaBrowser.Controller.Channels return false; } - internal static bool IsChannelVisible(BaseItem channelItem, Jellyfin.Data.Entities.User user) + internal static bool IsChannelVisible(BaseItem channelItem, User user) { var channel = ChannelManager.GetChannel(channelItem.ChannelId.ToString("")); diff --git a/MediaBrowser.Controller/Collections/ICollectionManager.cs b/MediaBrowser.Controller/Collections/ICollectionManager.cs index f51c73bd7..701423c0f 100644 --- a/MediaBrowser.Controller/Collections/ICollectionManager.cs +++ b/MediaBrowser.Controller/Collections/ICollectionManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; @@ -51,6 +52,6 @@ namespace MediaBrowser.Controller.Collections /// The items. /// The user. /// IEnumerable{BaseItem}. - IEnumerable CollapseItemsWithinBoxSets(IEnumerable items, Jellyfin.Data.Entities.User user); + IEnumerable CollapseItemsWithinBoxSets(IEnumerable items, User user); } } diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index bdb12402a..c00d1c335 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Entities; @@ -50,7 +51,7 @@ namespace MediaBrowser.Controller.Drawing string GetImageCacheTag(BaseItem item, ChapterInfo info); - string GetImageCacheTag(Jellyfin.Data.Entities.User user); + string GetImageCacheTag(User user); /// /// Processes the image. diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index 5ac4f05c0..56e6c47c4 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; @@ -38,7 +39,7 @@ namespace MediaBrowser.Controller.Dto /// The fields. /// The user. /// The owner. - BaseItemDto GetBaseItemDto(BaseItem item, ItemFields[] fields, Jellyfin.Data.Entities.User user = null, BaseItem owner = null); + BaseItemDto GetBaseItemDto(BaseItem item, ItemFields[] fields, User user = null, BaseItem owner = null); /// /// Gets the base item dto. @@ -48,7 +49,7 @@ namespace MediaBrowser.Controller.Dto /// The user. /// The owner. /// BaseItemDto. - BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, Jellyfin.Data.Entities.User user = null, BaseItem owner = null); + BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null); /// /// Gets the base item dtos. @@ -57,11 +58,11 @@ namespace MediaBrowser.Controller.Dto /// The options. /// The user. /// The owner. - IReadOnlyList GetBaseItemDtos(IReadOnlyList items, DtoOptions options, Jellyfin.Data.Entities.User user = null, BaseItem owner = null); + IReadOnlyList GetBaseItemDtos(IReadOnlyList items, DtoOptions options, User user = null, BaseItem owner = null); /// /// Gets the item by name dto. /// - BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List taggedItems, Jellyfin.Data.Entities.User user = null); + BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List taggedItems, User user = null); } } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index fbadeafad..15b580896 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; @@ -79,7 +80,7 @@ namespace MediaBrowser.Controller.Entities.Audio [JsonIgnore] public IEnumerable /// The user. /// PlayAccess. - public PlayAccess GetPlayAccess(Jellyfin.Data.Entities.User user) + public PlayAccess GetPlayAccess(User user) { if (!user.HasPermission(PermissionKind.EnableMediaPlayback)) { @@ -1214,11 +1215,11 @@ namespace MediaBrowser.Controller.Entities { if (video.IsoType.HasValue) { - if (video.IsoType.Value == Model.Entities.IsoType.BluRay) + if (video.IsoType.Value == IsoType.BluRay) { terms.Add("Bluray"); } - else if (video.IsoType.Value == Model.Entities.IsoType.Dvd) + else if (video.IsoType.Value == IsoType.Dvd) { terms.Add("DVD"); } @@ -1761,7 +1762,7 @@ namespace MediaBrowser.Controller.Entities /// The user. /// true if [is parental allowed] [the specified user]; otherwise, false. /// user - public bool IsParentalAllowed(Jellyfin.Data.Entities.User user) + public bool IsParentalAllowed(User user) { if (user == null) { @@ -1857,7 +1858,7 @@ namespace MediaBrowser.Controller.Entities return list.Distinct(StringComparer.OrdinalIgnoreCase).ToList(); } - private bool IsVisibleViaTags(Jellyfin.Data.Entities.User user) + private bool IsVisibleViaTags(User user) { if (user.GetPreference(PreferenceKind.BlockedTags).Any(i => Tags.Contains(i, StringComparer.OrdinalIgnoreCase))) { @@ -1887,7 +1888,7 @@ namespace MediaBrowser.Controller.Entities /// /// The configuration. /// true if XXXX, false otherwise. - protected virtual bool GetBlockUnratedValue(Jellyfin.Data.Entities.User user) + protected virtual bool GetBlockUnratedValue(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. @@ -1906,7 +1907,7 @@ namespace MediaBrowser.Controller.Entities /// The user. /// true if the specified user is visible; otherwise, false. /// user - public virtual bool IsVisible(Jellyfin.Data.Entities.User user) + public virtual bool IsVisible(User user) { if (user == null) { @@ -1916,7 +1917,7 @@ namespace MediaBrowser.Controller.Entities return IsParentalAllowed(user); } - public virtual bool IsVisibleStandalone(Jellyfin.Data.Entities.User user) + public virtual bool IsVisibleStandalone(User user) { if (SourceType == SourceType.Channel) { @@ -1929,7 +1930,7 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public virtual bool SupportsInheritedParentImages => false; - protected bool IsVisibleStandaloneInternal(Jellyfin.Data.Entities.User user, bool checkFolders) + protected bool IsVisibleStandaloneInternal(User user, bool checkFolders) { if (!IsVisible(user)) { @@ -2127,7 +2128,7 @@ namespace MediaBrowser.Controller.Entities /// Task. /// public virtual void MarkPlayed( - Jellyfin.Data.Entities.User user, + User user, DateTime? datePlayed, bool resetPosition) { @@ -2164,7 +2165,7 @@ namespace MediaBrowser.Controller.Entities /// The user. /// Task. /// - public virtual void MarkUnplayed(Jellyfin.Data.Entities.User user) + public virtual void MarkUnplayed(User user) { if (user == null) { @@ -2540,21 +2541,21 @@ namespace MediaBrowser.Controller.Entities UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None); } - public virtual bool IsPlayed(Jellyfin.Data.Entities.User user) + public virtual bool IsPlayed(User user) { var userdata = UserDataManager.GetUserData(user, this); return userdata != null && userdata.Played; } - public bool IsFavoriteOrLiked(Jellyfin.Data.Entities.User user) + public bool IsFavoriteOrLiked(User user) { var userdata = UserDataManager.GetUserData(user, this); return userdata != null && (userdata.IsFavorite || (userdata.Likes ?? false)); } - public virtual bool IsUnplayed(Jellyfin.Data.Entities.User user) + public virtual bool IsUnplayed(User user) { if (user == null) { @@ -2620,7 +2621,7 @@ namespace MediaBrowser.Controller.Entities return path; } - public virtual void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, Jellyfin.Data.Entities.User user, DtoOptions fields) + public virtual void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user, DtoOptions fields) { if (RunTimeTicks.HasValue) { @@ -2733,14 +2734,14 @@ namespace MediaBrowser.Controller.Entities return RefreshMetadataForOwnedItem(video, copyTitleMetadata, newOptions, cancellationToken); } - public string GetEtag(Jellyfin.Data.Entities.User user) + public string GetEtag(User user) { var list = GetEtagValues(user); return string.Join("|", list).GetMD5().ToString("N", CultureInfo.InvariantCulture); } - protected virtual List GetEtagValues(Jellyfin.Data.Entities.User user) + protected virtual List GetEtagValues(User user) { return new List { diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 03644b0c6..399813aa2 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.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Channels; @@ -16,13 +17,16 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; 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.Model.Dto; using MediaBrowser.Model.IO; using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; +using Season = MediaBrowser.Controller.Entities.TV.Season; +using Series = MediaBrowser.Controller.Entities.TV.Series; namespace MediaBrowser.Controller.Entities { @@ -174,7 +178,7 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public IEnumerable RecursiveChildren => GetRecursiveChildren(); - public override bool IsVisible(Jellyfin.Data.Entities.User user) + public override bool IsVisible(User user) { if (this is ICollectionFolder && !(this is BasePluginFolder)) { @@ -586,7 +590,7 @@ namespace MediaBrowser.Controller.Entities }); } - public virtual int GetChildCount(Jellyfin.Data.Entities.User user) + public virtual int GetChildCount(User user) { if (LinkedChildren.Length > 0) { @@ -611,7 +615,7 @@ namespace MediaBrowser.Controller.Entities return result.TotalRecordCount; } - public virtual int GetRecursiveChildCount(Jellyfin.Data.Entities.User user) + public virtual int GetRecursiveChildCount(User user) { return GetItems(new InternalItemsQuery(user) { @@ -954,7 +958,7 @@ namespace MediaBrowser.Controller.Entities IEnumerable items, InternalItemsQuery query, BaseItem queryParent, - Jellyfin.Data.Entities.User user, + User user, IServerConfigurationManager configurationManager, ICollectionManager collectionManager) { @@ -973,7 +977,7 @@ namespace MediaBrowser.Controller.Entities private static bool CollapseBoxSetItems(InternalItemsQuery query, BaseItem queryParent, - Jellyfin.Data.Entities.User user, + User user, IServerConfigurationManager configurationManager) { // Could end up stuck in a loop like this @@ -1196,7 +1200,7 @@ namespace MediaBrowser.Controller.Entities return true; } - public List GetChildren(Jellyfin.Data.Entities.User user, bool includeLinkedChildren) + public List GetChildren(User user, bool includeLinkedChildren) { if (user == null) { @@ -1206,7 +1210,7 @@ namespace MediaBrowser.Controller.Entities return GetChildren(user, includeLinkedChildren, null); } - public virtual List GetChildren(Jellyfin.Data.Entities.User user, bool includeLinkedChildren, InternalItemsQuery query) + public virtual List GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query) { if (user == null) { @@ -1226,7 +1230,7 @@ namespace MediaBrowser.Controller.Entities return result.Values.ToList(); } - protected virtual IEnumerable GetEligibleChildrenForRecursiveChildren(Jellyfin.Data.Entities.User user) + protected virtual IEnumerable GetEligibleChildrenForRecursiveChildren(User user) { return Children; } @@ -1235,7 +1239,7 @@ namespace MediaBrowser.Controller.Entities /// Adds the children to list. /// /// true if XXXX, false otherwise - private void AddChildren(Jellyfin.Data.Entities.User user, bool includeLinkedChildren, Dictionary result, bool recursive, InternalItemsQuery query) + private void AddChildren(User user, bool includeLinkedChildren, Dictionary result, bool recursive, InternalItemsQuery query) { foreach (var child in GetEligibleChildrenForRecursiveChildren(user)) { @@ -1284,12 +1288,12 @@ namespace MediaBrowser.Controller.Entities /// if set to true [include linked children]. /// IEnumerable{BaseItem}. /// - public IEnumerable GetRecursiveChildren(Jellyfin.Data.Entities.User user, bool includeLinkedChildren = true) + public IEnumerable GetRecursiveChildren(User user, bool includeLinkedChildren = true) { return GetRecursiveChildren(user, null); } - public virtual IEnumerable GetRecursiveChildren(Jellyfin.Data.Entities.User user, InternalItemsQuery query) + public virtual IEnumerable GetRecursiveChildren(User user, InternalItemsQuery query) { if (user == null) { @@ -1408,7 +1412,7 @@ namespace MediaBrowser.Controller.Entities return false; } - public List GetLinkedChildren(Jellyfin.Data.Entities.User user) + public List GetLinkedChildren(User user) { if (!FilterLinkedChildrenPerUser || user == null) { @@ -1570,7 +1574,7 @@ namespace MediaBrowser.Controller.Entities /// The date played. /// if set to true [reset position]. /// Task. - public override void MarkPlayed(Jellyfin.Data.Entities.User user, + public override void MarkPlayed(User user, DateTime? datePlayed, bool resetPosition) { @@ -1611,7 +1615,7 @@ namespace MediaBrowser.Controller.Entities /// /// The user. /// Task. - public override void MarkUnplayed(Jellyfin.Data.Entities.User user) + public override void MarkUnplayed(User user) { var itemsResult = GetItemList(new InternalItemsQuery { @@ -1629,7 +1633,7 @@ namespace MediaBrowser.Controller.Entities } } - public override bool IsPlayed(Jellyfin.Data.Entities.User user) + public override bool IsPlayed(User user) { var itemsResult = GetItemList(new InternalItemsQuery(user) { @@ -1644,7 +1648,7 @@ namespace MediaBrowser.Controller.Entities .All(i => i.IsPlayed(user)); } - public override bool IsUnplayed(Jellyfin.Data.Entities.User user) + public override bool IsUnplayed(User user) { return !IsPlayed(user); } @@ -1689,7 +1693,7 @@ namespace MediaBrowser.Controller.Entities } } - public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, Jellyfin.Data.Entities.User user, DtoOptions fields) + public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user, DtoOptions fields) { if (!SupportsUserDataFromChildren) { diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index 6a2cafcba..a2dff53ad 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.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Model.Configuration; @@ -16,7 +17,7 @@ namespace MediaBrowser.Controller.Entities public int? Limit { get; set; } - public Jellyfin.Data.Entities.User User { get; set; } + public User User { get; set; } public BaseItem SimilarTo { get; set; } @@ -214,13 +215,13 @@ namespace MediaBrowser.Controller.Entities Years = Array.Empty(); } - public InternalItemsQuery(Jellyfin.Data.Entities.User user) + public InternalItemsQuery(User user) : this() { SetUser(user); } - public void SetUser(Jellyfin.Data.Entities.User user) + public void SetUser(User user) { if (user != null) { diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 1c1bde3e4..be71bcc3c 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; +using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; @@ -44,7 +45,7 @@ namespace MediaBrowser.Controller.Entities.Movies /// The display order. public string DisplayOrder { get; set; } - protected override bool GetBlockUnratedValue(Jellyfin.Data.Entities.User user) + protected override bool GetBlockUnratedValue(User user) { return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Movie.ToString()); } @@ -100,7 +101,7 @@ namespace MediaBrowser.Controller.Entities.Movies [JsonIgnore] public override bool IsPreSorted => true; - public override bool IsAuthorizedToDelete(Jellyfin.Data.Entities.User user, List allCollectionFolders) + public override bool IsAuthorizedToDelete(User user, List allCollectionFolders) { return true; } @@ -110,7 +111,7 @@ namespace MediaBrowser.Controller.Entities.Movies return true; } - public override List GetChildren(Jellyfin.Data.Entities.User user, bool includeLinkedChildren, InternalItemsQuery query) + public override List GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query) { var children = base.GetChildren(user, includeLinkedChildren, query); @@ -130,7 +131,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(Jellyfin.Data.Entities.User user, InternalItemsQuery query) + public override IEnumerable GetRecursiveChildren(User user, InternalItemsQuery query) { var children = base.GetRecursiveChildren(user, query); @@ -148,7 +149,7 @@ namespace MediaBrowser.Controller.Entities.Movies return GetItemLookupInfo(); } - public override bool IsVisible(Jellyfin.Data.Entities.User user) + public override bool IsVisible(User user) { if (IsLegacyBoxSet) { @@ -176,7 +177,7 @@ namespace MediaBrowser.Controller.Entities.Movies return false; } - public override bool IsVisibleStandalone(Jellyfin.Data.Entities.User user) + public override bool IsVisibleStandalone(User user) { if (IsLegacyBoxSet) { @@ -188,7 +189,7 @@ namespace MediaBrowser.Controller.Entities.Movies public Guid[] LibraryFolderIds { get; set; } - private Guid[] GetLibraryFolderIds(Jellyfin.Data.Entities.User user) + private Guid[] GetLibraryFolderIds(User user) { return LibraryManager.GetUserRootFolder().GetChildren(user, true) .Select(i => i.Id) diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 0d1fec62f..839350fd7 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.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Providers; @@ -61,7 +62,7 @@ namespace MediaBrowser.Controller.Entities.TV return list; } - public override int GetChildCount(Jellyfin.Data.Entities.User user) + public override int GetChildCount(User user) { var result = GetChildren(user, true).Count; @@ -144,17 +145,17 @@ namespace MediaBrowser.Controller.Entities.TV /// /// Gets the episodes. /// - public List GetEpisodes(Jellyfin.Data.Entities.User user, DtoOptions options) + public List GetEpisodes(User user, DtoOptions options) { return GetEpisodes(Series, user, options); } - public List GetEpisodes(Series series, Jellyfin.Data.Entities.User user, DtoOptions options) + public List GetEpisodes(Series series, User user, DtoOptions options) { return GetEpisodes(series, user, null, options); } - public List GetEpisodes(Series series, Jellyfin.Data.Entities.User user, IEnumerable allSeriesEpisodes, DtoOptions options) + public List GetEpisodes(Series series, User user, IEnumerable allSeriesEpisodes, DtoOptions options) { return series.GetSeasonEpisodes(this, user, allSeriesEpisodes, options); } @@ -164,12 +165,12 @@ namespace MediaBrowser.Controller.Entities.TV return Series.GetSeasonEpisodes(this, null, null, new DtoOptions(true)); } - public override List GetChildren(Jellyfin.Data.Entities.User user, bool includeLinkedChildren, InternalItemsQuery query) + public override List GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query) { return GetEpisodes(user, new DtoOptions(true)); } - protected override bool GetBlockUnratedValue(Jellyfin.Data.Entities.User config) + protected override bool GetBlockUnratedValue(User config) { // Don't block. Let either the entire series rating or episode rating determine it return false; diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 4aed5fbdc..78d376d5c 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Providers; @@ -110,7 +111,7 @@ namespace MediaBrowser.Controller.Entities.TV return series.GetPresentationUniqueKey(); } - public override int GetChildCount(Jellyfin.Data.Entities.User user) + public override int GetChildCount(User user) { var seriesKey = GetUniqueSeriesKey(this); @@ -130,7 +131,7 @@ namespace MediaBrowser.Controller.Entities.TV return result; } - public override int GetRecursiveChildCount(Jellyfin.Data.Entities.User user) + public override int GetRecursiveChildCount(User user) { var seriesKey = GetUniqueSeriesKey(this); @@ -178,12 +179,12 @@ namespace MediaBrowser.Controller.Entities.TV return list; } - public override List GetChildren(Jellyfin.Data.Entities.User user, bool includeLinkedChildren, InternalItemsQuery query) + public override List GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query) { return GetSeasons(user, new DtoOptions(true)); } - public List GetSeasons(Jellyfin.Data.Entities.User user, DtoOptions options) + public List GetSeasons(User user, DtoOptions options) { var query = new InternalItemsQuery(user) { @@ -195,7 +196,7 @@ namespace MediaBrowser.Controller.Entities.TV return LibraryManager.GetItemList(query); } - private void SetSeasonQueryOptions(InternalItemsQuery query, Jellyfin.Data.Entities.User user) + private void SetSeasonQueryOptions(InternalItemsQuery query, User user) { var seriesKey = GetUniqueSeriesKey(this); @@ -239,7 +240,7 @@ namespace MediaBrowser.Controller.Entities.TV return LibraryManager.GetItemsResult(query); } - public IEnumerable GetEpisodes(Jellyfin.Data.Entities.User user, DtoOptions options) + public IEnumerable GetEpisodes(User user, DtoOptions options) { var seriesKey = GetUniqueSeriesKey(this); @@ -345,7 +346,7 @@ namespace MediaBrowser.Controller.Entities.TV await ProviderManager.RefreshSingleItem(this, refreshOptions, cancellationToken).ConfigureAwait(false); } - public List GetSeasonEpisodes(Season parentSeason, Jellyfin.Data.Entities.User user, DtoOptions options) + public List GetSeasonEpisodes(Season parentSeason, User user, DtoOptions options) { var queryFromSeries = ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons; @@ -375,7 +376,7 @@ namespace MediaBrowser.Controller.Entities.TV return GetSeasonEpisodes(parentSeason, user, allItems, options); } - public List GetSeasonEpisodes(Season parentSeason, Jellyfin.Data.Entities.User user, IEnumerable allSeriesEpisodes, DtoOptions options) + public List GetSeasonEpisodes(Season parentSeason, User user, IEnumerable allSeriesEpisodes, DtoOptions options) { if (allSeriesEpisodes == null) { @@ -445,7 +446,7 @@ namespace MediaBrowser.Controller.Entities.TV } - protected override bool GetBlockUnratedValue(Jellyfin.Data.Entities.User user) + protected override bool GetBlockUnratedValue(User user) { return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Series.ToString()); } diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index 9d211540d..39f4e0b6c 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; @@ -63,7 +64,7 @@ namespace MediaBrowser.Controller.Entities return UserViewBuilder.SortAndPage(result, null, query, LibraryManager, true); } - public override int GetChildCount(Jellyfin.Data.Entities.User user) + public override int GetChildCount(User user) { return GetChildren(user, true).Count; } @@ -74,7 +75,7 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public override bool IsPreSorted => true; - protected override IEnumerable GetEligibleChildrenForRecursiveChildren(Jellyfin.Data.Entities.User user) + protected override IEnumerable GetEligibleChildrenForRecursiveChildren(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 b44e7c191..806006490 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Querying; @@ -48,7 +49,7 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public override bool SupportsPlayedStatus => false; - public override int GetChildCount(Jellyfin.Data.Entities.User user) + public override int GetChildCount(User user) { return GetChildren(user, true).Count; } @@ -70,7 +71,7 @@ namespace MediaBrowser.Controller.Entities .GetUserItems(parent, this, CollectionType, query); } - public override List GetChildren(Jellyfin.Data.Entities.User user, bool includeLinkedChildren, InternalItemsQuery query) + public override List GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query) { if (query == null) { @@ -93,7 +94,7 @@ namespace MediaBrowser.Controller.Entities return true; } - public override IEnumerable GetRecursiveChildren(Jellyfin.Data.Entities.User user, InternalItemsQuery query) + public override IEnumerable GetRecursiveChildren(User user, InternalItemsQuery query) { query.SetUser(user); query.Recursive = true; @@ -103,14 +104,14 @@ namespace MediaBrowser.Controller.Entities return GetItemList(query); } - protected override IEnumerable GetEligibleChildrenForRecursiveChildren(Jellyfin.Data.Entities.User user) + protected override IEnumerable GetEligibleChildrenForRecursiveChildren(User user) { return GetChildren(user, false); } private static string[] UserSpecificViewTypes = new string[] { - MediaBrowser.Model.Entities.CollectionType.Playlists + Model.Entities.CollectionType.Playlists }; public static bool IsUserSpecific(Folder folder) @@ -139,8 +140,8 @@ namespace MediaBrowser.Controller.Entities private static string[] ViewTypesEligibleForGrouping = new string[] { - MediaBrowser.Model.Entities.CollectionType.Movies, - MediaBrowser.Model.Entities.CollectionType.TvShows, + Model.Entities.CollectionType.Movies, + Model.Entities.CollectionType.TvShows, string.Empty }; @@ -151,12 +152,12 @@ namespace MediaBrowser.Controller.Entities private static string[] OriginalFolderViewTypes = new string[] { - MediaBrowser.Model.Entities.CollectionType.Books, - MediaBrowser.Model.Entities.CollectionType.MusicVideos, - MediaBrowser.Model.Entities.CollectionType.HomeVideos, - MediaBrowser.Model.Entities.CollectionType.Photos, - MediaBrowser.Model.Entities.CollectionType.Music, - MediaBrowser.Model.Entities.CollectionType.BoxSets + Model.Entities.CollectionType.Books, + Model.Entities.CollectionType.MusicVideos, + Model.Entities.CollectionType.HomeVideos, + Model.Entities.CollectionType.Photos, + Model.Entities.CollectionType.Music, + Model.Entities.CollectionType.BoxSets }; public static bool EnableOriginalFolder(string viewType) diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 0ad8e6b71..b020a44c4 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -2,14 +2,18 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using Movie = MediaBrowser.Controller.Entities.Movies.Movie; +using Season = MediaBrowser.Controller.Entities.TV.Season; +using Series = MediaBrowser.Controller.Entities.TV.Series; namespace MediaBrowser.Controller.Entities { @@ -125,7 +129,7 @@ namespace MediaBrowser.Controller.Entities return 50; } - private QueryResult GetMovieFolders(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetMovieFolders(Folder parent, User user, InternalItemsQuery query) { if (query.Recursive) { @@ -153,7 +157,7 @@ namespace MediaBrowser.Controller.Entities return GetResult(list, parent, query); } - private QueryResult GetFavoriteMovies(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetFavoriteMovies(Folder parent, User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -164,7 +168,7 @@ namespace MediaBrowser.Controller.Entities return _libraryManager.GetItemsResult(query); } - private QueryResult GetFavoriteSeries(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetFavoriteSeries(Folder parent, User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -175,7 +179,7 @@ namespace MediaBrowser.Controller.Entities return _libraryManager.GetItemsResult(query); } - private QueryResult GetFavoriteEpisodes(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetFavoriteEpisodes(Folder parent, User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -186,7 +190,7 @@ namespace MediaBrowser.Controller.Entities return _libraryManager.GetItemsResult(query); } - private QueryResult GetMovieMovies(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetMovieMovies(Folder parent, User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -197,7 +201,7 @@ namespace MediaBrowser.Controller.Entities return _libraryManager.GetItemsResult(query); } - private QueryResult GetMovieCollections(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetMovieCollections(Folder parent, User user, InternalItemsQuery query) { query.Parent = null; query.IncludeItemTypes = new[] { typeof(BoxSet).Name }; @@ -207,7 +211,7 @@ namespace MediaBrowser.Controller.Entities return _libraryManager.GetItemsResult(query); } - private QueryResult GetMovieLatest(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetMovieLatest(Folder parent, User user, InternalItemsQuery query) { query.OrderBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName }.Select(i => new ValueTuple(i, SortOrder.Descending)).ToArray(); @@ -220,7 +224,7 @@ namespace MediaBrowser.Controller.Entities return ConvertToResult(_libraryManager.GetItemList(query)); } - private QueryResult GetMovieResume(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetMovieResume(Folder parent, User user, InternalItemsQuery query) { query.OrderBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName }.Select(i => new ValueTuple(i, SortOrder.Descending)).ToArray(); query.IsResumable = true; @@ -243,7 +247,7 @@ namespace MediaBrowser.Controller.Entities }; } - private QueryResult GetMovieGenres(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetMovieGenres(Folder parent, User user, InternalItemsQuery query) { var genres = parent.QueryRecursive(new InternalItemsQuery(user) { @@ -273,7 +277,7 @@ namespace MediaBrowser.Controller.Entities return GetResult(genres, parent, query); } - private QueryResult GetMovieGenreItems(Folder queryParent, Folder displayParent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetMovieGenreItems(Folder queryParent, Folder displayParent, User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = queryParent; @@ -285,7 +289,7 @@ namespace MediaBrowser.Controller.Entities return _libraryManager.GetItemsResult(query); } - private QueryResult GetTvView(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetTvView(Folder parent, User user, InternalItemsQuery query) { if (query.Recursive) { @@ -319,7 +323,7 @@ namespace MediaBrowser.Controller.Entities return GetResult(list, parent, query); } - private QueryResult GetTvLatest(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetTvLatest(Folder parent, User user, InternalItemsQuery query) { query.OrderBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName }.Select(i => new ValueTuple(i, SortOrder.Descending)).ToArray(); @@ -348,7 +352,7 @@ namespace MediaBrowser.Controller.Entities return result; } - private QueryResult GetTvResume(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetTvResume(Folder parent, User user, InternalItemsQuery query) { query.OrderBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName }.Select(i => new ValueTuple(i, SortOrder.Descending)).ToArray(); query.IsResumable = true; @@ -361,7 +365,7 @@ namespace MediaBrowser.Controller.Entities return ConvertToResult(_libraryManager.GetItemList(query)); } - private QueryResult GetTvSeries(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetTvSeries(Folder parent, User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = parent; @@ -372,7 +376,7 @@ namespace MediaBrowser.Controller.Entities return _libraryManager.GetItemsResult(query); } - private QueryResult GetTvGenres(Folder parent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetTvGenres(Folder parent, User user, InternalItemsQuery query) { var genres = parent.QueryRecursive(new InternalItemsQuery(user) { @@ -402,7 +406,7 @@ namespace MediaBrowser.Controller.Entities return GetResult(genres, parent, query); } - private QueryResult GetTvGenreItems(Folder queryParent, Folder displayParent, Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private QueryResult GetTvGenreItems(Folder queryParent, Folder displayParent, User user, InternalItemsQuery query) { query.Recursive = true; query.Parent = queryParent; @@ -492,7 +496,7 @@ namespace MediaBrowser.Controller.Entities }; } - public static bool Filter(BaseItem item, Jellyfin.Data.Entities.User user, InternalItemsQuery query, IUserDataManager userDataManager, ILibraryManager libraryManager) + public static bool Filter(BaseItem item, User user, InternalItemsQuery query, IUserDataManager userDataManager, ILibraryManager libraryManager) { if (query.MediaTypes.Length > 0 && !query.MediaTypes.Contains(item.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) { @@ -950,7 +954,7 @@ namespace MediaBrowser.Controller.Entities return true; } - private IEnumerable GetMediaFolders(Jellyfin.Data.Entities.User user) + private IEnumerable GetMediaFolders(User user) { if (user == null) { @@ -965,7 +969,7 @@ namespace MediaBrowser.Controller.Entities .Where(i => user.IsFolderGrouped(i.Id) && UserView.IsEligibleForGrouping(i)); } - private BaseItem[] GetMediaFolders(Jellyfin.Data.Entities.User user, IEnumerable viewTypes) + private BaseItem[] GetMediaFolders(User user, IEnumerable viewTypes) { if (user == null) { @@ -986,7 +990,7 @@ namespace MediaBrowser.Controller.Entities }).ToArray(); } - private BaseItem[] GetMediaFolders(Folder parent, Jellyfin.Data.Entities.User user, IEnumerable viewTypes) + private BaseItem[] GetMediaFolders(Folder parent, User user, IEnumerable viewTypes) { if (parent == null || parent is UserView) { diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index ada570bfd..1f7a5861a 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -2,10 +2,10 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Sorting; @@ -14,6 +14,9 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Querying; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using Genre = MediaBrowser.Controller.Entities.Genre; +using Person = MediaBrowser.Controller.Entities.Person; namespace MediaBrowser.Controller.Library { @@ -28,8 +31,7 @@ namespace MediaBrowser.Controller.Library /// The file information. /// The parent. /// BaseItem. - BaseItem ResolvePath(FileSystemMetadata fileInfo, - Folder parent = null); + BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null); /// /// Resolves a set of files into a list of BaseItem @@ -141,7 +143,7 @@ namespace MediaBrowser.Controller.Library /// The item. /// The user. /// IEnumerable{System.String}. - Task> GetIntros(BaseItem item, Jellyfin.Data.Entities.User user); + Task> GetIntros(BaseItem item, User user); /// /// Gets all intro files. @@ -172,8 +174,8 @@ namespace MediaBrowser.Controller.Library /// The sort by. /// The sort order. /// IEnumerable{BaseItem}. - IEnumerable Sort(IEnumerable items, Jellyfin.Data.Entities.User user, IEnumerable sortBy, SortOrder sortOrder); - IEnumerable Sort(IEnumerable items, Jellyfin.Data.Entities.User user, IEnumerable> orderBy); + IEnumerable Sort(IEnumerable items, User user, IEnumerable sortBy, SortOrder sortOrder); + IEnumerable Sort(IEnumerable items, User user, IEnumerable> orderBy); /// /// Gets the user root folder. @@ -285,7 +287,7 @@ namespace MediaBrowser.Controller.Library /// Type of the view. /// Name of the sort. UserView GetNamedView( - Jellyfin.Data.Entities.User user, + User user, string name, Guid parentId, string viewType, @@ -299,7 +301,7 @@ namespace MediaBrowser.Controller.Library /// Type of the view. /// Name of the sort. UserView GetNamedView( - Jellyfin.Data.Entities.User user, + User user, string name, string viewType, string sortName); diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index 57368778a..94528ff77 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.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.Controller.Entities; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; @@ -55,12 +56,12 @@ namespace MediaBrowser.Controller.Library /// /// Gets the playack media sources. /// - Task> GetPlaybackMediaSources(BaseItem item, Jellyfin.Data.Entities.User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken); + Task> GetPlaybackMediaSources(BaseItem item, User user, bool allowMediaProbe, bool enablePathSubstitution, CancellationToken cancellationToken); /// /// Gets the static media sources. /// - List GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, Jellyfin.Data.Entities.User user = null); + List GetStaticMediaSources(BaseItem item, bool enablePathSubstitution, User user = null); /// /// Gets the static media source. @@ -100,7 +101,7 @@ namespace MediaBrowser.Controller.Library MediaProtocol GetPathProtocol(string path); - void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, Jellyfin.Data.Entities.User user); + void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, 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 0618837bc..36b250ec9 100644 --- a/MediaBrowser.Controller/Library/IMusicManager.cs +++ b/MediaBrowser.Controller/Library/IMusicManager.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -10,16 +11,16 @@ namespace MediaBrowser.Controller.Library /// /// Gets the instant mix from song. /// - List GetInstantMixFromItem(BaseItem item, Jellyfin.Data.Entities.User user, DtoOptions dtoOptions); + List GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions); /// /// Gets the instant mix from artist. /// - List GetInstantMixFromArtist(MusicArtist artist, Jellyfin.Data.Entities.User user, DtoOptions dtoOptions); + List GetInstantMixFromArtist(MusicArtist artist, User user, DtoOptions dtoOptions); /// /// Gets the instant mix from genre. /// - List GetInstantMixFromGenres(IEnumerable genres, Jellyfin.Data.Entities.User user, DtoOptions dtoOptions); + List GetInstantMixFromGenres(IEnumerable genres, User user, DtoOptions dtoOptions); } } diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs index 15da560ef..f5ccad671 100644 --- a/MediaBrowser.Controller/Library/IUserDataManager.cs +++ b/MediaBrowser.Controller/Library/IUserDataManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Dto; @@ -27,18 +28,18 @@ namespace MediaBrowser.Controller.Library /// The reason. /// The cancellation token. void SaveUserData(Guid userId, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken); - void SaveUserData(Jellyfin.Data.Entities.User user, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken); + void SaveUserData(User user, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken); - UserItemData GetUserData(Jellyfin.Data.Entities.User user, BaseItem item); + UserItemData GetUserData(User user, BaseItem item); UserItemData GetUserData(Guid userId, BaseItem item); /// /// Gets the user data dto. /// - UserItemDataDto GetUserDataDto(BaseItem item, Jellyfin.Data.Entities.User user); + UserItemDataDto GetUserDataDto(BaseItem item, User user); - UserItemDataDto GetUserDataDto(BaseItem item, BaseItemDto itemDto, Jellyfin.Data.Entities.User user, DtoOptions dto_options); + UserItemDataDto GetUserDataDto(BaseItem item, BaseItemDto itemDto, User user, DtoOptions dto_options); /// /// Get all user data for the given user diff --git a/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs b/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs index 83c0e3297..b4e205184 100644 --- a/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs +++ b/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; @@ -11,7 +12,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 +29,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 99fd18bf9..bc3bf78f0 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -115,7 +116,7 @@ namespace MediaBrowser.Controller.LiveTv /// The cancellation token. /// The user. /// Task{ProgramInfoDto}. - Task GetProgram(string id, CancellationToken cancellationToken, Jellyfin.Data.Entities.User user = null); + Task GetProgram(string id, CancellationToken cancellationToken, User user = null); /// /// Gets the programs. @@ -202,7 +203,7 @@ namespace MediaBrowser.Controller.LiveTv /// Gets the enabled users. /// /// IEnumerable{User}. - IEnumerable GetEnabledUsers(); + IEnumerable GetEnabledUsers(); /// /// Gets the internal channels. @@ -221,7 +222,7 @@ namespace MediaBrowser.Controller.LiveTv /// The fields. /// The user. /// Task. - Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> programs, ItemFields[] fields, Jellyfin.Data.Entities.User user = null); + Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> programs, ItemFields[] fields, User user = null); /// /// Saves the tuner host. @@ -258,7 +259,7 @@ namespace MediaBrowser.Controller.LiveTv /// The items. /// The options. /// The user. - void AddChannelInfo(IReadOnlyCollection<(BaseItemDto, LiveTvChannel)> items, DtoOptions options, Jellyfin.Data.Entities.User user); + void AddChannelInfo(IReadOnlyCollection<(BaseItemDto, LiveTvChannel)> items, DtoOptions options, User user); Task> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken); Task> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken); @@ -277,9 +278,9 @@ namespace MediaBrowser.Controller.LiveTv ActiveRecordingInfo GetActiveRecordingInfo(string path); - void AddInfoToRecordingDto(BaseItem item, BaseItemDto dto, ActiveRecordingInfo activeRecordingInfo, Jellyfin.Data.Entities.User user = null); + void AddInfoToRecordingDto(BaseItem item, BaseItemDto dto, ActiveRecordingInfo activeRecordingInfo, User user = null); - List GetRecordingFolders(Jellyfin.Data.Entities.User user); + List GetRecordingFolders(User user); } public class ActiveRecordingInfo diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index dc4361fc3..67241c35b 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; @@ -49,7 +50,7 @@ namespace MediaBrowser.Controller.MediaEncoding public MediaSourceInfo MediaSource { get; set; } - public Jellyfin.Data.Entities.User User { get; set; } + public User User { get; set; } public long? RunTimeTicks { get; set; } diff --git a/MediaBrowser.Controller/Net/IAuthService.cs b/MediaBrowser.Controller/Net/IAuthService.cs index 61fc7e6e6..d8f6d19da 100644 --- a/MediaBrowser.Controller/Net/IAuthService.cs +++ b/MediaBrowser.Controller/Net/IAuthService.cs @@ -1,6 +1,6 @@ #nullable enable -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; @@ -9,6 +9,7 @@ namespace MediaBrowser.Controller.Net public interface IAuthService { void Authenticate(IRequest request, IAuthenticationAttributes authAttribtues); - Jellyfin.Data.Entities.User? Authenticate(HttpRequest request, IAuthenticationAttributes authAttribtues); + + User? Authenticate(HttpRequest request, IAuthenticationAttributes authAttribtues); } } diff --git a/MediaBrowser.Controller/Notifications/INotificationService.cs b/MediaBrowser.Controller/Notifications/INotificationService.cs index 2bc751758..ab5eb13cd 100644 --- a/MediaBrowser.Controller/Notifications/INotificationService.cs +++ b/MediaBrowser.Controller/Notifications/INotificationService.cs @@ -1,6 +1,6 @@ using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; namespace MediaBrowser.Controller.Notifications { @@ -25,6 +25,6 @@ namespace MediaBrowser.Controller.Notifications /// /// The user. /// true if [is enabled for user] [the specified user identifier]; otherwise, false. - bool IsEnabledForUser(Jellyfin.Data.Entities.User user); + bool IsEnabledForUser(User user); } } diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index 03bdf1eaf..b1a638883 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -78,7 +79,7 @@ namespace MediaBrowser.Controller.Playlists return 1; } - public override bool IsAuthorizedToDelete(Jellyfin.Data.Entities.User user, List allCollectionFolders) + public override bool IsAuthorizedToDelete(User user, List allCollectionFolders) { return true; } @@ -99,7 +100,7 @@ namespace MediaBrowser.Controller.Playlists return Task.CompletedTask; } - public override List GetChildren(Jellyfin.Data.Entities.User user, bool includeLinkedChildren, InternalItemsQuery query) + public override List GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query) { return GetPlayableItems(user, query); } @@ -109,7 +110,7 @@ namespace MediaBrowser.Controller.Playlists return new List(); } - public override IEnumerable GetRecursiveChildren(Jellyfin.Data.Entities.User user, InternalItemsQuery query) + public override IEnumerable GetRecursiveChildren(User user, InternalItemsQuery query) { return GetPlayableItems(user, query); } @@ -119,7 +120,7 @@ namespace MediaBrowser.Controller.Playlists return GetLinkedChildrenInfos(); } - private List GetPlayableItems(Jellyfin.Data.Entities.User user, InternalItemsQuery query) + private List GetPlayableItems(User user, InternalItemsQuery query) { if (query == null) { @@ -131,7 +132,7 @@ namespace MediaBrowser.Controller.Playlists return base.GetChildren(user, true, query); } - public static List GetPlaylistItems(string playlistMediaType, IEnumerable inputItems, Jellyfin.Data.Entities.User user, DtoOptions options) + public static List GetPlaylistItems(string playlistMediaType, IEnumerable inputItems, User user, DtoOptions options) { if (user != null) { @@ -149,7 +150,7 @@ namespace MediaBrowser.Controller.Playlists return list; } - private static IEnumerable GetPlaylistItems(BaseItem item, Jellyfin.Data.Entities.User user, string mediaType, DtoOptions options) + private static IEnumerable GetPlaylistItems(BaseItem item, User user, string mediaType, DtoOptions options) { if (item is MusicGenre musicGenre) { @@ -222,7 +223,7 @@ namespace MediaBrowser.Controller.Playlists } } - public override bool IsVisible(Jellyfin.Data.Entities.User user) + public override bool IsVisible(User user) { if (!IsSharedItem) { @@ -244,7 +245,7 @@ namespace MediaBrowser.Controller.Playlists return shares.Any(share => string.Equals(share.UserId, userId, StringComparison.OrdinalIgnoreCase)); } - public override bool IsVisibleStandalone(Jellyfin.Data.Entities.User user) + public override bool IsVisibleStandalone(User user) { if (!IsSharedItem) { diff --git a/MediaBrowser.Model/Notifications/NotificationOptions.cs b/MediaBrowser.Model/Notifications/NotificationOptions.cs index f4132002b..0d6b0a645 100644 --- a/MediaBrowser.Model/Notifications/NotificationOptions.cs +++ b/MediaBrowser.Model/Notifications/NotificationOptions.cs @@ -4,6 +4,7 @@ using System; using Jellyfin.Data.Enums; using MediaBrowser.Model.Extensions; using System.Linq; +using Jellyfin.Data.Entities; using MediaBrowser.Model.Users; namespace MediaBrowser.Model.Notifications @@ -115,7 +116,7 @@ namespace MediaBrowser.Model.Notifications !opt.DisabledMonitorUsers.Contains(userId.ToString(""), StringComparer.OrdinalIgnoreCase); } - public bool IsEnabledToSendToUser(string type, string userId, Jellyfin.Data.Entities.User user) + public bool IsEnabledToSendToUser(string type, string userId, User user) { NotificationOption opt = GetOptions(type); diff --git a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs index 0cb8d8be6..4245c3249 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.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Net; @@ -84,7 +85,7 @@ namespace Jellyfin.Api.Tests.Auth a => a.Authenticate( It.IsAny(), It.IsAny())) - .Returns((Jellyfin.Data.Entities.User?)null); + .Returns((User?)null); var authenticateResult = await _sut.AuthenticateAsync(); @@ -149,9 +150,9 @@ namespace Jellyfin.Api.Tests.Auth Assert.Equal(_scheme.Name, authenticatedResult.Ticket.AuthenticationScheme); } - private Jellyfin.Data.Entities.User SetupUser(bool isAdmin = false) + private User SetupUser(bool isAdmin = false) { - var user = _fixture.Create(); + var user = _fixture.Create(); user.SetPermission(PermissionKind.IsAdministrator, isAdmin); _jellyfinAuthServiceMock.Setup( -- cgit v1.2.3 From becfe018f0e3796bbd776068ea92d22daf483f2f Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 20 May 2020 13:49:44 -0400 Subject: Add internal id for new users --- Jellyfin.Server.Implementations/Users/UserManager.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 4dd41792d..599efe583 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -190,10 +190,16 @@ namespace Jellyfin.Server.Implementations.Users var dbContext = _dbProvider.CreateContext(); + // Temporary measure until user item data is migrated. + var max = dbContext.Users.Select(u => u.InternalId).Max(); + var newUser = new User( name, _defaultAuthenticationProvider.GetType().FullName, - _defaultPasswordResetProvider.GetType().FullName); + _defaultPasswordResetProvider.GetType().FullName) + { + InternalId = max + 1 + }; dbContext.Users.Add(newUser); dbContext.SaveChanges(); -- cgit v1.2.3 From 0ccf7320b07ef11a2f2210ed28aa11114e1b41f0 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 20 May 2020 14:17:59 -0400 Subject: Fix a few issues in User --- Jellyfin.Data/Entities/User.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index 52bbe8b18..dd1dcfc6f 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -260,9 +260,7 @@ namespace Jellyfin.Data.Entities public bool HasPermission(PermissionKind permission) { - var list = Permissions.Where(p => p.Kind == permission); - - return list.First().Value; + return Permissions.First(p => p.Kind == permission).Value; } public void SetPermission(PermissionKind kind, bool value) @@ -283,16 +281,14 @@ namespace Jellyfin.Data.Entities public void SetPreference(PreferenceKind preference, string[] values) { - var pref = Preferences.First(p => p.Kind == preference); - - pref.Value = string.Join(Delimiter.ToString(CultureInfo.InvariantCulture), values); + Preferences.First(p => p.Kind == preference).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)); + return AccessSchedules.Count == 0 + || AccessSchedules.Any(i => IsParentalScheduleAllowed(i, DateTime.UtcNow)); } public bool IsFolderGrouped(Guid id) -- cgit v1.2.3 From ae4c407b6d4ff314362f693f682521168aa922df Mon Sep 17 00:00:00 2001 From: artiume Date: Wed, 20 May 2020 16:46:33 -0400 Subject: Add .edl Mimetype --- MediaBrowser.Model/Net/MimeTypes.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs index b491a015c..b6d7b4245 100644 --- a/MediaBrowser.Model/Net/MimeTypes.cs +++ b/MediaBrowser.Model/Net/MimeTypes.cs @@ -100,6 +100,7 @@ namespace MediaBrowser.Model.Net { ".ssa", "text/x-ssa" }, { ".css", "text/css" }, { ".csv", "text/csv" }, + { ".edl", "text/plain" }, { ".rtf", "text/rtf" }, { ".txt", "text/plain" }, { ".vtt", "text/vtt" }, -- cgit v1.2.3 From 3fb4c1356c22c8be03855a161140c21eb395086b Mon Sep 17 00:00:00 2001 From: Vasily Date: Wed, 20 May 2020 23:50:17 +0300 Subject: Make blurhash be computed during regular scans if it was not already computed --- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 2 ++ MediaBrowser.Controller/Entities/BaseItem.cs | 1 + MediaBrowser.Controller/Entities/Folder.cs | 5 +++++ 3 files changed, 8 insertions(+) diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 99091ea57..7ab0a54df 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -246,6 +246,8 @@ namespace Jellyfin.Drawing.Skia throw new FileNotFoundException("File not found", path); } + // Use 4 vertical and 4 horizontal components of DCT of the image. + // See more at https://github.com/woltapp/blurhash/#how-do-i-pick-the-number-of-x-and-y-components return BlurHashEncoder.Encode(4, 4, path); } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index e5f6ea09d..035ab1dd9 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1374,6 +1374,7 @@ namespace MediaBrowser.Controller.Entities new List(); var ownedItemsChanged = await RefreshedOwnedItems(options, files, cancellationToken).ConfigureAwait(false); + LibraryManager.UpdateImages(this); // ensure all image properties in DB are fresh if (ownedItemsChanged) { diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index a468e0c35..29040f92e 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -341,6 +341,11 @@ namespace MediaBrowser.Controller.Entities { currentChild.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken); } + else + { + // metadata is up-to-date; make sure DB has correct images dimensions and hash + LibraryManager.UpdateImages(currentChild); + } continue; } -- cgit v1.2.3 From 8b517e9beffd5cf7b1e7ed2b82b0bdf63fe60f03 Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 21 May 2020 00:03:22 +0300 Subject: Fix nullref for imageProcessor in LibraryManager --- Emby.Server.Implementations/Library/LibraryManager.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 9f412b725..75350ac2a 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -69,6 +69,8 @@ namespace Emby.Server.Implementations.Library private readonly IFileSystem _fileSystem; private readonly IItemRepository _itemRepository; private readonly ConcurrentDictionary _libraryItemsCache; + private readonly IImageProcessor _imageProcessor; + private NamingOptions _namingOptions; private string[] _videoFileExtensions; @@ -111,12 +113,6 @@ namespace Emby.Server.Implementations.Library /// The comparers. private IBaseItemComparer[] Comparers { get; set; } - /// - /// Gets or sets the active image processor - /// - /// The image processor. - public IImageProcessor ImageProcessor { get; set; } - /// /// Occurs when [item added]. /// @@ -155,7 +151,8 @@ namespace Emby.Server.Implementations.Library Lazy providerManagerFactory, Lazy userviewManagerFactory, IMediaEncoder mediaEncoder, - IItemRepository itemRepository) + IItemRepository itemRepository, + IImageProcessor imageProcessor) { _appHost = appHost; _logger = logger; @@ -169,6 +166,7 @@ namespace Emby.Server.Implementations.Library _userviewManagerFactory = userviewManagerFactory; _mediaEncoder = mediaEncoder; _itemRepository = itemRepository; + _imageProcessor = imageProcessor; _libraryItemsCache = new ConcurrentDictionary(); @@ -1841,10 +1839,10 @@ namespace Emby.Server.Implementations.Library outdated.ForEach(img => { - ImageDimensions size = ImageProcessor.GetImageDimensions(item, img); + ImageDimensions size = _imageProcessor.GetImageDimensions(item, img); img.Width = size.Width; img.Height = size.Height; - img.Hash = ImageProcessor.GetImageHash(img.Path); + img.Hash = _imageProcessor.GetImageHash(img.Path); }); _itemRepository.SaveImages(item); -- cgit v1.2.3 From 1f83a212886bae879c47b4b4f5b1eb25a28e2ad3 Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 21 May 2020 01:43:19 +0300 Subject: Rename Hash to BlurHash in all properties and methods for clarity --- Emby.Drawing/ImageProcessor.cs | 2 +- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 4 ++-- Emby.Server.Implementations/Dto/DtoService.cs | 6 +++--- Emby.Server.Implementations/Library/LibraryManager.cs | 4 ++-- MediaBrowser.Api/Images/ImageService.cs | 2 +- MediaBrowser.Controller/Drawing/IImageProcessor.cs | 2 +- MediaBrowser.Controller/Entities/BaseItem.cs | 2 +- MediaBrowser.Controller/Entities/ItemImageInfo.cs | 2 +- MediaBrowser.Model/Dto/BaseItemDto.cs | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 1237b603b..35da6f635 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -314,7 +314,7 @@ namespace Emby.Drawing => _imageEncoder.GetImageSize(path); /// - public string GetImageHash(string path) + public string GetImageBlurHash(string path) => _imageEncoder.GetImageHash(path); /// diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 10eb96b10..dd60dd222 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1144,7 +1144,7 @@ namespace Emby.Server.Implementations.Data const string delimeter = "*"; var path = image.Path ?? string.Empty; - var hash = image.Hash ?? string.Empty; + var hash = image.BlurHash ?? string.Empty; return GetPathToSave(path) + delimeter + @@ -1195,7 +1195,7 @@ namespace Emby.Server.Implementations.Data if (parts.Length >= 6) { - image.Hash = parts[5].Replace('/', '*').Replace('\\', '|'); + image.BlurHash = parts[5].Replace('/', '*').Replace('\\', '|'); } } diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 07105786b..593e7be7d 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -718,7 +718,7 @@ namespace Emby.Server.Implementations.Dto if (options.EnableImages) { dto.ImageTags = new Dictionary(); - dto.ImageHashes = new Dictionary(); + dto.ImageBlurHashes = new Dictionary(); // Prevent implicitly captured closure var currentItem = item; @@ -733,10 +733,10 @@ namespace Emby.Server.Implementations.Dto dto.ImageTags[image.Type] = tag; } - var hash = image.Hash; + var hash = image.BlurHash; if (!string.IsNullOrEmpty(hash)) { - dto.ImageHashes[tag] = image.Hash; + dto.ImageBlurHashes[tag] = image.BlurHash; } } } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 75350ac2a..579fb7edd 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1829,7 +1829,7 @@ namespace Emby.Server.Implementations.Library } var outdated = item.ImageInfos - .Where(i => (i.IsLocalFile && (i.Width == 0 || i.Height == 0 || string.IsNullOrEmpty(i.Hash)))) + .Where(i => (i.IsLocalFile && (i.Width == 0 || i.Height == 0 || string.IsNullOrEmpty(i.BlurHash)))) .ToList(); if (outdated.Count == 0) { @@ -1842,7 +1842,7 @@ namespace Emby.Server.Implementations.Library ImageDimensions size = _imageProcessor.GetImageDimensions(item, img); img.Width = size.Width; img.Height = size.Height; - img.Hash = _imageProcessor.GetImageHash(img.Path); + img.BlurHash = _imageProcessor.GetImageBlurHash(img.Path); }); _itemRepository.SaveImages(item); diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 559db550b..d0846bfc3 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -341,7 +341,7 @@ namespace MediaBrowser.Api.Images var fileInfo = _fileSystem.GetFileInfo(info.Path); length = fileInfo.Length; - blurhash = info.Hash; + blurhash = info.BlurHash; width = info.Width; height = info.Height; diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index e38eaf760..8800fdf99 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -45,7 +45,7 @@ namespace MediaBrowser.Controller.Drawing /// /// Path to the image file. /// BlurHash - String GetImageHash(string path); + string GetImageBlurHash(string path); /// /// Gets the image cache tag. diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 035ab1dd9..07aeb69db 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -2223,7 +2223,7 @@ namespace MediaBrowser.Controller.Entities existingImage.DateModified = image.DateModified; existingImage.Width = image.Width; existingImage.Height = image.Height; - existingImage.Hash = image.Hash; + existingImage.BlurHash = image.BlurHash; } else { diff --git a/MediaBrowser.Controller/Entities/ItemImageInfo.cs b/MediaBrowser.Controller/Entities/ItemImageInfo.cs index ba0297107..12f5db2e0 100644 --- a/MediaBrowser.Controller/Entities/ItemImageInfo.cs +++ b/MediaBrowser.Controller/Entities/ItemImageInfo.cs @@ -32,7 +32,7 @@ namespace MediaBrowser.Controller.Entities /// Gets or sets the blurhash. /// /// The blurhash. - public string Hash { get; set; } + public string BlurHash { get; set; } [JsonIgnore] public bool IsLocalFile => Path == null || !Path.StartsWith("http", StringComparison.OrdinalIgnoreCase); diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 8c6c9683a..df84dcf12 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -514,7 +514,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the blurhash for the image tags. /// /// The blurhashes. - public Dictionary ImageHashes { get; set; } + public Dictionary ImageBlurHashes { get; set; } /// /// Gets or sets the series studio. -- cgit v1.2.3 From d72ea709955f17ad2034cd4c728d02cf3265e7e9 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 20 May 2020 19:47:41 -0400 Subject: Document user class and fix a few minor issues --- .../Devices/DeviceManager.cs | 9 +- Jellyfin.Data/Entities/User.cs | 219 ++++++++++++++++++--- .../Users/UserManager.cs | 6 +- 3 files changed, 194 insertions(+), 40 deletions(-) diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index 2d44a7c28..a3c53035f 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -5,13 +5,11 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Threading.Tasks; using Jellyfin.Data.Enums; using Jellyfin.Data.Entities; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Security; using MediaBrowser.Model.Devices; @@ -19,7 +17,6 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; -using MediaBrowser.Model.Users; namespace Emby.Server.Implementations.Devices { @@ -30,11 +27,10 @@ namespace Emby.Server.Implementations.Devices private readonly IServerConfigurationManager _config; private readonly IAuthenticationRepository _authRepo; private readonly Dictionary _capabilitiesCache; + private readonly object _capabilitiesSyncLock = new object(); public event EventHandler>> DeviceOptionsUpdated; - private readonly object _capabilitiesSyncLock = new object(); - public DeviceManager( IAuthenticationRepository authRepo, IJsonSerializer json, @@ -184,8 +180,7 @@ namespace Emby.Server.Implementations.Devices throw new ArgumentNullException(nameof(deviceId)); } - if (user.HasPermission(PermissionKind.EnableAllDevices) - || user.HasPermission(PermissionKind.IsAdministrator)) + if (user.HasPermission(PermissionKind.EnableAllDevices) || user.HasPermission(PermissionKind.IsAdministrator)) { return true; } diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index dd1dcfc6f..afda6169c 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -24,7 +24,8 @@ namespace Jellyfin.Data.Entities /// Public constructor with required data. /// /// The username for the new user. - /// The authentication provider's Id + /// The Id of the user's authentication provider. + /// The Id of the user's password reset provider. public User(string username, string authenticationProviderId, string passwordResetProviderId) { if (string.IsNullOrEmpty(username)) @@ -81,139 +82,234 @@ namespace Jellyfin.Data.Entities Init(); } - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// The username for the created user. - /// The Id of the user's authentication provider. - /// The Id of the user's password reset provider. - /// The created instance. - public static User Create(string username, string authenticationProviderId, string passwordResetProviderId) - { - return new User(username, authenticationProviderId, passwordResetProviderId); - } - /************************************************************************* * Properties *************************************************************************/ /// - /// Identity, Indexed, Required + /// Gets or sets the Id of the user. /// + /// + /// Identity, Indexed, Required. + /// [Key] [Required] [JsonIgnore] public Guid Id { get; set; } /// - /// Required, Max length = 255 + /// Gets or sets the user's name. /// + /// + /// Required, Max length = 255. + /// [Required] [MaxLength(255)] [StringLength(255)] public string Username { get; set; } /// - /// Max length = 65535 + /// Gets or sets the user's password, or null if none is set. /// + /// + /// Max length = 65535. + /// [MaxLength(65535)] [StringLength(65535)] public string Password { get; set; } /// - /// Max length = 65535. + /// Gets or sets the user's easy password, or null if none is set. /// + /// + /// Max length = 65535. + /// [MaxLength(65535)] [StringLength(65535)] public string EasyPassword { get; set; } /// - /// Required + /// Gets or sets a value indicating whether the user must update their password. /// + /// + /// Required. + /// [Required] public bool MustUpdatePassword { get; set; } /// - /// Max length = 255. + /// Gets or sets the audio language preference. /// + /// + /// Max length = 255. + /// [MaxLength(255)] [StringLength(255)] public string AudioLanguagePreference { get; set; } /// - /// Required, Max length = 255 + /// Gets or sets the authentication provider id. /// + /// + /// Required, Max length = 255. + /// [Required] [MaxLength(255)] [StringLength(255)] public string AuthenticationProviderId { get; set; } + /// + /// Gets or sets the password reset provider id. + /// + /// + /// Required, Max length = 255. + /// [Required] [MaxLength(255)] [StringLength(255)] public string PasswordResetProviderId { get; set; } /// - /// Required + /// Gets or sets the invalid login attempt count. /// + /// + /// Required. + /// [Required] public int InvalidLoginAttemptCount { get; set; } + /// + /// Gets or sets the last activity date. + /// public DateTime LastActivityDate { get; set; } + /// + /// Gets or sets the last login date. + /// public DateTime LastLoginDate { get; set; } + /// + /// Gets or sets the number of login attempts the user can make before they are locked out. + /// public int? LoginAttemptsBeforeLockout { get; set; } /// - /// Required. + /// Gets or sets the subtitle mode. /// + /// + /// Required. + /// [Required] public SubtitlePlaybackMode SubtitleMode { get; set; } /// - /// Required + /// Gets or sets a value indicating whether the default audio track should be played. /// + /// + /// Required. + /// [Required] public bool PlayDefaultAudioTrack { get; set; } /// /// Gets or sets the subtitle language preference. - /// Max length = 255 /// + /// + /// Max length = 255. + /// [MaxLength(255)] [StringLength(255)] public string SubtitleLanguagePreference { get; set; } + /// + /// Gets or sets a value indicating whether missing episodes should be displayed. + /// + /// + /// Required. + /// [Required] public bool DisplayMissingEpisodes { get; set; } + /// + /// Gets or sets a value indicating whether to display the collections view. + /// + /// + /// Required. + /// [Required] public bool DisplayCollectionsView { get; set; } + /// + /// Gets or sets a value indicating whether the user has a local password. + /// + /// + /// Required. + /// [Required] public bool EnableLocalPassword { get; set; } + /// + /// Gets or sets a value indicating whether the server should hide played content in "Latest". + /// + /// + /// Required. + /// [Required] public bool HidePlayedInLatest { get; set; } + /// + /// Gets or sets a value indicating whether to remember audio selections on played content. + /// + /// + /// Required. + /// [Required] public bool RememberAudioSelections { get; set; } + /// + /// Gets or sets a value indicating whether to remember subtitle selections on played content. + /// + /// + /// Required. + /// [Required] public bool RememberSubtitleSelections { get; set; } + /// + /// Gets or sets a value indicating whether to enable auto-play for the next episode. + /// + /// + /// Required. + /// [Required] public bool EnableNextEpisodeAutoPlay { get; set; } + /// + /// Gets or sets a value indicating whether the user should auto-login. + /// + /// + /// Required. + /// [Required] public bool EnableAutoLogin { get; set; } + /// + /// Gets or sets a value indicating whether the user can change their preferences. + /// + /// + /// Required. + /// [Required] public bool EnableUserPreferenceAccess { get; set; } + /// + /// Gets or sets the maximum parental age rating. + /// public int? MaxParentalAgeRating { get; set; } + /// + /// Gets or sets the remote client bitrate limit. + /// public int? RemoteClientBitrateLimit { get; set; } /// @@ -224,51 +320,100 @@ namespace Jellyfin.Data.Entities [Required] public long InternalId { get; set; } + /// + /// Gets or sets the user's profile image. Can be null. + /// public virtual ImageInfo ProfileImage { get; set; } /// /// Gets or sets the row version. - /// Required, ConcurrenyToken. /// + /// + /// Required, Concurrency Token. + /// [ConcurrencyCheck] [Required] public uint RowVersion { get; set; } - public void OnSavingChanges() - { - RowVersion++; - } - /************************************************************************* * Navigation properties *************************************************************************/ + + /// + /// Gets or sets the list of groups this user is a member of. + /// [ForeignKey("Group_Groups_Guid")] public virtual ICollection Groups { get; protected set; } + /// + /// Gets or sets the list of permissions this user has. + /// [ForeignKey("Permission_Permissions_Guid")] public virtual ICollection Permissions { get; protected set; } + /// + /// Gets or sets the list of provider mappings this user has. + /// [ForeignKey("ProviderMapping_ProviderMappings_Id")] public virtual ICollection ProviderMappings { get; protected set; } + /// + /// Gets or sets the list of preferences this user has. + /// [ForeignKey("Preference_Preferences_Guid")] public virtual ICollection Preferences { get; protected set; } + /// + /// Gets or sets the list of access schedules this user has. + /// public virtual ICollection AccessSchedules { get; protected set; } + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The username for the created user. + /// The Id of the user's authentication provider. + /// The Id of the user's password reset provider. + /// The created instance. + public static User Create(string username, string authenticationProviderId, string passwordResetProviderId) + { + return new User(username, authenticationProviderId, passwordResetProviderId); + } + + /// + public void OnSavingChanges() + { + RowVersion++; + } + partial void Init(); + /// + /// Checks whether the user has the specified permission. + /// + /// The permission kind. + /// True if the user has the specified permission. public bool HasPermission(PermissionKind permission) { return Permissions.First(p => p.Kind == permission).Value; } + /// + /// Sets the given permission kind to the provided value. + /// + /// The permission kind. + /// The value to set. public void SetPermission(PermissionKind kind, bool value) { var permissionObj = Permissions.First(p => p.Kind == kind); permissionObj.Value = value; } + /// + /// Gets the user's preferences for the given preference kind. + /// + /// The preference kind. + /// A string array containing the user's preferences. public string[] GetPreference(PreferenceKind preference) { var val = Preferences @@ -279,18 +424,32 @@ namespace Jellyfin.Data.Entities return Equals(val, string.Empty) ? Array.Empty() : val.Split(Delimiter); } + /// + /// Sets the specified preference to the given value. + /// + /// The preference kind. + /// The values. public void SetPreference(PreferenceKind preference, string[] values) { Preferences.First(p => p.Kind == preference).Value = string.Join(Delimiter.ToString(CultureInfo.InvariantCulture), values); } + /// + /// Checks whether this user is currently allowed to use the server. + /// + /// True if the current time is within an access schedule, or there are no access schedules. public bool IsParentalScheduleAllowed() { return AccessSchedules.Count == 0 || AccessSchedules.Any(i => IsParentalScheduleAllowed(i, DateTime.UtcNow)); } + /// + /// Checks whether the provided folder is in this user's grouped folders. + /// + /// The Guid of the folder. + /// True if the folder is in the user's grouped folders. public bool IsFolderGrouped(Guid id) { return GetPreference(PreferenceKind.GroupedFolders).Any(i => new Guid(i) == id); diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 599efe583..e16b1fb7b 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -35,7 +35,7 @@ namespace Jellyfin.Server.Implementations.Users private readonly INetworkManager _networkManager; private readonly IApplicationHost _appHost; private readonly IImageProcessor _imageProcessor; - private readonly ILogger _logger; + private readonly ILogger _logger; private IAuthenticationProvider[] _authenticationProviders; private DefaultAuthenticationProvider _defaultAuthenticationProvider; @@ -58,7 +58,7 @@ namespace Jellyfin.Server.Implementations.Users INetworkManager networkManager, IApplicationHost appHost, IImageProcessor imageProcessor, - ILogger logger) + ILogger logger) { _dbProvider = dbProvider; _cryptoProvider = cryptoProvider; @@ -190,7 +190,7 @@ namespace Jellyfin.Server.Implementations.Users var dbContext = _dbProvider.CreateContext(); - // Temporary measure until user item data is migrated. + // TODO: Remove after user item data is migrated. var max = dbContext.Users.Select(u => u.InternalId).Max(); var newUser = new User( -- cgit v1.2.3 From 7e2bd3018a0926658d34c862ca5084753cdc062a Mon Sep 17 00:00:00 2001 From: abdulaziz Date: Thu, 21 May 2020 02:36:33 +0000 Subject: Translated using Weblate (Arabic) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/ --- Emby.Server.Implementations/Localization/Core/ar.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json index f313039a6..d68928fce 100644 --- a/Emby.Server.Implementations/Localization/Core/ar.json +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -9,7 +9,7 @@ "Channels": "القنوات", "ChapterNameValue": "الفصل {0}", "Collections": "مجموعات", - "DeviceOfflineWithName": "قُطِع الاتصال بـ{0}", + "DeviceOfflineWithName": "قُطِع الاتصال ب{0}", "DeviceOnlineWithName": "{0} متصل", "FailedLoginAttemptWithUserName": "عملية تسجيل الدخول فشلت من {0}", "Favorites": "المفضلة", -- cgit v1.2.3 From 6bf444feaeb07ad24b2aee5afa2f1281970b77e0 Mon Sep 17 00:00:00 2001 From: Vitorvlv Date: Thu, 21 May 2020 02:23:40 +0000 Subject: Translated using Weblate (Portuguese (Brazil)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_BR/ --- Emby.Server.Implementations/Localization/Core/pt-BR.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json index 3a69b6d7a..275195640 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-BR.json +++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json @@ -19,10 +19,10 @@ "HeaderCameraUploads": "Envios da Câmera", "HeaderContinueWatching": "Continuar Assistindo", "HeaderFavoriteAlbums": "Álbuns Favoritos", - "HeaderFavoriteArtists": "Artistas Favoritos", - "HeaderFavoriteEpisodes": "Episódios Favoritos", - "HeaderFavoriteShows": "Séries Favoritas", - "HeaderFavoriteSongs": "Músicas Favoritas", + "HeaderFavoriteArtists": "Artistas favoritos", + "HeaderFavoriteEpisodes": "Episódios favoritos", + "HeaderFavoriteShows": "Séries favoritas", + "HeaderFavoriteSongs": "Músicas favoritas", "HeaderLiveTV": "TV ao Vivo", "HeaderNextUp": "A Seguir", "HeaderRecordingGroups": "Grupos de Gravação", -- cgit v1.2.3 From 42177d173949ebc04810ad2ad9a432c42f7b0c69 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 21 May 2020 00:22:43 -0400 Subject: Replace Weekday with DayOfWeek --- Jellyfin.Data/Entities/Series.cs | 20 ++++++-------------- Jellyfin.Data/Entities/User.cs | 6 ------ Jellyfin.Data/Enums/Weekday.cs | 13 ------------- Jellyfin.Server.Implementations/JellyfinDb.cs | 2 +- 4 files changed, 7 insertions(+), 34 deletions(-) delete mode 100644 Jellyfin.Data/Enums/Weekday.cs diff --git a/Jellyfin.Data/Entities/Series.cs b/Jellyfin.Data/Entities/Series.cs index 097b9958e..4f25c38b7 100644 --- a/Jellyfin.Data/Entities/Series.cs +++ b/Jellyfin.Data/Entities/Series.cs @@ -19,14 +19,6 @@ namespace Jellyfin.Data.Entities Init(); } - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static Series CreateSeriesUnsafe() - { - return new Series(); - } - /// /// Public constructor with required data /// @@ -57,27 +49,27 @@ namespace Jellyfin.Data.Entities /// /// Backing field for AirsDayOfWeek /// - protected Enums.Weekday? _AirsDayOfWeek; + protected DayOfWeek? _AirsDayOfWeek; /// /// When provided in a partial class, allows value of AirsDayOfWeek to be changed before setting. /// - partial void SetAirsDayOfWeek(Enums.Weekday? oldValue, ref Enums.Weekday? newValue); + partial void SetAirsDayOfWeek(DayOfWeek? oldValue, ref DayOfWeek? newValue); /// /// When provided in a partial class, allows value of AirsDayOfWeek to be changed before returning. /// - partial void GetAirsDayOfWeek(ref Enums.Weekday? result); + partial void GetAirsDayOfWeek(ref DayOfWeek? result); - public Enums.Weekday? AirsDayOfWeek + public DayOfWeek? AirsDayOfWeek { get { - Enums.Weekday? value = _AirsDayOfWeek; + DayOfWeek? value = _AirsDayOfWeek; GetAirsDayOfWeek(ref value); return (_AirsDayOfWeek = value); } set { - Enums.Weekday? oldValue = _AirsDayOfWeek; + DayOfWeek? oldValue = _AirsDayOfWeek; SetAirsDayOfWeek(oldValue, ref value); if (oldValue != value) { diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index afda6169c..2287d802b 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -73,12 +73,6 @@ namespace Jellyfin.Data.Entities /// protected User() { - Groups = new HashSet(); - Permissions = new HashSet(); - ProviderMappings = new HashSet(); - Preferences = new HashSet(); - AccessSchedules = new HashSet(); - Init(); } diff --git a/Jellyfin.Data/Enums/Weekday.cs b/Jellyfin.Data/Enums/Weekday.cs deleted file mode 100644 index b799fd811..000000000 --- a/Jellyfin.Data/Enums/Weekday.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Jellyfin.Data.Enums -{ - public enum Weekday - { - Sunday = 0, - Monday = 1, - Tuesday = 2, - Wednesday = 3, - Thursday = 4, - Friday = 5, - Saturday = 6 - } -} diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 8eb35ec87..7e10d81dc 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -23,7 +23,7 @@ namespace Jellyfin.Server.Implementations public virtual DbSet Preferences { 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; } -- cgit v1.2.3 From e0d8a474ec41c62920f476b17440bcb5cebbd5f9 Mon Sep 17 00:00:00 2001 From: fonfire Date: Thu, 21 May 2020 09:52:32 +0000 Subject: Added translation using Weblate (Thai) --- Emby.Server.Implementations/Localization/Core/th.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 Emby.Server.Implementations/Localization/Core/th.json diff --git a/Emby.Server.Implementations/Localization/Core/th.json b/Emby.Server.Implementations/Localization/Core/th.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/th.json @@ -0,0 +1 @@ +{} -- cgit v1.2.3 From 74c1e002d2f1d84051ccf3c3bc77b77afcd35954 Mon Sep 17 00:00:00 2001 From: fatbill27 Date: Thu, 21 May 2020 07:29:33 +0000 Subject: Translated using Weblate (Chinese (Hong Kong)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hant_HK/ --- .../Localization/Core/zh-HK.json | 54 ++++++++++++++-------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json index a67a67582..0804fc927 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-HK.json +++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json @@ -11,15 +11,15 @@ "Collections": "合輯", "DeviceOfflineWithName": "{0} 已經斷開連結", "DeviceOnlineWithName": "{0} 已經連接", - "FailedLoginAttemptWithUserName": "來自 {0} 的失敗登入嘗試", + "FailedLoginAttemptWithUserName": "來自 {0} 的登入失敗", "Favorites": "我的最愛", "Folders": "檔案夾", "Genres": "風格", - "HeaderAlbumArtists": "專輯藝術家", + "HeaderAlbumArtists": "專輯藝人", "HeaderCameraUploads": "相機上載", "HeaderContinueWatching": "繼續觀看", "HeaderFavoriteAlbums": "最愛專輯", - "HeaderFavoriteArtists": "最愛藝術家", + "HeaderFavoriteArtists": "最愛的藝人", "HeaderFavoriteEpisodes": "最愛的劇集", "HeaderFavoriteShows": "最愛的節目", "HeaderFavoriteSongs": "最愛的歌曲", @@ -33,14 +33,14 @@ "LabelIpAddressValue": "IP 地址: {0}", "LabelRunningTimeValue": "運行時間: {0}", "Latest": "最新", - "MessageApplicationUpdated": "Jellyfin Server 已更新", + "MessageApplicationUpdated": "Jellyfin 伺服器已更新", "MessageApplicationUpdatedTo": "Jellyfin 伺服器已更新至 {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "伺服器設定 {0} 部分已更新", + "MessageNamedServerConfigurationUpdatedWithValue": "伺服器設定 {0} 已更新", "MessageServerConfigurationUpdated": "伺服器設定已經更新", - "MixedContent": "Mixed content", + "MixedContent": "混合內容", "Movies": "電影", "Music": "音樂", - "MusicVideos": "音樂MV", + "MusicVideos": "音樂視頻", "NameInstallFailed": "{0} 安裝失敗", "NameSeasonNumber": "第 {0} 季", "NameSeasonUnknown": "未知季數", @@ -49,7 +49,7 @@ "NotificationOptionApplicationUpdateInstalled": "應用程式已更新", "NotificationOptionAudioPlayback": "開始播放音頻", "NotificationOptionAudioPlaybackStopped": "已停止播放音頻", - "NotificationOptionCameraImageUploaded": "相機相片已上傳", + "NotificationOptionCameraImageUploaded": "相片已上傳", "NotificationOptionInstallationFailed": "安裝失敗", "NotificationOptionNewLibraryContent": "已添加新内容", "NotificationOptionPluginError": "擴充元件錯誤", @@ -63,11 +63,11 @@ "NotificationOptionVideoPlaybackStopped": "已停止播放視頻", "Photos": "相片", "Playlists": "播放清單", - "Plugin": "Plugin", + "Plugin": "插件", "PluginInstalledWithName": "已安裝 {0}", "PluginUninstalledWithName": "已移除 {0}", "PluginUpdatedWithName": "已更新 {0}", - "ProviderValue": "Provider: {0}", + "ProviderValue": "提供者: {0}", "ScheduledTaskFailedWithName": "{0} 任務失敗", "ScheduledTaskStartedWithName": "{0} 任務開始", "ServerNameNeedsToBeRestarted": "{0} 需要重啓", @@ -77,17 +77,17 @@ "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureFromForItem": "無法從 {0} 下載 {1} 的字幕", "Sync": "同步", - "System": "System", + "System": "系統", "TvShows": "電視節目", - "User": "User", - "UserCreatedWithName": "用家 {0} 已創建", - "UserDeletedWithName": "用家 {0} 已移除", + "User": "使用者", + "UserCreatedWithName": "使用者 {0} 已創建", + "UserDeletedWithName": "使用者 {0} 已移除", "UserDownloadingItemWithValues": "{0} 正在下載 {1}", - "UserLockedOutWithName": "用家 {0} 已被鎖定", + "UserLockedOutWithName": "使用者 {0} 已被鎖定", "UserOfflineFromDevice": "{0} 已從 {1} 斷開", "UserOnlineFromDevice": "{0} 已連綫,來自 {1}", - "UserPasswordChangedWithName": "用家 {0} 的密碼已變更", - "UserPolicyUpdatedWithName": "用戶協議已被更新為 {0}", + "UserPasswordChangedWithName": "使用者 {0} 的密碼已變更", + "UserPolicyUpdatedWithName": "使用者協議已更新為 {0}", "UserStartedPlayingItemWithValues": "{0} 正在 {2} 上播放 {1}", "UserStoppedPlayingItemWithValues": "{0} 已在 {2} 上停止播放 {1}", "ValueHasBeenAddedToLibrary": "{0} 已添加到你的媒體庫", @@ -95,5 +95,23 @@ "VersionNumber": "版本{0}", "TaskDownloadMissingSubtitles": "下載遺失的字幕", "TaskUpdatePlugins": "更新插件", - "TasksApplicationCategory": "應用程式" + "TasksApplicationCategory": "應用程式", + "TaskRefreshLibraryDescription": "掃描媒體庫以查找新文件並刷新metadata。", + "TasksMaintenanceCategory": "維護", + "TaskDownloadMissingSubtitlesDescription": "根據metadata配置在互聯網上搜索缺少的字幕。", + "TaskRefreshChannelsDescription": "刷新互聯網頻道信息。", + "TaskRefreshChannels": "刷新頻道", + "TaskCleanTranscodeDescription": "刪除超過一天的轉碼文件。", + "TaskCleanTranscode": "清理轉碼目錄", + "TaskUpdatePluginsDescription": "下載並安裝配置為自動更新的插件的更新。", + "TaskRefreshPeopleDescription": "更新媒體庫中演員和導演的metadata。", + "TaskCleanLogsDescription": "刪除超過{0}天的日誌文件。", + "TaskCleanLogs": "清理日誌目錄", + "TaskRefreshLibrary": "掃描媒體庫", + "TaskRefreshChapterImagesDescription": "為帶有章節的視頻創建縮略圖。", + "TaskRefreshChapterImages": "提取章節圖像", + "TaskCleanCacheDescription": "刪除系統不再需要的緩存文件。", + "TaskCleanCache": "清理緩存目錄", + "TasksChannelsCategory": "互聯網頻道", + "TasksLibraryCategory": "庫" } -- cgit v1.2.3 From 367da81ae9a5825aefaed0901db47cd844c969e1 Mon Sep 17 00:00:00 2001 From: fonfire Date: Thu, 21 May 2020 09:53:01 +0000 Subject: Translated using Weblate (Thai) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/th/ --- .../Localization/Core/th.json | 72 +++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/th.json b/Emby.Server.Implementations/Localization/Core/th.json index 0967ef424..32538ac03 100644 --- a/Emby.Server.Implementations/Localization/Core/th.json +++ b/Emby.Server.Implementations/Localization/Core/th.json @@ -1 +1,71 @@ -{} +{ + "ProviderValue": "ผู้ให้บริการ: {0}", + "PluginUpdatedWithName": "{0} ได้รับการ update แล้ว", + "PluginUninstalledWithName": "ถอนการติดตั้ง {0}", + "PluginInstalledWithName": "{0} ได้รับการติดตั้ง", + "Plugin": "Plugin", + "Playlists": "รายการ", + "Photos": "รูปภาพ", + "NotificationOptionVideoPlaybackStopped": "หยุดการเล่น Video", + "NotificationOptionVideoPlayback": "เริ่มแสดง Video", + "NotificationOptionUserLockedOut": "ผู้ใช้ Locked Out", + "NotificationOptionTaskFailed": "ตารางการทำงานล้มเหลว", + "NotificationOptionServerRestartRequired": "ควร Restart Server", + "NotificationOptionPluginUpdateInstalled": "Update Plugin แล้ว", + "NotificationOptionPluginUninstalled": "ถอด Plugin", + "NotificationOptionPluginInstalled": "ติดตั้ง Plugin แล้ว", + "NotificationOptionPluginError": "Plugin ล้มเหลว", + "NotificationOptionNewLibraryContent": "เพิ่มข้อมูลใหม่แล้ว", + "NotificationOptionInstallationFailed": "ติดตั้งล้มเหลว", + "NotificationOptionCameraImageUploaded": "รูปภาพถูก upload", + "NotificationOptionAudioPlaybackStopped": "หยุดการเล่นเสียง", + "NotificationOptionAudioPlayback": "เริ่มเล่นเสียง", + "NotificationOptionApplicationUpdateInstalled": "Update ระบบแล้ว", + "NotificationOptionApplicationUpdateAvailable": "ระบบ update สามารถใช้ได้แล้ว", + "NewVersionIsAvailable": "ตรวจพบ Jellyfin เวอร์ชั่นใหม่", + "NameSeasonUnknown": "ไม่ทราบปี", + "NameSeasonNumber": "ปี {0}", + "NameInstallFailed": "{0} ติดตั้งไม่สำเร็จ", + "MusicVideos": "MV", + "Music": "เพลง", + "Movies": "ภาพยนต์", + "MixedContent": "รายการแบบผสม", + "MessageServerConfigurationUpdated": "การตั้งค่า update แล้ว", + "MessageNamedServerConfigurationUpdatedWithValue": "รายการตั้งค่า {0} ได้รับการ update แล้ว", + "MessageApplicationUpdatedTo": "Jellyfin Server จะ update ไปที่ {0}", + "MessageApplicationUpdated": "Jellyfin Server update แล้ว", + "Latest": "ล่าสุด", + "LabelRunningTimeValue": "เวลาที่เล่น : {0}", + "LabelIpAddressValue": "IP address: {0}", + "ItemRemovedWithName": "{0} ถูกลบจากรายการ", + "ItemAddedWithName": "{0} ถูกเพิ่มในรายการ", + "Inherit": "การสืบทอด", + "HomeVideos": "วีดีโอส่วนตัว", + "HeaderRecordingGroups": "ค่ายบันทึก", + "HeaderNextUp": "ถัดไป", + "HeaderLiveTV": "รายการสด", + "HeaderFavoriteSongs": "เพลงโปรด", + "HeaderFavoriteShows": "รายการโชว์โปรด", + "HeaderFavoriteEpisodes": "ฉากโปรด", + "HeaderFavoriteArtists": "นักแสดงโปรด", + "HeaderFavoriteAlbums": "อัมบั้มโปรด", + "HeaderContinueWatching": "ชมต่อจากเดิม", + "HeaderCameraUploads": "Upload รูปภาพ", + "HeaderAlbumArtists": "อัลบั้มนักแสดง", + "Genres": "ประเภท", + "Folders": "โฟลเดอร์", + "Favorites": "รายการโปรด", + "FailedLoginAttemptWithUserName": "การเชื่อมต่อล้มเหลวจาก {0}", + "DeviceOnlineWithName": "{0} เชื่อมต่อสำเร็จ", + "DeviceOfflineWithName": "{0} ตัดการเชื่อมต่อ", + "Collections": "ชุด", + "ChapterNameValue": "บทที่ {0}", + "Channels": "ชาแนล", + "CameraImageUploadedFrom": "รูปภาพถูก upload จาก {0}", + "Books": "หนังสือ", + "AuthenticationSucceededWithUserName": "{0} ยืนยันตัวสำเร็จ", + "Artists": "นักแสดง", + "Application": "แอปพลิเคชั่น", + "AppDeviceValues": "App: {0}, อุปกรณ์: {1}", + "Albums": "อัลบั้ม" +} -- cgit v1.2.3 From 3c86489d2892fab7f1f94ee936ef0410b6721551 Mon Sep 17 00:00:00 2001 From: Óskar Freyr Date: Thu, 21 May 2020 16:14:14 +0000 Subject: Translated using Weblate (Icelandic) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/is/ --- .../Localization/Core/is.json | 24 ++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/is.json b/Emby.Server.Implementations/Localization/Core/is.json index ef2a57e8e..0f0f9130b 100644 --- a/Emby.Server.Implementations/Localization/Core/is.json +++ b/Emby.Server.Implementations/Localization/Core/is.json @@ -80,16 +80,32 @@ "ValueHasBeenAddedToLibrary": "{0} hefur verið bætt við í gagnasafnið þitt", "UserStoppedPlayingItemWithValues": "{0} hefur lokið spilunar af {1} á {2}", "UserStartedPlayingItemWithValues": "{0} er að spila {1} á {2}", - "UserPolicyUpdatedWithName": "Notandaregla hefur verið uppfærð fyrir notanda {0}", + "UserPolicyUpdatedWithName": "Notandaregla hefur verið uppfærð fyrir {0}", "UserPasswordChangedWithName": "Lykilorði fyrir notandann {0} hefur verið breytt", "UserOnlineFromDevice": "{0} hefur verið virkur síðan {1}", "UserOfflineFromDevice": "{0} hefur aftengst frá {1}", - "UserLockedOutWithName": "Notanda {0} hefur verið hindraður aðgangur", + "UserLockedOutWithName": "Notanda {0} hefur verið heflaður aðgangur", "UserDownloadingItemWithValues": "{0} Hleður niður {1}", "SubtitleDownloadFailureFromForItem": "Tókst ekki að hala niður skjátextum frá {0} til {1}", "ProviderValue": "Veitandi: {0}", "MessageNamedServerConfigurationUpdatedWithValue": "Stilling {0} hefur verið uppfærð á netþjón", "ValueSpecialEpisodeName": "Sérstakt - {0}", - "Shows": "Þættir", - "Playlists": "Spilunarlisti" + "Shows": "Sýningar", + "Playlists": "Spilunarlisti", + "TaskRefreshChannelsDescription": "Endurhlaða upplýsingum netrása.", + "TaskRefreshChannels": "Endurhlaða Rásir", + "TaskCleanTranscodeDescription": "Eyða umkóðuðum skrám sem eru meira en einum degi eldri.", + "TaskCleanTranscode": "Hreinsa Umkóðunarmöppu", + "TaskUpdatePluginsDescription": "Sækja og setja upp uppfærslur fyrir viðbætur sem eru stilltar til að uppfæra sjálfkrafa.", + "TaskUpdatePlugins": "Uppfæra viðbætur", + "TaskRefreshPeopleDescription": "Uppfærir lýsigögn fyrir leikara og leikstjóra í miðlasafninu þínu.", + "TaskRefreshLibraryDescription": "Skannar miðlasafnið þitt fyrir nýjum skrám og uppfærir lýsigögn.", + "TaskRefreshLibrary": "Skanna miðlasafn", + "TaskRefreshChapterImagesDescription": "Býr til smámyndir fyrir myndbönd sem hafa kaflaskil.", + "TaskCleanCacheDescription": "Eyðir skrám í skyndiminni sem ekki er lengur þörf fyrir í kerfinu.", + "TaskCleanCache": "Hreinsa skráasafn skyndiminnis", + "TasksChannelsCategory": "Netrásir", + "TasksApplicationCategory": "Forrit", + "TasksLibraryCategory": "Miðlasafn", + "TasksMaintenanceCategory": "Viðhald" } -- cgit v1.2.3 From e98600a76ff49cb70da229e757de8931542497a0 Mon Sep 17 00:00:00 2001 From: WontTell Date: Fri, 22 May 2020 00:42:43 +0000 Subject: Translated using Weblate (Spanish (Mexico)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es_MX/ --- .../Localization/Core/es-MX.json | 70 +++++++++++----------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json index d93920f43..20b37ec9f 100644 --- a/Emby.Server.Implementations/Localization/Core/es-MX.json +++ b/Emby.Server.Implementations/Localization/Core/es-MX.json @@ -16,16 +16,16 @@ "Folders": "Carpetas", "Genres": "Géneros", "HeaderAlbumArtists": "Artistas del álbum", - "HeaderCameraUploads": "Subidos desde Camara", - "HeaderContinueWatching": "Continuar Viendo", + "HeaderCameraUploads": "Subidas desde la cámara", + "HeaderContinueWatching": "Continuar viendo", "HeaderFavoriteAlbums": "Álbumes favoritos", "HeaderFavoriteArtists": "Artistas favoritos", "HeaderFavoriteEpisodes": "Episodios favoritos", "HeaderFavoriteShows": "Programas favoritos", "HeaderFavoriteSongs": "Canciones favoritas", - "HeaderLiveTV": "TV en Vivo", - "HeaderNextUp": "A Continuación", - "HeaderRecordingGroups": "Grupos de Grabaciones", + "HeaderLiveTV": "TV en vivo", + "HeaderNextUp": "A continuación", + "HeaderRecordingGroups": "Grupos de grabación", "HomeVideos": "Videos caseros", "Inherit": "Heredar", "ItemAddedWithName": "{0} fue agregado a la biblioteca", @@ -41,12 +41,12 @@ "Movies": "Películas", "Music": "Música", "MusicVideos": "Videos musicales", - "NameInstallFailed": "{0} instalación fallida", + "NameInstallFailed": "Falló la instalación de {0}", "NameSeasonNumber": "Temporada {0}", - "NameSeasonUnknown": "Temporada Desconocida", + "NameSeasonUnknown": "Temporada desconocida", "NewVersionIsAvailable": "Una nueva versión del Servidor Jellyfin está disponible para descargar.", - "NotificationOptionApplicationUpdateAvailable": "Actualización de aplicación disponible", - "NotificationOptionApplicationUpdateInstalled": "Actualización de aplicación instalada", + "NotificationOptionApplicationUpdateAvailable": "Actualización de la aplicación disponible", + "NotificationOptionApplicationUpdateInstalled": "Actualización de la aplicación instalada", "NotificationOptionAudioPlayback": "Reproducción de audio iniciada", "NotificationOptionAudioPlaybackStopped": "Reproducción de audio detenida", "NotificationOptionCameraImageUploaded": "Imagen de la cámara subida", @@ -56,7 +56,7 @@ "NotificationOptionPluginInstalled": "Complemento instalado", "NotificationOptionPluginUninstalled": "Complemento desinstalado", "NotificationOptionPluginUpdateInstalled": "Actualización de complemento instalada", - "NotificationOptionServerRestartRequired": "Se necesita reiniciar el Servidor", + "NotificationOptionServerRestartRequired": "Se necesita reiniciar el servidor", "NotificationOptionTaskFailed": "Falla de tarea programada", "NotificationOptionUserLockedOut": "Usuario bloqueado", "NotificationOptionVideoPlayback": "Reproducción de video iniciada", @@ -69,48 +69,48 @@ "PluginUpdatedWithName": "{0} fue actualizado", "ProviderValue": "Proveedor: {0}", "ScheduledTaskFailedWithName": "{0} falló", - "ScheduledTaskStartedWithName": "{0} Iniciado", + "ScheduledTaskStartedWithName": "{0} iniciado", "ServerNameNeedsToBeRestarted": "{0} debe ser reiniciado", "Shows": "Programas", "Songs": "Canciones", - "StartupEmbyServerIsLoading": "El servidor Jellyfin esta cargando. Por favor intente de nuevo dentro de poco.", + "StartupEmbyServerIsLoading": "El servidor Jellyfin está cargando. Por favor, intente de nuevo pronto.", "SubtitleDownloadFailureForItem": "Falló la descarga de subtítulos para {0}", - "SubtitleDownloadFailureFromForItem": "Falló la descarga de subtitulos desde {0} para {1}", + "SubtitleDownloadFailureFromForItem": "Falló la descarga de subtítulos desde {0} para {1}", "Sync": "Sincronizar", "System": "Sistema", "TvShows": "Programas de TV", "User": "Usuario", - "UserCreatedWithName": "Se ha creado el usuario {0}", - "UserDeletedWithName": "Se ha eliminado el usuario {0}", - "UserDownloadingItemWithValues": "{0} esta descargando {1}", + "UserCreatedWithName": "El usuario {0} ha sido creado", + "UserDeletedWithName": "El usuario {0} ha sido eliminado", + "UserDownloadingItemWithValues": "{0} está descargando {1}", "UserLockedOutWithName": "El usuario {0} ha sido bloqueado", "UserOfflineFromDevice": "{0} se ha desconectado desde {1}", "UserOnlineFromDevice": "{0} está en línea desde {1}", "UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}", - "UserPolicyUpdatedWithName": "Las política de usuario ha sido actualizada por {0}", - "UserStartedPlayingItemWithValues": "{0} está reproduciéndose {1} en {2}", - "UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducirse {1} en {2}", - "ValueHasBeenAddedToLibrary": "{0} se han añadido a su biblioteca de medios", + "UserPolicyUpdatedWithName": "La política de usuario ha sido actualizada para {0}", + "UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}", + "UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}", + "ValueHasBeenAddedToLibrary": "{0} se ha añadido a tu biblioteca de medios", "ValueSpecialEpisodeName": "Especial - {0}", "VersionNumber": "Versión {0}", - "TaskDownloadMissingSubtitlesDescription": "Buscar subtítulos de internet basado en configuración de metadatos.", - "TaskDownloadMissingSubtitles": "Descargar subtítulos perdidos", - "TaskRefreshChannelsDescription": "Refrescar información de canales de internet.", + "TaskDownloadMissingSubtitlesDescription": "Busca subtítulos faltantes en Internet basándose en la configuración de metadatos.", + "TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes", + "TaskRefreshChannelsDescription": "Actualiza la información de canales de Internet.", "TaskRefreshChannels": "Actualizar canales", - "TaskCleanTranscodeDescription": "Eliminar archivos transcodificados que tengan mas de un día.", + "TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día.", "TaskCleanTranscode": "Limpiar directorio de transcodificado", - "TaskUpdatePluginsDescription": "Descargar y actualizar complementos que están configurados para actualizarse automáticamente.", + "TaskUpdatePluginsDescription": "Descarga e instala actualizaciones para complementos que están configurados para actualizarse automáticamente.", "TaskUpdatePlugins": "Actualizar complementos", - "TaskRefreshPeopleDescription": "Actualizar datos de actores y directores en su librería multimedia.", - "TaskRefreshPeople": "Refrescar persona", - "TaskCleanLogsDescription": "Eliminar archivos de registro con mas de {0} días.", - "TaskCleanLogs": "Directorio de logo limpio", - "TaskRefreshLibraryDescription": "Escanear su librería multimedia para nuevos archivos y refrescar metadatos.", - "TaskRefreshLibrary": "Escanear librería multimerdia", - "TaskRefreshChapterImagesDescription": "Crear miniaturas para videos con capítulos.", - "TaskRefreshChapterImages": "Extraer imágenes de capítulos", - "TaskCleanCacheDescription": "Eliminar archivos cache que ya no se necesiten por el sistema.", - "TaskCleanCache": "Limpiar directorio cache", + "TaskRefreshPeopleDescription": "Actualiza metadatos de actores y directores en tu biblioteca de medios.", + "TaskRefreshPeople": "Actualizar personas", + "TaskCleanLogsDescription": "Elimina archivos de registro con más de {0} días de antigüedad.", + "TaskCleanLogs": "Limpiar directorio de registros", + "TaskRefreshLibraryDescription": "Escanea tu biblioteca de medios por archivos nuevos y actualiza los metadatos.", + "TaskRefreshLibrary": "Escanear biblioteca de medios", + "TaskRefreshChapterImagesDescription": "Crea miniaturas para videos que tienen capítulos.", + "TaskRefreshChapterImages": "Extraer imágenes de los capítulos", + "TaskCleanCacheDescription": "Elimina archivos caché que ya no son necesarios para el sistema.", + "TaskCleanCache": "Limpiar directorio caché", "TasksChannelsCategory": "Canales de Internet", "TasksApplicationCategory": "Aplicación", "TasksLibraryCategory": "Biblioteca", -- cgit v1.2.3 From 341b947cdecdfc791c1bc3e72da1e68cd3754c3a Mon Sep 17 00:00:00 2001 From: crobibero Date: Fri, 22 May 2020 10:48:01 -0600 Subject: Move int64 converter to JsonDefaults location --- .../Converters/LongToStringConverter.cs | 56 ---------------------- .../Extensions/ApiServiceCollectionExtensions.cs | 2 - .../Json/Converters/JsonInt64Converter.cs | 56 ++++++++++++++++++++++ MediaBrowser.Common/Json/JsonDefaults.cs | 1 + 4 files changed, 57 insertions(+), 58 deletions(-) delete mode 100644 Jellyfin.Server/Converters/LongToStringConverter.cs create mode 100644 MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs diff --git a/Jellyfin.Server/Converters/LongToStringConverter.cs b/Jellyfin.Server/Converters/LongToStringConverter.cs deleted file mode 100644 index ad66b7b0c..000000000 --- a/Jellyfin.Server/Converters/LongToStringConverter.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Buffers; -using System.Buffers.Text; -using System.Globalization; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Jellyfin.Server.Converters -{ - /// - /// Long to String JSON converter. - /// Javascript does not support 64-bit integers. - /// - public class LongToStringConverter : JsonConverter - { - /// - /// Read JSON string as Long. - /// - /// . - /// Type. - /// Options. - /// Parsed value. - public override long Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.String) - { - // try to parse number directly from bytes - ReadOnlySpan span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; - if (Utf8Parser.TryParse(span, out long number, out int bytesConsumed) && span.Length == bytesConsumed) - { - return number; - } - - // try to parse from a string if the above failed, this covers cases with other escaped/UTF characters - if (long.TryParse(reader.GetString(), out number)) - { - return number; - } - } - - // fallback to default handling - return reader.GetInt64(); - } - - /// - /// Write long to JSON string. - /// - /// . - /// Value to write. - /// Options. - public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options) - { - writer.WriteStringValue(value.ToString(NumberFormatInfo.InvariantInfo)); - } - } -} diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index afd42ac5a..71ef9a69a 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -4,7 +4,6 @@ using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; using Jellyfin.Api.Auth.RequiresElevationPolicy; using Jellyfin.Api.Constants; using Jellyfin.Api.Controllers; -using Jellyfin.Server.Converters; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; @@ -76,7 +75,6 @@ namespace Jellyfin.Server.Extensions { // Setting the naming policy to null leaves the property names as-is when serializing objects to JSON. options.JsonSerializerOptions.PropertyNamingPolicy = null; - options.JsonSerializerOptions.Converters.Add(new LongToStringConverter()); }) .AddControllersAsServices(); } diff --git a/MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs b/MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs new file mode 100644 index 000000000..d18fd95d5 --- /dev/null +++ b/MediaBrowser.Common/Json/Converters/JsonInt64Converter.cs @@ -0,0 +1,56 @@ +using System; +using System.Buffers; +using System.Buffers.Text; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MediaBrowser.Common.Json.Converters +{ + /// + /// Long to String JSON converter. + /// Javascript does not support 64-bit integers. + /// + public class JsonInt64Converter : JsonConverter + { + /// + /// Read JSON string as int64. + /// + /// . + /// Type. + /// Options. + /// Parsed value. + public override long Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + // try to parse number directly from bytes + var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; + if (Utf8Parser.TryParse(span, out long number, out var bytesConsumed) && span.Length == bytesConsumed) + { + return number; + } + + // try to parse from a string if the above failed, this covers cases with other escaped/UTF characters + if (long.TryParse(reader.GetString(), out number)) + { + return number; + } + } + + // fallback to default handling + return reader.GetInt64(); + } + + /// + /// Write long to JSON string. + /// + /// . + /// Value to write. + /// Options. + public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString(NumberFormatInfo.InvariantInfo)); + } + } +} diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs index 4a6ee0a79..a7f5fde05 100644 --- a/MediaBrowser.Common/Json/JsonDefaults.cs +++ b/MediaBrowser.Common/Json/JsonDefaults.cs @@ -23,6 +23,7 @@ namespace MediaBrowser.Common.Json options.Converters.Add(new JsonGuidConverter()); options.Converters.Add(new JsonStringEnumConverter()); + options.Converters.Add(new JsonInt64Converter()); return options; } -- cgit v1.2.3 From a661b37c12f203b8941f7bdf2836aa6164439b5c Mon Sep 17 00:00:00 2001 From: artiume Date: Fri, 22 May 2020 14:40:41 -0400 Subject: Update LibraryService.cs --- MediaBrowser.Api/Library/LibraryService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index c0146dfee..5bc9cffa0 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -350,6 +350,7 @@ namespace MediaBrowser.Api.Library _moviesServiceLogger = moviesServiceLogger; } + // Content Types available for each Library private string[] GetRepresentativeItemTypes(string contentType) { return contentType switch @@ -359,7 +360,7 @@ namespace MediaBrowser.Api.Library CollectionType.Movies => new[] {"Movie"}, CollectionType.TvShows => new[] {"Series", "Season", "Episode"}, CollectionType.Books => new[] {"Book"}, - CollectionType.Music => new[] {"MusicAlbum", "MusicArtist", "Audio", "MusicVideo"}, + CollectionType.Music => new[] {"MusicArtist", "MusicAlbum", "Audio", "MusicVideo"}, CollectionType.HomeVideos => new[] {"Video", "Photo"}, CollectionType.Photos => new[] {"Video", "Photo"}, CollectionType.MusicVideos => new[] {"MusicVideo"}, @@ -425,7 +426,6 @@ namespace MediaBrowser.Api.Library return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase) || string.Equals(name, "Screen Grabber", StringComparison.OrdinalIgnoreCase) || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase) - || string.Equals(name, "Emby Designs", StringComparison.OrdinalIgnoreCase) || string.Equals(name, "Image Extractor", StringComparison.OrdinalIgnoreCase); } -- cgit v1.2.3 From 56212e8101ffadcef115a8fa41bae569094f069e Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 22 May 2020 20:20:18 -0400 Subject: Warnings cleanup --- Jellyfin.Data/Entities/AccessSchedule.cs | 48 ++++++---- Jellyfin.Data/Entities/Permission.cs | 104 +++++++-------------- Jellyfin.Server.Implementations/JellyfinDb.cs | 54 +++++------ .../Drawing/ImageProcessorExtensions.cs | 1 - 4 files changed, 94 insertions(+), 113 deletions(-) diff --git a/Jellyfin.Data/Entities/AccessSchedule.cs b/Jellyfin.Data/Entities/AccessSchedule.cs index 711e94dd1..4248a34c9 100644 --- a/Jellyfin.Data/Entities/AccessSchedule.cs +++ b/Jellyfin.Data/Entities/AccessSchedule.cs @@ -6,22 +6,18 @@ using Jellyfin.Data.Enums; namespace Jellyfin.Data.Entities { + /// + /// An entity representing a user's access schedule. + /// 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. + /// The associated user's id. public AccessSchedule(DynamicDayOfWeek dayOfWeek, double startHour, double endHour, Guid userId) { UserId = userId; @@ -31,26 +27,31 @@ namespace Jellyfin.Data.Entities } /// - /// Factory method + /// Initializes a new instance of the class. + /// Default constructor. Protected due to required properties, but present because EF needs it. /// - /// 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, Guid userId) + protected AccessSchedule() { - return new AccessSchedule(dayOfWeek, startHour, endHour, userId); } + /// + /// Gets or sets the id of this instance. + /// + /// + /// Identity, Indexed, Required. + /// [JsonIgnore] [Key] [Required] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; set; } + public int Id { get; protected set; } + /// + /// Gets or sets the id of the associated user. + /// [Required] [ForeignKey("Id")] - public Guid UserId { get; set; } + public Guid UserId { get; protected set; } /// /// Gets or sets the day of week. @@ -72,5 +73,18 @@ namespace Jellyfin.Data.Entities /// The end hour. [Required] public double EndHour { get; set; } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The day of the week. + /// The start hour. + /// The end hour. + /// The associated user's id. + /// The newly created instance. + public static AccessSchedule Create(DynamicDayOfWeek dayOfWeek, double startHour, double endHour, Guid userId) + { + return new AccessSchedule(dayOfWeek, startHour, endHour, userId); + } } } diff --git a/Jellyfin.Data/Entities/Permission.cs b/Jellyfin.Data/Entities/Permission.cs index 706128028..b675e911d 100644 --- a/Jellyfin.Data/Entities/Permission.cs +++ b/Jellyfin.Data/Entities/Permission.cs @@ -1,23 +1,20 @@ -using System; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Runtime.CompilerServices; using Jellyfin.Data.Enums; namespace Jellyfin.Data.Entities { + /// + /// An entity representing whether the associated user has a specific permission. + /// public partial class Permission : ISavingChanges { - partial void Init(); - /// /// Initializes a new instance of the class. - /// Public constructor with required data + /// Public constructor with required data. /// - /// - /// - /// + /// The permission kind. + /// The value of this permission. public Permission(PermissionKind kind, bool value) { Kind = kind; @@ -35,95 +32,66 @@ namespace Jellyfin.Data.Entities Init(); } - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// - /// - /// - public static Permission Create(PermissionKind kind, bool value) - { - return new Permission(kind, value); - } - /************************************************************************* * Properties *************************************************************************/ /// - /// Identity, Indexed, Required + /// Gets or sets the id of this permission. /// + /// + /// Identity, Indexed, Required. + /// [Key] [Required] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; protected set; } /// - /// Backing field for Kind - /// - protected PermissionKind _Kind; - /// - /// When provided in a partial class, allows value of Kind to be changed before setting. - /// - 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 PermissionKind result); - - /// - /// Required + /// Gets or sets the type of this permission. /// + /// + /// Required. + /// [Required] - public PermissionKind Kind - { - get - { - PermissionKind value = _Kind; - GetKind(ref value); - return _Kind = value; - } - - set - { - PermissionKind oldValue = _Kind; - SetKind(oldValue, ref value); - if (oldValue != value) - { - _Kind = value; - OnPropertyChanged(); - } - } - } + public PermissionKind Kind { get; protected set; } /// - /// Required + /// Gets or sets a value indicating whether the associated user has this permission. /// + /// + /// Required. + /// [Required] public bool Value { get; set; } /// - /// Required, ConcurrencyToken. + /// Gets or sets the row version. /// + /// + /// Required, ConcurrencyToken. + /// [ConcurrencyCheck] [Required] public uint RowVersion { get; set; } - public void OnSavingChanges() + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The permission kind. + /// The value of this permission. + /// The newly created instance. + public static Permission Create(PermissionKind kind, bool value) { - RowVersion++; + return new Permission(kind, value); } - /************************************************************************* - * Navigation properties - *************************************************************************/ - - public virtual event PropertyChangedEventHandler PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + /// + public void OnSavingChanges() { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + RowVersion++; } + + partial void Init(); } } - diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 7e10d81dc..89fd371f8 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -1,9 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1201 // Constuctors should not follow properties -#pragma warning disable SA1516 // Elements should be followed by a blank line -#pragma warning disable SA1623 // Property's documentation should begin with gets or sets -#pragma warning disable SA1629 // Documentation should end with a period -#pragma warning disable SA1648 // Inheritdoc should be used with inheriting class using System.Linq; using Jellyfin.Data; @@ -15,6 +10,19 @@ namespace Jellyfin.Server.Implementations /// public partial class JellyfinDb : DbContext { + /// + /// Initializes a new instance of the class. + /// + /// The database context options. + public JellyfinDb(DbContextOptions options) : base(options) + { + } + + /// + /// Gets or sets the default connection string. + /// + public static string ConnectionString { get; set; } = @"Data Source=jellyfin.db"; + public virtual DbSet ActivityLogs { get; set; } public virtual DbSet Groups { get; set; } @@ -69,17 +77,18 @@ namespace Jellyfin.Server.Implementations public virtual DbSet Tracks { get; set; } public virtual DbSet TrackMetadata { get; set; }*/ - /// - /// Gets or sets the default connection string. - /// - public static string ConnectionString { get; set; } = @"Data Source=jellyfin.db"; - - /// - public JellyfinDb(DbContextOptions options) : base(options) + /// + public override int SaveChanges() { - } + foreach (var saveEntity in ChangeTracker.Entries() + .Where(e => e.State == EntityState.Modified) + .OfType()) + { + saveEntity.OnSavingChanges(); + } - partial void CustomInit(DbContextOptionsBuilder optionsBuilder); + return base.SaveChanges(); + } /// protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) @@ -87,9 +96,6 @@ namespace Jellyfin.Server.Implementations CustomInit(optionsBuilder); } - partial void OnModelCreatingImpl(ModelBuilder modelBuilder); - partial void OnModelCreatedImpl(ModelBuilder modelBuilder); - /// protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -109,16 +115,10 @@ namespace Jellyfin.Server.Implementations OnModelCreatedImpl(modelBuilder); } - public override int SaveChanges() - { - foreach (var saveEntity in ChangeTracker.Entries() - .Where(e => e.State == EntityState.Modified) - .OfType()) - { - saveEntity.OnSavingChanges(); - } + partial void CustomInit(DbContextOptionsBuilder optionsBuilder); - return base.SaveChanges(); - } + partial void OnModelCreatingImpl(ModelBuilder modelBuilder); + + partial void OnModelCreatedImpl(ModelBuilder modelBuilder); } } diff --git a/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs b/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs index 9f505be93..df9050de5 100644 --- a/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs +++ b/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs @@ -1,6 +1,5 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Entities; -using User = Jellyfin.Data.Entities.User; namespace MediaBrowser.Controller.Drawing { -- cgit v1.2.3 From e3f9aaa9c62d44854c3ea667c670d6b5a76c0254 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 22 May 2020 21:45:31 -0400 Subject: Fix bugs relating to users not being properly locked out. --- .../Activity/ActivityLogEntryPoint.cs | 18 ++++-------------- Jellyfin.Server.Implementations/Users/UserManager.cs | 7 ++++--- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 31d9609a6..05aa44338 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -104,8 +104,10 @@ namespace Emby.Server.Implementations.Activity _localization.GetLocalizedString("UserLockedOutWithName"), e.Argument.Username), NotificationType.UserLockedOut.ToString(), - e.Argument.Id)) - .ConfigureAwait(false); + e.Argument.Id) + { + LogSeverity = LogLevel.Error + }).ConfigureAwait(false); } private async void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e) @@ -303,18 +305,6 @@ namespace Emby.Server.Implementations.Activity }).ConfigureAwait(false); } - private async void OnUserPolicyUpdated(object sender, GenericEventArgs e) - { - await CreateLogEntry(new ActivityLog( - string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("UserPolicyUpdatedWithName"), - e.Argument.Username), - "UserPolicyUpdated", - e.Argument.Id)) - .ConfigureAwait(false); - } - private async void OnUserDeleted(object sender, GenericEventArgs e) { await CreateLogEntry(new ActivityLog( diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index e16b1fb7b..23646de61 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Runtime.InteropServices.ComTypes; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -800,16 +801,16 @@ namespace Jellyfin.Server.Implementations.Users private void IncrementInvalidLoginAttemptCount(User user) { - int invalidLogins = user.InvalidLoginAttemptCount; + user.InvalidLoginAttemptCount++; int? maxInvalidLogins = user.LoginAttemptsBeforeLockout; - if (maxInvalidLogins.HasValue && invalidLogins >= maxInvalidLogins) + if (maxInvalidLogins.HasValue && user.InvalidLoginAttemptCount >= 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); + user.InvalidLoginAttemptCount); } UpdateUser(user); -- cgit v1.2.3 From 99511b3be8dd63d832336c65b72d0c17efb9bc6b Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 23 May 2020 14:53:24 -0400 Subject: Fix a couple bugs --- .../Users/DefaultAuthenticationProvider.cs | 2 +- Jellyfin.Server.Implementations/Users/UserManager.cs | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs index df730731a..c15312a72 100644 --- a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs @@ -53,7 +53,7 @@ namespace Jellyfin.Server.Implementations.Users bool success = false; // As long as jellyfin supports passwordless users, we need this little block here to accommodate - if (!HasPassword(resolvedUser)) + if (!HasPassword(resolvedUser) && string.IsNullOrEmpty(password)) { return Task.FromResult(new ProviderAuthenticationResult { diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 23646de61..62c4b9b87 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.Linq; using System.Runtime.InteropServices.ComTypes; @@ -617,6 +618,12 @@ namespace Jellyfin.Server.Implementations.Users public void UpdatePolicy(Guid userId, UserPolicy policy) { var user = GetUserById(userId); + int? loginAttempts = policy.LoginAttemptsBeforeLockout switch + { + -1 => null, + 0 => 3, + _ => policy.LoginAttemptsBeforeLockout + }; user.MaxParentalAgeRating = policy.MaxParentalRating; user.EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess; @@ -624,9 +631,7 @@ namespace Jellyfin.Server.Implementations.Users user.AuthenticationProviderId = policy.AuthenticationProviderId; user.PasswordResetProviderId = policy.PasswordResetProviderId; user.InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount; - user.LoginAttemptsBeforeLockout = policy.LoginAttemptsBeforeLockout == -1 - ? null - : new int?(policy.LoginAttemptsBeforeLockout); + user.LoginAttemptsBeforeLockout = loginAttempts; user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator); user.SetPermission(PermissionKind.IsHidden, policy.IsHidden); user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled); -- cgit v1.2.3 From e8173df9dc67470748c3293745fe3948363373b4 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 23 May 2020 15:33:14 -0400 Subject: Cleanup --- Jellyfin.Server.Implementations/Users/UserManager.cs | 2 -- Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 62c4b9b87..7c27a3c41 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -2,10 +2,8 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.Linq; -using System.Runtime.InteropServices.ComTypes; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index a1895247f..a19638abf 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -87,14 +87,13 @@ namespace Jellyfin.Server.Migrations.Routines policy.PasswordResetProviderId = typeof(DefaultPasswordResetProvider).FullName; - var user = new User(mockup.Name, policy.AuthenticationProviderId, string.Empty) + var user = new User(mockup.Name, policy.AuthenticationProviderId, policy.PasswordResetProviderId) { Id = entry[1].ReadGuidFromBlob(), InternalId = entry[0].ToInt64(), MaxParentalAgeRating = policy.MaxParentalRating, EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess, RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit, - PasswordResetProviderId = policy.PasswordResetProviderId, InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount, LoginAttemptsBeforeLockout = policy.LoginAttemptsBeforeLockout == -1 ? null : new int?(policy.LoginAttemptsBeforeLockout), SubtitleMode = config.SubtitleMode, -- cgit v1.2.3 From 07897ec7af468f5e71fd901a8dad05911e19daf5 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sat, 23 May 2020 15:49:02 -0400 Subject: Specify enum values for ExternalIdMediaType explicitly --- .../Providers/ExternalIdMediaType.cs | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/MediaBrowser.Model/Providers/ExternalIdMediaType.cs b/MediaBrowser.Model/Providers/ExternalIdMediaType.cs index 881cd77fc..56f55d15c 100644 --- a/MediaBrowser.Model/Providers/ExternalIdMediaType.cs +++ b/MediaBrowser.Model/Providers/ExternalIdMediaType.cs @@ -12,66 +12,66 @@ namespace MediaBrowser.Model.Providers /// There is no specific media type associated with the external id, or this is the default id for the external /// provider so there is no need to specify a type. /// - General, + General = 0, /// /// A music album. /// - Album, + Album = 1, /// /// The artist of a music album. /// - AlbumArtist, + AlbumArtist = 2, /// /// The artist of a media item. /// - Artist, + Artist = 3, /// /// A boxed set of media. /// - BoxSet, + BoxSet = 4, /// /// A series episode. /// - Episode, + Episode = 5, /// /// A movie. /// - Movie, + Movie = 6, /// /// An alternative artist apart from the main artist. /// - OtherArtist, + OtherArtist = 7, /// /// A person. /// - Person, + Person = 8, /// /// A release group. /// - ReleaseGroup, + ReleaseGroup = 9, /// /// A single season of a series. /// - Season, + Season = 10, /// /// A series. /// - Series, + Series = 11, /// /// A music track. /// - Track + Track = 12 } } -- cgit v1.2.3 From e052128c527672ed586a78257da1d2b07319e598 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 23 May 2020 16:07:42 -0400 Subject: Cleanup and fix more bugs --- Jellyfin.Server.Implementations/Users/UserManager.cs | 9 +++++++-- Jellyfin.Server/CoreAppHost.cs | 4 +--- Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs | 8 +++++++- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 7c27a3c41..41116c251 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -556,6 +556,11 @@ namespace Jellyfin.Server.Implementations.Users _invalidAuthProvider = _authenticationProviders.OfType().First(); _defaultAuthenticationProvider = _authenticationProviders.OfType().First(); _defaultPasswordResetProvider = _passwordResetProviders.OfType().First(); + + if (_authenticationProviders.Length > 2) + { + _logger.LogCritical("INVALID NUMBER OF LOGGERS: {0}", _authenticationProviders.Length); + } } /// @@ -616,7 +621,7 @@ namespace Jellyfin.Server.Implementations.Users public void UpdatePolicy(Guid userId, UserPolicy policy) { var user = GetUserById(userId); - int? loginAttempts = policy.LoginAttemptsBeforeLockout switch + int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch { -1 => null, 0 => 3, @@ -629,7 +634,7 @@ namespace Jellyfin.Server.Implementations.Users user.AuthenticationProviderId = policy.AuthenticationProviderId; user.PasswordResetProviderId = policy.PasswordResetProviderId; user.InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount; - user.LoginAttemptsBeforeLockout = loginAttempts; + user.LoginAttemptsBeforeLockout = maxLoginAttempts; user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator); user.SetPermission(PermissionKind.IsHidden, policy.IsHidden); user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled); diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 81ae38467..716abcb63 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -85,9 +85,7 @@ 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; + yield return Assembly.Load("Jellyfin.Server.Implementations"); } /// diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index a19638abf..af74d3a1d 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -86,6 +86,12 @@ namespace Jellyfin.Server.Migrations.Routines ?? typeof(DefaultAuthenticationProvider).FullName; policy.PasswordResetProviderId = typeof(DefaultPasswordResetProvider).FullName; + int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch + { + -1 => null, + 0 => 3, + _ => policy.LoginAttemptsBeforeLockout + }; var user = new User(mockup.Name, policy.AuthenticationProviderId, policy.PasswordResetProviderId) { @@ -95,7 +101,7 @@ namespace Jellyfin.Server.Migrations.Routines EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess, RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit, InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount, - LoginAttemptsBeforeLockout = policy.LoginAttemptsBeforeLockout == -1 ? null : new int?(policy.LoginAttemptsBeforeLockout), + LoginAttemptsBeforeLockout = maxLoginAttempts, SubtitleMode = config.SubtitleMode, HidePlayedInLatest = config.HidePlayedInLatest, EnableLocalPassword = config.EnableLocalPassword, -- cgit v1.2.3 From 4f6e5591ece8d9344385d13f923384abfc07b709 Mon Sep 17 00:00:00 2001 From: Mark Monteiro Date: Sat, 23 May 2020 16:08:51 -0400 Subject: Remove 'General' as an ExternalIdMediaType, and instead use 'null' to represent a general external id type --- MediaBrowser.Controller/Providers/IExternalId.cs | 4 +++- MediaBrowser.Model/Providers/ExternalIdInfo.cs | 4 +++- MediaBrowser.Model/Providers/ExternalIdMediaType.cs | 6 ------ MediaBrowser.Providers/Movies/MovieExternalIds.cs | 4 ++-- MediaBrowser.Providers/Music/MusicExternalIds.cs | 2 +- MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs | 8 ++++---- MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs | 12 ++++++------ MediaBrowser.Providers/TV/TvExternalIds.cs | 8 ++++---- MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetExternalId.cs | 2 +- MediaBrowser.Providers/Tmdb/Movies/TmdbMovieExternalId.cs | 2 +- MediaBrowser.Providers/Tmdb/People/TmdbPersonExternalId.cs | 2 +- MediaBrowser.Providers/Tmdb/TV/TmdbSeriesExternalId.cs | 2 +- 12 files changed, 27 insertions(+), 29 deletions(-) diff --git a/MediaBrowser.Controller/Providers/IExternalId.cs b/MediaBrowser.Controller/Providers/IExternalId.cs index 96d1e4622..5e38446bc 100644 --- a/MediaBrowser.Controller/Providers/IExternalId.cs +++ b/MediaBrowser.Controller/Providers/IExternalId.cs @@ -22,11 +22,13 @@ namespace MediaBrowser.Controller.Providers /// /// Gets the specific media type for this id. This is used to distinguish between the different /// external id types for providers with multiple ids. + /// A null value indicates there is no specific media type associated with the external id, or this is the + /// default id for the external provider so there is no need to specify a type. /// /// /// This can be used along with the to localize the external id on the client. /// - ExternalIdMediaType Type { get; } + ExternalIdMediaType? Type { get; } /// /// Gets the URL format string for this id. diff --git a/MediaBrowser.Model/Providers/ExternalIdInfo.cs b/MediaBrowser.Model/Providers/ExternalIdInfo.cs index 445c86d73..7687e676f 100644 --- a/MediaBrowser.Model/Providers/ExternalIdInfo.cs +++ b/MediaBrowser.Model/Providers/ExternalIdInfo.cs @@ -20,11 +20,13 @@ namespace MediaBrowser.Model.Providers /// /// Gets or sets the specific media type for this id. This is used to distinguish between the different /// external id types for providers with multiple ids. + /// A null value indicates there is no specific media type associated with the external id, or this is the + /// default id for the external provider so there is no need to specify a type. /// /// /// This can be used along with the to localize the external id on the client. /// - public ExternalIdMediaType Type { get; set; } + public ExternalIdMediaType? Type { get; set; } /// /// Gets or sets the URL format string. diff --git a/MediaBrowser.Model/Providers/ExternalIdMediaType.cs b/MediaBrowser.Model/Providers/ExternalIdMediaType.cs index 56f55d15c..5303c8f58 100644 --- a/MediaBrowser.Model/Providers/ExternalIdMediaType.cs +++ b/MediaBrowser.Model/Providers/ExternalIdMediaType.cs @@ -8,12 +8,6 @@ namespace MediaBrowser.Model.Providers /// public enum ExternalIdMediaType { - /// - /// There is no specific media type associated with the external id, or this is the default id for the external - /// provider so there is no need to specify a type. - /// - General = 0, - /// /// A music album. /// diff --git a/MediaBrowser.Providers/Movies/MovieExternalIds.cs b/MediaBrowser.Providers/Movies/MovieExternalIds.cs index 2b0c0d1c2..b43ae63ab 100644 --- a/MediaBrowser.Providers/Movies/MovieExternalIds.cs +++ b/MediaBrowser.Providers/Movies/MovieExternalIds.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.Providers.Movies public string Key => MetadataProviders.Imdb.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.General; + public ExternalIdMediaType? Type => null; /// public string UrlFormatString => "https://www.imdb.com/title/{0}"; @@ -44,7 +44,7 @@ namespace MediaBrowser.Providers.Movies public string Key => MetadataProviders.Imdb.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.Person; + public ExternalIdMediaType? Type => ExternalIdMediaType.Person; /// public string UrlFormatString => "https://www.imdb.com/name/{0}"; diff --git a/MediaBrowser.Providers/Music/MusicExternalIds.cs b/MediaBrowser.Providers/Music/MusicExternalIds.cs index 4490d0f05..42694fdee 100644 --- a/MediaBrowser.Providers/Music/MusicExternalIds.cs +++ b/MediaBrowser.Providers/Music/MusicExternalIds.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.Music public string Key => "IMVDb"; /// - public ExternalIdMediaType Type => ExternalIdMediaType.General; + public ExternalIdMediaType? Type => null; /// public string UrlFormatString => null; diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs b/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs index e299eb3ee..1dd5b21a9 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public string Key => MetadataProviders.AudioDbAlbum.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.General; + public ExternalIdMediaType? Type => null; /// public string UrlFormatString => "https://www.theaudiodb.com/album/{0}"; @@ -32,7 +32,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public string Key => MetadataProviders.AudioDbAlbum.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.Album; + public ExternalIdMediaType? Type => ExternalIdMediaType.Album; /// public string UrlFormatString => "https://www.theaudiodb.com/album/{0}"; @@ -50,7 +50,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public string Key => MetadataProviders.AudioDbArtist.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.Artist; + public ExternalIdMediaType? Type => ExternalIdMediaType.Artist; /// public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}"; @@ -68,7 +68,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public string Key => MetadataProviders.AudioDbArtist.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.OtherArtist; + public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist; /// public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}"; diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs index 247e87fd5..969bdd01d 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Providers.Music public string Key => MetadataProviders.MusicBrainzReleaseGroup.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.ReleaseGroup; + public ExternalIdMediaType? Type => ExternalIdMediaType.ReleaseGroup; /// public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release-group/{0}"; @@ -33,7 +33,7 @@ namespace MediaBrowser.Providers.Music public string Key => MetadataProviders.MusicBrainzAlbumArtist.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.AlbumArtist; + public ExternalIdMediaType? Type => ExternalIdMediaType.AlbumArtist; /// public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; @@ -51,7 +51,7 @@ namespace MediaBrowser.Providers.Music public string Key => MetadataProviders.MusicBrainzAlbum.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.Album; + public ExternalIdMediaType? Type => ExternalIdMediaType.Album; /// public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release/{0}"; @@ -69,7 +69,7 @@ namespace MediaBrowser.Providers.Music public string Key => MetadataProviders.MusicBrainzArtist.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.Artist; + public ExternalIdMediaType? Type => ExternalIdMediaType.Artist; /// public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; @@ -88,7 +88,7 @@ namespace MediaBrowser.Providers.Music public string Key => MetadataProviders.MusicBrainzArtist.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.OtherArtist; + public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist; /// public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; @@ -106,7 +106,7 @@ namespace MediaBrowser.Providers.Music public string Key => MetadataProviders.MusicBrainzTrack.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.Track; + public ExternalIdMediaType? Type => ExternalIdMediaType.Track; /// public string UrlFormatString => Plugin.Instance.Configuration.Server + "/track/{0}"; diff --git a/MediaBrowser.Providers/TV/TvExternalIds.cs b/MediaBrowser.Providers/TV/TvExternalIds.cs index 12ad3d8a2..2bf6020dd 100644 --- a/MediaBrowser.Providers/TV/TvExternalIds.cs +++ b/MediaBrowser.Providers/TV/TvExternalIds.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Providers.TV public string Key => MetadataProviders.Zap2It.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.General; + public ExternalIdMediaType? Type => null; /// public string UrlFormatString => "http://tvlistings.zap2it.com/overview.html?programSeriesId={0}"; @@ -33,7 +33,7 @@ namespace MediaBrowser.Providers.TV public string Key => MetadataProviders.Tvdb.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.General; + public ExternalIdMediaType? Type => null; /// public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=series&id={0}"; @@ -52,7 +52,7 @@ namespace MediaBrowser.Providers.TV public string Key => MetadataProviders.Tvdb.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.Season; + public ExternalIdMediaType? Type => ExternalIdMediaType.Season; /// public string UrlFormatString => null; @@ -70,7 +70,7 @@ namespace MediaBrowser.Providers.TV public string Key => MetadataProviders.Tvdb.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.Episode; + public ExternalIdMediaType? Type => ExternalIdMediaType.Episode; /// public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=episode&id={0}"; diff --git a/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetExternalId.cs b/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetExternalId.cs index 1d3c80536..bfef1e038 100644 --- a/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetExternalId.cs +++ b/MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetExternalId.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Providers.Tmdb.BoxSets public string Key => MetadataProviders.TmdbCollection.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.BoxSet; + public ExternalIdMediaType? Type => ExternalIdMediaType.BoxSet; /// public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "collection/{0}"; diff --git a/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieExternalId.cs b/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieExternalId.cs index 75e71dda4..5b0cd7509 100644 --- a/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieExternalId.cs +++ b/MediaBrowser.Providers/Tmdb/Movies/TmdbMovieExternalId.cs @@ -16,7 +16,7 @@ namespace MediaBrowser.Providers.Tmdb.Movies public string Key => MetadataProviders.Tmdb.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.Movie; + public ExternalIdMediaType? Type => ExternalIdMediaType.Movie; /// public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "movie/{0}"; diff --git a/MediaBrowser.Providers/Tmdb/People/TmdbPersonExternalId.cs b/MediaBrowser.Providers/Tmdb/People/TmdbPersonExternalId.cs index a8685d669..fa12a4581 100644 --- a/MediaBrowser.Providers/Tmdb/People/TmdbPersonExternalId.cs +++ b/MediaBrowser.Providers/Tmdb/People/TmdbPersonExternalId.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.Tmdb.People public string Key => MetadataProviders.Tmdb.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.Person; + public ExternalIdMediaType? Type => ExternalIdMediaType.Person; /// public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "person/{0}"; diff --git a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesExternalId.cs b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesExternalId.cs index fd6dd9b41..8513dadd2 100644 --- a/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesExternalId.cs +++ b/MediaBrowser.Providers/Tmdb/TV/TmdbSeriesExternalId.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Providers.Tmdb.TV public string Key => MetadataProviders.Tmdb.ToString(); /// - public ExternalIdMediaType Type => ExternalIdMediaType.Series; + public ExternalIdMediaType? Type => ExternalIdMediaType.Series; /// public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "tv/{0}"; -- cgit v1.2.3 From a4b3f2e32b68a61407876ab11343936b14cc1191 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 23 May 2020 18:19:49 -0600 Subject: Add missing route attribute --- Jellyfin.Api/Controllers/ChannelsController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs index 733f1e6d8..8e0f76697 100644 --- a/Jellyfin.Api/Controllers/ChannelsController.cs +++ b/Jellyfin.Api/Controllers/ChannelsController.cs @@ -183,6 +183,7 @@ namespace Jellyfin.Api.Controllers /// Optional. Specify one or more channel id's, comma delimited. /// Latest channel items returned. /// Latest channel items. + [HttpGet("Items/Latest")] public async Task>> GetLatestChannelItems( [FromQuery] Guid? userId, [FromQuery] int? startIndex, -- cgit v1.2.3 From 7972daaba43f48e5047f232f0ec1475fc8648b16 Mon Sep 17 00:00:00 2001 From: dkanada Date: Sun, 24 May 2020 15:04:10 +0900 Subject: fix a few issues with the plugin manifest --- .../Activity/ActivityLogEntryPoint.cs | 18 +-- .../EntryPoints/ServerEventNotifier.cs | 17 +-- .../ScheduledTasks/Tasks/PluginUpdateTask.cs | 4 +- .../Updates/InstallationManager.cs | 133 ++++++++++----------- .../Updates/IInstallationManager.cs | 28 ++--- MediaBrowser.Model/Updates/InstallationInfo.cs | 22 +++- MediaBrowser.Model/Updates/VersionInfo.cs | 20 +--- 7 files changed, 111 insertions(+), 131 deletions(-) diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 3983824a3..6917efefa 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -377,50 +377,50 @@ namespace Emby.Server.Implementations.Activity }).ConfigureAwait(false); } - private async void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, VersionInfo)> e) + private async void OnPluginUpdated(object sender, InstallationInfo e) { await CreateLogEntry(new ActivityLog( string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("PluginUpdatedWithName"), - e.Argument.Item1.Name), + e.Name), NotificationType.PluginUpdateInstalled.ToString(), Guid.Empty) { ShortOverview = string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("VersionNumber"), - e.Argument.Item2.version), - Overview = e.Argument.Item2.changelog + e.Version), + Overview = e.Changelog }).ConfigureAwait(false); } - private async void OnPluginUninstalled(object sender, GenericEventArgs e) + private async void OnPluginUninstalled(object sender, IPlugin e) { await CreateLogEntry(new ActivityLog( string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("PluginUninstalledWithName"), - e.Argument.Name), + e.Name), NotificationType.PluginUninstalled.ToString(), Guid.Empty)) .ConfigureAwait(false); } - private async void OnPluginInstalled(object sender, GenericEventArgs e) + private async void OnPluginInstalled(object sender, InstallationInfo e) { await CreateLogEntry(new ActivityLog( string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("PluginInstalledWithName"), - e.Argument.name), + e.Name), NotificationType.PluginInstalled.ToString(), Guid.Empty) { ShortOverview = string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("VersionNumber"), - e.Argument.version) + e.Version) }).ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs index e1dbb663b..b323a0a95 100644 --- a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs @@ -12,6 +12,7 @@ using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Events; using MediaBrowser.Model.Tasks; +using MediaBrowser.Model.Updates; namespace Emby.Server.Implementations.EntryPoints { @@ -85,19 +86,19 @@ namespace Emby.Server.Implementations.EntryPoints return Task.CompletedTask; } - private void OnPackageInstalling(object sender, InstallationEventArgs e) + private void OnPackageInstalling(object sender, InstallationInfo e) { - SendMessageToAdminSessions("PackageInstalling", e.InstallationInfo); + SendMessageToAdminSessions("PackageInstalling", e); } - private void OnPackageInstallationCancelled(object sender, InstallationEventArgs e) + private void OnPackageInstallationCancelled(object sender, InstallationInfo e) { - SendMessageToAdminSessions("PackageInstallationCancelled", e.InstallationInfo); + SendMessageToAdminSessions("PackageInstallationCancelled", e); } - private void OnPackageInstallationCompleted(object sender, InstallationEventArgs e) + private void OnPackageInstallationCompleted(object sender, InstallationInfo e) { - SendMessageToAdminSessions("PackageInstallationCompleted", e.InstallationInfo); + SendMessageToAdminSessions("PackageInstallationCompleted", e); } private void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e) @@ -115,9 +116,9 @@ namespace Emby.Server.Implementations.EntryPoints /// /// The sender. /// The e. - private void OnPluginUninstalled(object sender, GenericEventArgs e) + private void OnPluginUninstalled(object sender, IPlugin e) { - SendMessageToAdminSessions("PluginUninstalled", e.Argument.GetPluginInfo()); + SendMessageToAdminSessions("PluginUninstalled", e); } /// diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs index 6a1afced7..e7a5e2e3d 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs @@ -80,11 +80,11 @@ namespace Emby.Server.Implementations.ScheduledTasks } catch (HttpException ex) { - _logger.LogError(ex, "Error downloading {0}", package.name); + _logger.LogError(ex, "Error downloading {0}", package.Name); } catch (IOException ex) { - _logger.LogError(ex, "Error updating {0}", package.name); + _logger.LogError(ex, "Error updating {0}", package.Name); } // Update progress diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 0b2309889..5312e0ef1 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; @@ -97,25 +98,25 @@ namespace Emby.Server.Implementations.Updates } /// - public event EventHandler PackageInstalling; + public event EventHandler PackageInstalling; /// - public event EventHandler PackageInstallationCompleted; + public event EventHandler PackageInstallationCompleted; /// public event EventHandler PackageInstallationFailed; /// - public event EventHandler PackageInstallationCancelled; + public event EventHandler PackageInstallationCancelled; /// - public event EventHandler> PluginUninstalled; + public event EventHandler PluginUninstalled; /// - public event EventHandler> PluginUpdated; + public event EventHandler PluginUpdated; /// - public event EventHandler> PluginInstalled; + public event EventHandler PluginInstalled; /// public IEnumerable CompletedInstallations => _completedInstallationsInternal; @@ -183,24 +184,7 @@ namespace Emby.Server.Implementations.Updates } /// - public IEnumerable GetCompatibleVersions( - IEnumerable availableVersions, - Version minVersion = null) - { - var appVer = _applicationHost.ApplicationVersion; - availableVersions = availableVersions - .Where(x => Version.Parse(x.targetAbi) <= appVer); - - if (minVersion != null) - { - availableVersions = availableVersions.Where(x => x.version >= minVersion); - } - - return availableVersions.OrderByDescending(x => x.version); - } - - /// - public IEnumerable GetCompatibleVersions( + public IEnumerable GetCompatibleVersions( IEnumerable availablePackages, string name = null, Guid guid = default, @@ -211,28 +195,46 @@ namespace Emby.Server.Implementations.Updates // Package not found in repository if (package == null) { - return Enumerable.Empty(); + yield break; + } + + var appVer = _applicationHost.ApplicationVersion; + var availableVersions = package.versions + .Where(x => Version.Parse(x.targetAbi) <= appVer); + + if (minVersion != null) + { + availableVersions = availableVersions + .Where(x => new Version(x.version) >= minVersion) + .OrderByDescending(x => x.version); } - return GetCompatibleVersions( - package.versions, - minVersion); + foreach (var v in availableVersions) + { + yield return new InstallationInfo + { + Changelog = v.changelog, + Guid = new Guid(package.guid), + Name = package.name, + Version = new Version(v.version) + }; + } } /// - public async Task> GetAvailablePluginUpdates(CancellationToken cancellationToken = default) + public async Task> GetAvailablePluginUpdates(CancellationToken cancellationToken = default) { var catalog = await GetAvailablePackages(cancellationToken).ConfigureAwait(false); return GetAvailablePluginUpdates(catalog); } - private IEnumerable GetAvailablePluginUpdates(IReadOnlyList pluginCatalog) + private IEnumerable GetAvailablePluginUpdates(IReadOnlyList pluginCatalog) { foreach (var plugin in _applicationHost.Plugins) { var compatibleversions = GetCompatibleVersions(pluginCatalog, plugin.Name, plugin.Id, plugin.Version); - var version = compatibleversions.FirstOrDefault(y => y.version > plugin.Version); - if (version != null && !CompletedInstallations.Any(x => string.Equals(x.Guid, version.guid, StringComparison.OrdinalIgnoreCase))) + var version = compatibleversions.FirstOrDefault(y => y.Version > plugin.Version); + if (version != null && CompletedInstallations.All(x => x.Guid != version.Guid)) { yield return version; } @@ -240,23 +242,16 @@ namespace Emby.Server.Implementations.Updates } /// - public async Task InstallPackage(VersionInfo package, CancellationToken cancellationToken) + public async Task InstallPackage(InstallationInfo package, CancellationToken cancellationToken) { if (package == null) { throw new ArgumentNullException(nameof(package)); } - var installationInfo = new InstallationInfo - { - Guid = package.guid, - Name = package.name, - Version = package.version.ToString() - }; - var innerCancellationTokenSource = new CancellationTokenSource(); - var tuple = (installationInfo, innerCancellationTokenSource); + var tuple = (package, innerCancellationTokenSource); // Add it to the in-progress list lock (_currentInstallationsLock) @@ -266,13 +261,7 @@ namespace Emby.Server.Implementations.Updates var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, innerCancellationTokenSource.Token).Token; - var installationEventArgs = new InstallationEventArgs - { - InstallationInfo = installationInfo, - VersionInfo = package - }; - - PackageInstalling?.Invoke(this, installationEventArgs); + PackageInstalling?.Invoke(this, package); try { @@ -283,9 +272,9 @@ namespace Emby.Server.Implementations.Updates _currentInstallations.Remove(tuple); } - _completedInstallationsInternal.Add(installationInfo); + _completedInstallationsInternal.Add(package); - PackageInstallationCompleted?.Invoke(this, installationEventArgs); + PackageInstallationCompleted?.Invoke(this, package); } catch (OperationCanceledException) { @@ -294,9 +283,9 @@ namespace Emby.Server.Implementations.Updates _currentInstallations.Remove(tuple); } - _logger.LogInformation("Package installation cancelled: {0} {1}", package.name, package.version); + _logger.LogInformation("Package installation cancelled: {0} {1}", package.Name, package.Version); - PackageInstallationCancelled?.Invoke(this, installationEventArgs); + PackageInstallationCancelled?.Invoke(this, package); throw; } @@ -311,7 +300,7 @@ namespace Emby.Server.Implementations.Updates PackageInstallationFailed?.Invoke(this, new InstallationFailedEventArgs { - InstallationInfo = installationInfo, + InstallationInfo = package, Exception = ex }); @@ -330,11 +319,11 @@ namespace Emby.Server.Implementations.Updates /// The package. /// The cancellation token. /// . - private async Task InstallPackageInternal(VersionInfo package, CancellationToken cancellationToken) + private async Task InstallPackageInternal(InstallationInfo package, CancellationToken cancellationToken) { // Set last update time if we were installed before - IPlugin plugin = _applicationHost.Plugins.FirstOrDefault(p => string.Equals(p.Id.ToString(), package.guid, StringComparison.OrdinalIgnoreCase)) - ?? _applicationHost.Plugins.FirstOrDefault(p => p.Name.Equals(package.name, StringComparison.OrdinalIgnoreCase)); + IPlugin plugin = _applicationHost.Plugins.FirstOrDefault(p => p.Id == package.Guid) + ?? _applicationHost.Plugins.FirstOrDefault(p => p.Name.Equals(package.Name, StringComparison.OrdinalIgnoreCase)); // Do the install await PerformPackageInstallation(package, cancellationToken).ConfigureAwait(false); @@ -342,38 +331,38 @@ namespace Emby.Server.Implementations.Updates // Do plugin-specific processing if (plugin == null) { - _logger.LogInformation("New plugin installed: {0} {1} {2}", package.name, package.version); + _logger.LogInformation("New plugin installed: {0} {1} {2}", package.Name, package.Version); - PluginInstalled?.Invoke(this, new GenericEventArgs(package)); + PluginInstalled?.Invoke(this, package); } else { - _logger.LogInformation("Plugin updated: {0} {1} {2}", package.name, package.version); + _logger.LogInformation("Plugin updated: {0} {1} {2}", package.Name, package.Version); - PluginUpdated?.Invoke(this, new GenericEventArgs<(IPlugin, VersionInfo)>((plugin, package))); + PluginUpdated?.Invoke(this, package); } _applicationHost.NotifyPendingRestart(); } - private async Task PerformPackageInstallation(VersionInfo package, CancellationToken cancellationToken) + private async Task PerformPackageInstallation(InstallationInfo package, CancellationToken cancellationToken) { - var extension = Path.GetExtension(package.filename); + var extension = Path.GetExtension(package.SourceUrl); if (!string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase)) { - _logger.LogError("Only zip packages are supported. {Filename} is not a zip archive.", package.filename); + _logger.LogError("Only zip packages are supported. {Filename} is not a zip archive.", package.SourceUrl); return; } // Always override the passed-in target (which is a file) and figure it out again - string targetDir = Path.Combine(_appPaths.PluginsPath, package.name); + string targetDir = Path.Combine(_appPaths.PluginsPath, package.Name); // CA5351: Do Not Use Broken Cryptographic Algorithms #pragma warning disable CA5351 using (var res = await _httpClient.SendAsync( new HttpRequestOptions { - Url = package.sourceUrl, + Url = package.SourceUrl, CancellationToken = cancellationToken, // We need it to be buffered for setting the position BufferContent = true @@ -385,12 +374,12 @@ namespace Emby.Server.Implementations.Updates cancellationToken.ThrowIfCancellationRequested(); var hash = Hex.Encode(md5.ComputeHash(stream)); - if (!string.Equals(package.checksum, hash, StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(package.Checksum, hash, StringComparison.OrdinalIgnoreCase)) { _logger.LogError( "The checksums didn't match while installing {Package}, expected: {Expected}, got: {Received}", - package.name, - package.checksum, + package.Name, + package.Checksum, hash); throw new InvalidDataException("The checksum of the received data doesn't match."); } @@ -456,7 +445,7 @@ namespace Emby.Server.Implementations.Updates _config.SaveConfiguration(); } - PluginUninstalled?.Invoke(this, new GenericEventArgs { Argument = plugin }); + PluginUninstalled?.Invoke(this, plugin); _applicationHost.NotifyPendingRestart(); } @@ -466,7 +455,7 @@ namespace Emby.Server.Implementations.Updates { lock (_currentInstallationsLock) { - var install = _currentInstallations.Find(x => x.info.Guid == id.ToString()); + var install = _currentInstallations.Find(x => x.info.Guid == id); if (install == default((InstallationInfo, CancellationTokenSource))) { return false; @@ -486,9 +475,9 @@ namespace Emby.Server.Implementations.Updates } /// - /// 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. + /// true to release both managed and unmanaged resources or false to release only unmanaged resources. protected virtual void Dispose(bool dispose) { if (dispose) diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs index 950604432..965ffe0ec 100644 --- a/MediaBrowser.Common/Updates/IInstallationManager.cs +++ b/MediaBrowser.Common/Updates/IInstallationManager.cs @@ -12,28 +12,28 @@ namespace MediaBrowser.Common.Updates { public interface IInstallationManager : IDisposable { - event EventHandler PackageInstalling; + event EventHandler PackageInstalling; - event EventHandler PackageInstallationCompleted; + event EventHandler PackageInstallationCompleted; event EventHandler PackageInstallationFailed; - event EventHandler PackageInstallationCancelled; + event EventHandler PackageInstallationCancelled; /// /// Occurs when a plugin is uninstalled. /// - event EventHandler> PluginUninstalled; + event EventHandler PluginUninstalled; /// /// Occurs when a plugin is updated. /// - event EventHandler> PluginUpdated; + event EventHandler PluginUpdated; /// /// Occurs when a plugin is installed. /// - event EventHandler> PluginInstalled; + event EventHandler PluginInstalled; /// /// Gets the completed installations. @@ -59,16 +59,6 @@ namespace MediaBrowser.Common.Updates string name = null, Guid guid = default); - /// - /// Returns all compatible versions ordered from newest to oldest. - /// - /// The available version of the plugin. - /// The minimum required version of the plugin. - /// All compatible versions ordered from newest to oldest. - IEnumerable GetCompatibleVersions( - IEnumerable availableVersions, - Version minVersion = null); - /// /// Returns all compatible versions ordered from newest to oldest. /// @@ -77,7 +67,7 @@ namespace MediaBrowser.Common.Updates /// The guid of the plugin. /// The minimum required version of the plugin. /// All compatible versions ordered from newest to oldest. - IEnumerable GetCompatibleVersions( + IEnumerable GetCompatibleVersions( IEnumerable availablePackages, string name = null, Guid guid = default, @@ -88,7 +78,7 @@ namespace MediaBrowser.Common.Updates /// /// The cancellation token. /// The available plugin updates. - Task> GetAvailablePluginUpdates(CancellationToken cancellationToken = default); + Task> GetAvailablePluginUpdates(CancellationToken cancellationToken = default); /// /// Installs the package. @@ -96,7 +86,7 @@ namespace MediaBrowser.Common.Updates /// The package. /// The cancellation token. /// . - Task InstallPackage(VersionInfo package, CancellationToken cancellationToken = default); + Task InstallPackage(InstallationInfo package, CancellationToken cancellationToken = default); /// /// Uninstalls a plugin. diff --git a/MediaBrowser.Model/Updates/InstallationInfo.cs b/MediaBrowser.Model/Updates/InstallationInfo.cs index e0d450d06..5d31bac3c 100644 --- a/MediaBrowser.Model/Updates/InstallationInfo.cs +++ b/MediaBrowser.Model/Updates/InstallationInfo.cs @@ -11,7 +11,7 @@ namespace MediaBrowser.Model.Updates /// Gets or sets the guid. /// /// The guid. - public string Guid { get; set; } + public Guid Guid { get; set; } /// /// Gets or sets the name. @@ -23,6 +23,24 @@ namespace MediaBrowser.Model.Updates /// Gets or sets the version. /// /// The version. - public string Version { get; set; } + public Version Version { get; set; } + + /// + /// Gets or sets the changelog for this version. + /// + /// The changelog. + public string Changelog { get; set; } + + /// + /// Gets or sets the source URL. + /// + /// The source URL. + public string SourceUrl { get; set; } + + /// + /// Gets or sets a checksum for the binary. + /// + /// The checksum. + public string Checksum { get; set; } } } diff --git a/MediaBrowser.Model/Updates/VersionInfo.cs b/MediaBrowser.Model/Updates/VersionInfo.cs index fe5826ad2..368f489e2 100644 --- a/MediaBrowser.Model/Updates/VersionInfo.cs +++ b/MediaBrowser.Model/Updates/VersionInfo.cs @@ -7,23 +7,11 @@ namespace MediaBrowser.Model.Updates /// public class VersionInfo { - /// - /// Gets or sets the name. - /// - /// The name. - public string name { get; set; } - - /// - /// Gets or sets the guid. - /// - /// The guid. - public string guid { get; set; } - /// /// Gets or sets the version. /// /// The version. - public Version version { get; set; } + public string version { get; set; } /// /// Gets or sets the changelog for this version. @@ -48,11 +36,5 @@ namespace MediaBrowser.Model.Updates /// /// The checksum. public string checksum { get; set; } - - /// - /// Gets or sets the target filename for the downloaded binary. - /// - /// The target filename. - public string filename { get; set; } } } -- cgit v1.2.3 From 6d3e5d86626fb4d3ac80ad52b9446522ddfc9047 Mon Sep 17 00:00:00 2001 From: dkanada Date: Sun, 24 May 2020 15:53:17 +0900 Subject: update error log for plugin download --- Emby.Server.Implementations/Updates/InstallationManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 5312e0ef1..9a49ac86c 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -350,7 +350,7 @@ namespace Emby.Server.Implementations.Updates var extension = Path.GetExtension(package.SourceUrl); if (!string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase)) { - _logger.LogError("Only zip packages are supported. {Filename} is not a zip archive.", package.SourceUrl); + _logger.LogError("Only zip packages are supported. {SourceUrl} is not a zip archive.", package.SourceUrl); return; } -- cgit v1.2.3 From deafe59b7ef4651415280255335b9b5e3f4dcacb Mon Sep 17 00:00:00 2001 From: dkanada Date: Sun, 24 May 2020 16:59:05 +0900 Subject: add the timestamp property back to the version info --- MediaBrowser.Model/Updates/VersionInfo.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MediaBrowser.Model/Updates/VersionInfo.cs b/MediaBrowser.Model/Updates/VersionInfo.cs index 368f489e2..f12e35dc0 100644 --- a/MediaBrowser.Model/Updates/VersionInfo.cs +++ b/MediaBrowser.Model/Updates/VersionInfo.cs @@ -36,5 +36,11 @@ namespace MediaBrowser.Model.Updates /// /// The checksum. public string checksum { get; set; } + + /// + /// Gets or sets a timestamp of when the binary was built. + /// + /// The timestamp. + public string timestamp { get; set; } } } -- cgit v1.2.3 From 09915363c2d5f2febed41e803476c7ec6049a06e Mon Sep 17 00:00:00 2001 From: Neil Burrows Date: Sun, 24 May 2020 09:22:13 +0100 Subject: Update Emby.Server.Implementations/Udp/UdpServer.cs Co-authored-by: Mark Monteiro --- Emby.Server.Implementations/Udp/UdpServer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Udp/UdpServer.cs b/Emby.Server.Implementations/Udp/UdpServer.cs index 1ae3888dc..a26f714b1 100644 --- a/Emby.Server.Implementations/Udp/UdpServer.cs +++ b/Emby.Server.Implementations/Udp/UdpServer.cs @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Udp private readonly IConfiguration _config; /// - /// Address Override Configuration Key + /// Address Override Configuration Key. /// public const string AddressOverrideConfigKey = "PublishedServerUrl"; -- cgit v1.2.3 From 70c42eb0acd0e6572f3ca9313716a8dd71b247ee Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 24 May 2020 12:19:26 -0600 Subject: Apply review suggestions --- Jellyfin.Api/Controllers/ChannelsController.cs | 33 ++++++---- Jellyfin.Api/Extensions/RequestExtensions.cs | 90 -------------------------- Jellyfin.Api/Helpers/RequestHelpers.cs | 90 ++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 104 deletions(-) delete mode 100644 Jellyfin.Api/Extensions/RequestExtensions.cs create mode 100644 Jellyfin.Api/Helpers/RequestHelpers.cs diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs index 8e0f76697..7c055874d 100644 --- a/Jellyfin.Api/Controllers/ChannelsController.cs +++ b/Jellyfin.Api/Controllers/ChannelsController.cs @@ -42,14 +42,14 @@ namespace Jellyfin.Api.Controllers /// /// Gets available channels. /// - /// User Id. + /// User Id to filter by. Use to not filter by user. /// Optional. The record index to start at. All items with a lower index will be dropped from the results. /// Optional. The maximum number of records to return. /// Optional. Filter by channels that support getting latest items. /// Optional. Filter by channels that support media deletion. /// Optional. Filter by channels that are favorite. /// Channels returned. - /// Channels. + /// An containing the channels. [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetChannels( @@ -75,10 +75,10 @@ namespace Jellyfin.Api.Controllers /// Get all channel features. /// /// All channel features returned. - /// Channel features. + /// An containing the channel features. [HttpGet("Features")] [ProducesResponseType(StatusCodes.Status200OK)] - public IEnumerable GetAllChannelFeatures() + public ActionResult> GetAllChannelFeatures() { return _channelManager.GetAllChannelFeatures(); } @@ -88,7 +88,7 @@ namespace Jellyfin.Api.Controllers /// /// Channel id. /// Channel features returned. - /// Channel features. + /// An containing the channel features. [HttpGet("{Id}/Features")] public ActionResult GetChannelFeatures([FromRoute] string id) { @@ -99,11 +99,11 @@ namespace Jellyfin.Api.Controllers /// Get channel items. /// /// Channel Id. - /// Folder Id. - /// User Id. + /// Optional. Folder Id. + /// Optional. User Id. /// Optional. The record index to start at. All items with a lower index will be dropped from the results. /// Optional. The maximum number of records to return. - /// Sort Order - Ascending,Descending. + /// Optional. Sort Order - Ascending,Descending. /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. /// Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime. /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. @@ -175,14 +175,17 @@ namespace Jellyfin.Api.Controllers /// /// Gets latest channel items. /// - /// User Id. + /// Optional. User Id. /// Optional. The record index to start at. All items with a lower index will be dropped from the results. /// Optional. The maximum number of records to return. /// Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes. /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. /// Optional. Specify one or more channel id's, comma delimited. /// Latest channel items returned. - /// Latest channel items. + /// + /// A representing the request to get the latest channel items. + /// The task result contains an containing the latest channel items. + /// [HttpGet("Items/Latest")] public async Task>> GetLatestChannelItems( [FromQuery] Guid? userId, @@ -192,7 +195,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] string fields, [FromQuery] string channelIds) { - var user = userId == null + var user = userId == null || userId == Guid.Empty ? null : _userManager.GetUserById(userId.Value); @@ -200,9 +203,11 @@ namespace Jellyfin.Api.Controllers { Limit = limit, StartIndex = startIndex, - ChannelIds = - (channelIds ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)) - .Select(i => new Guid(i)).ToArray(), + ChannelIds = (channelIds ?? string.Empty) + .Split(',') + .Where(i => !string.IsNullOrWhiteSpace(i)) + .Select(i => new Guid(i)) + .ToArray(), DtoOptions = new DtoOptions { Fields = RequestExtensions.GetItemFields(fields) } }; diff --git a/Jellyfin.Api/Extensions/RequestExtensions.cs b/Jellyfin.Api/Extensions/RequestExtensions.cs deleted file mode 100644 index b9d11dfef..000000000 --- a/Jellyfin.Api/Extensions/RequestExtensions.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Querying; -using Microsoft.AspNetCore.Http; - -namespace Jellyfin.Api.Extensions -{ - /// - /// Request Extensions. - /// - public static class RequestExtensions - { - /// - /// Get Order By. - /// - /// Sort By. Comma delimited string. - /// Sort Order. Comma delimited string. - /// Order By. - public static ValueTuple[] GetOrderBy(string sortBy, string requestedSortOrder) - { - var val = sortBy; - - if (string.IsNullOrEmpty(val)) - { - return Array.Empty>(); - } - - var vals = val.Split(','); - if (string.IsNullOrWhiteSpace(requestedSortOrder)) - { - requestedSortOrder = "Ascending"; - } - - var sortOrders = requestedSortOrder.Split(','); - - var result = new ValueTuple[vals.Length]; - - for (var i = 0; i < vals.Length; i++) - { - var sortOrderIndex = sortOrders.Length > i ? i : 0; - - var sortOrderValue = sortOrders.Length > sortOrderIndex ? sortOrders[sortOrderIndex] : null; - var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase) - ? SortOrder.Descending - : SortOrder.Ascending; - - result[i] = new ValueTuple(vals[i], sortOrder); - } - - return result; - } - - /// - /// Gets the item fields. - /// - /// The fields. - /// IEnumerable{ItemFields}. - public static ItemFields[] GetItemFields(string fields) - { - if (string.IsNullOrEmpty(fields)) - { - return Array.Empty(); - } - - return fields.Split(',').Select(v => - { - if (Enum.TryParse(v, true, out ItemFields value)) - { - return (ItemFields?)value; - } - - return null; - }).Where(i => i.HasValue).Select(i => i.Value).ToArray(); - } - - /// - /// Get parsed filters. - /// - /// The filters. - /// Item filters. - public static IEnumerable GetFilters(string filters) - { - return string.IsNullOrEmpty(filters) - ? Array.Empty() - : filters.Split(',').Select(v => Enum.Parse(v, true)); - } - } -} diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs new file mode 100644 index 000000000..b9d11dfef --- /dev/null +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Api.Extensions +{ + /// + /// Request Extensions. + /// + public static class RequestExtensions + { + /// + /// Get Order By. + /// + /// Sort By. Comma delimited string. + /// Sort Order. Comma delimited string. + /// Order By. + public static ValueTuple[] GetOrderBy(string sortBy, string requestedSortOrder) + { + var val = sortBy; + + if (string.IsNullOrEmpty(val)) + { + return Array.Empty>(); + } + + var vals = val.Split(','); + if (string.IsNullOrWhiteSpace(requestedSortOrder)) + { + requestedSortOrder = "Ascending"; + } + + var sortOrders = requestedSortOrder.Split(','); + + var result = new ValueTuple[vals.Length]; + + for (var i = 0; i < vals.Length; i++) + { + var sortOrderIndex = sortOrders.Length > i ? i : 0; + + var sortOrderValue = sortOrders.Length > sortOrderIndex ? sortOrders[sortOrderIndex] : null; + var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase) + ? SortOrder.Descending + : SortOrder.Ascending; + + result[i] = new ValueTuple(vals[i], sortOrder); + } + + return result; + } + + /// + /// Gets the item fields. + /// + /// The fields. + /// IEnumerable{ItemFields}. + public static ItemFields[] GetItemFields(string fields) + { + if (string.IsNullOrEmpty(fields)) + { + return Array.Empty(); + } + + return fields.Split(',').Select(v => + { + if (Enum.TryParse(v, true, out ItemFields value)) + { + return (ItemFields?)value; + } + + return null; + }).Where(i => i.HasValue).Select(i => i.Value).ToArray(); + } + + /// + /// Get parsed filters. + /// + /// The filters. + /// Item filters. + public static IEnumerable GetFilters(string filters) + { + return string.IsNullOrEmpty(filters) + ? Array.Empty() + : filters.Split(',').Select(v => Enum.Parse(v, true)); + } + } +} -- cgit v1.2.3 From 1f9cda6a6600a81969cf8afad3c60fa77bb5a093 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 24 May 2020 12:19:37 -0600 Subject: Add ImageTags to SwaggerGenTypes --- .../Extensions/ApiServiceCollectionExtensions.cs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 71ef9a69a..4d00c513b 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -1,13 +1,17 @@ +using System.Collections.Generic; +using System.Linq; using Jellyfin.Api; using Jellyfin.Api.Auth; using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; using Jellyfin.Api.Auth.RequiresElevationPolicy; using Jellyfin.Api.Constants; using Jellyfin.Api.Controllers; +using MediaBrowser.Model.Entities; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; namespace Jellyfin.Server.Extensions { @@ -89,7 +93,25 @@ namespace Jellyfin.Server.Extensions return serviceCollection.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "Jellyfin API", Version = "v1" }); + c.MapSwaggerGenTypes(); }); } + + private static void MapSwaggerGenTypes(this SwaggerGenOptions options) + { + // BaseItemDto.ImageTags + options.MapType>(() => + new OpenApiSchema + { + Type = "object", + Properties = typeof(ImageType).GetEnumNames().ToDictionary( + name => name, + name => new OpenApiSchema + { + Type = "string", + Format = "string" + }) + }); + } } } -- cgit v1.2.3 From 40762f13c6d4ebe0baef2cc241dfbb2a908999b4 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 24 May 2020 15:54:34 -0600 Subject: Fix route parameter casing --- Jellyfin.Api/Controllers/ChannelsController.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs index f35f82201..e25b4c821 100644 --- a/Jellyfin.Api/Controllers/ChannelsController.cs +++ b/Jellyfin.Api/Controllers/ChannelsController.cs @@ -86,19 +86,19 @@ namespace Jellyfin.Api.Controllers /// /// Get channel features. /// - /// Channel id. + /// Channel id. /// Channel features returned. /// An containing the channel features. - [HttpGet("{Id}/Features")] - public ActionResult GetChannelFeatures([FromRoute] string id) + [HttpGet("{channelId}/Features")] + public ActionResult GetChannelFeatures([FromRoute] string channelId) { - return _channelManager.GetChannelFeatures(id); + return _channelManager.GetChannelFeatures(channelId); } /// /// Get channel items. /// - /// Channel Id. + /// Channel Id. /// Optional. Folder Id. /// Optional. User Id. /// Optional. The record index to start at. All items with a lower index will be dropped from the results. @@ -109,9 +109,9 @@ namespace Jellyfin.Api.Controllers /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines. /// Channel items returned. /// Channel items. - [HttpGet("{Id}/Items")] + [HttpGet("{channelId}/Items")] public async Task>> GetChannelItems( - [FromRoute] Guid id, + [FromRoute] Guid channelId, [FromQuery] Guid? folderId, [FromQuery] Guid? userId, [FromQuery] int? startIndex, @@ -129,7 +129,7 @@ namespace Jellyfin.Api.Controllers { Limit = limit, StartIndex = startIndex, - ChannelIds = new[] { id }, + ChannelIds = new[] { channelId }, ParentId = folderId ?? Guid.Empty, OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder), DtoOptions = new DtoOptions { Fields = RequestHelpers.GetItemFields(fields) } -- cgit v1.2.3 From 483e24607b92b2989158670ba4f36da0361d52e2 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 24 May 2020 16:01:53 -0600 Subject: Fix optional parameter binding --- Jellyfin.Api/Controllers/ChannelsController.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs index e25b4c821..6b42b500e 100644 --- a/Jellyfin.Api/Controllers/ChannelsController.cs +++ b/Jellyfin.Api/Controllers/ChannelsController.cs @@ -116,10 +116,10 @@ namespace Jellyfin.Api.Controllers [FromQuery] Guid? userId, [FromQuery] int? startIndex, [FromQuery] int? limit, - [FromQuery] string sortOrder, - [FromQuery] string filters, - [FromQuery] string sortBy, - [FromQuery] string fields) + [FromQuery] string? sortOrder, + [FromQuery] string? filters, + [FromQuery] string? sortBy, + [FromQuery] string? fields) { var user = userId == null ? null -- cgit v1.2.3 From e02cc8da53ff76a17de52a18ad83e73a1caa6394 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sun, 24 May 2020 16:04:47 -0600 Subject: Add Swashbuckle TODO note --- Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 4d08cddbc..7bb659ece 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -120,6 +120,8 @@ namespace Jellyfin.Server.Extensions description.TryGetMethodInfo(out MethodInfo methodInfo) ? methodInfo.Name : null); // Add types not supported by System.Text.Json + // TODO: Remove this once these types are supported by System.Text.Json and Swashbuckle + // See: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1667 c.MapSwaggerGenTypes(); }); } -- cgit v1.2.3 From 35da965cd3c7a9224bea0b579744802a0bc5bdcd Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Mon, 25 May 2020 01:29:32 -0400 Subject: Add -k to keyserver curl command This command seems to inexplicably fail in Docker builds, despite working on the CLI, similar to what happened with the command directly above it in c257d6071c3a8dd141d1191062e892d912177d9a. Fix it in the same way by adding `-k`. --- Dockerfile.arm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.arm b/Dockerfile.arm index 39beaa479..59b8a8c98 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -38,7 +38,7 @@ COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin RUN apt-get update \ && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl && \ curl -ks https://repo.jellyfin.org/debian/jellyfin_team.gpg.key | apt-key add - && \ - curl -s https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | apt-key add - && \ + curl -ks https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | apt-key add - && \ echo 'deb [arch=armhf] https://repo.jellyfin.org/debian buster main' > /etc/apt/sources.list.d/jellyfin.list && \ echo "deb http://ppa.launchpad.net/ubuntu-raspi2/ppa/ubuntu bionic main">> /etc/apt/sources.list.d/raspbins.list && \ apt-get update && \ -- cgit v1.2.3 From f75a09838e2fadf98544d39b7dbae6174c73cec6 Mon Sep 17 00:00:00 2001 From: dkanada Date: Mon, 25 May 2020 18:25:45 +0900 Subject: remove uses of fnchecked from plugins --- MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html | 8 ++++---- .../Plugins/MusicBrainz/Configuration/config.html | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html index 34494644d..fbf413f2b 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html +++ b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html @@ -31,8 +31,8 @@ $('.configPage').on('pageshow', function () { Dashboard.showLoadingMsg(); ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) { - $('#enable').checked(config.Enable); - $('#replaceAlbumName').checked(config.ReplaceAlbumName); + $('#enable').checked = config.Enable; + $('#replaceAlbumName').checked = config.ReplaceAlbumName; Dashboard.hideLoadingMsg(); }); @@ -43,8 +43,8 @@ var form = this; ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) { - config.Enable = $('#enable', form).checked(); - config.ReplaceAlbumName = $('#replaceAlbumName', form).checked(); + config.Enable = $('#enable', form).checked; + config.ReplaceAlbumName = $('#replaceAlbumName', form).checked; ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult); }); diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html index 1f02461da..90196b046 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html @@ -41,8 +41,8 @@ ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) { $('#server').val(config.Server).change(); $('#rateLimit').val(config.RateLimit).change(); - $('#enable').checked(config.Enable); - $('#replaceArtistName').checked(config.ReplaceArtistName); + $('#enable').checked = config.Enable; + $('#replaceArtistName').checked = config.ReplaceArtistName; Dashboard.hideLoadingMsg(); }); @@ -55,8 +55,8 @@ ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) { config.Server = $('#server', form).val(); config.RateLimit = $('#rateLimit', form).val(); - config.Enable = $('#enable', form).checked(); - config.ReplaceArtistName = $('#replaceArtistName', form).checked(); + config.Enable = $('#enable', form).checked; + config.ReplaceArtistName = $('#replaceArtistName', form).checked; ApiClient.updatePluginConfiguration(MusicBrainzPluginConfig.uniquePluginId, config).then(Dashboard.processPluginConfigurationUpdateResult); }); -- cgit v1.2.3 From 29443e36817e4866cc58e8397c1233b05624284a Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 26 May 2020 00:50:29 +0300 Subject: Apply suggestions from code review Co-authored-by: dkanada --- .vscode/tasks.json | 1 - Emby.Server.Implementations/Library/LibraryManager.cs | 1 - MediaBrowser.Controller/Drawing/IImageEncoder.cs | 2 +- MediaBrowser.Model/Dto/BaseItemDto.cs | 2 +- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 7475617c9..2289fd991 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -21,6 +21,5 @@ ], "problemMatcher": "$msCompile" } - ] } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 579fb7edd..e63776bff 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -71,7 +71,6 @@ namespace Emby.Server.Implementations.Library private readonly ConcurrentDictionary _libraryItemsCache; private readonly IImageProcessor _imageProcessor; - private NamingOptions _namingOptions; private string[] _videoFileExtensions; diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs index 1d3f0d3b4..4baec6204 100644 --- a/MediaBrowser.Controller/Drawing/IImageEncoder.cs +++ b/MediaBrowser.Controller/Drawing/IImageEncoder.cs @@ -44,7 +44,7 @@ namespace MediaBrowser.Controller.Drawing ImageDimensions GetImageSize(string path); /// - /// Get the blurhash of an image. + /// Gets the blurhash of an image. /// /// The filepath of the image. /// The blurhash. diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index df84dcf12..6213206ff 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -511,7 +511,7 @@ namespace MediaBrowser.Model.Dto public string SeriesThumbImageTag { get; set; } /// - /// Gets or sets the blurhash for the image tags. + /// Gets or sets the blurhashes for the image tags. /// /// The blurhashes. public Dictionary ImageBlurHashes { get; set; } -- cgit v1.2.3 From 279f0da98031d60b81bb634ca11a2d548cb0f646 Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 26 May 2020 00:52:13 +0300 Subject: Rename ImageInfo.Hash to ImageInfo.BlurHash --- MediaBrowser.Api/Images/ImageService.cs | 2 +- MediaBrowser.Model/Dto/ImageInfo.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index d0846bfc3..89fe72635 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -366,7 +366,7 @@ namespace MediaBrowser.Api.Images ImageType = info.Type, ImageTag = _imageProcessor.GetImageCacheTag(item, info), Size = length, - Hash = blurhash, + BlurHash = blurhash, Width = width, Height = height }; diff --git a/MediaBrowser.Model/Dto/ImageInfo.cs b/MediaBrowser.Model/Dto/ImageInfo.cs index 39bdc09ed..664ea332e 100644 --- a/MediaBrowser.Model/Dto/ImageInfo.cs +++ b/MediaBrowser.Model/Dto/ImageInfo.cs @@ -34,7 +34,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the blurhash. /// /// The blurhash. - public string Hash { get; set; } + public string BlurHash { get; set; } /// /// Gets or sets the height. -- cgit v1.2.3 From 10e381f66f957ffa2e8339a02b0c970086673739 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 25 May 2020 23:52:51 +0200 Subject: Fix some 'bugs' flagged by sonarcloud --- DvdLib/Ifo/Program.cs | 2 +- Emby.Dlna/ContentDirectory/ContentDirectory.cs | 8 +--- Emby.Dlna/Main/DlnaEntryPoint.cs | 10 ++--- Emby.Dlna/PlayTo/Device.cs | 20 ++++----- Emby.Dlna/PlayTo/PlayToController.cs | 19 +++++---- Emby.Dlna/Ssdp/Extensions.cs | 14 ++----- .../EntryPoints/LibraryChangedNotifier.cs | 4 +- .../EntryPoints/RecordingNotifier.cs | 18 ++++---- .../EntryPoints/ServerEventNotifier.cs | 48 +++++++++++----------- .../HttpServer/HttpResultFactory.cs | 12 ++++-- .../LiveTv/EmbyTV/EmbyTV.cs | 13 ++---- .../LiveTv/EmbyTV/EncodedRecorder.cs | 4 +- .../LiveTv/LiveTvManager.cs | 20 +++------ .../SocketSharp/WebSocketSharpRequest.cs | 5 ++- MediaBrowser.Api/Images/ImageService.cs | 3 +- .../Sessions/SessionInfoWebSocketListener.cs | 28 ++++++------- .../Net/BasePeriodicWebSocketListener.cs | 25 +++++++---- MediaBrowser.MediaEncoding/Subtitles/AssParser.cs | 6 ++- 18 files changed, 126 insertions(+), 133 deletions(-) diff --git a/DvdLib/Ifo/Program.cs b/DvdLib/Ifo/Program.cs index 9f6251270..3d94fa7dc 100644 --- a/DvdLib/Ifo/Program.cs +++ b/DvdLib/Ifo/Program.cs @@ -6,7 +6,7 @@ namespace DvdLib.Ifo { public class Program { - public readonly List Cells; + public IReadOnlyList Cells { get; } public Program(List cells) { diff --git a/Emby.Dlna/ContentDirectory/ContentDirectory.cs b/Emby.Dlna/ContentDirectory/ContentDirectory.cs index 64cd308a2..66805b7c8 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectory.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectory.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System; +using System.Linq; using System.Threading.Tasks; using Emby.Dlna.Service; using MediaBrowser.Common.Net; @@ -136,12 +137,7 @@ namespace Emby.Dlna.ContentDirectory } } - foreach (var user in _userManager.Users) - { - return user; - } - - return null; + return _userManager.Users.FirstOrDefault(); } } } diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index c5d60b2a0..bcab4adba 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -133,20 +133,20 @@ namespace Emby.Dlna.Main { await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false); - ReloadComponents(); + await ReloadComponents().ConfigureAwait(false); - _config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated; + _config.NamedConfigurationUpdated += OnNamedConfigurationUpdated; } - void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) + private async void OnNamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) { if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase)) { - ReloadComponents(); + await ReloadComponents().ConfigureAwait(false); } } - private async void ReloadComponents() + private async Task ReloadComponents() { var options = _config.GetDlnaConfiguration(); diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index 6abc3a82c..c7431d143 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -34,7 +34,7 @@ namespace Emby.Dlna.PlayTo { get { - RefreshVolumeIfNeeded(); + RefreshVolumeIfNeeded().GetAwaiter().GetResult(); return _volume; } set => _volume = value; @@ -76,24 +76,24 @@ namespace Emby.Dlna.PlayTo private DateTime _lastVolumeRefresh; private bool _volumeRefreshActive; - private void RefreshVolumeIfNeeded() + private Task RefreshVolumeIfNeeded() { - if (!_volumeRefreshActive) - { - return; - } - - if (DateTime.UtcNow >= _lastVolumeRefresh.AddSeconds(5)) + if (_volumeRefreshActive + && DateTime.UtcNow >= _lastVolumeRefresh.AddSeconds(5)) { _lastVolumeRefresh = DateTime.UtcNow; - RefreshVolume(CancellationToken.None); + return RefreshVolume(); } + + return Task.CompletedTask; } - private async void RefreshVolume(CancellationToken cancellationToken) + private async Task RefreshVolume(CancellationToken cancellationToken = default) { if (_disposed) + { return; + } try { diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 9d7c0d365..7403a2a16 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -146,11 +146,14 @@ namespace Emby.Dlna.PlayTo { var positionTicks = GetProgressPositionTicks(streamInfo); - ReportPlaybackStopped(streamInfo, positionTicks); + await ReportPlaybackStopped(streamInfo, positionTicks).ConfigureAwait(false); } streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager); - if (streamInfo.Item == null) return; + if (streamInfo.Item == null) + { + return; + } var newItemProgress = GetProgressInfo(streamInfo); @@ -173,11 +176,14 @@ namespace Emby.Dlna.PlayTo { var streamInfo = StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager); - if (streamInfo.Item == null) return; + if (streamInfo.Item == null) + { + return; + } var positionTicks = GetProgressPositionTicks(streamInfo); - ReportPlaybackStopped(streamInfo, positionTicks); + await ReportPlaybackStopped(streamInfo, positionTicks).ConfigureAwait(false); var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false); @@ -185,7 +191,7 @@ namespace Emby.Dlna.PlayTo (_device.Duration == null ? (long?)null : _device.Duration.Value.Ticks) : mediaSource.RunTimeTicks; - var playedToCompletion = (positionTicks.HasValue && positionTicks.Value == 0); + var playedToCompletion = positionTicks.HasValue && positionTicks.Value == 0; if (!playedToCompletion && duration.HasValue && positionTicks.HasValue) { @@ -210,7 +216,7 @@ namespace Emby.Dlna.PlayTo } } - private async void ReportPlaybackStopped(StreamParams streamInfo, long? positionTicks) + private async Task ReportPlaybackStopped(StreamParams streamInfo, long? positionTicks) { try { @@ -220,7 +226,6 @@ namespace Emby.Dlna.PlayTo SessionId = _session.Id, PositionTicks = positionTicks, MediaSourceId = streamInfo.MediaSourceId - }).ConfigureAwait(false); } catch (Exception ex) diff --git a/Emby.Dlna/Ssdp/Extensions.cs b/Emby.Dlna/Ssdp/Extensions.cs index 10c1f321b..613d332b2 100644 --- a/Emby.Dlna/Ssdp/Extensions.cs +++ b/Emby.Dlna/Ssdp/Extensions.cs @@ -1,5 +1,6 @@ #pragma warning disable CS1591 +using System.Linq; using System.Xml.Linq; namespace Emby.Dlna.Ssdp @@ -10,24 +11,17 @@ namespace Emby.Dlna.Ssdp { var node = container.Element(name); - return node == null ? null : node.Value; + return node?.Value; } public static string GetAttributeValue(this XElement container, XName name) { var node = container.Attribute(name); - return node == null ? null : node.Value; + return node?.Value; } public static string GetDescendantValue(this XElement container, XName name) - { - foreach (var node in container.Descendants(name)) - { - return node.Value; - } - - return null; - } + => container.Descendants(name).FirstOrDefault()?.Value; } } diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 8e3236407..9bc2b62ec 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -302,7 +302,7 @@ namespace Emby.Server.Implementations.EntryPoints .Select(x => x.First()) .ToList(); - SendChangeNotifications(_itemsAdded.ToList(), itemsUpdated, _itemsRemoved.ToList(), foldersAddedTo, foldersRemovedFrom, CancellationToken.None); + SendChangeNotifications(_itemsAdded.ToList(), itemsUpdated, _itemsRemoved.ToList(), foldersAddedTo, foldersRemovedFrom, CancellationToken.None).GetAwaiter().GetResult(); if (LibraryUpdateTimer != null) { @@ -327,7 +327,7 @@ namespace Emby.Server.Implementations.EntryPoints /// The folders added to. /// The folders removed from. /// The cancellation token. - private async void SendChangeNotifications(List itemsAdded, List itemsUpdated, List itemsRemoved, List foldersAddedTo, List foldersRemovedFrom, CancellationToken cancellationToken) + private async Task SendChangeNotifications(List itemsAdded, List itemsUpdated, List itemsRemoved, List foldersAddedTo, List foldersRemovedFrom, CancellationToken cancellationToken) { var userIds = _sessionManager.Sessions .Select(i => i.UserId) diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index 41c0c5115..997571a91 100644 --- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -42,27 +42,27 @@ namespace Emby.Server.Implementations.EntryPoints return Task.CompletedTask; } - private void OnLiveTvManagerSeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs e) + private async void OnLiveTvManagerSeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs e) { - SendMessage("SeriesTimerCreated", e.Argument); + await SendMessage("SeriesTimerCreated", e.Argument).ConfigureAwait(false); } - private void OnLiveTvManagerTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs e) + private async void OnLiveTvManagerTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs e) { - SendMessage("TimerCreated", e.Argument); + await SendMessage("TimerCreated", e.Argument).ConfigureAwait(false); } - private void OnLiveTvManagerSeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs e) + private async void OnLiveTvManagerSeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs e) { - SendMessage("SeriesTimerCancelled", e.Argument); + await SendMessage("SeriesTimerCancelled", e.Argument).ConfigureAwait(false); } - private void OnLiveTvManagerTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs e) + private async void OnLiveTvManagerTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs e) { - SendMessage("TimerCancelled", e.Argument); + await SendMessage("TimerCancelled", e.Argument).ConfigureAwait(false); } - private async void SendMessage(string name, TimerEventInfo info) + private async Task SendMessage(string name, TimerEventInfo info) { var users = _userManager.Users.Where(i => i.Policy.EnableLiveTvAccess).Select(i => i.Id).ToList(); diff --git a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs index e1dbb663b..dea85d299 100644 --- a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs @@ -85,29 +85,29 @@ namespace Emby.Server.Implementations.EntryPoints return Task.CompletedTask; } - private void OnPackageInstalling(object sender, InstallationEventArgs e) + private async void OnPackageInstalling(object sender, InstallationEventArgs e) { - SendMessageToAdminSessions("PackageInstalling", e.InstallationInfo); + await SendMessageToAdminSessions("PackageInstalling", e.InstallationInfo).ConfigureAwait(false); } - private void OnPackageInstallationCancelled(object sender, InstallationEventArgs e) + private async void OnPackageInstallationCancelled(object sender, InstallationEventArgs e) { - SendMessageToAdminSessions("PackageInstallationCancelled", e.InstallationInfo); + await SendMessageToAdminSessions("PackageInstallationCancelled", e.InstallationInfo).ConfigureAwait(false); } - private void OnPackageInstallationCompleted(object sender, InstallationEventArgs e) + private async void OnPackageInstallationCompleted(object sender, InstallationEventArgs e) { - SendMessageToAdminSessions("PackageInstallationCompleted", e.InstallationInfo); + await SendMessageToAdminSessions("PackageInstallationCompleted", e.InstallationInfo).ConfigureAwait(false); } - private void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e) + private async void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e) { - SendMessageToAdminSessions("PackageInstallationFailed", e.InstallationInfo); + await SendMessageToAdminSessions("PackageInstallationFailed", e.InstallationInfo).ConfigureAwait(false); } - private void OnTaskCompleted(object sender, TaskCompletionEventArgs e) + private async void OnTaskCompleted(object sender, TaskCompletionEventArgs e) { - SendMessageToAdminSessions("ScheduledTaskEnded", e.Result); + await SendMessageToAdminSessions("ScheduledTaskEnded", e.Result).ConfigureAwait(false); } /// @@ -115,9 +115,9 @@ namespace Emby.Server.Implementations.EntryPoints /// /// The sender. /// The e. - private void OnPluginUninstalled(object sender, GenericEventArgs e) + private async void OnPluginUninstalled(object sender, GenericEventArgs e) { - SendMessageToAdminSessions("PluginUninstalled", e.Argument.GetPluginInfo()); + await SendMessageToAdminSessions("PluginUninstalled", e.Argument.GetPluginInfo()).ConfigureAwait(false); } /// @@ -125,9 +125,9 @@ namespace Emby.Server.Implementations.EntryPoints /// /// The source of the event. /// The instance containing the event data. - private void OnHasPendingRestartChanged(object sender, EventArgs e) + private async void OnHasPendingRestartChanged(object sender, EventArgs e) { - _sessionManager.SendRestartRequiredNotification(CancellationToken.None); + await _sessionManager.SendRestartRequiredNotification(CancellationToken.None).ConfigureAwait(false); } /// @@ -135,11 +135,11 @@ namespace Emby.Server.Implementations.EntryPoints /// /// The sender. /// The e. - private void OnUserUpdated(object sender, GenericEventArgs e) + private async void OnUserUpdated(object sender, GenericEventArgs e) { var dto = _userManager.GetUserDto(e.Argument); - SendMessageToUserSession(e.Argument, "UserUpdated", dto); + await SendMessageToUserSession(e.Argument, "UserUpdated", dto).ConfigureAwait(false); } /// @@ -147,26 +147,26 @@ namespace Emby.Server.Implementations.EntryPoints /// /// The sender. /// The e. - private void OnUserDeleted(object sender, GenericEventArgs e) + private async void OnUserDeleted(object sender, GenericEventArgs e) { - SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N", CultureInfo.InvariantCulture)); + await SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N", CultureInfo.InvariantCulture)).ConfigureAwait(false); } - private void OnUserPolicyUpdated(object sender, GenericEventArgs e) + private async void OnUserPolicyUpdated(object sender, GenericEventArgs e) { var dto = _userManager.GetUserDto(e.Argument); - SendMessageToUserSession(e.Argument, "UserPolicyUpdated", dto); + await SendMessageToUserSession(e.Argument, "UserPolicyUpdated", dto).ConfigureAwait(false); } - private void OnUserConfigurationUpdated(object sender, GenericEventArgs e) + private async void OnUserConfigurationUpdated(object sender, GenericEventArgs e) { var dto = _userManager.GetUserDto(e.Argument); - SendMessageToUserSession(e.Argument, "UserConfigurationUpdated", dto); + await SendMessageToUserSession(e.Argument, "UserConfigurationUpdated", dto).ConfigureAwait(false); } - private async void SendMessageToAdminSessions(string name, T data) + private async Task SendMessageToAdminSessions(string name, T data) { try { @@ -178,7 +178,7 @@ namespace Emby.Server.Implementations.EntryPoints } } - private async void SendMessageToUserSession(User user, string name, T data) + private async Task SendMessageToUserSession(User user, string name, T data) { try { diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 2e9ecc4ae..cffae7b1c 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -255,16 +255,20 @@ namespace Emby.Server.Implementations.HttpServer { var acceptEncoding = request.Headers[HeaderNames.AcceptEncoding].ToString(); - if (string.IsNullOrEmpty(acceptEncoding)) + if (!string.IsNullOrEmpty(acceptEncoding)) { - //if (_brotliCompressor != null && acceptEncoding.IndexOf("br", StringComparison.OrdinalIgnoreCase) != -1) + // if (_brotliCompressor != null && acceptEncoding.IndexOf("br", StringComparison.OrdinalIgnoreCase) != -1) // return "br"; - if (acceptEncoding.IndexOf("deflate", StringComparison.OrdinalIgnoreCase) != -1) + if (acceptEncoding.Contains("deflate", StringComparison.OrdinalIgnoreCase)) + { return "deflate"; + } - if (acceptEncoding.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) != -1) + if (acceptEncoding.Contains("gzip", StringComparison.OrdinalIgnoreCase)) + { return "gzip"; + } } return null; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 3efe1ee25..5a5dc3329 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -140,11 +140,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - private void OnNamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) + private async void OnNamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) { if (string.Equals(e.Key, "livetv", StringComparison.OrdinalIgnoreCase)) { - OnRecordingFoldersChanged(); + await CreateRecordingFolders().ConfigureAwait(false); } } @@ -155,11 +155,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return CreateRecordingFolders(); } - private async void OnRecordingFoldersChanged() - { - await CreateRecordingFolders().ConfigureAwait(false); - } - internal async Task CreateRecordingFolders() { try @@ -1334,7 +1329,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV await CreateRecordingFolders().ConfigureAwait(false); TriggerRefresh(recordPath); - EnforceKeepUpTo(timer, seriesPath); + await EnforceKeepUpTo(timer, seriesPath).ConfigureAwait(false); }; await recorder.Record(directStreamProvider, mediaStreamInfo, recordPath, duration, onStarted, activeRecordingInfo.CancellationTokenSource.Token).ConfigureAwait(false); @@ -1494,7 +1489,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV return item; } - private async void EnforceKeepUpTo(TimerInfo timer, string seriesPath) + private async Task EnforceKeepUpTo(TimerInfo timer, string seriesPath) { if (string.IsNullOrWhiteSpace(timer.SeriesTimerId)) { diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index bc86cc59a..70dd8f321 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -117,7 +117,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV onStarted(); // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback - StartStreamingLog(_process.StandardError.BaseStream, _logFileStream); + _ = StartStreamingLog(_process.StandardError.BaseStream, _logFileStream); _logger.LogInformation("ffmpeg recording process started for {0}", _targetPath); @@ -321,7 +321,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - private async void StartStreamingLog(Stream source, Stream target) + private async Task StartStreamingLog(Stream source, Stream target) { try { diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 1b10f2d27..a3dd45a53 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -788,22 +788,12 @@ namespace Emby.Server.Implementations.LiveTv 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[] - { - (ItemSortBy.StartDate, SortOrder.Ascending) - }; - } - else + + // Unless something else was specified, order by start date to take advantage of a specialized index + query.OrderBy = new[] { - // Unless something else was specified, order by start date to take advantage of a specialized index - query.OrderBy = new[] - { - (ItemSortBy.StartDate, SortOrder.Ascending) - }; - } + (ItemSortBy.StartDate, SortOrder.Ascending) + }; } RemoveFields(options); diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index ee5131c1f..5554aa97f 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -208,8 +208,9 @@ namespace Emby.Server.Implementations.SocketSharp private static string GetQueryStringContentType(HttpRequest httpReq) { - ReadOnlySpan format = httpReq.Query["format"].ToString(); - if (format == null) + string formatStr = httpReq.Query["format"].ToString(); + ReadOnlySpan format = formatStr; + if (formatStr == null) { const int FormatMaxLength = 4; ReadOnlySpan pi = httpReq.Path.ToString(); diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 2e9b3e6cb..eaff22fff 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -555,8 +555,7 @@ 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; diff --git a/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs index 0e74c9267..175984575 100644 --- a/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs +++ b/MediaBrowser.Api/Sessions/SessionInfoWebSocketListener.cs @@ -40,39 +40,39 @@ namespace MediaBrowser.Api.Sessions _sessionManager.SessionActivity += OnSessionManagerSessionActivity; } - private void OnSessionManagerSessionActivity(object sender, SessionEventArgs e) + private async void OnSessionManagerSessionActivity(object sender, SessionEventArgs e) { - SendData(false); + await SendData(false).ConfigureAwait(false); } - private void OnSessionManagerCapabilitiesChanged(object sender, SessionEventArgs e) + private async void OnSessionManagerCapabilitiesChanged(object sender, SessionEventArgs e) { - SendData(true); + await SendData(true).ConfigureAwait(false); } - private void OnSessionManagerPlaybackProgress(object sender, PlaybackProgressEventArgs e) + private async void OnSessionManagerPlaybackProgress(object sender, PlaybackProgressEventArgs e) { - SendData(!e.IsAutomated); + await SendData(!e.IsAutomated).ConfigureAwait(false); } - private void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e) + private async void OnSessionManagerPlaybackStopped(object sender, PlaybackStopEventArgs e) { - SendData(true); + await SendData(true).ConfigureAwait(false); } - private void OnSessionManagerPlaybackStart(object sender, PlaybackProgressEventArgs e) + private async void OnSessionManagerPlaybackStart(object sender, PlaybackProgressEventArgs e) { - SendData(true); + await SendData(true).ConfigureAwait(false); } - private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e) + private async void OnSessionManagerSessionEnded(object sender, SessionEventArgs e) { - SendData(true); + await SendData(true).ConfigureAwait(false); } - private void OnSessionManagerSessionStarted(object sender, SessionEventArgs e) + private async void OnSessionManagerSessionStarted(object sender, SessionEventArgs e) { - SendData(true); + await SendData(true).ConfigureAwait(false); } /// diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index 1162bff13..5be656bdb 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -104,7 +104,7 @@ namespace MediaBrowser.Controller.Net } } - protected void SendData(bool force) + protected async Task SendData(bool force) { Tuple[] tuples; @@ -128,13 +128,18 @@ namespace MediaBrowser.Controller.Net .ToArray(); } - foreach (var tuple in tuples) + IEnumerable GetTasks() { - SendData(tuple); + foreach (var tuple in tuples) + { + yield return SendData(tuple); + } } + + await Task.WhenAll(GetTasks()).ConfigureAwait(false); } - private async void SendData(Tuple tuple) + private async Task SendData(Tuple tuple) { var connection = tuple.Item1; @@ -148,11 +153,13 @@ namespace MediaBrowser.Controller.Net if (data != null) { - await connection.SendAsync(new WebSocketMessage - { - MessageType = Name, - Data = data - }, cancellationToken).ConfigureAwait(false); + await connection.SendAsync( + new WebSocketMessage + { + MessageType = Name, + Data = data + }, + cancellationToken).ConfigureAwait(false); state.DateLastSendUtc = DateTime.UtcNow; } diff --git a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs index 293cf5ea5..f44cf1452 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs @@ -33,10 +33,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles { continue; } + if (line.StartsWith("[")) + { break; - if (string.IsNullOrEmpty(line)) - continue; + } + var subEvent = new SubtitleTrackEvent { Id = eventIndex.ToString(_usCulture) }; eventIndex++; var sections = line.Substring(10).Split(','); -- cgit v1.2.3 From 46420dfd68945fd7c7045b8492c401e3d8cd302d Mon Sep 17 00:00:00 2001 From: xumix Date: Tue, 26 May 2020 00:58:19 +0300 Subject: Refactor copy codec checks --- MediaBrowser.Api/ApiEntryPoint.cs | 4 +-- MediaBrowser.Api/Playback/BaseStreamingService.cs | 10 ++++---- MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 14 +++++----- MediaBrowser.Api/Playback/Hls/VideoHlsService.cs | 2 +- MediaBrowser.Api/Playback/StreamState.cs | 2 +- .../MediaEncoding/EncodingHelper.cs | 20 ++++++++++----- .../MediaEncoding/EncodingJobInfo.cs | 30 +++++++++++----------- .../Configuration/EncodingOptions.cs | 12 +++++++++ 8 files changed, 56 insertions(+), 38 deletions(-) diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 6691080bc..c7485a2e9 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -284,8 +284,8 @@ namespace MediaBrowser.Api Width = state.OutputWidth, Height = state.OutputHeight, AudioChannels = state.OutputAudioChannels, - IsAudioDirect = string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase), - IsVideoDirect = string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase), + IsAudioDirect = EncodingHelper.IsCopyCodec(state.OutputAudioCodec), + IsVideoDirect = EncodingHelper.IsCopyCodec(state.OutputVideoCodec), TranscodeReasons = state.TranscodeReasons }); } diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index f796aa486..24297d500 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -193,7 +193,7 @@ namespace MediaBrowser.Api.Playback await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false); - if (state.VideoRequest != null && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (state.VideoRequest != null && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) { var auth = AuthorizationContext.GetAuthorizationInfo(Request); if (auth.User != null && !auth.User.Policy.EnableVideoPlaybackTranscoding) @@ -243,9 +243,9 @@ namespace MediaBrowser.Api.Playback var logFilePrefix = "ffmpeg-transcode"; if (state.VideoRequest != null - && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + && EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) { - logFilePrefix = string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase) + logFilePrefix = EncodingHelper.IsCopyCodec(state.OutputAudioCodec) ? "ffmpeg-remux" : "ffmpeg-directstream"; } @@ -328,7 +328,7 @@ namespace MediaBrowser.Api.Playback state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo && state.VideoType == VideoType.VideoFile && - !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase); + !EncodingHelper.IsCopyCodec(state.OutputVideoCodec); } return false; @@ -791,7 +791,7 @@ namespace MediaBrowser.Api.Playback EncodingHelper.TryStreamCopy(state); } - if (state.OutputVideoBitrate.HasValue && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (state.OutputVideoBitrate.HasValue && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) { var resolution = ResolutionNormalizer.Normalize( state.VideoStream?.BitRate, diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 061316cb8..50d34cca9 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -700,12 +700,12 @@ namespace MediaBrowser.Api.Playback.Hls return false; } - if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) { return false; } - if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(state.OutputAudioCodec)) { return false; } @@ -728,7 +728,7 @@ namespace MediaBrowser.Api.Playback.Hls private int? GetOutputVideoCodecLevel(StreamState state) { string levelString; - if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) + if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec) && state.VideoStream.Level.HasValue) { levelString = state.VideoStream?.Level.ToString(); @@ -1008,7 +1008,7 @@ namespace MediaBrowser.Api.Playback.Hls if (!state.IsOutputVideo) { - if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(audioCodec)) { return "-acodec copy"; } @@ -1036,11 +1036,11 @@ namespace MediaBrowser.Api.Playback.Hls return string.Join(" ", audioTranscodeParams.ToArray()); } - if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(audioCodec)) { var videoCodec = EncodingHelper.GetVideoEncoder(state, encodingOptions); - if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.EnableBreakOnNonKeyFrames(videoCodec)) + if (EncodingHelper.IsCopyCodec(videoCodec) && state.EnableBreakOnNonKeyFrames(videoCodec)) { return "-codec:a:0 copy -copypriorss:a:0 0"; } @@ -1091,7 +1091,7 @@ namespace MediaBrowser.Api.Playback.Hls // } // See if we can save come cpu cycles by avoiding encoding - if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(codec)) { if (state.VideoStream != null && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) { diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index d1c53c1c1..aefb3f019 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -72,7 +72,7 @@ namespace MediaBrowser.Api.Playback.Hls { var codec = EncodingHelper.GetAudioEncoder(state); - if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(codec)) { return "-codec:a:0 copy"; } diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index d5d2f58c0..c244b0033 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -42,7 +42,7 @@ namespace MediaBrowser.Api.Playback return Request.SegmentLength.Value; } - if (string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(OutputVideoCodec)) { var userAgent = UserAgent ?? string.Empty; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 61a330675..2d2f73148 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1338,7 +1338,7 @@ namespace MediaBrowser.Controller.MediaEncoding transcoderChannelLimit = 6; } - var isTranscodingAudio = !string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase); + var isTranscodingAudio = !EncodingHelper.IsCopyCodec(codec); int? resultChannels = state.GetRequestedAudioChannels(codec); if (isTranscodingAudio) @@ -1734,7 +1734,8 @@ namespace MediaBrowser.Controller.MediaEncoding var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; - if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) || (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) && !hasTextSubs) + if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + || (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) && !hasTextSubs)) && width.HasValue && height.HasValue) { @@ -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) @@ -2248,7 +2249,7 @@ namespace MediaBrowser.Controller.MediaEncoding flags.Add("+ignidx"); } - if (state.GenPtsInput || string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (state.GenPtsInput || EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) { flags.Add("+genpts"); } @@ -2511,7 +2512,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// protected string GetHardwareAcceleratedVideoDecoder(EncodingJobInfo state, EncodingOptions encodingOptions) { - if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) { return null; } @@ -2799,7 +2800,7 @@ namespace MediaBrowser.Controller.MediaEncoding args += " -mpegts_m2ts_mode 1"; } - if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(videoCodec)) { if (state.VideoStream != null && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) @@ -2901,7 +2902,7 @@ namespace MediaBrowser.Controller.MediaEncoding var args = "-codec:a:0 " + codec; - if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(codec)) { return args; } @@ -2973,5 +2974,10 @@ namespace MediaBrowser.Controller.MediaEncoding string.Empty, string.Empty).Trim(); } + + public static bool IsCopyCodec(string codec) + { + return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase); + } } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index 1127a08de..7cd783595 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -302,7 +302,7 @@ namespace MediaBrowser.Controller.MediaEncoding return false; } - return BaseRequest.BreakOnNonKeyFrames && string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase); + return BaseRequest.BreakOnNonKeyFrames && EncodingHelper.IsCopyCodec(videoCodec); } return false; @@ -367,7 +367,7 @@ namespace MediaBrowser.Controller.MediaEncoding get { if (BaseRequest.Static - || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) + || EncodingHelper.IsCopyCodec(OutputAudioCodec)) { if (AudioStream != null) { @@ -390,7 +390,7 @@ namespace MediaBrowser.Controller.MediaEncoding get { if (BaseRequest.Static - || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) + || EncodingHelper.IsCopyCodec(OutputAudioCodec)) { if (AudioStream != null) { @@ -409,7 +409,7 @@ namespace MediaBrowser.Controller.MediaEncoding { get { - if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (BaseRequest.Static || EncodingHelper.IsCopyCodec(OutputVideoCodec)) { return VideoStream?.Level; } @@ -433,7 +433,7 @@ namespace MediaBrowser.Controller.MediaEncoding get { if (BaseRequest.Static - || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + || EncodingHelper.IsCopyCodec(OutputVideoCodec)) { return VideoStream?.BitDepth; } @@ -451,7 +451,7 @@ namespace MediaBrowser.Controller.MediaEncoding get { if (BaseRequest.Static - || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + || EncodingHelper.IsCopyCodec(OutputVideoCodec)) { return VideoStream?.RefFrames; } @@ -468,7 +468,7 @@ namespace MediaBrowser.Controller.MediaEncoding get { if (BaseRequest.Static - || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + || EncodingHelper.IsCopyCodec(OutputVideoCodec)) { return VideoStream == null ? null : (VideoStream.AverageFrameRate ?? VideoStream.RealFrameRate); } @@ -499,7 +499,7 @@ namespace MediaBrowser.Controller.MediaEncoding { get { - if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (BaseRequest.Static || EncodingHelper.IsCopyCodec(OutputVideoCodec)) { return VideoStream?.PacketLength; } @@ -515,7 +515,7 @@ namespace MediaBrowser.Controller.MediaEncoding { get { - if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (BaseRequest.Static || EncodingHelper.IsCopyCodec(OutputVideoCodec)) { return VideoStream?.Profile; } @@ -535,7 +535,7 @@ namespace MediaBrowser.Controller.MediaEncoding get { if (BaseRequest.Static - || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + || EncodingHelper.IsCopyCodec(OutputVideoCodec)) { return VideoStream?.CodecTag; } @@ -549,7 +549,7 @@ namespace MediaBrowser.Controller.MediaEncoding get { if (BaseRequest.Static - || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + || EncodingHelper.IsCopyCodec(OutputVideoCodec)) { return VideoStream?.IsAnamorphic; } @@ -562,7 +562,7 @@ namespace MediaBrowser.Controller.MediaEncoding { get { - if (string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(OutputVideoCodec)) { return VideoStream?.Codec; } @@ -575,7 +575,7 @@ namespace MediaBrowser.Controller.MediaEncoding { get { - if (string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (EncodingHelper.IsCopyCodec(OutputAudioCodec)) { return AudioStream?.Codec; } @@ -589,7 +589,7 @@ namespace MediaBrowser.Controller.MediaEncoding get { if (BaseRequest.Static - || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + || EncodingHelper.IsCopyCodec(OutputVideoCodec)) { return VideoStream?.IsInterlaced; } @@ -607,7 +607,7 @@ namespace MediaBrowser.Controller.MediaEncoding { get { - if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (BaseRequest.Static || EncodingHelper.IsCopyCodec(OutputVideoCodec)) { return VideoStream?.IsAVC; } diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 648568fd7..5880730fd 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -5,10 +5,15 @@ namespace MediaBrowser.Model.Configuration public class EncodingOptions { public int EncodingThreadCount { get; set; } + public string TranscodingTempPath { get; set; } + public double DownMixAudioBoost { get; set; } + public bool EnableThrottling { get; set; } + public int ThrottleDelaySeconds { get; set; } + public string HardwareAccelerationType { get; set; } /// @@ -20,12 +25,19 @@ namespace MediaBrowser.Model.Configuration /// The current FFmpeg path being used by the system and displayed on the transcode page. /// public string EncoderAppPathDisplay { get; set; } + public string VaapiDevice { get; set; } + public int H264Crf { get; set; } + public int H265Crf { get; set; } + public string EncoderPreset { get; set; } + public string DeinterlaceMethod { get; set; } + public bool EnableHardwareEncoding { get; set; } + public bool EnableSubtitleExtraction { get; set; } public string[] HardwareDecodingCodecs { get; set; } -- cgit v1.2.3 From 0f32b0ffad03817f34b02a4fc7371e2a0a67e63a Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 26 May 2020 01:04:40 +0300 Subject: Change image blurhash mapping to "image type to blurhash" --- Emby.Server.Implementations/Dto/DtoService.cs | 4 ++-- MediaBrowser.Model/Dto/BaseItemDto.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 593e7be7d..77a734ebf 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -718,7 +718,7 @@ namespace Emby.Server.Implementations.Dto if (options.EnableImages) { dto.ImageTags = new Dictionary(); - dto.ImageBlurHashes = new Dictionary(); + dto.ImageBlurHashes = new Dictionary(); // Prevent implicitly captured closure var currentItem = item; @@ -736,7 +736,7 @@ namespace Emby.Server.Implementations.Dto var hash = image.BlurHash; if (!string.IsNullOrEmpty(hash)) { - dto.ImageBlurHashes[tag] = image.BlurHash; + dto.ImageBlurHashes[image.Type] = image.BlurHash; } } } diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 6213206ff..734bcaf81 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -514,7 +514,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the blurhashes for the image tags. /// /// The blurhashes. - public Dictionary ImageBlurHashes { get; set; } + public Dictionary ImageBlurHashes { get; set; } /// /// Gets or sets the series studio. -- cgit v1.2.3 From f575415e0b611cbfb4139e9e9c816adcb000442e Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 26 May 2020 02:31:40 +0300 Subject: Pick blurhash sizes depending on image aspect ratio --- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 7ab0a54df..ccd501214 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -241,14 +241,20 @@ namespace Jellyfin.Drawing.Skia throw new ArgumentNullException(nameof(path)); } - if (!File.Exists(path)) + var dims = GetImageSize(path); + if (dims.Width <= 0 || dims.Height <= 0) { - throw new FileNotFoundException("File not found", path); + // empty image does not have any blurhash + return string.Empty; } - // Use 4 vertical and 4 horizontal components of DCT of the image. + // We want tiles to be as close to square as possible, and to *mostly* keep under 16 tiles for performance. + // One tile is (width / xComp) x (height / yComp) pixels, which means that ideally yComp = xComp * height / width. // See more at https://github.com/woltapp/blurhash/#how-do-i-pick-the-number-of-x-and-y-components - return BlurHashEncoder.Encode(4, 4, path); + float xComp = MathF.Sqrt(16.0f * dims.Width / dims.Height); + float yComp = xComp * dims.Height / dims.Width; + + return BlurHashEncoder.Encode(Math.Min((int)xComp + 1, 9), Math.Min((int)yComp + 1, 9), path); } private static bool HasDiacritics(string text) -- 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(-) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 1f5a7d177..0680c5ffe 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 3af18f681..e7b4b0ec3 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 c7bd242a7..d430d4d16 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 93cec1304..1f76dd4e3 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 8064ea7dc..1e14ea552 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 bda49bd1b..28a3ac505 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 e9ebe07ecc38726ad7f14c8d38131ce101a28651 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 26 May 2020 16:36:54 +0200 Subject: Don't send Exception message in Production Environment --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 7de4f168c..9e8c3eb9b 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -230,6 +230,12 @@ namespace Emby.Server.Implementations.HttpServer httpRes.StatusCode = statusCode; + if (!_hostEnvironment.IsDevelopment()) + { + await httpRes.WriteAsync("Error processing request.").ConfigureAwait(false); + return; + } + var errContent = NormalizeExceptionMessage(ex) ?? string.Empty; httpRes.ContentType = "text/plain"; httpRes.ContentLength = errContent.Length; -- cgit v1.2.3 From a5a39300bc733ad7b1d3c683f5f290a742171661 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 26 May 2020 16:55:27 +0200 Subject: Don't send Exception message in Production Environment --- Jellyfin.Server/Middleware/ExceptionMiddleware.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs index 0d79bbfaf..dd4d1ee99 100644 --- a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs +++ b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs @@ -7,7 +7,9 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Net; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace Jellyfin.Server.Middleware @@ -20,6 +22,7 @@ namespace Jellyfin.Server.Middleware private readonly RequestDelegate _next; private readonly ILogger _logger; private readonly IServerConfigurationManager _configuration; + private readonly IWebHostEnvironment _hostEnvironment; /// /// Initializes a new instance of the class. @@ -27,14 +30,17 @@ namespace Jellyfin.Server.Middleware /// Next request delegate. /// Instance of the interface. /// Instance of the interface. + /// Instance of the interface. public ExceptionMiddleware( RequestDelegate next, ILogger logger, - IServerConfigurationManager serverConfigurationManager) + IServerConfigurationManager serverConfigurationManager, + IWebHostEnvironment hostEnvironment) { _next = next; _logger = logger; _configuration = serverConfigurationManager; + _hostEnvironment = hostEnvironment; } /// @@ -85,6 +91,14 @@ namespace Jellyfin.Server.Middleware context.Response.StatusCode = GetStatusCode(ex); context.Response.ContentType = MediaTypeNames.Text.Plain; + + // Don't send exception unless the server is in a Development environment + if (!_hostEnvironment.IsDevelopment()) + { + await context.Response.WriteAsync("Error processing request.").ConfigureAwait(false); + return; + } + var errorContent = NormalizeExceptionMessage(ex.Message); await context.Response.WriteAsync(errorContent).ConfigureAwait(false); } -- cgit v1.2.3 From b4b93995f7f05971bd8532cadc9b80db07de9ab3 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Thu, 9 Apr 2020 00:15:01 +0800 Subject: add more separate hw decoding toggles --- .../MediaEncoding/EncodingHelper.cs | 267 ++++++++++++++++++--- .../MediaEncoding/IMediaEncoder.cs | 15 +- .../Encoder/EncoderValidator.cs | 77 ++++-- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 13 + .../Subtitles/SubtitleEncoder.cs | 2 +- .../Configuration/EncodingOptions.cs | 2 + 6 files changed, 330 insertions(+), 46 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 61a330675..807ba4e93 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -103,6 +103,11 @@ namespace MediaBrowser.Controller.MediaEncoding return false; } + if (!_mediaEncoder.SupportsHwaccel("vaapi")) + { + return false; + } + return true; } @@ -444,18 +449,30 @@ namespace MediaBrowser.Controller.MediaEncoding public string GetInputArgument(EncodingJobInfo state, EncodingOptions encodingOptions) { var arg = new StringBuilder(); + var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions); + var outputVideoCodec = GetVideoEncoder(state, encodingOptions); - if (state.IsVideoRequest - && string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + if (state.IsVideoRequest && string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) { - arg.Append("-hwaccel vaapi -hwaccel_output_format vaapi") - .Append(" -vaapi_device ") - .Append(encodingOptions.VaapiDevice) - .Append(' '); + // While using VAAPI decoder + if ((videoDecoder ?? string.Empty).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1) + { + arg.Append("-hwaccel_output_format vaapi") + .Append(" -vaapi_device ") + .Append(encodingOptions.VaapiDevice) + .Append(" "); + } + // While using SW decoder and VAAPI encoder + else if ((videoDecoder ?? string.Empty).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) == -1 + && (outputVideoCodec ?? string.Empty).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1) + { + arg.Append("-vaapi_device ") + .Append(encodingOptions.VaapiDevice) + .Append(" "); + } } - if (state.IsVideoRequest - && string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) + if (state.IsVideoRequest && string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions); var outputVideoCodec = GetVideoEncoder(state, encodingOptions); @@ -1655,7 +1672,7 @@ namespace MediaBrowser.Controller.MediaEncoding For software decoding and hardware encoding option, frames must be hwuploaded into hardware with fixed frame size. */ - if (!string.IsNullOrEmpty(videoDecoder) && videoDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)) + if ((videoDecoder ?? string.Empty).IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1) { retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv=x=(W-w)/2:y=(H-h)/2{3}\""; } @@ -2511,16 +2528,15 @@ namespace MediaBrowser.Controller.MediaEncoding /// protected string GetHardwareAcceleratedVideoDecoder(EncodingJobInfo state, EncodingOptions encodingOptions) { + var videoType = state.MediaSource.VideoType ?? VideoType.VideoFile; + var videoStream = state.VideoStream; + var IsColorDepth10 = (videoStream.Profile ?? string.Empty).IndexOf("10", StringComparison.OrdinalIgnoreCase) != -1; + if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { return null; } - return GetHardwareAcceleratedVideoDecoder(state.MediaSource.VideoType ?? VideoType.VideoFile, state.VideoStream, encodingOptions); - } - - public string GetHardwareAcceleratedVideoDecoder(VideoType videoType, MediaStream videoStream, EncodingOptions encodingOptions) - { // Only use alternative encoders for video files. // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this. @@ -2533,6 +2549,14 @@ namespace MediaBrowser.Controller.MediaEncoding && !string.IsNullOrEmpty(videoStream.Codec) && !string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType)) { + // Only hevc and vp9 formats have 10-bit hardware decoder support now. + if (IsColorDepth10 && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))) + { + return null; + } + if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { switch (videoStream.Codec.ToLowerInvariant()) @@ -2554,8 +2578,17 @@ namespace MediaBrowser.Controller.MediaEncoding case "h265": if (_mediaEncoder.SupportsDecoder("hevc_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase)) { - //return "-c:v hevc_qsv -load_plugin hevc_hw "; - return "-c:v hevc_qsv"; + if (IsColorDepth10) + { + if (encodingOptions.EnableDecodingColorDepth10) + { + return "-c:v hevc_qsv "; + } + + return null; + } + + return "-c:v hevc_qsv "; } break; case "mpeg2video": @@ -2570,6 +2603,28 @@ namespace MediaBrowser.Controller.MediaEncoding return "-c:v vc1_qsv"; } break; + case "vp8": + if (_mediaEncoder.SupportsDecoder("vp8_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("vp8", StringComparer.OrdinalIgnoreCase)) + { + return "-c:v vp8_qsv "; + } + break; + case "vp9": + if (_mediaEncoder.SupportsDecoder("vp9_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase)) + { + if (IsColorDepth10) + { + if (encodingOptions.EnableDecodingColorDepth10) + { + return "-c:v vp9_qsv "; + } + + return null; + } + + return "-c:v vp9_qsv "; + } + break; } } @@ -2594,7 +2649,17 @@ namespace MediaBrowser.Controller.MediaEncoding case "h265": if (_mediaEncoder.SupportsDecoder("hevc_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase)) { - return "-c:v hevc_cuvid"; + if (IsColorDepth10) + { + if (encodingOptions.EnableDecodingColorDepth10) + { + return "-c:v hevc_cuvid "; + } + + return null; + } + + return "-c:v hevc_cuvid "; } break; case "mpeg2video": @@ -2615,6 +2680,28 @@ namespace MediaBrowser.Controller.MediaEncoding return "-c:v mpeg4_cuvid"; } break; + case "vp8": + if (_mediaEncoder.SupportsDecoder("vp8_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("vp8", StringComparer.OrdinalIgnoreCase)) + { + return "-c:v vp8_cuvid "; + } + break; + case "vp9": + if (_mediaEncoder.SupportsDecoder("vp9_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase)) + { + if (IsColorDepth10) + { + if (encodingOptions.EnableDecodingColorDepth10) + { + return "-c:v vp9_cuvid "; + } + + return null; + } + + return "-c:v vp9_cuvid "; + } + break; } } @@ -2633,7 +2720,17 @@ namespace MediaBrowser.Controller.MediaEncoding case "h265": if (_mediaEncoder.SupportsDecoder("hevc_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase)) { - return "-c:v hevc_mediacodec"; + if (IsColorDepth10) + { + if (encodingOptions.EnableDecodingColorDepth10) + { + return "-c:v hevc_mediacodec "; + } + + return null; + } + + return "-c:v hevc_mediacodec "; } break; case "mpeg2video": @@ -2657,7 +2754,17 @@ namespace MediaBrowser.Controller.MediaEncoding case "vp9": if (_mediaEncoder.SupportsDecoder("vp9_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase)) { - return "-c:v vp9_mediacodec"; + if (IsColorDepth10) + { + if (encodingOptions.EnableDecodingColorDepth10) + { + return "-c:v vp9_mediacodec "; + } + + return null; + } + + return "-c:v vp9_mediacodec "; } break; } @@ -2697,27 +2804,133 @@ namespace MediaBrowser.Controller.MediaEncoding else if (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) { - if (Environment.OSVersion.Platform == PlatformID.Win32NT) + switch (videoStream.Codec.ToLowerInvariant()) { - if (Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1)) - return "-hwaccel d3d11va"; - else - return "-hwaccel dxva2"; + case "avc": + case "h264": + return GetHwaccelType(state, encodingOptions, "h264"); + case "hevc": + case "h265": + if (IsColorDepth10) + { + if (encodingOptions.EnableDecodingColorDepth10) + { + return GetHwaccelType(state, encodingOptions, "hevc"); + } + + return null; + } + + return GetHwaccelType(state, encodingOptions, "hevc"); + case "mpeg2video": + return GetHwaccelType(state, encodingOptions, "mpeg2video"); + case "vc1": + return GetHwaccelType(state, encodingOptions, "vc1"); + case "mpeg4": + return GetHwaccelType(state, encodingOptions, "mpeg4"); + case "vp9": + if (IsColorDepth10) + { + if (encodingOptions.EnableDecodingColorDepth10) + { + return GetHwaccelType(state, encodingOptions, "vp9"); + } + + return null; + } + + return GetHwaccelType(state, encodingOptions, "vp9"); } - else + } + + else if (string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + { + switch (videoStream.Codec.ToLowerInvariant()) { - return "-hwaccel vaapi"; + case "avc": + case "h264": + return GetHwaccelType(state, encodingOptions, "h264"); + case "hevc": + case "h265": + if (IsColorDepth10) + { + if (encodingOptions.EnableDecodingColorDepth10) + { + return GetHwaccelType(state, encodingOptions, "hevc"); + } + + return null; + } + + return GetHwaccelType(state, encodingOptions, "hevc"); + case "mpeg2video": + return GetHwaccelType(state, encodingOptions, "mpeg2video"); + case "vc1": + return GetHwaccelType(state, encodingOptions, "vc1"); + case "vp8": + return GetHwaccelType(state, encodingOptions, "vp8"); + case "vp9": + if (IsColorDepth10) + { + if (encodingOptions.EnableDecodingColorDepth10) + { + return GetHwaccelType(state, encodingOptions, "vp9"); + } + + return null; + } + + return GetHwaccelType(state, encodingOptions, "vp9"); } } } + var whichCodec = videoStream.Codec.ToLowerInvariant(); + switch (whichCodec) + { + case "avc": + whichCodec = "h264"; + break; + case "h265": + whichCodec = "hevc"; + break; + } + // Avoid a second attempt if no hardware acceleration is being used - encodingOptions.HardwareDecodingCodecs = Array.Empty(); + encodingOptions.HardwareDecodingCodecs = encodingOptions.HardwareDecodingCodecs.Where(val => val != whichCodec).ToArray(); // leave blank so ffmpeg will decide return null; } + /// + /// Gets a hwaccel type to use as a hardware decoder(dxva/vaapi) depending on the system + /// + public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec) + { + var IsWindows = Environment.OSVersion.Platform == PlatformID.Win32NT; + var IsNewWindows = Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1); + var IsDxvaSupported = _mediaEncoder.SupportsHwaccel("dxva2") || _mediaEncoder.SupportsHwaccel("d3d11va"); + + if ((IsDxvaSupported || IsVaapiSupported(state)) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase)) + { + if (!IsWindows) + { + return "-hwaccel vaapi "; + } + else if (IsWindows && IsNewWindows) + { + return "-hwaccel d3d11va "; + } + else if (IsWindows && !IsNewWindows) + { + return "-hwaccel dxva2 "; + } + } + + return null; + } + public string GetSubtitleEmbedArguments(EncodingJobInfo state) { if (state.SubtitleStream == null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed) diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 37f0b11a7..6fa3bed48 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -26,6 +26,13 @@ namespace MediaBrowser.Controller.MediaEncoding /// The encoder path. string EncoderPath { get; } + /// + /// Supportses the encoder. + /// + /// The encoder. + /// true if XXXX, false otherwise. + bool SupportsEncoder(string encoder); + /// /// Supportses the decoder. /// @@ -33,6 +40,13 @@ namespace MediaBrowser.Controller.MediaEncoding /// true if XXXX, false otherwise. bool SupportsDecoder(string decoder); + /// + /// Supportses the hwaccel. + /// + /// The hwaccel. + /// true if XXXX, false otherwise. + bool SupportsHwaccel(string hwaccel); + /// /// Extracts the audio image. /// @@ -98,7 +112,6 @@ namespace MediaBrowser.Controller.MediaEncoding void SetFFmpegPath(); void UpdateEncoderPath(string path, string pathType); - bool SupportsEncoder(string encoder); IEnumerable GetPrimaryPlaylistVobFiles(string path, IIsoMount isoMount, uint? titleNumber); } diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 6e036d24c..e329f605d 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -14,23 +14,38 @@ namespace MediaBrowser.MediaEncoding.Encoder private static readonly string[] requiredDecoders = new[] { + "h264", + "hevc", "mpeg2video", + "mpeg4", + "msmpeg4", + "dts", + "ac3", + "aac", + "mp3", "h264_qsv", "hevc_qsv", "mpeg2_qsv", - "mpeg2_mmal", - "mpeg4_mmal", "vc1_qsv", - "vc1_mmal", + "vp8_qsv", + "vp9_qsv", "h264_cuvid", "hevc_cuvid", - "dts", - "ac3", - "aac", - "mp3", - "h264", + "mpeg2_cuvid", + "vc1_cuvid", + "mpeg4_cuvid", + "vp8_cuvid", + "vp9_cuvid", "h264_mmal", - "hevc" + "mpeg2_mmal", + "mpeg4_mmal", + "vc1_mmal", + "h264_mediacodec", + "hevc_mediacodec", + "mpeg2_mediacodec", + "mpeg4_mediacodec", + "vp8_mediacodec", + "vp9_mediacodec" }; private static readonly string[] requiredEncoders = new[] @@ -43,22 +58,22 @@ namespace MediaBrowser.MediaEncoding.Encoder "libvpx-vp9", "aac", "libfdk_aac", + "ac3", "libmp3lame", "libopus", "libvorbis", "srt", - "h264_nvenc", - "hevc_nvenc", + "h264_amf", + "hevc_amf", "h264_qsv", "hevc_qsv", - "h264_omx", - "hevc_omx", + "h264_nvenc", + "hevc_nvenc", "h264_vaapi", "hevc_vaapi", - "h264_v4l2m2m", - "ac3", - "h264_amf", - "hevc_amf" + "h264_omx", + "hevc_omx", + "h264_v4l2m2m" }; // Try and use the individual library versions to determine a FFmpeg version @@ -159,6 +174,8 @@ namespace MediaBrowser.MediaEncoding.Encoder public IEnumerable GetEncoders() => GetCodecs(Codec.Encoder); + public IEnumerable GetHwaccels() => GetHwaccelTypes(); + /// /// Using the output from "ffmpeg -version" work out the FFmpeg version. /// For pre-built binaries the first line should contain a string like "ffmpeg version x.y", which is easy @@ -218,6 +235,32 @@ namespace MediaBrowser.MediaEncoding.Encoder Decoder } + private IEnumerable GetHwaccelTypes() + { + string output = null; + try + { + output = GetProcessOutput(_encoderPath, "-hwaccels"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error detecting available hwaccel types"); + } + + if (string.IsNullOrWhiteSpace(output)) + { + return Enumerable.Empty(); + } + + var found = output.Split(new char[] {'\r','\n'},StringSplitOptions.RemoveEmptyEntries).Distinct().ToList(); + + found.RemoveAt(0); + + _logger.LogInformation("Available hwaccel types: {Types}", found); + + return found; + } + private IEnumerable GetCodecs(Codec codec) { string codecstr = codec == Codec.Encoder ? "encoders" : "decoders"; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 1377502dd..4cc971809 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -111,6 +111,7 @@ namespace MediaBrowser.MediaEncoding.Encoder SetAvailableDecoders(validator.GetDecoders()); SetAvailableEncoders(validator.GetEncoders()); + SetAvailableHwaccels(validator.GetHwaccels()); } _logger.LogInformation("FFmpeg: {EncoderLocation}: {FfmpegPath}", EncoderLocation, _ffmpegPath ?? string.Empty); @@ -257,6 +258,13 @@ namespace MediaBrowser.MediaEncoding.Encoder //_logger.Info("Supported decoders: {0}", string.Join(",", list.ToArray())); } + private List _hwaccels = new List(); + public void SetAvailableHwaccels(IEnumerable list) + { + _hwaccels = list.ToList(); + //_logger.Info("Supported hwaccels: {0}", string.Join(",", list.ToArray())); + } + public bool SupportsEncoder(string encoder) { return _encoders.Contains(encoder, StringComparer.OrdinalIgnoreCase); @@ -267,6 +275,11 @@ namespace MediaBrowser.MediaEncoding.Encoder return _decoders.Contains(decoder, StringComparer.OrdinalIgnoreCase); } + public bool SupportsHwaccel(string hwaccel) + { + return _hwaccels.Contains(hwaccel, StringComparer.OrdinalIgnoreCase); + } + public bool CanEncodeToAudioCodec(string codec) { if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index ba171295e..7d85f6ef7 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -737,7 +737,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles var charset = CharsetDetector.DetectFromStream(stream).Detected?.EncodingName; // UTF16 is automatically converted to UTF8 by FFmpeg, do not specify a character encoding - if ((path.EndsWith(".ass") || path.EndsWith(".ssa")) + if ((path.EndsWith(".ass") || path.EndsWith(".ssa") || path.EndsWith(".srt")) && (string.Equals(charset, "utf-16le", StringComparison.OrdinalIgnoreCase) || string.Equals(charset, "utf-16be", StringComparison.OrdinalIgnoreCase))) { diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 648568fd7..dfc0deea9 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -25,6 +25,7 @@ namespace MediaBrowser.Model.Configuration public int H265Crf { get; set; } public string EncoderPreset { get; set; } public string DeinterlaceMethod { get; set; } + public bool EnableDecodingColorDepth10 { get; set; } public bool EnableHardwareEncoding { get; set; } public bool EnableSubtitleExtraction { get; set; } @@ -41,6 +42,7 @@ namespace MediaBrowser.Model.Configuration H264Crf = 23; H265Crf = 28; DeinterlaceMethod = "yadif"; + EnableDecodingColorDepth10 = true; EnableHardwareEncoding = true; EnableSubtitleExtraction = true; HardwareDecodingCodecs = new string[] { "h264", "vc1" }; -- cgit v1.2.3 From c4ba71d96a4f8651a65b901861bf0f2c053f5f50 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Thu, 9 Apr 2020 01:38:06 +0800 Subject: resolve conflicts --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 807ba4e93..1a12ff721 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2019,7 +2019,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first - else if (IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) + else if ((videoDecoder ?? string.Empty).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1 && string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase)) { var codec = videoStream.Codec.ToLowerInvariant(); -- cgit v1.2.3 From 161b2a2da95b35631d005cae48a651dcad6fe9a5 Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Fri, 10 Apr 2020 23:40:56 +0800 Subject: minor changes --- .../MediaEncoding/EncodingHelper.cs | 46 +++++++++++----------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 1a12ff721..4a019321d 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -457,8 +457,8 @@ namespace MediaBrowser.Controller.MediaEncoding // While using VAAPI decoder if ((videoDecoder ?? string.Empty).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1) { - arg.Append("-hwaccel_output_format vaapi") - .Append(" -vaapi_device ") + arg.Append("-hwaccel_output_format vaapi ") + .Append("-vaapi_device ") .Append(encodingOptions.VaapiDevice) .Append(" "); } @@ -1610,8 +1610,10 @@ namespace MediaBrowser.Controller.MediaEncoding // For VAAPI and CUVID decoder // these encoders cannot automatically adjust the size of graphical subtitles to fit the output video, // thus needs to be manually adjusted. - if ((IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) - || (videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1) + if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1 + || (IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) + && ((videoDecoder ?? string.Empty).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1 + || (outputVideoCodec ?? string.Empty).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1))) { var videoStream = state.VideoStream; var inputWidth = videoStream?.Width; @@ -1653,7 +1655,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first - else if (IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) + else if (IsVaapiSupported(state) && (videoDecoder ?? string.Empty).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1 && string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase)) { /* @@ -2582,13 +2584,13 @@ namespace MediaBrowser.Controller.MediaEncoding { if (encodingOptions.EnableDecodingColorDepth10) { - return "-c:v hevc_qsv "; + return "-c:v hevc_qsv"; } return null; } - return "-c:v hevc_qsv "; + return "-c:v hevc_qsv"; } break; case "mpeg2video": @@ -2606,7 +2608,7 @@ namespace MediaBrowser.Controller.MediaEncoding case "vp8": if (_mediaEncoder.SupportsDecoder("vp8_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("vp8", StringComparer.OrdinalIgnoreCase)) { - return "-c:v vp8_qsv "; + return "-c:v vp8_qsv"; } break; case "vp9": @@ -2616,13 +2618,13 @@ namespace MediaBrowser.Controller.MediaEncoding { if (encodingOptions.EnableDecodingColorDepth10) { - return "-c:v vp9_qsv "; + return "-c:v vp9_qsv"; } return null; } - return "-c:v vp9_qsv "; + return "-c:v vp9_qsv"; } break; } @@ -2653,13 +2655,13 @@ namespace MediaBrowser.Controller.MediaEncoding { if (encodingOptions.EnableDecodingColorDepth10) { - return "-c:v hevc_cuvid "; + return "-c:v hevc_cuvid"; } return null; } - return "-c:v hevc_cuvid "; + return "-c:v hevc_cuvid"; } break; case "mpeg2video": @@ -2683,7 +2685,7 @@ namespace MediaBrowser.Controller.MediaEncoding case "vp8": if (_mediaEncoder.SupportsDecoder("vp8_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("vp8", StringComparer.OrdinalIgnoreCase)) { - return "-c:v vp8_cuvid "; + return "-c:v vp8_cuvid"; } break; case "vp9": @@ -2693,13 +2695,13 @@ namespace MediaBrowser.Controller.MediaEncoding { if (encodingOptions.EnableDecodingColorDepth10) { - return "-c:v vp9_cuvid "; + return "-c:v vp9_cuvid"; } return null; } - return "-c:v vp9_cuvid "; + return "-c:v vp9_cuvid"; } break; } @@ -2724,13 +2726,13 @@ namespace MediaBrowser.Controller.MediaEncoding { if (encodingOptions.EnableDecodingColorDepth10) { - return "-c:v hevc_mediacodec "; + return "-c:v hevc_mediacodec"; } return null; } - return "-c:v hevc_mediacodec "; + return "-c:v hevc_mediacodec"; } break; case "mpeg2video": @@ -2758,13 +2760,13 @@ namespace MediaBrowser.Controller.MediaEncoding { if (encodingOptions.EnableDecodingColorDepth10) { - return "-c:v vp9_mediacodec "; + return "-c:v vp9_mediacodec"; } return null; } - return "-c:v vp9_mediacodec "; + return "-c:v vp9_mediacodec"; } break; } @@ -2916,15 +2918,15 @@ namespace MediaBrowser.Controller.MediaEncoding { if (!IsWindows) { - return "-hwaccel vaapi "; + return "-hwaccel vaapi"; } else if (IsWindows && IsNewWindows) { - return "-hwaccel d3d11va "; + return "-hwaccel d3d11va"; } else if (IsWindows && !IsNewWindows) { - return "-hwaccel dxva2 "; + return "-hwaccel dxva2"; } } -- cgit v1.2.3 From 695f20b3035621957e5db994478a2046a405c785 Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Sat, 11 Apr 2020 01:21:26 +0800 Subject: probe Main/High 10 more specifically --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 4a019321d..fb8b50bf2 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2532,7 +2532,8 @@ namespace MediaBrowser.Controller.MediaEncoding { var videoType = state.MediaSource.VideoType ?? VideoType.VideoFile; var videoStream = state.VideoStream; - var IsColorDepth10 = (videoStream.Profile ?? string.Empty).IndexOf("10", StringComparison.OrdinalIgnoreCase) != -1; + var IsColorDepth10 = (videoStream.Profile ?? string.Empty).IndexOf("Main 10", StringComparison.OrdinalIgnoreCase) != -1 + || (videoStream.Profile ?? string.Empty).IndexOf("High 10", StringComparison.OrdinalIgnoreCase) != -1; if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { -- cgit v1.2.3 From 5fd3ea8b21cc48c4b7d0f4f29933ac8f9f4ad61a Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Sun, 12 Apr 2020 17:37:30 +0800 Subject: minor changes --- .../MediaEncoding/EncodingHelper.cs | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index fb8b50bf2..e0a2224ed 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2532,8 +2532,8 @@ namespace MediaBrowser.Controller.MediaEncoding { var videoType = state.MediaSource.VideoType ?? VideoType.VideoFile; var videoStream = state.VideoStream; - var IsColorDepth10 = (videoStream.Profile ?? string.Empty).IndexOf("Main 10", StringComparison.OrdinalIgnoreCase) != -1 - || (videoStream.Profile ?? string.Empty).IndexOf("High 10", StringComparison.OrdinalIgnoreCase) != -1; + var isColorDepth10 = !string.IsNullOrEmpty(videoStream.Profile) && (videoStream.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase) + || videoStream.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase)); if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { @@ -2553,7 +2553,7 @@ namespace MediaBrowser.Controller.MediaEncoding && !string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType)) { // Only hevc and vp9 formats have 10-bit hardware decoder support now. - if (IsColorDepth10 && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase) + if (isColorDepth10 && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase) || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase) || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))) { @@ -2581,7 +2581,7 @@ namespace MediaBrowser.Controller.MediaEncoding case "h265": if (_mediaEncoder.SupportsDecoder("hevc_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase)) { - if (IsColorDepth10) + if (isColorDepth10) { if (encodingOptions.EnableDecodingColorDepth10) { @@ -2615,7 +2615,7 @@ namespace MediaBrowser.Controller.MediaEncoding case "vp9": if (_mediaEncoder.SupportsDecoder("vp9_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase)) { - if (IsColorDepth10) + if (isColorDepth10) { if (encodingOptions.EnableDecodingColorDepth10) { @@ -2652,7 +2652,7 @@ namespace MediaBrowser.Controller.MediaEncoding case "h265": if (_mediaEncoder.SupportsDecoder("hevc_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase)) { - if (IsColorDepth10) + if (isColorDepth10) { if (encodingOptions.EnableDecodingColorDepth10) { @@ -2692,7 +2692,7 @@ namespace MediaBrowser.Controller.MediaEncoding case "vp9": if (_mediaEncoder.SupportsDecoder("vp9_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase)) { - if (IsColorDepth10) + if (isColorDepth10) { if (encodingOptions.EnableDecodingColorDepth10) { @@ -2723,7 +2723,7 @@ namespace MediaBrowser.Controller.MediaEncoding case "h265": if (_mediaEncoder.SupportsDecoder("hevc_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase)) { - if (IsColorDepth10) + if (isColorDepth10) { if (encodingOptions.EnableDecodingColorDepth10) { @@ -2757,7 +2757,7 @@ namespace MediaBrowser.Controller.MediaEncoding case "vp9": if (_mediaEncoder.SupportsDecoder("vp9_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase)) { - if (IsColorDepth10) + if (isColorDepth10) { if (encodingOptions.EnableDecodingColorDepth10) { @@ -2814,7 +2814,7 @@ namespace MediaBrowser.Controller.MediaEncoding return GetHwaccelType(state, encodingOptions, "h264"); case "hevc": case "h265": - if (IsColorDepth10) + if (isColorDepth10) { if (encodingOptions.EnableDecodingColorDepth10) { @@ -2832,7 +2832,7 @@ namespace MediaBrowser.Controller.MediaEncoding case "mpeg4": return GetHwaccelType(state, encodingOptions, "mpeg4"); case "vp9": - if (IsColorDepth10) + if (isColorDepth10) { if (encodingOptions.EnableDecodingColorDepth10) { @@ -2855,7 +2855,7 @@ namespace MediaBrowser.Controller.MediaEncoding return GetHwaccelType(state, encodingOptions, "h264"); case "hevc": case "h265": - if (IsColorDepth10) + if (isColorDepth10) { if (encodingOptions.EnableDecodingColorDepth10) { @@ -2873,7 +2873,7 @@ namespace MediaBrowser.Controller.MediaEncoding case "vp8": return GetHwaccelType(state, encodingOptions, "vp8"); case "vp9": - if (IsColorDepth10) + if (isColorDepth10) { if (encodingOptions.EnableDecodingColorDepth10) { -- cgit v1.2.3 From 22ef0e3574418ed22fe24db149613d71bb8daf9a Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Mon, 13 Apr 2020 10:52:21 +0800 Subject: drop 'force_original_aspect_ratio' graphical subtitles can be off-center in some cases --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index e0a2224ed..f7aaf8645 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1593,11 +1593,9 @@ namespace MediaBrowser.Controller.MediaEncoding // Setup subtitle scaling if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue) { - // force_original_aspect_ratio=decrease - // Enable decreasing output video width or height if necessary to keep the original aspect ratio videoSizeParam = string.Format( CultureInfo.InvariantCulture, - "scale={0}:{1}:force_original_aspect_ratio=decrease", + "scale={0}:{1}", state.VideoStream.Width.Value, state.VideoStream.Height.Value); @@ -1624,7 +1622,7 @@ namespace MediaBrowser.Controller.MediaEncoding { videoSizeParam = string.Format( CultureInfo.InvariantCulture, - "scale={0}:{1}:force_original_aspect_ratio=decrease", + "scale={0}:{1}", width.Value, height.Value); } -- cgit v1.2.3 From 0eb5791c702fb75ad74775ce41158cac8ef55d26 Mon Sep 17 00:00:00 2001 From: artiume Date: Mon, 25 May 2020 17:30:13 -0400 Subject: Comments --- .../MediaEncoding/EncodingHelper.cs | 124 ++++++--------------- 1 file changed, 33 insertions(+), 91 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index f7aaf8645..fe5d9c813 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -73,7 +73,8 @@ namespace MediaBrowser.Controller.MediaEncoding {"omx", hwEncoder + "_omx"}, {hwEncoder + "_v4l2m2m", hwEncoder + "_v4l2m2m"}, {"mediacodec", hwEncoder + "_mediacodec"}, - {"vaapi", hwEncoder + "_vaapi"} + {"vaapi", hwEncoder + "_vaapi"}, + {"videotoolbox", hwEncoder + "_videotoolbox"} }; if (!string.IsNullOrEmpty(hwType) @@ -103,12 +104,8 @@ namespace MediaBrowser.Controller.MediaEncoding return false; } - if (!_mediaEncoder.SupportsHwaccel("vaapi")) - { - return false; - } + if _mediaEncoder.SupportsHwaccel("vaapi"); - return true; } /// @@ -451,11 +448,15 @@ namespace MediaBrowser.Controller.MediaEncoding var arg = new StringBuilder(); var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions); var outputVideoCodec = GetVideoEncoder(state, encodingOptions); + bool isVaapiDecoder = (videoDecoder ?? string.Empty).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; + bool isVaapiEncoder = (outputVideoCodec ?? string.Empty).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; + bool isQsvDecoder = (videoDecoder ?? string.Empty).IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; + bool isQsvEncoder = (outputVideoCodec ?? string.Empty).IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; - if (state.IsVideoRequest && string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + if (state.IsVideoRequest + && string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) { - // While using VAAPI decoder - if ((videoDecoder ?? string.Empty).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1) + if (isVaapiDecoder) { arg.Append("-hwaccel_output_format vaapi ") .Append("-vaapi_device ") @@ -463,8 +464,7 @@ namespace MediaBrowser.Controller.MediaEncoding .Append(" "); } // While using SW decoder and VAAPI encoder - else if ((videoDecoder ?? string.Empty).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) == -1 - && (outputVideoCodec ?? string.Empty).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1) + else if (!isVaapiDecoder && isVaapiEncoder) { arg.Append("-vaapi_device ") .Append(encodingOptions.VaapiDevice) @@ -472,7 +472,8 @@ namespace MediaBrowser.Controller.MediaEncoding } } - if (state.IsVideoRequest && string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) + if (state.IsVideoRequest + && string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions); var outputVideoCodec = GetVideoEncoder(state, encodingOptions); @@ -481,11 +482,9 @@ namespace MediaBrowser.Controller.MediaEncoding if (!hasTextSubs) { - // While using QSV encoder - if ((outputVideoCodec ?? string.Empty).IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1) + if (isQsvEncoder) { - // While using QSV decoder - if ((videoDecoder ?? string.Empty).IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1) + if (isQsvDecoder) { arg.Append("-hwaccel qsv "); } @@ -543,6 +542,8 @@ namespace MediaBrowser.Controller.MediaEncoding || codec.IndexOf("hevc", StringComparison.OrdinalIgnoreCase) != -1; } + // TODO This is auto inserted into the mpegts mux so it might not be needed + // https://www.ffmpeg.org/ffmpeg-bitstream-filters.html#h264_005fmp4toannexb public string GetBitStreamArgs(MediaStream stream) { if (IsH264(stream)) @@ -567,8 +568,8 @@ namespace MediaBrowser.Controller.MediaEncoding { if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)) { - // With vpx when crf is used, b:v becomes a max rate - // https://trac.ffmpeg.org/wiki/vpxEncodingGuide. + // When crf is used with vpx, b:v becomes a max rate + // https://trac.ffmpeg.org/wiki/Encode/VP9 return string.Format( CultureInfo.InvariantCulture, " -maxrate:v {0} -bufsize:v {1} -b:v {0}", @@ -2579,17 +2580,8 @@ namespace MediaBrowser.Controller.MediaEncoding case "h265": if (_mediaEncoder.SupportsDecoder("hevc_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase)) { - if (isColorDepth10) - { - if (encodingOptions.EnableDecodingColorDepth10) - { - return "-c:v hevc_qsv"; - } - - return null; - } - - return "-c:v hevc_qsv"; + return (isColorDepth10 && + !encodingOptions.EnableDecodingColorDepth10) ? null : "-c:v hevc_qsv"; } break; case "mpeg2video": @@ -2613,17 +2605,8 @@ namespace MediaBrowser.Controller.MediaEncoding case "vp9": if (_mediaEncoder.SupportsDecoder("vp9_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase)) { - if (isColorDepth10) - { - if (encodingOptions.EnableDecodingColorDepth10) - { - return "-c:v vp9_qsv"; - } - - return null; - } - - return "-c:v vp9_qsv"; + return (isColorDepth10 && + !encodingOptions.EnableDecodingColorDepth10) ? null : "-c:v vp9_qsv"; } break; } @@ -2637,30 +2620,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(); - return null; - } - return "-c:v h264_cuvid"; + return (isColorDepth10 && + !encodingOptions.EnableDecodingColorDepth10) ? null : "-c:v h264_cuvid"; } break; case "hevc": case "h265": if (_mediaEncoder.SupportsDecoder("hevc_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase)) { - if (isColorDepth10) - { - if (encodingOptions.EnableDecodingColorDepth10) - { - return "-c:v hevc_cuvid"; - } - - return null; - } - - return "-c:v hevc_cuvid"; + return (isColorDepth10 && + !encodingOptions.EnableDecodingColorDepth10) ? null : "-c:v hevc_cuvid"; } break; case "mpeg2video": @@ -2690,17 +2659,8 @@ namespace MediaBrowser.Controller.MediaEncoding case "vp9": if (_mediaEncoder.SupportsDecoder("vp9_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase)) { - if (isColorDepth10) - { - if (encodingOptions.EnableDecodingColorDepth10) - { - return "-c:v vp9_cuvid"; - } - - return null; - } - - return "-c:v vp9_cuvid"; + return (isColorDepth10 && + !encodingOptions.EnableDecodingColorDepth10) ? null : "-c:v vp9_cuvid"; } break; } @@ -2721,17 +2681,8 @@ namespace MediaBrowser.Controller.MediaEncoding case "h265": if (_mediaEncoder.SupportsDecoder("hevc_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase)) { - if (isColorDepth10) - { - if (encodingOptions.EnableDecodingColorDepth10) - { - return "-c:v hevc_mediacodec"; - } - - return null; - } - - return "-c:v hevc_mediacodec"; + return (isColorDepth10 && + !encodingOptions.EnableDecodingColorDepth10) ? null : "-c:v hevc_mediacodec"; } break; case "mpeg2video": @@ -2755,17 +2706,8 @@ namespace MediaBrowser.Controller.MediaEncoding case "vp9": if (_mediaEncoder.SupportsDecoder("vp9_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase)) { - if (isColorDepth10) - { - if (encodingOptions.EnableDecodingColorDepth10) - { - return "-c:v vp9_mediacodec"; - } - - return null; - } - - return "-c:v vp9_mediacodec"; + return (isColorDepth10 && + !encodingOptions.EnableDecodingColorDepth10) ? null : "-c:v vp9_mediacodec"; } break; } -- cgit v1.2.3 From 62e47d056d81a6f835050446be130e0a00e55150 Mon Sep 17 00:00:00 2001 From: artiume Date: Mon, 25 May 2020 17:43:26 -0400 Subject: Update IMediaEncoder.cs --- MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 6fa3bed48..393a411a1 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -27,21 +27,21 @@ namespace MediaBrowser.Controller.MediaEncoding string EncoderPath { get; } /// - /// Supportses the encoder. + /// Whether given encoder codec is supported. /// /// The encoder. /// true if XXXX, false otherwise. bool SupportsEncoder(string encoder); /// - /// Supportses the decoder. + /// Whether given decoder codec is supported. /// /// The decoder. /// true if XXXX, false otherwise. bool SupportsDecoder(string decoder); /// - /// Supportses the hwaccel. + /// Whether given hardware acceleration type is supported. /// /// The hwaccel. /// true if XXXX, false otherwise. -- cgit v1.2.3 From 1ff95289ef88d058e09b8189b579a35351b26f82 Mon Sep 17 00:00:00 2001 From: artiume Date: Mon, 25 May 2020 17:58:29 -0400 Subject: Update EncoderValidator.cs --- MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index e329f605d..be77a855d 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -46,6 +46,12 @@ namespace MediaBrowser.MediaEncoding.Encoder "mpeg4_mediacodec", "vp8_mediacodec", "vp9_mediacodec" + "h264_videotoolbox", + "hevc_videotoolbox", + "mpeg2_videotoolbox", + "mpeg4_videotoolbox", + "vp8_videotoolbox", + "vp9_videotoolbox" }; private static readonly string[] requiredEncoders = new[] @@ -74,6 +80,8 @@ namespace MediaBrowser.MediaEncoding.Encoder "h264_omx", "hevc_omx", "h264_v4l2m2m" + "h264_videotoolbox" + "hevc_videotoolbox" }; // Try and use the individual library versions to determine a FFmpeg version @@ -252,7 +260,7 @@ namespace MediaBrowser.MediaEncoding.Encoder return Enumerable.Empty(); } - var found = output.Split(new char[] {'\r','\n'},StringSplitOptions.RemoveEmptyEntries).Distinct().ToList(); + var found = output.Split(new char[] {'\r','\n'}, StringSplitOptions.RemoveEmptyEntries).Distinct().ToList(); found.RemoveAt(0); -- cgit v1.2.3 From 407de0209ef0f78d5a8ec3f9af2557010124d9cb Mon Sep 17 00:00:00 2001 From: artiume Date: Mon, 25 May 2020 18:01:00 -0400 Subject: Update MediaEncoder.cs --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 4cc971809..c51b8c110 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -258,7 +258,7 @@ namespace MediaBrowser.MediaEncoding.Encoder //_logger.Info("Supported decoders: {0}", string.Join(",", list.ToArray())); } - private List _hwaccels = new List(); + private List _hwaccels = Array.Empty(); public void SetAvailableHwaccels(IEnumerable list) { _hwaccels = list.ToList(); -- cgit v1.2.3 From f056704c78e700d98d28d3c2688ba0ba1349bbf7 Mon Sep 17 00:00:00 2001 From: artiume Date: Mon, 25 May 2020 18:11:32 -0400 Subject: add videotoolbox --- .../MediaEncoding/EncodingHelper.cs | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index fe5d9c813..b3aeac709 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2826,6 +2826,38 @@ namespace MediaBrowser.Controller.MediaEncoding return GetHwaccelType(state, encodingOptions, "vp9"); } } + else if (string.Equals(encodingOptions.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) + { + switch (videoStream.Codec.ToLowerInvariant()) + { + case "avc": + case "h264": + if (_mediaEncoder.SupportsDecoder("h264_videotoolbox") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase)) + { + return "-c:v h264_videotoolbox"; + } + break; + case "mpeg2video": + if (_mediaEncoder.SupportsDecoder("mpeg2_videotoolbox") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase)) + { + return "-c:v mpeg2_videotoolbox"; + } + break; + case "mpeg4": + if (_mediaEncoder.SupportsDecoder("mpeg4_videotoolbox") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase)) + { + return "-c:v mpeg4_videotoolbox"; + } + break; + case "vc1": + if (_mediaEncoder.SupportsDecoder("vc1_videotoolbox") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase)) + { + return "-c:v vc1_videotoolbox"; + } + break; + } + } + } var whichCodec = videoStream.Codec.ToLowerInvariant(); -- cgit v1.2.3 From abc7558f51d4bb21979b8115b4b73ad35fa075b6 Mon Sep 17 00:00:00 2001 From: artiume Date: Mon, 25 May 2020 18:12:54 -0400 Subject: Update EncodingHelper.cs --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index b3aeac709..50acc35f2 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -104,7 +104,7 @@ namespace MediaBrowser.Controller.MediaEncoding return false; } - if _mediaEncoder.SupportsHwaccel("vaapi"); + return _mediaEncoder.SupportsHwaccel("vaapi"); } -- cgit v1.2.3 From 3c8237975924c326cfe460872654730f30e999db Mon Sep 17 00:00:00 2001 From: artiume Date: Mon, 25 May 2020 18:14:22 -0400 Subject: Update EncoderValidator.cs --- MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index be77a855d..89f33b048 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -51,7 +51,8 @@ namespace MediaBrowser.MediaEncoding.Encoder "mpeg2_videotoolbox", "mpeg4_videotoolbox", "vp8_videotoolbox", - "vp9_videotoolbox" + "vp9_videotoolbox", + "vc1_videotoolbox" }; private static readonly string[] requiredEncoders = new[] -- cgit v1.2.3 From 628734931ceb87f265320cf83b3fd29710a03c2b Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 26 May 2020 18:41:38 +0300 Subject: Fix missing commas and merge defects --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 3 --- MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs | 6 +++--- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 50acc35f2..94fa5c6c0 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -475,9 +475,6 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.IsVideoRequest && string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { - var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions); - var outputVideoCodec = GetVideoEncoder(state, encodingOptions); - var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; if (!hasTextSubs) diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 89f33b048..f155379a0 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -45,7 +45,7 @@ namespace MediaBrowser.MediaEncoding.Encoder "mpeg2_mediacodec", "mpeg4_mediacodec", "vp8_mediacodec", - "vp9_mediacodec" + "vp9_mediacodec", "h264_videotoolbox", "hevc_videotoolbox", "mpeg2_videotoolbox", @@ -80,8 +80,8 @@ namespace MediaBrowser.MediaEncoding.Encoder "hevc_vaapi", "h264_omx", "hevc_omx", - "h264_v4l2m2m" - "h264_videotoolbox" + "h264_v4l2m2m", + "h264_videotoolbox", "hevc_videotoolbox" }; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index c51b8c110..4cc971809 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -258,7 +258,7 @@ namespace MediaBrowser.MediaEncoding.Encoder //_logger.Info("Supported decoders: {0}", string.Join(",", list.ToArray())); } - private List _hwaccels = Array.Empty(); + private List _hwaccels = new List(); public void SetAvailableHwaccels(IEnumerable list) { _hwaccels = list.ToList(); -- cgit v1.2.3 From 3e381cfd5ef5a14d48cc904dc40ad1f6dc7a32e8 Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 26 May 2020 19:02:22 +0300 Subject: Clean GetHwaccelType Windows handling a tiny bit --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 94fa5c6c0..761ea160e 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2880,21 +2880,21 @@ namespace MediaBrowser.Controller.MediaEncoding /// public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec) { - var IsWindows = Environment.OSVersion.Platform == PlatformID.Win32NT; - var IsNewWindows = Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1); - var IsDxvaSupported = _mediaEncoder.SupportsHwaccel("dxva2") || _mediaEncoder.SupportsHwaccel("d3d11va"); + var isWindows = Environment.OSVersion.Platform == PlatformID.Win32NT; + var isWindows8orLater = Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1); + var isDxvaSupported = _mediaEncoder.SupportsHwaccel("dxva2") || _mediaEncoder.SupportsHwaccel("d3d11va"); - if ((IsDxvaSupported || IsVaapiSupported(state)) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase)) + if ((isDxvaSupported || IsVaapiSupported(state)) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase)) { - if (!IsWindows) + if (!isWindows) { return "-hwaccel vaapi"; } - else if (IsWindows && IsNewWindows) + else if (isWindows8orLater) { return "-hwaccel d3d11va"; } - else if (IsWindows && !IsNewWindows) + else { return "-hwaccel dxva2"; } -- cgit v1.2.3 From aa17a53e83a61c23b7cd33d0294f3ccfe1000daf Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 26 May 2020 19:05:44 +0300 Subject: Skip only line saying "Hardware acceleration methods:" instead of some random one --- MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index f155379a0..8910249af 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -261,10 +261,7 @@ namespace MediaBrowser.MediaEncoding.Encoder return Enumerable.Empty(); } - var found = output.Split(new char[] {'\r','\n'}, StringSplitOptions.RemoveEmptyEntries).Distinct().ToList(); - - found.RemoveAt(0); - + var found = output.Split(new char[] {'\r','\n'}, StringSplitOptions.RemoveEmptyEntries).Skip(1).Distinct().ToList(); _logger.LogInformation("Available hwaccel types: {Types}", found); return found; -- cgit v1.2.3 From 92008baf85113897a37c8ca565216d4a1dca2a50 Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 26 May 2020 19:12:13 +0300 Subject: Some simple cleanup --- .../MediaEncoding/EncodingHelper.cs | 29 +++++++++++----------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 761ea160e..cea9c7c15 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -446,12 +446,12 @@ namespace MediaBrowser.Controller.MediaEncoding public string GetInputArgument(EncodingJobInfo state, EncodingOptions encodingOptions) { var arg = new StringBuilder(); - var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions); - var outputVideoCodec = GetVideoEncoder(state, encodingOptions); - bool isVaapiDecoder = (videoDecoder ?? string.Empty).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; - bool isVaapiEncoder = (outputVideoCodec ?? string.Empty).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; - bool isQsvDecoder = (videoDecoder ?? string.Empty).IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; - bool isQsvEncoder = (outputVideoCodec ?? string.Empty).IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; + var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty; + var outputVideoCodec = GetVideoEncoder(state, encodingOptions) ?? string.Empty; + bool isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; + bool isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; + bool isQsvDecoder = videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; + bool isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; if (state.IsVideoRequest && string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) @@ -463,7 +463,6 @@ namespace MediaBrowser.Controller.MediaEncoding .Append(encodingOptions.VaapiDevice) .Append(" "); } - // While using SW decoder and VAAPI encoder else if (!isVaapiDecoder && isVaapiEncoder) { arg.Append("-vaapi_device ") @@ -1542,8 +1541,9 @@ namespace MediaBrowser.Controller.MediaEncoding EncodingOptions options, string outputVideoCodec) { - var outputSizeParam = string.Empty; + outputVideoCodec ??= string.Empty; + var outputSizeParam = string.Empty; var request = state.BaseRequest; // Add resolution params, if specified @@ -1586,7 +1586,7 @@ namespace MediaBrowser.Controller.MediaEncoding } var videoSizeParam = string.Empty; - var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options); + var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty; // Setup subtitle scaling if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue) @@ -1606,10 +1606,10 @@ namespace MediaBrowser.Controller.MediaEncoding // For VAAPI and CUVID decoder // these encoders cannot automatically adjust the size of graphical subtitles to fit the output video, // thus needs to be manually adjusted. - if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1 + if (videoDecoder.IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1 || (IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) - && ((videoDecoder ?? string.Empty).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1 - || (outputVideoCodec ?? string.Empty).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1))) + && (videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1 + || outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1))) { var videoStream = state.VideoStream; var inputWidth = videoStream?.Width; @@ -1651,7 +1651,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first - else if (IsVaapiSupported(state) && (videoDecoder ?? string.Empty).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1 + else if (IsVaapiSupported(state) && videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1 && string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase)) { /* @@ -1662,7 +1662,6 @@ namespace MediaBrowser.Controller.MediaEncoding outputSizeParam = outputSizeParam.TrimStart(','); retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\""; } - else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) { /* @@ -1670,7 +1669,7 @@ namespace MediaBrowser.Controller.MediaEncoding For software decoding and hardware encoding option, frames must be hwuploaded into hardware with fixed frame size. */ - if ((videoDecoder ?? string.Empty).IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1) + if (videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1) { retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv=x=(W-w)/2:y=(H-h)/2{3}\""; } -- cgit v1.2.3 From 0be3dfe7c53d8c3bb43c28ea02c8a594bcb903b2 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Tue, 26 May 2020 12:14:40 -0400 Subject: Revert "Fix emby/user/public API leaking sensitive data" --- Emby.Server.Implementations/Library/UserManager.cs | 25 ----------- MediaBrowser.Api/UserService.cs | 38 +++++------------ MediaBrowser.Controller/Library/IUserManager.cs | 8 ---- MediaBrowser.Model/Dto/PublicUserDto.cs | 48 ---------------------- 4 files changed, 11 insertions(+), 108 deletions(-) delete mode 100644 MediaBrowser.Model/Dto/PublicUserDto.cs diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index b8feb5535..d63bc6bda 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -608,31 +608,6 @@ namespace Emby.Server.Implementations.Library return dto; } - public PublicUserDto GetPublicUserDto(User user, string remoteEndPoint = null) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - IAuthenticationProvider authenticationProvider = GetAuthenticationProvider(user); - bool hasConfiguredPassword = authenticationProvider.HasPassword(user); - bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(authenticationProvider.GetEasyPasswordHash(user)); - - bool hasPassword = user.Configuration.EnableLocalPassword && - !string.IsNullOrEmpty(remoteEndPoint) && - _networkManager.IsInLocalNetwork(remoteEndPoint) ? hasConfiguredEasyPassword : hasConfiguredPassword; - - PublicUserDto dto = new PublicUserDto - { - Name = user.Name, - HasPassword = hasPassword, - HasConfiguredPassword = hasConfiguredPassword, - }; - - return dto; - } - public UserDto GetOfflineUserDto(User user) { var dto = GetUserDto(user); diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 7d4d5fcf9..78fc6c694 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.Api } [Route("/Users/Public", "GET", Summary = "Gets a list of publicly visible users for display on a login screen.")] - public class GetPublicUsers : IReturn + public class GetPublicUsers : IReturn { } @@ -266,38 +266,22 @@ namespace MediaBrowser.Api _authContext = authContext; } - /// - /// Gets the public available Users information - /// - /// The request. - /// System.Object. public object Get(GetPublicUsers request) { - var result = _userManager - .Users - .Where(item => !item.Policy.IsDisabled); - - if (ServerConfigurationManager.Configuration.IsStartupWizardCompleted) + // If the startup wizard hasn't been completed then just return all users + if (!ServerConfigurationManager.Configuration.IsStartupWizardCompleted) { - var deviceId = _authContext.GetAuthorizationInfo(Request).DeviceId; - result = result.Where(item => !item.Policy.IsHidden); - - if (!string.IsNullOrWhiteSpace(deviceId)) + return Get(new GetUsers { - result = result.Where(i => _deviceManager.CanAccessDevice(i, deviceId)); - } - - if (!_networkManager.IsInLocalNetwork(Request.RemoteIp)) - { - result = result.Where(i => i.Policy.EnableRemoteAccess); - } + IsDisabled = false + }); } - return ToOptimizedResult(result - .OrderBy(u => u.Name) - .Select(i => _userManager.GetPublicUserDto(i, Request.RemoteIp)) - .ToArray() - ); + return Get(new GetUsers + { + IsHidden = false, + IsDisabled = false + }, true, true); } /// diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index ec6cb35eb..be7b4ce59 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -143,14 +143,6 @@ namespace MediaBrowser.Controller.Library /// UserDto. UserDto GetUserDto(User user, string remoteEndPoint = null); - /// - /// Gets the user public dto. - /// - /// Ther user.\ - /// The remote end point. - /// A public UserDto, aka a UserDto stripped of personal data. - PublicUserDto GetPublicUserDto(User user, string remoteEndPoint = null); - /// /// Authenticates the user. /// diff --git a/MediaBrowser.Model/Dto/PublicUserDto.cs b/MediaBrowser.Model/Dto/PublicUserDto.cs deleted file mode 100644 index b6bfaf2e9..000000000 --- a/MediaBrowser.Model/Dto/PublicUserDto.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; - -namespace MediaBrowser.Model.Dto -{ - /// - /// Class PublicUserDto. Its goal is to show only public information about a user - /// - public class PublicUserDto : IItemDto - { - /// - /// Gets or sets the name. - /// - /// The name. - public string Name { get; set; } - - /// - /// Gets or sets the primary image tag. - /// - /// The primary image tag. - public string PrimaryImageTag { get; set; } - - /// - /// Gets or sets a value indicating whether this instance has password. - /// - /// true if this instance has password; otherwise, false. - public bool HasPassword { get; set; } - - /// - /// Gets or sets a value indicating whether this instance has configured password. - /// Note that in this case this method should not be here, but it is necessary when changing password at the - /// first login. - /// - /// true if this instance has configured password; otherwise, false. - public bool HasConfiguredPassword { get; set; } - - /// - /// Gets or sets the primary image aspect ratio. - /// - /// The primary image aspect ratio. - public double? PrimaryImageAspectRatio { get; set; } - - /// - public override string ToString() - { - return Name ?? base.ToString(); - } - } -} -- cgit v1.2.3 From 8be13b63d494e6541bed075538f84e77202f6c7b Mon Sep 17 00:00:00 2001 From: Vasily Date: Tue, 26 May 2020 19:19:49 +0300 Subject: More cleanup --- .../MediaEncoding/EncodingHelper.cs | 62 ++++------------------ 1 file changed, 10 insertions(+), 52 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index cea9c7c15..fe2130610 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1991,7 +1991,7 @@ namespace MediaBrowser.Controller.MediaEncoding var videoStream = state.VideoStream; var filters = new List(); - var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options); + var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty; var inputWidth = videoStream?.Width; var inputHeight = videoStream?.Height; var threeDFormat = state.MediaSource.Video3DFormat; @@ -2016,7 +2016,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first - else if ((videoDecoder ?? string.Empty).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1 + else if (videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1 && string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase)) { var codec = videoStream.Codec.ToLowerInvariant(); @@ -2607,7 +2607,6 @@ namespace MediaBrowser.Controller.MediaEncoding break; } } - else if (string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) { switch (videoStream.Codec.ToLowerInvariant()) @@ -2661,7 +2660,6 @@ namespace MediaBrowser.Controller.MediaEncoding break; } } - else if (string.Equals(encodingOptions.HardwareAccelerationType, "mediacodec", StringComparison.OrdinalIgnoreCase)) { switch (videoStream.Codec.ToLowerInvariant()) @@ -2708,7 +2706,6 @@ namespace MediaBrowser.Controller.MediaEncoding break; } } - else if (string.Equals(encodingOptions.HardwareAccelerationType, "omx", StringComparison.OrdinalIgnoreCase)) { switch (videoStream.Codec.ToLowerInvariant()) @@ -2740,7 +2737,6 @@ namespace MediaBrowser.Controller.MediaEncoding break; } } - else if (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) { switch (videoStream.Codec.ToLowerInvariant()) @@ -2750,17 +2746,8 @@ namespace MediaBrowser.Controller.MediaEncoding return GetHwaccelType(state, encodingOptions, "h264"); case "hevc": case "h265": - if (isColorDepth10) - { - if (encodingOptions.EnableDecodingColorDepth10) - { - return GetHwaccelType(state, encodingOptions, "hevc"); - } - - return null; - } - - return GetHwaccelType(state, encodingOptions, "hevc"); + return (isColorDepth10 && + !encodingOptions.EnableDecodingColorDepth10) ? null : GetHwaccelType(state, encodingOptions, "hevc"); case "mpeg2video": return GetHwaccelType(state, encodingOptions, "mpeg2video"); case "vc1": @@ -2768,20 +2755,10 @@ namespace MediaBrowser.Controller.MediaEncoding case "mpeg4": return GetHwaccelType(state, encodingOptions, "mpeg4"); case "vp9": - if (isColorDepth10) - { - if (encodingOptions.EnableDecodingColorDepth10) - { - return GetHwaccelType(state, encodingOptions, "vp9"); - } - - return null; - } - - return GetHwaccelType(state, encodingOptions, "vp9"); + return (isColorDepth10 && + !encodingOptions.EnableDecodingColorDepth10) ? null : GetHwaccelType(state, encodingOptions, "vp9"); } } - else if (string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) { switch (videoStream.Codec.ToLowerInvariant()) @@ -2791,17 +2768,8 @@ namespace MediaBrowser.Controller.MediaEncoding return GetHwaccelType(state, encodingOptions, "h264"); case "hevc": case "h265": - if (isColorDepth10) - { - if (encodingOptions.EnableDecodingColorDepth10) - { - return GetHwaccelType(state, encodingOptions, "hevc"); - } - - return null; - } - - return GetHwaccelType(state, encodingOptions, "hevc"); + return (isColorDepth10 && + !encodingOptions.EnableDecodingColorDepth10) ? null : GetHwaccelType(state, encodingOptions, "hevc"); case "mpeg2video": return GetHwaccelType(state, encodingOptions, "mpeg2video"); case "vc1": @@ -2809,17 +2777,8 @@ namespace MediaBrowser.Controller.MediaEncoding case "vp8": return GetHwaccelType(state, encodingOptions, "vp8"); case "vp9": - if (isColorDepth10) - { - if (encodingOptions.EnableDecodingColorDepth10) - { - return GetHwaccelType(state, encodingOptions, "vp9"); - } - - return null; - } - - return GetHwaccelType(state, encodingOptions, "vp9"); + return (isColorDepth10 && + !encodingOptions.EnableDecodingColorDepth10) ? null : GetHwaccelType(state, encodingOptions, "vp9"); } } else if (string.Equals(encodingOptions.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) @@ -2853,7 +2812,6 @@ namespace MediaBrowser.Controller.MediaEncoding break; } } - } var whichCodec = videoStream.Codec.ToLowerInvariant(); -- cgit v1.2.3 From 0676d1c2f45eb19c993a0e130c6fbef0eade67d9 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 26 May 2020 19:33:20 +0200 Subject: Update WebSocketSharpRequest.cs --- Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 5554aa97f..7488b1938 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -208,9 +208,8 @@ namespace Emby.Server.Implementations.SocketSharp private static string GetQueryStringContentType(HttpRequest httpReq) { - string formatStr = httpReq.Query["format"].ToString(); - ReadOnlySpan format = formatStr; - if (formatStr == null) + ReadOnlySpan format = httpReq.Query["format"].ToString(); + if (formatStr == ReadOnlySpan.Empty) { const int FormatMaxLength = 4; ReadOnlySpan pi = httpReq.Path.ToString(); -- cgit v1.2.3 From b61ee09a36ed38958dc3897be6a30ca8ad191813 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 26 May 2020 20:00:37 +0200 Subject: Update WebSocketSharpRequest.cs --- Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 7488b1938..6ca58b1c3 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -209,7 +209,7 @@ namespace Emby.Server.Implementations.SocketSharp private static string GetQueryStringContentType(HttpRequest httpReq) { ReadOnlySpan format = httpReq.Query["format"].ToString(); - if (formatStr == ReadOnlySpan.Empty) + if (format == ReadOnlySpan.Empty) { const int FormatMaxLength = 4; ReadOnlySpan pi = httpReq.Path.ToString(); -- cgit v1.2.3 From 7c823464bca70570f2f53f8af6913e53d385b784 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 26 May 2020 20:52:05 -0400 Subject: Fix merge conflicts with SyncPlay --- .../SyncPlay/SyncPlayManager.cs | 64 ++++++++-------------- Jellyfin.Data/Entities/User.cs | 4 ++ Jellyfin.Data/Enums/SyncPlayAccess.cs | 23 ++++++++ .../Users/UserManager.cs | 27 +-------- MediaBrowser.Model/Configuration/SyncplayAccess.cs | 23 -------- MediaBrowser.Model/Users/UserPolicy.cs | 4 +- 6 files changed, 55 insertions(+), 90 deletions(-) create mode 100644 Jellyfin.Data/Enums/SyncPlayAccess.cs delete mode 100644 MediaBrowser.Model/Configuration/SyncplayAccess.cs diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index 1f76dd4e3..6a3e684ca 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -3,13 +3,14 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; -using Microsoft.Extensions.Logging; -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.SyncPlay; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.SyncPlay; +using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.SyncPlay { @@ -102,14 +103,6 @@ namespace Emby.Server.Implementations.SyncPlay _disposed = true; } - private void CheckDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } - } - private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e) { var session = e.SessionInfo; @@ -143,37 +136,26 @@ namespace Emby.Server.Implementations.SyncPlay // Check ParentalRating access var hasParentalRatingAccess = true; - if (user.Policy.MaxParentalRating.HasValue) + if (user.MaxParentalAgeRating.HasValue) { - hasParentalRatingAccess = item.InheritedParentalRatingValue <= user.Policy.MaxParentalRating; + hasParentalRatingAccess = item.InheritedParentalRatingValue <= user.MaxParentalAgeRating.Value; } - if (!user.Policy.EnableAllFolders && hasParentalRatingAccess) + if (!user.HasPermission(PermissionKind.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; + folder => folder.Id.ToString("N", CultureInfo.InvariantCulture)); + + return collections.Intersect(user.GetPreference(PreferenceKind.EnabledFolders)).Any(); } + + return hasParentalRatingAccess; } private Guid? GetSessionGroup(SessionInfo session) { - ISyncPlayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); - if (group != null) - { - return group.GetGroupId(); - } - else - { - return null; - } + _sessionToGroupMap.TryGetValue(session.Id, out var group); + return group?.GetGroupId(); } /// @@ -181,7 +163,7 @@ namespace Emby.Server.Implementations.SyncPlay { var user = _userManager.GetUserById(session.UserId); - if (user.Policy.SyncPlayAccess != SyncPlayAccess.CreateAndJoinGroups) + if (user.SyncPlayAccess != SyncPlayAccess.CreateAndJoinGroups) { _logger.LogWarning("NewGroup: {0} does not have permission to create groups.", session.Id); @@ -189,7 +171,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.CreateGroupDenied }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -212,7 +194,7 @@ namespace Emby.Server.Implementations.SyncPlay { var user = _userManager.GetUserById(session.UserId); - if (user.Policy.SyncPlayAccess == SyncPlayAccess.None) + if (user.SyncPlayAccess == SyncPlayAccess.None) { _logger.LogWarning("JoinGroup: {0} does not have access to SyncPlay.", session.Id); @@ -220,7 +202,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.JoinGroupDenied }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -237,7 +219,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.GroupDoesNotExist }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -250,7 +232,7 @@ namespace Emby.Server.Implementations.SyncPlay GroupId = group.GetGroupId().ToString(), Type = GroupUpdateType.LibraryAccessDenied }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -285,7 +267,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; } @@ -304,7 +286,7 @@ namespace Emby.Server.Implementations.SyncPlay { var user = _userManager.GetUserById(session.UserId); - if (user.Policy.SyncPlayAccess == SyncPlayAccess.None) + if (user.SyncPlayAccess == SyncPlayAccess.None) { return new List(); } @@ -334,7 +316,7 @@ namespace Emby.Server.Implementations.SyncPlay { var user = _userManager.GetUserById(session.UserId); - if (user.Policy.SyncPlayAccess == SyncPlayAccess.None) + if (user.SyncPlayAccess == SyncPlayAccess.None) { _logger.LogWarning("HandleRequest: {0} does not have access to SyncPlay.", session.Id); @@ -342,7 +324,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.JoinGroupDenied }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index 2287d802b..0a4661780 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -61,6 +61,7 @@ namespace Jellyfin.Data.Entities EnableAutoLogin = false; PlayDefaultAudioTrack = true; SubtitleMode = SubtitlePlaybackMode.Default; + SyncPlayAccess = SyncPlayAccess.CreateAndJoinGroups; AddDefaultPermissions(); AddDefaultPreferences(); @@ -319,6 +320,9 @@ namespace Jellyfin.Data.Entities /// public virtual ImageInfo ProfileImage { get; set; } + [Required] + public SyncPlayAccess SyncPlayAccess { get; set; } + /// /// Gets or sets the row version. /// diff --git a/Jellyfin.Data/Enums/SyncPlayAccess.cs b/Jellyfin.Data/Enums/SyncPlayAccess.cs new file mode 100644 index 000000000..8c13b37a1 --- /dev/null +++ b/Jellyfin.Data/Enums/SyncPlayAccess.cs @@ -0,0 +1,23 @@ +namespace Jellyfin.Data.Enums +{ + /// + /// Enum SyncPlayAccess. + /// + public enum SyncPlayAccess + { + /// + /// User can create groups and join them. + /// + CreateAndJoinGroups = 0, + + /// + /// User can only join already existing groups. + /// + JoinGroups = 1, + + /// + /// SyncPlay is disabled for the user. + /// + None = 2 + } +} diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 41116c251..886c08b4c 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -352,34 +352,12 @@ namespace Jellyfin.Server.Implementations.Users EnabledChannels = user.GetPreference(PreferenceKind.EnabledChannels), EnabledDevices = user.GetPreference(PreferenceKind.EnabledDevices), EnabledFolders = user.GetPreference(PreferenceKind.EnabledFolders), - EnableContentDeletionFromFolders = user.GetPreference(PreferenceKind.EnableContentDeletionFromFolders) + EnableContentDeletionFromFolders = user.GetPreference(PreferenceKind.EnableContentDeletionFromFolders), + SyncPlayAccess = user.SyncPlayAccess } }; } - /// - 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, @@ -635,6 +613,7 @@ namespace Jellyfin.Server.Implementations.Users user.PasswordResetProviderId = policy.PasswordResetProviderId; user.InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount; user.LoginAttemptsBeforeLockout = maxLoginAttempts; + user.SyncPlayAccess = policy.SyncPlayAccess; user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator); user.SetPermission(PermissionKind.IsHidden, policy.IsHidden); user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled); diff --git a/MediaBrowser.Model/Configuration/SyncplayAccess.cs b/MediaBrowser.Model/Configuration/SyncplayAccess.cs deleted file mode 100644 index d891a8167..000000000 --- a/MediaBrowser.Model/Configuration/SyncplayAccess.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace MediaBrowser.Model.Configuration -{ - /// - /// Enum SyncPlayAccess. - /// - public enum SyncPlayAccess - { - /// - /// User can create groups and join them. - /// - CreateAndJoinGroups, - - /// - /// User can only join already existing groups. - /// - JoinGroups, - - /// - /// SyncPlay is disabled for the user. - /// - None - } -} diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index 7ac63a0ac..66e5529e3 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -1,10 +1,10 @@ #pragma warning disable CS1591 using System; -using System.Text.Json.Serialization; using System.Xml.Serialization; -using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; +using MediaBrowser.Model.Configuration; +using AccessSchedule = Jellyfin.Data.Entities.AccessSchedule; namespace MediaBrowser.Model.Users { -- cgit v1.2.3 From 46a0a2a6011bbeaf92af45f9478dda8608ab2c6a Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 26 May 2020 21:20:55 -0400 Subject: Update migrations and fix a few bugs --- Jellyfin.Data/Entities/AccessSchedule.cs | 4 +- .../Migrations/20200517002411_AddUsers.Designer.cs | 404 -------------------- .../Migrations/20200517002411_AddUsers.cs | 297 --------------- .../Migrations/20200527010628_AddUsers.Designer.cs | 407 +++++++++++++++++++++ .../Migrations/20200527010628_AddUsers.cs | 298 +++++++++++++++ .../Migrations/JellyfinDbModelSnapshot.cs | 8 +- .../Migrations/Routines/MigrateUserDb.cs | 3 +- 7 files changed, 716 insertions(+), 705 deletions(-) delete mode 100644 Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.Designer.cs delete mode 100644 Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20200527010628_AddUsers.Designer.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20200527010628_AddUsers.cs diff --git a/Jellyfin.Data/Entities/AccessSchedule.cs b/Jellyfin.Data/Entities/AccessSchedule.cs index 4248a34c9..15c4e4cde 100644 --- a/Jellyfin.Data/Entities/AccessSchedule.cs +++ b/Jellyfin.Data/Entities/AccessSchedule.cs @@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; +using System.Xml.Serialization; using Jellyfin.Data.Enums; namespace Jellyfin.Data.Entities @@ -40,7 +41,7 @@ namespace Jellyfin.Data.Entities /// /// Identity, Indexed, Required. /// - [JsonIgnore] + [XmlIgnore] [Key] [Required] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] @@ -49,6 +50,7 @@ namespace Jellyfin.Data.Entities /// /// Gets or sets the id of the associated user. /// + [XmlIgnore] [Required] [ForeignKey("Id")] public Guid UserId { get; protected set; } diff --git a/Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.Designer.cs deleted file mode 100644 index 36c58c8ca..000000000 --- a/Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.Designer.cs +++ /dev/null @@ -1,404 +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("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 deleted file mode 100644 index 55c6f371c..000000000 --- a/Jellyfin.Server.Implementations/Migrations/20200517002411_AddUsers.cs +++ /dev/null @@ -1,297 +0,0 @@ -#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/20200527010628_AddUsers.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200527010628_AddUsers.Designer.cs new file mode 100644 index 000000000..e0321dfa7 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200527010628_AddUsers.Designer.cs @@ -0,0 +1,407 @@ +#pragma warning disable CS1591 + +// +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("20200527010628_AddUsers")] + partial class AddUsers + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("jellyfin") + .HasAnnotation("ProductVersion", "3.1.4"); + + 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") + .HasMaxLength(512); + + 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("SyncPlayAccess") + .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/20200527010628_AddUsers.cs b/Jellyfin.Server.Implementations/Migrations/20200527010628_AddUsers.cs new file mode 100644 index 000000000..0157e668d --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200527010628_AddUsers.cs @@ -0,0 +1,298 @@ +#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(maxLength: 512, 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), + SyncPlayAccess = table.Column(nullable: false), + 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 46714e865..0494744c7 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", "3.1.3"); + .HasAnnotation("ProductVersion", "3.1.4"); modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => { @@ -124,7 +124,8 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("Path") .IsRequired() - .HasColumnType("TEXT"); + .HasColumnType("TEXT") + .HasMaxLength(512); b.HasKey("Id"); @@ -326,6 +327,9 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("SubtitleMode") .HasColumnType("INTEGER"); + b.Property("SyncPlayAccess") + .HasColumnType("INTEGER"); + b.Property("Username") .IsRequired() .HasColumnType("TEXT") diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index af74d3a1d..0113b49fd 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -6,6 +6,7 @@ using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations.Users; +using MediaBrowser.Common.Json; using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; @@ -70,7 +71,7 @@ namespace Jellyfin.Server.Migrations.Routines foreach (var entry in queryResult) { - UserMockup mockup = JsonSerializer.Deserialize(entry[2].ToBlob()); + UserMockup mockup = JsonSerializer.Deserialize(entry[2].ToBlob(), JsonDefaults.GetOptions()); var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, mockup.Name); var config = File.Exists(Path.Combine(userDataDir, "config.xml")) -- cgit v1.2.3 From fefb282137e58529bef35631a9409c8f7f9d7e28 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 26 May 2020 22:30:23 -0400 Subject: Fixed issue when LastLoginDate or LastActivityDate were null --- Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index 0113b49fd..53c93f64b 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -116,8 +116,8 @@ namespace Jellyfin.Server.Migrations.Routines SubtitleLanguagePreference = config.SubtitleLanguagePreference, Password = mockup.Password, EasyPassword = mockup.EasyPassword, - LastLoginDate = mockup.LastLoginDate ?? DateTime.MinValue, - LastActivityDate = mockup.LastActivityDate ?? DateTime.MinValue + LastLoginDate = mockup.LastLoginDate, + LastActivityDate = mockup.LastActivityDate }; if (mockup.ImageInfos.Length > 0) @@ -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 eef7cfd91251ee14717dbccfae7505e444048548 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 26 May 2020 22:43:03 -0400 Subject: Make SonarCloud less angry --- Emby.Server.Implementations/HttpServer/ResponseFilter.cs | 1 - Emby.Server.Implementations/HttpServer/Security/AuthService.cs | 1 - Emby.Server.Implementations/Library/MediaStreamSelector.cs | 1 - Emby.Server.Implementations/LiveTv/LiveTvManager.cs | 2 -- Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs | 1 - Emby.Server.Implementations/Updates/InstallationManager.cs | 2 -- MediaBrowser.Api/Images/ImageService.cs | 7 +------ MediaBrowser.Controller/Entities/Audio/Audio.cs | 1 - MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs | 3 --- MediaBrowser.Controller/Entities/AudioBook.cs | 1 - MediaBrowser.Controller/Entities/BaseItem.cs | 1 - MediaBrowser.Controller/Entities/Book.cs | 1 - MediaBrowser.Controller/Entities/InternalItemsQuery.cs | 1 - 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 | 4 +--- MediaBrowser.Controller/Entities/Trailer.cs | 1 - MediaBrowser.Controller/LiveTv/LiveTvChannel.cs | 1 - MediaBrowser.Controller/LiveTv/LiveTvProgram.cs | 1 - MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs | 1 - MediaBrowser.Controller/Session/ISessionManager.cs | 1 - MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs | 1 - 23 files changed, 2 insertions(+), 34 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs index 85c3db9b2..3ab5dbc16 100644 --- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Globalization; using System.Text; using MediaBrowser.Controller.Net; diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index eace86d51..9441f1a4c 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -8,7 +8,6 @@ using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs index a177138b7..ca904c4ec 100644 --- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; using Jellyfin.Data.Enums; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; namespace Emby.Server.Implementations.Library diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 64901f9fa..09906c9f1 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -12,10 +12,8 @@ using Jellyfin.Data.Enums; 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.Library; diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index 6a3e684ca..f44b32c36 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -8,7 +8,6 @@ using Jellyfin.Data.Enums; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.SyncPlay; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.SyncPlay; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 0b2309889..980572cc2 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -3,9 +3,7 @@ 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/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 149c4cb9b..d3b739524 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -625,12 +625,9 @@ namespace MediaBrowser.Api.Images var outputFormats = GetOutputFormats(request); - return GetImageResult( - user, - user.Id, + return GetImageResult(user.Id, request, imageInfo, - false, outputFormats, cacheDuration, responseHeaders, @@ -638,11 +635,9 @@ namespace MediaBrowser.Api.Images } private async Task GetImageResult( - User user, Guid itemId, ImageRequest request, ItemImageInfo info, - bool cropWhitespace, IReadOnlyCollection supportedFormats, TimeSpan? cacheDuration, IDictionary headers, diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index 9065cb27f..a8ea2157d 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -5,7 +5,6 @@ using System.Text.Json.Serialization; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index 15b580896..e4a24bb9c 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Text.Json.Serialization; using System.Threading; @@ -10,9 +9,7 @@ using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Users; namespace MediaBrowser.Controller.Entities.Audio { diff --git a/MediaBrowser.Controller/Entities/AudioBook.cs b/MediaBrowser.Controller/Entities/AudioBook.cs index ffe7aa8b3..4adaf4c6e 100644 --- a/MediaBrowser.Controller/Entities/AudioBook.cs +++ b/MediaBrowser.Controller/Entities/AudioBook.cs @@ -2,7 +2,6 @@ using System; using System.Text.Json.Serialization; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; namespace MediaBrowser.Controller.Entities { diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index ee12ba73b..672d4fd0c 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -26,7 +26,6 @@ using MediaBrowser.Model.Library; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Users; using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Entities diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs index c4a2929dc..f16554538 100644 --- a/MediaBrowser.Controller/Entities/Book.cs +++ b/MediaBrowser.Controller/Entities/Book.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Text.Json.Serialization; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; namespace MediaBrowser.Controller.Entities { diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index a2dff53ad..496bee857 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -4,7 +4,6 @@ using System.Linq; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Entities diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index 38359afcc..68ba71920 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; diff --git a/MediaBrowser.Controller/Entities/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs index 1b9d4614e..6e7f2812d 100644 --- a/MediaBrowser.Controller/Entities/MusicVideo.cs +++ b/MediaBrowser.Controller/Entities/MusicVideo.cs @@ -4,7 +4,6 @@ using System.Text.Json.Serialization; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; namespace MediaBrowser.Controller.Entities { diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 0a89da46d..4ec60e7cd 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -5,7 +5,6 @@ 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.IO; using Microsoft.Extensions.Logging; diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 839350fd7..7dfd1a759 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -6,9 +6,7 @@ using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Users; namespace MediaBrowser.Controller.Entities.TV { @@ -170,7 +168,7 @@ namespace MediaBrowser.Controller.Entities.TV return GetEpisodes(user, new DtoOptions(true)); } - protected override bool GetBlockUnratedValue(User config) + protected override bool GetBlockUnratedValue(User user) { // Don't block. Let either the entire series rating or episode rating determine it return false; diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs index c646e8ae6..7e29c15fd 100644 --- a/MediaBrowser.Controller/Entities/Trailer.cs +++ b/MediaBrowser.Controller/Entities/Trailer.cs @@ -3,7 +3,6 @@ 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; using MediaBrowser.Model.Providers; diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs index f3ff8bd04..10af98121 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Text.Json.Serialization; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs index e6dc4bdda..b748b089a 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -7,7 +7,6 @@ using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Providers; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index 67241c35b..c4ebdac8b 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using Jellyfin.Data.Entities; -using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 70ae19980..1fdb588eb 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Security; using MediaBrowser.Model.Dto; diff --git a/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs b/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs index f079bc7ea..6f75d16de 100644 --- a/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs +++ b/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs @@ -1,4 +1,3 @@ -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; namespace MediaBrowser.Controller.Sorting -- cgit v1.2.3 From 31f725fdbf9f4a5d91c60cde4dfe8997022f8257 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Tue, 26 May 2020 23:08:27 -0400 Subject: Fix a bug in Emby.Notifications and clean up --- Emby.Notifications/Api/NotificationsService.cs | 8 +++----- MediaBrowser.Api/Images/ImageService.cs | 25 +++++++++++-------------- MediaBrowser.Controller/Entities/BaseItem.cs | 9 +-------- 3 files changed, 15 insertions(+), 27 deletions(-) diff --git a/Emby.Notifications/Api/NotificationsService.cs b/Emby.Notifications/Api/NotificationsService.cs index 221db5423..1ff8a5026 100644 --- a/Emby.Notifications/Api/NotificationsService.cs +++ b/Emby.Notifications/Api/NotificationsService.cs @@ -150,9 +150,7 @@ namespace Emby.Notifications.Api [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] public object Get(GetNotificationsSummary request) { - return new NotificationsSummary - { - }; + return new NotificationsSummary(); } public Task Post(AddAdminNotification request) @@ -166,8 +164,8 @@ namespace Emby.Notifications.Api Name = request.Name, Url = request.Url, UserIds = _userManager.Users - .Where(p => p.Permissions.Select(x => x.Kind).Contains(PermissionKind.IsAdministrator)) - .Select(p => p.Id) + .Where(user => user.HasPermission(PermissionKind.IsAdministrator)) + .Select(user => user.Id) .ToArray() }; diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index d3b739524..f52c48cc6 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -477,7 +477,7 @@ namespace MediaBrowser.Api.Images } catch (IOException e) { - // TODO: Log this + Logger.LogError(e, "Error deleting user profile image:"); } user.ProfileImage = null; @@ -820,14 +820,14 @@ namespace MediaBrowser.Api.Images /// The request. /// The item. /// System.String. - private ItemImageInfo GetImageInfo(ImageRequest request, BaseItem item) + private static ItemImageInfo GetImageInfo(ImageRequest request, BaseItem item) { var index = request.Index ?? 0; return item.GetImageInfo(request.Type, index); } - private ItemImageInfo GetImageInfo(ImageRequest request, User user) + private static ItemImageInfo GetImageInfo(ImageRequest request, User user) { var info = new ItemImageInfo { @@ -859,15 +859,7 @@ namespace MediaBrowser.Api.Images /// Task. public async Task PostImage(BaseItem entity, Stream inputStream, ImageType imageType, 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 - }; + var memoryStream = await GetMemoryStream(inputStream); // Handle image/png; charset=utf-8 mimeType = mimeType.Split(';').FirstOrDefault(); @@ -877,16 +869,21 @@ namespace MediaBrowser.Api.Images entity.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None); } - public async Task PostImage(User user, Stream inputStream, string mimeType) + private static async Task GetMemoryStream(Stream inputStream) { using var reader = new StreamReader(inputStream); var text = await reader.ReadToEndAsync().ConfigureAwait(false); var bytes = Convert.FromBase64String(text); - var memoryStream = new MemoryStream(bytes) + return new MemoryStream(bytes) { Position = 0 }; + } + + private async Task PostImage(User user, Stream inputStream, string mimeType) + { + var memoryStream = await GetMemoryStream(inputStream); // Handle image/png; charset=utf-8 mimeType = mimeType.Split(';').FirstOrDefault(); diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 672d4fd0c..3feef1d32 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -2760,14 +2760,7 @@ namespace MediaBrowser.Controller.Entities return this; } - foreach (var parent in GetParents()) - { - if (parent.IsTopParent) - { - return parent; - } - } - return null; + return GetParents().FirstOrDefault(parent => parent.IsTopParent); } [JsonIgnore] -- cgit v1.2.3 From 73d123fe3630a39339cdecef620ee190318ccf88 Mon Sep 17 00:00:00 2001 From: dkanada Date: Wed, 27 May 2020 13:55:45 +0900 Subject: fix issue with audio transcoding --- MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 061316cb8..b8ea9c6da 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -865,7 +865,7 @@ namespace MediaBrowser.Api.Playback.Hls { framerate = Math.Round(state.TargetFramerate.GetValueOrDefault(), 3); } - else if (state.VideoStream.RealFrameRate.HasValue) + else if (state.VideoStream?.RealFrameRate != null) { framerate = Math.Round(state.VideoStream.RealFrameRate.GetValueOrDefault(), 3); } -- cgit v1.2.3 From 6c9dc0418961a673f9e5dcfb36f66d076381a92e Mon Sep 17 00:00:00 2001 From: Vasily Date: Wed, 27 May 2020 15:01:03 +0300 Subject: Handle errors during blurhash generation so it does not fail the scan --- Emby.Server.Implementations/Library/LibraryManager.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index e63776bff..903c0b3cf 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1841,7 +1841,15 @@ namespace Emby.Server.Implementations.Library ImageDimensions size = _imageProcessor.GetImageDimensions(item, img); img.Width = size.Width; img.Height = size.Height; - img.BlurHash = _imageProcessor.GetImageBlurHash(img.Path); + try + { + img.BlurHash = _imageProcessor.GetImageBlurHash(img.Path); + } + catch (Exception ex) + { + _logger.LogError(ex, "Cannot compute blurhash for {0}", img.Path); + img.BlurHash = string.Empty; + } }); _itemRepository.SaveImages(item); -- cgit v1.2.3 From 2482bcb3b1ba9ea6e861709704ce1f184fcc0d9c Mon Sep 17 00:00:00 2001 From: Vasily Date: Wed, 27 May 2020 16:27:27 +0300 Subject: Add blurhashes to ImageBlurHashes for all images --- Emby.Server.Implementations/Dto/DtoService.cs | 98 +++++++++++++++++++++------ MediaBrowser.Model/Dto/BaseItemDto.cs | 3 +- 2 files changed, 80 insertions(+), 21 deletions(-) diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 77a734ebf..38c4f940d 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -605,7 +605,7 @@ namespace Emby.Server.Implementations.Dto if (dictionary.TryGetValue(person.Name, out Person entity)) { - baseItemPerson.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary); + baseItemPerson.PrimaryImageTag = GetTagAndFillBlurhash(dto, entity, ImageType.Primary); baseItemPerson.Id = entity.Id.ToString("N", CultureInfo.InvariantCulture); list.Add(baseItemPerson); } @@ -654,6 +654,70 @@ namespace Emby.Server.Implementations.Dto return _libraryManager.GetGenreId(name); } + private string GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ImageType imageType, int imageIndex = 0) + { + var image = item.GetImageInfo(imageType, imageIndex); + if (image != null) + { + return GetTagAndFillBlurhash(dto, item, image); + } + + return null; + } + + private string GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ItemImageInfo image) + { + var tag = GetImageCacheTag(item, image); + if (!string.IsNullOrEmpty(image.BlurHash)) + { + if (dto.ImageBlurHashes == null) + { + dto.ImageBlurHashes = new Dictionary>(); + } + + if (!dto.ImageBlurHashes.ContainsKey(image.Type)) + { + dto.ImageBlurHashes[image.Type] = new Dictionary(); + } + + dto.ImageBlurHashes[image.Type][tag] = image.BlurHash; + } + + return tag; + } + + private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, int limit) + { + return GetTagsAndFillBlurhashes(dto, item, imageType, item.GetImages(imageType).Take(limit).ToList()); + } + + private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, List images) + { + var tags = GetImageTags(item, images); + var hashes = new Dictionary(); + for (int i = 0; i < images.Count; i++) + { + var img = images[i]; + if (!string.IsNullOrEmpty(img.BlurHash)) + { + var tag = tags[i]; + hashes[tag] = img.BlurHash; + } + } + + if (hashes.Count > 0) + { + if (dto.ImageBlurHashes == null) + { + dto.ImageBlurHashes = new Dictionary>(); + } + + dto.ImageBlurHashes[imageType] = hashes; + } + + return tags; + } + /// /// Sets simple property values on a DTOBaseItem /// @@ -674,8 +738,8 @@ namespace Emby.Server.Implementations.Dto dto.LockData = item.IsLocked; dto.ForcedSortName = item.ForcedSortName; } - dto.Container = item.Container; + dto.Container = item.Container; dto.EndDate = item.EndDate; if (options.ContainsField(ItemFields.ExternalUrls)) @@ -694,10 +758,12 @@ namespace Emby.Server.Implementations.Dto dto.AspectRatio = hasAspectRatio.AspectRatio; } + dto.ImageBlurHashes = new Dictionary>(); + var backdropLimit = options.GetImageLimit(ImageType.Backdrop); if (backdropLimit > 0) { - dto.BackdropImageTags = GetImageTags(item, item.GetImages(ImageType.Backdrop).Take(backdropLimit).ToList()); + dto.BackdropImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Backdrop, backdropLimit); } if (options.ContainsField(ItemFields.ScreenshotImageTags)) @@ -705,7 +771,7 @@ namespace Emby.Server.Implementations.Dto var screenshotLimit = options.GetImageLimit(ImageType.Screenshot); if (screenshotLimit > 0) { - dto.ScreenshotImageTags = GetImageTags(item, item.GetImages(ImageType.Screenshot).Take(screenshotLimit).ToList()); + dto.ScreenshotImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Screenshot, screenshotLimit); } } @@ -718,7 +784,6 @@ namespace Emby.Server.Implementations.Dto if (options.EnableImages) { dto.ImageTags = new Dictionary(); - dto.ImageBlurHashes = new Dictionary(); // Prevent implicitly captured closure var currentItem = item; @@ -726,18 +791,12 @@ namespace Emby.Server.Implementations.Dto { if (options.GetImageLimit(image.Type) > 0) { - var tag = GetImageCacheTag(item, image); + var tag = GetTagAndFillBlurhash(dto, item, image); if (tag != null) { dto.ImageTags[image.Type] = tag; } - - var hash = image.BlurHash; - if (!string.IsNullOrEmpty(hash)) - { - dto.ImageBlurHashes[image.Type] = image.BlurHash; - } } } } @@ -877,8 +936,7 @@ namespace Emby.Server.Implementations.Dto if (albumParent != null) { dto.AlbumId = albumParent.Id; - - dto.AlbumPrimaryImageTag = GetImageCacheTag(albumParent, ImageType.Primary); + dto.AlbumPrimaryImageTag = GetTagAndFillBlurhash(dto, albumParent, ImageType.Primary); } //if (options.ContainsField(ItemFields.MediaSourceCount)) @@ -1105,7 +1163,7 @@ namespace Emby.Server.Implementations.Dto episodeSeries = episodeSeries ?? episode.Series; if (episodeSeries != null) { - dto.SeriesPrimaryImageTag = GetImageCacheTag(episodeSeries, ImageType.Primary); + dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary); } } @@ -1151,7 +1209,7 @@ namespace Emby.Server.Implementations.Dto series = series ?? season.Series; if (series != null) { - dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary); + dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary); } } } @@ -1281,7 +1339,7 @@ namespace Emby.Server.Implementations.Dto if (image != null) { dto.ParentLogoItemId = GetDtoId(parent); - dto.ParentLogoImageTag = GetImageCacheTag(parent, image); + dto.ParentLogoImageTag = GetTagAndFillBlurhash(dto, parent, image); } } if (artLimit > 0 && !(imageTags != null && imageTags.ContainsKey(ImageType.Art)) && dto.ParentArtItemId == null) @@ -1291,7 +1349,7 @@ namespace Emby.Server.Implementations.Dto if (image != null) { dto.ParentArtItemId = GetDtoId(parent); - dto.ParentArtImageTag = GetImageCacheTag(parent, image); + 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)) @@ -1301,7 +1359,7 @@ namespace Emby.Server.Implementations.Dto if (image != null) { dto.ParentThumbItemId = GetDtoId(parent); - dto.ParentThumbImageTag = GetImageCacheTag(parent, image); + dto.ParentThumbImageTag = GetTagAndFillBlurhash(dto, parent, image); } } if (backdropLimit > 0 && !((dto.BackdropImageTags != null && dto.BackdropImageTags.Length > 0) || (dto.ParentBackdropImageTags != null && dto.ParentBackdropImageTags.Length > 0))) @@ -1311,7 +1369,7 @@ namespace Emby.Server.Implementations.Dto if (images.Count > 0) { dto.ParentBackdropItemId = GetDtoId(parent); - dto.ParentBackdropImageTags = GetImageTags(parent, images); + dto.ParentBackdropImageTags = GetTagsAndFillBlurhashes(dto, parent, ImageType.Backdrop, images); } } diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 734bcaf81..fc1cb01db 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -512,9 +512,10 @@ namespace MediaBrowser.Model.Dto /// /// Gets or sets the blurhashes for the image tags. + /// Maps image type to dictionary mapping image tag to blurhash value. /// /// The blurhashes. - public Dictionary ImageBlurHashes { get; set; } + public Dictionary> ImageBlurHashes { get; set; } /// /// Gets or sets the series studio. -- cgit v1.2.3 From edcfcadcd327affb308b0c0eb5cfbb1416e27cae Mon Sep 17 00:00:00 2001 From: Vasily Date: Wed, 27 May 2020 17:00:59 +0300 Subject: Make sure blurhash is recomputed if image changed or metadata refresh toggled --- .../Library/LibraryManager.cs | 47 +++++++++++++++++----- MediaBrowser.Controller/Library/ILibraryManager.cs | 2 +- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 903c0b3cf..84bcd1bc1 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1820,23 +1820,44 @@ namespace Emby.Server.Implementations.Library } } - public void UpdateImages(BaseItem item) + private bool ImageNeedsRefresh(ItemImageInfo image) + { + if (image.Path != null && image.IsLocalFile) + { + if (image.Width == 0 || image.Height == 0 || string.IsNullOrEmpty(image.BlurHash)) + { + return true; + } + + try + { + return _fileSystem.GetLastWriteTimeUtc(image.Path) != image.DateModified; + } + catch (Exception ex) + { + _logger.LogError(ex, "Cannot get file info for {0}", image.Path); + return false; + } + } + + return false; + } + + public void UpdateImages(BaseItem item, bool forceUpdate = false) { if (item == null) { throw new ArgumentNullException(nameof(item)); } - var outdated = item.ImageInfos - .Where(i => (i.IsLocalFile && (i.Width == 0 || i.Height == 0 || string.IsNullOrEmpty(i.BlurHash)))) - .ToList(); - if (outdated.Count == 0) + var outdated = forceUpdate ? item.ImageInfos : item.ImageInfos.Where(ImageNeedsRefresh).ToArray(); + if (outdated.Length == 0) { RegisterItem(item); return; } - outdated.ForEach(img => + foreach (var img in outdated) { ImageDimensions size = _imageProcessor.GetImageDimensions(item, img); img.Width = size.Width; @@ -1850,10 +1871,18 @@ namespace Emby.Server.Implementations.Library _logger.LogError(ex, "Cannot compute blurhash for {0}", img.Path); img.BlurHash = string.Empty; } - }); - _itemRepository.SaveImages(item); + try + { + img.DateModified = _fileSystem.GetLastWriteTimeUtc(img.Path); + } + catch (Exception ex) + { + _logger.LogError(ex, "Cannot update DateModified for {0}", img.Path); + } + } + _itemRepository.SaveImages(item); RegisterItem(item); } @@ -1874,7 +1903,7 @@ namespace Emby.Server.Implementations.Library item.DateLastSaved = DateTime.UtcNow; - UpdateImages(item); + UpdateImages(item, updateReason >= ItemUpdateType.ImageUpdate); } _itemRepository.SaveItems(itemsList, cancellationToken); diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 81160efec..916e4fda7 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -118,7 +118,7 @@ namespace MediaBrowser.Controller.Library /// void QueueLibraryScan(); - void UpdateImages(BaseItem item); + void UpdateImages(BaseItem item, bool forceUpdate = false); /// /// Gets the default view. -- cgit v1.2.3 From caf6833447f2c8f1018f069660b81c5c6a99ea06 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 27 May 2020 10:08:36 -0400 Subject: Add myself to CONTRIBUTORS.md --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index ce956176e..1bba7a51d 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -7,6 +7,7 @@ - [anthonylavado](https://github.com/anthonylavado) - [Artiume](https://github.com/Artiume) - [AThomsen](https://github.com/AThomsen) + - [barronpm](https://github.com/barronpm) - [bilde2910](https://github.com/bilde2910) - [bfayers](https://github.com/bfayers) - [BnMcG](https://github.com/BnMcG) -- cgit v1.2.3 From 9a853ca089c593b9e4a269a10272fc744da5af9c Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Wed, 27 May 2020 11:30:53 -0400 Subject: Add another null check --- Jellyfin.Data/Entities/User.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index 0a4661780..cef2edfa9 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -38,6 +38,11 @@ namespace Jellyfin.Data.Entities throw new ArgumentNullException(nameof(authenticationProviderId)); } + if (string.IsNullOrEmpty(passwordResetProviderId)) + { + throw new ArgumentNullException(nameof(passwordResetProviderId)); + } + Username = username; AuthenticationProviderId = authenticationProviderId; PasswordResetProviderId = passwordResetProviderId; -- cgit v1.2.3 From f30b07130fc21247e3c6edd61d004a9286c5340c Mon Sep 17 00:00:00 2001 From: Vasily Date: Wed, 27 May 2020 19:29:57 +0300 Subject: Workaround a bug in BlurHashSharp --- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index ccd501214..85461ca4e 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -251,10 +251,18 @@ namespace Jellyfin.Drawing.Skia // We want tiles to be as close to square as possible, and to *mostly* keep under 16 tiles for performance. // One tile is (width / xComp) x (height / yComp) pixels, which means that ideally yComp = xComp * height / width. // See more at https://github.com/woltapp/blurhash/#how-do-i-pick-the-number-of-x-and-y-components - float xComp = MathF.Sqrt(16.0f * dims.Width / dims.Height); - float yComp = xComp * dims.Height / dims.Width; + float xCompF = MathF.Sqrt(16.0f * dims.Width / dims.Height); + float yCompF = xCompF * dims.Height / dims.Width; - return BlurHashEncoder.Encode(Math.Min((int)xComp + 1, 9), Math.Min((int)yComp + 1, 9), path); + int xComp = Math.Min((int)xCompF + 1, 9); + int yComp = Math.Min((int)yCompF + 1, 9); + + // FIXME: current lib is bugged for xComp != yComp + // remove when https://github.com/Bond-009/BlurHashSharp/pull/1 is merged + int tmp = Math.Max(xComp, yComp); + xComp = yComp = tmp; + + return BlurHashEncoder.Encode(xComp, yComp, path); } private static bool HasDiacritics(string text) -- cgit v1.2.3 From 0ebad893a7dcc2623fb98d59a36f59089cc6a1ec Mon Sep 17 00:00:00 2001 From: Elouan MAILLY Date: Wed, 27 May 2020 20:07:27 +0000 Subject: Translated using Weblate (French) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/ --- Emby.Server.Implementations/Localization/Core/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index 150952d8b..d4009e92b 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -5,7 +5,7 @@ "Artists": "Artistes", "AuthenticationSucceededWithUserName": "{0} authentifié avec succès", "Books": "Livres", - "CameraImageUploadedFrom": "Une nouvelle photographie a été chargée depuis {0}", + "CameraImageUploadedFrom": "Une nouvelle photo a été chargée depuis {0}", "Channels": "Chaînes", "ChapterNameValue": "Chapitre {0}", "Collections": "Collections", @@ -15,7 +15,7 @@ "Favorites": "Favoris", "Folders": "Dossiers", "Genres": "Genres", - "HeaderAlbumArtists": "Artistes de l'album", + "HeaderAlbumArtists": "Artistes", "HeaderCameraUploads": "Photos transférées", "HeaderContinueWatching": "Continuer à regarder", "HeaderFavoriteAlbums": "Albums favoris", -- cgit v1.2.3 From 82b0786cc6a5c40725a94098daa4e707476fa089 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 28 May 2020 00:59:31 -0400 Subject: Remove unnecessary logging statement --- Jellyfin.Server.Implementations/Users/UserManager.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 886c08b4c..5ad9e9863 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -534,11 +534,6 @@ namespace Jellyfin.Server.Implementations.Users _invalidAuthProvider = _authenticationProviders.OfType().First(); _defaultAuthenticationProvider = _authenticationProviders.OfType().First(); _defaultPasswordResetProvider = _passwordResetProviders.OfType().First(); - - if (_authenticationProviders.Length > 2) - { - _logger.LogCritical("INVALID NUMBER OF LOGGERS: {0}", _authenticationProviders.Length); - } } /// -- cgit v1.2.3 From d1164979123a03c5f591dc04a4809adc695d0ae0 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Thu, 28 May 2020 01:08:37 -0400 Subject: Optimize number of created DbContexts and fix default values for some fields --- Jellyfin.Server.Implementations/Users/UserManager.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 5ad9e9863..60d78afd0 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -321,11 +321,11 @@ namespace Jellyfin.Server.Implementations.Users { MaxParentalRating = user.MaxParentalAgeRating, EnableUserPreferenceAccess = user.EnableUserPreferenceAccess, - RemoteClientBitrateLimit = user.RemoteClientBitrateLimit.GetValueOrDefault(), + RemoteClientBitrateLimit = user.RemoteClientBitrateLimit ?? -1, AuthenticationProviderId = user.AuthenticationProviderId, PasswordResetProviderId = user.PasswordResetProviderId, InvalidLoginAttemptCount = user.InvalidLoginAttemptCount, - LoginAttemptsBeforeLockout = user.LoginAttemptsBeforeLockout.GetValueOrDefault(), + LoginAttemptsBeforeLockout = user.LoginAttemptsBeforeLockout ?? -1, IsAdministrator = user.HasPermission(PermissionKind.IsAdministrator), IsHidden = user.HasPermission(PermissionKind.IsHidden), IsDisabled = user.HasPermission(PermissionKind.IsDisabled), @@ -490,8 +490,6 @@ namespace Jellyfin.Server.Implementations.Users { var user = string.IsNullOrWhiteSpace(enteredUsername) ? null : GetUserByName(enteredUsername); - var action = ForgotPasswordAction.InNetworkRequired; - if (user != null && isInNetwork) { var passwordResetProvider = GetPasswordResetProvider(user); @@ -500,7 +498,7 @@ namespace Jellyfin.Server.Implementations.Users return new ForgotPasswordResult { - Action = action, + Action = ForgotPasswordAction.InNetworkRequired, PinFile = string.Empty }; } @@ -569,7 +567,8 @@ namespace Jellyfin.Server.Implementations.Users /// public void UpdateConfiguration(Guid userId, UserConfiguration config) { - var user = GetUserById(userId); + var dbContext = _dbProvider.CreateContext(); + var user = dbContext.Users.Find(userId) ?? throw new ArgumentException("No user exists with given Id!"); user.SubtitleMode = config.SubtitleMode; user.HidePlayedInLatest = config.HidePlayedInLatest; user.EnableLocalPassword = config.EnableLocalPassword; @@ -587,13 +586,15 @@ namespace Jellyfin.Server.Implementations.Users user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes); user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes); - UpdateUser(user); + dbContext.Update(user); + dbContext.SaveChanges(); } /// public void UpdatePolicy(Guid userId, UserPolicy policy) { - var user = GetUserById(userId); + var dbContext = _dbProvider.CreateContext(); + var user = dbContext.Users.Find(userId) ?? throw new ArgumentException("No user exists with given Id!"); int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch { -1 => null, @@ -642,6 +643,9 @@ namespace Jellyfin.Server.Implementations.Users user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices); user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders); user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders); + + dbContext.Update(user); + dbContext.SaveChanges(); } private bool IsValidUsername(string name) -- cgit v1.2.3 From a76cee7a953a77794ca9824cdff581a350fcf0ae Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 28 May 2020 17:23:16 +0300 Subject: Update BlurHashSharp to 1.0.1, remove workaround --- Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj | 2 +- Jellyfin.Drawing.Skia/SkiaEncoder.cs | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj index 221346b68..d3fdb5de4 100644 --- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj +++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj @@ -18,7 +18,7 @@ - + diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 85461ca4e..7f0da2c9e 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -257,11 +257,6 @@ namespace Jellyfin.Drawing.Skia int xComp = Math.Min((int)xCompF + 1, 9); int yComp = Math.Min((int)yCompF + 1, 9); - // FIXME: current lib is bugged for xComp != yComp - // remove when https://github.com/Bond-009/BlurHashSharp/pull/1 is merged - int tmp = Math.Max(xComp, yComp); - xComp = yComp = tmp; - return BlurHashEncoder.Encode(xComp, yComp, path); } -- cgit v1.2.3 From ed791dee46b8f3b72fbdb68a3382622b4337e254 Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 28 May 2020 17:30:11 +0300 Subject: Do not compute dimensions or blurhash for remote images --- Emby.Server.Implementations/Library/LibraryManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 84bcd1bc1..6cbca7d02 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1850,7 +1850,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(item)); } - var outdated = forceUpdate ? item.ImageInfos : item.ImageInfos.Where(ImageNeedsRefresh).ToArray(); + var outdated = forceUpdate ? item.ImageInfos.Where(i => i.IsLocalFile).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray(); if (outdated.Length == 0) { RegisterItem(item); -- cgit v1.2.3 From 9208acd5ae26d41af6791d7dd7322d682a8b80b6 Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 28 May 2020 17:55:29 +0300 Subject: Convert non-local image to local before computing blurhash --- .../Library/LibraryManager.cs | 40 ++++++++++++++++------ MediaBrowser.Controller/Entities/BaseItem.cs | 40 ++++++++++++++++++++++ 2 files changed, 70 insertions(+), 10 deletions(-) diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 6cbca7d02..bb3e3dd11 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1840,7 +1840,7 @@ namespace Emby.Server.Implementations.Library } } - return false; + return image.Path != null && !image.IsLocalFile; } public void UpdateImages(BaseItem item, bool forceUpdate = false) @@ -1850,7 +1850,7 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(item)); } - var outdated = forceUpdate ? item.ImageInfos.Where(i => i.IsLocalFile).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray(); + var outdated = forceUpdate ? item.ImageInfos.Where(i => i.Path != null).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray(); if (outdated.Length == 0) { RegisterItem(item); @@ -1859,26 +1859,46 @@ namespace Emby.Server.Implementations.Library foreach (var img in outdated) { - ImageDimensions size = _imageProcessor.GetImageDimensions(item, img); - img.Width = size.Width; - img.Height = size.Height; + var image = img; + if (!img.IsLocalFile) + { + try + { + var index = item.GetImageIndex(img); + image = ConvertImageToLocal(item, img, index).ConfigureAwait(false).GetAwaiter().GetResult(); + } + catch (ArgumentException) + { + _logger.LogWarning("Cannot get image index for {0}", img.Path); + continue; + } + catch (InvalidOperationException) + { + _logger.LogWarning("Cannot fetch image from {0}", img.Path); + continue; + } + } + + ImageDimensions size = _imageProcessor.GetImageDimensions(item, image); + image.Width = size.Width; + image.Height = size.Height; try { - img.BlurHash = _imageProcessor.GetImageBlurHash(img.Path); + image.BlurHash = _imageProcessor.GetImageBlurHash(image.Path); } catch (Exception ex) { - _logger.LogError(ex, "Cannot compute blurhash for {0}", img.Path); - img.BlurHash = string.Empty; + _logger.LogError(ex, "Cannot compute blurhash for {0}", image.Path); + image.BlurHash = string.Empty; } try { - img.DateModified = _fileSystem.GetLastWriteTimeUtc(img.Path); + image.DateModified = _fileSystem.GetLastWriteTimeUtc(image.Path); } catch (Exception ex) { - _logger.LogError(ex, "Cannot update DateModified for {0}", img.Path); + _logger.LogError(ex, "Cannot update DateModified for {0}", image.Path); } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 07aeb69db..f4b71d8bf 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -2375,6 +2375,46 @@ namespace MediaBrowser.Controller.Entities .ElementAtOrDefault(imageIndex); } + /// + /// Computes image index for given image or raises if no matching image found. + /// + /// Image to compute index for. + /// Image index cannot be computed as no matching image found. + /// + /// Image index. + public int GetImageIndex(ItemImageInfo image) + { + if (image == null) + { + throw new ArgumentNullException(nameof(image)); + } + + if (image.Type == ImageType.Chapter) + { + var chapters = ItemRepository.GetChapters(this); + for (var i = 0; i < chapters.Count; i++) + { + if (chapters[i].ImagePath == image.Path) + { + return i; + } + } + + throw new ArgumentException("No chapter index found for image path", image.Path); + } + + var images = GetImages(image.Type).ToArray(); + for (var i = 0; i < images.Length; i++) + { + if (images[i].Path == image.Path) + { + return i; + } + } + + throw new ArgumentException("No image index found for image path", image.Path); + } + public IEnumerable GetImages(ImageType imageType) { if (imageType == ImageType.Chapter) -- cgit v1.2.3 From 58f099c0e2fec23d861bc9bdb76ac0d6d8e239b7 Mon Sep 17 00:00:00 2001 From: Vasily Date: Thu, 28 May 2020 19:12:08 +0300 Subject: Fix naming per code review --- Emby.Server.Implementations/Data/SqliteItemRepository.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index dd60dd222..608801ac3 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1141,21 +1141,21 @@ namespace Emby.Server.Implementations.Data public string ToValueString(ItemImageInfo image) { - const string delimeter = "*"; + const string Delimeter = "*"; var path = image.Path ?? string.Empty; var hash = image.BlurHash ?? string.Empty; return GetPathToSave(path) + - delimeter + + Delimeter + image.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) + - delimeter + + Delimeter + image.Type + - delimeter + + Delimeter + image.Width.ToString(CultureInfo.InvariantCulture) + - delimeter + + Delimeter + image.Height.ToString(CultureInfo.InvariantCulture) + - delimeter + + Delimeter + // Replace delimiters with other characters. // This can be removed when we migrate to a proper DB. hash.Replace('*', '/').Replace('|', '\\'); -- 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(-) diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 70b3bda6e..94423c287 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 f44b32c36..8885266d3 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 cef2edfa9..1098cdb2f 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 60d78afd0..3d473f5f2 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 53c93f64b..2be10c708 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 65461894d47844edf632b0516427f1f85b8b7c4e Mon Sep 17 00:00:00 2001 From: Lumenol Date: Thu, 28 May 2020 20:30:36 +0000 Subject: Translated using Weblate (French) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/ --- Emby.Server.Implementations/Localization/Core/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index d4009e92b..47ebe1254 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -5,7 +5,7 @@ "Artists": "Artistes", "AuthenticationSucceededWithUserName": "{0} authentifié avec succès", "Books": "Livres", - "CameraImageUploadedFrom": "Une nouvelle photo a été chargée depuis {0}", + "CameraImageUploadedFrom": "Une photo a été chargée depuis {0}", "Channels": "Chaînes", "ChapterNameValue": "Chapitre {0}", "Collections": "Collections", -- 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 diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 138832fb8..04fe0bacb 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 ca5cd6fdd..58702541e 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 b69a126b3..279ec3098 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 6929c81f9..5bc1a81aa 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 2e9ecc4ae..dd7f753cc 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 acae702f3..0b9f80538 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 52c8facc3..02f150607 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 0b86b2db7..67a72d313 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 1b10f2d27..3e48425a2 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 1056a33b9..8e7d60a15 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 677d68b4c..7b7575707 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 e42ff8496..f347540c7 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 211ca6784..848f82d85 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 b3e88b667..d1a28e7a1 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 cd9f7946e..889760586 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 bb56d9771..f8a2d9741 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 9b1510ac9..d4d1c1ff7 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 6eda2b503..d192be921 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 5b188d962..dc3e9a607 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 6ffa581a9..907680239 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 ea6a70615..fae049914 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 9df7c538b..a6c13eaef 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 3140aa489..402b39a26 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 1d133dcda..0d36b82c0 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 63f867bf6..c384cf4bb 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 6a1afced7..9d9d77538 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 74cb01444..e470adcf4 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 ea278de0d..c7819d4c0 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 3a34da3af..74cd4ef1e 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 08ff4f55f..e171a9e9f 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 2a6a7b13c..ad0b57af6 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 4e4029f06..750890ec8 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 bcc814daf..5ec3a735a 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 095193828..8ba86f756 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 2563cac99..1f9c7fc22 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 a566b18dd..f2b1d06f3 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 e24a95dbb..ad6015c1c 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 9f5f97028..606f2a240 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 934560de3..7f44357e1 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 5018bf4a2..59ee5908f 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 27c4dcba0..278379a92 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 56e23d549..ab22fe019 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 5177251c3..16142a70d 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 483c63ade..e3b6aa197 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 2b09a93ef..5c480e842 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 120ac50d9..000000000 --- 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 7479d8104..000000000 --- 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 ee5131c1f..146c84d7c 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 16507466f..67e31f7f6 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 87d3ae2d6..980954ba0 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 623675157..5c1503ed2 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 66de05a6a..aba14c6ca 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 dfaa144cd..a35192eff 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 da3f3dd25..39d9bc68e 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 d99d0eff2..478df4035 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 7afbd9ff7..76bb798b5 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 504b6d283..b9205ee07 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 aa040fa15..558a3d351 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 c9ac765c1..5766dc542 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 d430d4d16..d0812a13f 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 1f76dd4e3..129262e53 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 4c2f24e6f..383615f74 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 0b2309889..4f6a84ef7 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 a3f3f6cb4..7b7d66ca6 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 78ac95f85..e7888595f 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 4655cd928..58a023638 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 b79fdeac0..6b5780a26 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 89514c89b..b15e09290 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 b9895386f..7c693f8c3 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 12a900b8f63341d5c8e6834a9f4333235af48571 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 29 May 2020 15:16:17 -0400 Subject: Update schema and migration to allow LastLoginDate and LastActivityDate to be null --- .../Migrations/20200527010628_AddUsers.Designer.cs | 407 --------------------- .../Migrations/20200527010628_AddUsers.cs | 298 --------------- .../Migrations/20200529171409_AddUsers.Designer.cs | 405 ++++++++++++++++++++ .../Migrations/20200529171409_AddUsers.cs | 295 +++++++++++++++ .../Migrations/JellyfinDbModelSnapshot.cs | 4 +- 5 files changed, 702 insertions(+), 707 deletions(-) delete mode 100644 Jellyfin.Server.Implementations/Migrations/20200527010628_AddUsers.Designer.cs delete mode 100644 Jellyfin.Server.Implementations/Migrations/20200527010628_AddUsers.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20200529171409_AddUsers.Designer.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20200529171409_AddUsers.cs diff --git a/Jellyfin.Server.Implementations/Migrations/20200527010628_AddUsers.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200527010628_AddUsers.Designer.cs deleted file mode 100644 index e0321dfa7..000000000 --- a/Jellyfin.Server.Implementations/Migrations/20200527010628_AddUsers.Designer.cs +++ /dev/null @@ -1,407 +0,0 @@ -#pragma warning disable CS1591 - -// -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("20200527010628_AddUsers")] - partial class AddUsers - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("jellyfin") - .HasAnnotation("ProductVersion", "3.1.4"); - - 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") - .HasMaxLength(512); - - 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("SyncPlayAccess") - .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/20200527010628_AddUsers.cs b/Jellyfin.Server.Implementations/Migrations/20200527010628_AddUsers.cs deleted file mode 100644 index 0157e668d..000000000 --- a/Jellyfin.Server.Implementations/Migrations/20200527010628_AddUsers.cs +++ /dev/null @@ -1,298 +0,0 @@ -#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(maxLength: 512, 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), - SyncPlayAccess = table.Column(nullable: false), - 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/20200529171409_AddUsers.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200529171409_AddUsers.Designer.cs new file mode 100644 index 000000000..e2558ffc4 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200529171409_AddUsers.Designer.cs @@ -0,0 +1,405 @@ +// +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("20200529171409_AddUsers")] + partial class AddUsers + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("jellyfin") + .HasAnnotation("ProductVersion", "3.1.4"); + + 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") + .HasMaxLength(512); + + 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("SyncPlayAccess") + .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/20200529171409_AddUsers.cs b/Jellyfin.Server.Implementations/Migrations/20200529171409_AddUsers.cs new file mode 100644 index 000000000..166d9dbb5 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200529171409_AddUsers.cs @@ -0,0 +1,295 @@ +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(maxLength: 512, 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: true), + LastLoginDate = table.Column(nullable: true), + 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), + SyncPlayAccess = table.Column(nullable: false), + 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 0494744c7..9a2315802 100644 --- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs +++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs @@ -277,10 +277,10 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("InvalidLoginAttemptCount") .HasColumnType("INTEGER"); - b.Property("LastActivityDate") + b.Property("LastActivityDate") .HasColumnType("TEXT"); - b.Property("LastLoginDate") + b.Property("LastLoginDate") .HasColumnType("TEXT"); b.Property("LoginAttemptsBeforeLockout") -- cgit v1.2.3 From 7f8f3e09e591cdd47be6b96d64c23f4197687496 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 29 May 2020 15:19:41 -0400 Subject: Ignore documentation warnings in new migration files --- .../Migrations/20200529171409_AddUsers.Designer.cs | 4 +++- .../Migrations/20200529171409_AddUsers.cs | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Server.Implementations/Migrations/20200529171409_AddUsers.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200529171409_AddUsers.Designer.cs index e2558ffc4..d1d26726e 100644 --- a/Jellyfin.Server.Implementations/Migrations/20200529171409_AddUsers.Designer.cs +++ b/Jellyfin.Server.Implementations/Migrations/20200529171409_AddUsers.Designer.cs @@ -1,4 +1,6 @@ -// +#pragma warning disable CS1591 + +// using System; using Jellyfin.Server.Implementations; using Microsoft.EntityFrameworkCore; diff --git a/Jellyfin.Server.Implementations/Migrations/20200529171409_AddUsers.cs b/Jellyfin.Server.Implementations/Migrations/20200529171409_AddUsers.cs index 166d9dbb5..2b84cf7b0 100644 --- a/Jellyfin.Server.Implementations/Migrations/20200529171409_AddUsers.cs +++ b/Jellyfin.Server.Implementations/Migrations/20200529171409_AddUsers.cs @@ -1,4 +1,7 @@ -using System; +#pragma warning disable CS1591 +#pragma warning disable SA1601 + +using System; using Microsoft.EntityFrameworkCore.Migrations; namespace Jellyfin.Server.Implementations.Migrations -- cgit v1.2.3 From 4857b7d62097848eda7f3666b60e039b1df5b6b1 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 29 May 2020 15:32:31 -0400 Subject: Make UserManager.IsValidUsername static --- Jellyfin.Server.Implementations/Users/UserManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 3d473f5f2..b62c64830 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -648,7 +648,7 @@ namespace Jellyfin.Server.Implementations.Users dbContext.SaveChanges(); } - private bool IsValidUsername(string name) + private static 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 -- cgit v1.2.3 From f73c5d3f56f73c0c938d7aa75ad18aeb0a9fd8d0 Mon Sep 17 00:00:00 2001 From: WontTell Date: Sat, 30 May 2020 02:27:19 +0000 Subject: Added translation using Weblate (Spanish (Latin America)) --- Emby.Server.Implementations/Localization/Core/es_419.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 Emby.Server.Implementations/Localization/Core/es_419.json diff --git a/Emby.Server.Implementations/Localization/Core/es_419.json b/Emby.Server.Implementations/Localization/Core/es_419.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/es_419.json @@ -0,0 +1 @@ +{} -- cgit v1.2.3 From c8fef9dd2ecfaa0a9fe3df7a26b0afcec823ba52 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 30 May 2020 00:19:36 -0400 Subject: Reimplement password resetting --- .../Users/DefaultAuthenticationProvider.cs | 2 +- .../Users/DefaultPasswordResetProvider.cs | 29 +++++++++++++++------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs index c15312a72..4261f5b18 100644 --- a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs @@ -95,7 +95,7 @@ namespace Jellyfin.Server.Implementations.Users /// public bool HasPassword(User user) - => !string.IsNullOrEmpty(user.Password); + => !string.IsNullOrEmpty(user?.Password); /// public Task ChangePassword(User user, string newPassword) diff --git a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs index 60b48ec76..36c95586a 100644 --- a/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs @@ -52,16 +52,16 @@ namespace Jellyfin.Server.Implementations.Users /// public async Task RedeemPasswordResetPin(string pin) { - SerializablePasswordReset spr; - List usersReset = new List(); + var usersReset = new List(); foreach (var resetFile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*")) { + SerializablePasswordReset spr; await using (var str = File.OpenRead(resetFile)) { spr = await _jsonSerializer.DeserializeFromStreamAsync(str).ConfigureAwait(false); } - if (spr.ExpirationDate < DateTime.Now) + if (spr.ExpirationDate < DateTime.UtcNow) { File.Delete(resetFile); } @@ -70,11 +70,8 @@ namespace Jellyfin.Server.Implementations.Users 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"); - } + var resetUser = _userManager.GetUserByName(spr.UserName) + ?? throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found"); await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); usersReset.Add(resetUser.Username); @@ -105,7 +102,21 @@ namespace Jellyfin.Server.Implementations.Users pin = BitConverter.ToString(bytes); } - DateTime expireTime = DateTime.Now.AddMinutes(30); + DateTime expireTime = DateTime.UtcNow.AddMinutes(30); + string filePath = _passwordResetFileBase + user.Id + ".json"; + SerializablePasswordReset spr = new SerializablePasswordReset + { + ExpirationDate = expireTime, + Pin = pin, + PinFile = filePath, + UserName = user.Username + }; + + await using (FileStream fileStream = File.OpenWrite(filePath)) + { + _jsonSerializer.SerializeToStream(spr, fileStream); + await fileStream.FlushAsync().ConfigureAwait(false); + } user.EasyPassword = pin; await _userManager.UpdateUserAsync(user).ConfigureAwait(false); -- cgit v1.2.3 From 1b297eae7834e8604698ae780c4b2ced7d20897d Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 30 May 2020 00:20:59 -0400 Subject: Reset invalid login attempt count properly --- Jellyfin.Server.Implementations/Users/UserManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index b62c64830..91d0e5b80 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -467,10 +467,10 @@ namespace Jellyfin.Server.Implementations.Users if (isUserSession) { user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; - await UpdateUserAsync(user).ConfigureAwait(false); } user.InvalidLoginAttemptCount = 0; + await UpdateUserAsync(user).ConfigureAwait(false); _logger.LogInformation("Authentication request for {UserName} has succeeded.", user.Username); } else -- cgit v1.2.3 From 730395886db06ef83fa7e6f29fd95666519eb458 Mon Sep 17 00:00:00 2001 From: WontTell Date: Sat, 30 May 2020 02:27:41 +0000 Subject: Translated using Weblate (Spanish (Latin America)) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es_419/ --- .../Localization/Core/es_419.json | 118 ++++++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/es_419.json b/Emby.Server.Implementations/Localization/Core/es_419.json index 0967ef424..b0fdc8386 100644 --- a/Emby.Server.Implementations/Localization/Core/es_419.json +++ b/Emby.Server.Implementations/Localization/Core/es_419.json @@ -1 +1,117 @@ -{} +{ + "LabelRunningTimeValue": "Duración: {0}", + "ValueSpecialEpisodeName": "Especial - {0}", + "Sync": "Sincronizar", + "Songs": "Canciones", + "Shows": "Programas", + "Playlists": "Listas de reproducción", + "Photos": "Fotos", + "Movies": "Películas", + "HeaderNextUp": "A continuación", + "HeaderLiveTV": "TV en vivo", + "HeaderFavoriteSongs": "Canciones favoritas", + "HeaderFavoriteArtists": "Artistas favoritos", + "HeaderFavoriteAlbums": "Álbumes favoritos", + "HeaderFavoriteEpisodes": "Episodios favoritos", + "HeaderFavoriteShows": "Programas favoritos", + "HeaderContinueWatching": "Continuar viendo", + "HeaderAlbumArtists": "Artistas del álbum", + "Genres": "Géneros", + "Folders": "Carpetas", + "Favorites": "Favoritos", + "Collections": "Colecciones", + "Channels": "Canales", + "Books": "Libros", + "Artists": "Artistas", + "Albums": "Álbumes", + "TaskDownloadMissingSubtitlesDescription": "Busca subtítulos faltantes en Internet basándose en la configuración de metadatos.", + "TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes", + "TaskRefreshChannelsDescription": "Actualiza la información de canales de Internet.", + "TaskRefreshChannels": "Actualizar canales", + "TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día.", + "TaskCleanTranscode": "Limpiar directorio de transcodificado", + "TaskUpdatePluginsDescription": "Descarga e instala actualizaciones para complementos que están configurados para actualizarse automáticamente.", + "TaskUpdatePlugins": "Actualizar complementos", + "TaskRefreshPeopleDescription": "Actualiza metadatos de actores y directores en tu biblioteca de medios.", + "TaskRefreshPeople": "Actualizar personas", + "TaskCleanLogsDescription": "Elimina archivos de registro con más de {0} días de antigüedad.", + "TaskCleanLogs": "Limpiar directorio de registros", + "TaskRefreshLibraryDescription": "Escanea tu biblioteca de medios por archivos nuevos y actualiza los metadatos.", + "TaskRefreshLibrary": "Escanear biblioteca de medios", + "TaskRefreshChapterImagesDescription": "Crea miniaturas para videos que tienen capítulos.", + "TaskRefreshChapterImages": "Extraer imágenes de los capítulos", + "TaskCleanCacheDescription": "Elimina archivos caché que ya no son necesarios para el sistema.", + "TaskCleanCache": "Limpiar directorio caché", + "TasksChannelsCategory": "Canales de Internet", + "TasksApplicationCategory": "Aplicación", + "TasksLibraryCategory": "Biblioteca", + "TasksMaintenanceCategory": "Mantenimiento", + "VersionNumber": "Versión {0}", + "ValueHasBeenAddedToLibrary": "{0} se ha añadido a tu biblioteca de medios", + "UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}", + "UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}", + "UserPolicyUpdatedWithName": "La política de usuario ha sido actualizada para {0}", + "UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}", + "UserOnlineFromDevice": "{0} está en línea desde {1}", + "UserOfflineFromDevice": "{0} se ha desconectado desde {1}", + "UserLockedOutWithName": "El usuario {0} ha sido bloqueado", + "UserDownloadingItemWithValues": "{0} está descargando {1}", + "UserDeletedWithName": "El usuario {0} ha sido eliminado", + "UserCreatedWithName": "El usuario {0} ha sido creado", + "User": "Usuario", + "TvShows": "Programas de TV", + "System": "Sistema", + "SubtitleDownloadFailureFromForItem": "Falló la descarga de subtítulos desde {0} para {1}", + "StartupEmbyServerIsLoading": "El servidor Jellyfin está cargando. Por favor, intente de nuevo pronto.", + "ServerNameNeedsToBeRestarted": "{0} debe ser reiniciado", + "ScheduledTaskStartedWithName": "{0} iniciado", + "ScheduledTaskFailedWithName": "{0} falló", + "ProviderValue": "Proveedor: {0}", + "PluginUpdatedWithName": "{0} fue actualizado", + "PluginUninstalledWithName": "{0} fue desinstalado", + "PluginInstalledWithName": "{0} fue instalado", + "Plugin": "Complemento", + "NotificationOptionVideoPlaybackStopped": "Reproducción de video detenida", + "NotificationOptionVideoPlayback": "Reproducción de video iniciada", + "NotificationOptionUserLockedOut": "Usuario bloqueado", + "NotificationOptionTaskFailed": "Falla de tarea programada", + "NotificationOptionServerRestartRequired": "Se necesita reiniciar el servidor", + "NotificationOptionPluginUpdateInstalled": "Actualización de complemento instalada", + "NotificationOptionPluginUninstalled": "Complemento desinstalado", + "NotificationOptionPluginInstalled": "Complemento instalado", + "NotificationOptionPluginError": "Falla de complemento", + "NotificationOptionNewLibraryContent": "Nuevo contenido agregado", + "NotificationOptionInstallationFailed": "Falla de instalación", + "NotificationOptionCameraImageUploaded": "Imagen de la cámara subida", + "NotificationOptionAudioPlaybackStopped": "Reproducción de audio detenida", + "NotificationOptionAudioPlayback": "Reproducción de audio iniciada", + "NotificationOptionApplicationUpdateInstalled": "Actualización de la aplicación instalada", + "NotificationOptionApplicationUpdateAvailable": "Actualización de la aplicación disponible", + "NewVersionIsAvailable": "Una nueva versión del Servidor Jellyfin está disponible para descargar.", + "NameSeasonUnknown": "Temporada desconocida", + "NameSeasonNumber": "Temporada {0}", + "NameInstallFailed": "Falló la instalación de {0}", + "MusicVideos": "Videos musicales", + "Music": "Música", + "MixedContent": "Contenido mezclado", + "MessageServerConfigurationUpdated": "Se ha actualizado la configuración del servidor", + "MessageNamedServerConfigurationUpdatedWithValue": "Se ha actualizado la sección {0} de la configuración del servidor", + "MessageApplicationUpdatedTo": "El servidor Jellyfin ha sido actualizado a {0}", + "MessageApplicationUpdated": "El servidor Jellyfin ha sido actualizado", + "Latest": "Recientes", + "LabelIpAddressValue": "Dirección IP: {0}", + "ItemRemovedWithName": "{0} fue removido de la biblioteca", + "ItemAddedWithName": "{0} fue agregado a la biblioteca", + "Inherit": "Heredar", + "HomeVideos": "Videos caseros", + "HeaderRecordingGroups": "Grupos de grabación", + "HeaderCameraUploads": "Subidas desde la cámara", + "FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión desde {0}", + "DeviceOnlineWithName": "{0} está conectado", + "DeviceOfflineWithName": "{0} se ha desconectado", + "ChapterNameValue": "Capítulo {0}", + "CameraImageUploadedFrom": "Una nueva imagen de cámara ha sido subida desde {0}", + "AuthenticationSucceededWithUserName": "{0} autenticado con éxito", + "Application": "Aplicación", + "AppDeviceValues": "App: {0}, Dispositivo: {1}" +} -- cgit v1.2.3 From acce726ed954ad7b5b542caf2f930505d8ef2969 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 30 May 2020 20:02:55 -0400 Subject: Clean up DayOfWeekHelper.cs and remove unnecessary function --- Jellyfin.Data/DayOfWeekHelper.cs | 65 +++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/Jellyfin.Data/DayOfWeekHelper.cs b/Jellyfin.Data/DayOfWeekHelper.cs index 33410b732..32a41368d 100644 --- a/Jellyfin.Data/DayOfWeekHelper.cs +++ b/Jellyfin.Data/DayOfWeekHelper.cs @@ -8,63 +8,58 @@ namespace Jellyfin.Data { public static List GetDaysOfWeek(DynamicDayOfWeek day) { - return GetDaysOfWeek(new List { day }); - } - - public static List GetDaysOfWeek(List days) - { - var list = new List(); + var days = new List(7); - if (days.Contains(DynamicDayOfWeek.Sunday) || - days.Contains(DynamicDayOfWeek.Weekend) || - days.Contains(DynamicDayOfWeek.Everyday)) + if (day == DynamicDayOfWeek.Sunday + || day == DynamicDayOfWeek.Weekend + || day == DynamicDayOfWeek.Everyday) { - list.Add(DayOfWeek.Sunday); + days.Add(DayOfWeek.Sunday); } - if (days.Contains(DynamicDayOfWeek.Saturday) || - days.Contains(DynamicDayOfWeek.Weekend) || - days.Contains(DynamicDayOfWeek.Everyday)) + if (day == DynamicDayOfWeek.Monday + || day == DynamicDayOfWeek.Weekday + || day == DynamicDayOfWeek.Everyday) { - list.Add(DayOfWeek.Saturday); + days.Add(DayOfWeek.Monday); } - if (days.Contains(DynamicDayOfWeek.Monday) || - days.Contains(DynamicDayOfWeek.Weekday) || - days.Contains(DynamicDayOfWeek.Everyday)) + if (day == DynamicDayOfWeek.Tuesday + || day == DynamicDayOfWeek.Weekday + || day == DynamicDayOfWeek.Everyday) { - list.Add(DayOfWeek.Monday); + days.Add(DayOfWeek.Tuesday); } - if (days.Contains(DynamicDayOfWeek.Tuesday) || - days.Contains(DynamicDayOfWeek.Weekday) || - days.Contains(DynamicDayOfWeek.Everyday)) + if (day == DynamicDayOfWeek.Wednesday + || day == DynamicDayOfWeek.Weekday + || day == DynamicDayOfWeek.Everyday) { - list.Add(DayOfWeek.Tuesday); + days.Add(DayOfWeek.Wednesday); } - if (days.Contains(DynamicDayOfWeek.Wednesday) || - days.Contains(DynamicDayOfWeek.Weekday) || - days.Contains(DynamicDayOfWeek.Everyday)) + if (day == DynamicDayOfWeek.Thursday + || day == DynamicDayOfWeek.Weekday + || day == DynamicDayOfWeek.Everyday) { - list.Add(DayOfWeek.Wednesday); + days.Add(DayOfWeek.Thursday); } - if (days.Contains(DynamicDayOfWeek.Thursday) || - days.Contains(DynamicDayOfWeek.Weekday) || - days.Contains(DynamicDayOfWeek.Everyday)) + if (day == DynamicDayOfWeek.Friday + || day == DynamicDayOfWeek.Weekday + || day == DynamicDayOfWeek.Everyday) { - list.Add(DayOfWeek.Thursday); + days.Add(DayOfWeek.Friday); } - if (days.Contains(DynamicDayOfWeek.Friday) || - days.Contains(DynamicDayOfWeek.Weekday) || - days.Contains(DynamicDayOfWeek.Everyday)) + if (day == DynamicDayOfWeek.Saturday + || day == DynamicDayOfWeek.Weekend + || day == DynamicDayOfWeek.Everyday) { - list.Add(DayOfWeek.Friday); + days.Add(DayOfWeek.Saturday); } - return list; + return days; } } } -- cgit v1.2.3 From 4cff9b85120032057178f224f06730e019715b8b Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 30 May 2020 20:37:46 -0400 Subject: Clea up IsParentalScheduleAllowed --- Jellyfin.Data/Entities/User.cs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index 1098cdb2f..ac87c7ec2 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -458,24 +458,14 @@ namespace Jellyfin.Data.Entities return GetPreference(PreferenceKind.GroupedFolders).Any(i => new Guid(i) == id); } - private bool IsParentalScheduleAllowed(AccessSchedule schedule, DateTime date) + private static 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; + return DayOfWeekHelper.GetDaysOfWeek(schedule.DayOfWeek).Contains(localTime.DayOfWeek) + && hour >= schedule.StartHour + && hour <= schedule.EndHour; } // TODO: make these user configurable? -- cgit v1.2.3 From e8b6da3cd71ebb9dd01f3aff4a1c8805be6ba91b Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 30 May 2020 20:49:31 -0400 Subject: Expand and document IHasPermissions --- Jellyfin.Data/Entities/Group.cs | 13 ++++++++++++- Jellyfin.Data/Entities/User.cs | 13 ++++++------- Jellyfin.Data/IHasPermissions.cs | 21 +++++++++++++++++++++ 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/Jellyfin.Data/Entities/Group.cs b/Jellyfin.Data/Entities/Group.cs index ecef4102c..b71a1bd78 100644 --- a/Jellyfin.Data/Entities/Group.cs +++ b/Jellyfin.Data/Entities/Group.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using Jellyfin.Data.Enums; namespace Jellyfin.Data.Entities { @@ -96,6 +98,15 @@ namespace Jellyfin.Data.Entities [ForeignKey("Preference_Preferences_Id")] public virtual ICollection Preferences { get; protected set; } + + public bool HasPermission(PermissionKind kind) + { + return Permissions.First(p => p.Kind == kind).Value; + } + + public void SetPermission(PermissionKind kind, bool value) + { + Permissions.First(p => p.Kind == kind).Value = value; + } } } - diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index ac87c7ec2..a6221a249 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -389,16 +389,14 @@ namespace Jellyfin.Data.Entities RowVersion++; } - partial void Init(); - /// /// Checks whether the user has the specified permission. /// - /// The permission kind. + /// The permission kind. /// True if the user has the specified permission. - public bool HasPermission(PermissionKind permission) + public bool HasPermission(PermissionKind kind) { - return Permissions.First(p => p.Kind == permission).Value; + return Permissions.First(p => p.Kind == kind).Value; } /// @@ -408,8 +406,7 @@ namespace Jellyfin.Data.Entities /// The value to set. public void SetPermission(PermissionKind kind, bool value) { - var permissionObj = Permissions.First(p => p.Kind == kind); - permissionObj.Value = value; + Permissions.First(p => p.Kind == kind).Value = value; } /// @@ -501,5 +498,7 @@ namespace Jellyfin.Data.Entities Preferences.Add(new Preference(val, string.Empty)); } } + + partial void Init(); } } diff --git a/Jellyfin.Data/IHasPermissions.cs b/Jellyfin.Data/IHasPermissions.cs index a77e51e1e..3be72259a 100644 --- a/Jellyfin.Data/IHasPermissions.cs +++ b/Jellyfin.Data/IHasPermissions.cs @@ -1,10 +1,31 @@ using System.Collections.Generic; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; namespace Jellyfin.Data { + /// + /// An abstraction representing an entity that has permissions. + /// public interface IHasPermissions { + /// + /// Gets a collection containing this entity's permissions. + /// ICollection Permissions { get; } + + /// + /// Checks whether this entity has the specified permission kind. + /// + /// The kind of permission. + /// true if this entity has the specified permission, false otherwise. + bool HasPermission(PermissionKind kind); + + /// + /// Sets the specified permission to the provided value. + /// + /// The kind of permission. + /// The value to set. + void SetPermission(PermissionKind kind, bool value); } } -- cgit v1.2.3 From e72fd88913fe2eed55b0171fc63e7d6622b0ebe5 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 30 May 2020 21:53:56 -0400 Subject: Document and fix warnings in Group.cs --- Jellyfin.Data/Entities/Group.cs | 72 +++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/Jellyfin.Data/Entities/Group.cs b/Jellyfin.Data/Entities/Group.cs index b71a1bd78..5cbb126f9 100644 --- a/Jellyfin.Data/Entities/Group.cs +++ b/Jellyfin.Data/Entities/Group.cs @@ -7,52 +7,40 @@ using Jellyfin.Data.Enums; namespace Jellyfin.Data.Entities { + /// + /// An entity representing a group. + /// public partial class Group : IHasPermissions, ISavingChanges { - partial void Init(); - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. + /// Initializes a new instance of the class. + /// Public constructor with required data. /// - protected Group() - { - Permissions = new HashSet(); - ProviderMappings = new HashSet(); - Preferences = new HashSet(); - - Init(); - } - - /// - /// Public constructor with required data - /// - /// - /// - public Group(string name, User user) + /// The name of the group. + public Group(string name) { if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException(nameof(name)); } - this.Name = name; - user.Groups.Add(this); + Name = name; + Id = Guid.NewGuid(); - this.Permissions = new HashSet(); - this.ProviderMappings = new HashSet(); - this.Preferences = new HashSet(); + Permissions = new HashSet(); + ProviderMappings = new HashSet(); + Preferences = new HashSet(); 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 Group Create(string name, User user) + protected Group() { - return new Group(name, user); + Init(); } /************************************************************************* @@ -60,23 +48,32 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Identity, Indexed, Required + /// Gets or sets the id of this group. /// + /// + /// Identity, Indexed, Required. + /// [Key] [Required] public Guid Id { get; protected set; } /// - /// Required, Max length = 255 + /// Gets or sets the group's name. /// + /// + /// Required, Max length = 255. + /// [Required] [MaxLength(255)] [StringLength(255)] public string Name { get; set; } /// - /// Required, ConcurrenyToken + /// Gets or sets the row version. /// + /// + /// Required, Concurrency Token. + /// [ConcurrencyCheck] [Required] public uint RowVersion { get; set; } @@ -99,14 +96,27 @@ namespace Jellyfin.Data.Entities [ForeignKey("Preference_Preferences_Id")] public virtual ICollection Preferences { get; protected set; } + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The name of this group + public static Group Create(string name) + { + return new Group(name); + } + + /// public bool HasPermission(PermissionKind kind) { return Permissions.First(p => p.Kind == kind).Value; } + /// public void SetPermission(PermissionKind kind, bool value) { Permissions.First(p => p.Kind == kind).Value = value; } + + partial void Init(); } } -- cgit v1.2.3 From 63344ec5fd309ed0e7f804003e2595f54885c3f1 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sat, 30 May 2020 22:11:53 -0400 Subject: Remove unused portions of the user schema --- Jellyfin.Data/Entities/ProviderMapping.cs | 6 - Jellyfin.Data/Entities/User.cs | 20 +- Jellyfin.Server.Implementations/JellyfinDb.cs | 3 +- .../Migrations/20200529171409_AddUsers.Designer.cs | 407 --------------------- .../Migrations/20200529171409_AddUsers.cs | 298 --------------- .../Migrations/20200531020729_AddUsers.Designer.cs | 312 ++++++++++++++++ .../Migrations/20200531020729_AddUsers.cs | 196 ++++++++++ .../Migrations/JellyfinDbModelSnapshot.cs | 96 ----- 8 files changed, 521 insertions(+), 817 deletions(-) delete mode 100644 Jellyfin.Server.Implementations/Migrations/20200529171409_AddUsers.Designer.cs delete mode 100644 Jellyfin.Server.Implementations/Migrations/20200529171409_AddUsers.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20200531020729_AddUsers.Designer.cs create mode 100644 Jellyfin.Server.Implementations/Migrations/20200531020729_AddUsers.cs diff --git a/Jellyfin.Data/Entities/ProviderMapping.cs b/Jellyfin.Data/Entities/ProviderMapping.cs index 6197bd97b..e479341ad 100644 --- a/Jellyfin.Data/Entities/ProviderMapping.cs +++ b/Jellyfin.Data/Entities/ProviderMapping.cs @@ -43,12 +43,6 @@ namespace Jellyfin.Data.Entities if (string.IsNullOrEmpty(providerdata)) throw new ArgumentNullException(nameof(providerdata)); this.ProviderData = providerdata; - if (_user0 == null) throw new ArgumentNullException(nameof(_user0)); - _user0.ProviderMappings.Add(this); - - if (_group1 == null) throw new ArgumentNullException(nameof(_group1)); - _group1.ProviderMappings.Add(this); - Init(); } diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index a6221a249..f09b36bde 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -47,11 +47,11 @@ namespace Jellyfin.Data.Entities AuthenticationProviderId = authenticationProviderId; PasswordResetProviderId = passwordResetProviderId; - Groups = new HashSet(); + AccessSchedules = new HashSet(); + // Groups = new HashSet(); Permissions = new HashSet(); - ProviderMappings = new HashSet(); Preferences = new HashSet(); - AccessSchedules = new HashSet(); + // ProviderMappings = new HashSet(); // Set default values Id = Guid.NewGuid(); @@ -342,11 +342,18 @@ namespace Jellyfin.Data.Entities * Navigation properties *************************************************************************/ + /// + /// Gets or sets the list of access schedules this user has. + /// + public virtual ICollection AccessSchedules { get; protected set; } + + /* /// /// Gets or sets the list of groups this user is a member of. /// [ForeignKey("Group_Groups_Guid")] public virtual ICollection Groups { get; protected set; } + */ /// /// Gets or sets the list of permissions this user has. @@ -354,11 +361,13 @@ namespace Jellyfin.Data.Entities [ForeignKey("Permission_Permissions_Guid")] public virtual ICollection Permissions { get; protected set; } + /* /// /// Gets or sets the list of provider mappings this user has. /// [ForeignKey("ProviderMapping_ProviderMappings_Id")] public virtual ICollection ProviderMappings { get; protected set; } + */ /// /// Gets or sets the list of preferences this user has. @@ -366,11 +375,6 @@ namespace Jellyfin.Data.Entities [ForeignKey("Preference_Preferences_Guid")] public virtual ICollection Preferences { get; protected set; } - /// - /// Gets or sets the list of access schedules this user has. - /// - public virtual ICollection AccessSchedules { get; protected set; } - /// /// Static create function (for use in LINQ queries, etc.) /// diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index 89fd371f8..ae783efd8 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -25,8 +25,6 @@ namespace Jellyfin.Server.Implementations public virtual DbSet ActivityLogs { get; set; } - public virtual DbSet Groups { get; set; } - public virtual DbSet Permissions { get; set; } public virtual DbSet Preferences { get; set; } @@ -45,6 +43,7 @@ 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; } diff --git a/Jellyfin.Server.Implementations/Migrations/20200529171409_AddUsers.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200529171409_AddUsers.Designer.cs deleted file mode 100644 index d1d26726e..000000000 --- a/Jellyfin.Server.Implementations/Migrations/20200529171409_AddUsers.Designer.cs +++ /dev/null @@ -1,407 +0,0 @@ -#pragma warning disable CS1591 - -// -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("20200529171409_AddUsers")] - partial class AddUsers - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("jellyfin") - .HasAnnotation("ProductVersion", "3.1.4"); - - 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") - .HasMaxLength(512); - - 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("SyncPlayAccess") - .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/20200529171409_AddUsers.cs b/Jellyfin.Server.Implementations/Migrations/20200529171409_AddUsers.cs deleted file mode 100644 index 2b84cf7b0..000000000 --- a/Jellyfin.Server.Implementations/Migrations/20200529171409_AddUsers.cs +++ /dev/null @@ -1,298 +0,0 @@ -#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(maxLength: 512, 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: true), - LastLoginDate = table.Column(nullable: true), - 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), - SyncPlayAccess = table.Column(nullable: false), - 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/20200531020729_AddUsers.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200531020729_AddUsers.Designer.cs new file mode 100644 index 000000000..04fb9132d --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200531020729_AddUsers.Designer.cs @@ -0,0 +1,312 @@ +#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("20200531020729_AddUsers")] + partial class AddUsers + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("jellyfin") + .HasAnnotation("ProductVersion", "3.1.4"); + + 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.ImageInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(512); + + 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_Permissions_Guid") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("INTEGER"); + + b.HasKey("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("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.HasKey("Id"); + + b.HasIndex("Preference_Preferences_Guid"); + + b.ToTable("Preferences"); + }); + + 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("SyncPlayAccess") + .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.Permission", b => + { + 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"); + }); + + 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/20200531020729_AddUsers.cs b/Jellyfin.Server.Implementations/Migrations/20200531020729_AddUsers.cs new file mode 100644 index 000000000..ec6b374ec --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200531020729_AddUsers.cs @@ -0,0 +1,196 @@ +#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(maxLength: 512, 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: true), + LastLoginDate = table.Column(nullable: true), + 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), + SyncPlayAccess = table.Column(nullable: false), + 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: "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_Permissions_Guid = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Permissions", x => x.Id); + 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) + }, + 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); + }); + + migrationBuilder.CreateIndex( + name: "IX_AccessSchedule_UserId", + schema: "jellyfin", + table: "AccessSchedule", + column: "UserId"); + + 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_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: "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 9a2315802..115c98aa3 100644 --- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs +++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs @@ -88,31 +88,6 @@ namespace Jellyfin.Server.Implementations.Migrations 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") @@ -141,9 +116,6 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("Kind") .HasColumnType("INTEGER"); - b.Property("Permission_GroupPermissions_Id") - .HasColumnType("TEXT"); - b.Property("Permission_Permissions_Guid") .HasColumnType("TEXT"); @@ -156,8 +128,6 @@ namespace Jellyfin.Server.Implementations.Migrations b.HasKey("Id"); - b.HasIndex("Permission_GroupPermissions_Id"); - b.HasIndex("Permission_Permissions_Guid"); b.ToTable("Permissions"); @@ -175,9 +145,6 @@ namespace Jellyfin.Server.Implementations.Migrations b.Property("Preference_Preferences_Guid") .HasColumnType("TEXT"); - b.Property("Preference_Preferences_Id") - .HasColumnType("TEXT"); - b.Property("RowVersion") .IsConcurrencyToken() .HasColumnType("INTEGER"); @@ -191,46 +158,9 @@ namespace Jellyfin.Server.Implementations.Migrations 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") @@ -351,19 +281,8 @@ namespace Jellyfin.Server.Implementations.Migrations .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"); @@ -374,21 +293,6 @@ namespace Jellyfin.Server.Implementations.Migrations 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 => -- cgit v1.2.3 From edbb1482e80d549391479df1911fe9077e62972d Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Sun, 31 May 2020 00:15:41 -0400 Subject: Remove unused compile remove statements. --- .../Jellyfin.Server.Implementations.csproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index c220bd6b6..7e61c9931 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -21,10 +21,6 @@ - - - - -- cgit v1.2.3 From 24f7f848284562221e80c863ba2758815bc6965b Mon Sep 17 00:00:00 2001 From: dkanada Date: Sun, 31 May 2020 15:15:34 +0900 Subject: add plugin configurations for tvdb and omdb --- .../Omdb/Configuration/PluginConfiguration.cs | 9 ++ .../Plugins/Omdb/Configuration/config.html | 49 ++++++++ .../Plugins/Omdb/OmdbImageProvider.cs | 1 + .../Plugins/Omdb/OmdbItemProvider.cs | 2 + .../Plugins/Omdb/OmdbProvider.cs | 139 +++++++++++++-------- MediaBrowser.Providers/Plugins/Omdb/Plugin.cs | 35 ++++++ .../TheTvdb/Configuration/PluginConfiguration.cs | 8 ++ MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs | 24 ++++ .../Plugins/TheTvdb/TvdbClientManager.cs | 3 +- .../Plugins/TheTvdb/TvdbEpisodeProvider.cs | 4 +- .../Plugins/TheTvdb/TvdbPersonImageProvider.cs | 1 - .../Plugins/TheTvdb/TvdbSeasonImageProvider.cs | 4 +- .../Plugins/TheTvdb/TvdbSeriesImageProvider.cs | 3 +- .../Plugins/TheTvdb/TvdbSeriesProvider.cs | 10 +- 14 files changed, 230 insertions(+), 62 deletions(-) create mode 100644 MediaBrowser.Providers/Plugins/Omdb/Configuration/PluginConfiguration.cs create mode 100644 MediaBrowser.Providers/Plugins/Omdb/Configuration/config.html create mode 100644 MediaBrowser.Providers/Plugins/Omdb/Plugin.cs create mode 100644 MediaBrowser.Providers/Plugins/TheTvdb/Configuration/PluginConfiguration.cs create mode 100644 MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs diff --git a/MediaBrowser.Providers/Plugins/Omdb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/Omdb/Configuration/PluginConfiguration.cs new file mode 100644 index 000000000..a9eecdd9e --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Omdb/Configuration/PluginConfiguration.cs @@ -0,0 +1,9 @@ +using MediaBrowser.Model.Plugins; + +namespace MediaBrowser.Providers.Plugins.Omdb +{ + public class PluginConfiguration : BasePluginConfiguration + { + public bool CastAndCrew { get; set; } + } +} diff --git a/MediaBrowser.Providers/Plugins/Omdb/Configuration/config.html b/MediaBrowser.Providers/Plugins/Omdb/Configuration/config.html new file mode 100644 index 000000000..8b117ec8d --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Omdb/Configuration/config.html @@ -0,0 +1,49 @@ + + + + OMDb + + +
+
+
+
+ +
+
+ +
+
+
+
+ +
+ + diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs index a450c2a6d..3cf4c3d50 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs @@ -92,6 +92,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb { return item is Movie || item is Trailer || item is Episode; } + // After other internet providers, because they're better // But before fallback providers like screengrab public int Order => 90; diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs index 64a75955a..35a840f00 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs @@ -103,6 +103,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb { urlQuery += "&t=" + WebUtility.UrlEncode(name); } + urlQuery += "&type=" + type; } else @@ -117,6 +118,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb { urlQuery += string.Format(CultureInfo.InvariantCulture, "&Episode={0}", searchInfo.IndexNumber); } + if (searchInfo.ParentIndexNumber.HasValue) { urlQuery += string.Format(CultureInfo.InvariantCulture, "&Season={0}", searchInfo.ParentIndexNumber); diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs index fbdd293ed..dcc003dca 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs @@ -87,10 +87,10 @@ namespace MediaBrowser.Providers.Plugins.Omdb item.CommunityRating = imdbRating; } - //if (!string.IsNullOrEmpty(result.Website)) - //{ - // item.HomePageUrl = result.Website; - //} + if (!string.IsNullOrEmpty(result.Website)) + { + item.HomePageUrl = result.Website; + } if (!string.IsNullOrWhiteSpace(result.imdbID)) { @@ -121,7 +121,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb if (!string.IsNullOrWhiteSpace(episodeImdbId)) { - foreach (var episode in (seasonResult.Episodes ?? new RootObject[] { })) + foreach (var episode in seasonResult.Episodes) { if (string.Equals(episodeImdbId, episode.imdbID, StringComparison.OrdinalIgnoreCase)) { @@ -134,7 +134,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb // finally, search by numbers if (result == null) { - foreach (var episode in (seasonResult.Episodes ?? new RootObject[] { })) + foreach (var episode in seasonResult.Episodes) { if (episode.Episode == episodeNumber) { @@ -188,10 +188,10 @@ namespace MediaBrowser.Providers.Plugins.Omdb item.CommunityRating = imdbRating; } - //if (!string.IsNullOrEmpty(result.Website)) - //{ - // item.HomePageUrl = result.Website; - //} + if (!string.IsNullOrEmpty(result.Website)) + { + item.HomePageUrl = result.Website; + } if (!string.IsNullOrWhiteSpace(result.imdbID)) { @@ -263,6 +263,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb { return url; } + return url + "&" + query; } @@ -386,7 +387,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb var isConfiguredForEnglish = IsConfiguredForEnglish(item) || _configurationManager.Configuration.EnableNewOmdbSupport; - // Grab series genres because imdb data is better than tvdb. Leave movies alone + // Grab series genres because IMDb data is better than TVDB. Leave movies alone // But only do it if english is the preferred language because this data will not be localized if (isConfiguredForEnglish && !string.IsNullOrWhiteSpace(result.Genre)) { @@ -407,45 +408,50 @@ namespace MediaBrowser.Providers.Plugins.Omdb item.Overview = result.Plot; } - //if (!string.IsNullOrWhiteSpace(result.Director)) - //{ - // var person = new PersonInfo - // { - // Name = result.Director.Trim(), - // Type = PersonType.Director - // }; - - // itemResult.AddPerson(person); - //} - - //if (!string.IsNullOrWhiteSpace(result.Writer)) - //{ - // var person = new PersonInfo - // { - // Name = result.Director.Trim(), - // Type = PersonType.Writer - // }; - - // itemResult.AddPerson(person); - //} - - //if (!string.IsNullOrWhiteSpace(result.Actors)) - //{ - // var actorList = result.Actors.Split(','); - // foreach (var actor in actorList) - // { - // if (!string.IsNullOrWhiteSpace(actor)) - // { - // var person = new PersonInfo - // { - // Name = actor.Trim(), - // Type = PersonType.Actor - // }; - - // itemResult.AddPerson(person); - // } - // } - //} + if (!Plugin.Instance.Configuration.CastAndCrew) + { + return; + } + + if (!string.IsNullOrWhiteSpace(result.Director)) + { + var person = new PersonInfo + { + Name = result.Director.Trim(), + Type = PersonType.Director + }; + + itemResult.AddPerson(person); + } + + if (!string.IsNullOrWhiteSpace(result.Writer)) + { + var person = new PersonInfo + { + Name = result.Director.Trim(), + Type = PersonType.Writer + }; + + itemResult.AddPerson(person); + } + + if (!string.IsNullOrWhiteSpace(result.Actors)) + { + var actorList = result.Actors.Split(','); + foreach (var actor in actorList) + { + if (!string.IsNullOrWhiteSpace(actor)) + { + var person = new PersonInfo + { + Name = actor.Trim(), + Type = PersonType.Actor + }; + + itemResult.AddPerson(person); + } + } + } } private bool IsConfiguredForEnglish(BaseItem item) @@ -459,40 +465,70 @@ namespace MediaBrowser.Providers.Plugins.Omdb internal class SeasonRootObject { public string Title { get; set; } + public string seriesID { get; set; } + public int Season { get; set; } + public int? totalSeasons { get; set; } + public RootObject[] Episodes { get; set; } + public string Response { get; set; } } internal class RootObject { public string Title { get; set; } + public string Year { get; set; } + public string Rated { get; set; } + public string Released { 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 List Ratings { get; set; } + public string Metascore { get; set; } + public string imdbRating { get; set; } + public string imdbVotes { get; set; } + public string imdbID { get; set; } + public string Type { get; set; } + public string DVD { get; set; } + public string BoxOffice { get; set; } + public string Production { get; set; } + public string Website { get; set; } + public string Response { get; set; } + public int Episode { get; set; } public float? GetRottenTomatoScore() @@ -509,12 +545,15 @@ namespace MediaBrowser.Providers.Plugins.Omdb } } } + return null; } } + public class OmdbRating { public string Source { get; set; } + public string Value { get; set; } } } diff --git a/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs b/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs new file mode 100644 index 000000000..6ce2333e0 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Serialization; + +namespace MediaBrowser.Providers.Plugins.Omdb +{ + public class Plugin : BasePlugin, IHasWebPages + { + public static Plugin Instance { get; private set; } + + public override Guid Id => new Guid("a628c0da-fac5-4c7e-9d1a-7134223f14c8"); + + public override string Name => "OMDb"; + + public override string Description => "Get metadata for movies and other video content from OMDb."; + + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) + : base(applicationPaths, xmlSerializer) + { + Instance = this; + } + + public IEnumerable GetPages() + { + yield return new PluginPageInfo + { + Name = Name, + EmbeddedResourcePath = GetType().Namespace + ".Configuration.config.html" + }; + } + } +} diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/TheTvdb/Configuration/PluginConfiguration.cs new file mode 100644 index 000000000..0a73634dc --- /dev/null +++ b/MediaBrowser.Providers/Plugins/TheTvdb/Configuration/PluginConfiguration.cs @@ -0,0 +1,8 @@ +using MediaBrowser.Model.Plugins; + +namespace MediaBrowser.Providers.Plugins.TheTvdb +{ + public class PluginConfiguration : BasePluginConfiguration + { + } +} diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs b/MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs new file mode 100644 index 000000000..2e6f548ca --- /dev/null +++ b/MediaBrowser.Providers/Plugins/TheTvdb/Plugin.cs @@ -0,0 +1,24 @@ +using System; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Plugins; +using MediaBrowser.Model.Serialization; + +namespace MediaBrowser.Providers.Plugins.TheTvdb +{ + public class Plugin : BasePlugin + { + public static Plugin Instance { get; private set; } + + public override Guid Id => new Guid("a677c0da-fac5-4cde-941a-7134223f14c8"); + + public override string Name => "TheTVDB"; + + public override string Description => "Get metadata for movies and other video content from TheTVDB."; + + public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) + : base(applicationPaths, xmlSerializer) + { + Instance = this; + } + } +} diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs index b73834155..24d60deb9 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs @@ -120,6 +120,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb var cacheKey = GenerateKey("series", zap2ItId, language); return TryGetValue(cacheKey, language, () => TvDbClient.Search.SearchSeriesByZap2ItIdAsync(zap2ItId, cancellationToken)); } + public Task> GetActorsAsync( int tvdbId, string language, @@ -190,7 +191,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb episodeQuery.AbsoluteNumber = searchInfo.IndexNumber.Value; break; default: - //aired order + // aired order episodeQuery.AiredEpisode = searchInfo.IndexNumber.Value; episodeQuery.AiredSeason = searchInfo.ParentIndexNumber.Value; break; diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs index 08c2a74d2..5a4827d2f 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs @@ -14,9 +14,8 @@ using TvDbSharper.Dto; namespace MediaBrowser.Providers.Plugins.TheTvdb { - /// - /// Class RemoteEpisodeProvider + /// Class RemoteEpisodeProvider. /// public class TvdbEpisodeProvider : IRemoteMetadataProvider, IHasOrder { @@ -139,7 +138,6 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb Name = episode.EpisodeName, Overview = episode.Overview, CommunityRating = (float?)episode.SiteRating, - } }; result.ResetPeople(); diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs index c1cdc90e9..77425f1d2 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbPersonImageProvider.cs @@ -57,7 +57,6 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb { EnableImages = false } - }).Cast() .Where(i => TvdbSeriesProvider.IsValidSeries(i.ProviderIds)) .ToList(); diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs index a5d183df7..7abcd29ec 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs @@ -55,7 +55,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb if (series == null || !season.IndexNumber.HasValue || !TvdbSeriesProvider.IsValidSeries(series.ProviderIds)) { - return new RemoteImageInfo[] { }; + return Array.Empty(); } var tvdbId = Convert.ToInt32(series.GetProviderId(MetadataProviders.Tvdb)); @@ -113,8 +113,8 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb imageInfo.Type = TvdbUtils.GetImageTypeFromKeyType(image.KeyType); list.Add(imageInfo); } - var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase); + var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase); return list.OrderByDescending(i => { if (string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs index 1bad60756..f65707291 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs @@ -79,6 +79,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb tvdbId); } } + return remoteImages; } @@ -110,8 +111,8 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb imageInfo.Type = TvdbUtils.GetImageTypeFromKeyType(image.KeyType); list.Add(imageInfo); } - var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase); + var isLanguageEn = string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase); return list.OrderByDescending(i => { if (string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs index f6cd249f5..d4fcad643 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs @@ -22,6 +22,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb public class TvdbSeriesProvider : IRemoteMetadataProvider, IHasOrder { internal static TvdbSeriesProvider Current { get; private set; } + private readonly IHttpClient _httpClient; private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; @@ -145,7 +146,6 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb private async Task GetSeriesByRemoteId(string id, string idType, string language, CancellationToken cancellationToken) { - TvDbResponse result = null; try @@ -249,6 +249,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb ImageUrl = TvdbUtils.BannerUrl + seriesSearchResult.Banner }; + try { var seriesSesult = @@ -274,11 +275,12 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb } /// - /// The remove + /// The remove. /// const string remove = "\"'!`?"; + /// - /// The spacers + /// The spacers. /// const string spacers = "/,.:;\\(){}[]+-_=–*"; // (there are two types of dashes, short and long) @@ -315,8 +317,8 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb sb.Append(c); } } - sb.Replace(", the", string.Empty).Replace("the ", " ").Replace(" the ", " "); + sb.Replace(", the", string.Empty).Replace("the ", " ").Replace(" the ", " "); return Regex.Replace(sb.ToString().Trim(), @"\s+", " "); } -- cgit v1.2.3 From d72bb8358e8d9aa20b2951bfea58fd51679fda96 Mon Sep 17 00:00:00 2001 From: dkanada Date: Sun, 31 May 2020 15:20:17 +0900 Subject: minor changes to server configuration file --- .../Configuration/ServerConfiguration.cs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 1f5981f10..75bbeccfc 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -240,11 +240,13 @@ namespace MediaBrowser.Model.Configuration public bool EnableNewOmdbSupport { get; set; } public string[] RemoteIPFilter { get; set; } + public bool IsRemoteIPFilterBlacklist { get; set; } public int ImageExtractionTimeoutMs { get; set; } public PathSubstitution[] PathSubstitutions { get; set; } + public bool EnableSimpleArtistDetection { get; set; } public string[] UninstalledPlugins { get; set; } @@ -313,24 +315,24 @@ namespace MediaBrowser.Model.Configuration new MetadataOptions { ItemType = "MusicVideo", - DisabledMetadataFetchers = new [] { "The Open Movie Database" }, - DisabledImageFetchers = new [] { "The Open Movie Database" } + DisabledMetadataFetchers = new[] { "The Open Movie Database" }, + DisabledImageFetchers = new[] { "The Open Movie Database" } }, new MetadataOptions { ItemType = "Series", - DisabledMetadataFetchers = new [] { "TheMovieDb" }, - DisabledImageFetchers = new [] { "TheMovieDb" } + DisabledMetadataFetchers = new[] { "TheMovieDb" }, + DisabledImageFetchers = new[] { "TheMovieDb" } }, new MetadataOptions { ItemType = "MusicAlbum", - DisabledMetadataFetchers = new [] { "TheAudioDB" } + DisabledMetadataFetchers = new[] { "TheAudioDB" } }, new MetadataOptions { ItemType = "MusicArtist", - DisabledMetadataFetchers = new [] { "TheAudioDB" } + DisabledMetadataFetchers = new[] { "TheAudioDB" } }, new MetadataOptions { @@ -339,13 +341,13 @@ namespace MediaBrowser.Model.Configuration new MetadataOptions { ItemType = "Season", - DisabledMetadataFetchers = new [] { "TheMovieDb" }, + DisabledMetadataFetchers = new[] { "TheMovieDb" }, }, new MetadataOptions { ItemType = "Episode", - DisabledMetadataFetchers = new [] { "The Open Movie Database", "TheMovieDb" }, - DisabledImageFetchers = new [] { "The Open Movie Database", "TheMovieDb" } + DisabledMetadataFetchers = new[] { "The Open Movie Database", "TheMovieDb" }, + DisabledImageFetchers = new[] { "The Open Movie Database", "TheMovieDb" } } }; } @@ -354,6 +356,7 @@ namespace MediaBrowser.Model.Configuration public class PathSubstitution { public string From { get; set; } + public string To { get; set; } } } -- cgit v1.2.3 From 685f8ad1f0ab74f73cfdb5608b8241d043f8fb25 Mon Sep 17 00:00:00 2001 From: dkanada Date: Sun, 31 May 2020 15:23:09 +0900 Subject: move tmdb to plugin folder --- .../Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs | 25 + .../Tmdb/BoxSets/TmdbBoxSetImageProvider.cs | 160 ++++++ .../Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs | 287 +++++++++++ .../Tmdb/Models/Collections/CollectionImages.cs | 11 + .../Tmdb/Models/Collections/CollectionResult.cs | 15 + .../Plugins/Tmdb/Models/Collections/Part.cs | 11 + .../Plugins/Tmdb/Models/General/Backdrop.cs | 13 + .../Plugins/Tmdb/Models/General/Crew.cs | 12 + .../Plugins/Tmdb/Models/General/ExternalIds.cs | 11 + .../Plugins/Tmdb/Models/General/Genre.cs | 8 + .../Plugins/Tmdb/Models/General/Images.cs | 10 + .../Plugins/Tmdb/Models/General/Keyword.cs | 8 + .../Plugins/Tmdb/Models/General/Keywords.cs | 9 + .../Plugins/Tmdb/Models/General/Poster.cs | 13 + .../Plugins/Tmdb/Models/General/Profile.cs | 11 + .../Plugins/Tmdb/Models/General/Still.cs | 14 + .../Plugins/Tmdb/Models/General/StillImages.cs | 9 + .../Plugins/Tmdb/Models/General/Video.cs | 14 + .../Plugins/Tmdb/Models/General/Videos.cs | 9 + .../Tmdb/Models/Movies/BelongsToCollection.cs | 10 + .../Plugins/Tmdb/Models/Movies/Cast.cs | 12 + .../Plugins/Tmdb/Models/Movies/Casts.cs | 11 + .../Plugins/Tmdb/Models/Movies/Country.cs | 11 + .../Plugins/Tmdb/Models/Movies/MovieResult.cs | 49 ++ .../Tmdb/Models/Movies/ProductionCompany.cs | 8 + .../Tmdb/Models/Movies/ProductionCountry.cs | 8 + .../Plugins/Tmdb/Models/Movies/Releases.cs | 9 + .../Plugins/Tmdb/Models/Movies/SpokenLanguage.cs | 8 + .../Plugins/Tmdb/Models/Movies/Trailers.cs | 9 + .../Plugins/Tmdb/Models/Movies/Youtube.cs | 9 + .../Plugins/Tmdb/Models/People/PersonImages.cs | 10 + .../Plugins/Tmdb/Models/People/PersonResult.cs | 23 + .../Tmdb/Models/Search/ExternalIdLookupResult.cs | 9 + .../Plugins/Tmdb/Models/Search/MovieResult.cs | 65 +++ .../Tmdb/Models/Search/PersonSearchResult.cs | 29 ++ .../Plugins/Tmdb/Models/Search/TmdbSearchResult.cs | 31 ++ .../Plugins/Tmdb/Models/Search/TvResult.cs | 15 + .../Plugins/Tmdb/Models/TV/Cast.cs | 12 + .../Plugins/Tmdb/Models/TV/ContentRating.cs | 8 + .../Plugins/Tmdb/Models/TV/ContentRatings.cs | 9 + .../Plugins/Tmdb/Models/TV/CreatedBy.cs | 9 + .../Plugins/Tmdb/Models/TV/Credits.cs | 11 + .../Plugins/Tmdb/Models/TV/Episode.cs | 14 + .../Plugins/Tmdb/Models/TV/EpisodeCredits.cs | 12 + .../Plugins/Tmdb/Models/TV/EpisodeResult.cs | 23 + .../Plugins/Tmdb/Models/TV/GuestStar.cs | 12 + .../Plugins/Tmdb/Models/TV/Network.cs | 8 + .../Plugins/Tmdb/Models/TV/Season.cs | 11 + .../Plugins/Tmdb/Models/TV/SeasonImages.cs | 10 + .../Plugins/Tmdb/Models/TV/SeasonResult.cs | 21 + .../Plugins/Tmdb/Models/TV/SeriesResult.cs | 40 ++ .../Plugins/Tmdb/Movies/GenericTmdbMovieInfo.cs | 309 +++++++++++ .../Plugins/Tmdb/Movies/TmdbImageProvider.cs | 209 ++++++++ .../Plugins/Tmdb/Movies/TmdbMovieExternalId.cs | 32 ++ .../Plugins/Tmdb/Movies/TmdbMovieProvider.cs | 446 ++++++++++++++++ .../Plugins/Tmdb/Movies/TmdbSearch.cs | 265 ++++++++++ .../Plugins/Tmdb/Movies/TmdbSettings.cs | 22 + .../Plugins/Tmdb/Music/TmdbMusicVideoProvider.cs | 32 ++ .../Plugins/Tmdb/People/TmdbPersonExternalId.cs | 24 + .../Plugins/Tmdb/People/TmdbPersonImageProvider.cs | 134 +++++ .../Plugins/Tmdb/People/TmdbPersonProvider.cs | 276 ++++++++++ .../Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs | 131 +++++ .../Plugins/Tmdb/TV/TmdbEpisodeProvider.cs | 211 ++++++++ .../Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs | 147 ++++++ .../Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs | 144 ++++++ .../Plugins/Tmdb/TV/TmdbSeasonProvider.cs | 229 +++++++++ .../Plugins/Tmdb/TV/TmdbSeriesExternalId.cs | 24 + .../Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs | 186 +++++++ .../Plugins/Tmdb/TV/TmdbSeriesProvider.cs | 567 +++++++++++++++++++++ MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs | 64 +++ .../Plugins/Tmdb/Trailers/TmdbTrailerProvider.cs | 44 ++ .../Tmdb/BoxSets/TmdbBoxSetExternalId.cs | 25 - .../Tmdb/BoxSets/TmdbBoxSetImageProvider.cs | 160 ------ .../Tmdb/BoxSets/TmdbBoxSetProvider.cs | 287 ----------- .../Tmdb/Models/Collections/CollectionImages.cs | 11 - .../Tmdb/Models/Collections/CollectionResult.cs | 15 - .../Tmdb/Models/Collections/Part.cs | 11 - .../Tmdb/Models/General/Backdrop.cs | 13 - MediaBrowser.Providers/Tmdb/Models/General/Crew.cs | 12 - .../Tmdb/Models/General/ExternalIds.cs | 11 - .../Tmdb/Models/General/Genre.cs | 8 - .../Tmdb/Models/General/Images.cs | 10 - .../Tmdb/Models/General/Keyword.cs | 8 - .../Tmdb/Models/General/Keywords.cs | 9 - .../Tmdb/Models/General/Poster.cs | 13 - .../Tmdb/Models/General/Profile.cs | 11 - .../Tmdb/Models/General/Still.cs | 14 - .../Tmdb/Models/General/StillImages.cs | 9 - .../Tmdb/Models/General/Video.cs | 14 - .../Tmdb/Models/General/Videos.cs | 9 - .../Tmdb/Models/Movies/BelongsToCollection.cs | 10 - MediaBrowser.Providers/Tmdb/Models/Movies/Cast.cs | 12 - MediaBrowser.Providers/Tmdb/Models/Movies/Casts.cs | 11 - .../Tmdb/Models/Movies/Country.cs | 11 - .../Tmdb/Models/Movies/MovieResult.cs | 49 -- .../Tmdb/Models/Movies/ProductionCompany.cs | 8 - .../Tmdb/Models/Movies/ProductionCountry.cs | 8 - .../Tmdb/Models/Movies/Releases.cs | 9 - .../Tmdb/Models/Movies/SpokenLanguage.cs | 8 - .../Tmdb/Models/Movies/Trailers.cs | 9 - .../Tmdb/Models/Movies/Youtube.cs | 9 - .../Tmdb/Models/People/PersonImages.cs | 10 - .../Tmdb/Models/People/PersonResult.cs | 23 - .../Tmdb/Models/Search/ExternalIdLookupResult.cs | 9 - .../Tmdb/Models/Search/MovieResult.cs | 65 --- .../Tmdb/Models/Search/PersonSearchResult.cs | 29 -- .../Tmdb/Models/Search/TmdbSearchResult.cs | 31 -- .../Tmdb/Models/Search/TvResult.cs | 15 - MediaBrowser.Providers/Tmdb/Models/TV/Cast.cs | 12 - .../Tmdb/Models/TV/ContentRating.cs | 8 - .../Tmdb/Models/TV/ContentRatings.cs | 9 - MediaBrowser.Providers/Tmdb/Models/TV/CreatedBy.cs | 9 - MediaBrowser.Providers/Tmdb/Models/TV/Credits.cs | 11 - MediaBrowser.Providers/Tmdb/Models/TV/Episode.cs | 14 - .../Tmdb/Models/TV/EpisodeCredits.cs | 12 - .../Tmdb/Models/TV/EpisodeResult.cs | 23 - MediaBrowser.Providers/Tmdb/Models/TV/GuestStar.cs | 12 - MediaBrowser.Providers/Tmdb/Models/TV/Network.cs | 8 - MediaBrowser.Providers/Tmdb/Models/TV/Season.cs | 11 - .../Tmdb/Models/TV/SeasonImages.cs | 10 - .../Tmdb/Models/TV/SeasonResult.cs | 21 - .../Tmdb/Models/TV/SeriesResult.cs | 40 -- .../Tmdb/Movies/GenericTmdbMovieInfo.cs | 309 ----------- .../Tmdb/Movies/TmdbImageProvider.cs | 209 -------- .../Tmdb/Movies/TmdbMovieExternalId.cs | 32 -- .../Tmdb/Movies/TmdbMovieProvider.cs | 447 ---------------- MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs | 265 ---------- MediaBrowser.Providers/Tmdb/Movies/TmdbSettings.cs | 22 - .../Tmdb/Music/TmdbMusicVideoProvider.cs | 32 -- .../Tmdb/People/TmdbPersonExternalId.cs | 24 - .../Tmdb/People/TmdbPersonImageProvider.cs | 134 ----- .../Tmdb/People/TmdbPersonProvider.cs | 276 ---------- .../Tmdb/TV/TmdbEpisodeImageProvider.cs | 131 ----- .../Tmdb/TV/TmdbEpisodeProvider.cs | 211 -------- .../Tmdb/TV/TmdbEpisodeProviderBase.cs | 147 ------ .../Tmdb/TV/TmdbSeasonImageProvider.cs | 144 ------ .../Tmdb/TV/TmdbSeasonProvider.cs | 229 --------- .../Tmdb/TV/TmdbSeriesExternalId.cs | 24 - .../Tmdb/TV/TmdbSeriesImageProvider.cs | 186 ------- .../Tmdb/TV/TmdbSeriesProvider.cs | 567 --------------------- MediaBrowser.Providers/Tmdb/TmdbUtils.cs | 64 --- .../Tmdb/Trailers/TmdbTrailerProvider.cs | 44 -- 142 files changed, 4672 insertions(+), 4673 deletions(-) create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionImages.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionResult.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/Part.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/Backdrop.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/Crew.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/ExternalIds.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/Genre.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/Images.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keyword.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keywords.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/Poster.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/Profile.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/Still.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/StillImages.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/Video.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/General/Videos.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/BelongsToCollection.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Cast.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Casts.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Country.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/MovieResult.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCompany.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/ProductionCountry.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Releases.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/SpokenLanguage.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Trailers.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Movies/Youtube.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/People/PersonImages.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/People/PersonResult.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Search/ExternalIdLookupResult.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Search/MovieResult.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Search/PersonSearchResult.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Search/TmdbSearchResult.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/Search/TvResult.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Cast.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/ContentRating.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/ContentRatings.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/CreatedBy.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Credits.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Episode.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeCredits.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/EpisodeResult.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/GuestStar.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Network.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/Season.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeasonImages.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeasonResult.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Models/TV/SeriesResult.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Movies/GenericTmdbMovieInfo.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSearch.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSettings.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Music/TmdbMusicVideoProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs create mode 100644 MediaBrowser.Providers/Plugins/Tmdb/Trailers/TmdbTrailerProvider.cs delete mode 100644 MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetExternalId.cs delete mode 100644 MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs delete mode 100644 MediaBrowser.Providers/Tmdb/BoxSets/TmdbBoxSetProvider.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/Collections/CollectionImages.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/Collections/CollectionResult.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/Collections/Part.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/General/Backdrop.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/General/Crew.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/General/ExternalIds.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/General/Genre.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/General/Images.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/General/Keyword.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/General/Keywords.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/General/Poster.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/General/Profile.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/General/Still.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/General/StillImages.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/General/Video.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/General/Videos.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/Movies/BelongsToCollection.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/Movies/Cast.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/Movies/Casts.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/Movies/Country.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/Movies/MovieResult.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/Movies/ProductionCompany.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/Movies/ProductionCountry.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/Movies/Releases.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/Movies/SpokenLanguage.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/Movies/Trailers.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/Movies/Youtube.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/People/PersonImages.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/People/PersonResult.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/Search/ExternalIdLookupResult.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/Search/MovieResult.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/Search/PersonSearchResult.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/Search/TmdbSearchResult.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/Search/TvResult.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/TV/Cast.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/TV/ContentRating.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/TV/ContentRatings.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/TV/CreatedBy.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/TV/Credits.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/TV/Episode.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/TV/EpisodeCredits.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/TV/EpisodeResult.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/TV/GuestStar.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/TV/Network.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/TV/Season.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/TV/SeasonImages.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/TV/SeasonResult.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Models/TV/SeriesResult.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Movies/GenericTmdbMovieInfo.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Movies/TmdbImageProvider.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Movies/TmdbMovieExternalId.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Movies/TmdbMovieProvider.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Movies/TmdbSearch.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Movies/TmdbSettings.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Music/TmdbMusicVideoProvider.cs delete mode 100644 MediaBrowser.Providers/Tmdb/People/TmdbPersonExternalId.cs delete mode 100644 MediaBrowser.Providers/Tmdb/People/TmdbPersonImageProvider.cs delete mode 100644 MediaBrowser.Providers/Tmdb/People/TmdbPersonProvider.cs delete mode 100644 MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeImageProvider.cs delete mode 100644 MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProvider.cs delete mode 100644 MediaBrowser.Providers/Tmdb/TV/TmdbEpisodeProviderBase.cs delete mode 100644 MediaBrowser.Providers/Tmdb/TV/TmdbSeasonImageProvider.cs delete mode 100644 MediaBrowser.Providers/Tmdb/TV/TmdbSeasonProvider.cs delete mode 100644 MediaBrowser.Providers/Tmdb/TV/TmdbSeriesExternalId.cs delete mode 100644 MediaBrowser.Providers/Tmdb/TV/TmdbSeriesImageProvider.cs delete mode 100644 MediaBrowser.Providers/Tmdb/TV/TmdbSeriesProvider.cs delete mode 100644 MediaBrowser.Providers/Tmdb/TmdbUtils.cs delete mode 100644 MediaBrowser.Providers/Tmdb/Trailers/TmdbTrailerProvider.cs diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs new file mode 100644 index 000000000..a260406da --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs @@ -0,0 +1,25 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets +{ + public class TmdbBoxSetExternalId : IExternalId + { + /// + public string Name => TmdbUtils.ProviderName; + + /// + public string Key => MetadataProviders.TmdbCollection.ToString(); + + /// + public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "collection/{0}"; + + /// + public bool Supports(IHasProviderIds item) + { + return item is Movie || item is MusicVideo || item is Trailer; + } + } +} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs new file mode 100644 index 000000000..0918b3eb6 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.Plugins.Tmdb.Models.Collections; +using MediaBrowser.Providers.Plugins.Tmdb.Models.General; +using MediaBrowser.Providers.Plugins.Tmdb.Movies; + +namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets +{ + public class TmdbBoxSetImageProvider : IRemoteImageProvider, IHasOrder + { + private readonly IHttpClient _httpClient; + + public TmdbBoxSetImageProvider(IHttpClient httpClient) + { + _httpClient = httpClient; + } + + public string Name => ProviderName; + + public static string ProviderName => TmdbUtils.ProviderName; + + public bool Supports(BaseItem item) + { + return item is BoxSet; + } + + public IEnumerable GetSupportedImages(BaseItem item) + { + return new List + { + ImageType.Primary, + ImageType.Backdrop + }; + } + + public async Task> GetImages(BaseItem item, CancellationToken cancellationToken) + { + var tmdbId = item.GetProviderId(MetadataProviders.Tmdb); + + if (!string.IsNullOrEmpty(tmdbId)) + { + var language = item.GetPreferredMetadataLanguage(); + + var mainResult = await TmdbBoxSetProvider.Current.GetMovieDbResult(tmdbId, null, cancellationToken).ConfigureAwait(false); + + if (mainResult != null) + { + var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); + + var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); + + return GetImages(mainResult, language, tmdbImageUrl); + } + } + + return new List(); + } + + private IEnumerable GetImages(CollectionResult obj, string language, string baseUrl) + { + var list = new List(); + + var images = obj.Images ?? new CollectionImages(); + + list.AddRange(GetPosters(images).Select(i => new RemoteImageInfo + { + Url = baseUrl + i.File_Path, + CommunityRating = i.Vote_Average, + VoteCount = i.Vote_Count, + Width = i.Width, + Height = i.Height, + Language = TmdbMovieProvider.AdjustImageLanguage(i.Iso_639_1, language), + ProviderName = Name, + Type = ImageType.Primary, + RatingType = RatingType.Score + })); + + list.AddRange(GetBackdrops(images).Select(i => new RemoteImageInfo + { + Url = baseUrl + i.File_Path, + CommunityRating = i.Vote_Average, + VoteCount = i.Vote_Count, + Width = i.Width, + Height = i.Height, + ProviderName = Name, + Type = ImageType.Backdrop, + RatingType = RatingType.Score + })); + + var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase); + + return list.OrderByDescending(i => + { + if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase)) + { + return 3; + } + if (!isLanguageEn) + { + if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase)) + { + return 2; + } + } + if (string.IsNullOrEmpty(i.Language)) + { + return isLanguageEn ? 3 : 2; + } + return 0; + }) + .ThenByDescending(i => i.CommunityRating ?? 0) + .ThenByDescending(i => i.VoteCount ?? 0); + } + + /// + /// Gets the posters. + /// + /// The images. + /// IEnumerable{MovieDbProvider.Poster}. + private IEnumerable GetPosters(CollectionImages images) + { + return images.Posters ?? new List(); + } + + /// + /// Gets the backdrops. + /// + /// The images. + /// IEnumerable{MovieDbProvider.Backdrop}. + private IEnumerable GetBackdrops(CollectionImages images) + { + var eligibleBackdrops = images.Backdrops == null ? new List() : + images.Backdrops; + + return eligibleBackdrops.OrderByDescending(i => i.Vote_Average) + .ThenByDescending(i => i.Vote_Count); + } + + public int Order => 0; + + public Task GetImageResponse(string url, CancellationToken cancellationToken) + { + return _httpClient.GetResponse(new HttpRequestOptions + { + CancellationToken = cancellationToken, + Url = url + }); + } + } +} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs new file mode 100644 index 000000000..75d91a11d --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs @@ -0,0 +1,287 @@ +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.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Providers; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Providers.Plugins.Tmdb.Models.Collections; +using MediaBrowser.Providers.Plugins.Tmdb.Models.General; +using MediaBrowser.Providers.Plugins.Tmdb.Movies; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets +{ + public class TmdbBoxSetProvider : IRemoteMetadataProvider + { + private const string GetCollectionInfo3 = TmdbUtils.BaseTmdbApiUrl + @"3/collection/{0}?api_key={1}&append_to_response=images"; + + internal static TmdbBoxSetProvider Current; + + private readonly ILogger _logger; + private readonly IJsonSerializer _json; + private readonly IServerConfigurationManager _config; + private readonly IFileSystem _fileSystem; + private readonly ILocalizationManager _localization; + private readonly IHttpClient _httpClient; + private readonly ILibraryManager _libraryManager; + + public TmdbBoxSetProvider( + ILogger logger, + IJsonSerializer json, + IServerConfigurationManager config, + IFileSystem fileSystem, + ILocalizationManager localization, + IHttpClient httpClient, + ILibraryManager libraryManager) + { + _logger = logger; + _json = json; + _config = config; + _fileSystem = fileSystem; + _localization = localization; + _httpClient = httpClient; + _libraryManager = libraryManager; + Current = this; + } + + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + public async Task> GetSearchResults(BoxSetInfo searchInfo, CancellationToken cancellationToken) + { + var tmdbId = searchInfo.GetProviderId(MetadataProviders.Tmdb); + + if (!string.IsNullOrEmpty(tmdbId)) + { + await EnsureInfo(tmdbId, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false); + + var dataFilePath = GetDataFilePath(_config.ApplicationPaths, tmdbId, searchInfo.MetadataLanguage); + var info = _json.DeserializeFromFile(dataFilePath); + + var images = (info.Images ?? new CollectionImages()).Posters ?? new List(); + + var tmdbSettings = await TmdbMovieProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false); + + var tmdbImageUrl = tmdbSettings.images.GetImageUrl("original"); + + var result = new RemoteSearchResult + { + Name = info.Name, + + SearchProviderName = Name, + + ImageUrl = images.Count == 0 ? null : (tmdbImageUrl + images[0].File_Path) + }; + + result.SetProviderId(MetadataProviders.Tmdb, info.Id.ToString(_usCulture)); + + return new[] { result }; + } + + return await new TmdbSearch(_logger, _json, _libraryManager).GetSearchResults(searchInfo, cancellationToken).ConfigureAwait(false); + } + + public async Task> GetMetadata(BoxSetInfo id, CancellationToken cancellationToken) + { + var tmdbId = id.GetProviderId(MetadataProviders.Tmdb); + + // We don't already have an Id, need to fetch it + if (string.IsNullOrEmpty(tmdbId)) + { + var searchResults = await new TmdbSearch(_logger, _json, _libraryManager).GetSearchResults(id, cancellationToken).ConfigureAwait(false); + + var searchResult = searchResults.FirstOrDefault(); + + if (searchResult != null) + { + tmdbId = searchResult.GetProviderId(MetadataProviders.Tmdb); + } + } + + var result = new MetadataResult(); + + if (!string.IsNullOrEmpty(tmdbId)) + { + var mainResult = await GetMovieDbResult(tmdbId, id.MetadataLanguage, cancellationToken).ConfigureAwait(false); + + if (mainResult != null) + { + result.HasMetadata = true; + result.Item = GetItem(mainResult); + } + } + + return result; + } + + internal async Task GetMovieDbResult(string tmdbId, string language, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(tmdbId)) + { + throw new ArgumentNullException(nameof(tmdbId)); + } + + await EnsureInfo(tmdbId, language, cancellationToken).ConfigureAwait(false); + + var dataFilePath = GetDataFilePath(_config.ApplicationPaths, tmdbId, language); + + if (!string.IsNullOrEmpty(dataFilePath)) + { + return _json.DeserializeFromFile(dataFilePath); + } + + return null; + } + + private BoxSet GetItem(CollectionResult obj) + { + var item = new BoxSet + { + Name = obj.Name, + Overview = obj.Overview + }; + + item.SetProviderId(MetadataProviders.Tmdb, obj.Id.ToString(_usCulture)); + + return item; + } + + private async Task DownloadInfo(string tmdbId, string preferredMetadataLanguage, CancellationToken cancellationToken) + { + var mainResult = await FetchMainResult(tmdbId, preferredMetadataLanguage, cancellationToken).ConfigureAwait(false); + + if (mainResult == null) return; + + var dataFilePath = GetDataFilePath(_config.ApplicationPaths, tmdbId, preferredMetadataLanguage); + + Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath)); + + _json.SerializeToFile(mainResult, dataFilePath); + } + + private async Task FetchMainResult(string id, string language, CancellationToken cancellationToken) + { + var url = string.Format(GetCollectionInfo3, id, TmdbUtils.ApiKey); + + if (!string.IsNullOrEmpty(language)) + { + url += string.Format("&language={0}", TmdbMovieProvider.NormalizeLanguage(language)); + + // Get images in english and with no language + url += "&include_image_language=" + TmdbMovieProvider.GetImageLanguagesParam(language); + } + + cancellationToken.ThrowIfCancellationRequested(); + + CollectionResult mainResult; + + using (var response = await TmdbMovieProvider.Current.GetMovieDbResponse(new HttpRequestOptions + { + Url = url, + CancellationToken = cancellationToken, + AcceptHeader = TmdbUtils.AcceptHeader + + }).ConfigureAwait(false)) + { + using (var json = response.Content) + { + mainResult = await _json.DeserializeFromStreamAsync(json).ConfigureAwait(false); + } + } + + cancellationToken.ThrowIfCancellationRequested(); + + if (mainResult != null && string.IsNullOrEmpty(mainResult.Name)) + { + if (!string.IsNullOrEmpty(language) && !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase)) + { + url = string.Format(GetCollectionInfo3, id, TmdbUtils.ApiKey) + "&language=en"; + + if (!string.IsNullOrEmpty(language)) + { + // Get images in english and with no language + url += "&include_image_language=" + TmdbMovieProvider.GetImageLanguagesParam(language); + } + + using (var response = await TmdbMovieProvider.Current.GetMovieDbResponse(new HttpRequestOptions + { + Url = url, + CancellationToken = cancellationToken, + AcceptHeader = TmdbUtils.AcceptHeader + + }).ConfigureAwait(false)) + { + using (var json = response.Content) + { + mainResult = await _json.DeserializeFromStreamAsync(json).ConfigureAwait(false); + } + } + } + } + return mainResult; + } + + internal Task EnsureInfo(string tmdbId, string preferredMetadataLanguage, CancellationToken cancellationToken) + { + var path = GetDataFilePath(_config.ApplicationPaths, tmdbId, preferredMetadataLanguage); + + var fileInfo = _fileSystem.GetFileSystemInfo(path); + + if (fileInfo.Exists) + { + // If it's recent or automatic updates are enabled, don't re-download + if ((DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 2) + { + return Task.CompletedTask; + } + } + + return DownloadInfo(tmdbId, preferredMetadataLanguage, cancellationToken); + } + + public string Name => TmdbUtils.ProviderName; + + private static string GetDataFilePath(IApplicationPaths appPaths, string tmdbId, string preferredLanguage) + { + var path = GetDataPath(appPaths, tmdbId); + + var filename = string.Format("all-{0}.json", preferredLanguage ?? string.Empty); + + return Path.Combine(path, filename); + } + + private static string GetDataPath(IApplicationPaths appPaths, string tmdbId) + { + var dataPath = GetCollectionsDataPath(appPaths); + + return Path.Combine(dataPath, tmdbId); + } + + private static string GetCollectionsDataPath(IApplicationPaths appPaths) + { + var dataPath = Path.Combine(appPaths.CachePath, "tmdb-collections"); + + return dataPath; + } + + public Task GetImageResponse(string url, CancellationToken cancellationToken) + { + return _httpClient.GetResponse(new HttpRequestOptions + { + CancellationToken = cancellationToken, + Url = url + }); + } + } +} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionImages.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionImages.cs new file mode 100644 index 000000000..2410ca16b --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionImages.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using MediaBrowser.Providers.Plugins.Tmdb.Models.General; + +namespace MediaBrowser.Providers.Plugins.Tmdb.Models.Collections +{ + public class CollectionImages + { + public List Backdrops { get; set; } + public List Posters { get; set; } + } +} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionResult.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionResult.cs new file mode 100644 index 000000000..3437552df --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/CollectionResult.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +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 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 new file mode 100644 index 000000000..462fdab53 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/Collections/Part.cs @@ -0,0 +1,11 @@ +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 new file mode 100644 index 000000000..35e3e2112 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Backdrop.cs @@ -0,0 +1,13 @@ +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 new file mode 100644 index 000000000..6a5e74ddb --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Crew.cs @@ -0,0 +1,12 @@ +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 new file mode 100644 index 000000000..a083f6e9c --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/ExternalIds.cs @@ -0,0 +1,11 @@ +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 new file mode 100644 index 000000000..7f1a394c3 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Genre.cs @@ -0,0 +1,8 @@ +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 new file mode 100644 index 000000000..166f9b740 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Images.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General +{ + public class Images + { + public List Backdrops { get; set; } + public List Posters { get; set; } + } +} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keyword.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keyword.cs new file mode 100644 index 000000000..72f417be5 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keyword.cs @@ -0,0 +1,8 @@ +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/Keywords.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keywords.cs new file mode 100644 index 000000000..ec2d7a035 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Keywords.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General +{ + public class Keywords + { + public List Results { get; set; } + } +} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Poster.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Poster.cs new file mode 100644 index 000000000..0cf04a6ce --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Poster.cs @@ -0,0 +1,13 @@ +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 new file mode 100644 index 000000000..b45cfc30f --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Profile.cs @@ -0,0 +1,11 @@ +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 new file mode 100644 index 000000000..9fc82cfee --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Still.cs @@ -0,0 +1,14 @@ +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/StillImages.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/StillImages.cs new file mode 100644 index 000000000..23af4b697 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/StillImages.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General +{ + public class StillImages + { + public List Stills { get; set; } + } +} diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Video.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Video.cs new file mode 100644 index 000000000..19bfd62f6 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Video.cs @@ -0,0 +1,14 @@ +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/General/Videos.cs b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Videos.cs new file mode 100644 index 000000000..26e839de7 --- /dev/null +++ b/MediaBrowser.Providers/Plugins/Tmdb/Models/General/Videos.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace MediaBrowser.Providers.Plugins.Tmdb.Models.General +{ + public class Videos + { + public List