aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ci/azure-pipelines.yml4
-rw-r--r--Emby.Dlna/Api/DlnaServerService.cs14
-rw-r--r--Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs4
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs124
-rw-r--r--Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs33
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj3
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpListenerHost.cs3
-rw-r--r--Emby.Server.Implementations/HttpServer/Security/AuthService.cs17
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs7
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs5
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs12
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs4
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs5
-rw-r--r--Emby.Server.Implementations/Localization/Core/af.json1
-rw-r--r--Emby.Server.Implementations/Localization/Core/de.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/en-US.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/tr.json110
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-HK.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-TW.json2
-rw-r--r--Emby.Server.Implementations/Networking/NetworkManager.cs40
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs22
-rw-r--r--Emby.Server.Implementations/ServerApplicationPaths.cs34
-rw-r--r--Emby.Server.Implementations/Session/SessionWebSocketListener.cs4
-rw-r--r--Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs45
-rw-r--r--Jellyfin.Api/Auth/CustomAuthenticationHandler.cs68
-rw-r--r--Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs43
-rw-r--r--Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedRequirement.cs11
-rw-r--r--Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs23
-rw-r--r--Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationRequirement.cs11
-rw-r--r--Jellyfin.Api/BaseJellyfinApiController.cs13
-rw-r--r--Jellyfin.Api/Constants/AuthenticationSchemes.cs13
-rw-r--r--Jellyfin.Api/Constants/Policies.cs18
-rw-r--r--Jellyfin.Api/Constants/UserRoles.cs23
-rw-r--r--Jellyfin.Api/Controllers/StartupController.cs127
-rw-r--r--Jellyfin.Api/Jellyfin.Api.csproj32
-rw-r--r--Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs23
-rw-r--r--Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs18
-rw-r--r--Jellyfin.Api/MvcRoutePrefix.cs56
-rw-r--r--Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs27
-rw-r--r--Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs90
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj6
-rw-r--r--Jellyfin.Server/Program.cs104
-rw-r--r--Jellyfin.Server/Resources/Configuration/logging.json3
-rw-r--r--Jellyfin.Server/Startup.cs81
-rw-r--r--Jellyfin.Server/StartupOptions.cs18
-rw-r--r--MediaBrowser.Api/ApiEntryPoint.cs8
-rw-r--r--MediaBrowser.Api/BaseApiService.cs10
-rw-r--r--MediaBrowser.Api/EnvironmentService.cs36
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs7
-rw-r--r--MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs13
-rw-r--r--MediaBrowser.Api/StartupWizardService.cs135
-rw-r--r--MediaBrowser.Common/Configuration/EncodingConfigurationExtensions.cs28
-rw-r--r--MediaBrowser.Common/Net/INetworkManager.cs15
-rw-r--r--MediaBrowser.Controller/IServerApplicationHost.cs5
-rw-r--r--MediaBrowser.Controller/IServerApplicationPaths.cs8
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs17
-rw-r--r--MediaBrowser.Controller/Net/IAuthService.cs3
-rw-r--r--MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs30
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs4
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs3
-rw-r--r--MediaBrowser.sln11
-rw-r--r--README.md80
-rw-r--r--deployment/debian-package-arm64/Dockerfile.amd646
-rw-r--r--deployment/debian-package-arm64/Dockerfile.arm644
-rwxr-xr-xdeployment/debian-package-arm64/docker-build.sh4
-rw-r--r--deployment/debian-package-armhf/Dockerfile.amd646
-rw-r--r--deployment/debian-package-armhf/Dockerfile.armhf4
-rwxr-xr-xdeployment/debian-package-armhf/docker-build.sh4
-rw-r--r--deployment/debian-package-x64/Dockerfile4
-rwxr-xr-xdeployment/debian-package-x64/docker-build.sh4
-rw-r--r--deployment/debian-package-x64/pkg-src/control2
-rw-r--r--deployment/linux-x64/Dockerfile4
-rw-r--r--deployment/macos/Dockerfile4
-rw-r--r--deployment/portable/Dockerfile4
-rw-r--r--deployment/ubuntu-package-arm64/Dockerfile.amd646
-rw-r--r--deployment/ubuntu-package-arm64/Dockerfile.arm644
-rwxr-xr-xdeployment/ubuntu-package-arm64/docker-build.sh4
-rw-r--r--deployment/ubuntu-package-armhf/Dockerfile.amd646
-rw-r--r--deployment/ubuntu-package-armhf/Dockerfile.armhf4
-rwxr-xr-xdeployment/ubuntu-package-armhf/docker-build.sh4
-rwxr-xr-xdeployment/ubuntu-package-x64/docker-build.sh4
-rw-r--r--deployment/win-x64/Dockerfile4
-rw-r--r--deployment/win-x86/Dockerfile4
-rw-r--r--jellyfin.ruleset2
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
diff --git a/README.md b/README.md
index 07d49fc51..bbac4dd25 100644
--- a/README.md
+++ b/README.md
@@ -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 -->