diff options
84 files changed, 1171 insertions, 647 deletions
diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index c829da98a..13cc67528 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -200,8 +200,8 @@ jobs: persistCredentials: true - task: CmdLine@2 - displayName: "Check out web" - condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion')) + displayName: "Check out web (master, release or tag)" + condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master'), contains(variables['Build.SourceBranch'], 'tag')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion')) inputs: script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web' diff --git a/Emby.Dlna/Api/DlnaServerService.cs b/Emby.Dlna/Api/DlnaServerService.cs index 1f137e620..7ddcaf7e6 100644 --- a/Emby.Dlna/Api/DlnaServerService.cs +++ b/Emby.Dlna/Api/DlnaServerService.cs @@ -214,11 +214,13 @@ namespace Emby.Dlna.Api string baseUrl = _configurationManager.Configuration.BaseUrl; // backwards compatibility - if (baseUrl.Length == 0 - && (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase) - || string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase))) + if (baseUrl.Length == 0) { - index++; + if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase) + || string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase)) + { + index++; + } } else if (string.Equals(first, baseUrl.Remove(0, 1))) { @@ -234,7 +236,7 @@ namespace Emby.Dlna.Api return pathInfo[index]; } - private List<string> Parse(string pathUri) + private static string[] Parse(string pathUri) { var actionParts = pathUri.Split(new[] { "://" }, StringSplitOptions.None); @@ -248,7 +250,7 @@ namespace Emby.Dlna.Api var args = pathInfo.Split('/'); - return args.Skip(1).ToList(); + return args.Skip(1).ToArray(); } public object Get(GetIcon request) diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs index 0b7c35f3e..67bb25b07 100644 --- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs +++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Threading; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; @@ -314,8 +313,7 @@ namespace Emby.Server.Implementations.AppBase throw new ArgumentException("Expected configuration type is " + configurationType.Name); } - var validatingStore = configurationStore as IValidatingConfiguration; - if (validatingStore != null) + if (configurationStore is IValidatingConfiguration validatingStore) { var currentConfiguration = GetConfiguration(key); diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 1bacd8aef..8c625539a 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -88,7 +88,6 @@ using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Diagnostics; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Events; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; @@ -110,9 +109,8 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; -using ServiceStack; +using Microsoft.OpenApi.Models; using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace Emby.Server.Implementations @@ -232,7 +230,25 @@ namespace Emby.Server.Implementations } } - protected IServiceProvider _serviceProvider; + /// <summary> + /// Gets or sets the service provider. + /// </summary> + public IServiceProvider ServiceProvider { get; set; } + + /// <summary> + /// Gets the http port for the webhost. + /// </summary> + public int HttpPort { get; private set; } + + /// <summary> + /// Gets the https port for the webhost. + /// </summary> + public int HttpsPort { get; private set; } + + /// <summary> + /// Gets the content root for the webhost. + /// </summary> + public string ContentRoot { get; private set; } /// <summary> /// Gets the server configuration manager. @@ -461,7 +477,7 @@ namespace Emby.Server.Implementations /// <param name="type">The type.</param> /// <returns>System.Object.</returns> public object CreateInstance(Type type) - => ActivatorUtilities.CreateInstance(_serviceProvider, type); + => ActivatorUtilities.CreateInstance(ServiceProvider, type); /// <summary> /// Creates an instance of type and resolves all constructor dependencies. @@ -469,7 +485,7 @@ namespace Emby.Server.Implementations /// /// <typeparam name="T">The type.</typeparam> /// <returns>T.</returns> public T CreateInstance<T>() - => ActivatorUtilities.CreateInstance<T>(_serviceProvider); + => ActivatorUtilities.CreateInstance<T>(ServiceProvider); /// <summary> /// Creates the instance safe. @@ -481,7 +497,7 @@ namespace Emby.Server.Implementations try { Logger.LogDebug("Creating instance of {Type}", type); - return ActivatorUtilities.CreateInstance(_serviceProvider, type); + return ActivatorUtilities.CreateInstance(ServiceProvider, type); } catch (Exception ex) { @@ -495,7 +511,7 @@ namespace Emby.Server.Implementations /// </summary> /// <typeparam name="T">The type</typeparam> /// <returns>``0.</returns> - public T Resolve<T>() => _serviceProvider.GetService<T>(); + public T Resolve<T>() => ServiceProvider.GetService<T>(); /// <summary> /// Gets the export types. @@ -612,77 +628,14 @@ namespace Emby.Server.Implementations await RegisterResources(serviceCollection).ConfigureAwait(false); - FindParts(); - - string contentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; - if (string.IsNullOrEmpty(contentRoot)) - { - contentRoot = ServerConfigurationManager.ApplicationPaths.WebPath; - } - - var host = new WebHostBuilder() - .UseKestrel(options => - { - var addresses = ServerConfigurationManager - .Configuration - .LocalNetworkAddresses - .Select(NormalizeConfiguredLocalAddress) - .Where(i => i != null) - .ToList(); - if (addresses.Any()) - { - foreach (var address in addresses) - { - Logger.LogInformation("Kestrel listening on {ipaddr}", address); - options.Listen(address, HttpPort); - - if (EnableHttps && Certificate != null) - { - options.Listen(address, HttpsPort, listenOptions => listenOptions.UseHttps(Certificate)); - } - } - } - else - { - Logger.LogInformation("Kestrel listening on all interfaces"); - options.ListenAnyIP(HttpPort); - - if (EnableHttps && Certificate != null) - { - options.ListenAnyIP(HttpsPort, listenOptions => listenOptions.UseHttps(Certificate)); - } - } - }) - .UseContentRoot(contentRoot) - .ConfigureServices(services => - { - services.AddResponseCompression(); - services.AddHttpContextAccessor(); - }) - .Configure(app => - { - app.UseWebSockets(); - - app.UseResponseCompression(); - - // TODO app.UseMiddleware<WebSocketMiddleware>(); - app.Use(ExecuteWebsocketHandlerAsync); - app.Use(ExecuteHttpHandlerAsync); - }) - .Build(); - - try - { - await host.StartAsync().ConfigureAwait(false); - } - catch + ContentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; + if (string.IsNullOrEmpty(ContentRoot)) { - Logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in system.xml and try again."); - throw; + ContentRoot = ServerConfigurationManager.ApplicationPaths.WebPath; } } - private async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next) + public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next) { if (!context.WebSockets.IsWebSocketRequest) { @@ -693,7 +646,7 @@ namespace Emby.Server.Implementations await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false); } - private async Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next) + public async Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next) { if (context.WebSockets.IsWebSocketRequest) { @@ -911,7 +864,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton<IAuthorizationContext>(authContext); serviceCollection.AddSingleton<ISessionContext>(new SessionContext(UserManager, authContext, SessionManager)); - AuthService = new AuthService(authContext, ServerConfigurationManager, SessionManager, NetworkManager); + AuthService = new AuthService(LoggerFactory.CreateLogger<AuthService>(), authContext, ServerConfigurationManager, SessionManager, NetworkManager); serviceCollection.AddSingleton(AuthService); SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory); @@ -930,8 +883,6 @@ namespace Emby.Server.Implementations ((UserDataManager)UserDataManager).Repository = userDataRepo; ItemRepository.Initialize(userDataRepo, UserManager); ((LibraryManager)LibraryManager).ItemRepository = ItemRepository; - - _serviceProvider = serviceCollection.BuildServiceProvider(); } public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths) @@ -1088,9 +1039,9 @@ namespace Emby.Server.Implementations /// <summary> /// Finds the parts. /// </summary> - protected void FindParts() + public void FindParts() { - InstallationManager = _serviceProvider.GetService<IInstallationManager>(); + InstallationManager = ServiceProvider.GetService<IInstallationManager>(); InstallationManager.PluginInstalled += PluginInstalled; if (!ServerConfigurationManager.Configuration.IsPortAuthorized) @@ -1219,7 +1170,7 @@ namespace Emby.Server.Implementations private CertificateInfo CertificateInfo { get; set; } - protected X509Certificate2 Certificate { get; private set; } + public X509Certificate2 Certificate { get; private set; } private IEnumerable<string> GetUrlPrefixes() { @@ -1435,6 +1386,7 @@ namespace Emby.Server.Implementations public async Task<SystemInfo> GetSystemInfo(CancellationToken cancellationToken) { var localAddress = await GetLocalApiUrl(cancellationToken).ConfigureAwait(false); + var transcodingTempPath = ConfigurationManager.GetTranscodePath(); return new SystemInfo { @@ -1458,7 +1410,7 @@ namespace Emby.Server.Implementations CanSelfRestart = CanSelfRestart, CanLaunchWebBrowser = CanLaunchWebBrowser, HasUpdateAvailable = HasUpdateAvailable, - TranscodingTempPath = ApplicationPaths.TranscodePath, + TranscodingTempPath = transcodingTempPath, ServerName = FriendlyName, LocalAddress = localAddress, SupportsLibraryMonitor = true, @@ -1603,7 +1555,7 @@ namespace Emby.Server.Implementations return resultList; } - private IPAddress NormalizeConfiguredLocalAddress(string address) + public IPAddress NormalizeConfiguredLocalAddress(string address) { var index = address.Trim('/').IndexOf('/'); @@ -1679,10 +1631,6 @@ namespace Emby.Server.Implementations ? Environment.MachineName : ServerConfigurationManager.Configuration.ServerName; - public int HttpPort { get; private set; } - - public int HttpsPort { get; private set; } - /// <summary> /// Shuts down. /// </summary> diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs index fe705cbe2..2291345be 100644 --- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using Emby.Server.Implementations.AppBase; using MediaBrowser.Common.Configuration; @@ -14,7 +13,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Configuration { /// <summary> - /// Class ServerConfigurationManager + /// Class ServerConfigurationManager. /// </summary> public class ServerConfigurationManager : BaseConfigurationManager, IServerConfigurationManager { @@ -62,13 +61,6 @@ namespace Emby.Server.Implementations.Configuration base.OnConfigurationUpdated(); } - public override void AddParts(IEnumerable<IConfigurationFactory> factories) - { - base.AddParts(factories); - - UpdateTranscodePath(); - } - /// <summary> /// Updates the metadata path. /// </summary> @@ -85,28 +77,6 @@ namespace Emby.Server.Implementations.Configuration } /// <summary> - /// Updates the transcoding temporary path. - /// </summary> - private void UpdateTranscodePath() - { - var encodingConfig = this.GetConfiguration<EncodingOptions>("encoding"); - - ((ServerApplicationPaths)ApplicationPaths).TranscodePath = string.IsNullOrEmpty(encodingConfig.TranscodingTempPath) ? - null : - Path.Combine(encodingConfig.TranscodingTempPath, "transcodes"); - } - - protected override void OnNamedConfigurationUpdated(string key, object configuration) - { - base.OnNamedConfigurationUpdated(key, configuration); - - if (string.Equals(key, "encoding", StringComparison.OrdinalIgnoreCase)) - { - UpdateTranscodePath(); - } - } - - /// <summary> /// Replaces the configuration. /// </summary> /// <param name="newConfiguration">The new configuration.</param> @@ -123,7 +93,6 @@ namespace Emby.Server.Implementations.Configuration base.ReplaceConfiguration(newConfiguration); } - /// <summary> /// Validates the SSL certificate. /// </summary> diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 385664737..fde4d7059 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -1,8 +1,9 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <ItemGroup> <ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" /> <ProjectReference Include="..\Emby.Notifications\Emby.Notifications.csproj" /> + <ProjectReference Include="..\Jellyfin.Api\Jellyfin.Api.csproj" /> <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" /> <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" /> <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" /> diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index dc1a56e27..6dd016f8a 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -18,7 +18,6 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Internal; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -164,7 +163,7 @@ namespace Emby.Server.Implementations.HttpServer { OnReceive = ProcessWebSocketMessageReceived, Url = e.Url, - QueryString = e.QueryString ?? new QueryCollection() + QueryString = e.QueryString }; connection.Closed += OnConnectionClosed; diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 93a61fe67..594f46498 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Emby.Server.Implementations.SocketSharp; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -7,22 +8,27 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.HttpServer.Security { public class AuthService : IAuthService { + private readonly ILogger<AuthService> _logger; private readonly IAuthorizationContext _authorizationContext; private readonly ISessionManager _sessionManager; private readonly IServerConfigurationManager _config; private readonly INetworkManager _networkManager; public AuthService( + ILogger<AuthService> logger, IAuthorizationContext authorizationContext, IServerConfigurationManager config, ISessionManager sessionManager, INetworkManager networkManager) { + _logger = logger; _authorizationContext = authorizationContext; _config = config; _sessionManager = sessionManager; @@ -34,7 +40,14 @@ namespace Emby.Server.Implementations.HttpServer.Security ValidateUser(request, authAttribtues); } - private void ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues) + public User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes) + { + var req = new WebSocketSharpRequest(request, null, request.Path, _logger); + var user = ValidateUser(req, authAttributes); + return user; + } + + private User ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues) { // This code is executed before the service var auth = _authorizationContext.GetAuthorizationInfo(request); @@ -81,6 +94,8 @@ namespace Emby.Server.Implementations.HttpServer.Security request.RemoteIp, user); } + + return user; } private void ValidateUserAccess( diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index f83456660..06f27fa3e 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -582,11 +582,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun modelInfo.TunerCount, FileSystem, Logger, - Config.ApplicationPaths, + Config, _appHost, _networkManager, _streamHelper); - } var enableHttpStream = true; @@ -611,7 +610,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun FileSystem, _httpClient, Logger, - Config.ApplicationPaths, + Config, _appHost, _streamHelper); } @@ -624,7 +623,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun modelInfo.TunerCount, FileSystem, Logger, - Config.ApplicationPaths, + Config, _appHost, _networkManager, _streamHelper); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index eafa86d54..649becbd3 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -6,6 +6,7 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Net; +using MediaBrowser.Common.Configuration; using MediaBrowser.Controller; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; @@ -33,11 +34,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun int numTuners, IFileSystem fileSystem, ILogger logger, - IServerApplicationPaths appPaths, + IConfigurationManager configurationManager, IServerApplicationHost appHost, INetworkManager networkManager, IStreamHelper streamHelper) - : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths, streamHelper) + : base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper) { _appHost = appHost; _networkManager = networkManager; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index 2ea171031..1d55e7992 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -5,8 +5,8 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Controller; using MediaBrowser.Controller.Library; +using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.IO; using MediaBrowser.Model.LiveTv; @@ -16,8 +16,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { public class LiveStream : ILiveStream { + private readonly IConfigurationManager _configurationManager; + protected readonly IFileSystem FileSystem; - protected readonly IServerApplicationPaths AppPaths; + protected readonly IStreamHelper StreamHelper; protected string TempFilePath; @@ -29,7 +31,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts TunerHostInfo tuner, IFileSystem fileSystem, ILogger logger, - IServerApplicationPaths appPaths, + IConfigurationManager configurationManager, IStreamHelper streamHelper) { OriginalMediaSource = mediaSource; @@ -44,7 +46,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts TunerHostId = tuner.Id; } - AppPaths = appPaths; + _configurationManager = configurationManager; StreamHelper = streamHelper; ConsumerCount = 1; @@ -68,7 +70,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts protected void SetTempFilePath(string extension) { - TempFilePath = Path.Combine(AppPaths.GetTranscodePath(), UniqueId + "." + extension); + TempFilePath = Path.Combine(_configurationManager.GetTranscodePath(), UniqueId + "." + extension); } public virtual Task Open(CancellationToken openCancellationToken) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index a02a9ade4..df054f1eb 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -114,11 +114,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) { - return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _streamHelper); + return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config, _appHost, _streamHelper); } } - return new LiveStream(mediaSource, info, FileSystem, Logger, Config.ApplicationPaths, _streamHelper); + return new LiveStream(mediaSource, info, FileSystem, Logger, Config, _streamHelper); } public async Task Validate(TunerHostInfo info) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index c6e894560..758495362 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Library; @@ -26,10 +27,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, - IServerApplicationPaths appPaths, + IConfigurationManager configurationManager, IServerApplicationHost appHost, IStreamHelper streamHelper) - : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths, streamHelper) + : base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper) { _httpClient = httpClient; _appHost = appHost; diff --git a/Emby.Server.Implementations/Localization/Core/af.json b/Emby.Server.Implementations/Localization/Core/af.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/af.json @@ -0,0 +1 @@ +{} diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json index 0db201769..12bc16d74 100644 --- a/Emby.Server.Implementations/Localization/Core/de.json +++ b/Emby.Server.Implementations/Localization/Core/de.json @@ -23,7 +23,7 @@ "HeaderFavoriteEpisodes": "Lieblingsepisoden", "HeaderFavoriteShows": "Lieblingsserien", "HeaderFavoriteSongs": "Lieblingslieder", - "HeaderLiveTV": "Live-TV", + "HeaderLiveTV": "Live TV", "HeaderNextUp": "Als Nächstes", "HeaderRecordingGroups": "Aufnahme-Gruppen", "HomeVideos": "Heimvideos", @@ -35,7 +35,7 @@ "Latest": "Neueste", "MessageApplicationUpdated": "Jellyfin-Server wurde aktualisiert", "MessageApplicationUpdatedTo": "Jellyfin-Server wurde auf Version {0} aktualisiert", - "MessageNamedServerConfigurationUpdatedWithValue": "Der Server Einstellungsbereich {0} wurde aktualisiert", + "MessageNamedServerConfigurationUpdatedWithValue": "Der Server-Einstellungsbereich {0} wurde aktualisiert", "MessageServerConfigurationUpdated": "Servereinstellungen wurden aktualisiert", "MixedContent": "Gemischte Inhalte", "Movies": "Filme", diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json index aa855ed21..dbbf5b27b 100644 --- a/Emby.Server.Implementations/Localization/Core/en-US.json +++ b/Emby.Server.Implementations/Localization/Core/en-US.json @@ -8,9 +8,9 @@ "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", "Channels": "Channels", "ChapterNameValue": "Chapter {0}", - "Collections": "Collections", - "DeviceOfflineWithName": "{0} has disconnected", - "DeviceOnlineWithName": "{0} is connected", + "Collections": "Versamelings", + "DeviceOfflineWithName": "{0} het afgesluit", + "DeviceOnlineWithName": "{0} is verbind", "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", "Favorites": "Favorites", "Folders": "Folders", diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json index 3b5ce29c7..eb1c2623f 100644 --- a/Emby.Server.Implementations/Localization/Core/tr.json +++ b/Emby.Server.Implementations/Localization/Core/tr.json @@ -25,73 +25,73 @@ "HeaderFavoriteSongs": "Favori Şarkılar", "HeaderLiveTV": "Canlı TV", "HeaderNextUp": "Sonraki hafta", - "HeaderRecordingGroups": "Recording Groups", - "HomeVideos": "Home videos", - "Inherit": "Inherit", - "ItemAddedWithName": "{0} was added to the library", - "ItemRemovedWithName": "{0} was removed from the library", - "LabelIpAddressValue": "Ip adresi: {0}", + "HeaderRecordingGroups": "Kayıt Grupları", + "HomeVideos": "Ev videoları", + "Inherit": "Devral", + "ItemAddedWithName": "{0} kütüphaneye eklendi", + "ItemRemovedWithName": "{0} kütüphaneden silindi", + "LabelIpAddressValue": "IP adresi: {0}", "LabelRunningTimeValue": "Çalışma süresi: {0}", - "Latest": "Latest", + "Latest": "En son", "MessageApplicationUpdated": "Jellyfin Sunucusu güncellendi", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", - "MessageServerConfigurationUpdated": "Server configuration has been updated", - "MixedContent": "Mixed content", + "MessageApplicationUpdatedTo": "Jellyfin Sunucusu {0} olarak güncellendi", + "MessageNamedServerConfigurationUpdatedWithValue": "Sunucu ayarları kısım {0} güncellendi", + "MessageServerConfigurationUpdated": "Sunucu ayarları güncellendi", + "MixedContent": "Karışık içerik", "Movies": "Filmler", "Music": "Müzik", "MusicVideos": "Müzik videoları", - "NameInstallFailed": "{0} kurulum başarısız", + "NameInstallFailed": "{0} kurulumu başarısız", "NameSeasonNumber": "Sezon {0}", "NameSeasonUnknown": "Bilinmeyen Sezon", "NewVersionIsAvailable": "Jellyfin Sunucusunun yeni bir versiyonu indirmek için hazır.", - "NotificationOptionApplicationUpdateAvailable": "Application update available", - "NotificationOptionApplicationUpdateInstalled": "Application update installed", - "NotificationOptionAudioPlayback": "Audio playback started", - "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", - "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionInstallationFailed": "Yükleme hatası", - "NotificationOptionNewLibraryContent": "New content added", - "NotificationOptionPluginError": "Plugin failure", - "NotificationOptionPluginInstalled": "Plugin installed", - "NotificationOptionPluginUninstalled": "Plugin uninstalled", - "NotificationOptionPluginUpdateInstalled": "Plugin update installed", - "NotificationOptionServerRestartRequired": "Server restart required", - "NotificationOptionTaskFailed": "Scheduled task failure", - "NotificationOptionUserLockedOut": "User locked out", - "NotificationOptionVideoPlayback": "Video playback started", - "NotificationOptionVideoPlaybackStopped": "Video playback stopped", + "NotificationOptionApplicationUpdateAvailable": "Uygulama güncellemesi mevcut", + "NotificationOptionApplicationUpdateInstalled": "Uygulama güncellemesi yüklendi", + "NotificationOptionAudioPlayback": "Ses çalma başladı", + "NotificationOptionAudioPlaybackStopped": "Ses çalma durduruldu", + "NotificationOptionCameraImageUploaded": "Kamera fotoğrafı yüklendi", + "NotificationOptionInstallationFailed": "Yükleme başarısız oldu", + "NotificationOptionNewLibraryContent": "Yeni içerik eklendi", + "NotificationOptionPluginError": "Eklenti hatası", + "NotificationOptionPluginInstalled": "Eklenti yüklendi", + "NotificationOptionPluginUninstalled": "Eklenti kaldırıldı", + "NotificationOptionPluginUpdateInstalled": "Eklenti güncellemesi yüklendi", + "NotificationOptionServerRestartRequired": "Sunucu yeniden başlatma gerekli", + "NotificationOptionTaskFailed": "Zamanlanmış görev hatası", + "NotificationOptionUserLockedOut": "Kullanıcı kitlendi", + "NotificationOptionVideoPlayback": "Video oynatma başladı", + "NotificationOptionVideoPlaybackStopped": "Video oynatma durduruldu", "Photos": "Fotoğraflar", "Playlists": "Çalma listeleri", - "Plugin": "Plugin", - "PluginInstalledWithName": "{0} was installed", - "PluginUninstalledWithName": "{0} was uninstalled", - "PluginUpdatedWithName": "{0} was updated", - "ProviderValue": "Provider: {0}", - "ScheduledTaskFailedWithName": "{0} failed", - "ScheduledTaskStartedWithName": "{0} started", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", + "Plugin": "Eklenti", + "PluginInstalledWithName": "{0} yüklendi", + "PluginUninstalledWithName": "{0} kaldırıldı", + "PluginUpdatedWithName": "{0} güncellendi", + "ProviderValue": "Sağlayıcı: {0}", + "ScheduledTaskFailedWithName": "{0} başarısız oldu", + "ScheduledTaskStartedWithName": "{0} başladı", + "ServerNameNeedsToBeRestarted": "{0} yeniden başlatılması gerekiyor", "Shows": "Diziler", "Songs": "Şarkılar", - "StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.", + "StartupEmbyServerIsLoading": "Jellyfin Sunucusu yükleniyor. Lütfen kısa süre sonra tekrar deneyin.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", - "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", + "SubtitleDownloadFailureFromForItem": "{1} için alt yazılar {0} 'dan indirilemedi", + "SubtitlesDownloadedForItem": "{0} için altyazılar indirildi", "Sync": "Eşitle", - "System": "System", - "TvShows": "TV Shows", - "User": "User", - "UserCreatedWithName": "User {0} has been created", - "UserDeletedWithName": "User {0} has been deleted", - "UserDownloadingItemWithValues": "{0} is downloading {1}", - "UserLockedOutWithName": "User {0} has been locked out", - "UserOfflineFromDevice": "{0} has disconnected from {1}", - "UserOnlineFromDevice": "{0} is online from {1}", - "UserPasswordChangedWithName": "Password has been changed for user {0}", - "UserPolicyUpdatedWithName": "User policy has been updated for {0}", - "UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}", - "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", - "ValueSpecialEpisodeName": "Özel -{0}", - "VersionNumber": "Version {0}" + "System": "Sistem", + "TvShows": "Diziler", + "User": "Kullanıcı", + "UserCreatedWithName": "{0} kullanıcısı oluşturuldu", + "UserDeletedWithName": "Kullanıcı {0} silindi", + "UserDownloadingItemWithValues": "{0} indiriliyor {1}", + "UserLockedOutWithName": "Kullanıcı {0} kitlendi", + "UserOfflineFromDevice": "{0}, {1} ile bağlantısı kesildi", + "UserOnlineFromDevice": "{0}, {1} çevrimiçi", + "UserPasswordChangedWithName": "{0} kullanıcısı için şifre değiştirildi", + "UserPolicyUpdatedWithName": "Kullanıcı politikası {0} için güncellendi", + "UserStartedPlayingItemWithValues": "{0}, {2} cihazında {1} izliyor", + "UserStoppedPlayingItemWithValues": "{0}, {2} cihazında {1} izlemeyi bitirdi", + "ValueHasBeenAddedToLibrary": "Medya kitaplığınıza {0} eklendi", + "ValueSpecialEpisodeName": "Özel - {0}", + "VersionNumber": "Versiyon {0}" } diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json index 387fc2b92..33fcb2d37 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-HK.json +++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json @@ -2,7 +2,7 @@ "Albums": "Albums", "AppDeviceValues": "App: {0}, Device: {1}", "Application": "Application", - "Artists": "Artists", + "Artists": "藝人", "AuthenticationSucceededWithUserName": "{0} successfully authenticated", "Books": "Books", "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json index 293fc28a8..33bdbfb98 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-TW.json +++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json @@ -1,6 +1,6 @@ { "Albums": "專輯", - "AppDeviceValues": "應用: {0}, 裝置: {1}", + "AppDeviceValues": "軟體: {0}, 裝置: {1}", "Application": "應用程式", "Artists": "演出者", "AuthenticationSucceededWithUserName": "{0} 成功授權", diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index 0b3567986..1d8d3cf39 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -7,8 +7,6 @@ using System.Net.NetworkInformation; using System.Net.Sockets; using System.Threading.Tasks; using MediaBrowser.Common.Net; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Net; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Networking @@ -55,10 +53,7 @@ namespace Emby.Server.Implementations.Networking _macAddresses = null; } - if (NetworkChanged != null) - { - NetworkChanged(this, EventArgs.Empty); - } + NetworkChanged?.Invoke(this, EventArgs.Empty); } public IPAddress[] GetLocalIpAddresses(bool ignoreVirtualInterface = true) @@ -261,10 +256,10 @@ namespace Emby.Server.Implementations.Networking return true; } - if (normalizedSubnet.IndexOf('/') != -1) + if (normalizedSubnet.Contains('/', StringComparison.Ordinal)) { - var ipnetwork = IPNetwork.Parse(normalizedSubnet); - if (ipnetwork.Contains(address)) + var ipNetwork = IPNetwork.Parse(normalizedSubnet); + if (ipNetwork.Contains(address)) { return true; } @@ -455,9 +450,9 @@ namespace Emby.Server.Implementations.Networking public bool IsInSameSubnet(IPAddress address1, IPAddress address2, IPAddress subnetMask) { - IPAddress network1 = GetNetworkAddress(address1, subnetMask); - IPAddress network2 = GetNetworkAddress(address2, subnetMask); - return network1.Equals(network2); + IPAddress network1 = GetNetworkAddress(address1, subnetMask); + IPAddress network2 = GetNetworkAddress(address2, subnetMask); + return network1.Equals(network2); } private IPAddress GetNetworkAddress(IPAddress address, IPAddress subnetMask) @@ -473,7 +468,7 @@ namespace Emby.Server.Implementations.Networking byte[] broadcastAddress = new byte[ipAdressBytes.Length]; for (int i = 0; i < broadcastAddress.Length; i++) { - broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i])); + broadcastAddress[i] = (byte)(ipAdressBytes[i] & subnetMaskBytes[i]); } return new IPAddress(broadcastAddress); @@ -513,24 +508,5 @@ namespace Emby.Server.Implementations.Networking return null; } - - /// <summary> - /// Gets the network shares. - /// </summary> - /// <param name="path">The path.</param> - /// <returns>IEnumerable{NetworkShare}.</returns> - public virtual IEnumerable<NetworkShare> GetNetworkShares(string path) - { - return new List<NetworkShare>(); - } - - /// <summary> - /// Gets available devices within the domain - /// </summary> - /// <returns>PC's in the Domain</returns> - public virtual IEnumerable<FileSystemEntryInfo> GetNetworkDevices() - { - return new List<FileSystemEntryInfo>(); - } } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs index 200649ba9..72b524df0 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; using MediaBrowser.Model.IO; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; @@ -15,24 +16,18 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// </summary> public class DeleteTranscodeFileTask : IScheduledTask, IConfigurableScheduledTask { - /// <summary> - /// Gets or sets the application paths. - /// </summary> - /// <value>The application paths.</value> - private ServerApplicationPaths ApplicationPaths { get; set; } - private readonly ILogger _logger; - + private readonly IConfigurationManager _configurationManager; private readonly IFileSystem _fileSystem; /// <summary> /// Initializes a new instance of the <see cref="DeleteTranscodeFileTask" /> class. /// </summary> - public DeleteTranscodeFileTask(ServerApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem) + public DeleteTranscodeFileTask(ILogger logger, IFileSystem fileSystem, IConfigurationManager configurationManager) { - ApplicationPaths = appPaths; _logger = logger; _fileSystem = fileSystem; + _configurationManager = configurationManager; } /// <summary> @@ -52,14 +47,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks var minDateModified = DateTime.UtcNow.AddDays(-1); progress.Report(50); - try - { - DeleteTempFilesFromDirectory(cancellationToken, ApplicationPaths.TranscodePath, minDateModified, progress); - } - catch (DirectoryNotFoundException) - { - // No biggie here. Nothing to delete - } + DeleteTempFilesFromDirectory(cancellationToken, _configurationManager.GetTranscodePath(), minDateModified, progress); return Task.CompletedTask; } diff --git a/Emby.Server.Implementations/ServerApplicationPaths.cs b/Emby.Server.Implementations/ServerApplicationPaths.cs index 87de9804a..4b8298abb 100644 --- a/Emby.Server.Implementations/ServerApplicationPaths.cs +++ b/Emby.Server.Implementations/ServerApplicationPaths.cs @@ -107,40 +107,6 @@ namespace Emby.Server.Implementations /// <value>The user configuration directory path.</value> public string UserConfigurationDirectoryPath => Path.Combine(ConfigurationDirectoryPath, "users"); - public string DefaultTranscodePath => _defaultTranscodePath ?? (_defaultTranscodePath = Path.Combine(ProgramDataPath, "transcodes")); - - public string TranscodePath - { - get => _transcodePath ?? (_transcodePath = DefaultTranscodePath); - set => _transcodePath = value; - } - - public string GetTranscodePath() - { - var path = TranscodePath; - - if (!string.Equals(path, DefaultTranscodePath, StringComparison.OrdinalIgnoreCase)) - { - try - { - Directory.CreateDirectory(path); - - var testPath = Path.Combine(path, Guid.NewGuid().ToString()); - Directory.CreateDirectory(testPath); - Directory.Delete(testPath); - - return path; - } - catch - { - } - } - - path = DefaultTranscodePath; - Directory.CreateDirectory(path); - return path; - } - public string InternalMetadataPath { get => _internalMetadataPath ?? (_internalMetadataPath = Path.Combine(DataPath, "metadata")); diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 63ec75762..930f2d35d 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -4,7 +4,6 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Events; using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -67,7 +66,7 @@ namespace Emby.Server.Implementations.Session { if (queryString == null) { - throw new ArgumentNullException(nameof(queryString)); + return null; } var token = queryString["api_key"]; @@ -75,6 +74,7 @@ namespace Emby.Server.Implementations.Session { return null; } + var deviceId = queryString["deviceId"]; return _sessionManager.GetSessionByAuthenticationToken(token, deviceId, remoteEndpoint); } diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 690ba0be4..1781df8b5 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Net; +using System.Net.Mime; using MediaBrowser.Common.Net; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; @@ -14,9 +15,9 @@ namespace Emby.Server.Implementations.SocketSharp { public class WebSocketSharpRequest : IHttpRequest { - public const string FormUrlEncoded = "application/x-www-form-urlencoded"; - public const string MultiPartFormData = "multipart/form-data"; - public const string Soap11 = "text/xml; charset=utf-8"; + private const string FormUrlEncoded = "application/x-www-form-urlencoded"; + private const string MultiPartFormData = "multipart/form-data"; + private const string Soap11 = "text/xml; charset=utf-8"; private string _remoteIp; private Dictionary<string, object> _items; @@ -77,7 +78,7 @@ namespace Emby.Server.Implementations.SocketSharp get => _responseContentType ?? (_responseContentType = GetResponseContentType(Request)); - set => this._responseContentType = value; + set => _responseContentType = value; } public string PathInfo => Request.Path.Value; @@ -90,7 +91,6 @@ namespace Emby.Server.Implementations.SocketSharp public bool IsLocal => Request.HttpContext.Connection.LocalIpAddress.Equals(Request.HttpContext.Connection.RemoteIpAddress); - public string HttpMethod => Request.Method; public string Verb => HttpMethod; @@ -123,24 +123,29 @@ namespace Emby.Server.Implementations.SocketSharp return specifiedContentType; } - const string serverDefaultContentType = "application/json"; + const string ServerDefaultContentType = MediaTypeNames.Application.Json; var acceptContentTypes = httpReq.Headers.GetCommaSeparatedValues(HeaderNames.Accept); string defaultContentType = null; if (HasAnyOfContentTypes(httpReq, FormUrlEncoded, MultiPartFormData)) { - defaultContentType = serverDefaultContentType; + defaultContentType = ServerDefaultContentType; } var acceptsAnything = false; var hasDefaultContentType = defaultContentType != null; if (acceptContentTypes != null) { - foreach (var acceptsType in acceptContentTypes) + foreach (ReadOnlySpan<char> acceptsType in acceptContentTypes) { - // TODO: @bond move to Span when Span.Split lands - // https://github.com/dotnet/corefx/issues/26528 - var contentType = acceptsType?.Split(';')[0].Trim(); + ReadOnlySpan<char> contentType = acceptsType; + var index = contentType.IndexOf(';'); + if (index != -1) + { + contentType = contentType.Slice(0, index); + } + + contentType = contentType.Trim(); acceptsAnything = contentType.Equals("*/*", StringComparison.OrdinalIgnoreCase); if (acceptsAnything) @@ -157,7 +162,7 @@ namespace Emby.Server.Implementations.SocketSharp } else { - return serverDefaultContentType; + return ServerDefaultContentType; } } } @@ -168,7 +173,7 @@ namespace Emby.Server.Implementations.SocketSharp } // We could also send a '406 Not Acceptable', but this is allowed also - return serverDefaultContentType; + return ServerDefaultContentType; } public static bool HasAnyOfContentTypes(HttpRequest request, params string[] contentTypes) @@ -196,12 +201,12 @@ namespace Emby.Server.Implementations.SocketSharp private static string GetQueryStringContentType(HttpRequest httpReq) { - ReadOnlySpan<char> format = httpReq.Query["format"].ToString().AsSpan(); + ReadOnlySpan<char> format = httpReq.Query["format"].ToString(); if (format == null) { - const int formatMaxLength = 4; - ReadOnlySpan<char> pi = httpReq.Path.ToString().AsSpan(); - if (pi == null || pi.Length <= formatMaxLength) + const int FormatMaxLength = 4; + ReadOnlySpan<char> pi = httpReq.Path.ToString(); + if (pi == null || pi.Length <= FormatMaxLength) { return null; } @@ -212,18 +217,18 @@ namespace Emby.Server.Implementations.SocketSharp } format = LeftPart(pi, '/'); - if (format.Length > formatMaxLength) + if (format.Length > FormatMaxLength) { return null; } } format = LeftPart(format, '.'); - if (format.Contains("json".AsSpan(), StringComparison.OrdinalIgnoreCase)) + if (format.Contains("json", StringComparison.OrdinalIgnoreCase)) { return "application/json"; } - else if (format.Contains("xml".AsSpan(), StringComparison.OrdinalIgnoreCase)) + else if (format.Contains("xml", StringComparison.OrdinalIgnoreCase)) { return "application/xml"; } diff --git a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs new file mode 100644 index 000000000..26f7d9d2d --- /dev/null +++ b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs @@ -0,0 +1,68 @@ +using System.Security.Claims; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Jellyfin.Api.Constants; +using MediaBrowser.Controller.Net; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Jellyfin.Api.Auth +{ + /// <summary> + /// Custom authentication handler wrapping the legacy authentication. + /// </summary> + public class CustomAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions> + { + private readonly IAuthService _authService; + + /// <summary> + /// Initializes a new instance of the <see cref="CustomAuthenticationHandler" /> class. + /// </summary> + /// <param name="authService">The jellyfin authentication service.</param> + /// <param name="options">Options monitor.</param> + /// <param name="logger">The logger.</param> + /// <param name="encoder">The url encoder.</param> + /// <param name="clock">The system clock.</param> + public CustomAuthenticationHandler( + IAuthService authService, + IOptionsMonitor<AuthenticationSchemeOptions> options, + ILoggerFactory logger, + UrlEncoder encoder, + ISystemClock clock) : base(options, logger, encoder, clock) + { + _authService = authService; + } + + /// <inheritdoc /> + protected override Task<AuthenticateResult> HandleAuthenticateAsync() + { + var authenticatedAttribute = new AuthenticatedAttribute(); + try + { + var user = _authService.Authenticate(Request, authenticatedAttribute); + if (user == null) + { + return Task.FromResult(AuthenticateResult.Fail("Invalid user")); + } + + var claims = new[] + { + new Claim(ClaimTypes.Name, user.Name), + new Claim( + ClaimTypes.Role, + value: user.Policy.IsAdministrator ? UserRoles.Administrator : UserRoles.User) + }; + var identity = new ClaimsIdentity(claims, Scheme.Name); + var principal = new ClaimsPrincipal(identity); + var ticket = new AuthenticationTicket(principal, Scheme.Name); + + return Task.FromResult(AuthenticateResult.Success(ticket)); + } + catch (SecurityException ex) + { + return Task.FromResult(AuthenticateResult.Fail(ex)); + } + } + } +} diff --git a/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs b/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs new file mode 100644 index 000000000..34aa5d12c --- /dev/null +++ b/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs @@ -0,0 +1,43 @@ +using System.Threading.Tasks; +using Jellyfin.Api.Constants; +using MediaBrowser.Common.Configuration; +using Microsoft.AspNetCore.Authorization; + +namespace Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy +{ + /// <summary> + /// Authorization handler for requiring first time setup or elevated privileges. + /// </summary> + public class FirstTimeSetupOrElevatedHandler : AuthorizationHandler<FirstTimeSetupOrElevatedRequirement> + { + private readonly IConfigurationManager _configurationManager; + + /// <summary> + /// Initializes a new instance of the <see cref="FirstTimeSetupOrElevatedHandler" /> class. + /// </summary> + /// <param name="configurationManager">The jellyfin configuration manager.</param> + public FirstTimeSetupOrElevatedHandler(IConfigurationManager configurationManager) + { + _configurationManager = configurationManager; + } + + /// <inheritdoc /> + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, FirstTimeSetupOrElevatedRequirement firstTimeSetupOrElevatedRequirement) + { + if (!_configurationManager.CommonConfiguration.IsStartupWizardCompleted) + { + context.Succeed(firstTimeSetupOrElevatedRequirement); + } + else if (context.User.IsInRole(UserRoles.Administrator)) + { + context.Succeed(firstTimeSetupOrElevatedRequirement); + } + else + { + context.Fail(); + } + + return Task.CompletedTask; + } + } +} diff --git a/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedRequirement.cs b/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedRequirement.cs new file mode 100644 index 000000000..51ba637b6 --- /dev/null +++ b/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedRequirement.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy +{ + /// <summary> + /// The authorization requirement, requiring incomplete first time setup or elevated privileges, for the authorization handler. + /// </summary> + public class FirstTimeSetupOrElevatedRequirement : IAuthorizationRequirement + { + } +} diff --git a/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs b/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs new file mode 100644 index 000000000..2d3bb1aa4 --- /dev/null +++ b/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; +using Jellyfin.Api.Constants; +using Microsoft.AspNetCore.Authorization; + +namespace Jellyfin.Api.Auth.RequiresElevationPolicy +{ + /// <summary> + /// Authorization handler for requiring elevated privileges. + /// </summary> + public class RequiresElevationHandler : AuthorizationHandler<RequiresElevationRequirement> + { + /// <inheritdoc /> + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RequiresElevationRequirement requirement) + { + if (context.User.IsInRole(UserRoles.Administrator)) + { + context.Succeed(requirement); + } + + return Task.CompletedTask; + } + } +} diff --git a/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationRequirement.cs b/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationRequirement.cs new file mode 100644 index 000000000..cfff1cc0c --- /dev/null +++ b/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationRequirement.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Jellyfin.Api.Auth.RequiresElevationPolicy +{ + /// <summary> + /// The authorization requirement for requiring elevated privileges in the authorization handler. + /// </summary> + public class RequiresElevationRequirement : IAuthorizationRequirement + { + } +} diff --git a/Jellyfin.Api/BaseJellyfinApiController.cs b/Jellyfin.Api/BaseJellyfinApiController.cs new file mode 100644 index 000000000..1f4508e6c --- /dev/null +++ b/Jellyfin.Api/BaseJellyfinApiController.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api +{ + /// <summary> + /// Base api controller for the API setting a default route. + /// </summary> + [ApiController] + [Route("[controller]")] + public class BaseJellyfinApiController : ControllerBase + { + } +} diff --git a/Jellyfin.Api/Constants/AuthenticationSchemes.cs b/Jellyfin.Api/Constants/AuthenticationSchemes.cs new file mode 100644 index 000000000..bac3379e7 --- /dev/null +++ b/Jellyfin.Api/Constants/AuthenticationSchemes.cs @@ -0,0 +1,13 @@ +namespace Jellyfin.Api.Constants +{ + /// <summary> + /// Authentication schemes for user authentication in the API. + /// </summary> + public static class AuthenticationSchemes + { + /// <summary> + /// Scheme name for the custom legacy authentication. + /// </summary> + public const string CustomAuthentication = "CustomAuthentication"; + } +} diff --git a/Jellyfin.Api/Constants/Policies.cs b/Jellyfin.Api/Constants/Policies.cs new file mode 100644 index 000000000..e2b383f75 --- /dev/null +++ b/Jellyfin.Api/Constants/Policies.cs @@ -0,0 +1,18 @@ +namespace Jellyfin.Api.Constants +{ + /// <summary> + /// Policies for the API authorization. + /// </summary> + public static class Policies + { + /// <summary> + /// Policy name for requiring first time setup or elevated privileges. + /// </summary> + public const string FirstTimeSetupOrElevated = "FirstTimeOrElevated"; + + /// <summary> + /// Policy name for requiring elevated privileges. + /// </summary> + public const string RequiresElevation = "RequiresElevation"; + } +} diff --git a/Jellyfin.Api/Constants/UserRoles.cs b/Jellyfin.Api/Constants/UserRoles.cs new file mode 100644 index 000000000..d9a536e7d --- /dev/null +++ b/Jellyfin.Api/Constants/UserRoles.cs @@ -0,0 +1,23 @@ +namespace Jellyfin.Api.Constants +{ + /// <summary> + /// Constants for user roles used in the authentication and authorization for the API. + /// </summary> + public static class UserRoles + { + /// <summary> + /// Guest user. + /// </summary> + public const string Guest = "Guest"; + + /// <summary> + /// Regular user with no special privileges. + /// </summary> + public const string User = "User"; + + /// <summary> + /// Administrator user with elevated privileges. + /// </summary> + public const string Administrator = "Administrator"; + } +} diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs new file mode 100644 index 000000000..1014c8c56 --- /dev/null +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -0,0 +1,127 @@ +using System.Linq; +using System.Threading.Tasks; +using Jellyfin.Api.Constants; +using Jellyfin.Api.Models.StartupDtos; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers +{ + /// <summary> + /// The startup wizard controller. + /// </summary> + [Authorize(Policy = Policies.FirstTimeSetupOrElevated)] + public class StartupController : BaseJellyfinApiController + { + private readonly IServerConfigurationManager _config; + private readonly IUserManager _userManager; + + /// <summary> + /// Initializes a new instance of the <see cref="StartupController" /> class. + /// </summary> + /// <param name="config">The server configuration manager.</param> + /// <param name="userManager">The user manager.</param> + public StartupController(IServerConfigurationManager config, IUserManager userManager) + { + _config = config; + _userManager = userManager; + } + + /// <summary> + /// Api endpoint for completing the startup wizard. + /// </summary> + [HttpPost("Complete")] + public void CompleteWizard() + { + _config.Configuration.IsStartupWizardCompleted = true; + _config.SetOptimalValues(); + _config.SaveConfiguration(); + } + + /// <summary> + /// Endpoint for getting the initial startup wizard configuration. + /// </summary> + /// <returns>The initial startup wizard configuration.</returns> + [HttpGet("Configuration")] + public StartupConfigurationDto GetStartupConfiguration() + { + var result = new StartupConfigurationDto + { + UICulture = _config.Configuration.UICulture, + MetadataCountryCode = _config.Configuration.MetadataCountryCode, + PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage + }; + + return result; + } + + /// <summary> + /// Endpoint for updating the initial startup wizard configuration. + /// </summary> + /// <param name="uiCulture">The UI language culture.</param> + /// <param name="metadataCountryCode">The metadata country code.</param> + /// <param name="preferredMetadataLanguage">The preferred language for metadata.</param> + [HttpPost("Configuration")] + public void UpdateInitialConfiguration( + [FromForm] string uiCulture, + [FromForm] string metadataCountryCode, + [FromForm] string preferredMetadataLanguage) + { + _config.Configuration.UICulture = uiCulture; + _config.Configuration.MetadataCountryCode = metadataCountryCode; + _config.Configuration.PreferredMetadataLanguage = preferredMetadataLanguage; + _config.SaveConfiguration(); + } + + /// <summary> + /// Endpoint for (dis)allowing remote access and UPnP. + /// </summary> + /// <param name="enableRemoteAccess">Enable remote access.</param> + /// <param name="enableAutomaticPortMapping">Enable UPnP.</param> + [HttpPost("RemoteAccess")] + public void SetRemoteAccess([FromForm] bool enableRemoteAccess, [FromForm] bool enableAutomaticPortMapping) + { + _config.Configuration.EnableRemoteAccess = enableRemoteAccess; + _config.Configuration.EnableUPnP = enableAutomaticPortMapping; + _config.SaveConfiguration(); + } + + /// <summary> + /// Endpoint for returning the first user. + /// </summary> + /// <returns>The first user.</returns> + [HttpGet("User")] + public StartupUserDto GetFirstUser() + { + var user = _userManager.Users.First(); + + return new StartupUserDto + { + Name = user.Name, + Password = user.Password + }; + } + + /// <summary> + /// Endpoint for updating the user name and password. + /// </summary> + /// <param name="startupUserDto">The DTO containing username and password.</param> + /// <returns>The async task.</returns> + [HttpPost("User")] + public async Task UpdateUser([FromForm] StartupUserDto startupUserDto) + { + var user = _userManager.Users.First(); + + user.Name = startupUserDto.Name; + + _userManager.UpdateUser(user); + + if (!string.IsNullOrEmpty(startupUserDto.Password)) + { + await _userManager.ChangePassword(user, startupUserDto.Password).ConfigureAwait(false); + } + } + } +} diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj new file mode 100644 index 000000000..a2818b45d --- /dev/null +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -0,0 +1,32 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>netstandard2.1</TargetFramework> + <GenerateDocumentationFile>true</GenerateDocumentationFile> + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" /> + <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.0.0" /> + <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" /> + <PackageReference Include="Swashbuckle.AspNetCore" Version="5.0.0-rc4" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" /> + </ItemGroup> + + <!-- Code analysers--> + <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" PrivateAssets="All" /> + <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> + <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> + <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> + </ItemGroup> + + <PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> + <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet> + </PropertyGroup> + +</Project> diff --git a/Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs b/Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs new file mode 100644 index 000000000..d048dad0a --- /dev/null +++ b/Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs @@ -0,0 +1,23 @@ +namespace Jellyfin.Api.Models.StartupDtos +{ + /// <summary> + /// The startup configuration DTO. + /// </summary> + public class StartupConfigurationDto + { + /// <summary> + /// Gets or sets UI language culture. + /// </summary> + public string UICulture { get; set; } + + /// <summary> + /// Gets or sets the metadata country code. + /// </summary> + public string MetadataCountryCode { get; set; } + + /// <summary> + /// Gets or sets the preferred language for the metadata. + /// </summary> + public string PreferredMetadataLanguage { get; set; } + } +} diff --git a/Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs b/Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs new file mode 100644 index 000000000..3a9348037 --- /dev/null +++ b/Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs @@ -0,0 +1,18 @@ +namespace Jellyfin.Api.Models.StartupDtos +{ + /// <summary> + /// The startup user DTO. + /// </summary> + public class StartupUserDto + { + /// <summary> + /// Gets or sets the username. + /// </summary> + public string Name { get; set; } + + /// <summary> + /// Gets or sets the user's password. + /// </summary> + public string Password { get; set; } + } +} diff --git a/Jellyfin.Api/MvcRoutePrefix.cs b/Jellyfin.Api/MvcRoutePrefix.cs new file mode 100644 index 000000000..e00973094 --- /dev/null +++ b/Jellyfin.Api/MvcRoutePrefix.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApplicationModels; + +namespace Jellyfin.Api +{ + /// <summary> + /// Route prefixing for ASP.NET MVC. + /// </summary> + public static class MvcRoutePrefix + { + /// <summary> + /// Adds route prefixes to the MVC conventions. + /// </summary> + /// <param name="opts">The MVC options.</param> + /// <param name="prefixes">The list of prefixes.</param> + public static void UseGeneralRoutePrefix(this MvcOptions opts, params string[] prefixes) + { + opts.Conventions.Insert(0, new RoutePrefixConvention(prefixes)); + } + + private class RoutePrefixConvention : IApplicationModelConvention + { + private readonly AttributeRouteModel[] _routePrefixes; + + public RoutePrefixConvention(IEnumerable<string> prefixes) + { + _routePrefixes = prefixes.Select(p => new AttributeRouteModel(new RouteAttribute(p))).ToArray(); + } + + public void Apply(ApplicationModel application) + { + foreach (var controller in application.Controllers) + { + if (controller.Selectors == null) + { + continue; + } + + var newSelectors = new List<SelectorModel>(); + foreach (var selector in controller.Selectors) + { + newSelectors.AddRange(_routePrefixes.Select(routePrefix => new SelectorModel(selector) + { + AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(routePrefix, selector.AttributeRouteModel) + })); + } + + controller.Selectors.Clear(); + newSelectors.ForEach(selector => controller.Selectors.Add(selector)); + } + } + } + } +} diff --git a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs new file mode 100644 index 000000000..db06eb455 --- /dev/null +++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Builder; + +namespace Jellyfin.Server.Extensions +{ + /// <summary> + /// Extensions for adding API specific functionality to the application pipeline. + /// </summary> + public static class ApiApplicationBuilderExtensions + { + /// <summary> + /// Adds swagger and swagger UI to the application pipeline. + /// </summary> + /// <param name="applicationBuilder">The application builder.</param> + /// <returns>The updated application builder.</returns> + public static IApplicationBuilder UseJellyfinApiSwagger(this IApplicationBuilder applicationBuilder) + { + applicationBuilder.UseSwagger(); + + // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), + // specifying the Swagger JSON endpoint. + return applicationBuilder.UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "Jellyfin API V1"); + }); + } + } +} diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs new file mode 100644 index 000000000..dd4f9cd23 --- /dev/null +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -0,0 +1,90 @@ +using Jellyfin.Api; +using Jellyfin.Api.Auth; +using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; +using Jellyfin.Api.Auth.RequiresElevationPolicy; +using Jellyfin.Api.Constants; +using Jellyfin.Api.Controllers; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; + +namespace Jellyfin.Server.Extensions +{ + /// <summary> + /// API specific extensions for the service collection. + /// </summary> + public static class ApiServiceCollectionExtensions + { + /// <summary> + /// Adds jellyfin API authorization policies to the DI container. + /// </summary> + /// <param name="serviceCollection">The service collection.</param> + /// <returns>The updated service collection.</returns> + public static IServiceCollection AddJellyfinApiAuthorization(this IServiceCollection serviceCollection) + { + serviceCollection.AddSingleton<IAuthorizationHandler, FirstTimeSetupOrElevatedHandler>(); + serviceCollection.AddSingleton<IAuthorizationHandler, RequiresElevationHandler>(); + return serviceCollection.AddAuthorizationCore(options => + { + options.AddPolicy( + Policies.RequiresElevation, + policy => + { + policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); + policy.AddRequirements(new RequiresElevationRequirement()); + }); + options.AddPolicy( + Policies.FirstTimeSetupOrElevated, + policy => + { + policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); + policy.AddRequirements(new FirstTimeSetupOrElevatedRequirement()); + }); + }); + } + + /// <summary> + /// Adds custom legacy authentication to the service collection. + /// </summary> + /// <param name="serviceCollection">The service collection.</param> + /// <returns>The updated service collection.</returns> + public static AuthenticationBuilder AddCustomAuthentication(this IServiceCollection serviceCollection) + { + return serviceCollection.AddAuthentication(AuthenticationSchemes.CustomAuthentication) + .AddScheme<AuthenticationSchemeOptions, CustomAuthenticationHandler>(AuthenticationSchemes.CustomAuthentication, null); + } + + /// <summary> + /// Extension method for adding the jellyfin API to the service collection. + /// </summary> + /// <param name="serviceCollection">The service collection.</param> + /// <param name="baseUrl">The base url for the API.</param> + /// <returns>The MVC builder.</returns> + public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, string baseUrl) + { + return serviceCollection.AddMvc(opts => + { + opts.UseGeneralRoutePrefix(baseUrl); + }) + + // Clear app parts to avoid other assemblies being picked up + .ConfigureApplicationPartManager(a => a.ApplicationParts.Clear()) + .AddApplicationPart(typeof(StartupController).Assembly) + .AddControllersAsServices(); + } + + /// <summary> + /// Adds Swagger to the service collection. + /// </summary> + /// <param name="serviceCollection">The service collection.</param> + /// <returns>The updated service collection.</returns> + public static IServiceCollection AddJellyfinApiSwagger(this IServiceCollection serviceCollection) + { + return serviceCollection.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo { Title = "Jellyfin API", Version = "v1" }); + }); + } + } +} diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 8afeb8750..110028176 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -10,6 +10,7 @@ <PropertyGroup> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> @@ -20,6 +21,10 @@ <EmbeddedResource Include="Resources/Configuration/*" /> </ItemGroup> + <ItemGroup> + <FrameworkReference Include="Microsoft.AspNetCore.App" /> + </ItemGroup> + <!-- Code analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" /> @@ -41,6 +46,7 @@ <PackageReference Include="Serilog.Sinks.Async" Version="1.4.0" /> <PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" /> <PackageReference Include="Serilog.Sinks.File" Version="4.1.0" /> + <PackageReference Include="Serilog.Sinks.Graylog" Version="2.1.1" /> <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.2" /> <PackageReference Include="SQLitePCLRaw.provider.sqlite3.netstandard11" Version="1.1.14" /> </ItemGroup> diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index bdf3689f1..5ac005b40 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -18,9 +19,12 @@ using Jellyfin.Drawing.Skia; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.Globalization; +using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Serilog; using Serilog.Extensions.Logging; using SQLitePCL; @@ -35,7 +39,7 @@ namespace Jellyfin.Server { private static readonly CancellationTokenSource _tokenSource = new CancellationTokenSource(); private static readonly ILoggerFactory _loggerFactory = new SerilogLoggerFactory(); - private static ILogger _logger; + private static ILogger _logger = NullLogger.Instance; private static bool _restartOnShutdown; /// <summary> @@ -86,6 +90,12 @@ namespace Jellyfin.Server { var stopWatch = new Stopwatch(); stopWatch.Start(); + + // Log all uncaught exceptions to std error + static void UnhandledExceptionToConsole(object sender, UnhandledExceptionEventArgs e) => + Console.Error.WriteLine("Unhandled Exception\n" + e.ExceptionObject.ToString()); + AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionToConsole; + ServerApplicationPaths appPaths = CreateApplicationPaths(options); // $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager @@ -97,6 +107,8 @@ namespace Jellyfin.Server _logger = _loggerFactory.CreateLogger("Main"); + // Log uncaught exceptions to the logging instead of std error + AppDomain.CurrentDomain.UnhandledException -= UnhandledExceptionToConsole; AppDomain.CurrentDomain.UnhandledException += (sender, e) => _logger.LogCritical((Exception)e.ExceptionObject, "Unhandled Exception"); @@ -129,7 +141,7 @@ namespace Jellyfin.Server _logger.LogInformation( "Jellyfin version: {Version}", - Assembly.GetEntryAssembly().GetName().Version.ToString(3)); + Assembly.GetEntryAssembly()!.GetName().Version!.ToString(3)); ApplicationHost.LogEnvironmentInfo(_logger, appPaths); @@ -157,7 +169,24 @@ namespace Jellyfin.Server appConfig); try { - await appHost.InitAsync(new ServiceCollection()).ConfigureAwait(false); + ServiceCollection serviceCollection = new ServiceCollection(); + await appHost.InitAsync(serviceCollection).ConfigureAwait(false); + + var host = CreateWebHostBuilder(appHost, serviceCollection).Build(); + + // A bit hacky to re-use service provider since ASP.NET doesn't allow a custom service collection. + appHost.ServiceProvider = host.Services; + appHost.FindParts(); + + try + { + await host.StartAsync().ConfigureAwait(false); + } + catch + { + _logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in system.xml and try again."); + throw; + } appHost.ImageProcessor.ImageEncoder = GetImageEncoder(appPaths, appHost.LocalizationManager); @@ -189,6 +218,55 @@ namespace Jellyfin.Server } } + private static IWebHostBuilder CreateWebHostBuilder(ApplicationHost appHost, IServiceCollection serviceCollection) + { + return new WebHostBuilder() + .UseKestrel(options => + { + var addresses = appHost.ServerConfigurationManager + .Configuration + .LocalNetworkAddresses + .Select(appHost.NormalizeConfiguredLocalAddress) + .Where(i => i != null) + .ToList(); + if (addresses.Any()) + { + foreach (var address in addresses) + { + _logger.LogInformation("Kestrel listening on {ipaddr}", address); + options.Listen(address, appHost.HttpPort); + + if (appHost.EnableHttps && appHost.Certificate != null) + { + options.Listen( + address, + appHost.HttpsPort, + listenOptions => listenOptions.UseHttps(appHost.Certificate)); + } + } + } + else + { + _logger.LogInformation("Kestrel listening on all interfaces"); + options.ListenAnyIP(appHost.HttpPort); + + if (appHost.EnableHttps && appHost.Certificate != null) + { + options.ListenAnyIP( + appHost.HttpsPort, + listenOptions => listenOptions.UseHttps(appHost.Certificate)); + } + } + }) + .UseContentRoot(appHost.ContentRoot) + .ConfigureServices(services => + { + // Merge the external ServiceCollection into ASP.NET DI + services.TryAdd(serviceCollection); + }) + .UseStartup<Startup>(); + } + /// <summary> /// Create the data, config and log paths from the variety of inputs(command line args, /// environment variables) or decide on what default to use. For Windows it's %AppPath% @@ -354,16 +432,25 @@ namespace Jellyfin.Server private static async Task<IConfiguration> CreateConfiguration(IApplicationPaths appPaths) { + const string ResourcePath = "Jellyfin.Server.Resources.Configuration.logging.json"; string configPath = Path.Combine(appPaths.ConfigurationDirectoryPath, "logging.json"); if (!File.Exists(configPath)) { // For some reason the csproj name is used instead of the assembly name - using (Stream rscstr = typeof(Program).Assembly - .GetManifestResourceStream("Jellyfin.Server.Resources.Configuration.logging.json")) - using (Stream fstr = File.Open(configPath, FileMode.CreateNew)) + using (Stream? resource = typeof(Program).Assembly.GetManifestResourceStream(ResourcePath)) { - await rscstr.CopyToAsync(fstr).ConfigureAwait(false); + if (resource == null) + { + throw new InvalidOperationException( + string.Format( + CultureInfo.InvariantCulture, + "Invalid resource path: '{0}'", + ResourcePath)); + } + + using Stream dst = File.Open(configPath, FileMode.CreateNew); + await resource.CopyToAsync(dst).ConfigureAwait(false); } } @@ -426,7 +513,7 @@ namespace Jellyfin.Server { _logger.LogInformation("Starting new instance"); - string module = options.RestartPath; + var module = options.RestartPath; if (string.IsNullOrWhiteSpace(module)) { @@ -434,7 +521,6 @@ namespace Jellyfin.Server } string commandLineArgsString; - if (options.RestartArgs != null) { commandLineArgsString = options.RestartArgs ?? string.Empty; diff --git a/Jellyfin.Server/Resources/Configuration/logging.json b/Jellyfin.Server/Resources/Configuration/logging.json index d16991277..e85ef05af 100644 --- a/Jellyfin.Server/Resources/Configuration/logging.json +++ b/Jellyfin.Server/Resources/Configuration/logging.json @@ -17,6 +17,9 @@ "Args": { "path": "%JELLYFIN_LOG_DIR%//log_.log", "rollingInterval": "Day", + "retainedFileCountLimit": 3, + "rollOnFileSizeLimit": true, + "fileSizeLimitBytes": 100000000, "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message}{NewLine}{Exception}" } } diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs new file mode 100644 index 000000000..3ee5fb8b5 --- /dev/null +++ b/Jellyfin.Server/Startup.cs @@ -0,0 +1,81 @@ +using Jellyfin.Server.Extensions; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Jellyfin.Server +{ + /// <summary> + /// Startup configuration for the Kestrel webhost. + /// </summary> + public class Startup + { + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// <summary> + /// Initializes a new instance of the <see cref="Startup" /> class. + /// </summary> + /// <param name="serverConfigurationManager">The server configuration manager.</param> + public Startup(IServerConfigurationManager serverConfigurationManager) + { + _serverConfigurationManager = serverConfigurationManager; + } + + /// <summary> + /// Configures the service collection for the webhost. + /// </summary> + /// <param name="services">The service collection.</param> + public void ConfigureServices(IServiceCollection services) + { + services.AddResponseCompression(); + services.AddHttpContextAccessor(); + services.AddJellyfinApi(_serverConfigurationManager.Configuration.BaseUrl.TrimStart('/')); + + services.AddJellyfinApiSwagger(); + + // configure custom legacy authentication + services.AddCustomAuthentication(); + + services.AddJellyfinApiAuthorization(); + } + + /// <summary> + /// Configures the app builder for the webhost. + /// </summary> + /// <param name="app">The application builder.</param> + /// <param name="env">The webhost environment.</param> + /// <param name="serverApplicationHost">The server application host.</param> + public void Configure( + IApplicationBuilder app, + IWebHostEnvironment env, + IServerApplicationHost serverApplicationHost) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseWebSockets(); + + app.UseResponseCompression(); + + // TODO app.UseMiddleware<WebSocketMiddleware>(); + app.Use(serverApplicationHost.ExecuteWebsocketHandlerAsync); + + // TODO use when old API is removed: app.UseAuthentication(); + app.UseJellyfinApiSwagger(); + app.UseRouting(); + app.UseAuthorization(); + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + + app.Use(serverApplicationHost.ExecuteHttpHandlerAsync); + } + } +} diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index bb0adaf63..1fb1c5af8 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -13,39 +13,39 @@ namespace Jellyfin.Server /// </summary> /// <value>The path to the data directory.</value> [Option('d', "datadir", Required = false, HelpText = "Path to use for the data folder (database files, etc.).")] - public string DataDir { get; set; } + public string? DataDir { get; set; } /// <summary> /// Gets or sets the path to the web directory. /// </summary> /// <value>The path to the web directory.</value> [Option('w', "webdir", Required = false, HelpText = "Path to the Jellyfin web UI resources.")] - public string WebDir { get; set; } + public string? WebDir { get; set; } /// <summary> /// Gets or sets the path to the cache directory. /// </summary> /// <value>The path to the cache directory.</value> [Option('C', "cachedir", Required = false, HelpText = "Path to use for caching.")] - public string CacheDir { get; set; } + public string? CacheDir { get; set; } /// <summary> /// Gets or sets the path to the config directory. /// </summary> /// <value>The path to the config directory.</value> [Option('c', "configdir", Required = false, HelpText = "Path to use for configuration data (user settings and pictures).")] - public string ConfigDir { get; set; } + public string? ConfigDir { get; set; } /// <summary> /// Gets or sets the path to the log directory. /// </summary> /// <value>The path to the log directory.</value> [Option('l', "logdir", Required = false, HelpText = "Path to use for writing log files.")] - public string LogDir { get; set; } + public string? LogDir { get; set; } /// <inheritdoc /> [Option("ffmpeg", Required = false, HelpText = "Path to external FFmpeg executable to use in place of default found in PATH.")] - public string FFmpegPath { get; set; } + public string? FFmpegPath { get; set; } /// <inheritdoc /> [Option("service", Required = false, HelpText = "Run as headless service.")] @@ -57,14 +57,14 @@ namespace Jellyfin.Server /// <inheritdoc /> [Option("package-name", Required = false, HelpText = "Used when packaging Jellyfin (example, synology).")] - public string PackageName { get; set; } + public string? PackageName { get; set; } /// <inheritdoc /> [Option("restartpath", Required = false, HelpText = "Path to restart script.")] - public string RestartPath { get; set; } + public string? RestartPath { get; set; } /// <inheritdoc /> [Option("restartargs", Required = false, HelpText = "Arguments for restart script.")] - public string RestartArgs { get; set; } + public string? RestartArgs { get; set; } } } diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index a962b0240..0542807af 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -13,7 +13,6 @@ using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Diagnostics; using MediaBrowser.Model.Dto; using MediaBrowser.Model.IO; @@ -157,17 +156,12 @@ namespace MediaBrowser.Api return Task.CompletedTask; } - public EncodingOptions GetEncodingOptions() - { - return ConfigurationManagerExtensions.GetConfiguration<EncodingOptions>(ConfigurationManager, "encoding"); - } - /// <summary> /// Deletes the encoded media cache. /// </summary> private void DeleteEncodedMediaCache() { - var path = ConfigurationManager.ApplicationPaths.GetTranscodePath(); + var path = ConfigurationManager.GetTranscodePath(); if (!Directory.Exists(path)) { diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 311105596..5f1f6c5b1 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -300,11 +300,13 @@ namespace MediaBrowser.Api string baseUrl = ApiEntryPoint.Instance.ConfigurationManager.Configuration.BaseUrl; // backwards compatibility - if (baseUrl.Length == 0 - && (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase) - || string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase))) + if (baseUrl.Length == 0) { - index++; + if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase) + || string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase)) + { + index++; + } } else if (string.Equals(first, baseUrl.Remove(0, 1))) { diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs index f4813e713..a63ef9f05 100644 --- a/MediaBrowser.Api/EnvironmentService.cs +++ b/MediaBrowser.Api/EnvironmentService.cs @@ -52,6 +52,7 @@ namespace MediaBrowser.Api public bool? IsFile { get; set; } } + [Obsolete] [Route("/Environment/NetworkShares", "GET", Summary = "Gets shares from a network device")] public class GetNetworkShares : IReturn<List<FileSystemEntryInfo>> { @@ -192,22 +193,18 @@ namespace MediaBrowser.Api var networkPrefix = UncSeparatorString + UncSeparatorString; - if (path.StartsWith(networkPrefix, StringComparison.OrdinalIgnoreCase) && path.LastIndexOf(UncSeparator) == 1) + if (path.StartsWith(networkPrefix, StringComparison.OrdinalIgnoreCase) + && path.LastIndexOf(UncSeparator) == 1) { - return ToOptimizedResult(GetNetworkShares(path).OrderBy(i => i.Path).ToList()); + return ToOptimizedResult(Array.Empty<FileSystemEntryInfo>()); } return ToOptimizedResult(GetFileSystemEntries(request).ToList()); } + [Obsolete] public object Get(GetNetworkShares request) - { - var path = request.Path; - - var shares = GetNetworkShares(path).OrderBy(i => i.Path).ToList(); - - return ToOptimizedResult(shares); - } + => ToOptimizedResult(Array.Empty<FileSystemEntryInfo>()); /// <summary> /// Gets the specified request. @@ -241,26 +238,7 @@ namespace MediaBrowser.Api /// <param name="request">The request.</param> /// <returns>System.Object.</returns> public object Get(GetNetworkDevices request) - { - var result = _networkManager.GetNetworkDevices().ToList(); - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the network shares. - /// </summary> - /// <param name="path">The path.</param> - /// <returns>IEnumerable{FileSystemEntryInfo}.</returns> - private IEnumerable<FileSystemEntryInfo> GetNetworkShares(string path) - { - return _networkManager.GetNetworkShares(path).Where(s => s.ShareType == NetworkShareType.Disk).Select(c => new FileSystemEntryInfo - { - Name = c.Name, - Path = Path.Combine(path, c.Name), - Type = FileSystemEntryType.NetworkShare - }); - } + => ToOptimizedResult(Array.Empty<FileSystemEntryInfo>()); /// <summary> /// Gets the file system entries. diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 833fcc54b..4bd729aac 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; @@ -141,7 +142,7 @@ namespace MediaBrowser.Api.Playback var filename = data.GetMD5().ToString("N", CultureInfo.InvariantCulture); var ext = outputFileExtension.ToLowerInvariant(); - var folder = ServerConfigurationManager.ApplicationPaths.TranscodePath; + var folder = ServerConfigurationManager.GetTranscodePath(); if (EnableOutputInSubFolder) { @@ -215,7 +216,7 @@ namespace MediaBrowser.Api.Playback } } - var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions(); + var encodingOptions = ServerConfigurationManager.GetEncodingOptions(); var process = new Process() { @@ -845,7 +846,7 @@ namespace MediaBrowser.Api.Playback ? GetOutputFileExtension(state) : ('.' + state.OutputContainer); - var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions(); + var encodingOptions = ServerConfigurationManager.GetEncodingOptions(); state.OutputFilePath = GetOutputFilePath(state, encodingOptions, ext); diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs index 7487f2b64..ca5a73ff1 100644 --- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs +++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs @@ -2,7 +2,7 @@ using System; using System.IO; using System.Linq; using System.Threading.Tasks; -using MediaBrowser.Controller; +using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; @@ -83,13 +83,11 @@ namespace MediaBrowser.Api.Playback.Hls public class HlsSegmentService : BaseApiService { - private readonly IServerApplicationPaths _appPaths; private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; - public HlsSegmentService(IServerApplicationPaths appPaths, IServerConfigurationManager config, IFileSystem fileSystem) + public HlsSegmentService(IServerConfigurationManager config, IFileSystem fileSystem) { - _appPaths = appPaths; _config = config; _fileSystem = fileSystem; } @@ -97,7 +95,7 @@ namespace MediaBrowser.Api.Playback.Hls public Task<object> Get(GetHlsPlaylistLegacy request) { var file = request.PlaylistId + Path.GetExtension(Request.PathInfo); - file = Path.Combine(_appPaths.TranscodePath, file); + file = Path.Combine(_config.GetTranscodePath(), file); return GetFileResult(file, file); } @@ -115,8 +113,7 @@ namespace MediaBrowser.Api.Playback.Hls public Task<object> Get(GetHlsVideoSegmentLegacy request) { var file = request.SegmentId + Path.GetExtension(Request.PathInfo); - - var transcodeFolderPath = _config.ApplicationPaths.TranscodePath; + var transcodeFolderPath = _config.GetTranscodePath(); file = Path.Combine(transcodeFolderPath, file); var normalizedPlaylistId = request.PlaylistId; @@ -136,7 +133,7 @@ namespace MediaBrowser.Api.Playback.Hls { // TODO: Deprecate with new iOS app var file = request.SegmentId + Path.GetExtension(Request.PathInfo); - file = Path.Combine(_appPaths.TranscodePath, file); + file = Path.Combine(_config.GetTranscodePath(), file); return ResultFactory.GetStaticFileResult(Request, file, FileShareMode.ReadWrite); } diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index 3a9eb7a55..e69de29bb 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -1,135 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Api -{ - [Route("/Startup/Complete", "POST", Summary = "Reports that the startup wizard has been completed", IsHidden = true)] - public class ReportStartupWizardComplete : IReturnVoid - { - } - - [Route("/Startup/Configuration", "GET", Summary = "Gets initial server configuration", IsHidden = true)] - public class GetStartupConfiguration : IReturn<StartupConfiguration> - { - } - - [Route("/Startup/Configuration", "POST", Summary = "Updates initial server configuration", IsHidden = true)] - public class UpdateStartupConfiguration : StartupConfiguration, IReturnVoid - { - } - - [Route("/Startup/RemoteAccess", "POST", Summary = "Updates initial server configuration", IsHidden = true)] - public class UpdateRemoteAccessConfiguration : IReturnVoid - { - public bool EnableRemoteAccess { get; set; } - public bool EnableAutomaticPortMapping { get; set; } - } - - [Route("/Startup/User", "GET", Summary = "Gets initial user info", IsHidden = true)] - public class GetStartupUser : IReturn<StartupUser> - { - } - - [Route("/Startup/User", "POST", Summary = "Updates initial user info", IsHidden = true)] - public class UpdateStartupUser : StartupUser - { - } - - [Authenticated(AllowBeforeStartupWizard = true, Roles = "Admin")] - public class StartupWizardService : BaseApiService - { - private readonly IServerConfigurationManager _config; - private readonly IServerApplicationHost _appHost; - private readonly IUserManager _userManager; - private readonly IMediaEncoder _mediaEncoder; - private readonly IHttpClient _httpClient; - - public StartupWizardService(IServerConfigurationManager config, IHttpClient httpClient, IServerApplicationHost appHost, IUserManager userManager, IMediaEncoder mediaEncoder) - { - _config = config; - _appHost = appHost; - _userManager = userManager; - _mediaEncoder = mediaEncoder; - _httpClient = httpClient; - } - - public void Post(ReportStartupWizardComplete request) - { - _config.Configuration.IsStartupWizardCompleted = true; - _config.SetOptimalValues(); - _config.SaveConfiguration(); - } - - public object Get(GetStartupConfiguration request) - { - var result = new StartupConfiguration - { - UICulture = _config.Configuration.UICulture, - MetadataCountryCode = _config.Configuration.MetadataCountryCode, - PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage - }; - - return result; - } - - public void Post(UpdateStartupConfiguration request) - { - _config.Configuration.UICulture = request.UICulture; - _config.Configuration.MetadataCountryCode = request.MetadataCountryCode; - _config.Configuration.PreferredMetadataLanguage = request.PreferredMetadataLanguage; - _config.SaveConfiguration(); - } - - public void Post(UpdateRemoteAccessConfiguration request) - { - _config.Configuration.EnableRemoteAccess = request.EnableRemoteAccess; - _config.Configuration.EnableUPnP = request.EnableAutomaticPortMapping; - _config.SaveConfiguration(); - } - - public object Get(GetStartupUser request) - { - var user = _userManager.Users.First(); - - return new StartupUser - { - Name = user.Name, - Password = user.Password - }; - } - - public async Task Post(UpdateStartupUser request) - { - var user = _userManager.Users.First(); - - user.Name = request.Name; - - _userManager.UpdateUser(user); - - if (!string.IsNullOrEmpty(request.Password)) - { - await _userManager.ChangePassword(user, request.Password).ConfigureAwait(false); - } - } - } - - public class StartupConfiguration - { - public string UICulture { get; set; } - public string MetadataCountryCode { get; set; } - public string PreferredMetadataLanguage { get; set; } - } - - public class StartupUser - { - public string Name { get; set; } - public string Password { get; set; } - } -} diff --git a/MediaBrowser.Common/Configuration/EncodingConfigurationExtensions.cs b/MediaBrowser.Common/Configuration/EncodingConfigurationExtensions.cs new file mode 100644 index 000000000..2bf1f6bc8 --- /dev/null +++ b/MediaBrowser.Common/Configuration/EncodingConfigurationExtensions.cs @@ -0,0 +1,28 @@ +using System.IO; +using MediaBrowser.Model.Configuration; + +namespace MediaBrowser.Common.Configuration +{ + /// <summary> + /// Class containing extension methods for working with the encoding configuration. + /// </summary> + public static class EncodingConfigurationExtensions + { + /// <summary> + /// Gets the encoding options. + /// </summary> + /// <param name="configurationManager">The configuration manager.</param> + /// <returns>The encoding options.</returns> + public static EncodingOptions GetEncodingOptions(this IConfigurationManager configurationManager) + => configurationManager.GetConfiguration<EncodingOptions>("encoding"); + + /// <summary> + /// Retrieves the transcoding temp path from the encoding configuration. + /// </summary> + /// <param name="configurationManager">The Configuration manager.</param> + /// <returns>The transcoding temp path.</returns> + public static string GetTranscodePath(this IConfigurationManager configurationManager) + => configurationManager.GetEncodingOptions().TranscodingTempPath + ?? Path.Combine(configurationManager.CommonApplicationPaths.ProgramDataPath, "transcodes"); + } +} diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index 97504a471..0b99dc910 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -4,8 +4,6 @@ using System; using System.Collections.Generic; using System.Net; using System.Net.NetworkInformation; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Net; namespace MediaBrowser.Common.Net { @@ -37,19 +35,6 @@ namespace MediaBrowser.Common.Net bool IsInPrivateAddressSpace(string endpoint); /// <summary> - /// Gets the network shares. - /// </summary> - /// <param name="path">The path.</param> - /// <returns>IEnumerable{NetworkShare}.</returns> - IEnumerable<NetworkShare> GetNetworkShares(string path); - - /// <summary> - /// Gets available devices within the domain - /// </summary> - /// <returns>PC's in the Domain</returns> - IEnumerable<FileSystemEntryInfo> GetNetworkDevices(); - - /// <summary> /// Determines whether [is in local network] [the specified endpoint]. /// </summary> /// <param name="endpoint">The endpoint.</param> diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 61b2c15ae..b3c56bdd5 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; using MediaBrowser.Model.System; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller { @@ -87,5 +88,9 @@ namespace MediaBrowser.Controller string ExpandVirtualPath(string path); string ReverseVirtualPath(string path); + + Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next); + + Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next); } } diff --git a/MediaBrowser.Controller/IServerApplicationPaths.cs b/MediaBrowser.Controller/IServerApplicationPaths.cs index 127a31329..56e7e4e47 100644 --- a/MediaBrowser.Controller/IServerApplicationPaths.cs +++ b/MediaBrowser.Controller/IServerApplicationPaths.cs @@ -83,12 +83,6 @@ namespace MediaBrowser.Controller string UserConfigurationDirectoryPath { get; } /// <summary> - /// Gets the transcoding temporary path. - /// </summary> - /// <value>The transcoding temporary path.</value> - string TranscodePath { get; } - - /// <summary> /// Gets the internal metadata path. /// </summary> /// <value>The internal metadata path.</value> @@ -96,7 +90,5 @@ namespace MediaBrowser.Controller string VirtualInternalMetadataPath { get; } string ArtistsPath { get; } - - string GetTranscodePath(); } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 4dfb27130..0664bdd98 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -10,7 +10,6 @@ using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; @@ -2530,13 +2529,25 @@ namespace MediaBrowser.Controller.MediaEncoding case "h264": if (_mediaEncoder.SupportsDecoder("h264_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase)) { - return "-c:v h264_mmal"; + return "-c:v h264_mmal "; } break; case "mpeg2video": if (_mediaEncoder.SupportsDecoder("mpeg2_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase)) { - return "-c:v mpeg2_mmal"; + return "-c:v mpeg2_mmal "; + } + break; + case "mpeg4": + if (_mediaEncoder.SupportsDecoder("mpeg4_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase)) + { + return "-c:v mpeg4_mmal "; + } + break; + case "vc1": + if (_mediaEncoder.SupportsDecoder("vc1_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase)) + { + return "-c:v vc1_mmal "; } break; } diff --git a/MediaBrowser.Controller/Net/IAuthService.cs b/MediaBrowser.Controller/Net/IAuthService.cs index 142f1d91c..4c9120e0c 100644 --- a/MediaBrowser.Controller/Net/IAuthService.cs +++ b/MediaBrowser.Controller/Net/IAuthService.cs @@ -1,9 +1,12 @@ +using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net { public interface IAuthService { void Authenticate(IRequest request, IAuthenticationAttributes authAttribtues); + User Authenticate(HttpRequest request, IAuthenticationAttributes authAttribtues); } } diff --git a/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs b/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs index bb806ee55..75534b5bd 100644 --- a/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs +++ b/MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs @@ -1,54 +1,46 @@ +using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.IO; namespace MediaBrowser.MediaEncoding.Configuration { public class EncodingConfigurationFactory : IConfigurationFactory { - private readonly IFileSystem _fileSystem; - - public EncodingConfigurationFactory(IFileSystem fileSystem) - { - _fileSystem = fileSystem; - } - public IEnumerable<ConfigurationStore> GetConfigurations() { return new[] { - new EncodingConfigurationStore(_fileSystem) + new EncodingConfigurationStore() }; } } public class EncodingConfigurationStore : ConfigurationStore, IValidatingConfiguration { - private readonly IFileSystem _fileSystem; - - public EncodingConfigurationStore(IFileSystem fileSystem) + public EncodingConfigurationStore() { ConfigurationType = typeof(EncodingOptions); Key = "encoding"; - _fileSystem = fileSystem; } public void Validate(object oldConfig, object newConfig) { - var oldEncodingConfig = (EncodingOptions)oldConfig; - var newEncodingConfig = (EncodingOptions)newConfig; - - var newPath = newEncodingConfig.TranscodingTempPath; + var newPath = ((EncodingOptions)newConfig).TranscodingTempPath; if (!string.IsNullOrWhiteSpace(newPath) - && !string.Equals(oldEncodingConfig.TranscodingTempPath ?? string.Empty, newPath)) + && !string.Equals(((EncodingOptions)oldConfig).TranscodingTempPath, newPath, StringComparison.Ordinal)) { // Validate if (!Directory.Exists(newPath)) { - throw new FileNotFoundException(string.Format("{0} does not exist.", newPath)); + throw new DirectoryNotFoundException( + string.Format( + CultureInfo.InvariantCulture, + "{0} does not exist.", + newPath)); } } } diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 3620abfee..1feca0ec9 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -18,7 +18,10 @@ namespace MediaBrowser.MediaEncoding.Encoder "h264_qsv", "hevc_qsv", "mpeg2_qsv", + "mpeg2_mmal", + "mpeg4_mmal", "vc1_qsv", + "vc1_mmal", "h264_cuvid", "hevc_cuvid", "dts", @@ -26,6 +29,7 @@ namespace MediaBrowser.MediaEncoding.Encoder "aac", "mp3", "h264", + "h264_mmal", "hevc" }; diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs index eaebc13e3..fc7f12b1a 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs @@ -57,7 +57,8 @@ namespace MediaBrowser.Providers.TV.TheTVDB { IndexNumber = episode.IndexNumber.Value, ParentIndexNumber = episode.ParentIndexNumber.Value, - SeriesProviderIds = series.ProviderIds + SeriesProviderIds = series.ProviderIds, + SeriesDisplayOrder = series.DisplayOrder }; string episodeTvdbId = await _tvDbClientManager .GetEpisodeTvdbId(episodeInfo, language, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 27c8c1668..58bfb55f6 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -1,4 +1,3 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26730.3 @@ -51,6 +50,8 @@ 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}" +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}" @@ -89,10 +90,6 @@ Global {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.Build.0 = Release|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.Build.0 = Release|Any CPU {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.Build.0 = Debug|Any CPU {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -153,6 +150,10 @@ Global {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.Build.0 = Debug|Any CPU {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.ActiveCfg = Release|Any CPU {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.Build.0 = Release|Any CPU + {DFBEFB4C-DA19-4143-98B7-27320C7F7163}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DFBEFB4C-DA19-4143-98B7-27320C7F7163}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DFBEFB4C-DA19-4143-98B7-27320C7F7163}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DFBEFB4C-DA19-4143-98B7-27320C7F7163}.Release|Any CPU.Build.0 = Release|Any CPU {DF194677-DFD3-42AF-9F75-D44D5A416478}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DF194677-DFD3-42AF-9F75-D44D5A416478}.Debug|Any CPU.Build.0 = Debug|Any CPU {DF194677-DFD3-42AF-9F75-D44D5A416478}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -4,42 +4,62 @@ --- <p align="center"> -<img alt="Logo banner" src="https://raw.githubusercontent.com/jellyfin/jellyfin-ux/master/branding/SVG/banner-logo-solid.svg?sanitize=true"/> -<br/><br/> -<a href="https://github.com/jellyfin/jellyfin"><img alt="GPL 2.0 License" src="https://img.shields.io/github/license/jellyfin/jellyfin.svg"/></a> -<a href="https://github.com/jellyfin/jellyfin/releases"><img alt="Current Release" src="https://img.shields.io/github/release/jellyfin/jellyfin.svg"/></a> -<a href="https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/?utm_source=widget"><img src="https://translate.jellyfin.org/widgets/jellyfin/-/jellyfin-core/svg-badge.svg" alt="Translation status" /></a> -<a href="https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=1"><img alt="Azure DevOps builds" src="https://dev.azure.com/jellyfin-project/jellyfin/_apis/build/status/Jellyfin%20CI"></a> -<a href="https://hub.docker.com/r/jellyfin/jellyfin"><img alt="Docker Pull Count" src="https://img.shields.io/docker/pulls/jellyfin/jellyfin.svg"/></a> +<img alt="Logo Banner" src="https://raw.githubusercontent.com/jellyfin/jellyfin-ux/master/branding/SVG/banner-logo-solid.svg?sanitize=true"/> +<br/> +<br/> +<a href="https://github.com/jellyfin/jellyfin"> +<img alt="GPL 2.0 License" src="https://img.shields.io/github/license/jellyfin/jellyfin.svg"/> +</a> +<a href="https://github.com/jellyfin/jellyfin/releases"> +<img alt="Current Release" src="https://img.shields.io/github/release/jellyfin/jellyfin.svg"/> +</a> +<a href="https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/?utm_source=widget"> +<img src="https://translate.jellyfin.org/widgets/jellyfin/-/jellyfin-core/svg-badge.svg" alt="Translation Status"/> +</a> +<a href="https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=1"> +<img alt="Azure Builds" src="https://dev.azure.com/jellyfin-project/jellyfin/_apis/build/status/Jellyfin%20CI"/> +</a> +<a href="https://hub.docker.com/r/jellyfin/jellyfin"> +<img alt="Docker Pull Count" src="https://img.shields.io/docker/pulls/jellyfin/jellyfin.svg"/> +</a> </br> -<a href="https://opencollective.com/jellyfin"><img alt="Donate" src="https://img.shields.io/opencollective/all/jellyfin.svg?label=backers"/></a> -<a href="https://features.jellyfin.org"/><img alt="Submit and vote on feature requests" src="https://img.shields.io/badge/fider-vote%20on%20features-success.svg"/></a> -<a href="https://forum.jellyfin.org"/><img alt="Discuss on our Forum" src="https://img.shields.io/discourse/https/forum.jellyfin.org/users.svg"/></a> -<a href="https://matrix.to/#/+jellyfin:matrix.org"><img alt="Chat on Matrix" src="https://img.shields.io/matrix/jellyfin:matrix.org.svg?logo=matrix"/></a> -<a href="https://www.reddit.com/r/jellyfin/"><img alt="Join our Subreddit" src="https://img.shields.io/badge/reddit-r%2Fjellyfin-%23FF5700.svg"/></a> +<a href="https://opencollective.com/jellyfin"> +<img alt="Donate" src="https://img.shields.io/opencollective/all/jellyfin.svg?label=backers"/> +</a> +<a href="https://features.jellyfin.org"> +<img alt="Submit Feature Requests" src="https://img.shields.io/badge/fider-vote%20on%20features-success.svg"/> +</a> +<a href="https://forum.jellyfin.org"> +<img alt="Discuss on our Forum" src="https://img.shields.io/discourse/https/forum.jellyfin.org/users.svg"/> +</a> +<a href="https://matrix.to/#/+jellyfin:matrix.org"> +<img alt="Chat on Matrix" src="https://img.shields.io/matrix/jellyfin:matrix.org.svg?logo=matrix"/> +</a> +<a href="https://www.reddit.com/r/jellyfin"> +<img alt="Join our Subreddit" src="https://img.shields.io/badge/reddit-r%2Fjellyfin-%23FF5700.svg"/> +</a> </p> --- Jellyfin is a Free Software Media System that puts you in control of managing and streaming your media. It is an alternative to the proprietary Emby and Plex, to provide media from a dedicated server to end-user devices via multiple apps. Jellyfin is descended from Emby's 3.5.2 release and ported to the .NET Core framework to enable full cross-platform support. There are no strings attached, no premium licenses or features, and no hidden agendas: just a team who want to build something better and work together to achieve it. We welcome anyone who is interested in joining us in our quest! -For further details, please see [our documentation page](https://docs.jellyfin.org/). To receive the latest updates, get help with Jellyfin, and join the community, please visit [one of our communication channels on Matrix/Riot or social media](https://docs.jellyfin.org/general/getting-help.html). +For further details, please see [our documentation page](https://docs.jellyfin.org/). To receive the latest updates, get help with Jellyfin, and join the community, please visit [one of our communication channels](https://docs.jellyfin.org/general/getting-help.html). For more information about the project, please see our [about page](https://docs.jellyfin.org/general/about.html). -For more information about the project, please see our [about page](https://docs.jellyfin.org/general/about.html). +<strong>Want to get started?</strong><br/> +Choose from <a href="https://docs.jellyfin.org/general/administration/installing.html">Prebuilt Packages</a> or <a href="https://docs.jellyfin.org/general/administration/building.html">Build from Source</a>, then see our <a href="https://docs.jellyfin.org/general/administration/quick-start.html">quick start guide</a>.<br/> -<p align="center"> -<strong>Want to get started?</strong> -<em>Choose from <a href="https://docs.jellyfin.org/general/administration/installing.html">Prebuilt Packages</a> or <a href="https://docs.jellyfin.org/general/administration/building.html">Build from Source</a>, then see our <a href="https://docs.jellyfin.org/general/administration/quick-start.html">quick start guide</a>.</em> -</p> -<p align="center"> -<strong>Want to contribute?</strong> -<em>Check out <a href="https://docs.jellyfin.org/general/contributing/index.html">our documentation for guidelines</a>.</em> -</p> -<p align="center"> -<strong>New idea or improvement?</strong> -<em>Check out our <a href="https://features.jellyfin.org/?view=most-wanted">feature request hub</a>.</em> -</p> -<p align="center"> -<strong>Something not working right?</strong> -<em>Open an <a href="https://docs.jellyfin.org/general/contributing/issues.html">Issue</a>.</em> -</p> +<strong>Something not working right?</strong><br/> +Open an <a href="https://docs.jellyfin.org/general/contributing/issues.html">Issue</a> on GitHub.<br/> + +<strong>Want to contribute?</strong><br/> +Check out <a href="https://docs.jellyfin.org/general/contributing/index.html">our documentation for guidelines</a>.<br/> + +<strong>New idea or improvement?</strong><br/> +Check out our <a href="https://features.jellyfin.org/?view=most-wanted">feature request hub</a>.<br/> + +Most of the translations can be found in the web client but we have several other clients that have missing strings. Translations can be improved very easily from our <a href="https://translate.jellyfin.org/projects/jellyfin/jellyfin-core">Weblate</a> instance. Look through the following graphic to see if your native language could use some work! + +<a href="https://translate.jellyfin.org/engage/jellyfin/?utm_source=widget"> +<img src="https://translate.jellyfin.org/widgets/jellyfin/-/jellyfin-web/multi-auto.svg" alt="Detailed Translation Status"/> +</a> diff --git a/deployment/debian-package-arm64/Dockerfile.amd64 b/deployment/debian-package-arm64/Dockerfile.amd64 index 7a674d029..069c2ed35 100644 --- a/deployment/debian-package-arm64/Dockerfile.amd64 +++ b/deployment/debian-package-arm64/Dockerfile.amd64 @@ -3,7 +3,7 @@ FROM debian:10 ARG SOURCE_DIR=/jellyfin ARG PLATFORM_DIR=/jellyfin/deployment/debian-package-arm64 ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=2.2 +ARG SDK_VERSION=3.0 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -12,11 +12,11 @@ ENV ARCH=amd64 # Prepare Debian build environment RUN apt-get update \ - && apt-get install -y apt-transport-https debhelper gnupg wget npm devscripts mmv + && apt-get install -y apt-transport-https debhelper gnupg wget npm devscripts mmv # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/228832ea-805f-45ab-8c88-fa36165701b9/16ce29a06031eeb09058dee94d6f5330/dotnet-sdk-2.2.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/4f51cfd8-311d-43fe-a887-c80b40358cfd/440d10dc2091b8d0f1a12b7124034e49/dotnet-sdk-3.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/debian-package-arm64/Dockerfile.arm64 b/deployment/debian-package-arm64/Dockerfile.arm64 index 2b43d70ac..d2e1c1f12 100644 --- a/deployment/debian-package-arm64/Dockerfile.arm64 +++ b/deployment/debian-package-arm64/Dockerfile.arm64 @@ -3,7 +3,7 @@ FROM debian:10 ARG SOURCE_DIR=/jellyfin ARG PLATFORM_DIR=/jellyfin/deployment/debian-package-arm64 ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=2.2 +ARG SDK_VERSION=3.0 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/1560f31a-d566-4de0-9fef-1a40b2b2a748/163f23fb8018e064034f3492f54358f1/dotnet-sdk-2.2.401-linux-arm64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/89fb60b1-3359-414e-94cf-359f57f37c7c/256e6dac8f44f9bad01f23f9a27b01ee/dotnet-sdk-3.0.101-linux-arm64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/debian-package-arm64/docker-build.sh b/deployment/debian-package-arm64/docker-build.sh index 1c75ece8e..b36b928ba 100755 --- a/deployment/debian-package-arm64/docker-build.sh +++ b/deployment/debian-package-arm64/docker-build.sh @@ -8,8 +8,8 @@ set -o xtrace # Move to source directory pushd ${SOURCE_DIR} -# Remove build-dep for dotnet-sdk-2.2, since it's not a package in this image -sed -i '/dotnet-sdk-2.2,/d' debian/control +# Remove build-dep for dotnet-sdk-3.0, since it's not a package in this image +sed -i '/dotnet-sdk-3.0,/d' debian/control # Build DEB export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} diff --git a/deployment/debian-package-armhf/Dockerfile.amd64 b/deployment/debian-package-armhf/Dockerfile.amd64 index 2f15d2fcd..d0afbed51 100644 --- a/deployment/debian-package-armhf/Dockerfile.amd64 +++ b/deployment/debian-package-armhf/Dockerfile.amd64 @@ -3,7 +3,7 @@ FROM debian:10 ARG SOURCE_DIR=/jellyfin ARG PLATFORM_DIR=/jellyfin/deployment/debian-package-armhf ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=2.2 +ARG SDK_VERSION=3.0 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -12,11 +12,11 @@ ENV ARCH=amd64 # Prepare Debian build environment RUN apt-get update \ - && apt-get install -y apt-transport-https debhelper gnupg wget npm devscripts mmv + && apt-get install -y apt-transport-https debhelper gnupg wget npm devscripts mmv # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/228832ea-805f-45ab-8c88-fa36165701b9/16ce29a06031eeb09058dee94d6f5330/dotnet-sdk-2.2.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/4f51cfd8-311d-43fe-a887-c80b40358cfd/440d10dc2091b8d0f1a12b7124034e49/dotnet-sdk-3.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/debian-package-armhf/Dockerfile.armhf b/deployment/debian-package-armhf/Dockerfile.armhf index 17a6fa0a1..dd9e3297e 100644 --- a/deployment/debian-package-armhf/Dockerfile.armhf +++ b/deployment/debian-package-armhf/Dockerfile.armhf @@ -3,7 +3,7 @@ FROM debian:10 ARG SOURCE_DIR=/jellyfin ARG PLATFORM_DIR=/jellyfin/deployment/debian-package-armhf ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=2.2 +ARG SDK_VERSION=3.0 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/3cb1d917-19cc-4399-9a53-03bb5de223f6/be3e011601610d9fe0a4f6b1962378ea/dotnet-sdk-2.2.401-linux-arm.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/0b30374c-3d52-45ad-b4e5-9a39d0bf5bf0/deb17f7b32968b3a2186650711456152/dotnet-sdk-3.0.101-linux-arm.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/debian-package-armhf/docker-build.sh b/deployment/debian-package-armhf/docker-build.sh index df35345bd..1b3af9a93 100755 --- a/deployment/debian-package-armhf/docker-build.sh +++ b/deployment/debian-package-armhf/docker-build.sh @@ -8,8 +8,8 @@ set -o xtrace # Move to source directory pushd ${SOURCE_DIR} -# Remove build-dep for dotnet-sdk-2.2, since it's not a package in this image -sed -i '/dotnet-sdk-2.2,/d' debian/control +# Remove build-dep for dotnet-sdk-3.0, since it's not a package in this image +sed -i '/dotnet-sdk-3.0,/d' debian/control # Build DEB export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} diff --git a/deployment/debian-package-x64/Dockerfile b/deployment/debian-package-x64/Dockerfile index 172bbe8fc..36e8cf322 100644 --- a/deployment/debian-package-x64/Dockerfile +++ b/deployment/debian-package-x64/Dockerfile @@ -3,7 +3,7 @@ FROM debian:10 ARG SOURCE_DIR=/jellyfin ARG PLATFORM_DIR=/jellyfin/deployment/debian-package-x64 ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=2.2 +ARG SDK_VERSION=3.0 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/228832ea-805f-45ab-8c88-fa36165701b9/16ce29a06031eeb09058dee94d6f5330/dotnet-sdk-2.2.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/4f51cfd8-311d-43fe-a887-c80b40358cfd/440d10dc2091b8d0f1a12b7124034e49/dotnet-sdk-3.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/debian-package-x64/docker-build.sh b/deployment/debian-package-x64/docker-build.sh index 9781879f6..bb27bc7ee 100755 --- a/deployment/debian-package-x64/docker-build.sh +++ b/deployment/debian-package-x64/docker-build.sh @@ -8,8 +8,8 @@ set -o xtrace # Move to source directory pushd ${SOURCE_DIR} -# Remove build-dep for dotnet-sdk-2.2, since it's not a package in this image -sed -i '/dotnet-sdk-2.2,/d' debian/control +# Remove build-dep for dotnet-sdk-3.0, since it's not a package in this image +sed -i '/dotnet-sdk-3.0,/d' debian/control # Build DEB dpkg-buildpackage -us -uc diff --git a/deployment/debian-package-x64/pkg-src/control b/deployment/debian-package-x64/pkg-src/control index e8c9d2e23..07e82069f 100644 --- a/deployment/debian-package-x64/pkg-src/control +++ b/deployment/debian-package-x64/pkg-src/control @@ -3,7 +3,7 @@ Section: misc Priority: optional Maintainer: Jellyfin Team <team@jellyfin.org> Build-Depends: debhelper (>= 9), - dotnet-sdk-2.2, + dotnet-sdk-3.0, libc6-dev, libcurl4-openssl-dev, libfontconfig1-dev, diff --git a/deployment/linux-x64/Dockerfile b/deployment/linux-x64/Dockerfile index d634b55de..169d07a57 100644 --- a/deployment/linux-x64/Dockerfile +++ b/deployment/linux-x64/Dockerfile @@ -3,7 +3,7 @@ FROM debian:10 ARG SOURCE_DIR=/jellyfin ARG PLATFORM_DIR=/jellyfin/deployment/linux-x64 ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=2.2 +ARG SDK_VERSION=3.0 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/228832ea-805f-45ab-8c88-fa36165701b9/16ce29a06031eeb09058dee94d6f5330/dotnet-sdk-2.2.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/4f51cfd8-311d-43fe-a887-c80b40358cfd/440d10dc2091b8d0f1a12b7124034e49/dotnet-sdk-3.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/macos/Dockerfile b/deployment/macos/Dockerfile index 406a2d853..c8b4e80bf 100644 --- a/deployment/macos/Dockerfile +++ b/deployment/macos/Dockerfile @@ -3,7 +3,7 @@ FROM debian:10 ARG SOURCE_DIR=/jellyfin ARG PLATFORM_DIR=/jellyfin/deployment/macos ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=2.2 +ARG SDK_VERSION=3.0 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/228832ea-805f-45ab-8c88-fa36165701b9/16ce29a06031eeb09058dee94d6f5330/dotnet-sdk-2.2.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/4f51cfd8-311d-43fe-a887-c80b40358cfd/440d10dc2091b8d0f1a12b7124034e49/dotnet-sdk-3.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/portable/Dockerfile b/deployment/portable/Dockerfile index bdbf978fe..17297a298 100644 --- a/deployment/portable/Dockerfile +++ b/deployment/portable/Dockerfile @@ -3,7 +3,7 @@ FROM debian:10 ARG SOURCE_DIR=/jellyfin ARG PLATFORM_DIR=/jellyfin/deployment/portable ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=2.2 +ARG SDK_VERSION=3.0 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/228832ea-805f-45ab-8c88-fa36165701b9/16ce29a06031eeb09058dee94d6f5330/dotnet-sdk-2.2.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/4f51cfd8-311d-43fe-a887-c80b40358cfd/440d10dc2091b8d0f1a12b7124034e49/dotnet-sdk-3.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/ubuntu-package-arm64/Dockerfile.amd64 b/deployment/ubuntu-package-arm64/Dockerfile.amd64 index 44e67a406..fac00ffea 100644 --- a/deployment/ubuntu-package-arm64/Dockerfile.amd64 +++ b/deployment/ubuntu-package-arm64/Dockerfile.amd64 @@ -3,7 +3,7 @@ FROM ubuntu:bionic ARG SOURCE_DIR=/jellyfin ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-arm64 ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=2.2 +ARG SDK_VERSION=3.0 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -12,11 +12,11 @@ ENV ARCH=amd64 # Prepare Debian build environment RUN apt-get update \ - && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv + && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/69937b49-a877-4ced-81e6-286620b390ab/8ab938cf6f5e83b2221630354160ef21/dotnet-sdk-2.2.104-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/4f51cfd8-311d-43fe-a887-c80b40358cfd/440d10dc2091b8d0f1a12b7124034e49/dotnet-sdk-3.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/ubuntu-package-arm64/Dockerfile.arm64 b/deployment/ubuntu-package-arm64/Dockerfile.arm64 index 58f3d3aaf..304cd0efd 100644 --- a/deployment/ubuntu-package-arm64/Dockerfile.arm64 +++ b/deployment/ubuntu-package-arm64/Dockerfile.arm64 @@ -3,7 +3,7 @@ FROM ubuntu:bionic ARG SOURCE_DIR=/jellyfin ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-arm64 ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=2.2 +ARG SDK_VERSION=3.0 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/d9f37b73-df8d-4dfa-a905-b7648d3401d0/6312573ac13d7a8ddc16e4058f7d7dc5/dotnet-sdk-2.2.104-linux-arm.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/89fb60b1-3359-414e-94cf-359f57f37c7c/256e6dac8f44f9bad01f23f9a27b01ee/dotnet-sdk-3.0.101-linux-arm64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/ubuntu-package-arm64/docker-build.sh b/deployment/ubuntu-package-arm64/docker-build.sh index 1c75ece8e..b36b928ba 100755 --- a/deployment/ubuntu-package-arm64/docker-build.sh +++ b/deployment/ubuntu-package-arm64/docker-build.sh @@ -8,8 +8,8 @@ set -o xtrace # Move to source directory pushd ${SOURCE_DIR} -# Remove build-dep for dotnet-sdk-2.2, since it's not a package in this image -sed -i '/dotnet-sdk-2.2,/d' debian/control +# Remove build-dep for dotnet-sdk-3.0, since it's not a package in this image +sed -i '/dotnet-sdk-3.0,/d' debian/control # Build DEB export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} diff --git a/deployment/ubuntu-package-armhf/Dockerfile.amd64 b/deployment/ubuntu-package-armhf/Dockerfile.amd64 index d69a75b50..3c6053775 100644 --- a/deployment/ubuntu-package-armhf/Dockerfile.amd64 +++ b/deployment/ubuntu-package-armhf/Dockerfile.amd64 @@ -3,7 +3,7 @@ FROM ubuntu:bionic ARG SOURCE_DIR=/jellyfin ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-armhf ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=2.2 +ARG SDK_VERSION=3.0 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -12,11 +12,11 @@ ENV ARCH=amd64 # Prepare Debian build environment RUN apt-get update \ - && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv + && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/69937b49-a877-4ced-81e6-286620b390ab/8ab938cf6f5e83b2221630354160ef21/dotnet-sdk-2.2.104-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/4f51cfd8-311d-43fe-a887-c80b40358cfd/440d10dc2091b8d0f1a12b7124034e49/dotnet-sdk-3.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/ubuntu-package-armhf/Dockerfile.armhf b/deployment/ubuntu-package-armhf/Dockerfile.armhf index 5d1025080..1d019bf2d 100644 --- a/deployment/ubuntu-package-armhf/Dockerfile.armhf +++ b/deployment/ubuntu-package-armhf/Dockerfile.armhf @@ -3,7 +3,7 @@ FROM ubuntu:bionic ARG SOURCE_DIR=/jellyfin ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-armhf ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=2.2 +ARG SDK_VERSION=3.0 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/d9f37b73-df8d-4dfa-a905-b7648d3401d0/6312573ac13d7a8ddc16e4058f7d7dc5/dotnet-sdk-2.2.104-linux-arm.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/0b30374c-3d52-45ad-b4e5-9a39d0bf5bf0/deb17f7b32968b3a2186650711456152/dotnet-sdk-3.0.101-linux-arm.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/ubuntu-package-armhf/docker-build.sh b/deployment/ubuntu-package-armhf/docker-build.sh index df35345bd..1b3af9a93 100755 --- a/deployment/ubuntu-package-armhf/docker-build.sh +++ b/deployment/ubuntu-package-armhf/docker-build.sh @@ -8,8 +8,8 @@ set -o xtrace # Move to source directory pushd ${SOURCE_DIR} -# Remove build-dep for dotnet-sdk-2.2, since it's not a package in this image -sed -i '/dotnet-sdk-2.2,/d' debian/control +# Remove build-dep for dotnet-sdk-3.0, since it's not a package in this image +sed -i '/dotnet-sdk-3.0,/d' debian/control # Build DEB export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} diff --git a/deployment/ubuntu-package-x64/docker-build.sh b/deployment/ubuntu-package-x64/docker-build.sh index 9781879f6..bb27bc7ee 100755 --- a/deployment/ubuntu-package-x64/docker-build.sh +++ b/deployment/ubuntu-package-x64/docker-build.sh @@ -8,8 +8,8 @@ set -o xtrace # Move to source directory pushd ${SOURCE_DIR} -# Remove build-dep for dotnet-sdk-2.2, since it's not a package in this image -sed -i '/dotnet-sdk-2.2,/d' debian/control +# Remove build-dep for dotnet-sdk-3.0, since it's not a package in this image +sed -i '/dotnet-sdk-3.0,/d' debian/control # Build DEB dpkg-buildpackage -us -uc diff --git a/deployment/win-x64/Dockerfile b/deployment/win-x64/Dockerfile index 7f64c7dae..0f85a07d8 100644 --- a/deployment/win-x64/Dockerfile +++ b/deployment/win-x64/Dockerfile @@ -3,7 +3,7 @@ FROM debian:10 ARG SOURCE_DIR=/jellyfin ARG PLATFORM_DIR=/jellyfin/deployment/win-x64 ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=2.2 +ARG SDK_VERSION=3.0 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/228832ea-805f-45ab-8c88-fa36165701b9/16ce29a06031eeb09058dee94d6f5330/dotnet-sdk-2.2.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/4f51cfd8-311d-43fe-a887-c80b40358cfd/440d10dc2091b8d0f1a12b7124034e49/dotnet-sdk-3.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/win-x86/Dockerfile b/deployment/win-x86/Dockerfile index fb5f5d6b6..f07a8d7fe 100644 --- a/deployment/win-x86/Dockerfile +++ b/deployment/win-x86/Dockerfile @@ -3,7 +3,7 @@ FROM debian:10 ARG SOURCE_DIR=/jellyfin ARG PLATFORM_DIR=/jellyfin/deployment/win-x86 ARG ARTIFACT_DIR=/dist -ARG SDK_VERSION=2.2 +ARG SDK_VERSION=3.0 # Docker run environment ENV SOURCE_DIR=/jellyfin ENV ARTIFACT_DIR=/dist @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/228832ea-805f-45ab-8c88-fa36165701b9/16ce29a06031eeb09058dee94d6f5330/dotnet-sdk-2.2.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/4f51cfd8-311d-43fe-a887-c80b40358cfd/440d10dc2091b8d0f1a12b7124034e49/dotnet-sdk-3.0.101-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 768e6dad6..75b5573b6 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -6,6 +6,8 @@ <!-- disable warning SA1204: Static members must appear before non-static members --> <Rule Id="SA1204" Action="Info" /> + <!-- disable warning SA1009: Closing parenthesis should be followed by a space. --> + <Rule Id="SA1009" Action="None" /> <!-- disable warning SA1101: Prefix local calls with 'this.' --> <Rule Id="SA1101" Action="None" /> <!-- disable warning SA1108: Block statements should not contain embedded comments --> |
