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.Server.Implementations/ApplicationHost.cs | 34 +++----------------------- 1 file changed, 3 insertions(+), 31 deletions(-) (limited to 'Emby.Server.Implementations/ApplicationHost.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 0bb1d832fb..e3e071af95 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -45,7 +45,6 @@ using Emby.Server.Implementations.ScheduledTasks; using Emby.Server.Implementations.Security; using Emby.Server.Implementations.Serialization; using Emby.Server.Implementations.Session; -using Emby.Server.Implementations.SocketSharp; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; using MediaBrowser.Api; @@ -105,7 +104,6 @@ using MediaBrowser.Providers.TV.TheTVDB; using MediaBrowser.WebDashboard.Api; using MediaBrowser.XbmcMetadata.Providers; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -629,32 +627,8 @@ namespace Emby.Server.Implementations } } - public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) - { - if (!context.WebSockets.IsWebSocketRequest) - { - await next().ConfigureAwait(false); - return; - } - - await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false); - } - - public async Task ExecuteHttpHandlerAsync(HttpContext context, Func next) - { - if (context.WebSockets.IsWebSocketRequest) - { - await next().ConfigureAwait(false); - return; - } - - var request = context.Request; - var response = context.Response; - var localPath = context.Request.Path.ToString(); - - var req = new WebSocketSharpRequest(request, response, request.Path, Logger); - await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, context.RequestAborted).ConfigureAwait(false); - } + public Task ExecuteHttpHandlerAsync(HttpContext context, Func next) + => HttpServer.RequestHandler(context); /// /// Registers resources that classes will depend on @@ -777,7 +751,7 @@ namespace Emby.Server.Implementations NetworkManager, JsonSerializer, XmlSerializer, - CreateHttpListener()) + LoggerFactory) { GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading") }; @@ -1194,8 +1168,6 @@ namespace Emby.Server.Implementations }); } - protected IHttpListener CreateHttpListener() => new WebSocketSharpListener(Logger); - private CertificateInfo GetCertificateInfo(bool generateCertificate) { // Custom cert -- 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(-) (limited to 'Emby.Server.Implementations/ApplicationHost.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index c959cc974a..8158b45592 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 e290c62e16..2023c470a8 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 7a812f3201..e3f8ec0146 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 4abdd59aaf..ddd1054ad0 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 608ffc61c2..d999f76dbf 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 cfa7684c91..83c60563e7 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 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(-) (limited to 'Emby.Server.Implementations/ApplicationHost.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 8158b45592..9cb7471717 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 d999f76dbf..7742279f7a 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 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(-) (limited to 'Emby.Server.Implementations/ApplicationHost.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 6a324e0904..a39baa84ab 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 7742279f7a..d999f76dbf 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(-) (limited to 'Emby.Server.Implementations/ApplicationHost.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index a39baa84ab..7699faf14e 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 d999f76dbf..8537e41800 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 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(-) (limited to 'Emby.Server.Implementations/ApplicationHost.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 9655d9f5ee..edfed67a18 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 7aa238efac..1c586ffdd6 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 6ee6a1f932..e4cd6c34ad 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 9753f4e06d..f2c5aa1e39 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 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(-) (limited to 'Emby.Server.Implementations/ApplicationHost.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index edfed67a18..8f20a4921f 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 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 (limited to 'Emby.Server.Implementations/ApplicationHost.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 8b387e195a..6279ce5d0b 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 2648b57d57..e75140d6c0 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 5015937256..0000000000 --- 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 6880766f9a..0000000000 --- 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 b85750c9b9..0000000000 --- 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 31a7468fbc..0000000000 --- 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(-) (limited to 'Emby.Server.Implementations/ApplicationHost.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 6279ce5d0b..11fed24f7a 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -93,7 +93,6 @@ using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; using MediaBrowser.Model.Tasks; -using MediaBrowser.Model.Updates; using MediaBrowser.Providers.Chapters; using MediaBrowser.Providers.Manager; using MediaBrowser.Providers.Plugins.TheTvdb; @@ -101,12 +100,10 @@ using MediaBrowser.Providers.Subtitles; using MediaBrowser.WebDashboard.Api; using MediaBrowser.XbmcMetadata.Providers; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; using Prometheus.DotNetRuntime; +using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace Emby.Server.Implementations { diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index e75140d6c0..7358c1a81d 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -28,12 +28,11 @@ using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Net.Http.Headers; using ServiceStack.Text.Jsv; namespace Emby.Server.Implementations.HttpServer { - public class HttpListenerHost : IHttpServer, IDisposable + public class HttpListenerHost : IHttpServer { /// /// The key for a setting that specifies the default redirect path @@ -699,28 +698,6 @@ namespace Emby.Server.Implementations.HttpServer return _baseUrlPrefix + NormalizeUrlPath(path); } - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (disposing) - { - // TODO: - } - - _disposed = true; - } - /// /// Processes the web socket message received. /// diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 1af748ebc2..095725c504 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -36,8 +36,6 @@ namespace Emby.Server.Implementations.HttpServer /// private readonly WebSocket _socket; - private bool _disposed = false; - /// /// Initializes a new instance of the class. /// @@ -221,12 +219,6 @@ namespace Emby.Server.Implementations.HttpServer }; await OnReceive(info).ConfigureAwait(false); - - // Stop reading if there's no more data coming - if (result.IsCompleted) - { - return; - } } } } diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs index c7ef9b1cec..a0274acd20 100644 --- a/Emby.Server.Implementations/Session/WebSocketController.cs +++ b/Emby.Server.Implementations/Session/WebSocketController.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.Session private readonly ISessionManager _sessionManager; private readonly SessionInfo _session; - private List _sockets; + private readonly List _sockets; private bool _disposed = false; public WebSocketController( diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs index 666ac1cfef..f1c4417613 100644 --- a/MediaBrowser.Controller/Net/IHttpServer.cs +++ b/MediaBrowser.Controller/Net/IHttpServer.cs @@ -2,15 +2,14 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using MediaBrowser.Model.Events; -using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net { /// - /// Interface IHttpServer + /// Interface IHttpServer. /// - public interface IHttpServer : IDisposable + public interface IHttpServer { /// /// Gets the URL prefix. -- cgit v1.2.3 From 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 (limited to 'Emby.Server.Implementations/ApplicationHost.cs') diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 4685a03ac3..54894fd65b 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 81bebae3d2..0000000000 --- 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 22796ba3f8..0000000000 --- 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 ffc916b980..ddd9c79533 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 44fc932e39..dccbe2a9a6 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 fd488ce7d7..0000000000 --- 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 0000000000..6338389913 --- /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 0000000000..5388b921d8 --- /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 73ea593b0b..8eae366bab 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 0000000000..d7bbf793c4 --- /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 0000000000..a31f28f64a --- /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 0000000000..9c1a23877b --- /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 0000000000..8fdeab0887 --- /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 0000000000..3fb0fd51e1 --- /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 0000000000..f6f2f1a81b --- /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 0000000000..72a4a8c3b6 --- /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 0000000000..8cdd101af4 --- /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 88114d9994..4194070aa9 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 b5ea04dcac..82e3045862 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 0000000000..9f1f5b92eb --- /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 a54640b2fd..997b1c45a8 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 f95fa7ca0b..0a5fc9433b 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 80f01b66ee..5ab904394e 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 f336f5272c..6742dc8fc4 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 66144ec478..0000000000 --- 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 b41d0af1d1..5c6e313e07 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 a1dbe80476..6d01b0dcde 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 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(-) (limited to 'Emby.Server.Implementations/ApplicationHost.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 8f20a4921f..2201c3cfc2 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 96096e142a..ccb733b3ca 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 900f12062f..3efe1ee253 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 03ee5bfb65..82b1f3cf1f 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 d63588bbd1..083fcd0299 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 8537e41800..0028bd6893 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(-) (limited to 'Emby.Server.Implementations/ApplicationHost.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 10a7e879e9..693b049cd8 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 ccb733b3ca..7f7c6a0be4 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 0028bd6893..4f0ff1ee12 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 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(-) (limited to 'Emby.Server.Implementations/ApplicationHost.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ffc916b980..b6e75b3862 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 85b1b6e323..6c9ba7c272 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 681db4896e..5f5cd0e928 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 f5dda79db3..f7c9c736e3 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 8adf9c6216..0b53342359 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 c4d44042b1..f3c0441e12 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 37160dd2c0..f0328e8d87 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 3aadda5d07..64a75955a2 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 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 (limited to 'Emby.Server.Implementations/ApplicationHost.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 371b5a5b9b..8e5c3c9cf2 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 a6eaf2d0a3..305e67e8c3 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 dccbe2a9a6..896e4310e7 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 df3026a770..8fbf6eaabf 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 8eae366bab..b2a3f7eb34 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 531b529dce..0b398b60cd 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 a31f28f64a..149ca50209 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 6fc8d251b8..23714b24a1 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 8fdeab0887..eab531d386 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 e1ee9b34aa..0000000000 --- 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 42fac865ce..0000000000 --- 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 0000000000..98a83b7450 --- /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 0000000000..5e0b454d8b --- /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 23a0fdc784..a1b58eb5a9 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 27f5fe24b0..1e7ffd2359 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 f678e714c1..331a32c737 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 4194070aa9..9eec6ed4eb 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 f2c37d7117..a6bacad4fc 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 6742dc8fc4..9dab5e77b7 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 22a42322a6..1f5981f101 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 0000000000..8b77fd7fc3 --- /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 02570650e9..0000000000 --- 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(-) (limited to 'Emby.Server.Implementations/ApplicationHost.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 272a9355a8..e6410f8570 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